User prompt
try again, player walks behind background assets still. place forest platforms atop their place within the world background asset. see no doors or stairs ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
stairs and stairs2 need to be in the front most layer where platforms are. buildings should have platforms at their base so they dont appear to spring from nowhere. buildings and signs need to be in front of backgrounds, behind platforms and clouds. make sure all background assets are implemented in their respective biomes, along with their respective platforms. platforms need to continue spawning upward and downward along with stairs ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'Error: The supplied index is out of bounds' in or related to this line: 'worldContainer.setChildIndex(bloodmage, targetIndex);' Line Number: 5567
User prompt
Please fix the bug: 'Error: The supplied index is out of bounds' in or related to this line: 'worldContainer.setChildIndex(bloodmage, targetIndex);' Line Number: 5567
User prompt
player still moves behind background image. move stairs to foreground as well. characters standing on moving platforms should move with platform. platform4 should always be a moving platform. buildings should be in front of background, behind platforms snd players. neonsign assets should be in fromt of buildings, behind characters and platforms ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
please try again
User prompt
Player is still walking behind background assets as well as moving behind platforms. Player should always be layered in front of backgrounds, and walk in front of platforms, until jumping and standing on one. the assets meant to be in the sky to the north, assets representing space, appear in the forest biome. Forest assets to the East of city assets, cosmic assets to the north of city, sea assets to the west, underground assets to the south. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
When traveling East, player begins walking behind background assets (player should always be in front of backgrounds), and there’s cosmic assets appearing, which should only be in the sky to the north. The East should contain the various forest backgrounds and forest platforms. Move the cosmic assets to the north (I do like the spinning animation, though the asset is giant) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Platforms still overlap. They still only cover a narrow band, and stop appearing altogether when moving vertically up or down. Cosmic assets are spinning to the south with underground background assets should be layered to conceal the bottom edge of city assets. Platforms should be strategically and logically placed and sized. Some background assets never end and some are tiny. Ocean assets should be layered for background to the west. Game world shouldn’t cease in any of the cardinal directions ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Way too many platforms packed way too tightly together. They should never overlap. They should extend in all directions, vertically and horizontally. They should continue endlessly. Use bubbles as environmental effect foreground animation when in ocean biome. Player should never move behind background assets. Cosmic assets and effects should only appear to the north. Cave and underbg assets to the south. Forest assets to east, with forest platforms. Environments should extend endlessly ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Way too many platforms packed way too tightly. Platforms. Should extend both upwards and downwards. Player should never walking behind background assets. Implement bubbles in ocean areas as animated environmental effect. Use caveoverlay for underground areas ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Background assets need to continue off the bottom edge of the screen. Player should be able to travel south, downward, and see environments, not black empty space. To the western edge, use sea sea2 sea3 sea4 sea5 and sea6 to the southern edge use cave and cavern themed assets. To the eastern edge use forest themed assets and platforms. To the northern edge use cosmic and galaxy themed background assets. Layer these to conceal edges, partial opacity, with building layers of background effects and animated assets. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Clean up the coding errors preventing me from loading the game, check for any other potential issues or ways to make the game run more efficiently and smoothly
User prompt
Please fix the bug: 'Can't find variable: chunkStartX' in or related to this line: 'var startX = chunkStartX;' Line Number: 3189
User prompt
Please fix the bug: 'Can't find variable: chunkStartX' in or related to this line: 'var startX = chunkStartX;' Line Number: 3188
User prompt
Please fix the bug: 'Can't find variable: startX' in or related to this line: 'if (startX < -1000) {' Line Number: 3186
User prompt
Please fix the bug: 'Can't find variable: startX' in or related to this line: 'if (startX < -1000) {' Line Number: 3185
User prompt
To the left side of the screen, platforms should continue but gradually transitioning from platform platform2 and platform3 to use seaplatform and seaplatform2 along with layering sea - sea6 as background assets. Platforms should extend both horizontally and vertically—make sure any coding duplicates or errors are ironed out before i test so we don’t endlessly loop errors consuming all of my daily credits
User prompt
Please fix the bug: 'Can't find variable: startX' in or related to this line: 'if (startX < -1000) {' Line Number: 3187
User prompt
Please fix the bug: 'Can't find variable: startX' in or related to this line: 'if (startX < -1000) {' Line Number: 3185
User prompt
To the left side of the screen, platforms should continue but gradually transitioning from platform platform2 and platform3 to use seaplatform and seaplatform2 along with layering sea - sea6 as background assets. Platforms should extend both horizontally and vertically
User prompt
Implement prowl - prowl20 as walk animation (it’s directionally facing left and should playing in looping forward, then rewind, then forward loop facing the direction indicated) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
I can no longer see player character when I was trying to prevent multiple of identical npcs from appearing onscreen simultaneously.
User prompt
I’m unable to see the opening cutscene. Also, make sure that platforms, stairs, and doors aren’t overlapping each other, that characters aren’t appearing onscreen visibly simultaneously (no clones, no visible character repetition)
User prompt
Use expositionbox for exposition and dialoguebox for speech and rooftop for background to add an opening cutscene, animated, based on the following script: [expositionbox fades in, followed by the text fading into expositionbox] In the heart of Nocturneon City, where dazzling holograms conceal grim realities, an ancient legend, long forgotten, awakes . Chase Æros, a Bloodmage with a Wand of Want, seeks to turn the tide against the corporate occult. His weapon? Charm. His army? The beasts of his Brostiary. Chase : Time to shake up this dystopian disaster. One hunk at a time! (Chase leaps from a rooftop, wand glowing, as neon sigils flare around him.) /when action occurs like in brackets or parentheses, don’t show that text, but rather animate the action being described
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BattleUI = Container.expand(function () {
var self = Container.call(this);
var bg = self.attachAsset('battleBg', {
anchorX: 0.5,
anchorY: 0.5
});
bg.alpha = 0.9;
self.hunkDisplay = null;
self.resistanceBar = null;
self.resistanceFill = null;
self.spellButtons = [];
self.setup = function (hunk) {
self.hunkDisplay = self.addChild(new Container());
var hunkAssets = ['hunk', 'hunk2', 'hunk3', 'hunk4'];
// For animated hunks, we need to create an animated display
if (hunk.hunkType === 0) {
// Create animation frames for battle display - mushroom hunk
self.hunkIdleFrames = [];
self.currentBattleFrame = 0;
var frameAssets = ['hunk', 'hunka', 'hunkb', 'hunkc', 'hunkd', 'hunke'];
for (var i = 0; i < frameAssets.length; i++) {
var frame = self.hunkDisplay.attachAsset(frameAssets[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
alpha: i === 0 ? 1 : 0
});
self.hunkIdleFrames.push(frame);
}
// Start battle idle animation
self.startBattleIdleAnimation();
} else if (hunk.hunkType === 1) {
// Create animation frames for battle display - hunk2
self.hunkIdleFrames = [];
self.currentBattleFrame = 0;
var frameAssets = ['hunk2', 'hunk2a', 'hunk2b', 'hunk2c', 'hunk2d'];
for (var i = 0; i < frameAssets.length; i++) {
var frame = self.hunkDisplay.attachAsset(frameAssets[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
alpha: i === 0 ? 1 : 0
});
self.hunkIdleFrames.push(frame);
}
// Start battle idle animation
self.startBattleIdleAnimation();
} else if (hunk.hunkType === 2) {
// Create animation frames for battle display - hunk3
self.hunkIdleFrames = [];
self.currentBattleFrame = 0;
var frameAssets = ['hunk3', 'hunk3a', 'hunk3b', 'hunk3c', 'hunk3d'];
for (var i = 0; i < frameAssets.length; i++) {
var frame = self.hunkDisplay.attachAsset(frameAssets[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
alpha: i === 0 ? 1 : 0
});
self.hunkIdleFrames.push(frame);
}
// Start battle idle animation
self.startBattleIdleAnimation();
} else if (hunk.hunkType === 3) {
// Create animation frames for battle display - hunk4
self.hunkIdleFrames = [];
self.currentBattleFrame = 0;
var frameAssets = ['hunk4', 'hunk4a', 'hunk4b', 'hunk4c', 'hunk4d'];
for (var i = 0; i < frameAssets.length; i++) {
var frame = self.hunkDisplay.attachAsset(frameAssets[i], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
alpha: i === 0 ? 1 : 0
});
self.hunkIdleFrames.push(frame);
}
// Start battle idle animation
self.startBattleIdleAnimation();
} else {
// Normal static display for other hunks
var hunkGraphic = self.hunkDisplay.attachAsset(hunkAssets[hunk.hunkType], {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
}
self.hunkDisplay.y = -200;
var barBg = self.attachAsset('resistanceBar', {
anchorX: 0.5,
anchorY: 0.5,
y: 50
});
self.resistanceFill = self.attachAsset('resistanceFill', {
anchorX: 0,
anchorY: 0.5,
x: -150,
y: 50
});
self.updateResistance(hunk.resistance, hunk.maxResistance);
var spell1 = self.attachAsset('spellButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: 200
});
spell1.interactive = true;
self.spellButtons.push(spell1);
var spell1Text = new Text2('Hypnotize', {
size: 40,
fill: 0xFFFFFF
});
spell1Text.anchor.set(0.5, 0.5);
spell1.addChild(spell1Text);
var spell2 = self.attachAsset('spellButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 200
});
spell2.interactive = true;
self.spellButtons.push(spell2);
var spell2Text = new Text2('Charm', {
size: 40,
fill: 0xFFFFFF
});
spell2Text.anchor.set(0.5, 0.5);
spell2.addChild(spell2Text);
var captureBtn = self.attachAsset('crystal', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 350,
scaleX: 1.5,
scaleY: 1.5
});
captureBtn.interactive = true;
self.captureButton = captureBtn;
var captureText = new Text2('Capture', {
size: 30,
fill: 0xFFFFFF
});
captureText.anchor.set(0.5, 0.5);
captureBtn.addChild(captureText);
};
self.updateResistance = function (current, max) {
if (self.resistanceFill) {
self.resistanceFill.scale.x = current / max;
}
};
self.animationDirection = 1; // 1 for forward, -1 for reverse
self.startBattleIdleAnimation = function () {
if (!self.hunkIdleFrames || self.hunkIdleFrames.length === 0) return;
var currentFrame = self.hunkIdleFrames[self.currentBattleFrame];
// Calculate next frame with direction
var nextFrameIndex = self.currentBattleFrame + self.animationDirection;
// Check if we need to reverse direction
if (nextFrameIndex >= self.hunkIdleFrames.length - 1) {
nextFrameIndex = self.hunkIdleFrames.length - 1;
self.animationDirection = -1;
} else if (nextFrameIndex <= 0) {
nextFrameIndex = 0;
self.animationDirection = 1;
}
var nextFrame = self.hunkIdleFrames[nextFrameIndex];
tween(currentFrame, {
alpha: 0
}, {
duration: 150,
easing: tween.easeInOut
});
tween(nextFrame, {
alpha: 1
}, {
duration: 150,
easing: tween.easeInOut
});
self.currentBattleFrame = nextFrameIndex;
self.battleAnimationTimer = LK.setTimeout(function () {
self.startBattleIdleAnimation();
}, 200);
};
self.cleanup = function () {
if (self.battleAnimationTimer) {
LK.clearTimeout(self.battleAnimationTimer);
}
// Clean up dialogue boxes if they exist
if (self.playerDialogueBox) {
self.playerDialogueBox.destroy();
}
if (self.hunkDialogueBox) {
self.hunkDialogueBox.destroy();
}
// Clean up combat menu elements
if (self.combatMenu) {
self.combatMenu.destroy();
}
if (self.cursor) {
self.cursor.destroy();
}
self.destroy();
};
return self;
});
var Bloodmage = Container.expand(function () {
var self = Container.call(this);
var body = self.attachAsset('bloodmage', {
anchorX: 0.5,
anchorY: 1.0
});
self.velocityX = 0;
self.velocityY = 0;
self.grounded = false;
self.doubleJumpAvailable = true;
self.speed = 14;
self.jumpPower = 25;
self.gravity = 1.2;
self.isMoving = false;
self.idleFrames = [];
self.walkFrames = [];
self.currentIdleFrame = 0;
self.currentWalkFrame = 0;
self.idleAnimationTimer = 0;
self.walkAnimationTimer = 0;
self.jumpFrames = [];
self.currentJumpFrame = 0;
self.jumpAnimationTimer = 0;
self.isJumping = false;
self.facingDirection = -1; // -1 for right (assets face left), 1 for left
self.lastJumpTime = 0;
self.jumpCount = 0;
// Create idle animation frames with consistent scaling
var baseIdleScale = 1.0; // Standard scale for idle frames
self.idleFrames.push(self.attachAsset('playeridle', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 1,
scaleX: baseIdleScale,
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle2', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 1.21,
// Adjust for narrower asset
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle3', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 1.04,
// Adjust for slightly narrower asset
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle4', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 0.96,
// Adjust for wider asset
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle5', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 1.05,
// Adjust for narrower asset
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle6', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 1.15,
scaleY: baseIdleScale
}));
// Add more idle animation frames for enhanced variety
self.idleFrames.push(self.attachAsset('playeridle9', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 1.08,
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle10', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 0.98,
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle11', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 1.13,
scaleY: baseIdleScale
}));
self.idleFrames.push(self.attachAsset('playeridle12', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseIdleScale * 1.02,
scaleY: baseIdleScale
}));
// Create walking animation frames with prowl assets using ping-pong method
var baseWalkScale = 1.0; // Match idle animation scale exactly
self.walkFrames.push(self.attachAsset('prowl', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl2', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl3', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl4', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl5', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl6', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl7', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl8', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl9', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl10', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl11', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl12', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl13', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl14', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl15', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl16', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl17', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl18', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl19', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('prowl20', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale,
scaleY: baseWalkScale
}));
// Create jump animation frames with consistent scaling
var baseJumpScale = 1.0; // Standard scale for jump frames
self.jumpFrames.push(self.attachAsset('playerjump', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseJumpScale * 0.60,
// Adjust for much wider asset
scaleY: baseJumpScale
}));
self.jumpFrames.push(self.attachAsset('playerjump2', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseJumpScale * 0.55,
// Adjust for much wider asset
scaleY: baseJumpScale
}));
self.jumpFrames.push(self.attachAsset('playerjump3', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseJumpScale * 0.52,
// Adjust for much wider asset
scaleY: baseJumpScale
}));
self.jumpFrames.push(self.attachAsset('playerjump4', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseJumpScale * 0.58,
// Adjust for much wider asset
scaleY: baseJumpScale
}));
self.jumpFrames.push(self.attachAsset('playerjump5', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseJumpScale * 0.56,
// Adjust for much wider asset
scaleY: baseJumpScale
}));
self.jumpFrames.push(self.attachAsset('playerjump6', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseJumpScale * 0.57,
// Adjust for much wider asset
scaleY: baseJumpScale
}));
// Create fly animation frames with consistent scaling
var baseFlyScale = 1.0;
self.flyFrames = [];
self.flyFrames.push(self.attachAsset('playerfly', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 0.99,
scaleY: baseFlyScale
}));
self.flyFrames.push(self.attachAsset('playerfly2', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 0.79,
scaleY: baseFlyScale
}));
self.flyFrames.push(self.attachAsset('playerfly3', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 1.06,
scaleY: baseFlyScale
}));
self.flyFrames.push(self.attachAsset('playerfly4', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 0.73,
scaleY: baseFlyScale
}));
self.flyFrames.push(self.attachAsset('playerfly5', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 0.76,
scaleY: baseFlyScale
}));
self.flyFrames.push(self.attachAsset('playerfly6', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 0.80,
scaleY: baseFlyScale
}));
// Create falling frames
self.fallFrames = [];
self.fallFrames.push(self.attachAsset('playerfly7', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 0.97,
scaleY: baseFlyScale
}));
self.fallFrames.push(self.attachAsset('playerfly8', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseFlyScale * 0.79,
scaleY: baseFlyScale
}));
self.flyAnimationDirection = 1;
self.currentFlyFrame = 0;
self.isFlying = false;
self.idleAnimationDirection = 1; // 1 for forward, -1 for reverse
// REBUILT ANIMATION SYSTEM: Clear hierarchy with no conflicts
self.currentAnimationState = 'idle'; // Track current state: idle, walk, jump, fly, fall
self.clearAllAnimations = function () {
// Clear ALL timers first
LK.clearTimeout(self.idleAnimationTimer);
LK.clearTimeout(self.walkAnimationTimer);
LK.clearTimeout(self.jumpAnimationTimer);
LK.clearTimeout(self.flyAnimationTimer);
// Hide ALL frames
body.alpha = 0;
for (var i = 0; i < self.idleFrames.length; i++) {
self.idleFrames[i].alpha = 0;
}
for (var i = 0; i < self.walkFrames.length; i++) {
self.walkFrames[i].alpha = 0;
}
for (var i = 0; i < self.jumpFrames.length; i++) {
self.jumpFrames[i].alpha = 0;
}
for (var i = 0; i < self.flyFrames.length; i++) {
self.flyFrames[i].alpha = 0;
}
for (var i = 0; i < self.fallFrames.length; i++) {
self.fallFrames[i].alpha = 0;
}
};
self.startIdleAnimation = function () {
// ONLY start if truly idle - no other states active
if (self.isMoving || self.isJumping || self.isFlying || self.currentAnimationState !== 'idle') return;
// Clear everything first
self.clearAllAnimations();
// Verify idle frames exist (playeridle to playeridle12)
if (!self.idleFrames || self.idleFrames.length < 6) return;
// Show current idle frame ONLY
self.idleFrames[self.currentIdleFrame].alpha = 1;
// Calculate next frame with ping-pong effect
var nextFrameIndex = self.currentIdleFrame + self.idleAnimationDirection;
// Reverse direction at boundaries for smooth ping-pong
if (nextFrameIndex >= self.idleFrames.length - 1) {
nextFrameIndex = self.idleFrames.length - 1;
self.idleAnimationDirection = -1;
} else if (nextFrameIndex <= 0) {
nextFrameIndex = 0;
self.idleAnimationDirection = 1;
}
var currentFrame = self.idleFrames[self.currentIdleFrame];
var nextFrame = self.idleFrames[nextFrameIndex];
// Smooth cross-fade transition
tween(currentFrame, {
alpha: 0
}, {
duration: 120,
easing: tween.easeInOut
});
tween(nextFrame, {
alpha: 1
}, {
duration: 120,
easing: tween.easeInOut
});
self.currentIdleFrame = nextFrameIndex;
// Continue ONLY if still in idle state
self.idleAnimationTimer = LK.setTimeout(function () {
if (self.currentAnimationState === 'idle' && !self.isMoving && !self.isJumping && !self.isFlying) {
self.startIdleAnimation();
}
}, 180);
};
self.stopIdleAnimation = function () {
LK.clearTimeout(self.idleAnimationTimer);
// Hide main body and idle frames
body.alpha = 0;
for (var i = 0; i < self.idleFrames.length; i++) {
self.idleFrames[i].alpha = 0;
}
// Only start walking if we're actually moving
if (self.isMoving) {
self.startWalkingAnimation();
}
};
self.walkAnimationDirection = 1; // 1 for forward, -1 for reverse
self.startWalkingAnimation = function () {
if (!self.isMoving) return;
// Hide idle frames and show current walk frame
for (var i = 0; i < self.idleFrames.length; i++) {
self.idleFrames[i].alpha = 0;
}
for (var i = 0; i < self.walkFrames.length; i++) {
self.walkFrames[i].alpha = i === self.currentWalkFrame ? 1 : 0;
}
// Update facing direction based on velocity
if (self.velocityX > 0) self.facingDirection = -1; // Moving right, but assets face left by default
else if (self.velocityX < 0) self.facingDirection = 1; // Moving left, matches asset default
// Apply direction to all frames - prowl assets face left, so flip for right movement
for (var i = 0; i < self.walkFrames.length; i++) {
self.walkFrames[i].scale.x = Math.abs(self.walkFrames[i].scale.x) * self.facingDirection;
}
// Smoothly fade out current frame and fade in next frame
var currentFrame = self.walkFrames[self.currentWalkFrame];
// Calculate next frame with ping-pong direction for smooth looping
var nextFrameIndex = self.currentWalkFrame + self.walkAnimationDirection;
// Check if we need to reverse direction for ping-pong effect
if (nextFrameIndex >= self.walkFrames.length - 1) {
nextFrameIndex = self.walkFrames.length - 1;
self.walkAnimationDirection = -1;
} else if (nextFrameIndex <= 0) {
nextFrameIndex = 0;
self.walkAnimationDirection = 1;
}
var nextFrame = self.walkFrames[nextFrameIndex];
// Smooth cross-fade transition between prowl frames
tween(currentFrame, {
alpha: 0
}, {
duration: 60,
easing: tween.easeInOut
});
tween(nextFrame, {
alpha: 1
}, {
duration: 60,
easing: tween.easeInOut
});
self.currentWalkFrame = nextFrameIndex;
// Schedule next frame change with proper timing for prowl animation
self.walkAnimationTimer = LK.setTimeout(function () {
if (self.isMoving) {
self.startWalkingAnimation();
}
}, 80); // Slightly slower for smoother prowl animation
};
self.stopWalkingAnimation = function () {
LK.clearTimeout(self.walkAnimationTimer);
// Hide walking frames
for (var i = 0; i < self.walkFrames.length; i++) {
self.walkFrames[i].alpha = 0;
}
};
self.startJumpAnimation = function () {
// Hide all other frames
body.alpha = 0;
for (var i = 0; i < self.idleFrames.length; i++) {
self.idleFrames[i].alpha = 0;
}
for (var i = 0; i < self.walkFrames.length; i++) {
self.walkFrames[i].alpha = 0;
}
// Apply direction to jump frames
for (var i = 0; i < self.jumpFrames.length; i++) {
self.jumpFrames[i].scale.x = Math.abs(self.jumpFrames[i].scale.x) * self.facingDirection;
}
// Use different frames based on jump type
var frameIndex = 0;
if (self.isFlying) {
// Flight mode uses playerjump3 (frame 2)
frameIndex = 2;
} else if (self.jumpCount === 1) {
// Double jump uses playerjump2 (frame 1)
frameIndex = 1;
} else {
// Regular jump uses playerjump (frame 0)
frameIndex = 0;
}
// Show appropriate jump frame
for (var i = 0; i < self.jumpFrames.length; i++) {
self.jumpFrames[i].alpha = i === frameIndex ? 1 : 0;
}
};
self.stopJumpAnimation = function () {
// Hide all jump frames
for (var i = 0; i < self.jumpFrames.length; i++) {
self.jumpFrames[i].alpha = 0;
}
};
self.startFlyAnimation = function () {
// Hide all other frames
body.alpha = 0;
for (var i = 0; i < self.idleFrames.length; i++) {
self.idleFrames[i].alpha = 0;
}
for (var i = 0; i < self.walkFrames.length; i++) {
self.walkFrames[i].alpha = 0;
}
for (var i = 0; i < self.jumpFrames.length; i++) {
self.jumpFrames[i].alpha = 0;
}
for (var i = 0; i < self.fallFrames.length; i++) {
self.fallFrames[i].alpha = 0;
}
// Apply direction to fly frames
for (var i = 0; i < self.flyFrames.length; i++) {
self.flyFrames[i].scale.x = Math.abs(self.flyFrames[i].scale.x) * self.facingDirection;
}
// Show current fly frame
for (var i = 0; i < self.flyFrames.length; i++) {
self.flyFrames[i].alpha = i === self.currentFlyFrame ? 1 : 0;
}
// Animate fly frames with ping-pong effect
var currentFrame = self.flyFrames[self.currentFlyFrame];
var nextFrameIndex = self.currentFlyFrame + self.flyAnimationDirection;
if (nextFrameIndex >= self.flyFrames.length - 1) {
nextFrameIndex = self.flyFrames.length - 1;
self.flyAnimationDirection = -1;
} else if (nextFrameIndex <= 0) {
nextFrameIndex = 0;
self.flyAnimationDirection = 1;
}
var nextFrame = self.flyFrames[nextFrameIndex];
tween(currentFrame, {
alpha: 0
}, {
duration: 80,
easing: tween.easeInOut
});
tween(nextFrame, {
alpha: 1
}, {
duration: 80,
easing: tween.easeInOut
});
self.currentFlyFrame = nextFrameIndex;
if (self.isFlying) {
self.flyAnimationTimer = LK.setTimeout(function () {
self.startFlyAnimation();
}, 120);
}
};
self.stopFlyAnimation = function () {
LK.clearTimeout(self.flyAnimationTimer);
for (var i = 0; i < self.flyFrames.length; i++) {
self.flyFrames[i].alpha = 0;
}
};
self.startFallAnimation = function () {
// Hide fly frames
for (var i = 0; i < self.flyFrames.length; i++) {
self.flyFrames[i].alpha = 0;
}
// Apply direction and show fall frames
for (var i = 0; i < self.fallFrames.length; i++) {
self.fallFrames[i].scale.x = Math.abs(self.fallFrames[i].scale.x) * self.facingDirection;
}
// Alternate between fall frames
var frameIndex = Math.floor(Date.now() / 150) % self.fallFrames.length;
for (var i = 0; i < self.fallFrames.length; i++) {
self.fallFrames[i].alpha = i === frameIndex ? 1 : 0;
}
};
self.stopFallAnimation = function () {
for (var i = 0; i < self.fallFrames.length; i++) {
self.fallFrames[i].alpha = 0;
}
};
self.update = function () {
// REBUILT PHYSICS SYSTEM: Clear separation of flight and ground physics
// FLIGHT MODE: Pure trackpad control with zero drift
if (self.isFlying && jumpButtonHeld) {
self.currentAnimationState = 'fly';
// ABSOLUTE ZERO DRIFT: Force all velocities to zero
self.velocityX = 0;
self.velocityY = 0;
self.grounded = false;
// ONLY move if trackpad is pressed with valid input
if (trackpadPressed && trackpadAngle !== undefined) {
var moveSpeed = self.speed * 0.8;
var moveX = Math.cos(trackpadAngle) * moveSpeed;
var moveY = Math.sin(trackpadAngle) * moveSpeed;
// Apply movement directly - NO velocity interference
self.x += moveX;
self.y += moveY;
// Update facing direction
if (moveX > 0) self.facingDirection = -1;else if (moveX < 0) self.facingDirection = 1;
}
// If no trackpad input: COMPLETE STILLNESS (no drift at all)
// Start/continue flight animation
if (self.currentAnimationState === 'fly') {
self.startFlyAnimation();
}
// Skip ALL other physics
return;
}
// GROUND/AIR PHYSICS: Traditional system
if (!self.grounded && !self.isFlying) {
// Falling
self.velocityY += self.gravity;
self.velocityX *= 0.98; // Air resistance
self.currentAnimationState = 'fall';
} else if (self.grounded) {
// Ground physics
self.velocityY += self.gravity;
}
// Apply velocities
self.x += self.velocityX;
self.y += self.velocityY;
// REBUILT ANIMATION STATE MACHINE
var newState = 'idle';
self.isMoving = Math.abs(self.velocityX) > 0.1;
self.isJumping = !self.grounded && !self.isFlying && Math.abs(self.velocityY) > 2;
if (self.isFlying) {
newState = 'fly';
} else if (self.isJumping) {
if (self.velocityY > 5) {
newState = 'fall';
} else {
newState = 'jump';
}
} else if (self.isMoving) {
newState = 'walk';
} else {
newState = 'idle';
}
// ONLY change animation if state actually changed
if (newState !== self.currentAnimationState) {
self.currentAnimationState = newState;
// Clear all animations before starting new one
self.clearAllAnimations();
// Start appropriate animation
if (newState === 'idle') {
self.startIdleAnimation();
} else if (newState === 'walk') {
self.startWalkingAnimation();
} else if (newState === 'jump') {
self.startJumpAnimation();
} else if (newState === 'fly') {
self.startFlyAnimation();
} else if (newState === 'fall') {
self.startFallAnimation();
}
}
};
self.jump = function () {
var jumpExecuted = false;
if (self.grounded) {
// First jump from ground
self.velocityY = -self.jumpPower;
self.grounded = false;
self.doubleJumpAvailable = true; // Reset double jump availability when leaving ground
LK.getSound('jump').play();
jumpExecuted = true;
} else if (self.doubleJumpAvailable && !self.grounded) {
// Double jump in air - always use higher power
self.velocityY = -self.jumpPower * 1.3;
self.doubleJumpAvailable = false;
LK.getSound('jump').play();
jumpExecuted = true;
// Create spell2 effect at the base of the character
var spellEffect = worldContainer.attachAsset('spell2', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y - 10,
// Position at base of character (shoes level)
alpha: 0.8
});
// Animate the spell effect - fade in and out quickly
tween(spellEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
spellEffect.destroy();
}
});
}
return jumpExecuted;
};
return self;
});
var Building = Container.expand(function () {
var self = Container.call(this);
// Randomly choose between building assets
var buildingAsset = Math.random() > 0.5 ? 'building' : 'Building2';
var building = self.attachAsset(buildingAsset, {
anchorX: 0.5,
anchorY: 0.95
});
building.tint = 0x2a2a2a + Math.floor(Math.random() * 0x222222);
// Randomly choose between all neon sign assets including new ones
var neonAssets = ['neonSign', 'neonsign2', 'neonsign3', 'neonsign4'];
var neonAsset = neonAssets[Math.floor(Math.random() * neonAssets.length)];
var neonSign = self.attachAsset(neonAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: Math.random() * 200 - 100,
y: -building.height * 0.5 + Math.random() * 100
});
neonSign.alpha = 0.8;
// Apply random tint to neon signs for variety
neonSign.tint = [0xFF00FF, 0x00FFFF, 0xFFFF00, 0xFF0088, 0x88FF00][Math.floor(Math.random() * 5)];
// Add enhanced blinking and color changing for neonsign3 and neonsign4
if (neonAsset === 'neonsign3' || neonAsset === 'neonsign4') {
var blinkAndChangeColor = function blinkAndChangeColor() {
// Enhanced blinking effect
tween(neonSign, {
alpha: 0.2 + Math.random() * 0.3
}, {
duration: 200 + Math.random() * 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Change color during blink
currentColorIndex = (currentColorIndex + 1) % hologramColors.length;
neonSign.tint = hologramColors[currentColorIndex];
tween(neonSign, {
alpha: 0.8 + Math.random() * 0.2
}, {
duration: 300 + Math.random() * 200,
easing: tween.easeInOut
});
}
});
}; // Random blinking interval for each sign
var hologramColors = [0xFF00FF, 0x00FFFF, 0xFFFF00, 0xFF0088, 0x88FF00, 0xFF6600];
var currentColorIndex = Math.floor(Math.random() * hologramColors.length);
LK.setInterval(function () {
blinkAndChangeColor();
}, 1000 + Math.random() * 2000);
}
// Animate neon sign with smooth pulsing
var animDuration = 1500 + Math.random() * 1000;
var _animateNeonSign = function animateNeonSign() {
tween(neonSign, {
alpha: 0.3
}, {
duration: animDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(neonSign, {
alpha: 0.8
}, {
duration: animDuration,
easing: tween.easeInOut,
onFinish: _animateNeonSign
});
}
});
};
_animateNeonSign();
return self;
});
var Hunk = Container.expand(function () {
var self = Container.call(this);
// Randomly select one of the 4 hunk types
var hunkAssets = ['hunk', 'hunk2', 'hunk3', 'hunk4'];
self.hunkType = Math.floor(Math.random() * hunkAssets.length);
var body = self.attachAsset(hunkAssets[self.hunkType], {
anchorX: 0.5,
anchorY: 1.0
});
// Different resistance values for different hunk types
self.resistance = [100, 120, 150, 80][self.hunkType];
self.maxResistance = self.resistance;
self.captured = false;
self.idleFrames = [];
self.currentIdleFrame = 0;
self.idleAnimationTimer = 0;
self.idleAnimationDirection = 1; // 1 for forward, -1 for reverse
self.startIdleAnimation = function () {
if (self.hunkType !== 0 && self.hunkType !== 1 && self.hunkType !== 2 && self.hunkType !== 3 || self.captured) return;
// Get current and next frame
var currentFrame = self.idleFrames[self.currentIdleFrame];
// Calculate next frame with direction
var nextFrameIndex = self.currentIdleFrame + self.idleAnimationDirection;
// Check if we need to reverse direction
if (nextFrameIndex >= self.idleFrames.length - 1) {
nextFrameIndex = self.idleFrames.length - 1;
self.idleAnimationDirection = -1;
} else if (nextFrameIndex <= 0) {
nextFrameIndex = 0;
self.idleAnimationDirection = 1;
}
var nextFrame = self.idleFrames[nextFrameIndex];
// Smoothly fade between frames
tween(currentFrame, {
alpha: 0
}, {
duration: 150,
easing: tween.easeInOut
});
tween(nextFrame, {
alpha: 1
}, {
duration: 150,
easing: tween.easeInOut
});
self.currentIdleFrame = nextFrameIndex;
// Schedule next frame change
self.idleAnimationTime = LK.setTimeout(function () {
if (!self.captured && (self.hunkType === 0 || self.hunkType === 1 || self.hunkType === 2 || self.hunkType === 3)) {
self.startIdleAnimation();
}
}, 200);
};
// If this is hunk (index 0), set up idle animation with hunk and hunka-e
if (self.hunkType === 0) {
// Hide main body for animated hunk as we'll use animation frames
body.alpha = 0;
// Create idle animation frames for mushroom hunk with consistent scaling
var baseHunkScale = 1.0;
self.idleFrames.push(self.attachAsset('hunk', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 1,
scaleX: baseHunkScale,
scaleY: baseHunkScale
}));
self.idleFrames.push(self.attachAsset('hunka', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunkScale * 1.21,
// Adjust for narrower asset
scaleY: baseHunkScale
}));
self.idleFrames.push(self.attachAsset('hunkb', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunkScale * 1.21,
// Adjust for narrower asset
scaleY: baseHunkScale
}));
self.idleFrames.push(self.attachAsset('hunkc', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunkScale * 1.37,
// Adjust for much narrower asset
scaleY: baseHunkScale
}));
self.idleFrames.push(self.attachAsset('hunkd', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunkScale * 0.63,
// Adjust for wider asset
scaleY: baseHunkScale
}));
self.idleFrames.push(self.attachAsset('hunke', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunkScale * 1.07,
// Adjust for narrower asset
scaleY: baseHunkScale
}));
// Start idle animation
self.startIdleAnimation();
} else if (self.hunkType === 1) {
// Hide main body for animated hunk2 as we'll use animation frames
body.alpha = 0;
// Create idle animation frames for hunk2 with consistent scaling
var baseHunk2Scale = 1.0;
self.idleFrames.push(self.attachAsset('hunk2', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 1,
scaleX: baseHunk2Scale,
scaleY: baseHunk2Scale
}));
self.idleFrames.push(self.attachAsset('hunk2a', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk2Scale * 1.03,
// Adjust for slightly narrower asset
scaleY: baseHunk2Scale
}));
self.idleFrames.push(self.attachAsset('hunk2b', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk2Scale * 1.10,
// Adjust for narrower asset
scaleY: baseHunk2Scale
}));
self.idleFrames.push(self.attachAsset('hunk2c', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk2Scale * 1.35,
// Adjust for much narrower asset
scaleY: baseHunk2Scale
}));
self.idleFrames.push(self.attachAsset('hunk2d', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk2Scale * 0.98,
// Adjust for slightly wider asset
scaleY: baseHunk2Scale
}));
// Start idle animation
self.startIdleAnimation();
} else if (self.hunkType === 2) {
// Hide main body for animated hunk3 as we'll use animation frames
body.alpha = 0;
// Create idle animation frames for hunk3 with consistent scaling
var baseHunk3Scale = 1.0;
self.idleFrames.push(self.attachAsset('hunk3', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 1,
scaleX: baseHunk3Scale,
scaleY: baseHunk3Scale * 0.99
}));
self.idleFrames.push(self.attachAsset('hunk3a', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
//{7B}
scaleX: baseHunk3Scale * 1.04,
// Adjust for narrower asset
scaleY: baseHunk3Scale
}));
self.idleFrames.push(self.attachAsset('hunk3b', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk3Scale * 1.07,
// Adjust for narrower asset
scaleY: baseHunk3Scale
}));
self.idleFrames.push(self.attachAsset('hunk3c', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk3Scale * 1.04,
// Adjust for narrower asset
scaleY: baseHunk3Scale
}));
self.idleFrames.push(self.attachAsset('hunk3d', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk3Scale * 0.99,
// Adjust for slightly wider asset
scaleY: baseHunk3Scale
}));
// Start idle animation
self.startIdleAnimation();
} else if (self.hunkType === 3) {
// Hide main body for animated hunk4 as we'll use animation frames
body.alpha = 0;
// Create idle animation frames for hunk4 with consistent scaling
var baseHunk4Scale = 1.0;
self.idleFrames.push(self.attachAsset('hunk4', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 1,
scaleX: baseHunk4Scale,
scaleY: baseHunk4Scale
}));
self.idleFrames.push(self.attachAsset('hunk4a', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk4Scale * 1.26,
// Adjust for narrower asset
scaleY: baseHunk4Scale
}));
self.idleFrames.push(self.attachAsset('hunk4b', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk4Scale * 1.12,
// Adjust for narrower asset
scaleY: baseHunk4Scale
}));
self.idleFrames.push(self.attachAsset('hunk4c', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk4Scale * 1.18,
// Adjust for narrower asset
scaleY: baseHunk4Scale
}));
self.idleFrames.push(self.attachAsset('hunk4d', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseHunk4Scale * 1.21,
// Adjust for narrower asset
scaleY: baseHunk4Scale
}));
// Start idle animation
self.startIdleAnimation();
}
self.stopIdleAnimation = function () {
if (self.idleAnimationTimer) {
LK.clearTimeout(self.idleAnimationTimer);
self.idleAnimationTimer = 0;
}
// Hide all idle frames
for (var i = 0; i < self.idleFrames.length; i++) {
self.idleFrames[i].alpha = 0;
}
};
// Override destroy to clean up animation
var originalDestroy = self.destroy;
self.destroy = function () {
self.stopIdleAnimation();
originalDestroy.call(self);
};
return self;
});
var Platform = Container.expand(function () {
var self = Container.call(this);
// Always use platform4 for this class
var selectedPlatform = 'platform4';
var platform = self.attachAsset(selectedPlatform, {
anchorX: 0.5,
anchorY: 0.5
});
// Store platform type for reference
self.platformType = selectedPlatform;
return self;
});
var Stairs = Container.expand(function () {
var self = Container.call(this);
// Randomly choose between stairs assets
var stairsAsset = Math.random() > 0.5 ? 'Stairs' : 'Stairs2';
var stairs = self.attachAsset(stairsAsset, {
anchorX: 0.5,
anchorY: 1.0
});
// Store stairs type for reference
self.stairsType = stairsAsset;
// Store dimensions for collision detection
self.width = stairs.width;
self.height = stairs.height;
// Make stairs interactive for climbing
stairs.interactive = true;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
game.setBackgroundColor(0x0a0a0a);
// --- Opening Cutscene Implementation ---
function showOpeningCutscene() {
// Overlay container for cutscene
var cutsceneOverlay = game.addChild(new Container());
// 1. Rooftop background, fade in
var rooftopBg = cutsceneOverlay.attachAsset('rooftop', {
anchorX: 0.5,
anchorY: 1.0,
x: 1024,
y: 1800,
scaleX: 2.2,
scaleY: 2.2,
alpha: 0
});
tween(rooftopBg, {
alpha: 1
}, {
duration: 700,
easing: tween.easeInOut
});
// 2. Exposition box, fade in after rooftop
var expobox = cutsceneOverlay.attachAsset('expobox', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 600,
scaleX: 10,
scaleY: 5,
alpha: 0
});
LK.setTimeout(function () {
tween(expobox, {
alpha: 1
}, {
duration: 500,
easing: tween.easeInOut
});
}, 400);
// 3. Exposition text, fade in after expobox
var expositionText = new Text2("In the heart of Nocturneon City, where dazzling holograms conceal grim realities, an ancient legend, long forgotten, awakes. Chase Æros, a Bloodmage with a Wand of Want, seeks to turn the tide against the corporate occult. His weapon? Charm. His army? The beasts of his Brostiary.", {
size: 54,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 1600,
fontWeight: 'bold'
});
expositionText.anchor.set(0.5, 0.5);
expositionText.x = 1024;
expositionText.y = 600;
expositionText.alpha = 0;
LK.setTimeout(function () {
cutsceneOverlay.addChild(expositionText);
tween(expositionText, {
alpha: 1
}, {
duration: 900,
easing: tween.easeInOut
});
}, 900);
// 4. After a pause, fade out exposition text and box, then show Chase's speech
LK.setTimeout(function () {
tween(expositionText, {
alpha: 0
}, {
duration: 400,
easing: tween.easeInOut
});
tween(expobox, {
alpha: 0
}, {
duration: 400,
easing: tween.easeInOut
});
}, 4800);
// 5. Show dialoguebox for Chase's line, fade in
var dialogueBox, chaseText;
LK.setTimeout(function () {
dialogueBox = cutsceneOverlay.attachAsset('dialoguebox', {
anchorX: 0.5,
anchorY: 1.0,
x: 1024,
y: 1100,
scaleX: 8,
scaleY: 4,
alpha: 0
});
tween(dialogueBox, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut
});
chaseText = new Text2("Time to shake up this dystopian disaster. One hunk at a time!", {
size: 60,
fill: 0xFF00FF,
fontWeight: 'bold',
wordWrap: true,
wordWrapWidth: 1200
});
chaseText.anchor.set(0.5, 0.5);
chaseText.x = 1024;
chaseText.y = 1200;
chaseText.alpha = 0;
cutsceneOverlay.addChild(chaseText);
LK.setTimeout(function () {
tween(chaseText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeInOut
});
}, 200);
}, 5400);
// 6. Animate Chase leaping from rooftop, wand glowing, neon sigils flaring
LK.setTimeout(function () {
// Remove dialogue after a moment
tween(chaseText, {
alpha: 0
}, {
duration: 300,
easing: tween.easeInOut
});
tween(dialogueBox, {
alpha: 0
}, {
duration: 300,
easing: tween.easeInOut
});
// Animate Chase on rooftop
var chase = cutsceneOverlay.attachAsset('bloodmage', {
anchorX: 0.5,
anchorY: 1.0,
x: 1024,
y: 1450,
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
});
tween(chase, {
alpha: 1
}, {
duration: 300,
easing: tween.easeInOut
});
// Wand glow effect (spell2 asset)
var wandGlow = cutsceneOverlay.attachAsset('spell2', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024 + 60,
y: 1450 - 600,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.7
});
// Neon sigils (spell4 asset, animated)
var sigil = cutsceneOverlay.attachAsset('spell4', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1450 - 350,
scaleX: 0.7,
scaleY: 0.7,
alpha: 0.0
});
// Animate sigil flare in and out
tween(sigil, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 350,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(sigil, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 350,
easing: tween.easeIn
});
}
});
// Animate wand glow pulsing
function pulseGlow() {
tween(wandGlow, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wandGlow, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.7
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: pulseGlow
});
}
});
}
pulseGlow();
// Animate Chase leaping up and off the rooftop
LK.setTimeout(function () {
tween(chase, {
y: 900,
x: 1024 + 300,
alpha: 0.0
}, {
duration: 700,
easing: tween.easeIn,
onFinish: function onFinish() {
chase.destroy();
wandGlow.destroy();
sigil.destroy();
}
});
}, 400);
// Fade out rooftop background
LK.setTimeout(function () {
tween(rooftopBg, {
alpha: 0
}, {
duration: 600,
easing: tween.easeInOut
});
}, 900);
// Remove cutscene overlay and start game after all animations
LK.setTimeout(function () {
cutsceneOverlay.destroy();
}, 1600);
}, 7200);
}
// Execute cutscene immediately
showOpeningCutscene();
var bloodmage = null;
var platforms = [];
var buildings = [];
var hunks = [];
var clouds = [];
var currentHunk = null;
var battleUI = null;
// Removed inBattle - using proximity-based battle UI instead
var cameraX = 0;
var worldContainer = null;
var trackpadPressed = false;
var trackpadAngle = 0;
var capturedHunks = storage.capturedHunks || [];
var brostiary = storage.brostiary || {};
var bropageOverlay = null;
var bropageShowing = false;
var currentBropageIndex = 0;
var lastJumpTime = 0;
var doubleTapThreshold = 300; // milliseconds for double tap detection
var isFlying = false;
var flyFrames = [];
var currentFlyFrame = 0;
var flyAnimationTimer = 0;
var flyAnimationDirection = 1;
var jumpHoldStartTime = 0;
var jumpButtonHeld = false;
var lastGeneratedX = 0; // Track the rightmost generated position
var backgroundElements = []; // Track all background elements for cleanup
var spellProjectiles = []; // Track active spell projectiles
var isCasting = false; // Track if player is casting
var chunkStartX = 0; // Initialize chunk position tracker
worldContainer = game.addChild(new Container());
function generateLevelChunk(chunkStartX) {
// Ensure chunkStartX is defined with a fallback value
chunkStartX = chunkStartX || lastGeneratedX || 0;
var startX = chunkStartX;
// Track all generated platforms to prevent overlaps
var generatedPlatformPositions = [];
var minPlatformDistance = 800; // Minimum distance between platform centers
function isValidPlatformPosition(x, y) {
for (var i = 0; i < generatedPlatformPositions.length; i++) {
var existing = generatedPlatformPositions[i];
var distX = Math.abs(x - existing.x);
var distY = Math.abs(y - existing.y);
var distance = Math.sqrt(distX * distX + distY * distY);
if (distance < minPlatformDistance) {
return false;
}
}
return true;
}
function addPlatformPosition(x, y) {
generatedPlatformPositions.push({
x: x,
y: y
});
}
// === COMPREHENSIVE DIRECTIONAL BACKGROUND SYSTEM ===
// Northern Edge: Cosmic/Galaxy themed backgrounds - ONLY in northern biome (high Y coordinates, above city)
if (startX > 5000) {
for (var northLayer = 0; northLayer < 5; northLayer++) {
var cosmicAssets = ['Galaxybg', 'galaxy', 'galaxy2', 'galaxy3', 'galaxy4', 'galaxybg2', 'galaxybg3', 'galaxybg4'];
var cosmicAsset = cosmicAssets[northLayer % cosmicAssets.length];
var northBg = worldContainer.attachAsset(cosmicAsset, {
anchorX: 0.5,
anchorY: 1.0,
x: startX + northLayer * 900,
y: 700,
// Northern edge above city - extend upward only in north biome
scaleX: 2.8 + northLayer * 0.4,
scaleY: 2.8 + northLayer * 0.4
});
northBg.alpha = 0.4 + northLayer * 0.18;
northBg.tint = [0x9966FF, 0x66FFFF, 0xFF66FF, 0xFFFF66, 0xFF99FF][northLayer % 5];
backgroundElements.push(northBg);
worldContainer.setChildIndex(northBg, northLayer + 1);
// Add animated cosmic effects
(function (cosmicBg, layerIndex) {
function animateCosmic() {
tween(cosmicBg, {
rotation: cosmicBg.rotation + Math.PI * 2,
scaleX: cosmicBg.scale.x * (1.15 + Math.sin(Date.now() * 0.0008) * 0.15),
scaleY: cosmicBg.scale.y * (1.15 + Math.cos(Date.now() * 0.0008) * 0.15)
}, {
duration: 9000 + layerIndex * 1200,
easing: tween.linear,
onFinish: animateCosmic
});
}
animateCosmic();
})(northBg, northLayer);
}
}
// Southern Edge: Cave/Cavern themed backgrounds extending downward ONLY
if (startX < 1000) {
for (var southLayer = 0; southLayer < 6; southLayer++) {
var caveAssets = ['cavern', 'cavern2', 'underbg', 'underbg2', 'underbg3', 'underbg4', 'underbg5', 'cavefloor', 'cavefloor2'];
var caveAsset = caveAssets[southLayer % caveAssets.length];
var southBg = worldContainer.attachAsset(caveAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + southLayer * 700,
y: 2950 + southLayer * 250,
scaleX: 3.5 + southLayer * 0.3,
scaleY: 3.5 + southLayer * 0.3
});
southBg.alpha = 0.5 + southLayer * 0.12;
southBg.tint = 0x333333 + Math.floor(Math.random() * 0x333333);
backgroundElements.push(southBg);
worldContainer.setChildIndex(southBg, southLayer);
// Add cave atmosphere effects with proper spacing
if (southLayer % 2 === 0) {
var caveEffect = worldContainer.attachAsset('undereffect', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + southLayer * 700 + Math.random() * 500 - 250,
y: 2950 + southLayer * 250 + Math.random() * 400,
scaleX: 2.0 + Math.random() * 1.2,
scaleY: 2.0 + Math.random() * 1.2
});
caveEffect.alpha = 0.4 + Math.random() * 0.4;
backgroundElements.push(caveEffect);
// Add cave overlay for underground areas - multiple layers for depth
for (var cLayer = 0; cLayer < 2; cLayer++) {
var caveOverlayEffect = worldContainer.attachAsset('caveoverlay', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + southLayer * 700 + cLayer * 300,
y: 2850 + southLayer * 250 + cLayer * 150,
scaleX: 3.5 + southLayer * 0.4 + cLayer * 0.5,
scaleY: 3.5 + southLayer * 0.4 + cLayer * 0.5
});
caveOverlayEffect.alpha = 0.15 + southLayer * 0.08 + cLayer * 0.05;
caveOverlayEffect.tint = [0x332211, 0x442211, 0x331111][cLayer % 3];
backgroundElements.push(caveOverlayEffect);
worldContainer.setChildIndex(caveOverlayEffect, southLayer + 8 + cLayer);
}
// Animate cave effects with mysterious floating motion
(function (effect) {
function floatEffect() {
tween(effect, {
y: effect.y + 60 + Math.random() * 120,
alpha: effect.alpha * 0.6,
rotation: effect.rotation + 0.6,
scaleX: effect.scale.x * 1.05,
scaleY: effect.scale.y * 1.05
}, {
duration: 3500 + Math.random() * 2500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(effect, {
y: effect.y - 60 - Math.random() * 120,
alpha: effect.alpha * 1.6,
rotation: effect.rotation - 0.6,
scaleX: effect.scale.x * 0.95,
scaleY: effect.scale.y * 0.95
}, {
duration: 3500 + Math.random() * 2500,
easing: tween.easeInOut,
onFinish: floatEffect
});
}
});
}
floatEffect();
})(caveEffect);
}
}
}
// Western Edge: Sea themed backgrounds with multiple layers
if (startX < 1000) {
for (var westLayer = 0; westLayer < 6; westLayer++) {
var seaAssets = ['sea', 'sea2', 'sea3', 'sea4', 'sea5', 'sea6'];
var seaAsset = seaAssets[westLayer % seaAssets.length];
var westBg = worldContainer.attachAsset(seaAsset, {
anchorX: 1.0,
anchorY: 1.0,
x: startX - westLayer * 400 - 200,
y: 2732,
scaleX: 2.8 + westLayer * 0.3,
scaleY: 2.8 + westLayer * 0.3
});
westBg.alpha = 0.5 + westLayer * 0.08;
westBg.tint = 0x4488BB + Math.floor(westLayer * 0x111111);
backgroundElements.push(westBg);
worldContainer.setChildIndex(westBg, Math.max(0, westLayer));
// Add animated sea effects
if (westLayer < 3) {
var seaEffect = worldContainer.attachAsset('seaeffect', {
anchorX: 0.5,
anchorY: 0.5,
x: startX - westLayer * 400 - 200 + Math.random() * 600,
y: 2200 + Math.random() * 400,
scaleX: 1.2 + Math.random() * 0.8,
scaleY: 1.2 + Math.random() * 0.8
});
seaEffect.alpha = 0.4 + Math.random() * 0.3;
seaEffect.tint = 0x6699DD;
backgroundElements.push(seaEffect);
// Add comprehensive bubble animations as environmental effect in ocean/sea biome areas
for (var b = 0; b < 15; b++) {
var bubble = worldContainer.attachAsset('bubbles', {
anchorX: 0.5,
anchorY: 0.5,
x: startX - westLayer * 400 - 200 + Math.random() * 1000,
y: 1900 + Math.random() * 900,
scaleX: 0.6 + Math.random() * 2.0,
scaleY: 0.6 + Math.random() * 2.0
});
bubble.alpha = 0.25 + Math.random() * 0.5;
bubble.tint = [0x88CCFF, 0x66DDFF, 0xAAEEFF, 0x44CCFF][Math.floor(Math.random() * 4)];
backgroundElements.push(bubble);
// Animate bubbles rising and floating with natural physics
(function (bubbleObj, bubbleIndex) {
var wobbleAmount = 30 + Math.random() * 50;
var wobbleSpeed = 0.5 + Math.random() * 1.5;
function animateBubble() {
var wobbleX = Math.sin(Date.now() * 0.001 * wobbleSpeed + bubbleIndex) * wobbleAmount;
tween(bubbleObj, {
y: bubbleObj.y - 200 - Math.random() * 150,
x: bubbleObj.x + wobbleX,
alpha: bubbleObj.alpha * 0.5,
scaleX: bubbleObj.scale.x * (0.95 + Math.random() * 0.2),
scaleY: bubbleObj.scale.y * (0.95 + Math.random() * 0.2)
}, {
duration: 5000 + Math.random() * 3000,
easing: tween.easeOut,
onFinish: function onFinish() {
bubbleObj.y = 2700 + Math.random() * 300;
bubbleObj.x = startX - westLayer * 400 - 200 + Math.random() * 1000;
bubbleObj.alpha = 0.25 + Math.random() * 0.5;
bubbleObj.scale.x = 0.6 + Math.random() * 2.0;
bubbleObj.scale.y = 0.6 + Math.random() * 2.0;
animateBubble();
}
});
}
animateBubble();
})(bubble, b);
}
// Wave motion animation
(function (waveEffect, layerIdx) {
function animateWave() {
tween(waveEffect, {
y: waveEffect.y + 30 + Math.sin(layerIdx) * 20,
x: waveEffect.x + 20,
alpha: waveEffect.alpha * 0.8
}, {
duration: 2500 + Math.random() * 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(waveEffect, {
y: waveEffect.y - 30 - Math.sin(layerIdx) * 20,
x: waveEffect.x - 20,
alpha: waveEffect.alpha * 1.25
}, {
duration: 2500 + Math.random() * 1500,
easing: tween.easeInOut,
onFinish: animateWave
});
}
});
}
animateWave();
})(seaEffect, westLayer);
}
}
}
// Eastern Edge: Forest themed backgrounds with depth - ONLY in eastern biome (far to the right/east)
if (startX > 3000) {
for (var eastLayer = 0; eastLayer < 6; eastLayer++) {
var forestAssets = ['forest', 'forest2', 'forest3', 'forest5', 'trees', 'trees2', 'trees3', 'trees4'];
var forestAsset = forestAssets[eastLayer % forestAssets.length];
var eastBg = worldContainer.attachAsset(forestAsset, {
anchorX: 0.0,
anchorY: 1.0,
x: startX + 2700 + eastLayer * 600,
y: 2732,
scaleX: 2.8 + eastLayer * 0.5,
scaleY: 2.8 + eastLayer * 0.5
});
eastBg.alpha = 0.5 + eastLayer * 0.15;
eastBg.tint = [0x1a6d2e, 0x228844, 0x2d9a4a, 0x3aaa55][eastLayer % 4];
backgroundElements.push(eastBg);
worldContainer.setChildIndex(eastBg, Math.max(0, 3 + eastLayer));
// Add forest environmental effects with proper spacing
if (eastLayer % 2 === 1) {
var forestEffect = worldContainer.attachAsset('foresteffect', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + 2700 + eastLayer * 600 + Math.random() * 500,
y: 1700 + Math.random() * 700,
scaleX: 1.2 + Math.random() * 1.4,
scaleY: 1.2 + Math.random() * 1.4
});
forestEffect.alpha = 0.4 + Math.random() * 0.5;
forestEffect.tint = [0x55BB77, 0x44AA66, 0x66CC88][Math.floor(Math.random() * 3)];
backgroundElements.push(forestEffect);
// Gentle swaying forest animation
(function (treeEffect, effectIndex) {
function swayTrees() {
tween(treeEffect, {
rotation: 0.12,
scaleX: treeEffect.scale.x * 1.08,
alpha: treeEffect.alpha * 0.85
}, {
duration: 4500 + Math.random() * 2500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(treeEffect, {
rotation: -0.12,
scaleX: treeEffect.scale.x * 0.92,
alpha: treeEffect.alpha * 1.18
}, {
duration: 4500 + Math.random() * 2500,
easing: tween.easeInOut,
onFinish: swayTrees
});
}
});
}
swayTrees();
})(forestEffect, eastLayer);
}
}
}
// Generate comprehensive background elements for visual coherence
var bgAssets = ['bg2', 'bg3', 'bg4'];
// Add layered backgrounds with better coverage
for (var layer = 0; layer < 3; layer++) {
var bgAsset = bgAssets[layer];
var bg = worldContainer.attachAsset(bgAsset, {
anchorX: 0.5,
anchorY: 1.0,
x: startX + 2048,
y: 2732,
scaleX: 3.0 - layer * 0.2,
scaleY: 3.0 - layer * 0.2
});
bg.alpha = 0.4 + layer * 0.2;
backgroundElements.push(bg);
worldContainer.setChildIndex(bg, layer);
}
// === EDGE CONCEALMENT AND ATMOSPHERIC SYSTEMS ===
// Add comprehensive edge concealment overlays with partial opacity
var edgeOverlays = [{
asset: 'caveoverlay',
x: startX + 1024,
y: 3200,
scale: 4.0,
alpha: 0.3,
tint: 0x332222
},
// Bottom edge
{
asset: 'underoverlay',
x: startX - 800,
y: 2732,
scale: 3.5,
alpha: 0.25,
tint: 0x446688
},
// Left bottom
{
asset: 'foresteffect2',
x: startX + 3000,
y: 2500,
scale: 3.0,
alpha: 0.35,
tint: 0x225533
},
// Right edge
{
asset: 'galaxybg4',
x: startX + 1024,
y: 400,
scale: 3.5,
alpha: 0.2,
tint: 0x664499
} // Top edge
];
for (var overlay = 0; overlay < edgeOverlays.length; overlay++) {
var overlayData = edgeOverlays[overlay];
var edgeOverlay = worldContainer.attachAsset(overlayData.asset, {
anchorX: 0.5,
anchorY: 0.5,
x: overlayData.x,
y: overlayData.y,
scaleX: overlayData.scale,
scaleY: overlayData.scale
});
edgeOverlay.alpha = overlayData.alpha;
edgeOverlay.tint = overlayData.tint;
backgroundElements.push(edgeOverlay);
// Ensure overlays stay behind platforms but in front of base backgrounds
worldContainer.setChildIndex(edgeOverlay, Math.max(3, 4 + overlay));
// Add subtle movement to overlays for atmosphere
(function (overlayObj, index) {
function animateOverlay() {
var moveDistance = 30 + Math.random() * 20;
var moveDuration = 5000 + Math.random() * 3000;
tween(overlayObj, {
x: overlayObj.x + (index % 2 === 0 ? moveDistance : -moveDistance),
y: overlayObj.y + Math.sin(index) * 15,
alpha: overlayObj.alpha * (0.8 + Math.random() * 0.4)
}, {
duration: moveDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(overlayObj, {
x: overlayObj.x - (index % 2 === 0 ? moveDistance : -moveDistance),
y: overlayObj.y - Math.sin(index) * 15,
alpha: overlayObj.alpha * (1.2 + Math.random() * 0.3)
}, {
duration: moveDuration,
easing: tween.easeInOut,
onFinish: animateOverlay
});
}
});
}
animateOverlay();
})(edgeOverlay, overlay);
}
// Add additional background fill elements to eliminate black spaces
for (var fill = 0; fill < 12; fill++) {
var fillAssets = ['bg2', 'bg3', 'bg4', 'underbg', 'sea2', 'forest3', 'galaxy2'];
var fillAsset = fillAssets[fill % fillAssets.length];
var fillBg = worldContainer.attachAsset(fillAsset, {
anchorX: 0.5,
anchorY: 1.0,
x: startX + fill * 400 - 800,
y: 2732 + Math.random() * 400 - 200,
scaleX: 2.8 + Math.random() * 1.8,
scaleY: 2.8 + Math.random() * 1.8
});
fillBg.alpha = 0.15 + Math.random() * 0.25;
fillBg.tint = 0x333333 + Math.floor(Math.random() * 0x555555);
backgroundElements.push(fillBg);
worldContainer.setChildIndex(fillBg, Math.max(0, fill));
}
// === ENHANCED ATMOSPHERIC EFFECTS SYSTEM ===
// Add comprehensive sky and atmospheric fill
for (var sky = 0; sky < 8; sky++) {
var skyAssets = ['bg4', 'galaxy', 'sea3', 'forest2'];
var skyAsset = skyAssets[sky % skyAssets.length];
var skyFill = worldContainer.attachAsset(skyAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + sky * 600 - 1200,
y: 800 + Math.random() * 1200,
scaleX: 4.5 + Math.random() * 1.5,
scaleY: 4.5 + Math.random() * 1.5
});
skyFill.alpha = 0.12 + Math.random() * 0.15;
skyFill.tint = [0x222244, 0x442266, 0x224466, 0x226644][sky % 4];
backgroundElements.push(skyFill);
worldContainer.setChildIndex(skyFill, 0);
// Add drifting animation to sky elements
(function (skyElement, skyIndex) {
function driftSky() {
tween(skyElement, {
x: skyElement.x + 100 + Math.random() * 200,
y: skyElement.y + 50 * Math.sin(skyIndex),
rotation: skyElement.rotation + 0.2,
alpha: skyElement.alpha * (0.7 + Math.random() * 0.6)
}, {
duration: 15000 + Math.random() * 10000,
easing: tween.linear,
onFinish: function onFinish() {
// Reset position for continuous drift
skyElement.x = startX - 1200;
driftSky();
}
});
}
driftSky();
})(skyFill, sky);
}
// Add floating particle effects for atmosphere
for (var particle = 0; particle < 20; particle++) {
var particleAssets = ['spell', 'crystal', 'starlight', 'undereffect'];
var particleAsset = particleAssets[particle % particleAssets.length];
var atmosphereParticle = worldContainer.attachAsset(particleAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + Math.random() * 4000 - 1000,
y: 600 + Math.random() * 2000,
scaleX: 0.3 + Math.random() * 0.7,
scaleY: 0.3 + Math.random() * 0.7
});
atmosphereParticle.alpha = 0.1 + Math.random() * 0.3;
atmosphereParticle.tint = [0x9966FF, 0x66FFFF, 0xFF6699, 0x99FF66][particle % 4];
backgroundElements.push(atmosphereParticle);
// Floating particle animation with random patterns
(function (particleObj, particleIndex) {
function floatParticle() {
var floatX = Math.cos(particleIndex) * 150;
var floatY = Math.sin(particleIndex) * 100;
tween(particleObj, {
x: particleObj.x + floatX,
y: particleObj.y + floatY,
rotation: Math.PI * 2,
alpha: particleObj.alpha * (0.5 + Math.random() * 1.0),
scaleX: particleObj.scale.x * (0.8 + Math.random() * 0.4),
scaleY: particleObj.scale.y * (0.8 + Math.random() * 0.4)
}, {
duration: 8000 + Math.random() * 6000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(particleObj, {
x: particleObj.x - floatX,
y: particleObj.y - floatY,
rotation: 0,
alpha: particleObj.alpha * (1.5 + Math.random() * 0.5)
}, {
duration: 8000 + Math.random() * 6000,
easing: tween.easeInOut,
onFinish: floatParticle
});
}
});
}
floatParticle();
})(atmosphereParticle, particle);
}
// Add statue as decoration
if (Math.random() > 0.7) {
var statue = worldContainer.attachAsset('statue', {
anchorX: 0.5,
anchorY: 1.0,
x: startX + Math.random() * 2000,
y: 2000,
scaleX: 0.8,
scaleY: 0.8
});
statue.tint = 0x666666;
backgroundElements.push(statue);
// Ensure statue stays behind platforms
var statueIndex = worldContainer.getChildIndex(statue);
worldContainer.setChildIndex(statue, Math.max(0, statueIndex - 10));
}
// Generate buildings
for (var i = 0; i < 4; i++) {
var building = new Building();
building.x = startX + 200 + i * 600;
building.y = 2000;
building.scale.x = 0.8 + Math.random() * 0.4;
building.scale.y = 0.8 + Math.random() * 0.4;
buildings.push(building);
worldContainer.addChild(building);
// Ensure buildings stay behind platforms
var buildingIndex = worldContainer.getChildIndex(building);
worldContainer.setChildIndex(building, Math.max(0, buildingIndex - 10));
}
// Generate lower clouds (beneath platforms) with proper layering
for (var i = 0; i < 6; i++) {
var cloudAssets = ['cloud1', 'cloud2', 'cloud3'];
var cloudType = cloudAssets[Math.floor(Math.random() * cloudAssets.length)];
var cloud = worldContainer.attachAsset(cloudType, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + Math.random() * 3000,
y: 2100 + Math.random() * 400 // Lower clouds
});
// Vary cloud sizes for depth
var cloudScale = 0.5 + Math.random() * 1.0;
cloud.scale.x = cloudScale;
cloud.scale.y = cloudScale;
// Make clouds more visible with better opacity
cloud.alpha = 0.6 + Math.random() * 0.4;
// Store drift speed for animation
cloud.driftSpeed = 0.5 + Math.random() * 1.5;
clouds.push(cloud);
// Ensure clouds stay in FOREGROUND - behind platforms but in front of ALL backgrounds
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(cloud, Math.min(totalChildren - 20, Math.max(10, totalChildren - 30)));
}
// Generate upper clouds (among tall buildings) with proper layering
for (var i = 0; i < 4; i++) {
var cloudAssets = ['cloud1', 'cloud2', 'cloud3'];
var cloudType = cloudAssets[Math.floor(Math.random() * cloudAssets.length)];
var cloud = worldContainer.attachAsset(cloudType, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + Math.random() * 3000,
y: 800 + Math.random() * 600 // Upper clouds among buildings
});
// Make upper clouds larger and more translucent
var cloudScale = 0.8 + Math.random() * 1.5;
cloud.scale.x = cloudScale;
cloud.scale.y = cloudScale;
// More translucent for upper clouds
cloud.alpha = 0.2 + Math.random() * 0.3;
// Slower drift for upper clouds
cloud.driftSpeed = 0.3 + Math.random() * 0.8;
clouds.push(cloud);
// Ensure clouds stay in FOREGROUND - behind platforms but in front of ALL backgrounds
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(cloud, Math.min(totalChildren - 15, Math.max(15, totalChildren - 25)));
}
// Generate platforms AFTER backgrounds and clouds to ensure they're in foreground
// Significantly reduced platform density with much larger spacing to prevent overlaps
for (var i = 0; i < 3; i++) {
var platform = new Platform();
platform.x = startX + 800 + i * 1200; // Increased to 1200px spacing
platform.y = 1600 - i % 2 * 400; // Increased vertical spacing to 400px
platforms.push(platform);
worldContainer.addChild(platform);
// Ensure platform appears in front of backgrounds
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(platform, Math.max(totalChildren - 10, totalChildren - 1));
}
// Add extended platforms in all directions with massive spacing to prevent overlaps
var extendedPlatforms = [
// Upward extension platforms - extending north with large gaps
{
x: startX + 800,
y: 1000
}, {
x: startX + 2000,
y: 800
}, {
x: startX + 3200,
y: 600
}, {
x: startX + 4400,
y: 400
},
// Continue upward platforms for vertical progression
{
x: startX + 5600,
y: 200
}, {
x: startX + 6800,
y: 100
},
// Downward extension platforms - extending south with large gaps
{
x: startX + 600,
y: 1900
}, {
x: startX + 1800,
y: 2100
}, {
x: startX + 3000,
y: 2300
}, {
x: startX + 4200,
y: 2500
},
// Continue downward platforms for vertical progression
{
x: startX + 5400,
y: 2700
}, {
x: startX + 6600,
y: 2900
},
// Horizontal extension east with large gaps
{
x: startX + 5400,
y: 1400
}, {
x: startX + 6600,
y: 1200
},
// Horizontal extension west with large gaps
{
x: startX - 1000,
y: 1500
}, {
x: startX - 2200,
y: 1300
}];
for (var p = 0; p < extendedPlatforms.length; p++) {
var platform = new Platform();
platform.x = extendedPlatforms[p].x;
platform.y = extendedPlatforms[p].y;
platform.platformType = 'standard';
platforms.push(platform);
worldContainer.addChild(platform);
// CRITICAL: Ensure all platforms appear in FOREGROUND layer (LAYER_PLATFORMS)
worldContainer.setChildIndex(platform, LAYER_PLATFORMS + p);
// Add hunks very sparsely - only on every 4th platform
if (p % 4 === 1) {
var hunk = new Hunk();
hunk.x = platform.x;
hunk.y = platform.y - 20;
hunks.push(hunk);
worldContainer.addChild(hunk);
// Ensure hunk appears in foreground but below player
worldContainer.setChildIndex(hunk, LAYER_PLATFORMS + p + 1);
}
}
// Generate Holocosmos area at upper edge of city assets (Y coordinates 400-1000)
if (startX > 6000) {
// Generate Holocosmos starting from upper edge of city
// Add galaxy backgrounds for Holocosmos positioned at city's upper edge
var galaxyBgs = ['Galaxybg', 'galaxybg2', 'galaxybg3', 'galaxybg4'];
for (var layer = 0; layer < galaxyBgs.length; layer++) {
var galaxyBg = worldContainer.attachAsset(galaxyBgs[layer], {
anchorX: 0.5,
anchorY: 1.0,
x: startX + 1000,
y: 600 - layer * 100,
// Upper edge of city level
scaleX: 2.8 - layer * 0.15,
scaleY: 2.8 - layer * 0.15
});
galaxyBg.alpha = 0.5 + layer * 0.12;
backgroundElements.push(galaxyBg);
worldContainer.setChildIndex(galaxyBg, layer);
}
// Add galaxy midground elements
var galaxyMidgrounds = ['galaxy', 'starlight2', 'galaxy2'];
for (var i = 0; i < galaxyMidgrounds.length; i++) {
var midground = worldContainer.attachAsset(galaxyMidgrounds[i], {
anchorX: 0.5,
anchorY: 0.5,
x: startX + 500 + i * 800,
y: 600 + Math.random() * 400,
scaleX: 1.5 + Math.random() * 1.0,
scaleY: 1.5 + Math.random() * 1.0
});
midground.alpha = 0.6 + Math.random() * 0.3;
backgroundElements.push(midground);
}
// Add swirling galaxy environmental effects with enhanced animations
var _loop = function _loop() {
swirl = worldContainer.attachAsset('galaxy', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + Math.random() * 2000,
y: 350 + Math.random() * 500,
scaleX: 0.6 + Math.random() * 1.0,
scaleY: 0.6 + Math.random() * 1.0
});
swirl.alpha = 0.4 + Math.random() * 0.4;
swirl.tint = [0x9966FF, 0x66FFFF, 0xFF66FF, 0xFFFF66][Math.floor(Math.random() * 4)];
// Add complex swirling animation with pulsing and rotation
function animateSwirl(swirlObj) {
// Continuous rotation
tween(swirlObj, {
rotation: swirlObj.rotation + Math.PI * 2
}, {
duration: 4000 + Math.random() * 3000,
easing: tween.linear,
onFinish: function onFinish() {
animateSwirl(swirlObj);
}
});
// Pulsing scale effect
function pulseSwirl() {
tween(swirlObj, {
scaleX: swirlObj.scale.x * 1.3,
scaleY: swirlObj.scale.y * 1.3,
alpha: swirlObj.alpha * 0.7
}, {
duration: 2000 + Math.random() * 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(swirlObj, {
scaleX: swirlObj.scale.x * 0.8,
scaleY: swirlObj.scale.y * 0.8,
alpha: swirlObj.alpha * 1.4
}, {
duration: 2000 + Math.random() * 1500,
easing: tween.easeInOut,
onFinish: pulseSwirl
});
}
});
}
pulseSwirl();
}
animateSwirl(swirl);
backgroundElements.push(swirl);
},
swirl;
for (var i = 0; i < 8; i++) {
_loop();
}
// Generate cosmic platforms using starlight and galaxy assets
var cosmicPlatformAssets = ['starlight', 'starlight2', 'galaxy3', 'galaxy4'];
for (var i = 0; i < 6; i++) {
var platformAsset = cosmicPlatformAssets[Math.floor(Math.random() * cosmicPlatformAssets.length)];
var cosmicPlatform = worldContainer.attachAsset(platformAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + 300 + i * 400,
y: 600 - i % 3 * 150,
// Floating cosmic platforms
scaleX: 3.0,
scaleY: 3.0
});
// Add cosmic glow effect
cosmicPlatform.tint = [0x9966FF, 0x66FFFF, 0xFF66FF, 0xFFFF66][Math.floor(Math.random() * 4)];
cosmicPlatform.alpha = 0.8;
// Make platforms solid for collision
platforms.push({
x: cosmicPlatform.x,
y: cosmicPlatform.y,
width: cosmicPlatform.width * cosmicPlatform.scale.x,
height: cosmicPlatform.height * cosmicPlatform.scale.y,
scale: {
x: cosmicPlatform.scale.x,
y: cosmicPlatform.scale.y
},
intersects: function intersects(other) {
// Basic intersection check
return cosmicPlatform.intersects ? cosmicPlatform.intersects(other) : false;
}
});
worldContainer.addChild(cosmicPlatform);
}
// Add HOLOCOSMOS title using alphabet assets with enhanced hologram effects
var holocosmosLetters = [{
asset: 'h',
x: startX + 200
}, {
asset: 'o',
x: startX + 300
}, {
asset: 'l',
x: startX + 400
}, {
asset: 'o',
x: startX + 500
}, {
asset: 'c',
x: startX + 600
}, {
asset: 'o',
x: startX + 700
}, {
asset: 's',
x: startX + 800
}, {
asset: 'm',
x: startX + 900
}, {
asset: 'o',
x: startX + 1000
}, {
asset: 's',
x: startX + 1100
}];
for (var i = 0; i < holocosmosLetters.length; i++) {
var letter = worldContainer.attachAsset(holocosmosLetters[i].asset, {
anchorX: 0.5,
anchorY: 0.5,
x: holocosmosLetters[i].x,
y: 300,
scaleX: 2.5,
scaleY: 2.5
});
letter.tint = 0x9966FF;
letter.alpha = 0.9;
// Proper layering: behind clouds but in front of backgrounds
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(letter, Math.max(8, Math.min(totalChildren - 35, 12)));
// Add enhanced hologram effects with blinking and color changes
(function (letterObj, index) {
var hologramColors = [0x9966FF, 0x66FFFF, 0xFF66FF, 0xFFFF66, 0x00FFFF, 0xFF00FF];
var currentColorIndex = 0;
function floatLetter() {
tween(letterObj, {
y: 250 + Math.sin(Date.now() * 0.001 + index) * 30
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: floatLetter
});
}
function blinkAndChangeColor() {
// Blink effect
tween(letterObj, {
alpha: 0.2
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Change color during blink
currentColorIndex = (currentColorIndex + 1) % hologramColors.length;
letterObj.tint = hologramColors[currentColorIndex];
tween(letterObj, {
alpha: 0.9
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
}
function alternateVisualEffects() {
// Random chance for different effects
var effectRoll = Math.random();
if (effectRoll < 0.3) {
// Shimmer effect
tween(letterObj, {
scaleX: 2.8,
scaleY: 2.8
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(letterObj, {
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 200,
easing: tween.easeInOut
});
}
});
} else if (effectRoll < 0.6) {
// Color fade
var newColor = hologramColors[Math.floor(Math.random() * hologramColors.length)];
tween(letterObj, {
tint: newColor
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
floatLetter();
// Staggered blinking for wave effect
LK.setInterval(function () {
blinkAndChangeColor();
}, 1200 + index * 150);
// Periodic visual effects
LK.setInterval(function () {
alternateVisualEffects();
}, 3000 + Math.random() * 2000);
})(letter, i);
backgroundElements.push(letter);
}
}
// Update last generated position
lastGeneratedX = startX + 2500;
}
function cleanupOldElements() {
var cleanupThreshold = cameraX - 3000;
// Cleanup old background elements
for (var i = backgroundElements.length - 1; i >= 0; i--) {
if (backgroundElements[i].x < cleanupThreshold) {
backgroundElements[i].destroy();
backgroundElements.splice(i, 1);
}
}
// Cleanup old buildings
for (var i = buildings.length - 1; i >= 0; i--) {
if (buildings[i].x < cleanupThreshold) {
buildings[i].destroy();
buildings.splice(i, 1);
}
}
// Cleanup old platforms
for (var i = platforms.length - 1; i >= 0; i--) {
if (platforms[i].x < cleanupThreshold) {
platforms[i].destroy();
platforms.splice(i, 1);
}
}
// Cleanup old hunks
for (var i = hunks.length - 1; i >= 0; i--) {
if (hunks[i].x < cleanupThreshold && hunks[i].captured) {
hunks[i].destroy();
hunks.splice(i, 1);
}
}
// Cleanup old clouds
for (var i = clouds.length - 1; i >= 0; i--) {
if (clouds[i].x < cleanupThreshold - 2000) {
clouds[i].destroy();
clouds.splice(i, 1);
}
}
}
function createLevel() {
// Add maplarge as the bottom-most background layer
var maplargeBg = worldContainer.attachAsset('maplarge', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
// Centered for 2500px width, adjust as needed for your world
y: 2732,
scaleX: 1.0,
scaleY: 1.0
});
worldContainer.setChildIndex(maplargeBg, 0);
// Add world backgrounds, layered and tiled to minimize borders and expand left/right
var worldBgLeft = worldContainer.attachAsset('world', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 2732,
scaleX: 3.0,
scaleY: 3.0
});
worldContainer.setChildIndex(worldBgLeft, 1);
var worldBgCenter = worldContainer.attachAsset('world', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
y: 2732,
scaleX: 3.0,
scaleY: 3.0
});
worldContainer.setChildIndex(worldBgCenter, 1);
var worldBgRight = worldContainer.attachAsset('world', {
anchorX: 0.5,
anchorY: 1.0,
x: 2500,
y: 2732,
scaleX: 3.0,
scaleY: 3.0
});
worldContainer.setChildIndex(worldBgRight, 1);
// Add nocturnecitylayout backgrounds, layered and tiled to minimize borders and expand left/right
var nocturneCityLayoutLeft = worldContainer.attachAsset('nocturnecitylayout', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 2732,
scaleX: 2.8,
scaleY: 2.8
});
worldContainer.setChildIndex(nocturneCityLayoutLeft, 2);
var nocturneCityLayoutCenter = worldContainer.attachAsset('nocturnecitylayout', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
y: 2732,
scaleX: 2.8,
scaleY: 2.8
});
worldContainer.setChildIndex(nocturneCityLayoutCenter, 2);
var nocturneCityLayoutRight = worldContainer.attachAsset('nocturnecitylayout', {
anchorX: 0.5,
anchorY: 1.0,
x: 2500,
y: 2732,
scaleX: 2.8,
scaleY: 2.8
});
worldContainer.setChildIndex(nocturneCityLayoutRight, 2);
// Add nocturnecity backgrounds, layered and tiled to minimize borders and expand left/right
var nocturneCityBgLeft = worldContainer.attachAsset('nocturnecity', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 2732,
scaleX: 2.5,
scaleY: 2.5
});
worldContainer.setChildIndex(nocturneCityBgLeft, 3);
var nocturneCityBgCenter = worldContainer.attachAsset('nocturnecity', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
y: 2732,
scaleX: 2.5,
scaleY: 2.5
});
worldContainer.setChildIndex(nocturneCityBgCenter, 3);
var nocturneCityBgRight = worldContainer.attachAsset('nocturnecity', {
anchorX: 0.5,
anchorY: 1.0,
x: 2500,
y: 2732,
scaleX: 2.5,
scaleY: 2.5
});
worldContainer.setChildIndex(nocturneCityBgRight, 3);
// Add bg2 as a background fill layer, tiled to left and right for coverage
var background2Left = worldContainer.attachAsset('bg2', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 2732,
scaleX: 2.0,
scaleY: 2.0
});
background2Left.alpha = 0.7;
var background2Center = worldContainer.attachAsset('bg2', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
y: 2732,
scaleX: 2.0,
scaleY: 2.0
});
background2Center.alpha = 0.7;
var background2Right = worldContainer.attachAsset('bg2', {
anchorX: 0.5,
anchorY: 1.0,
x: 2500,
y: 2732,
scaleX: 2.0,
scaleY: 2.0
});
background2Right.alpha = 0.7;
// Add main background layer on top, tiled for coverage
var backgroundLeft = worldContainer.attachAsset('bg', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 2732,
scaleX: 0.8,
scaleY: 0.8
});
backgroundLeft.alpha = 0.8;
var backgroundCenter = worldContainer.attachAsset('bg', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
y: 2732,
scaleX: 0.8,
scaleY: 0.8
});
backgroundCenter.alpha = 0.8;
var backgroundRight = worldContainer.attachAsset('bg', {
anchorX: 0.5,
anchorY: 1.0,
x: 2500,
y: 2732,
scaleX: 0.8,
scaleY: 0.8
});
backgroundRight.alpha = 0.8;
// Add additional colored background shapes to fill gaps
var bg3 = worldContainer.attachAsset('bg3', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
y: 2732,
scaleX: 30,
scaleY: 15
});
bg3.alpha = 0.3;
worldContainer.setChildIndex(bg3, 0); // Put behind other backgrounds
// Add bg4 with improved scaling to match other backgrounds, tiled for coverage and dimension
var bg4Left = worldContainer.attachAsset('bg4', {
anchorX: 0.5,
anchorY: 1.0,
x: 0,
y: 2732,
scaleX: 2.0,
scaleY: 2.0
});
bg4Left.alpha = 0.2;
worldContainer.setChildIndex(bg4Left, 1);
var bg4Center = worldContainer.attachAsset('bg4', {
anchorX: 0.5,
anchorY: 1.0,
x: 1250,
y: 2732,
scaleX: 2.0,
scaleY: 2.0
});
bg4Center.alpha = 0.2;
worldContainer.setChildIndex(bg4Center, 1);
var bg4Right = worldContainer.attachAsset('bg4', {
anchorX: 0.5,
anchorY: 1.0,
x: 2500,
y: 2732,
scaleX: 2.0,
scaleY: 2.0
});
bg4Right.alpha = 0.2;
worldContainer.setChildIndex(bg4Right, 1);
// Create clouds drifting beneath the starting platform
for (var i = 0; i < 12; i++) {
var cloudAssets = ['cloud1', 'cloud2', 'cloud3'];
var cloudType = cloudAssets[Math.floor(Math.random() * cloudAssets.length)];
var cloud = worldContainer.attachAsset(cloudType, {
anchorX: 0.5,
anchorY: 0.5,
x: Math.random() * 4096 - 1024,
// Spread across wider area
y: 2100 + Math.random() * 400 // Position below starting platform (1800-2000)
});
// Vary cloud sizes for depth
var cloudScale = 0.5 + Math.random() * 1.0;
cloud.scale.x = cloudScale;
cloud.scale.y = cloudScale;
// Make clouds more visible with better opacity
cloud.alpha = 0.8 + Math.random() * 0.2;
// Store initial position and drift speed for animation
cloud.initialX = cloud.x;
cloud.driftSpeed = 0.5 + Math.random() * 1.5;
clouds.push(cloud);
// Put clouds behind platforms but in front of backgrounds
worldContainer.setChildIndex(cloud, 4 + i);
}
// Create more buildings with varied positioning to fill the midground
for (var i = 0; i < 15; i++) {
var building = new Building();
building.x = 100 + i * 400;
building.y = 2000; // Position at ground level so buildings meet platforms
building.scale.x = 0.6 + Math.random() * 0.8; // More varied building sizes
building.scale.y = 0.6 + Math.random() * 0.8;
buildings.push(building);
worldContainer.addChild(building);
// Add a platform at the base of each building so it doesn't float
var basePlatform = new Platform();
basePlatform.x = building.x;
basePlatform.y = building.y;
basePlatform.scale.x = 1.2 * building.scale.x;
basePlatform.scale.y = 0.8 * building.scale.y;
platforms.push(basePlatform);
worldContainer.addChild(basePlatform);
// Ensure base platform is in the platform layer
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(basePlatform, Math.max(LAYER_PLATFORMS, totalChildren - 1));
}
// Add additional statues for better edge concealment
for (var s = 0; s < 8; s++) {
var statue = worldContainer.attachAsset('statue', {
anchorX: 0.5,
anchorY: 1.0,
x: 500 + s * 800,
y: 2000,
scaleX: 0.7 + Math.random() * 0.6,
scaleY: 0.7 + Math.random() * 0.6
});
statue.tint = 0x555555 + Math.floor(Math.random() * 0x333333);
backgroundElements.push(statue);
// Ensure statues stay behind platforms
var statueIndex = worldContainer.getChildIndex(statue);
worldContainer.setChildIndex(statue, Math.max(0, statueIndex - 5));
}
// Add additional neon signs for better visual coverage
for (var n = 0; n < 15; n++) {
var neonAsset = Math.random() > 0.5 ? 'neonSign' : 'neonsign2';
var neonSign = worldContainer.attachAsset(neonAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: 200 + n * 450,
y: 1300 + Math.random() * 600,
scaleX: 0.6 + Math.random() * 0.8,
scaleY: 0.6 + Math.random() * 0.8
});
neonSign.alpha = 0.5 + Math.random() * 0.4;
neonSign.tint = [0xFF00FF, 0x00FFFF, 0xFFFF00, 0xFF0088, 0x88FF00][Math.floor(Math.random() * 5)];
backgroundElements.push(neonSign);
// Place neonsign in front of all buildings, but behind player and platforms and clouds
var highestBuildingIndex = -1;
var highestCloudIndex = -1;
for (var j = 0; j < worldContainer.children.length; j++) {
var child = worldContainer.children[j];
if (child !== neonSign && child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url && child.texture.baseTexture.resource.url.indexOf('building') !== -1) {
var idx = worldContainer.getChildIndex(child);
if (idx > highestBuildingIndex) highestBuildingIndex = idx;
}
// Also check for clouds
if (child !== neonSign && child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url && child.texture.baseTexture.resource.url.indexOf('cloud') !== -1) {
var cidx = worldContainer.getChildIndex(child);
if (cidx > highestCloudIndex) highestCloudIndex = cidx;
}
}
worldContainer.addChild(neonSign);
// Place neonsign above buildings, but below clouds, platforms, and player
var neonTargetIndex = Math.max(highestBuildingIndex + 1, highestCloudIndex + 1);
var totalChildren = worldContainer.children.length;
neonTargetIndex = Math.min(neonTargetIndex, totalChildren - 1);
worldContainer.setChildIndex(neonSign, neonTargetIndex);
}
// Add more vertical decorative elements to fill gaps
for (var v = 0; v < 12; v++) {
var decorAssets = ['crystal', 'artefact', 'skilltree'];
var decorAsset = decorAssets[Math.floor(Math.random() * decorAssets.length)];
var decoration = worldContainer.attachAsset(decorAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: 400 + v * 500,
y: 1000 + Math.random() * 800,
scaleX: 1.5 + Math.random() * 1.0,
scaleY: 1.5 + Math.random() * 1.0
});
decoration.alpha = 0.4 + Math.random() * 0.3;
decoration.tint = 0x666666 + Math.floor(Math.random() * 0x999999);
backgroundElements.push(decoration);
var decorIndex = worldContainer.getChildIndex(decoration);
worldContainer.setChildIndex(decoration, Math.max(0, decorIndex - 8));
}
var ground = new Platform();
ground.x = 1024;
ground.y = 2000;
ground.scale.x = 10;
platforms.push(ground);
worldContainer.addChild(ground);
// Extend background assets to fill bottom of screen, including behind trackpad and jump+spell buttons
for (var bgb = 0; bgb < 3; bgb++) {
var bottomBg = worldContainer.attachAsset('bg4', {
anchorX: 0.5,
anchorY: 1.0,
x: 1024 + bgb * 1024 - 1024,
y: 2732,
scaleX: 2.2,
scaleY: 1.2
});
bottomBg.alpha = 0.35;
backgroundElements.push(bottomBg);
worldContainer.setChildIndex(bottomBg, 1);
}
// Create repeating bottom platform foundation, aligned with bottom edge of building assets
var bottomY = 2000; // This should match the y of buildings
for (var b = 0; b < 15; b++) {
var bottomPlatform = new Platform();
bottomPlatform.x = 300 + b * 400;
bottomPlatform.y = bottomY; // Aligned with building base
bottomPlatform.scale.x = 1.2;
bottomPlatform.scale.y = 0.8;
platforms.push(bottomPlatform);
worldContainer.addChild(bottomPlatform);
}
// Create platform layout with increased spacing to prevent overlapping and extend in all 4 directions
var platformPositions = [
// Main vertical progression with wider horizontal spacing
{
x: 400,
y: 1800,
type: 'standard'
},
// Start
{
x: 1000,
y: 1600,
type: 'standard'
}, {
x: 1600,
y: 1500,
type: 'standard'
}, {
x: 2200,
y: 1600,
type: 'standard'
}, {
x: 2800,
y: 1400,
type: 'standard'
}, {
x: 3400,
y: 1200,
type: 'standard'
}, {
x: 4000,
y: 1000,
type: 'standard'
}, {
x: 4600,
y: 1100,
type: 'standard'
}, {
x: 5200,
y: 1300,
type: 'standard'
}, {
x: 5800,
y: 1500,
type: 'standard'
}, {
x: 6400,
y: 1200,
type: 'standard'
}, {
x: 7000,
y: 1000,
type: 'standard'
}, {
x: 7600,
y: 1300,
type: 'standard'
}, {
x: 8200,
y: 1400,
type: 'standard'
}, {
x: 8800,
y: 1100,
type: 'standard'
},
// Extend platforms to the right (east) with proper spacing
{
x: 9400,
y: 1200,
type: 'standard'
}, {
x: 10000,
y: 1000,
type: 'standard'
},
// Transition zone to the left (west) - gradual shift from standard to sea platforms
{
x: -200,
y: 1600,
type: 'platform3' // Last standard platform type
}, {
x: -600,
y: 1500,
type: 'seaplatform' // First sea platform
}, {
x: -1000,
y: 1400,
type: 'seaplatform2'
}, {
x: -1400,
y: 1300,
type: 'seaplatform'
}, {
x: -1800,
y: 1500,
type: 'seaplatform2'
}, {
x: -2200,
y: 1400,
type: 'seaplatform'
}, {
x: -2600,
y: 1600,
type: 'seaplatform2'
},
// Extend platforms upward (north) with wider spacing
{
x: 1024,
y: 600,
type: 'standard'
}, {
x: 2200,
y: 800,
type: 'standard'
}, {
// Sea platforms extending north in transition zone
x: -800,
y: 1000,
type: 'seaplatform'
}, {
x: -1200,
y: 800,
type: 'seaplatform2'
},
// Extend platforms downward (south, near bottom) with wider spacing
{
x: 1024,
y: 2100,
type: 'standard'
}, {
x: 2200,
y: 2100,
type: 'standard'
}, {
// Sea platforms extending south in transition zone
x: -600,
y: 1800,
type: 'seaplatform2'
}, {
x: -1000,
y: 2000,
type: 'seaplatform'
}];
// Door positions based on layout guide with wider spacing
var doorPositions = [{
platformIndex: 3,
isShop: true
},
// Shop door moved to avoid overlap
{
platformIndex: 7,
isShop: false
},
// Mid level door with proper spacing
{
platformIndex: 12,
isShop: false
} // Upper right door with proper spacing
];
for (var i = 0; i < platformPositions.length; i++) {
var platform;
var platformPos = platformPositions[i];
// Create appropriate platform type based on position
if (platformPos.type === 'seaplatform' || platformPos.type === 'seaplatform2') {
// Create sea platform
platform = worldContainer.attachAsset(platformPos.type, {
anchorX: 0.5,
anchorY: 0.5,
x: platformPos.x,
y: platformPos.y,
scaleX: 1.2,
scaleY: 1.2
});
platform.tint = 0x4488BB; // Ocean tint
platform.alpha = 0.9;
// Add to platforms array for collision detection
platforms.push({
x: platform.x,
y: platform.y,
width: platform.width * platform.scale.x,
height: platform.height * platform.scale.y,
scale: platform.scale,
intersects: function intersects(other) {
return platform.intersects ? platform.intersects(other) : false;
}
});
} else {
// Create standard platform
platform = new Platform();
platform.x = platformPos.x;
platform.y = platformPos.y;
platforms.push(platform);
worldContainer.addChild(platform);
}
// Ensure platform appears in foreground
var platformIndex = worldContainer.getChildIndex(platform);
worldContainer.setChildIndex(platform, worldContainer.children.length - 1);
// Animate some platforms to move in a fixed line (every 4th platform)
if (i % 4 === 2) {
(function (movingPlatform, baseX, baseY, idx) {
var moveDistance = 300 + Math.random() * 200;
var moveAxis = idx % 2 === 0 ? 'x' : 'y';
function animatePlatformForward() {
var props = {};
props[moveAxis] = moveAxis === 'x' ? baseX + moveDistance : baseY + moveDistance;
tween(movingPlatform, props, {
duration: 2200 + Math.random() * 800,
easing: tween.easeInOut,
onFinish: animatePlatformBackward
});
}
function animatePlatformBackward() {
var props = {};
props[moveAxis] = moveAxis === 'x' ? baseX : baseY;
tween(movingPlatform, props, {
duration: 2200 + Math.random() * 800,
easing: tween.easeInOut,
onFinish: animatePlatformForward
});
}
animatePlatformForward();
})(platform, platform.x, platform.y, i);
}
// Check if this platform should have a door
var doorInfo = doorPositions.find(function (d) {
return d.platformIndex === i;
});
if (doorInfo) {
// Add platform beneath door for proper standing surface
var doorPlatform = new Platform();
doorPlatform.x = platform.x;
doorPlatform.y = platform.y + 50; // Slightly below main platform
doorPlatform.scale.x = 1.2;
platforms.push(doorPlatform);
worldContainer.addChild(doorPlatform);
// Ensure door platform is in foreground
var doorPlatformIndex = worldContainer.getChildIndex(doorPlatform);
worldContainer.setChildIndex(doorPlatform, worldContainer.children.length - 1);
var door = worldContainer.attachAsset('door', {
anchorX: 0.5,
anchorY: 1.0,
x: platform.x,
y: platform.y - 20,
scaleX: 0.8,
scaleY: 0.8
});
door.interactive = true;
door.platformIndex = i;
door.isShop = doorInfo.isShop;
// Ensure door appears in front of platforms
var doorIndex = worldContainer.getChildIndex(door);
worldContainer.setChildIndex(door, worldContainer.children.length - 1);
if (doorInfo.isShop) {
doorAsset = door; // Store shop door reference
// Add hidesign floating over shop door
var hideSign = worldContainer.attachAsset('hidesign', {
anchorX: 0.5,
anchorY: 1.0,
x: platform.x,
y: platform.y - 600,
scaleX: 4,
scaleY: 4
});
hideSign.interactive = true;
hideSign.down = function (x, y, obj) {
enterShop();
};
// Animate sign floating
var _animateSign = function animateSign() {
tween(hideSign, {
y: platform.y - 330
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(hideSign, {
y: platform.y - 370
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: _animateSign
});
}
});
};
_animateSign();
} else {
// Non-shop doors get random interior
door.interiorAsset = 'interior' + (Math.floor(Math.random() * 8) + 1);
door.down = function () {
showInterior(this.interiorAsset);
};
}
}
}
// Add single hunks at strategic positions - positioned away from doors
var hunkPositions = [{
x: 1000,
y: 1480,
platformIndex: 1
},
// First hunk on safe platform
{
x: 3400,
y: 980,
platformIndex: 7
},
// Mid level hunk - away from doors
{
x: 5400,
y: 1280,
platformIndex: 13
} // Final hunk - clear area
];
for (var h = 0; h < hunkPositions.length; h++) {
var hunk = new Hunk();
hunk.x = hunkPositions[h].x;
hunk.y = hunkPositions[h].y;
hunks.push(hunk);
worldContainer.addChild(hunk);
}
// Add stairs with increased spacing to prevent overlapping
var stairsPositions = [{
x: 750,
y: 1700,
connectsTo: {
x: 1000,
y: 1600
}
}, {
x: 1350,
y: 1550,
connectsTo: {
x: 1600,
y: 1500
}
}, {
x: 1950,
y: 1450,
connectsTo: {
x: 2200,
y: 1400
}
}, {
x: 2550,
y: 1300,
connectsTo: {
x: 2800,
y: 1200
}
}, {
x: 3150,
y: 1100,
connectsTo: {
x: 3400,
y: 1000
}
}, {
x: 3750,
y: 1200,
connectsTo: {
x: 4000,
y: 1300
}
}, {
x: 4350,
y: 1350,
connectsTo: {
x: 4600,
y: 1200
}
}, {
x: 4950,
y: 1100,
connectsTo: {
x: 5200,
y: 1000
}
}, {
x: 6500,
y: 900,
connectsTo: {
x: 6800,
y: 800
}
}, {
x: 7100,
y: 950,
connectsTo: {
x: 7400,
y: 850
}
}];
// Add stairs as solid collision platforms with proper foreground positioning
for (var i = 0; i < stairsPositions.length; i++) {
var stairs = new Stairs();
stairs.x = stairsPositions[i].x;
stairs.y = stairsPositions[i].y;
// Scale stairs based on height difference with better proportions
var heightDiff = Math.abs(stairsPositions[i].y - stairsPositions[i].connectsTo.y);
stairs.scale.x = 0.8;
stairs.scale.y = 0.6 + heightDiff / 300 * 0.4;
// Rotate stairs to point toward connected platform
var dx = stairsPositions[i].connectsTo.x - stairsPositions[i].x;
if (dx < 0) {
stairs.scale.x = -0.8; // Flip stairs if going left
}
// Add stairs to platforms array for collision detection
platforms.push({
x: stairs.x,
y: stairs.y,
width: stairs.width,
height: stairs.height,
scale: stairs.scale,
intersects: function intersects(other) {
return stairs.intersects ? stairs.intersects(other) : false;
}
});
worldContainer.addChild(stairs);
// Place stairs in the frontmost layer, above all platforms, player, buildings, and backgrounds
// Find the highest platform index
var highestPlatformIndex = -1;
for (var j = 0; j < worldContainer.children.length; j++) {
var child = worldContainer.children[j];
if (child !== stairs && child.platformType) {
var idx = worldContainer.getChildIndex(child);
if (idx > highestPlatformIndex) highestPlatformIndex = idx;
}
}
// Place stairs just above all platforms (and thus above buildings/backgrounds)
var totalChildren = worldContainer.children.length;
var stairsTargetIndex = Math.max(highestPlatformIndex + 1, totalChildren - 1);
stairsTargetIndex = Math.min(stairsTargetIndex, totalChildren - 1);
worldContainer.setChildIndex(stairs, stairsTargetIndex);
}
// Add hunks inside interiors for immersion
var interiorHunks = [{
x: 2400,
y: 1000,
interior: true
}, {
x: 4200,
y: 900,
interior: true
}];
for (var ih = 0; ih < interiorHunks.length; ih++) {
var interiorHunk = new Hunk();
interiorHunk.x = interiorHunks[ih].x;
interiorHunk.y = interiorHunks[ih].y;
interiorHunk.isInterior = true;
hunks.push(interiorHunk);
worldContainer.addChild(interiorHunk);
}
// Add doors to forest biome (east side)
for (var d = 0; d < 3; d++) {
var forestDoor = worldContainer.attachAsset('door', {
anchorX: 0.5,
anchorY: 1.0,
x: 2800 + d * 1200,
y: 1300,
scaleX: 0.7,
scaleY: 0.7
});
forestDoor.interactive = true;
forestDoor.interiorAsset = 'interior' + (Math.floor(Math.random() * 8) + 1);
forestDoor.down = function () {
showInterior(this.interiorAsset);
};
// Ensure door stays in foreground
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(forestDoor, Math.max(totalChildren - 1, LAYER_PLATFORMS + 5));
}
// Add doors to cosmic biome (north side)
for (var d = 0; d < 2; d++) {
var cosmicDoor = worldContainer.attachAsset('door2', {
anchorX: 0.5,
anchorY: 1.0,
x: 5400 + d * 1500,
y: 500,
scaleX: 0.6,
scaleY: 0.6
});
cosmicDoor.interactive = true;
cosmicDoor.interiorAsset = 'interior' + (Math.floor(Math.random() * 8) + 1);
cosmicDoor.down = function () {
showInterior(this.interiorAsset);
};
// Ensure door stays in foreground
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(cosmicDoor, Math.max(totalChildren - 1, LAYER_PLATFORMS + 5));
}
// Add stairs to forest connecting platforms
var forestStairs = [{
x: 2650,
y: 1200
}, {
x: 3850,
y: 1050
}];
for (var fs = 0; fs < forestStairs.length; fs++) {
var forestStair = new Stairs();
forestStair.x = forestStairs[fs].x;
forestStair.y = forestStairs[fs].y;
forestStair.scale.x = 0.8;
forestStair.scale.y = 0.6;
platforms.push({
x: forestStair.x,
y: forestStair.y,
width: forestStair.width,
height: forestStair.height,
scale: forestStair.scale,
intersects: function intersects(other) {
return forestStair.intersects ? forestStair.intersects(other) : false;
}
});
worldContainer.addChild(forestStair);
var totalChildren = worldContainer.children.length;
worldContainer.setChildIndex(forestStair, Math.max(totalChildren - 1, LAYER_PLATFORMS));
}
bloodmage = new Bloodmage();
bloodmage.x = 400; // Start on first platform
bloodmage.y = 1780;
bloodmage.grounded = true; // Ensure character starts on ground
bloodmage.isMoving = false; // Ensure character is not moving
bloodmage.isJumping = false; // Ensure character is not jumping
bloodmage.isFlying = false; // Ensure character is not flying
worldContainer.addChild(bloodmage);
// COMPLETELY REBUILT ANIMATION INITIALIZATION
// Set initial state
bloodmage.currentAnimationState = 'idle';
bloodmage.currentIdleFrame = 0;
bloodmage.idleAnimationDirection = 1;
bloodmage.isMoving = false;
bloodmage.isJumping = false;
bloodmage.isFlying = false;
// Clear ALL animations and timers to prevent duplicates
bloodmage.clearAllAnimations();
// Ensure only one Bloodmage instance is present
for (var i = worldContainer.children.length - 1; i >= 0; i--) {
var child = worldContainer.children[i];
if (child !== bloodmage && child instanceof Bloodmage) {
child.destroy();
worldContainer.children.splice(i, 1);
}
}
// Reorganize layer hierarchy after all content creation
// Move all background elements to LAYER_BACKGROUNDS
for (var i = 0; i < worldContainer.children.length; i++) {
var child = worldContainer.children[i];
// Check if it's a background-type element
if (child && !child.platformType && child !== bloodmage && !child.hunkType && child.x !== undefined && child.y !== undefined) {
// Don't move platforms or hunks or player
var isSpecial = false;
for (var j = 0; j < platforms.length; j++) {
if (platforms[j] === child) {
isSpecial = true;
break;
}
}
for (var j = 0; j < hunks.length; j++) {
if (hunks[j] === child) {
isSpecial = true;
break;
}
}
if (!isSpecial && child !== bloodmage) {
worldContainer.setChildIndex(child, LAYER_BACKGROUNDS);
}
}
}
// Position player in foreground after all reorganization
worldContainer.setChildIndex(bloodmage, LAYER_PLAYER);
// Consolidate layer management - push all existing backgrounds to proper layer
for (var i = 0; i < worldContainer.children.length; i++) {
var child = worldContainer.children[i];
if (child && !child.platformType && child !== bloodmage && !child.hunkType && child.x !== undefined && child.y !== undefined) {
var isSpecial = false;
// Don't move platforms or hunks or player
for (var j = 0; j < platforms.length; j++) {
if (platforms[j] === child) {
isSpecial = true;
break;
}
}
for (var j = 0; j < hunks.length; j++) {
if (hunks[j] === child) {
isSpecial = true;
break;
}
}
if (!isSpecial && child !== bloodmage) {
worldContainer.setChildIndex(child, LAYER_BACKGROUNDS);
}
}
}
// Always keep player in front of backgrounds and platforms except when standing on a platform
updatePlayerLayer();
// Verify idle frames exist and are properly set up (playeridle to playeridle12)
if (bloodmage.idleFrames.length >= 6) {
// Show ONLY the first idle frame (playeridle)
bloodmage.idleFrames[0].alpha = 1;
bloodmage.currentIdleFrame = 0;
// Start proper idle animation after cutscene
LK.setTimeout(function () {
bloodmage.startIdleAnimation();
}, 8000); // Delay until after cutscene completes
}
// Deploy cosmic/space assets north of city backgrounds
for (var i = 0; i < 8; i++) {
var cosmicAsset = ['galaxy', 'galaxy2', 'galaxy3', 'galaxy4', 'Galaxybg', 'starlight', 'starlight2'][Math.floor(Math.random() * 7)];
var cosmic = worldContainer.attachAsset(cosmicAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: 1024 + Math.random() * 2048 - 1024,
y: 400 + Math.random() * 200,
// Above city backgrounds
scaleX: 1.2 + Math.random() * 1.2,
scaleY: 1.2 + Math.random() * 1.2
});
cosmic.alpha = 0.5 + Math.random() * 0.3;
backgroundElements.push(cosmic);
worldContainer.setChildIndex(cosmic, LAYER_BACKGROUNDS);
}
// Deploy forest assets to the east (right) of city backgrounds with platforms on top
for (var i = 0; i < 6; i++) {
var forestAsset = ['forest', 'forest2', 'forest3', 'forest5', 'trees', 'trees2', 'trees3', 'trees4'][Math.floor(Math.random() * 8)];
var forest = worldContainer.attachAsset(forestAsset, {
anchorX: 0.5,
anchorY: 1.0,
x: 2500 + 400 + i * 300,
y: 2732 - 400 + Math.random() * 200,
scaleX: 1.2 + Math.random() * 1.2,
scaleY: 1.2 + Math.random() * 1.2
});
forest.alpha = 0.5 + Math.random() * 0.3;
backgroundElements.push(forest);
worldContainer.setChildIndex(forest, LAYER_BACKGROUNDS);
// Add platform on top of forest asset
var forestPlatform = new Platform();
forestPlatform.x = forest.x;
forestPlatform.y = forest.y - forest.height * forest.scale.y / 2 - 50;
platforms.push(forestPlatform);
worldContainer.addChild(forestPlatform);
worldContainer.setChildIndex(forestPlatform, LAYER_PLATFORMS);
}
// Deploy ocean assets to the west (left) of city backgrounds with enhanced layering
for (var i = 0; i < 6; i++) {
var oceanAsset = ['sea', 'sea2', 'sea3', 'sea4', 'sea5', 'sea6'][Math.floor(Math.random() * 6)];
var ocean = worldContainer.attachAsset(oceanAsset, {
anchorX: 0.5,
anchorY: 1.0,
x: 0 - 400 - i * 300,
y: 2732 - 400 + Math.random() * 200,
scaleX: 1.2 + Math.random() * 1.2,
scaleY: 1.2 + Math.random() * 1.2
});
ocean.alpha = 0.5 + Math.random() * 0.3;
backgroundElements.push(ocean);
worldContainer.setChildIndex(ocean, LAYER_BACKGROUNDS);
}
// Add sea transition area to the left with layered sea backgrounds and sea platforms
// Ensure startX is defined for this scope - use the parameter directly
if (startX < -1000) {
// Layer sea backgrounds with depth, using all sea assets for a smooth gradient
var seaBgAssets = ['sea', 'sea2', 'sea3', 'sea4', 'sea5', 'sea6'];
for (var layer = 0; layer < seaBgAssets.length; layer++) {
var seaBg = worldContainer.attachAsset(seaBgAssets[layer], {
anchorX: 0.5,
anchorY: 1.0,
x: startX - 500,
y: 2732 - layer * 100,
scaleX: 3.0 - layer * 0.2,
scaleY: 3.0 - layer * 0.2
});
seaBg.alpha = 0.6 + layer * 0.1;
backgroundElements.push(seaBg);
worldContainer.setChildIndex(seaBg, layer);
}
// Add sea effect animations
for (var i = 0; i < 4; i++) {
var seaEffect = worldContainer.attachAsset('seaeffect', {
anchorX: 0.5,
anchorY: 0.5,
x: startX + Math.random() * 2000 - 1000,
y: 1800 + Math.random() * 400,
scaleX: 1.0 + Math.random() * 1.0,
scaleY: 1.0 + Math.random() * 1.0
});
seaEffect.alpha = 0.4 + Math.random() * 0.3;
backgroundElements.push(seaEffect);
// Animate sea effects with wave motion
(function (effectObj) {
function animateSeaEffect() {
tween(effectObj, {
y: effectObj.y + 30,
alpha: effectObj.alpha * 0.7
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(effectObj, {
y: effectObj.y - 30,
alpha: effectObj.alpha * 1.4
}, {
duration: 2000 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: animateSeaEffect
});
}
});
}
animateSeaEffect();
})(seaEffect);
}
// Generate sea platforms extending horizontally and vertically, with gradual transition
var seaPlatformPositions = [];
// Horizontal extension westward
for (var sx = 0; sx < 6; sx++) {
seaPlatformPositions.push({
x: startX - 800 - sx * 400,
y: 1600 - sx % 2 * 100,
type: sx % 2 === 0 ? 'seaplatform' : 'seaplatform2'
});
}
// Vertical extension upward
for (var sy = 0; sy < 4; sy++) {
seaPlatformPositions.push({
x: startX - 1000 - sy * 200,
y: 1200 - sy * 200,
type: sy % 2 === 0 ? 'seaplatform2' : 'seaplatform'
});
}
// Vertical extension downward
for (var sy = 0; sy < 4; sy++) {
seaPlatformPositions.push({
x: startX - 800 - sy * 200,
y: 1800 + sy * 200,
type: sy % 2 === 0 ? 'seaplatform2' : 'seaplatform'
});
}
// Mixed positions for variety
seaPlatformPositions.push({
x: startX - 1400,
y: 1100,
type: 'seaplatform'
}, {
x: startX - 1800,
y: 1600,
type: 'seaplatform2'
});
for (var i = 0; i < seaPlatformPositions.length; i++) {
var seaPlatform = worldContainer.attachAsset(seaPlatformPositions[i].type, {
anchorX: 0.5,
anchorY: 0.5,
x: seaPlatformPositions[i].x,
y: seaPlatformPositions[i].y,
scaleX: 1.2,
scaleY: 1.2
});
seaPlatform.tint = 0x4488BB; // Ocean tint
seaPlatform.alpha = 0.9;
// Add to platforms array for collision detection
platforms.push({
x: seaPlatform.x,
y: seaPlatform.y,
width: seaPlatform.width * seaPlatform.scale.x,
height: seaPlatform.height * seaPlatform.scale.y,
scale: seaPlatform.scale,
intersects: function intersects(other) {
return seaPlatform.intersects ? seaPlatform.intersects(other) : false;
}
});
// Animate some platforms to move back and forth
if (i % 3 === 1) {
(function (movingPlatform, baseX, baseY, idx) {
var moveDistance = 200 + Math.random() * 150;
var moveAxis = idx % 2 === 0 ? 'x' : 'y';
function animatePlatformForward() {
var props = {};
props[moveAxis] = moveAxis === 'x' ? baseX + moveDistance : baseY + moveDistance;
tween(movingPlatform, props, {
duration: 2500 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: animatePlatformBackward
});
}
function animatePlatformBackward() {
var props = {};
props[moveAxis] = moveAxis === 'x' ? baseX : baseY;
tween(movingPlatform, props, {
duration: 2500 + Math.random() * 1000,
easing: tween.easeInOut,
onFinish: animatePlatformForward
});
}
animatePlatformForward();
})(seaPlatform, seaPlatform.x, seaPlatform.y, i);
}
}
}
// Generate initial Holocosmos area at upper edge of Nocturne City
chunkStartX = 6000;
generateLevelChunk(chunkStartX); // Generate Holocosmos area at city's upper edge
// Track initial generation position
lastGeneratedX = 8500; // Extended to accommodate Holocosmos area
}
createLevel();
var trackpadBg = LK.gui.bottomLeft.attachAsset('trackpadBg', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -200
});
trackpadBg.interactive = true;
var trackpadThumb = LK.gui.bottomLeft.attachAsset('trackpadThumb', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -200
});
var jumpBtn = LK.gui.bottomRight.attachAsset('jumpButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: -200
});
jumpBtn.interactive = true;
var actionBtn = LK.gui.bottomRight.attachAsset('actionbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: -200,
y: -450
});
actionBtn.interactive = true;
// Direct event handlers for better responsiveness
trackpadBg.down = function (x, y, obj) {
if (battleMenuVisible || bropageShowing || shopShowing) return;
trackpadPressed = true;
updateTrackpad(x, y);
};
trackpadBg.move = function (x, y, obj) {
if (!trackpadPressed || battleMenuVisible || bropageShowing || shopShowing) return;
updateTrackpad(x, y);
};
trackpadBg.up = function () {
trackpadPressed = false;
if (bloodmage) {
bloodmage.velocityX = 0;
}
trackpadThumb.x = 200;
trackpadThumb.y = -200;
};
jumpBtn.down = function () {
if (bloodmage && !battleMenuVisible && !bropageShowing && !shopShowing) {
jumpButtonHeld = true;
jumpHoldStartTime = Date.now();
var _animateButtonBlink = function animateButtonBlink() {
if (currentBlinkFrame < blinkFrames.length) {
var frameAsset = LK.getAsset(blinkFrames[currentBlinkFrame], {
anchorX: 0.5,
anchorY: 0.5
});
var blinkFrame = LK.gui.bottomRight.addChild(frameAsset);
blinkFrame.x = -200;
blinkFrame.y = -200;
blinkFrame.alpha = 0;
blinkFrame.scale.x = 0.8;
blinkFrame.scale.y = 0.8;
tween(blinkFrame, {
alpha: 0.8,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 60,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(blinkFrame, {
alpha: 0,
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 60,
easing: tween.easeIn,
onFinish: function onFinish() {
blinkFrame.destroy();
currentBlinkFrame++;
if (currentBlinkFrame < blinkFrames.length) {
_animateButtonBlink();
}
}
});
}
});
}
};
var blinkFrames = ['buttonblink', 'buttonblink2', 'buttonblink3', 'buttonblink4'];
var currentBlinkFrame = 0;
_animateButtonBlink();
var currentTime = Date.now();
var timeSinceLastJump = currentTime - bloodmage.lastJumpTime;
// Check for double tap timing - but only if finger held down on second tap
if (timeSinceLastJump < doubleTapThreshold && bloodmage.jumpCount === 1) {
// Double tap detected - check if this is being held for flight
// Start flight mode immediately on double tap detection
bloodmage.isFlying = true;
isFlying = true;
// Strong initial upward boost for flight initiation
bloodmage.velocityY = -12; // Stronger boost to feel responsive
bloodmage.grounded = false;
bloodmage.startFlyAnimation();
LK.getSound('jump').play();
bloodmage.jumpCount = 0;
// Add visual flight initiation effect
var flightEffect = worldContainer.attachAsset('spell2', {
anchorX: 0.5,
anchorY: 0.5,
x: bloodmage.x,
y: bloodmage.y - 10,
alpha: 0.9,
scaleX: 0.8,
scaleY: 0.8
});
tween(flightEffect, {
alpha: 0,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
flightEffect.destroy();
}
});
} else {
// Normal jump attempt - record this as first tap
var jumpSuccess = bloodmage.jump();
if (jumpSuccess) {
bloodmage.lastJumpTime = currentTime;
bloodmage.jumpCount = 1; // Mark as first jump for double-tap detection
}
}
}
};
jumpBtn.up = function () {
if (jumpButtonHeld && isFlying && bloodmage) {
// Release flight mode with graceful descent
jumpButtonHeld = false;
isFlying = false;
bloodmage.isFlying = false;
bloodmage.stopFlyAnimation();
bloodmage.startFallAnimation();
// Apply realistic falling physics with parabolic arc
var currentUpwardVelocity = Math.min(bloodmage.velocityY, 0);
bloodmage.velocityY = Math.max(currentUpwardVelocity, 3); // Gentle fall start
// Maintain some horizontal momentum for natural arc
bloodmage.velocityX *= 0.7; // Slight air resistance
// Add falling visual effect
var fallEffect = worldContainer.attachAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: bloodmage.x,
y: bloodmage.y - 20,
alpha: 0.6,
scaleX: 1.5,
scaleY: 0.8
});
tween(fallEffect, {
alpha: 0,
y: bloodmage.y + 50,
scaleX: 0.8,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
fallEffect.destroy();
}
});
}
jumpButtonHeld = false;
};
var jumpText = new Text2('JUMP', {
size: 40,
fill: 0xFFFFFF
});
jumpText.anchor.set(0.5, 0.5);
jumpBtn.addChild(jumpText);
var actionText = new Text2('CAST', {
size: 40,
fill: 0xFFFFFF
});
actionText.anchor.set(0.5, 0.5);
actionBtn.addChild(actionText);
actionBtn.down = function () {
if (bloodmage && !battleMenuVisible && !bropageShowing && !shopShowing && !isCasting) {
// Check if player is near door using the stored door reference
var nearDoor = false;
if (doorAsset) {
var dx = Math.abs(bloodmage.x - doorAsset.x);
var dy = Math.abs(bloodmage.y - doorAsset.y);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 300) {
// Increased detection range further
nearDoor = true;
enterShop();
return; // Exit early to prevent casting
}
}
// Only cast spell if explicitly not near door
if (!nearDoor) {
castPlayerSpell();
}
}
};
// Score text removed - using Brostiary for hunk tracking instead
// Add brostiary icon to top right corner
var brostiaryIcon = LK.gui.topRight.attachAsset('brostiary', {
anchorX: 0.5,
anchorY: 0.5,
x: -150,
y: 150,
scaleX: 1.5,
scaleY: 1.5
});
brostiaryIcon.interactive = true;
brostiaryIcon.down = function () {
showBropage();
};
// Updated shop items with plush varieties and crystal hearts
var shopOverlay = null;
var shopShowing = false;
var currentShopPage = 0;
var shopItems = [{
name: 'crystalheart',
asset: 'crystalheart',
price: 50,
description: 'MYSTICAL CRYSTAL WITH ARCANE PROPERTIES',
useDisplay: true
}, {
name: 'plush',
asset: 'plush',
price: 30,
description: 'ADORABLE COMPANION FOR LONELY NIGHTS',
useShelf: true
}, {
name: 'plush2',
asset: 'plush2',
price: 35,
description: 'CUTE CUDDLY FRIEND',
useShelf: true
}, {
name: 'plush3',
asset: 'plush3',
price: 40,
description: 'PREMIUM PLUSH COMPANION',
useShelf: true
}, {
name: 'crystalheart',
asset: 'crystalheart',
price: 75,
description: 'RARE MYSTICAL HEART CRYSTAL',
useDisplay: true
}, {
name: 'crystalheart',
asset: 'crystalheart',
price: 100,
description: 'LEGENDARY HEART OF POWER',
useDisplay: true
}];
function showBropage() {
if (bropageShowing || battleMenuVisible) return;
bropageShowing = true;
currentBropageIndex = 0;
// Create overlay container
bropageOverlay = game.addChild(new Container());
// Add bropage background
var bgOverlay = bropageOverlay.attachAsset('bropage', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 2,
scaleY: 2.5
});
bgOverlay.alpha = 0.95;
// Add exit button (X) in upper right corner
var exitButton = bropageOverlay.attachAsset('x', {
anchorX: 0.5,
anchorY: 0.5,
x: 1800,
y: 400,
scaleX: 1.2,
scaleY: 1.2
});
exitButton.interactive = true;
exitButton.down = function () {
closeBropage();
};
// Create function to display current hunk entry
var _displayHunkEntry = function displayHunkEntry() {
// Clear previous content (except background and exit button)
for (var i = bropageOverlay.children.length - 1; i >= 2; i--) {
bropageOverlay.children[i].destroy();
}
var hunkAssets = ['hunk', 'hunk2', 'hunk3', 'hunk4'];
var hunkNames = ['MUSHROOM HUNK', 'CYBER HUNK', 'MEGA HUNK', 'MINOTAUR HUNK'];
var hunkDescriptions = ['A MYSTERIOUS FUNGAL BEING WITH HYPNOTIC SPORES THAT CAN ENTRANCE ENEMIES.', 'A DIGITAL WARRIOR FROM THE CYBER REALM WITH ELECTROMAGNETIC POWERS.', 'AN ENORMOUS POWERHOUSE WITH INCREDIBLE STRENGTH AND ENDURANCE.', 'A POWERFUL BULL-HEADED WARRIOR WITH ANCIENT COMBAT TECHNIQUES AND BERSERKER RAGE.'];
var hunkAttacks = [['SPORE BURST', 'MIND MELD', 'FUNGAL SHIELD'], ['DATA DRAIN', 'FIREWALL', 'SYSTEM CRASH'], ['GROUND POUND', 'BULK UP', 'RAMPAGE'], ['HORN CHARGE', 'BERSERKER RAGE', 'BULL RUSH']];
var hunkStats = [{
hp: 100,
power: 25,
defense: 20,
speed: 15
}, {
hp: 120,
power: 30,
defense: 25,
speed: 20
}, {
hp: 150,
power: 40,
defense: 35,
speed: 10
}, {
hp: 80,
power: 35,
defense: 15,
speed: 30
}];
// Hunk image in upper left frame (positioned to fit within the frame outline)
if (brostiary[currentBropageIndex]) {
var hunkImage = bropageOverlay.attachAsset(hunkAssets[currentBropageIndex], {
anchorX: 0.5,
anchorY: 0.5,
x: 600,
y: 800,
scaleX: 1.0,
scaleY: 1.0
});
} else {
// Show silhouette or placeholder for uncaptured hunks
var placeholder = new Text2('?', {
size: 150,
fill: 0x666666
});
placeholder.anchor.set(0.5, 0.5);
placeholder.x = 600;
placeholder.y = 800;
bropageOverlay.addChild(placeholder);
}
// Hunk name (positioned in top left name field)
var nameText = new Text2(hunkNames[currentBropageIndex], {
size: 80,
fill: brostiary[currentBropageIndex] ? 0x00FFFF : 0x666666
});
nameText.anchor.set(0, 0.5);
nameText.x = 350;
nameText.y = 550;
bropageOverlay.addChild(nameText);
// Bio section (bottom right boxed area - positioned within asset's bio box)
if (brostiary[currentBropageIndex]) {
var descText = new Text2(hunkDescriptions[currentBropageIndex], {
size: 36,
fill: 0xFFFFFF,
wordWrap: true,
wordWrapWidth: 550
});
descText.anchor.set(0, 0);
descText.x = 1150;
descText.y = 1700;
bropageOverlay.addChild(descText);
// Attacks section (lower left side - positioned within asset's attacks box)
var attacks = hunkAttacks[currentBropageIndex];
for (var i = 0; i < attacks.length; i++) {
var attackText = new Text2(attacks[i], {
size: 42,
fill: 0xFFFF00
});
attackText.anchor.set(0, 0);
attackText.x = 380;
attackText.y = 1720 + i * 70;
bropageOverlay.addChild(attackText);
}
// Stats section (positioned within asset's stats area)
var stats = hunkStats[currentBropageIndex];
var statLabels = ['HP: ', 'POWER: ', 'DEFENSE: ', 'SPEED: '];
var statValues = [stats.hp, stats.power, stats.defense, stats.speed];
for (var i = 0; i < statLabels.length; i++) {
var statText = new Text2(statLabels[i] + statValues[i], {
size: 48,
fill: 0x00FF00
});
statText.anchor.set(0, 0);
statText.x = 1200;
statText.y = 1050 + i * 70;
bropageOverlay.addChild(statText);
}
} else {
var lockedText = new Text2('⚠ DATA ENCRYPTED ⚠', {
size: 80,
fill: 0xFF0000
});
lockedText.anchor.set(0.5, 0.5);
lockedText.x = 1024;
lockedText.y = 1400;
bropageOverlay.addChild(lockedText);
var sublockText = new Text2('CAPTURE HUNK TO UNLOCK', {
size: 50,
fill: 0x888888
});
sublockText.anchor.set(0.5, 0.5);
sublockText.x = 1024;
sublockText.y = 1500;
bropageOverlay.addChild(sublockText);
}
// Navigation arrows
// Previous arrow
if (currentBropageIndex > 0) {
var prevArrow = new Text2('◄', {
size: 120,
fill: 0xFFFF00
});
prevArrow.anchor.set(0.5, 0.5);
prevArrow.x = 250;
prevArrow.y = 1366;
prevArrow.interactive = true;
prevArrow.down = function () {
currentBropageIndex--;
_displayHunkEntry();
};
bropageOverlay.addChild(prevArrow);
}
// Next arrow
if (currentBropageIndex < 3) {
var nextArrow = new Text2('►', {
size: 120,
fill: 0xFFFF00
});
nextArrow.anchor.set(0.5, 0.5);
nextArrow.x = 1774;
nextArrow.y = 1366;
nextArrow.interactive = true;
nextArrow.down = function () {
currentBropageIndex++;
_displayHunkEntry();
};
bropageOverlay.addChild(nextArrow);
}
// Page indicator
var pageText = new Text2('[ ' + (currentBropageIndex + 1) + ' / 4 ]', {
size: 60,
fill: 0xFFFFFF
});
pageText.anchor.set(0.5, 0.5);
pageText.x = 1024;
pageText.y = 1750;
bropageOverlay.addChild(pageText);
};
// Display first entry
_displayHunkEntry();
}
function closeBropage() {
if (!bropageShowing || !bropageOverlay) return;
bropageShowing = false;
bropageOverlay.destroy();
bropageOverlay = null;
}
var doorAsset = null;
var hideSignAsset = null;
var startX = 0; // Global startX variable for level generation
for (var i = 0; i < worldContainer.children.length; i++) {
var child = worldContainer.children[i];
if (child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url && child.texture.baseTexture.resource.url.includes('door')) {
doorAsset = child;
// Add direct door interaction
doorAsset.down = function (x, y, obj) {
enterShop();
};
} else if (child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url && child.texture.baseTexture.resource.url.includes('hidesign')) {
hideSignAsset = child;
// Add direct sign interaction
hideSignAsset.interactive = true;
hideSignAsset.down = function (x, y, obj) {
enterShop();
};
}
}
function enterShop() {
if (shopShowing || battleMenuVisible) return;
shopShowing = true;
currentShopPage = 0;
// Create transition overlay
var transitionOverlay = game.addChild(new Container());
// Start with door asset that expands
var expandingDoor = transitionOverlay.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0.8
});
// Animate door expanding with enhanced transition
tween(expandingDoor, {
scaleX: 6.0,
scaleY: 6.0,
alpha: 0.9
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create the actual shop overlay
shopOverlay = game.addChild(new Container());
// Add shop background - single main asset properly sized
var shopBg = shopOverlay.attachAsset('shop', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
tween(shopBg, {
scaleX: 2.0,
scaleY: 2.7,
alpha: 0.95
}, {
duration: 600,
easing: tween.easeOut
});
// Add shop2 as overlay detail
var shopDetail = shopOverlay.attachAsset('shop2', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
tween(shopDetail, {
scaleX: 2.0,
scaleY: 2.7,
alpha: 0.6
}, {
duration: 700,
easing: tween.easeOut
});
// Add Mandroid character with entrance animation
var mandroid = shopOverlay.attachAsset('mandroid', {
anchorX: 0.5,
anchorY: 1.0,
x: 350,
y: 1800,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
});
tween(mandroid, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 1
}, {
duration: 700,
easing: tween.bounceOut
});
// Add dialogue box
var dialogueBox = shopOverlay.attachAsset('dialoguebox', {
anchorX: 0.5,
anchorY: 0.5,
x: 1400,
y: 1000,
scaleX: 12,
scaleY: 8,
alpha: 0
});
tween(dialogueBox, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
// Add Mandroid's name with innuendo
var mandroidName = new Text2('HARD-WARE', {
size: 180,
fill: 0xFFFFFF,
fontWeight: 'bold'
});
mandroidName.anchor.set(0.5, 0.5);
mandroidName.x = 350;
mandroidName.y = 1200;
mandroidName.alpha = 0;
// Add hot pink glow effect
mandroidName.style = {
fontSize: 180,
fill: 0xFFFFFF,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0xFF1493,
dropShadowBlur: 25,
dropShadowDistance: 0
};
shopOverlay.addChild(mandroidName);
tween(mandroidName, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Flirtatious dialogue options
var dialogueOptions = ["WELCOME TO MY BOUTIQUE, GORGEOUS~ *MECHANICAL PURR*", "FANCY SOME PREMIUM HARDWARE? I'VE GOT WHAT YOU NEED~", "MY CIRCUITS ARE OVERHEATING JUST LOOKING AT YOU, SUGAR", "CARE TO BROWSE MY... EXTENSIVE COLLECTION? *WINK*", "I SPECIALIZE IN BOTH FASHION AND... HARDER TO FIND ITEMS~"];
var currentDialogue = dialogueOptions[Math.floor(Math.random() * dialogueOptions.length)];
var dialogueText = new Text2(currentDialogue, {
size: 96,
fill: 0xFFFFFF,
fontWeight: 'bold',
wordWrap: true,
wordWrapWidth: 800
});
dialogueText.anchor.set(0.5, 0.5);
dialogueText.x = 1450;
dialogueText.y = 1000;
dialogueText.alpha = 0;
// Add hot pink glow
dialogueText.style = {
fontSize: 96,
fill: 0xFFFFFF,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0xFF1493,
dropShadowBlur: 20,
dropShadowDistance: 0,
wordWrap: true,
wordWrapWidth: 800
};
shopOverlay.addChild(dialogueText);
tween(dialogueText, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
// Add exit button (X) in upper left corner
var exitButton = shopOverlay.attachAsset('x', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 200,
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
});
exitButton.interactive = true;
exitButton.down = function () {
closeShop();
};
tween(exitButton, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
// Display shop items with delay
LK.setTimeout(function () {
displayShopItems();
}, 1000);
// Add navigation buttons
var prevButton = new Text2('◄ PREV', {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold'
});
prevButton.anchor.set(0.5, 0.5);
prevButton.x = 200;
prevButton.y = 2400;
prevButton.interactive = true;
prevButton.alpha = 0;
// Add hot pink glow
prevButton.style = {
fontSize: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0xFF1493,
dropShadowBlur: 15,
dropShadowDistance: 0
};
prevButton.down = function () {
if (currentShopPage > 0) {
currentShopPage--;
displayShopItems();
}
};
shopOverlay.addChild(prevButton);
tween(prevButton, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
var nextButton = new Text2('NEXT ►', {
size: 100,
fill: 0xFFFFFF,
fontWeight: 'bold'
});
nextButton.anchor.set(0.5, 0.5);
nextButton.x = 1800;
nextButton.y = 2400;
nextButton.interactive = true;
nextButton.alpha = 0;
// Add hot pink glow
nextButton.style = {
fontSize: 100,
fill: 0xFFFFFF,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0xFF1493,
dropShadowBlur: 15,
dropShadowDistance: 0
};
nextButton.down = function () {
var maxPages = Math.ceil(shopItems.length / 3) - 1;
if (currentShopPage < maxPages) {
currentShopPage++;
displayShopItems();
}
};
shopOverlay.addChild(nextButton);
tween(nextButton, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Add currency display using gold coins instead
var currencyText = new Text2('GOLD: ' + capturedHunks.length * 10, {
size: 100,
fill: 0xFFD700,
fontWeight: 'bold'
});
currencyText.anchor.set(0.5, 0.5);
currencyText.x = 1024;
currencyText.y = 2500;
currencyText.alpha = 0;
// Add gold glow
currencyText.style = {
fontSize: 100,
fill: 0xFFD700,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0xFF8C00,
dropShadowBlur: 15,
dropShadowDistance: 0
};
shopOverlay.addChild(currencyText);
tween(currencyText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
function displayShopItems() {
// Clear previous items (keep first 10 children which are backgrounds and UI)
while (shopOverlay.children.length > 10) {
shopOverlay.children[shopOverlay.children.length - 1].destroy();
}
var itemsPerPage = 3;
var startIndex = currentShopPage * itemsPerPage;
var endIndex = Math.min(startIndex + itemsPerPage, shopItems.length);
for (var i = startIndex; i < endIndex; i++) {
var item = shopItems[i];
var localIndex = i - startIndex;
var itemX = 500 + localIndex * 500;
var itemY = 1800;
// Add appropriate display platform with better positioning
if (item.useDisplay) {
// Display platform for crystal hearts - positioned beneath item
var displayPlatform = shopOverlay.attachAsset('displayplatform', {
anchorX: 0.5,
anchorY: 0.5,
x: itemX,
y: itemY + 80,
scaleX: 2.5,
scaleY: 2.5
});
} else if (item.useShelf) {
// Shelf for plush items - positioned beneath item
var platShelf = shopOverlay.attachAsset('platshelf', {
anchorX: 0.5,
anchorY: 0.5,
x: itemX,
y: itemY + 100,
scaleX: 3,
scaleY: 3
});
}
// Item asset positioned above platform/shelf
var itemAsset = shopOverlay.attachAsset(item.asset, {
anchorX: 0.5,
anchorY: 1.0,
x: itemX,
y: itemY - 20,
scaleX: 1.8,
scaleY: 1.8,
alpha: 0
});
// Animate item appearing
tween(itemAsset, {
alpha: 1,
y: itemY - 50
}, {
duration: 400,
easing: tween.bounceOut
});
// Use proper text instead of alphabet assets for better legibility
var itemNameText = new Text2(item.name.toUpperCase(), {
size: 48,
fill: 0xFFFFFF,
fontWeight: 'bold',
wordWrap: true,
wordWrapWidth: 300
});
itemNameText.anchor.set(0.5, 0.5);
itemNameText.x = itemX;
itemNameText.y = itemY + 150;
itemNameText.alpha = 0;
// Add glow effect for better visibility
itemNameText.style = {
fontSize: 48,
fill: 0xFFFFFF,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0x000000,
dropShadowBlur: 8,
dropShadowDistance: 2,
wordWrap: true,
wordWrapWidth: 300
};
shopOverlay.addChild(itemNameText);
// Animate text appearing
tween(itemNameText, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
// Item price with better visual hierarchy
var itemPrice = new Text2('PRICE: ' + item.price, {
size: 56,
fill: 0xFFFF00,
fontWeight: 'bold'
});
itemPrice.anchor.set(0.5, 0.5);
itemPrice.x = itemX;
itemPrice.y = itemY + 220;
itemPrice.alpha = 0;
// Add hot pink glow
itemPrice.style = {
fontSize: 56,
fill: 0xFFFF00,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0xFF1493,
dropShadowBlur: 15,
dropShadowDistance: 0
};
shopOverlay.addChild(itemPrice);
tween(itemPrice, {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut
});
// Buy button with enhanced visual feedback
var buyButton = new Text2('BUY', {
size: 80,
fill: capturedHunks.length >= item.price ? 0x00FF00 : 0xFF0000,
fontWeight: 'bold'
});
buyButton.anchor.set(0.5, 0.5);
buyButton.x = itemX;
buyButton.y = itemY + 300;
buyButton.interactive = true;
buyButton.alpha = 0;
// Add hot pink glow
buyButton.style = {
fontSize: 80,
fill: capturedHunks.length >= item.price ? 0x00FF00 : 0xFF0000,
fontWeight: 'bold',
dropShadow: true,
dropShadowColor: 0xFF1493,
dropShadowBlur: 15,
dropShadowDistance: 0
};
buyButton.down = function (itemIndex) {
return function () {
// Add purchase animation
tween(buyButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(buyButton, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
purchaseItem(itemIndex);
};
}(i);
shopOverlay.addChild(buyButton);
tween(buyButton, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
}
// Update navigation buttons
prevButton.alpha = currentShopPage > 0 ? 1 : 0.3;
var maxPages = Math.ceil(shopItems.length / 3) - 1;
nextButton.alpha = currentShopPage < maxPages ? 1 : 0.3;
// Update currency display
currencyText.setText('GOLD: ' + capturedHunks.length * 10);
}
// Clean up transition
transitionOverlay.destroy();
}
});
}
function showShop() {
enterShop();
}
function purchaseItem(itemIndex) {
var item = shopItems[itemIndex];
var goldAmount = capturedHunks.length * 10;
if (goldAmount >= item.price) {
// Calculate how many hunks to remove based on price
var hunksToRemove = Math.ceil(item.price / 10);
for (var i = 0; i < hunksToRemove && capturedHunks.length > 0; i++) {
capturedHunks.pop();
}
storage.capturedHunks = capturedHunks;
// Score text was removed, no need to update
// Show purchase success
LK.effects.flashScreen(0x00FF00, 500);
// Update Mandroid's dialogue
var successDialogues = ["EXCELLENT CHOICE, DARLING~ *MECHANICAL SATISFACTION*", "PLEASURE DOING BUSINESS WITH SUCH A STUNNING CUSTOMER~", "YOUR TASTE IS AS EXQUISITE AS YOUR APPEARANCE, SWEETIE", "COME BACK ANYTIME, BEAUTIFUL~ I'LL BE WAITING~"];
var dialogueText = shopOverlay.children.find(function (child) {
return child instanceof Text2 && child.x === 1400 && child.y === 1000;
});
if (dialogueText) {
dialogueText.setText(successDialogues[Math.floor(Math.random() * successDialogues.length)]);
}
displayShopItems();
} else {
// Not enough currency
LK.effects.flashScreen(0xFF0000, 500);
var dialogueText = shopOverlay.children.find(function (child) {
return child instanceof Text2 && child.x === 1400 && child.y === 1000;
});
if (dialogueText) {
dialogueText.setText("NOT ENOUGH HUNKS, SUGAR~ COME BACK WHEN YOU'RE RICHER");
}
}
}
function closeShop() {
if (!shopShowing || !shopOverlay) return;
shopShowing = false;
shopOverlay.destroy();
shopOverlay = null;
}
function castPlayerSpell() {
if (!bloodmage || isCasting) return;
isCasting = true;
LK.getSound('spell').play();
// Stop all animations first
bloodmage.stopIdleAnimation();
bloodmage.stopWalkingAnimation();
bloodmage.stopJumpAnimation();
// Hide ALL assets in bloodmage to ensure nothing remains visible
for (var i = 0; i < bloodmage.children.length; i++) {
bloodmage.children[i].alpha = 0;
}
// Hide all other frames
for (var i = 0; i < bloodmage.idleFrames.length; i++) {
bloodmage.idleFrames[i].alpha = 0;
}
for (var i = 0; i < bloodmage.walkFrames.length; i++) {
bloodmage.walkFrames[i].alpha = 0;
}
for (var i = 0; i < bloodmage.jumpFrames.length; i++) {
bloodmage.jumpFrames[i].alpha = 0;
}
// Create cast animation frames
var castFrames = ['cast', 'cast2', 'cast3', 'cast4', 'cast5', 'cast6', 'cast7', 'cast8', 'cast9'];
var castAnimFrames = [];
for (var i = 0; i < castFrames.length; i++) {
var frame = worldContainer.attachAsset(castFrames[i], {
anchorX: 0.5,
anchorY: 1.0,
x: bloodmage.x,
y: bloodmage.y,
alpha: 0
});
frame.scale.x = bloodmage.facingDirection;
castAnimFrames.push(frame);
}
// Animate through cast frames with smooth transitions
var currentCastFrame = 0;
function animateCast() {
if (currentCastFrame < castAnimFrames.length) {
// Fade out previous frame smoothly
if (currentCastFrame > 0) {
tween(castAnimFrames[currentCastFrame - 1], {
alpha: 0
}, {
duration: 100,
easing: tween.easeInOut
});
}
// Fade in current frame smoothly
var currentFrame = castAnimFrames[currentCastFrame];
tween(currentFrame, {
alpha: 1
}, {
duration: 100,
easing: tween.easeInOut
});
// Launch projectile when reaching spell8 frame (frame 7)
if (currentCastFrame === 7) {
var projectile = worldContainer.attachAsset('spell4', {
anchorX: 0.5,
anchorY: 0.5,
x: bloodmage.x + bloodmage.facingDirection * -80,
y: bloodmage.y - 350
});
projectile.velocity = bloodmage.facingDirection * -15;
projectile.scale.x = bloodmage.facingDirection;
// Add smooth fade-in for projectile
projectile.alpha = 0;
tween(projectile, {
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
spellProjectiles.push(projectile);
}
currentCastFrame++;
// Slower frame rate for smoother animation
LK.setTimeout(animateCast, 120);
} else {
// Smooth fade out of last frame
tween(castAnimFrames[castAnimFrames.length - 1], {
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Clean up cast animation
for (var i = 0; i < castAnimFrames.length; i++) {
castAnimFrames[i].destroy();
}
isCasting = false;
// Resume idle animation
if (!bloodmage.isMoving && bloodmage.grounded) {
bloodmage.startIdleAnimation();
}
}
});
}
}
animateCast();
}
function updateCamera() {
if (bloodmage) {
var targetX = -bloodmage.x + 1024;
var targetY = -bloodmage.y + 1366;
cameraX += (targetX - cameraX) * 0.1;
// Add vertical camera tracking for flight mode
var currentCameraY = worldContainer.y || 0;
currentCameraY += (targetY - currentCameraY) * 0.1;
worldContainer.x = cameraX;
worldContainer.y = currentCameraY;
}
}
function checkCollisions() {
if (!bloodmage) return;
bloodmage.grounded = false;
// Check platform collisions
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var platLeft = platform.x - platform.width * platform.scale.x / 2;
var platRight = platform.x + platform.width * platform.scale.x / 2;
var platTop = platform.y - platform.height / 2;
var platBottom = platform.y + platform.height / 2;
var mageLeft = bloodmage.x - 40;
var mageRight = bloodmage.x + 40;
var mageTop = bloodmage.y - 120;
var mageBottom = bloodmage.y;
if (mageRight > platLeft && mageLeft < platRight && mageBottom > platTop && mageTop < platBottom) {
if (bloodmage.velocityY >= 0 && mageBottom - bloodmage.velocityY <= platTop) {
// If platform is moving, move player with platform
if (typeof platform.lastX !== "undefined" && typeof platform.lastY !== "undefined") {
var dx = platform.x - platform.lastX;
var dy = platform.y - platform.lastY;
bloodmage.x += dx;
bloodmage.y += dy;
}
bloodmage.y = platTop;
bloodmage.velocityY = 0;
bloodmage.grounded = true;
bloodmage.jumpCount = 0; // Reset jump count when landing
}
}
}
// Check stairs collisions with enhanced walkable surface detection
for (var i = 0; i < worldContainer.children.length; i++) {
var child = worldContainer.children[i];
if (child.stairsType) {
// Enhanced stairs collision with proper walkable surface
var stairsLeft = child.x - child.width * Math.abs(child.scale.x) / 2;
var stairsRight = child.x + child.width * Math.abs(child.scale.x) / 2;
var stairsTop = child.y - child.height * Math.abs(child.scale.y);
var stairsBottom = child.y;
var mageLeft = bloodmage.x - 40;
var mageRight = bloodmage.x + 40;
var mageTop = bloodmage.y - 120;
var mageBottom = bloodmage.y;
// Check both top surface collision and step climbing
if (mageRight > stairsLeft && mageLeft < stairsRight && mageBottom > stairsTop && mageTop < stairsBottom) {
if (bloodmage.velocityY >= 0 && mageBottom - bloodmage.velocityY <= stairsTop + 50) {
// Land on stairs top surface
bloodmage.y = stairsTop;
bloodmage.velocityY = 0;
bloodmage.grounded = true;
bloodmage.jumpCount = 0;
} else if (Math.abs(bloodmage.velocityX) > 1 && mageBottom > stairsTop + 20 && mageBottom < stairsBottom - 20) {
// Walking up/down stairs - gradual height adjustment
var stairProgress = (bloodmage.x - stairsLeft) / (stairsRight - stairsLeft);
var targetY = stairsBottom - stairProgress * child.height * Math.abs(child.scale.y);
if (Math.abs(bloodmage.y - targetY) < 100) {
bloodmage.y += (targetY - bloodmage.y) * 0.3; // Smooth stair climbing
bloodmage.grounded = true;
bloodmage.velocityY = 0;
}
}
}
}
}
if (bloodmage.y > 2500) {
bloodmage.x = 200;
bloodmage.y = 1800;
bloodmage.velocityX = 0;
bloodmage.velocityY = 0;
}
// Remove automatic proximity battle UI - now using tap-to-interact
}
// Global variables for battle UI management
var battleHunk = null;
var battleMenuVisible = false;
var combatMenuUI = null;
var cursorUI = null;
function showBattleUI() {
if (battleMenuVisible || !currentHunk) return;
battleMenuVisible = true;
// Create combat menu properly positioned and sized between trackpad and buttons
combatMenuUI = game.attachAsset('combatmenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2200,
scaleX: 2.8,
scaleY: 1.8,
alpha: 0
});
combatMenuUI.interactive = true;
// Animate combat UI appearing
tween(combatMenuUI, {
alpha: 0.95
}, {
duration: 600,
easing: tween.easeOut
});
// Position cursor with proper sizing for 4-option grid
cursorUI = game.attachAsset('cursor', {
anchorX: 0.5,
anchorY: 0.5,
x: 774,
y: 2050,
scaleX: 0.42,
scaleY: 0.56,
alpha: 0
});
// Animate cursor appearing
LK.setTimeout(function () {
tween(cursorUI, {
alpha: 1
}, {
duration: 400,
easing: tween.easeOut
});
}, 500);
window.currentSelectedOption = 0;
// Store global references for cleanup
window.currentCombatMenu = combatMenuUI;
window.currentCursor = cursorUI;
}
function hideBattleUI() {
if (!battleMenuVisible) return;
battleMenuVisible = false;
if (combatMenuUI) {
combatMenuUI.destroy();
combatMenuUI = null;
}
if (cursorUI) {
cursorUI.destroy();
cursorUI = null;
}
window.currentCombatMenu = null;
window.currentCursor = null;
}
function startBattle(hunk) {
// Don't lock player in battle mode, just set current hunk for proximity detection
currentHunk = hunk;
battleHunk = hunk;
}
function castSpell(damage) {
if (!currentHunk) return;
currentHunk.resistance = Math.max(0, currentHunk.resistance - damage);
LK.getSound('spell').play();
// Flash the hunk to show damage
LK.effects.flashObject(currentHunk, 0xFF00FF, 500);
// Create spell effect
var spellEffect = worldContainer.attachAsset('spell2', {
anchorX: 0.5,
anchorY: 0.5,
x: currentHunk.x,
y: currentHunk.y - 50,
alpha: 0.8
});
// Animate the spell effect
tween(spellEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
spellEffect.destroy();
}
});
}
function attemptCapture() {
if (!currentHunk) return;
if (currentHunk.resistance < 30) {
LK.getSound('capture').play();
currentHunk.captured = true;
currentHunk.visible = false;
// Stop idle animation for animated hunk
if (currentHunk.hunkType === 0 || currentHunk.hunkType === 1 || currentHunk.hunkType === 2 || currentHunk.hunkType === 3) {
currentHunk.stopIdleAnimation();
}
capturedHunks.push(currentHunk.hunkType);
storage.capturedHunks = capturedHunks;
if (!brostiary[currentHunk.hunkType]) {
brostiary[currentHunk.hunkType] = true;
storage.brostiary = brostiary;
}
// scoreText removed - using Brostiary for hunk tracking instead
// Hide battle UI after capture
hideBattleUI();
currentHunk = null;
// Don't teleport player - they stay at current position
if (capturedHunks.length >= 10) {
LK.showYouWin();
}
} else {
// Flash screen red for failed capture
LK.effects.flashScreen(0xFF0000, 500);
}
}
game.down = function (x, y, obj) {
// Handle combat menu interaction when battle UI is visible
if (battleMenuVisible && window.currentCombatMenu) {
// Check if clicking within combat menu bounds (centered at 1024, 2200 with scale 2.8x1.8)
var menuLeft = 1024 - 350; // Full width of scaled menu (250 * 2.8 scale)
var menuRight = 1024 + 350;
var menuTop = 2200 - 225; // Full height of scaled menu (250 * 1.8 scale)
var menuBottom = 2200 + 225;
if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) {
// 2x2 grid layout for 4 options
var relativeX = (x - menuLeft) / (menuRight - menuLeft);
var relativeY = (y - menuTop) / (menuBottom - menuTop);
var colIndex = relativeX < 0.5 ? 0 : 1; // Left or right column
var rowIndex = relativeY < 0.5 ? 0 : 1; // Top or bottom row
var newOption = rowIndex * 2 + colIndex; // Convert 2D to 1D index
newOption = Math.max(0, Math.min(3, newOption));
// Cursor positions for 2x2 grid layout with updated combat menu position
var positions = [{
x: 774,
y: 2050
}, {
x: 1274,
y: 2050
}, {
x: 774,
y: 2350
}, {
x: 1274,
y: 2350
}];
if (newOption === window.currentSelectedOption) {
// Same option clicked - select it with visual feedback
LK.effects.flashObject(window.currentCursor, 0x00FF00, 200);
selectCurrentOption();
} else {
// Different option - move cursor there with smooth animation
window.currentSelectedOption = newOption;
if (window.currentCursor && positions[window.currentSelectedOption]) {
// Flash cursor to show selection change
LK.effects.flashObject(window.currentCursor, 0xFFFF00, 150);
tween(window.currentCursor, positions[window.currentSelectedOption], {
duration: 200,
easing: tween.easeOut
});
}
}
return;
}
return;
}
if (bropageShowing || shopShowing) return;
// Check if tapping directly on door or sign area in world coordinates
var worldPos = worldContainer.toLocal({
x: x,
y: y
});
if (doorAsset) {
var doorDistance = Math.sqrt(Math.pow(worldPos.x - doorAsset.x, 2) + Math.pow(worldPos.y - doorAsset.y, 2));
if (doorDistance < 200) {
enterShop();
return;
}
}
if (hideSignAsset) {
var signDistance = Math.sqrt(Math.pow(worldPos.x - hideSignAsset.x, 2) + Math.pow(worldPos.y - hideSignAsset.y, 2));
if (signDistance < 150) {
enterShop();
return;
}
}
// Check if tapping on a hunk to start battle - more generous interaction
for (var i = 0; i < hunks.length; i++) {
var hunk = hunks[i];
if (!hunk.captured) {
// More generous proximity and tap area
var playerDistance = Math.sqrt(Math.pow(bloodmage.x - hunk.x, 2) + Math.pow(bloodmage.y - hunk.y, 2));
var tapDistance = Math.sqrt(Math.pow(worldPos.x - hunk.x, 2) + Math.pow(worldPos.y - hunk.y, 2));
if (playerDistance < 600 && tapDistance < 500) {
currentHunk = hunk;
showBattleUI();
return;
}
}
}
// Check if near door for action button press
var nearDoor = false;
if (bloodmage && doorAsset) {
var distance = Math.abs(bloodmage.x - doorAsset.x) + Math.abs(bloodmage.y - doorAsset.y);
if (distance < 200) {
nearDoor = true;
}
}
// Convert game coordinates to local GUI coordinates for trackpad
var localPos = LK.gui.bottomLeft.toLocal({
x: x,
y: y
});
var dx = localPos.x - 200;
var dy = localPos.y - -200;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 150) {
// Increased hit area
trackpadPressed = true;
trackpadAngle = Math.atan2(dy, dx);
updateTrackpad(x, y);
return; // Exit early to prevent overlap
}
// Check jump button
localPos = LK.gui.bottomRight.toLocal({
x: x,
y: y
});
dx = localPos.x - -200;
dy = localPos.y - -200;
dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 150 && bloodmage) {
jumpButtonHeld = true;
jumpHoldStartTime = Date.now();
var _animateButtonBlink2 = function animateButtonBlink() {
if (currentBlinkFrame < blinkFrames.length) {
// Get the current frame asset and apply it to the button
var frameAsset = LK.getAsset(blinkFrames[currentBlinkFrame], {
anchorX: 0.5,
anchorY: 0.5
});
// Replace button content temporarily
// Keep jump button visible - just layer blink on top
// Add blink frame to GUI
var blinkFrame = LK.gui.bottomRight.addChild(frameAsset);
blinkFrame.x = -200;
blinkFrame.y = -200;
// Animate the blink frame
tween(blinkFrame, {
alpha: 0.8
}, {
duration: 50,
onFinish: function onFinish() {
tween(blinkFrame, {
alpha: 0
}, {
duration: 50,
onFinish: function onFinish() {
blinkFrame.destroy();
currentBlinkFrame++;
if (currentBlinkFrame < blinkFrames.length) {
_animateButtonBlink2();
}
}
});
}
});
}
};
// Increased hit area
// Start button blink animation
var blinkFrames = ['buttonblink', 'buttonblink2', 'buttonblink3', 'buttonblink4'];
var currentBlinkFrame = 0;
_animateButtonBlink2();
var currentTime = Date.now();
var timeSinceLastJump = currentTime - bloodmage.lastJumpTime;
// Check for double tap timing - initiate flight if conditions met regardless of movement
if (timeSinceLastJump < doubleTapThreshold) {
// Double tap detected - initiate flight mode
bloodmage.isFlying = true;
isFlying = true;
// Gentle lift to enter flight mode smoothly
bloodmage.velocityY = Math.min(bloodmage.velocityY, -8); // Smooth entry
bloodmage.grounded = false;
bloodmage.startFlyAnimation();
LK.getSound('jump').play();
bloodmage.jumpCount = 0; // Reset for next sequence
// Visual flight effect
var flightEffect = worldContainer.attachAsset('spell2', {
anchorX: 0.5,
anchorY: 0.5,
x: bloodmage.x,
y: bloodmage.y - 10,
alpha: 0.9,
scaleX: 0.8,
scaleY: 0.8
});
tween(flightEffect, {
alpha: 0,
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
flightEffect.destroy();
}
});
} else {
// Normal jump attempt
var jumpSuccess = bloodmage.jump();
if (jumpSuccess) {
bloodmage.lastJumpTime = currentTime;
bloodmage.jumpCount = 1; // Mark first jump for double-tap detection
}
}
}
// Check action button
localPos = LK.gui.bottomRight.toLocal({
x: x,
y: y
});
dx = localPos.x - -200;
dy = localPos.y - -450;
dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 150 && bloodmage && !isCasting) {
// Double-check door proximity with improved calculation
var doorDistance = doorAsset ? Math.sqrt(Math.pow(bloodmage.x - doorAsset.x, 2) + Math.pow(bloodmage.y - doorAsset.y, 2)) : 999;
if (doorDistance < 300) {
enterShop();
return; // Exit early to prevent casting
} else {
castPlayerSpell();
}
}
// Check shop icon
localPos = LK.gui.topRight.toLocal({
x: x,
y: y
});
dx = localPos.x - -300;
dy = localPos.y - 150;
dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 75) {
showShop();
}
};
game.move = function (x, y, obj) {
if (!trackpadPressed || battleMenuVisible || bropageShowing || shopShowing) return;
updateTrackpad(x, y);
};
game.up = function () {
// Handle jump button release for flight mode
if (jumpButtonHeld && isFlying && bloodmage) {
// Release flight mode with graceful descent
jumpButtonHeld = false;
isFlying = false;
bloodmage.isFlying = false;
bloodmage.stopFlyAnimation();
bloodmage.startFallAnimation();
// Apply realistic falling physics with parabolic arc
var currentUpwardVelocity = Math.min(bloodmage.velocityY, 0);
bloodmage.velocityY = Math.max(currentUpwardVelocity, 3); // Gentle fall start
// Maintain some horizontal momentum for natural arc
bloodmage.velocityX *= 0.7; // Slight air resistance
// Add falling visual effect
var fallEffect = worldContainer.attachAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: bloodmage.x,
y: bloodmage.y - 20,
alpha: 0.6,
scaleX: 1.5,
scaleY: 0.8
});
tween(fallEffect, {
alpha: 0,
y: bloodmage.y + 50,
scaleX: 0.8,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
fallEffect.destroy();
}
});
}
// Handle trackpad release
trackpadPressed = false;
if (bloodmage) {
bloodmage.velocityX = 0;
}
trackpadThumb.x = 200;
trackpadThumb.y = -200;
// Always reset jump button held state on any touch release
jumpButtonHeld = false;
};
function updateTrackpad(x, y) {
// Convert to local trackpad coordinates
var localPos = LK.gui.bottomLeft.toLocal({
x: x,
y: y
});
var dx = localPos.x - 200;
var dy = localPos.y - -200;
var dist = Math.sqrt(dx * dx + dy * dy);
// Constrain to trackpad area
if (dist > 80) {
dx = dx / dist * 80;
dy = dy / dist * 80;
}
// Update visual trackpad thumb
trackpadThumb.x = 200 + dx;
trackpadThumb.y = -200 + dy;
if (bloodmage && dist > 8) {
// Calculate angle for ALL 8 directions with proper recognition
trackpadAngle = Math.atan2(dy, dx);
if (bloodmage.isFlying && jumpButtonHeld) {
// REBUILT FLIGHT CONTROL: Pure 8-directional movement
var flightSpeed = bloodmage.speed * 0.8;
// Calculate movement components for ALL 8 directions
var moveX = Math.cos(trackpadAngle) * flightSpeed;
var moveY = Math.sin(trackpadAngle) * flightSpeed;
// CRITICAL: The flight movement is handled in bloodmage.update()
// Just store the trackpad state - NO direct position changes here
bloodmage.velocityX = 0;
bloodmage.velocityY = 0;
bloodmage.grounded = false;
// Update facing direction
if (moveX > 0) bloodmage.facingDirection = -1;else if (moveX < 0) bloodmage.facingDirection = 1;
} else {
// Ground movement - horizontal only
bloodmage.velocityX = dx / 80 * bloodmage.speed * 3.5;
}
}
}
// Layer management constants
var LAYER_BACKGROUNDS = 5;
var LAYER_PLATFORMS = 20;
var LAYER_PLAYER = 25;
// Function to maintain proper player layering
function updatePlayerLayer() {
if (!bloodmage) return;
// Always keep player in front of backgrounds and platforms, except when standing on a platform
var playerIndex = worldContainer.getChildIndex(bloodmage);
var maxIndex = worldContainer.children.length - 1;
var isOnPlatform = bloodmage.grounded;
if (isOnPlatform) {
// Find the highest platform the player is standing on
var highestPlatIndex = -1;
for (var i = 0; i < worldContainer.children.length; i++) {
var child = worldContainer.children[i];
if (child !== bloodmage && child.platformType) {
// Check if player is standing on this platform
var platLeft = child.x - (child.width ? child.width * (child.scale ? child.scale.x : 1) / 2 : 0);
var platRight = child.x + (child.width ? child.width * (child.scale ? child.scale.x : 1) / 2 : 0);
var platTop = child.y - (child.height ? child.height / 2 : 0);
var platBottom = child.y + (child.height ? child.height / 2 : 0);
var mageLeft = bloodmage.x - 40;
var mageRight = bloodmage.x + 40;
var mageBottom = bloodmage.y;
if (mageRight > platLeft && mageLeft < platRight && mageBottom >= platTop && mageBottom <= platBottom) {
var platIndex = worldContainer.getChildIndex(child);
if (platIndex > highestPlatIndex) highestPlatIndex = platIndex;
}
}
}
// Place player just above the highest platform found, or at maxIndex if none
var targetIndex = highestPlatIndex >= 0 ? highestPlatIndex + 1 : maxIndex;
// Clamp targetIndex to valid range
targetIndex = Math.min(targetIndex, maxIndex);
if (playerIndex !== targetIndex && targetIndex >= 0 && targetIndex <= maxIndex) {
worldContainer.setChildIndex(bloodmage, targetIndex);
}
return;
}
// When not on platform: player should always be in front of backgrounds and platforms, but behind neonsigns and platforms
// Find the highest platform index
var highestPlatformIndex = -1;
for (var i = 0; i < worldContainer.children.length; i++) {
var child = worldContainer.children[i];
if (child !== bloodmage && child.platformType) {
var platIndex = worldContainer.getChildIndex(child);
if (platIndex > highestPlatformIndex) highestPlatformIndex = platIndex;
}
}
// Find the highest neonsign index
var highestNeonIndex = -1;
for (var i = 0; i < worldContainer.children.length; i++) {
var child = worldContainer.children[i];
if (child !== bloodmage && child.texture && child.texture.baseTexture && child.texture.baseTexture.resource && child.texture.baseTexture.resource.url) {
var url = child.texture.baseTexture.resource.url;
if (url.indexOf('neonSign') !== -1 || url.indexOf('neonsign') !== -1) {
var neonIndex = worldContainer.getChildIndex(child);
if (neonIndex > highestNeonIndex) highestNeonIndex = neonIndex;
}
}
}
// Player should be in front of backgrounds, buildings, neonsigns, but behind platforms (unless standing on one)
var targetIndex = Math.max(highestPlatformIndex, highestNeonIndex) + 1;
// Clamp targetIndex to valid range
targetIndex = Math.min(targetIndex, maxIndex);
if (playerIndex !== targetIndex && targetIndex >= 0 && targetIndex <= maxIndex) {
worldContainer.setChildIndex(bloodmage, targetIndex);
}
}
game.update = function () {
if (!battleMenuVisible) {
checkCollisions();
updatePlayerLayer();
updateCamera();
// Update action button text based on door proximity
if (bloodmage && actionText && doorAsset) {
var dx = Math.abs(bloodmage.x - doorAsset.x);
var dy = Math.abs(bloodmage.y - doorAsset.y);
var distance = Math.sqrt(dx * dx + dy * dy);
var nearDoor = distance < 300;
actionText.setText(nearDoor ? 'ENTER' : 'CAST');
}
// Check if we need to generate more level
if (bloodmage && bloodmage.x > lastGeneratedX - 2000) {
chunkStartX = lastGeneratedX;
generateLevelChunk(chunkStartX);
cleanupOldElements();
}
}
// Track lastX/lastY for moving platforms so player can move with them
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
if (typeof platform.x !== "undefined" && typeof platform.y !== "undefined") {
if (typeof platform.lastX === "undefined") platform.lastX = platform.x;
if (typeof platform.lastY === "undefined") platform.lastY = platform.y;
// Save current position for next frame
platform.lastX = platform.x;
platform.lastY = platform.y;
}
}
// Animate clouds drifting with proper layering maintenance
for (var i = 0; i < clouds.length; i++) {
var cloud = clouds[i];
cloud.x += cloud.driftSpeed;
// Ensure clouds ALWAYS stay in foreground - behind player and platforms, never behind backgrounds
var totalChildren = worldContainer.children.length;
var playerIndex = worldContainer.getChildIndex(bloodmage);
var minForegroundIndex = Math.max(10, playerIndex - 2); // Just behind player/platforms
var currentIndex = worldContainer.getChildIndex(cloud);
if (currentIndex < minForegroundIndex) {
worldContainer.setChildIndex(cloud, minForegroundIndex);
}
// For procedurally generated clouds, wrap them based on current view
var rightEdge = -cameraX + 3072;
var leftEdge = -cameraX - 1024;
if (cloud.x > rightEdge) {
cloud.x = leftEdge;
}
}
// Update spell projectiles
for (var i = spellProjectiles.length - 1; i >= 0; i--) {
var projectile = spellProjectiles[i];
projectile.x += projectile.velocity;
// Check if projectile is off screen
var screenLeft = -cameraX - 500;
var screenRight = -cameraX + 2548;
if (projectile.x < screenLeft || projectile.x > screenRight) {
projectile.destroy();
spellProjectiles.splice(i, 1);
continue;
}
// Check collision with hunks
for (var j = 0; j < hunks.length; j++) {
var hunk = hunks[j];
if (!hunk.captured && projectile.intersects(hunk)) {
// Reduce hunk resistance
hunk.resistance = Math.max(0, hunk.resistance - 25);
// Create visual effect
var effect = worldContainer.attachAsset('spell2', {
anchorX: 0.5,
anchorY: 0.5,
x: hunk.x,
y: hunk.y - 50,
alpha: 1
});
// Animate effect
tween(effect, {
alpha: 0,
scaleX: 2,
scaleY: 2,
rotation: Math.PI * 2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
effect.destroy();
}
});
// Flash the hunk
LK.effects.flashObject(hunk, 0xFF00FF, 300);
// Show resistance bar above hunk temporarily
var resistanceText = new Text2('RESISTANCE: ' + hunk.resistance + '/' + hunk.maxResistance, {
size: 60,
fill: hunk.resistance < 30 ? 0x00FF00 : 0xFF0000
});
resistanceText.anchor.set(0.5, 0.5);
resistanceText.x = hunk.x;
resistanceText.y = hunk.y - 150;
worldContainer.addChild(resistanceText);
// Auto-hide resistance text after 2 seconds
tween(resistanceText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
resistanceText.destroy();
}
});
// Destroy projectile
projectile.destroy();
spellProjectiles.splice(i, 1);
break;
}
}
}
};
// Combat menu navigation functions removed - using direct touch interaction
function selectCurrentOption() {
if (!battleMenuVisible || !currentHunk) return;
var selectedOption = window.currentSelectedOption;
if (selectedOption === 0) {
// Enthrall (capture function) with enhanced animations
performCaptureAnimation();
} else if (selectedOption === 1) {
// Cast - perform casting animation
performBattleCast();
} else if (selectedOption === 2) {
// Speak - show dialogue with player response
showDualDialogue();
} else if (selectedOption === 3) {
// Leave - exit battle UI
hideBattleUI();
currentHunk = null;
}
}
function performCaptureAnimation() {
if (!currentHunk) return;
// Store player position to prevent falling
var castX = bloodmage.x;
var castY = bloodmage.y;
var wasGrounded = bloodmage.grounded;
// Disable physics completely during battle action
bloodmage.x = castX;
bloodmage.y = castY;
bloodmage.grounded = wasGrounded;
bloodmage.velocityX = 0;
bloodmage.velocityY = 0;
bloodmage.isMoving = false;
bloodmage.isFlying = false;
// Perform cast animation first
performPlayerCastAnimation();
// Throw capture device animation
var captureDevice = worldContainer.attachAsset('capturedevice', {
anchorX: 0.5,
anchorY: 0.5,
x: bloodmage.x,
y: bloodmage.y - 100,
scaleX: 1.5,
scaleY: 1.5
});
// Animate device flying toward hunk
tween(captureDevice, {
x: currentHunk.x,
y: currentHunk.y - 50,
rotation: Math.PI * 2
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create capture effect
var captureEffect = worldContainer.attachAsset('captureeffect', {
anchorX: 0.5,
anchorY: 0.5,
x: currentHunk.x,
y: currentHunk.y - 50,
scaleX: 0.1,
scaleY: 0.1
});
// Animate effect expanding
tween(captureEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0.8
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// RNG based on hunk's resistance meter
var captureChance = 1 - currentHunk.resistance / currentHunk.maxResistance;
var captureRoll = Math.random();
if (captureRoll < captureChance) {
// Success - shrink hunk into device
LK.getSound('capture').play();
tween(currentHunk, {
scaleX: 0.1,
scaleY: 0.1,
x: captureDevice.x,
y: captureDevice.y
}, {
duration: 400,
easing: tween.easeIn,
onFinish: function onFinish() {
currentHunk.captured = true;
currentHunk.visible = false;
capturedHunks.push(currentHunk.hunkType);
storage.capturedHunks = capturedHunks;
if (!brostiary[currentHunk.hunkType]) {
brostiary[currentHunk.hunkType] = true;
storage.brostiary = brostiary;
}
// scoreText removed - using Brostiary for hunk tracking instead
showXPMenu(true);
captureDevice.destroy();
captureEffect.destroy();
// Restore player position and state
bloodmage.x = castX;
bloodmage.y = castY;
bloodmage.grounded = wasGrounded;
bloodmage.velocityX = 0;
bloodmage.velocityY = 0;
}
});
} else {
// Failed - device bounces off hunk
tween(captureDevice, {
x: captureDevice.x + (captureDevice.x - currentHunk.x) * 0.5,
y: captureDevice.y - 100,
rotation: Math.PI * 4
}, {
duration: 400,
easing: tween.bounceOut,
onFinish: function onFinish() {
captureDevice.destroy();
}
});
// Break free animation
var breakEffect = worldContainer.attachAsset('effect', {
anchorX: 0.5,
anchorY: 0.5,
x: currentHunk.x,
y: currentHunk.y,
scaleX: 2,
scaleY: 2
});
LK.effects.flashScreen(0xFF0000, 500);
tween(breakEffect, {
alpha: 0,
scaleX: 4,
scaleY: 4
}, {
duration: 600,
onFinish: function onFinish() {
breakEffect.destroy();
captureEffect.destroy();
// Restore player position
bloodmage.x = castX;
bloodmage.y = castY;
bloodmage.grounded = wasGrounded;
}
});
}
}
});
}
});
}
function performBattleCast() {
if (!currentHunk) return;
// Store player position and state to prevent falling
var playerGrounded = bloodmage.grounded;
var playerX = bloodmage.x;
var playerY = bloodmage.y;
var playerVelY = bloodmage.velocityY;
// Cast spell animation with spell behind hunk
var spellBehind = worldContainer.attachAsset('spell', {
anchorX: 0.5,
anchorY: 0.5,
x: currentHunk.x,
y: currentHunk.y,
scaleX: 5,
scaleY: 5,
alpha: 0.8
});
worldContainer.setChildIndex(spellBehind, worldContainer.getChildIndex(currentHunk) - 1);
// Pulsing spell2 at bottom of hunk
var spellPulse = worldContainer.attachAsset('spell2', {
anchorX: 0.5,
anchorY: 0.5,
x: currentHunk.x,
y: currentHunk.y + 50,
scaleX: 0.5,
scaleY: 0.5
});
// Animate pulsing
function pulseSpell() {
tween(spellPulse, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 1
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(spellPulse, {
scaleX: 0.5,
scaleY: 0.5,
alpha: 0.6
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: pulseSpell
});
}
});
}
pulseSpell();
// Perform cast animation while keeping player in place
performPlayerCastAnimation();
// Damage hunk with additional sound effect
castSpell(25);
LK.getSound('climax').play();
// Clean up after 2 seconds
LK.setTimeout(function () {
if (spellBehind) spellBehind.destroy();
if (spellPulse) spellPulse.destroy();
// Restore player position and state
bloodmage.x = playerX;
bloodmage.y = playerY;
bloodmage.velocityY = playerGrounded ? 0 : playerVelY;
bloodmage.grounded = playerGrounded;
}, 2000);
}
function showDualDialogue() {
if (!currentHunk) return;
// Player dialogue floating above head
var playerDialogue = worldContainer.attachAsset('dialoguebox', {
anchorX: 0.5,
anchorY: 1.0,
x: bloodmage.x,
y: bloodmage.y - 400,
scaleX: 6,
scaleY: 4,
alpha: 0
});
// Animate dialogue appearing
tween(playerDialogue, {
alpha: 1,
y: bloodmage.y - 450
}, {
duration: 300,
easing: tween.easeOut
});
var playerText = new Text2('SUBMIT TO MY WILL!', {
size: 18,
fill: 0xFF00FF,
fontWeight: 'bold',
wordWrap: true,
wordWrapWidth: 120
});
playerText.anchor.set(0.5, 0.5);
playerText.x = 0;
playerText.y = 0;
playerDialogue.addChild(playerText);
// Hunk dialogue floating above head
var hunkDialogue = worldContainer.attachAsset('dialoguebox', {
anchorX: 0.5,
anchorY: 1.0,
x: currentHunk.x,
y: currentHunk.y - 400,
scaleX: 8,
scaleY: 6,
alpha: 0
});
// Animate dialogue appearing with slight delay
LK.setTimeout(function () {
tween(hunkDialogue, {
alpha: 1,
y: currentHunk.y - 450
}, {
duration: 300,
easing: tween.easeOut
});
}, 200);
var hunkNames = ['FUNGI-GUY', 'DJINN-TONIC', 'MINO-THROBBER', 'IGUANA-THRUST'];
var hunkDialogues = ['NEVER! MY SPORES PROTECT ME!', 'YOUR MAGIC IS WEAK, MORTAL!', 'I BOW TO NO ONE!', 'YOU CANNOT TAME THIS BEAST!'];
var hunkNameText = new Text2(hunkNames[currentHunk.hunkType], {
size: 32,
fill: 0xFF1493,
fontWeight: 'bold'
});
hunkNameText.anchor.set(0.5, 0);
hunkNameText.y = -40;
hunkDialogue.addChild(hunkNameText);
var hunkText = new Text2(hunkDialogues[currentHunk.hunkType], {
size: 20,
fill: 0xFFFFFF,
fontWeight: 'bold',
wordWrap: true,
wordWrapWidth: 160
});
hunkText.anchor.set(0.5, 0.5);
hunkText.y = 10;
hunkDialogue.addChild(hunkText);
// Play grunt sound for hunk dialogue
LK.getSound('grunt').play();
// Auto-hide both dialogues after 4 seconds with fade out
LK.setTimeout(function () {
tween(playerDialogue, {
alpha: 0,
y: bloodmage.y - 500
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (playerDialogue) playerDialogue.destroy();
}
});
tween(hunkDialogue, {
alpha: 0,
y: currentHunk.y - 500
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (hunkDialogue) hunkDialogue.destroy();
}
});
}, 4000);
}
function showXPMenu(captureSuccess) {
// Create XP menu overlay
var xpMenuOverlay = game.addChild(new Container());
var xpBg = xpMenuOverlay.attachAsset('xpmenu', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 12,
scaleY: 10,
alpha: 0
});
tween(xpBg, {
alpha: 0.95
}, {
duration: 500,
easing: tween.easeOut
});
// Calculate XP earned
var baseXP = 100;
var bonusXP = captureSuccess ? 50 : 0;
var totalXP = baseXP + bonusXP;
// XP text positioned within menu boundaries
var xpText = new Text2('EXPERIENCE GAINED: ' + totalXP, {
size: 60,
fill: 0x00FF00,
fontWeight: 'bold',
wordWrap: true,
wordWrapWidth: 600
});
xpText.anchor.set(0.5, 0.5);
xpText.x = 1024;
xpText.y = 1300;
xpText.alpha = 0;
xpMenuOverlay.addChild(xpText);
tween(xpText, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
if (captureSuccess) {
var successText = new Text2('HUNK CAPTURED!', {
size: 100,
fill: 0xFF00FF,
fontWeight: 'bold'
});
successText.anchor.set(0.5, 0.5);
successText.x = 1024;
successText.y = 1400;
successText.alpha = 0;
xpMenuOverlay.addChild(successText);
tween(successText, {
alpha: 1
}, {
duration: 800,
easing: tween.easeOut
});
}
// Continue button
var continueBtn = new Text2('CONTINUE', {
size: 60,
fill: 0xFFFFFF,
fontWeight: 'bold'
});
continueBtn.anchor.set(0.5, 0.5);
continueBtn.x = 1024;
continueBtn.y = 1600;
continueBtn.interactive = true;
continueBtn.alpha = 0;
continueBtn.down = function () {
xpMenuOverlay.destroy();
hideBattleUI();
};
xpMenuOverlay.addChild(continueBtn);
tween(continueBtn, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeOut
});
}
function showHunkDialogue() {
var hunkNames = ['FUNGI-GUY', 'DJINN-TONIC', 'MINO-THROBBER', 'IGUANA-THRUST'];
var hunkDialogues = ['READY TO GET FUNGAL?', 'YOUR WISH IS MY COMMAND~', 'PREPARE TO BE DOMINATED!', 'TASTE MY SCALES~'];
// Create dialogue box only when speak is selected with proper sizing
var hunkDialogueBox = worldContainer.attachAsset('dialoguebox', {
anchorX: 0.5,
anchorY: 0.5,
x: currentHunk.x,
y: currentHunk.y - 200,
scaleX: 8,
scaleY: 6
});
var hunkNameText = new Text2(hunkNames[currentHunk.hunkType], {
size: 32,
fill: 0xFF1493,
fontWeight: 'bold'
});
hunkNameText.anchor.set(0.5, 0);
hunkNameText.y = -40;
hunkDialogueBox.addChild(hunkNameText);
var hunkText = new Text2(hunkDialogues[currentHunk.hunkType], {
size: 24,
fill: 0xFFFFFF,
fontWeight: 'bold',
wordWrap: true,
wordWrapWidth: 200
});
hunkText.anchor.set(0.5, 0.5);
hunkText.y = 20;
hunkDialogueBox.addChild(hunkText);
// Auto-hide dialogue after 3 seconds
LK.setTimeout(function () {
if (hunkDialogueBox) {
hunkDialogueBox.destroy();
}
}, 3000);
}
LK.playMusic('hedonism');
// REBUILT TITLE ANIMATION: Proper spacing and squeeze effect with wave momentum
function showTitle() {
var titleContainer = game.addChild(new Container());
// Letter positions for "NOCTURNE CITY" - NOCTURNE moved 0.5 inch left with proper spacing
var letters = [{
asset: 'n',
finalX: -600,
// NOCTURNE moved left 0.5 inch
startX: 2500 // Start from right side for squeeze effect
}, {
asset: 'o',
finalX: -500,
startX: 2500
}, {
asset: 'c',
finalX: -400,
startX: 2500
}, {
asset: 't',
finalX: -300,
startX: 2500
}, {
asset: 'u',
finalX: -200,
startX: 2500
}, {
asset: 'r',
finalX: -100,
startX: 2500
}, {
asset: 'n',
finalX: 0,
startX: 2500
}, {
asset: 'e',
finalX: 100,
startX: 2500
}, {
// Space between NOCTURNE and CITY - CITY in original position
asset: 'c',
finalX: 300,
// Proper gap between words
startX: 3000
}, {
asset: 'i',
finalX: 400,
startX: 3000
}, {
asset: 't',
finalX: 500,
startX: 3000
}, {
asset: 'y',
finalX: 600,
startX: 3000
}];
var letterAssets = [];
// Create all letters starting from right side
for (var i = 0; i < letters.length; i++) {
var letter = titleContainer.attachAsset(letters[i].asset, {
anchorX: 0.5,
anchorY: 0.5,
x: letters[i].startX,
y: 600,
// Above player position
scaleX: 0.5,
// Start small for squeeze effect
scaleY: 0.5,
alpha: 0
});
letterAssets.push(letter);
}
// Animate letters with wave momentum and squeeze effect
function animateSqueezeIn() {
for (var i = 0; i < letterAssets.length; i++) {
var letter = letterAssets[i];
var finalX = 1024 + letters[i].finalX;
// Each letter appears with staggered timing for wave effect
(function (index, targetLetter, targetX) {
LK.setTimeout(function () {
// Fade in letter
tween(targetLetter, {
alpha: 1
}, {
duration: 200,
easing: tween.easeOut
});
// Squeeze animation - expand and move to final position
tween(targetLetter, {
x: targetX,
scaleX: 2.8,
// Final scale
scaleY: 2.8
}, {
duration: 600,
easing: tween.bounceOut // Wave momentum effect
});
}, index * 120); // Staggered timing
})(i, letter, finalX);
}
// Hold for 3 seconds then fade out left to right
LK.setTimeout(function () {
animateLettersOut();
}, 3500);
}
// Animate letters fading out from left to right
function animateLettersOut() {
for (var i = 0; i < letterAssets.length; i++) {
(function (index, targetLetter) {
LK.setTimeout(function () {
tween(targetLetter, {
alpha: 0,
y: 400,
scaleX: 0.2,
scaleY: 0.2
}, {
duration: 300,
easing: tween.easeIn
});
}, index * 80);
})(i, letterAssets[i]);
}
// Clean up after fade
LK.setTimeout(function () {
titleContainer.destroy();
}, 2000);
}
// Start the squeeze animation
animateSqueezeIn();
}
// Show title animation on game start
showTitle();
;
var placeholderInteriorShowing = false;
var placeholderOverlay = null;
var interiorShowing = false;
var interiorOverlay = null;
function showInterior(interiorAsset) {
if (interiorShowing) return;
interiorShowing = true;
// Create interior overlay with door expand animation
var transitionOverlay = game.addChild(new Container());
var expandingDoor = transitionOverlay.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0.8
});
// Animate door expanding
tween(expandingDoor, {
scaleX: 5.0,
scaleY: 5.0,
alpha: 0.95
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Create actual interior overlay
interiorOverlay = game.addChild(new Container());
var interior = interiorOverlay.attachAsset(interiorAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366,
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
});
tween(interior, {
alpha: 1
}, {
duration: 600,
easing: tween.easeOut
});
// Hide brostiary icon when interior is showing
if (brostiaryIcon) {
brostiaryIcon.alpha = 0;
}
// Add exit button
var exitBtn = interiorOverlay.attachAsset('x', {
anchorX: 0.5,
anchorY: 0.5,
x: 1800,
y: 300,
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
});
exitBtn.interactive = true;
exitBtn.down = function () {
closeInterior();
};
tween(exitBtn, {
alpha: 1
}, {
duration: 500,
easing: tween.easeOut
});
// Cleanup transition
transitionOverlay.destroy();
}
});
}
function closeInterior() {
if (!interiorShowing || !interiorOverlay) return;
interiorShowing = false;
// Show brostiary icon again
if (brostiaryIcon) {
brostiaryIcon.alpha = 1;
}
interiorOverlay.destroy();
interiorOverlay = null;
}
function showPlaceholderInterior() {
showInterior('interior');
}
function performPlayerCastAnimation() {
if (!bloodmage || isCasting) return;
isCasting = true;
LK.getSound('spell').play();
// Store player position to maintain it during animation
var castX = bloodmage.x;
var castY = bloodmage.y;
var wasGrounded = bloodmage.grounded;
// Stop all animations first
bloodmage.stopIdleAnimation();
bloodmage.stopWalkingAnimation();
bloodmage.stopJumpAnimation();
// Hide ALL assets in bloodmage to ensure nothing remains visible
for (var i = 0; i < bloodmage.children.length; i++) {
bloodmage.children[i].alpha = 0;
}
// Hide all other frames
for (var i = 0; i < bloodmage.idleFrames.length; i++) {
bloodmage.idleFrames[i].alpha = 0;
}
for (var i = 0; i < bloodmage.walkFrames.length; i++) {
bloodmage.walkFrames[i].alpha = 0;
}
for (var i = 0; i < bloodmage.jumpFrames.length; i++) {
bloodmage.jumpFrames[i].alpha = 0;
}
// Create cast animation frames
var castFrames = ['cast', 'cast2', 'cast3', 'cast4', 'cast5', 'cast6'];
var castAnimFrames = [];
for (var i = 0; i < castFrames.length; i++) {
var frame = worldContainer.attachAsset(castFrames[i], {
anchorX: 0.5,
anchorY: 1.0,
x: castX,
y: castY,
alpha: 0
});
frame.scale.x = bloodmage.facingDirection;
castAnimFrames.push(frame);
}
// Animate through cast frames
var currentCastFrame = 0;
function animateCast() {
if (currentCastFrame < castAnimFrames.length) {
// Keep player in exact position
bloodmage.x = castX;
bloodmage.y = castY;
bloodmage.grounded = wasGrounded;
bloodmage.velocityY = 0;
// Fade out previous frame
if (currentCastFrame > 0) {
tween(castAnimFrames[currentCastFrame - 1], {
alpha: 0
}, {
duration: 100,
easing: tween.easeInOut
});
}
// Fade in current frame
var currentFrame = castAnimFrames[currentCastFrame];
tween(currentFrame, {
alpha: 1
}, {
duration: 100,
easing: tween.easeInOut
});
currentCastFrame++;
LK.setTimeout(animateCast, 120);
} else {
// Clean up cast animation
for (var i = 0; i < castAnimFrames.length; i++) {
castAnimFrames[i].destroy();
}
isCasting = false;
// Restore player position one final time
bloodmage.x = castX;
bloodmage.y = castY;
bloodmage.grounded = wasGrounded;
bloodmage.velocityY = 0;
// Resume idle animation
if (!bloodmage.isMoving && bloodmage.grounded) {
bloodmage.startIdleAnimation();
}
}
}
animateCast();
} ===================================================================
--- original.js
+++ change.js
@@ -3264,8 +3264,22 @@
connectsTo: {
x: 5200,
y: 1000
}
+ }, {
+ x: 6500,
+ y: 900,
+ connectsTo: {
+ x: 6800,
+ y: 800
+ }
+ }, {
+ x: 7100,
+ y: 950,
+ connectsTo: {
+ x: 7400,
+ y: 850
+ }
}];
// Add stairs as solid collision platforms with proper foreground positioning
for (var i = 0; i < stairsPositions.length; i++) {
var stairs = new Stairs();
@@ -3325,8 +3339,74 @@
interiorHunk.isInterior = true;
hunks.push(interiorHunk);
worldContainer.addChild(interiorHunk);
}
+ // Add doors to forest biome (east side)
+ for (var d = 0; d < 3; d++) {
+ var forestDoor = worldContainer.attachAsset('door', {
+ anchorX: 0.5,
+ anchorY: 1.0,
+ x: 2800 + d * 1200,
+ y: 1300,
+ scaleX: 0.7,
+ scaleY: 0.7
+ });
+ forestDoor.interactive = true;
+ forestDoor.interiorAsset = 'interior' + (Math.floor(Math.random() * 8) + 1);
+ forestDoor.down = function () {
+ showInterior(this.interiorAsset);
+ };
+ // Ensure door stays in foreground
+ var totalChildren = worldContainer.children.length;
+ worldContainer.setChildIndex(forestDoor, Math.max(totalChildren - 1, LAYER_PLATFORMS + 5));
+ }
+ // Add doors to cosmic biome (north side)
+ for (var d = 0; d < 2; d++) {
+ var cosmicDoor = worldContainer.attachAsset('door2', {
+ anchorX: 0.5,
+ anchorY: 1.0,
+ x: 5400 + d * 1500,
+ y: 500,
+ scaleX: 0.6,
+ scaleY: 0.6
+ });
+ cosmicDoor.interactive = true;
+ cosmicDoor.interiorAsset = 'interior' + (Math.floor(Math.random() * 8) + 1);
+ cosmicDoor.down = function () {
+ showInterior(this.interiorAsset);
+ };
+ // Ensure door stays in foreground
+ var totalChildren = worldContainer.children.length;
+ worldContainer.setChildIndex(cosmicDoor, Math.max(totalChildren - 1, LAYER_PLATFORMS + 5));
+ }
+ // Add stairs to forest connecting platforms
+ var forestStairs = [{
+ x: 2650,
+ y: 1200
+ }, {
+ x: 3850,
+ y: 1050
+ }];
+ for (var fs = 0; fs < forestStairs.length; fs++) {
+ var forestStair = new Stairs();
+ forestStair.x = forestStairs[fs].x;
+ forestStair.y = forestStairs[fs].y;
+ forestStair.scale.x = 0.8;
+ forestStair.scale.y = 0.6;
+ platforms.push({
+ x: forestStair.x,
+ y: forestStair.y,
+ width: forestStair.width,
+ height: forestStair.height,
+ scale: forestStair.scale,
+ intersects: function intersects(other) {
+ return forestStair.intersects ? forestStair.intersects(other) : false;
+ }
+ });
+ worldContainer.addChild(forestStair);
+ var totalChildren = worldContainer.children.length;
+ worldContainer.setChildIndex(forestStair, Math.max(totalChildren - 1, LAYER_PLATFORMS));
+ }
bloodmage = new Bloodmage();
bloodmage.x = 400; // Start on first platform
bloodmage.y = 1780;
bloodmage.grounded = true; // Ensure character starts on ground
@@ -3378,8 +3458,31 @@
}
}
// Position player in foreground after all reorganization
worldContainer.setChildIndex(bloodmage, LAYER_PLAYER);
+ // Consolidate layer management - push all existing backgrounds to proper layer
+ for (var i = 0; i < worldContainer.children.length; i++) {
+ var child = worldContainer.children[i];
+ if (child && !child.platformType && child !== bloodmage && !child.hunkType && child.x !== undefined && child.y !== undefined) {
+ var isSpecial = false;
+ // Don't move platforms or hunks or player
+ for (var j = 0; j < platforms.length; j++) {
+ if (platforms[j] === child) {
+ isSpecial = true;
+ break;
+ }
+ }
+ for (var j = 0; j < hunks.length; j++) {
+ if (hunks[j] === child) {
+ isSpecial = true;
+ break;
+ }
+ }
+ if (!isSpecial && child !== bloodmage) {
+ worldContainer.setChildIndex(child, LAYER_BACKGROUNDS);
+ }
+ }
+ }
// Always keep player in front of backgrounds and platforms except when standing on a platform
updatePlayerLayer();
// Verify idle frames exist and are properly set up (playeridle to playeridle12)
if (bloodmage.idleFrames.length >= 6) {
@@ -3404,11 +3507,11 @@
scaleY: 1.2 + Math.random() * 1.2
});
cosmic.alpha = 0.5 + Math.random() * 0.3;
backgroundElements.push(cosmic);
- worldContainer.setChildIndex(cosmic, 2);
+ worldContainer.setChildIndex(cosmic, LAYER_BACKGROUNDS);
}
- // Deploy forest assets to the east (right) of city backgrounds
+ // Deploy forest assets to the east (right) of city backgrounds with platforms on top
for (var i = 0; i < 6; i++) {
var forestAsset = ['forest', 'forest2', 'forest3', 'forest5', 'trees', 'trees2', 'trees3', 'trees4'][Math.floor(Math.random() * 8)];
var forest = worldContainer.attachAsset(forestAsset, {
anchorX: 0.5,
@@ -3419,9 +3522,16 @@
scaleY: 1.2 + Math.random() * 1.2
});
forest.alpha = 0.5 + Math.random() * 0.3;
backgroundElements.push(forest);
- worldContainer.setChildIndex(forest, 2);
+ worldContainer.setChildIndex(forest, LAYER_BACKGROUNDS);
+ // Add platform on top of forest asset
+ var forestPlatform = new Platform();
+ forestPlatform.x = forest.x;
+ forestPlatform.y = forest.y - forest.height * forest.scale.y / 2 - 50;
+ platforms.push(forestPlatform);
+ worldContainer.addChild(forestPlatform);
+ worldContainer.setChildIndex(forestPlatform, LAYER_PLATFORMS);
}
// Deploy ocean assets to the west (left) of city backgrounds with enhanced layering
for (var i = 0; i < 6; i++) {
var oceanAsset = ['sea', 'sea2', 'sea3', 'sea4', 'sea5', 'sea6'][Math.floor(Math.random() * 6)];
@@ -3434,9 +3544,9 @@
scaleY: 1.2 + Math.random() * 1.2
});
ocean.alpha = 0.5 + Math.random() * 0.3;
backgroundElements.push(ocean);
- worldContainer.setChildIndex(ocean, 2);
+ worldContainer.setChildIndex(ocean, LAYER_BACKGROUNDS);
}
// Add sea transition area to the left with layered sea backgrounds and sea platforms
// Ensure startX is defined for this scope - use the parameter directly
if (startX < -1000) {
Neon cyberpunk App icon BROSTIARY encyclopedia of black leather biker jacket longsleeves shirtless musxles pants boots hunk outline linework glowing 3d hologram flat
Neon cyberpunk magic projectile effect
Idle animation, sleek graceful man Cyberpunk manga, facing forward idle pose shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, action shot wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model pose
Idle animation, sleek graceful man Cyberpunk manga, facing forward idle pose shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, action shot wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model pose
Neon cyberpunk anime city skyline futuristic holograms
Swishy graceful man Cyberpunk manga, wand pointed straight ahead, arm extended, jump attack, side profile view attack animation, shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, action shot wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model running pose
Cyberpunk manga man blonde undercut pompadour shiny black leather biker jacket longsleeves shirtless thin abs pecs necklaces gemstone tipped wand side profile view, action shot wand pointed straight in front arm extended wand casting spell feet planted, standing upright fierce fashion pose animation blonde undercut pompadour, vampire fangs, wine red joggers, combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and shoes, fully within the frame" Character fully contained within a square frame, no edges cut off,
Neon cyberpunk rectangular empty outline flat two crystals hologram 2 glowing Diamonds on each side symmetrical selection highlight overlay
cyberpunk neon anime metropolis skyline holograms billboards, occult-capitalism-consumerism imagery nighttime futuristic architecture glow
Neon cyberpunk magic spell effect 3d hologram
Neon cyberpunk 3d magical glowing crystal gemstone hologram orb sphere
Swishy graceful man Cyberpunk manga, idle animation, shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model idle pose
Neon cyberpunk mobile game trackpad too down flat 3d 2d hologram futuristic magic occult chic
round directional arrows Neon cyberpunk mobile game trackpad too down flat 3d 2d hologram futuristic magic occult chic
Neon cyberpunk skyscraper occult capitalist 3d hologram billboards futuristic elaborate architecture multidimensional towering city spires glowing
Neon cyberpunk futuristic glowing side-view 2d platformer style platform flat top
Neon cyberpunk futuristic glowing side-view 2d platformer style platform flat top hologram projection hovering hover platform antigravity jet thrusters
Neon cyberpunk button icon magic spell effect 3d hologram
Neon cyberpunk bag inventory button icon magic glowing futuristic 3d hologram
Same pose Limb positions variations, Front arm in front of body, front leg extended behind, back leg in front, limbs positions swapped, Swishy graceful man Cyberpunk manga, side profile view walking animation, shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model idle pose
man Cyberpunk manga, minotaur monster hunk, shiny black leather biker jacket longsleeves shirtless muscles, jockstrap combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from horns to hooves, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model idle pose
Neon cyberpunk mobile game button magic gemstone crystal sigil eyeball heart triangle topdown flat 3d 2d hologram futuristic glowing occult chic
idle animation hunky Minotaur man Cyberpunk manga, minotaur monster hunk, shiny black leather biker jacket longsleeves shirtless muscles, jockstrap combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from horns to hooves, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model idle pose
idle animation hunky Minotaur man Cyberpunk manga, minotaur monster hunk, shiny black leather biker jacket longsleeves shirtless muscles, jockstrap combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from horns to hooves, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model idle pose
idle animation hunky Minotaur man Cyberpunk manga, minotaur monster hunk, shiny black leather biker jacket longsleeves shirtless muscles, jockstrap combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from horns to hooves, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model idle pose
idle animation hunky Minotaur man Cyberpunk manga, minotaur monster hunk, shiny black leather biker jacket longsleeves shirtless muscles, jockstrap combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from horns to hooves, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model idle pose
Swishy graceful man Cyberpunk manga, wand pointed straight ahead, arm extended, side profile view attack animation, shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, action shot wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model walking pose Style of vogue magazine
Black leather combat boots, wine red joggers, Swishy graceful man Cyberpunk manga, arm extended, side profile view walking animation, shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, action shot wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model walking pose Style of vogue magazine
Neon cyberpunk mobile game menu overlay boxes for CAST, SPEAK, CALL, ENTHRALL, LEAVE magic gemstone crystal sigil eyeball heart triangle topdown flat 3d 2d hologram futuristic glowing occult chic HOLOGRAM menu
Neon cyberpunk door shop entryway storefront entrance doorway
Neon cyberpunk blank empty videogame meter hologram glowing
Magical spell effect 3d cyberpunk hologram sacred geometry diagram tree of life
Neon cyberpunk experience menu form blank boxes labeled space for current level, experience gained, flat 2d 3d hologram glowing futuristic occult sigil heart eye triangle circle magick
Swishy graceful man Cyberpunk manga, side profile view, flying flight levitation action shot shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, dynamic motion lifting off ground wine red joggers combat boots, tilting forward legs relaxed toes pointed arms relaxed at sides, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model pose style of Vogue magazine
Swishy graceful man Cyberpunk manga, side profile view, flying flight levitation magic spell action shot shiny black leather biker jacket longsleeves shirtless Skinny abs blonde pompadour guy, dynamic motion lifting off ground wine red joggers combat boots, gemstone tipped magic wand heart triangle eye sigil hologram 3d neon, tilting forward legs relaxed toes pointed arms relaxed at sides, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including face and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model pose style of Vogue magazine
3d hologram neon cyberpunk starlight magick galaxy overlay projection asteroid starfield Platforming magical dimension pathway level obstacle layout videogame
3d hologram neon cyberpunk starlight magick galaxy overlay projection asteroid starfield Platforming magical dimension pathway level obstacle layout videogame
neon galaxy projection 3d simulation glowing swirling nebulas supernova solar system outer space purple cyan stars black holes esoteric magick sigils sparkle glowing cyberpunk starlight magick galaxy overlay projection asteroid starfield swirling background magical dimension pathway level obstacle layout videogame
3d hologram neon cyberpunk starlight magick galaxy overlay projection asteroid starfield Platforming magical dimension pathway level obstacle layout videogame Map space layout magical mystical cosmic colorful glowing scene blackhole supernova nebula galaxies universe
Neon cyberpunk manga Magical underwater crystal cavern background image deep sea cave majestic cavern background scene landscape ocean floor seaweed
Magical underwater crystal cavern background image deep sea cave anime majestic
cyberpunk neon anime metropolis skyline holograms billboards, occult-capitalism-consumerism imagery nighttime futuristic architecture glow
16:9 banner, Neon cyberpunk horror landscape scene futuristic spires skyscrapers towers skyline hologram sigils lovecraftian bladerunner tower defense neon purple blue pink occult sigils projections 3d
Neon cyberpunk blank rectangular hologram empty overlay
3d hologram neon cyberpunk starlight magick galaxy overlay projection asteroid starfield Platforming magical dimension pathway level l layout videogame Map outerspace magical mystical cosmic colorful glowing scene blackholes supernovas nebulas galaxies universe background scene
cyberpunk neon anime metropolis skyline corporate holograms billboards, occult-capitalism-consumerism imagery nighttime futuristic architecture glow
3d hologram neon cyberpunk starlight magick galaxy overlay projection asteroid starfield magical dimension videogame Map outerspace magical mystical cosmic colorful glowing scene blackholes supernovas nebulas galaxies universe background scene
3d hologram neon cyberpunk starlight magick galaxy overlay projection asteroid starfield magical dimension videogame Map outerspace magical mystical cosmic colorful glowing scene blackholes supernovas nebulas galaxies universe background scene
neon cyberpunk manga seaside biome ocean beach holograms projections cyber deep sea background scene large hd overworld futuristic side platformer bg sea floor coastal
Swishy graceful man Cyberpunk manga, camera-facing arm bent In fro t of body, side profile view walking animation, shiny black leather biker jacket longsleeves shirtless Skinny abs blonde undercut pompadour guy, action shot wine red joggers combat boots, Background removed, "Full-body character, entirely in frame, no cropping of face, head, or feet" "Complete character visible, from head to toe, fully centered in the image" "Entire character, including hair and boots, fully within the frame" Character fully contained within a square frame, no edges cut off fashion model walking pose Style of vogue magazine
Top down isometric anime cyberpunk forest neon map grid hologram projection
Top down isometric anime forest River mountains neon map grid hologram
Top down isometric anime region neon map grid hologram
Top down isometric Cyberpunk forest thick foliage woods, anime Akira-nausicaa inspired, background scene, large sci-fi horror bioluminescent glowing alien flora
Top down isometric Cyberpunk forest, anime Akira-nausicaa inspired, background scene, large sci-fi horror bioluminescent glowing alien flora
Top down isometric neon cyberpunk castle mansion interior dungeon background large
Top down isometric neon cyberpunk castle courtyard dungeon background large
Top down isometric neon cyberpunk castle dungeon background large
Top down isometric cyberpunk simulation hologram grid projection forest biome, wilderness dense foliage wild rainforest mountain river
Top down isometric cyberpunk simulation hologram grid projection forest
Top down isometric Cyberpunk forest background image large
Cyberpunk forest background image large
Neon cyberpunk manga subterranean underground tunnels crystal caverns glowing huge ancient technology ruins futurustic underworld holograms spirits projections magical dimension cave background image deep caverns majestic background scene landscape tech purgatory catacombs maze Backdrop
neon cyberpunk manga undersea landscape oceanic holograms 3d deepsea seafloor underwater background scene large hd side platformer bg
2D platformer outerspace cosmic cyberpunk neon side profile view hologram stars magick galaxies cosmic black holes milkway star system overlay projection asteroid starfield Platforming magical dimension pathway level obstacle layout videogame
UI of 2D videogame asset management tool app, empty slots for assets, sleek flat intuitive User Interface futuristic style of Apple product ui, blank template sans uploaded assets
cyberpunk neon manga city metropolis skyline holograms billboards advertisements signs, magic sigils glow in subliminal messages with corporate logos, encouraging consumption, imagery nighttime futuristic hologram 3D projections horror sci-fi
cyberpunk neon anime metropolis skyline corporate holograms billboards, occult-capitalism-consumerism imagery nighttime futuristic architecture glow
neon cyberpunk manga undersea landscape ocean computer simulation underwater seafloor underwater background hd 2 sidescrolling platformer videogame bg undersea landscape
Expand on both sides landscape orientation neon cyberpunk metropolis world sidescrolling platformer futuristic holograms skyline jewel-tones forest cosmos ocean subterranean glowing simulation projection fantasy sci-fi background