User prompt
Import of asset
User prompt
Import of asset
User prompt
Import of asset
User prompt
Please make the frames of the walk animation equally sized, one is enormous compared to the rest, please add additional idle animation frames
User prompt
Import of asset
User prompt
Import of asset
User prompt
Import of asset
User prompt
Import of asset
User prompt
Import of asset
User prompt
Import of asset
User prompt
Alright—that explanation makes sense from layman’s perspective. Apologies for directing frustrations largely stemming from elsewhere toward you. Could you please implement your proposed solution under Real Solution Needed? The four steps, using the same technique for the trackpad and flight issues? Thanks, and sorry again (I’m anthropomorphizing, oops) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
I DONT UNDERSTAND WHY THE FUCK MY IDLE ANIMATION IS STILL MISSING. And upward drift still occurs! The track pad doesn’t even seem to register side to side or downward input and just defaults to an upward direction. You should be able to navigate in 8 distinct directions, with the cardinal up right down left, as well as the diagonal directions in between the primary four. Please add nocturnecity to the side as a background asset and make sure the player doesn’t move behind background assets like currently is happening with building assets when you travel far enough to the right. PLEASE default to the player, when stationary, playing the loop of playeridle - playeridle6 forward, then in reverse, then forward, and so on. Sorry for the temper this wouldn’t be an issue if this damn token system didn’t make each attempt a quantitatively finite resource ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Perhaps try first unassigning the idle animation, so there’s nothing that’s supposed to play when the player isn’t moving. Then, after the current idle animation no longer has a series of assets to loop, implement the correct idle animation with the prior instructions. Please move the word NOCTURNE 1/2 inch to the left to create a more distinct space between it and CITY. Flight mode still results in upward drift. Player should only move in the direction of the trackpad input, meaning able to navigate strictly right or left without rising higher into the air, as well as able to move lower while in flight mode, independent of simply dropping back to the ground upon releasing the jump button. Only an upward diagonal input should result in both horizontal and vertical movement concurrently. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
It appears to be playing part of the walk animation instead of the idle animation. Upward drift still happening. NOCTURNE CITY text now gone. It should animate centered above player, with each letter appearing from left to right, but animated so that as more letters appear, the text appears to slightly squeeze closer together denser, with a wave-like momentum effect. Then each letter should fade from left to right. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Still no. Idle animation doesn’t work. NOCTURNE spacing is better but leaves the word city squashed behind the Brostiary icon. Try animating it to appear to squeeze from right to left as the word expands. I really want my idle animation back and I don’t even know why it was changed and I’ve wasted all my daily credits repeatedly asking it be restored. Trackpad still doesn’t respond to all directions, upward flight drift still occurs when moving side to side, why do I keep trying?
User prompt
playeridle - playeridle6 is the default idle animation. That’s not what’s playing. It’s currently a glitched out walking animation. Fix it for the fifth time. Also, no improvement with upward drift or registering other directions other than up. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Still not the right idle animation. Upward drift still occurs in flight mode. It kind of seems like the trackpad doesn’t recognize the lower and side directions when attempting to navigate that way with trackpad. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Player should default to idle animation.
User prompt
None of the issues were fixed. It doesn’t look like anything changed. This credit system is truly bullshit
User prompt
Player idle animation should consist of playeridle - playeridle6 looped to play forward, then rewind, then forward. Also, there are different jump animations depending on if jumping, double jumping, or activating flight mode. Clouds are faded and hard to see when there should be plenty of them drifting left to right in the space between background assets and foreground platform, door and character assets. Upward drift still persists in flight mode when moving. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
NOCTURNE assets are still squished and overlapping each other. Please move it slightly to the left, while keeping center orientation. Flight mode still experiences upward drift when moving. Please restore player idle animation. Please increase walking speed, as it rate at which player character moves across screen ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
The NOCTURNE CITY text assets are still overlapping without a visible space between the two words. For some reason that update also changed the player character’s idle animation to walking in a deranged manner. Please restore idle animation and then please increase the speed of actual walking. Thanks ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please stop the player character from drifting upward when in flight mode. Player should only move the direction indicated by track pad. Player should be able to fly downward, too. Please resize and realign/rearrange the NOCTURNE CITY text so that the letters aren’t overlapping one another and there’s a clear, distinct space between the words NOCTURNE and CITY. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please size and space nocturne city text properly. They’re overlapping one another and there’s no discernible space between the first and second word. When in flight mode, character still drifts upward any direction pressed. When double tapping and holding jump button, I’d like player to be able to move in all directions as if underwater for the duration of the pressed button. This means only moving the direction indicated by track pad, including the downward direction. Currently, there’s inherent drift upwards, eventually causing the player character to appear offscreen. While I’d like the character to be able to explore higher altitudes like that, it’s important the camera follows the character so the upper environment is visible. Make sure there isn’t upward drift, and the player can move all directions including downward when in fight mode. Thanks! ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please size and space nocturne city text properly. They’re overlapping one another and there’s no discernible space between the first and second word. When in flight mode, character still drifts upward any direction pressed. When double tapping and holding jump button, I’d like player to be able to move in all directions as if underwater for the duration of the pressed button. Thanks! ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* 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 = 8;
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
}));
// Create walking animation frames with consistent scaling
var baseWalkScale = 1.0; // Standard scale for walk frames
self.walkFrames.push(self.attachAsset('playerwalk', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.70,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk2', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 1.05,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk3', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.85,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk4', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.60,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk5', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.60,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk6', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.58,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk7', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 1.20,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk8', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.60,
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk9', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 4.0,
// Fix warped frame - much larger scale needed
scaleY: baseWalkScale * 7.0
}));
self.walkFrames.push(self.attachAsset('playerwalk10', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.93,
// Adjust for narrower asset
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk11', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 1.51,
// Fix skinny frame
scaleY: baseWalkScale
}));
self.walkFrames.push(self.attachAsset('playerwalk12', {
anchorX: 0.5,
anchorY: 1.0,
alpha: 0,
scaleX: baseWalkScale * 0.78,
// Adjust for wider asset
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
self.startIdleAnimation = function () {
if (self.isMoving) return;
// Hide main body and walking frames, show current idle frame
body.alpha = 0;
for (var i = 0; i < self.walkFrames.length; i++) {
self.walkFrames[i].alpha = 0;
}
for (var i = 0; i < self.idleFrames.length; i++) {
self.idleFrames[i].alpha = i === self.currentIdleFrame ? 1 : 0;
}
// Smoothly fade out current frame and fade in 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];
tween(currentFrame, {
alpha: 0
}, {
duration: 100,
easing: tween.easeInOut
});
tween(nextFrame, {
alpha: 1
}, {
duration: 100,
easing: tween.easeInOut
});
self.currentIdleFrame = nextFrameIndex;
// Schedule next frame change
self.idleAnimationTimer = LK.setTimeout(function () {
if (!self.isMoving) {
self.startIdleAnimation();
}
}, 150);
};
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
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 direction
var nextFrameIndex = self.currentWalkFrame + self.walkAnimationDirection;
// Check if we need to reverse direction
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];
tween(currentFrame, {
alpha: 0
}, {
duration: 50,
easing: tween.easeInOut
});
tween(nextFrame, {
alpha: 1
}, {
duration: 50,
easing: tween.easeInOut
});
self.currentWalkFrame = nextFrameIndex;
// Schedule next frame change
self.walkAnimationTimer = LK.setTimeout(function () {
if (self.isMoving) {
self.startWalkingAnimation();
}
}, 60);
};
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 () {
// Handle flying vs normal physics
if (self.isFlying && jumpButtonHeld) {
// Flying mode - complete control via trackpad, zero inherent drift
if (trackpadPressed && trackpadAngle !== undefined) {
// Pure directional movement based solely on trackpad input
var moveX = Math.cos(trackpadAngle) * self.speed;
var moveY = Math.sin(trackpadAngle) * self.speed;
self.velocityX = moveX;
self.velocityY = moveY;
// Update facing direction based on horizontal movement
if (moveX > 0) self.facingDirection = -1;else if (moveX < 0) self.facingDirection = 1;
} else {
// No input - zero all movement, stay exactly in place
self.velocityX = 0;
self.velocityY = 0;
}
// No gravity or ground physics during flight
self.grounded = false;
} else if (!self.grounded && !self.isFlying) {
// Falling physics with realistic gravity
self.velocityY += self.gravity;
// Apply air resistance while falling
self.velocityX *= 0.98;
self.startFallAnimation();
} else {
// Normal ground physics
self.velocityY += self.gravity;
}
self.x += self.velocityX;
self.y += self.velocityY;
// Check if jumping/flying/falling
var wasJumping = self.isJumping;
var wasFlying = self.isFlying;
self.isJumping = !self.grounded && !self.isFlying && Math.abs(self.velocityY) > 2;
// Handle animation state
var wasMoving = self.isMoving;
self.isMoving = Math.abs(self.velocityX) > 0.1 || Math.abs(self.velocityY) > 0.1;
if (self.isFlying) {
// Flying animation
if (!wasFlying) {
self.stopIdleAnimation();
self.stopWalkingAnimation();
self.stopJumpAnimation();
self.stopFallAnimation();
self.startFlyAnimation();
}
} else if (self.isJumping) {
// Jump/fall animation
if (wasFlying) {
self.stopFlyAnimation();
self.startFallAnimation();
} else if (!wasJumping) {
self.stopWalkingAnimation();
self.stopIdleAnimation();
}
if (self.velocityY > 5) {
self.startFallAnimation();
} else {
self.startJumpAnimation();
}
} else {
// Ground movement
if (wasJumping || wasFlying) {
self.stopJumpAnimation();
self.stopFlyAnimation();
self.stopFallAnimation();
if (self.isMoving) {
self.startWalkingAnimation();
} else {
self.startIdleAnimation();
}
} else {
if (wasMoving && !self.isMoving) {
self.stopWalkingAnimation();
self.startIdleAnimation();
} else if (!wasMoving && self.isMoving) {
self.stopIdleAnimation();
}
}
}
};
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 neon sign assets
var neonAsset = Math.random() > 0.5 ? 'neonSign' : 'neonsign2';
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)];
// 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);
// Randomly select one of the 4 platform types
var platformAssets = ['platform', 'platform2', 'platform3', 'platform4'];
var selectedPlatform = platformAssets[Math.floor(Math.random() * platformAssets.length)];
var platform = self.attachAsset(selectedPlatform, {
anchorX: 0.5,
anchorY: 0.5
});
// Store platform type for reference
self.platformType = selectedPlatform;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x0a0a0a
});
/****
* Game Code
****/
game.setBackgroundColor(0x0a0a0a);
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
worldContainer = game.addChild(new Container());
function generateLevelChunk(startX) {
// Generate background elements
var bgAssets = ['bg2', 'bg3', 'bg4'];
// Add layered backgrounds
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: 2.0 - layer * 0.3,
scaleY: 2.0 - layer * 0.3
});
bg.alpha = 0.3 + layer * 0.2;
backgroundElements.push(bg);
worldContainer.setChildIndex(bg, layer);
}
// 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)
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 behind platforms but in front of backgrounds
var cloudIndex = worldContainer.getChildIndex(cloud);
worldContainer.setChildIndex(cloud, Math.max(4, cloudIndex - 5));
}
// Generate upper clouds (among tall buildings)
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 behind platforms but in front of backgrounds
var cloudIndex = worldContainer.getChildIndex(cloud);
worldContainer.setChildIndex(cloud, Math.max(4, cloudIndex - 5));
}
// Generate platforms AFTER backgrounds and clouds to ensure they're in foreground
for (var i = 0; i < 6; i++) {
var platform = new Platform();
platform.x = startX + 300 + i * 500;
platform.y = 1600 - i % 3 * 200;
platforms.push(platform);
worldContainer.addChild(platform);
// Add hunks on some platforms - avoid door positions
if (i % 2 === 1 && Math.random() > 0.3) {
var hunk = new Hunk();
// Offset hunk position to avoid doors
hunk.x = platform.x + (Math.random() > 0.5 ? 150 : -150);
hunk.y = platform.y - 20;
hunks.push(hunk);
worldContainer.addChild(hunk);
}
}
// Add more platforms and hunks for extended level
var extendedPlatforms = [{
x: startX + 400,
y: 1500
}, {
x: startX + 900,
y: 1300
}, {
x: startX + 1400,
y: 1100
}, {
x: startX + 1900,
y: 1400
}];
for (var p = 0; p < extendedPlatforms.length; p++) {
var platform = new Platform();
platform.x = extendedPlatforms[p].x;
platform.y = extendedPlatforms[p].y;
platforms.push(platform);
worldContainer.addChild(platform);
// Add single hunk every other extended platform
if (p % 2 === 1) {
var hunk = new Hunk();
hunk.x = platform.x;
hunk.y = platform.y - 20;
hunks.push(hunk);
worldContainer.addChild(hunk);
}
}
// 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 nocturnecity as the expanded background layer
var nocturneCityBg = worldContainer.attachAsset('nocturnecity', {
anchorX: 0.5,
anchorY: 1.0,
x: 2500,
y: 2732,
scaleX: 1.5,
scaleY: 1.5
});
worldContainer.setChildIndex(nocturneCityBg, 0);
// Add bg2 as the furthest background layer to fill empty spaces
var background2 = worldContainer.attachAsset('bg2', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048,
y: 2732,
scaleX: 2.0,
scaleY: 2.0
});
background2.alpha = 0.7;
// Add main background layer on top
var background = worldContainer.attachAsset('bg', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048,
y: 2732,
scaleX: 0.8,
scaleY: 0.8
});
background.alpha = 0.8;
// Add additional colored background shapes to fill gaps
var bg3 = worldContainer.attachAsset('bg3', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048,
y: 2732,
scaleX: 30,
scaleY: 15
});
bg3.alpha = 0.3;
worldContainer.setChildIndex(bg3, 0); // Put behind other backgrounds
var bg4 = worldContainer.attachAsset('bg4', {
anchorX: 0.5,
anchorY: 1.0,
x: 2048,
y: 2732,
scaleX: 25,
scaleY: 12
});
bg4.alpha = 0.2;
worldContainer.setChildIndex(bg4, 1); // Put behind main backgrounds
// 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.6 + Math.random() * 0.4;
// 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 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);
}
// 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);
// Create repeating bottom platform foundation
for (var b = 0; b < 15; b++) {
var bottomPlatform = new Platform();
bottomPlatform.x = 300 + b * 400;
bottomPlatform.y = 2000; // Ground level
bottomPlatform.scale.x = 1.2;
bottomPlatform.scale.y = 0.8;
platforms.push(bottomPlatform);
worldContainer.addChild(bottomPlatform);
}
// Create platform layout based on nocturnecitylayout guide
var platformPositions = [{
x: 400,
y: 1800
},
// Starting platform
{
x: 800,
y: 1600
},
// Lower left area
{
x: 1200,
y: 1500
},
// Lower mid
{
x: 1600,
y: 1600
},
// Shop door platform
{
x: 2000,
y: 1400
},
// Mid level ascending
{
x: 2400,
y: 1200
},
// Mid upper
{
x: 2800,
y: 1000
},
// Upper mid
{
x: 3200,
y: 1100
},
// Plateau area
{
x: 3600,
y: 1300
},
// Right mid
{
x: 4000,
y: 1500
},
// Right lower
{
x: 4400,
y: 1200
},
// Far right upper
{
x: 4800,
y: 1000
},
// Far right peak
{
x: 5200,
y: 1300
},
// Extended plateau
{
x: 5600,
y: 1400
},
// Final ascent
{
x: 6000,
y: 1100
} // End platform
];
// Door positions based on layout guide
var doorPositions = [{
platformIndex: 2,
isShop: true
},
// Bottom left shop door
{
platformIndex: 5,
isShop: false
},
// Mid level door
{
platformIndex: 8,
isShop: false
} // Upper right door
];
for (var i = 0; i < platformPositions.length; i++) {
var platform = new Platform();
platform.x = platformPositions[i].x;
platform.y = platformPositions[i].y;
platforms.push(platform);
worldContainer.addChild(platform);
// Ensure platform appears in foreground
var platformIndex = worldContainer.getChildIndex(platform);
worldContainer.setChildIndex(platform, worldContainer.children.length - 1);
// 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 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);
}
bloodmage = new Bloodmage();
bloodmage.x = 400; // Start on first platform
bloodmage.y = 1780;
worldContainer.addChild(bloodmage);
// Start idle animation initially
bloodmage.startIdleAnimation();
// Track initial generation position
lastGeneratedX = 6500; // Extended to accommodate new layout
}
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;
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;
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) {
bloodmage.y = platTop;
bloodmage.velocityY = 0;
bloodmage.grounded = true;
bloodmage.jumpCount = 0; // Reset jump count when landing
}
}
}
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);
if (dist > 80) {
dx = dx / dist * 80;
dy = dy / dist * 80;
}
trackpadThumb.x = 200 + dx;
trackpadThumb.y = -200 + dy;
if (bloodmage && dist > 5) {
// Store angle for flight movement
trackpadAngle = Math.atan2(dy, dx);
if (bloodmage.isFlying && jumpButtonHeld) {
// Pure directional control in flight - no inherent upward movement
var moveX = Math.cos(trackpadAngle) * bloodmage.speed;
var moveY = Math.sin(trackpadAngle) * bloodmage.speed;
bloodmage.velocityX = moveX;
bloodmage.velocityY = moveY;
// Update facing direction based on horizontal movement
if (moveX > 0) bloodmage.facingDirection = -1;else if (moveX < 0) bloodmage.facingDirection = 1;
} else {
// Normal ground movement - only horizontal with increased speed
bloodmage.velocityX = dx / 80 * bloodmage.speed * 2.5;
}
}
}
game.update = function () {
if (!battleMenuVisible) {
checkCollisions();
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) {
generateLevelChunk(lastGeneratedX);
cleanupOldElements();
}
}
// Animate clouds drifting
for (var i = 0; i < clouds.length; i++) {
var cloud = clouds[i];
cloud.x += cloud.driftSpeed;
// 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');
// Title animation - Create NOCTURNE CITY text and animate it
function showTitle() {
var titleContainer = game.addChild(new Container());
// Letter positions for "NOCTURNE CITY" with proper spacing and clear word separation
var letters = [{
asset: 'n',
x: -700
}, {
asset: 'o',
x: -550
}, {
asset: 'c',
x: -400
}, {
asset: 't',
x: -250
}, {
asset: 'u',
x: -100
}, {
asset: 'r',
x: 50
}, {
asset: 'n',
x: 200
}, {
asset: 'e',
x: 350
}, {
// Large space between words
asset: 'c',
x: 600
}, {
asset: 'i',
x: 700
}, {
asset: 't',
x: 800
}, {
asset: 'y',
x: 950
}];
var letterAssets = [];
// Create all letters
for (var i = 0; i < letters.length; i++) {
var letter = titleContainer.attachAsset(letters[i].asset, {
anchorX: 0.5,
anchorY: 0.5,
x: 1024 + letters[i].x,
y: 400,
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
});
letterAssets.push(letter);
}
// Animate letters fading in one by one
function animateLettersIn(index) {
if (index < letterAssets.length) {
tween(letterAssets[index], {
alpha: 1
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
// Animate next letter after short delay
LK.setTimeout(function () {
animateLettersIn(index + 1);
}, 100);
}
});
} else {
// All letters are in, hold for 2 seconds then fade out
LK.setTimeout(function () {
animateLettersOut(0);
}, 2000);
}
}
// Animate letters fading out
function animateLettersOut(index) {
if (index < letterAssets.length) {
tween(letterAssets[index], {
alpha: 0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
// Animate next letter fade out
LK.setTimeout(function () {
animateLettersOut(index + 1);
}, 50);
}
});
} else {
// All letters faded out, clean up
titleContainer.destroy();
}
}
// Start animation
animateLettersIn(0);
}
// 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
@@ -283,8 +283,15 @@
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
+ }));
// Create walking animation frames with consistent scaling
var baseWalkScale = 1.0; // Standard scale for walk frames
self.walkFrames.push(self.attachAsset('playerwalk', {
anchorX: 0.5,
@@ -613,23 +620,20 @@
// 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;
}
- // Calculate which frame to show based on jump progress
- var jumpProgress = 0;
- if (self.velocityY < -15) {
- jumpProgress = 0; // Start of jump
- } else if (self.velocityY < -5) {
- jumpProgress = 1; // Rising
- } else if (self.velocityY < 5) {
- jumpProgress = 2; // Peak
- } else if (self.velocityY < 15) {
- jumpProgress = 3; // Falling
+ // 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 {
- jumpProgress = 4; // Landing
+ // Regular jump uses playerjump (frame 0)
+ frameIndex = 0;
}
- // Ensure we don't go past available frames
- var frameIndex = Math.min(jumpProgress, self.jumpFrames.length - 1);
// Show appropriate jump frame
for (var i = 0; i < self.jumpFrames.length; i++) {
self.jumpFrames[i].alpha = i === frameIndex ? 1 : 0;
}
@@ -721,23 +725,23 @@
};
self.update = function () {
// Handle flying vs normal physics
if (self.isFlying && jumpButtonHeld) {
- // Flying mode - zero inherent movement, only trackpad input controls direction
+ // Flying mode - complete control via trackpad, zero inherent drift
if (trackpadPressed && trackpadAngle !== undefined) {
- // Full directional movement based purely on trackpad input - no upward drift
+ // Pure directional movement based solely on trackpad input
var moveX = Math.cos(trackpadAngle) * self.speed;
var moveY = Math.sin(trackpadAngle) * self.speed;
self.velocityX = moveX;
self.velocityY = moveY;
// Update facing direction based on horizontal movement
if (moveX > 0) self.facingDirection = -1;else if (moveX < 0) self.facingDirection = 1;
} else {
- // No input - completely stop all movement and maintain exact position
+ // No input - zero all movement, stay exactly in place
self.velocityX = 0;
self.velocityY = 0;
}
- // Disable gravity and grounded state during flight
+ // No gravity or ground physics during flight
self.grounded = false;
} else if (!self.grounded && !self.isFlying) {
// Falling physics with realistic gravity
self.velocityY += self.gravity;
@@ -1266,10 +1270,10 @@
// Vary cloud sizes for depth
var cloudScale = 0.5 + Math.random() * 1.0;
cloud.scale.x = cloudScale;
cloud.scale.y = cloudScale;
- // Make clouds semi-translucent with varying opacity
- cloud.alpha = 0.3 + Math.random() * 0.4;
+ // 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 behind platforms but in front of backgrounds
@@ -1452,10 +1456,10 @@
// Vary cloud sizes for depth
var cloudScale = 0.5 + Math.random() * 1.0;
cloud.scale.x = cloudScale;
cloud.scale.y = cloudScale;
- // Make clouds semi-translucent with varying opacity
- cloud.alpha = 0.3 + Math.random() * 0.4;
+ // Make clouds more visible with better opacity
+ cloud.alpha = 0.6 + Math.random() * 0.4;
// Store initial position and drift speed for animation
cloud.initialX = cloud.x;
cloud.driftSpeed = 0.5 + Math.random() * 1.5;
clouds.push(cloud);
@@ -3306,9 +3310,9 @@
if (bloodmage && dist > 5) {
// Store angle for flight movement
trackpadAngle = Math.atan2(dy, dx);
if (bloodmage.isFlying && jumpButtonHeld) {
- // Full directional movement in flight mode - supports all directions including down
+ // Pure directional control in flight - no inherent upward movement
var moveX = Math.cos(trackpadAngle) * bloodmage.speed;
var moveY = Math.sin(trackpadAngle) * bloodmage.speed;
bloodmage.velocityX = moveX;
bloodmage.velocityY = moveY;
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