User prompt
Please fix the bug: 'TypeError: player is undefined' in or related to this line: 'player.x = GAME_WIDTH / 4;' Line Number: 2178
User prompt
Please fix the bug: 'TypeError: player is undefined' in or related to this line: 'player.isInvulnerable = true;' Line Number: 2148
Code edit (5 edits merged)
Please save this source code
User prompt
Please fix the bug: 'TypeError: player is undefined' in or related to this line: 'player.isInvulnerable = true;' Line Number: 2148
Code edit (11 edits merged)
Please save this source code
User prompt
Play arrowpickup sound effect when arrow is picked up.
User prompt
When playing bow animation, if the player has no ammo, play bow fire sound effect
User prompt
If a player shoots, but has no arrows, play bow fire sound effect.
User prompt
Make sure when a player is dying or air dying game.up doesn’t register input.
Code edit (1 edits merged)
Please save this source code
Code edit (5 edits merged)
Please save this source code
User prompt
Update as needed with: var ArrowPickup = Container.expand(function () { var self = Container.call(this); self.sprite = self.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Initialize physics properties CollectibleBehavior.initPhysics(self); // Add a custom platform collision check self.checkPlatformCollision = function () { // Add an offset of 20 pixels upward for platform collision return GameUtils.checkPlatformCollision(self, 60, true) != null; };
Code edit (3 edits merged)
Please save this source code
User prompt
Update with: for (var i = arrows.length - 1; i >= 0; i--) { var arrow = arrows[i]; if (GameUtils.checkCollision(arrow.getBounds(), enemyBounds)) { // Only hit the enemy if it's not already hit or dying if (!enemies[j].isHit && !enemies[j].isDying) { enemies[j].hit(); if (enemies[j].type === 'eyeball') { LK.getSound('eyeballhit').play(); } else { LK.getSound('enemyhit').play(); } } // Always destroy the arrow on collision regardless arrow.destroy(); arrows.splice(i, 1); break; } }
Code edit (3 edits merged)
Please save this source code
User prompt
I want arrow pickups to land higher on the platform.
Code edit (3 edits merged)
Please save this source code
User prompt
Remove the random rotation from arrows spawned from jars and treasure chests. Instead set the rotation to 90 degrees counterclockwise
User prompt
Play arrow fire sound effect when arrow is fired from player bow.
Code edit (1 edits merged)
Please save this source code
User prompt
Add random clockwise rotation to arrows spawned from treasure chests. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add random clockwise rotation to arrows spawned from jars.
User prompt
Add some rotation to coins, potions, gems and arrows when they spawn.
Code edit (4 edits merged)
Please save this source code
User prompt
Rotate the ammoicon 90 degrees counterclockwise.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Arrow = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 25;
self.damage = 1;
self.update = function () {
self.x += self.speed;
// Destroy if off screen
if (self.x > GAME_WIDTH + 100) {
self.destroy();
}
};
self.getBounds = function () {
return {
left: self.x - 40,
right: self.x + 40,
top: self.y - 10,
bottom: self.y + 10
};
};
self.updateAmmo = function (amount) {
self.ammoText.setText('x' + amount);
// Adjust position based on digit count
if (amount >= 10) {
self.ammoText.x = -150;
} else {
self.ammoText.x = -100;
}
};
return self;
});
var ArrowPickup = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.7,
scaleX: 1.2,
scaleY: 1.2
});
// Initialize physics properties
CollectibleBehavior.initPhysics(self);
self.collect = function () {
if (!self.collected) {
self.collected = true;
player.ammoCount += 1;
scoreManager.updateAmmo(player.ammoCount);
// Create arrow popup
var popup = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
popup.x = self.x;
popup.y = self.y - 30;
popup.velocityY = -3;
popup.lifespan = 45;
popup.update = function () {
this.y += this.velocityY;
this.lifespan--;
if (this.lifespan < 15) {
this.alpha -= 0.07;
}
if (this.alpha <= 0 || this.lifespan <= 0) {
this.destroy();
}
};
game.addChild(popup);
self.destroy();
}
};
self.update = function () {
CollectibleBehavior.standardUpdate(self);
};
return self;
});
// Refactored classes - keeping original Container.expand pattern
var Coin = Container.expand(function (type) {
var self = Container.call(this);
// Set type and create sprite
self.type = type || 'coin';
self.sprite = self.attachAsset(self.type, {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFFFFFF
});
// Initialize physics properties
CollectibleBehavior.initPhysics(self);
// Get value based on type
self.getValue = function () {
switch (self.type) {
case 'diamond':
return 10;
case 'emerald':
return 5;
case 'ruby':
return 3;
default:
return 1;
}
};
// Collection functionality
self.collect = function () {
if (!self.collected) {
self.collected = true;
var value = self.getValue();
scoreManager.addScore(value, self.x, self.y);
LK.getSound('coincollect').play();
self.destroy();
}
};
// Standard update method using shared behavior
self.update = function () {
CollectibleBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
// Enemy class with refactored animation management
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
// Enemy properties
self.type = type || 'basic';
self.speed = 7;
self.isOnGround = true;
self.velocityY = 0;
self.currentPlatform = null;
self.groundY = GAME_HEIGHT / 1.5;
self.isHit = false;
self.isDying = false;
self.deathTimer = 0;
self.throwBackSpeed = 15;
self.throwBackDistance = 0;
self.maxThrowBack = 200;
self.hitType = 'none'; // Can be 'none', 'attack', or 'slide'
// Hitbox properties
self.hitboxWidth = 200;
self.hitboxHeight = self.type === 'eyeball' ? 90 : 260;
// Animation properties
self.sprites = [];
self.animationCounter = 0;
self.animationSpeed = 0.08;
// Define all animation arrays based on enemy type
if (self.type === 'eyeball') {
// Eyeball-specific properties
self.isFlying = true;
self.flyingHeight = 0;
self.verticalSpeed = 2;
self.maxVerticalSpeed = 4;
self.homingDelay = 80;
self.homingTimer = 0;
self.flyFrame = 0;
// Animation frames
self.flyAnimation = ['eyefly1', 'eyefly2', 'eyefly3', 'eyefly4', 'eyefly5', 'eyefly6', 'eyefly7', 'eyefly8'];
self.hitAnimation = ['eyedie1', 'eyedie2'];
self.dieAnimation = ['eyedie3', 'eyedie4', 'eyedie5'];
} else if (self.type === 'goblin') {
// Goblin-specific properties
self.runFrame = 0;
// Animation frames
self.runAnimation = ['goblinrun1', 'goblinrun2', 'goblinrun3', 'goblinrun4', 'goblinrun5', 'goblinrun6', 'goblinrun7', 'goblinrun8'];
self.hitAnimation = ['goblinhit1'];
self.dieAnimation = ['goblindie1', 'goblindie2', 'goblindie3', 'goblindie4'];
}
// Initialize animation sprites for eyeball
self.initEyeballSprites = function () {
// Add fly animations
for (var i = 0; i < self.flyAnimation.length; i++) {
var sprite = self.attachAsset(self.flyAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
// Add hit animations
for (var i = 0; i < self.hitAnimation.length; i++) {
var sprite = self.attachAsset(self.hitAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add die animations
for (var i = 0; i < self.dieAnimation.length; i++) {
var sprite = self.attachAsset(self.dieAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
};
// Initialize animation sprites for goblin
self.initGoblinSprites = function () {
// Add run animations
for (var i = 0; i < self.runAnimation.length; i++) {
var sprite = self.attachAsset(self.runAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
// Add hit animations
for (var i = 0; i < self.hitAnimation.length; i++) {
var sprite = self.attachAsset(self.hitAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add die animations
for (var i = 0; i < self.dieAnimation.length; i++) {
var sprite = self.attachAsset(self.dieAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
};
// Hide all animation sprites
self.hideAllSprites = function () {
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
};
// Update eyeball hit/die animation - fixed to allow falling without platform collision
self.updateEyeballDamageState = function () {
if (self.isHit) {
var hitOffset = self.flyAnimation.length;
self.throwBackDistance += Math.abs(self.throwBackSpeed);
self.x += self.throwBackSpeed;
// Apply vertical velocity without any dampening on the first few frames
// to ensure the eyeball gets significant vertical movement
if (self.hitTimer > 30) {
// Apply full vertical movement without reduction for initial frames
self.y += self.velocityY;
} else {
// After initial frames, gradually reduce the vertical movement
self.y += self.velocityY;
self.velocityY *= 0.8; // Less aggressive dampening to maintain vertical motion
}
self.throwBackSpeed *= 0.95;
// Show hit animation
var hitFrame = Math.floor(self.hitTimer / 100) % 2;
self.sprites[hitOffset + hitFrame].alpha = 1;
self.hitTimer--;
if (self.hitTimer <= 0) {
self.isHit = false;
if (self.hitType === 'attack') {
// Only start dying if it was a regular attack
self.isDying = true;
self.deathTimer = 180;
self.deathFrame = 0;
// Add dropLoot here when transitioning to dying state
self.dropLoot();
}
self.hitType = 'none';
}
} else if (self.isDying) {
var dieOffset = self.flyAnimation.length + self.hitAnimation.length;
// Apply gravity without platform collision - this ensures constant falling
self.velocityY += 0.5;
self.y += self.velocityY;
// Keep moving horizontally during fall
self.x -= PLATFORM_SPEED * 0.5;
// Progress through death frames
if (self.deathTimer > 120) {
self.sprites[dieOffset].alpha = 1; // eyedie3
} else if (self.deathTimer > 60) {
self.sprites[dieOffset + 1].alpha = 1; // eyedie4
} else {
self.sprites[dieOffset + 2].alpha = 1; // eyedie5
}
self.deathTimer--;
if (self.deathTimer <= 0) {
self.alpha -= 0.05;
if (self.alpha <= 0) {
self.destroy();
}
}
}
};
// Update eyeball normal movement
self.updateEyeballNormalState = function () {
self.x -= self.speed * gameSpeedMultiplier;
// Only start homing after delay
if (self.homingTimer >= self.homingDelay) {
// Home toward player
var deltaY = player.y - self.y;
self.velocityY += deltaY > 0 ? 0.2 : -0.2;
self.velocityY = Math.max(-self.maxVerticalSpeed, Math.min(self.maxVerticalSpeed, self.velocityY));
} else {
// Before homing, maintain height with slight wave motion
self.velocityY = Math.sin(self.homingTimer * 0.05) * 2;
self.homingTimer++;
}
self.y += self.velocityY;
// Animate
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.flyFrame = (self.flyFrame + 1) % self.flyAnimation.length;
}
self.sprites[self.flyFrame].alpha = 1;
};
// Update goblin hit animation
self.updateGoblinHitState = function () {
// Handle throw back motion
self.x += self.throwBackSpeed;
self.throwBackDistance += Math.abs(self.throwBackSpeed);
self.throwBackSpeed *= 0.95;
// Show hit animation
var hitOffset = self.runAnimation.length;
self.sprites[hitOffset].alpha = 1;
// Decrease hit timer
self.hitTimer--;
// Once hit timer expires, check hit type
if (self.hitTimer <= 0) {
self.isHit = false;
if (self.hitType === 'attack') {
// Only start death animation if it was a regular attack
self.isDying = true;
self.deathTimer = 60;
self.deathFrame = 0;
// Add dropLoot here when transitioning to dying state
self.dropLoot();
}
self.hitType = 'none';
}
};
// Update goblin dying animation
self.updateGoblinDyingState = function () {
// Continue throw back during death
self.x += self.throwBackSpeed;
// After halfway through death animation, match platform speed
if (self.deathFrame >= 2) {
self.x -= PLATFORM_SPEED;
}
self.throwBackSpeed *= 0.95;
// Handle death animation
var dieOffset = self.runAnimation.length + self.hitAnimation.length;
// Progress frame every 15 frames
if (self.deathTimer % 15 === 0 && self.deathFrame < self.dieAnimation.length - 1) {
self.deathFrame++;
}
self.sprites[dieOffset + self.deathFrame].alpha = 1;
// Count down death timer
self.deathTimer--;
// After timer expires, fade out
if (self.deathTimer <= 0) {
self.alpha -= 0.1;
if (self.alpha <= 0) {
self.destroy();
}
}
};
// Update goblin normal movement
self.updateGoblinNormalState = function () {
// Move left with speed multiplier
self.x -= self.speed * gameSpeedMultiplier;
// Platform and gravity code
if (!self.isOnGround) {
self.velocityY += 0.7;
self.y += self.velocityY;
self.checkPlatformCollision();
}
// Ensure goblin stays on platform
if (self.currentPlatform) {
var stillOnPlatform = self.x >= self.currentPlatform.x - PLATFORM_HALF_WIDTH && self.x <= self.currentPlatform.x + PLATFORM_HALF_WIDTH;
if (!stillOnPlatform) {
var foundAnotherPlatform = false;
// Check for another platform
for (var i = 0; i < platforms.length; i++) {
var otherPlatform = platforms[i];
if (otherPlatform === self.currentPlatform) {
continue;
}
if (self.x >= otherPlatform.x - PLATFORM_HALF_WIDTH && self.x <= otherPlatform.x + PLATFORM_HALF_WIDTH && Math.abs(self.y - (otherPlatform.y - ENEMY_PLATFORM_OFFSET)) < 5) {
self.currentPlatform = otherPlatform;
foundAnotherPlatform = true;
break;
}
}
// Start falling if no other platform found
if (!foundAnotherPlatform) {
self.isOnGround = false;
self.currentPlatform = null;
if (self.velocityY === 0) {
self.velocityY = 0.1;
}
}
}
}
// Animate running
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.runFrame = (self.runFrame + 1) % self.runAnimation.length;
}
self.sprites[self.runFrame].alpha = 1;
};
// Check platform collision
self.checkPlatformCollision = function () {
var onAnyPlatform = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var leftEdge = platform.x - PLATFORM_HALF_WIDTH;
var rightEdge = platform.x + PLATFORM_HALF_WIDTH;
// Check if we're on a platform (exact position check)
if (self.x >= leftEdge && self.x <= rightEdge) {
if (Math.abs(self.y - (platform.y - ENEMY_PLATFORM_OFFSET)) < 5) {
onAnyPlatform = true;
self.currentPlatform = platform;
self.y = platform.y - ENEMY_PLATFORM_OFFSET;
self.isOnGround = true;
self.velocityY = 0;
return true;
}
// Check for passing through platform during fall
if (self.velocityY > 0 && self.y < platform.y - ENEMY_PLATFORM_OFFSET && self.y + self.velocityY >= platform.y - ENEMY_PLATFORM_OFFSET) {
self.y = platform.y - ENEMY_PLATFORM_OFFSET;
self.velocityY = 0;
self.isOnGround = true;
self.currentPlatform = platform;
return true;
}
}
}
if (!onAnyPlatform) {
self.isOnGround = false;
self.currentPlatform = null;
}
return false;
};
// Get collision bounds
self.getBounds = function () {
return {
left: self.x - self.hitboxWidth / 2,
right: self.x + self.hitboxWidth / 2,
top: self.y - self.hitboxHeight / 2,
bottom: self.y + self.hitboxHeight / 2
};
};
self.slideHit = function () {
if (!self.isHit && !self.isDying) {
self.isHit = true;
self.hitType = 'slide'; // Specify this is a slide hit
self.throwBackSpeed = 25;
self.throwBackDistance = 0;
self.hitTimer = 30;
// Add vertical trajectory for eyeball hits based on Y position comparison with player
var verticalTrajectory;
if (self.type === 'eyeball') {
var yDifference = self.y - player.y;
var heightThreshold = 100; // Adjust this value to control the size of the middle zone
if (yDifference < -heightThreshold) {
// Eyeball is well above player, push upward strongly
verticalTrajectory = -20;
} else if (yDifference > heightThreshold) {
// Eyeball is well below player, push downward strongly
verticalTrajectory = 14;
} else {
// Eyeball is in middle zone, small upward push
verticalTrajectory = -2;
}
// Immediately apply the full vertical trajectory when hit with no reduction
self.velocityY = verticalTrajectory; // Set the velocity for the eyeball
}
// Add particle effect
var particleOffset = self.type === 'eyeball' ? 175 : 250;
particleSystem.emitFromHit(self.x + particleOffset, self.y, player.x, verticalTrajectory);
}
};
// Hit handling
self.hit = function () {
if (!self.isHit && !self.isDying) {
self.isHit = true;
self.hitType = 'attack'; // Specify this is a regular attack hit
self.throwBackSpeed = 25;
self.throwBackDistance = 0;
self.hitTimer = 35;
// Calculate vertical trajectory for eyeball hits based on Y position comparison with player
var verticalTrajectory;
if (self.type === 'eyeball') {
var yDifference = self.y - player.y;
var heightThreshold = 100; // Adjust this value to control the size of the middle zone
if (yDifference < -heightThreshold) {
// Eyeball is well above player, push upward strongly
verticalTrajectory = -20;
} else if (yDifference > heightThreshold) {
// Eyeball is well below player, push downward strongly
verticalTrajectory = 14;
} else {
// Eyeball is in middle zone, small upward push
verticalTrajectory = -2;
}
// Immediately apply the vertical trajectory with stronger values
self.velocityY = verticalTrajectory; // Set the velocity for the eyeball
} else {
self.velocityY = 0; // Reset vertical velocity for new hit for non-eyeball enemies
}
// Add particle effect
var particleOffset = self.type === 'eyeball' ? 175 : 250;
particleSystem.emitFromHit(self.x + particleOffset, self.y, player.x, verticalTrajectory);
}
};
self.dropLoot = function () {
// Determine number of coins to drop (1-5)
var coinCount = Math.floor(GameUtils.randomRange(1, 6));
// Small chance for jewels (2% each)
var dropJewel = Math.random();
if (dropJewel < 0.02) {
// Diamond (rarest)
var diamond = new Coin('diamond');
diamond.x = self.x;
diamond.y = self.y;
diamond.velocityX = GameUtils.randomRange(-2, 2); // Reduced horizontal velocity
diamond.velocityY = -GameUtils.randomRange(6, 10); // Reduced vertical velocity
game.addChild(diamond);
coins.push(diamond);
} else if (dropJewel < 0.04) {
// Emerald
var emerald = new Coin('emerald');
emerald.x = self.x;
emerald.y = self.y;
emerald.velocityX = GameUtils.randomRange(-2, 2); // Reduced horizontal velocity
emerald.velocityY = -GameUtils.randomRange(6, 10); // Reduced vertical velocity
game.addChild(emerald);
coins.push(emerald);
} else if (dropJewel < 0.06) {
// Ruby
var ruby = new Coin('ruby');
ruby.x = self.x;
ruby.y = self.y;
ruby.velocityX = GameUtils.randomRange(-2, 2); // Reduced horizontal velocity
ruby.velocityY = -GameUtils.randomRange(6, 10); // Reduced vertical velocity
game.addChild(ruby);
coins.push(ruby);
}
// Drop regular coins
for (var i = 0; i < coinCount; i++) {
var coin = new Coin();
coin.x = self.x;
coin.y = self.y;
coin.velocityX = GameUtils.randomRange(-2, 2); // Reduced horizontal velocity
coin.velocityY = -GameUtils.randomRange(6, 10); // Reduced vertical velocity
game.addChild(coin);
coins.push(coin);
}
};
// Main update method
self.update = function () {
// Hide all sprites first
self.hideAllSprites();
// Handle different enemy types and states
if (self.type === 'eyeball') {
if (self.isHit || self.isDying) {
self.updateEyeballDamageState();
} else {
self.updateEyeballNormalState();
}
} else {
// Goblin logic
if (self.isHit) {
self.updateGoblinHitState();
} else if (self.isDying) {
self.updateGoblinDyingState();
} else {
self.updateGoblinNormalState();
}
}
// Destroy if off screen
if (self.x < -50 || self.y > GAME_HEIGHT) {
self.destroy();
}
};
// Initialize based on enemy type AFTER defining the methods
if (self.type === 'eyeball') {
self.initEyeballSprites();
} else if (self.type === 'goblin') {
self.initGoblinSprites();
} else {
// Basic enemy
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
}
return self;
});
var HealthPotion = Container.expand(function () {
var self = Container.call(this);
// Create sprite
self.sprite = self.attachAsset('healthpotion', {
anchorX: 0.5,
anchorY: 0.8
});
// Initialize physics properties
CollectibleBehavior.initPhysics(self);
// Collection functionality
self.collect = function () {
if (!self.collected && player.currentHealth < player.maxHealth) {
self.collected = true;
player.currentHealth++;
player.heartContainer.updateHealth(player.currentHealth);
player.heartContainer.alpha = 1;
player.heartVisibilityTimer = player.heartVisibilityDuration;
// Create heart popup
var popup = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xFF0000
});
popup.scaleX = 1.6; // Adjust scale to match original size
popup.scaleY = 1.6;
popup.x = self.x;
popup.y = self.y - 30;
popup.velocityY = -3;
popup.lifespan = 45;
popup.update = function () {
this.y += this.velocityY;
this.lifespan--;
if (this.lifespan < 15) {
this.alpha -= 0.07;
}
if (this.alpha <= 0 || this.lifespan <= 0) {
this.destroy();
}
};
game.addChild(popup);
LK.getSound('potion').play();
self.destroy();
}
};
// Standard update using shared behavior
self.update = function () {
CollectibleBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
var HeartContainer = Container.expand(function () {
var self = Container.call(this);
self.maxHealth = 3;
self.currentHealth = 3;
self.hearts = [];
// Calculate total width of heart display
var heartSpacing = 80;
var totalWidth = (self.maxHealth - 1) * heartSpacing;
// Initialize hearts with centered positioning
for (var i = 0; i < self.maxHealth; i++) {
var heart = self.attachAsset('heart', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xCC0000
});
heart.x = i * heartSpacing - totalWidth / 2;
heart.y = 0;
self.hearts.push(heart);
}
// Start invisible
self.alpha = 0;
self.updateHealth = function (newHealth) {
self.currentHealth = newHealth;
// Update heart display
for (var i = 0; i < self.maxHealth; i++) {
if (i < newHealth) {
// Full heart
self.hearts[i].tint = 0xFF0000;
self.hearts[i].alpha = 1;
} else {
// Empty heart
self.hearts[i].tint = 0x000000;
self.hearts[i].alpha = 0.5;
}
}
};
return self;
});
var Jar = Container.expand(function () {
var self = Container.call(this);
// Attach jar sprite
self.sprite = self.attachAsset('jar', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as breakable
BreakableBehavior.initBreakable(self);
// Break functionality
self["break"] = function () {
BreakableBehavior.standardBreak(self, JarPiece, 4, function (jar) {
// Spawn health potion with low chance
if (Math.random() < 0.05) {
var potion = new HealthPotion();
potion.x = jar.x;
potion.y = jar.y;
potion.velocityX = GameUtils.randomRange(2, 6); // Reduced horizontal velocity
potion.velocityY = -GameUtils.randomRange(8, 14); // Reduced vertical velocity
game.addChild(potion);
coins.push(potion);
}
// Add chance for arrow (10%)
if (Math.random() < 0.1) {
var arrow = new ArrowPickup();
arrow.x = jar.x;
arrow.y = jar.y;
arrow.velocityX = GameUtils.randomRange(2, 6);
arrow.velocityY = -GameUtils.randomRange(8, 14);
// Set rotation to 90 degrees counterclockwise
arrow.sprite.rotation = -Math.PI / 2;
game.addChild(arrow);
coins.push(arrow);
}
// Spawn coins
var coinCount = Math.floor(GameUtils.randomRange(1, 9));
for (var i = 0; i < coinCount; i++) {
var coin = new Coin();
coin.x = jar.x;
coin.y = jar.y;
coin.velocityX = GameUtils.randomRange(2, 6); // Reduced horizontal velocity
coin.velocityY = -GameUtils.randomRange(8, 14); // Reduced vertical velocity
game.addChild(coin);
coins.push(coin);
}
LK.getSound('jarbreak').play();
});
};
return self;
});
var JarPiece = Container.expand(function (pieceNum) {
var self = Container.call(this);
// Attach piece sprite
self.sprite = self.attachAsset('jarpiece' + pieceNum, {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as piece
PieceBehavior.initPiece(self);
// Update method
self.update = function () {
PieceBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
var ParticlePool = Container.expand(function (maxParticles) {
var self = Container.call(this);
self.particles = [];
self.activeParticles = [];
self.redTints = [0xff0000, 0xff3333, 0xcc0000];
for (var i = 0; i < maxParticles; i++) {
var particle = self.attachAsset('pixel', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
particle.alpha = 0;
particle.velocityX = 0;
particle.velocityY = 0;
particle.lifespan = 0;
particle.fadeSpeed = 0;
self.particles.push(particle);
}
self.emitFromHit = function (x, y, playerX, verticalTrajectory) {
var directionX = x - playerX;
var directionSign = Math.sign(directionX);
for (var i = 0; i < 20; i++) {
if (self.particles.length === 0) {
break;
}
var particle = self.particles.pop();
self.activeParticles.push(particle);
particle.x = x;
particle.y = y;
particle.alpha = 1;
particle.tint = self.redTints[Math.floor(Math.random() * self.redTints.length)];
// Set scale
var particleSize = Math.random() * 0.2 + 0.2;
particle.scaleX = particleSize;
particle.scaleY = particleSize;
var angle = Math.random() * Math.PI / 2 - Math.PI / 4;
var speed = Math.random() * 5 + 10;
particle.velocityX = Math.cos(angle) * speed * directionSign;
// Use provided vertical trajectory if available, otherwise use default calculation
if (verticalTrajectory !== undefined) {
// Add some randomness while maintaining the general direction
var randomFactor = Math.random() * 0.5 + 0.75; // 0.75 to 1.25 range
particle.velocityY = verticalTrajectory * randomFactor;
} else {
particle.velocityY = Math.sin(angle) * speed;
}
particle.lifespan = 100;
particle.fadeSpeed = 1 / 60;
}
};
self.update = function () {
for (var i = self.activeParticles.length - 1; i >= 0; i--) {
var particle = self.activeParticles[i];
particle.x += particle.velocityX;
particle.y += particle.velocityY;
particle.alpha -= particle.fadeSpeed;
particle.lifespan--;
if (particle.lifespan <= 0 || particle.alpha <= 0) {
particle.alpha = 0;
self.activeParticles.splice(i, 1);
self.particles.push(particle);
}
}
};
return self;
});
var Platform = Container.expand(function () {
var self = Container.call(this);
var platformGraphics = self.attachAsset('platform', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = PLATFORM_SPEED;
self.passed = false;
self.update = function () {
self.x -= PLATFORM_SPEED * gameSpeedMultiplier;
if (self.x < -500) {
self.destroy();
}
};
return self;
});
// Player class with refactored animation management
var Player = Container.expand(function () {
var self = Container.call(this);
// Animation properties
self.runAnimation = ['playerrun1', 'playerrun2', 'playerrun3', 'playerrun4', 'playerrun5', 'playerrun6'];
self.jumpAnimation = ['playerjump1', 'playerjump2', 'playerjump3'];
self.attackAnimation = ['playerattack1', 'playerattack2', 'playerattack3', 'playerattack4', 'playerattack5'];
self.airAttackAnimation = ['playerairattack1', 'playerairattack2', 'playerairattack3', 'playerairattack4'];
self.slideAnimation = ['playerslide1', 'playerslide2'];
self.standUpAnimation = ['playerstand1', 'playerstand2', 'playerstand3'];
self.deathAnimation = ['playerdie1', 'playerdie2', 'playerdie3', 'playerdie4', 'playerdie5', 'playerdie6', 'playerdie7'];
self.bowAnimation = ['playerbow4', 'playerbow5', 'playerbow6', 'playerbow7', 'playerbow8', 'playerbow9'];
// Bow properties
self.isShooting = false;
self.bowFrame = 0;
self.bowCooldown = 20; // Frames of cooldown
self.bowCooldownTimer = 0;
self.ammoCount = 3;
// Animation states
self.isAttacking = false;
self.attackFrame = 0;
self.runFrame = 0;
self.animationSpeed = 0.08;
self.attackAnimationSpeed = 0.15;
self.animationCounter = 0;
self.sprites = [];
// Death animation properties
self.isDying = false;
self.deathFrame = 0;
self.deathTimer = 0;
self.deathDuration = 100;
self.deathAnimationSpeed = 0.1;
// Physics properties
self.groundY = GAME_HEIGHT * 0.9;
self.hitboxWidth = 150;
self.hitboxHeight = 300;
self.attackHitboxWidth = 200;
self.attackHitboxHeight = 400;
self.attackHitboxOffset = 50;
// Platform collision properties
self.isOnGround = true;
self.currentPlatform = null;
// Health properties
self.heartContainer = heartContainer;
self.maxHealth = 3;
self.currentHealth = 3;
self.isInvulnerable = false;
self.invulnerabilityDuration = 90;
self.invulnerabilityTimer = 0;
self.heartVisibilityTimer = 0;
self.heartVisibilityDuration = 120;
// Movement properties
self.speed = 5;
self.jumpHeight = 40;
self.isJumping = false;
self.velocityY = 0;
self.jumpState = "none";
self.jumpStartTime = 0;
self.isSliding = false;
self.slideTimer = 0;
self.slideDuration = 90;
self.standUpDuration = 30;
self.slideCooldown = 30;
self.lastSlideTime = 0;
self.slideSpeedMultiplier = 1.75;
self.normalHitboxHeight = self.hitboxHeight; // Store original height
self.slideHitboxHeight = 200; // Make it taller
self.slideHitboxYOffset = 150; // Move it much lower
// Initialize animation sprites
self.initAnimations = function () {
// Run animations
for (var i = 0; i < self.runAnimation.length; i++) {
var sprite = self.attachAsset(self.runAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = i === 0 ? 1 : 0;
self.sprites.push(sprite);
}
// Jump animations
for (var i = 0; i < self.jumpAnimation.length; i++) {
var sprite = self.attachAsset(self.jumpAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Attack animations
for (var i = 0; i < self.attackAnimation.length; i++) {
var sprite = self.attachAsset(self.attackAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Air attack animations
for (var i = 0; i < self.airAttackAnimation.length; i++) {
var sprite = self.attachAsset(self.airAttackAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add slide animations
for (var i = 0; i < self.slideAnimation.length; i++) {
var sprite = self.attachAsset(self.slideAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add stand up animations
for (var i = 0; i < self.standUpAnimation.length; i++) {
var sprite = self.attachAsset(self.standUpAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add death animations
for (var i = 0; i < self.deathAnimation.length; i++) {
var sprite = self.attachAsset(self.deathAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
// Add bow animations
for (var i = 0; i < self.bowAnimation.length; i++) {
var sprite = self.attachAsset(self.bowAnimation[i], {
anchorX: 0.5,
anchorY: 0.5
});
sprite.alpha = 0;
self.sprites.push(sprite);
}
};
// Call initialization
self.initAnimations();
// Get collision bounds
self.getBounds = function () {
// For collecting items, always use the full-size hitbox
return {
left: self.x - self.hitboxWidth / 2,
right: self.x + self.hitboxWidth / 2,
top: self.y - self.normalHitboxHeight / 2,
bottom: self.y + self.normalHitboxHeight / 2
};
};
// Get attack hitbox
self.getAttackBounds = function () {
if (!self.isAttacking || self.isSliding) {
// Add sliding check here
return null;
}
return {
left: self.x + (self.attackHitboxOffset - self.attackHitboxWidth / 2),
right: self.x + (self.attackHitboxOffset + self.attackHitboxWidth / 2),
top: self.y - self.attackHitboxHeight / 2,
bottom: self.y + self.attackHitboxHeight / 2
};
};
self.getSlideAttackBounds = function () {
if (!self.isSliding) {
return null;
}
return {
left: self.x + self.hitboxWidth / 2,
// Extend hitbox forward during slide
right: self.x + self.hitboxWidth / 2 + 100,
// Adjust width as needed
top: self.y - self.slideHitboxHeight / 2 + self.slideHitboxYOffset,
bottom: self.y + self.slideHitboxHeight / 2 + self.slideHitboxYOffset
};
};
self.getCollisionBounds = function () {
if (self.isSliding) {
return {
left: self.x - self.hitboxWidth / 2,
right: self.x + self.hitboxWidth / 2,
top: self.y - self.slideHitboxHeight / 2 + self.slideHitboxYOffset,
bottom: self.y + self.slideHitboxHeight / 2 + self.slideHitboxYOffset
};
}
return self.getBounds();
};
// Update heart container
self.updateHeartContainer = function () {
self.heartContainer.x = self.x + 80;
self.heartContainer.y = self.y - 200;
// Handle heart visibility
if (self.heartVisibilityTimer > 0) {
self.heartVisibilityTimer--;
if (self.heartVisibilityTimer <= 0) {
self.heartContainer.alpha = 0;
}
}
};
// Update invulnerability state
self.updateInvulnerability = function () {
if (self.isInvulnerable) {
self.invulnerabilityTimer--;
// Flash only the player, not the hearts
self.alpha = self.invulnerabilityTimer % 10 < 5 ? 0.5 : 1;
if (self.invulnerabilityTimer <= 0) {
self.isInvulnerable = false;
self.alpha = 1;
}
}
};
// Handle platform collision
self.handlePlatformCollision = function () {
// Check if on any platform
var onAnyPlatform = false;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
var leftEdge = platform.x - PLATFORM_HALF_WIDTH + 20;
var rightEdge = platform.x + PLATFORM_HALF_WIDTH + 20;
// Check if player is within horizontal bounds of platform
if (self.x >= leftEdge && self.x <= rightEdge) {
// If we're at platform height and not jumping
if (Math.abs(self.y - (platform.y - PLAYER_PLATFORM_OFFSET)) < 5 && !self.isJumping) {
onAnyPlatform = true;
self.currentPlatform = platform;
self.y = platform.y - PLAYER_PLATFORM_OFFSET;
self.isOnGround = true;
self.velocityY = 0;
break;
}
}
}
// Handle falling state
if (!onAnyPlatform && !self.isJumping) {
self.isOnGround = false;
self.currentPlatform = null;
// Apply gravity
if (self.velocityY === 0) {
self.velocityY = 0.1;
}
}
// If on a platform, check if still above it
if (self.currentPlatform) {
var stillOnPlatform = self.x > self.currentPlatform.x - PLATFORM_HALF_WIDTH && self.x < self.currentPlatform.x + PLATFORM_HALF_WIDTH;
if (!stillOnPlatform) {
var foundAnotherPlatform = false;
for (var i = 0; i < platforms.length; i++) {
var otherPlatform = platforms[i];
if (otherPlatform === self.currentPlatform) {
continue;
}
if (self.x > otherPlatform.x - PLATFORM_HALF_WIDTH && self.x < otherPlatform.x + PLATFORM_HALF_WIDTH) {
// Found another platform
self.currentPlatform = otherPlatform;
foundAnotherPlatform = true;
break;
}
}
// Fall if no other platform found
if (!foundAnotherPlatform) {
self.isOnGround = false;
self.currentPlatform = null;
self.velocityY = 0.1;
}
}
}
};
// Apply physics (separated from animation) - fixed to check collisions for ALL falling cases
self.applyPhysics = function () {
// Apply gravity and velocity to position
if (!self.isOnGround || self.isJumping) {
// Apply velocity and gravity
self.y += self.velocityY;
self.velocityY += 0.7;
// Check for landing on platforms
var hitPlatform = self.checkPlatformCollision();
// REMOVE THE GROUND CHECK - No invisible ground at the bottom!
// We only want to land on actual platforms
}
// Check for falling off screen - moved this check here to ensure it happens
// immediately after physics update
if (self.y > GAME_HEIGHT) {
LK.effects.flashScreen(0xff0000, 1000);
LK.showGameOver();
}
};
// Update attack animation
self.updateAttackAnimation = function () {
var attackOffset = self.runAnimation.length + self.jumpAnimation.length;
self.animationCounter += self.attackAnimationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.attackFrame++;
if (self.attackFrame >= self.attackAnimation.length) {
self.isAttacking = false;
self.attackFrame = 0;
}
}
self.sprites[attackOffset + self.attackFrame].alpha = 1;
};
// Update air attack animation
self.updateAirAttackAnimation = function () {
var airAttackOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length;
self.animationCounter += self.attackAnimationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.attackFrame++;
if (self.attackFrame >= self.airAttackAnimation.length) {
self.isAttacking = false;
self.attackFrame = 0;
}
}
self.sprites[airAttackOffset + self.attackFrame].alpha = 1;
};
// Update jump animation (only animation, not physics)
self.updateJumpAnimation = function () {
var jumpOffset = self.runAnimation.length;
var currentTime = Date.now();
// Show appropriate jump frame
if (currentTime - self.jumpStartTime < 100) {
self.sprites[jumpOffset + 0].alpha = 1;
} else if (self.velocityY < 0) {
self.sprites[jumpOffset + 1].alpha = 1;
} else if (self.velocityY > 0) {
self.sprites[jumpOffset + 2].alpha = 1;
}
};
// Update slide animation
self.updateSlideAnimation = function () {
var slideOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length + self.airAttackAnimation.length;
var standUpOffset = slideOffset + self.slideAnimation.length;
self.slideTimer--;
if (self.slideTimer > self.standUpDuration) {
// Main slide animation (alternate between first two frames)
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
}
var slideFrame = Math.floor(self.animationCounter * 2);
self.sprites[slideOffset + slideFrame].alpha = 1;
} else if (self.slideTimer > 0) {
// Stand up animation
var standUpFrame = Math.floor((self.standUpDuration - self.slideTimer) / (self.standUpDuration / self.standUpAnimation.length));
self.sprites[standUpOffset + standUpFrame].alpha = 1;
} else {
// End slide
if (self.slideTimer <= 0) {
self.isSliding = false;
self.hitboxHeight = self.normalHitboxHeight;
self.lastSlideTime = Date.now();
// Reset speed multiplier instead of base speed
gameSpeedMultiplier = 1.0;
}
}
};
self.updateBowAnimation = function () {
var bowOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length + self.airAttackAnimation.length + self.slideAnimation.length + self.standUpAnimation.length + self.deathAnimation.length;
self.animationCounter += self.attackAnimationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.bowFrame++;
// Spawn arrow at frame 5 if ammo is available
if (self.bowFrame === 5 && self.ammoCount > 0) {
var arrow = new Arrow();
arrow.x = self.x + 100;
arrow.y = self.y;
game.addChild(arrow);
arrows.push(arrow); // Add to global arrows array
self.ammoCount--;
scoreManager.updateAmmo(self.ammoCount);
// Play arrow fire sound effect
LK.getSound('arrowfire').play();
}
if (self.bowFrame >= self.bowAnimation.length) {
self.isShooting = false;
self.bowFrame = 0;
self.bowCooldownTimer = self.bowCooldown;
}
}
self.sprites[bowOffset + self.bowFrame].alpha = 1;
};
// Update death animation
self.updateDeathAnimation = function () {
// Calculate offset to find the death animation sprites
var deathOffset = self.runAnimation.length + self.jumpAnimation.length + self.attackAnimation.length + self.airAttackAnimation.length + self.slideAnimation.length + self.standUpAnimation.length;
// Progress timer
self.deathTimer++;
// Calculate frame based on timer
// We want the animation to play through all frames over the death duration
var frameProgress = self.deathTimer / self.deathDuration * self.deathAnimation.length;
var newFrame = Math.min(Math.floor(frameProgress), self.deathAnimation.length - 1);
// Update frame if it's changed
if (newFrame != self.deathFrame) {
self.deathFrame = newFrame;
}
// Show current frame
self.sprites[deathOffset + self.deathFrame].alpha = 1;
// Death animation is complete, but game over is handled by the timeout
if (self.deathTimer >= self.deathDuration) {
// Game over is now triggered by the timeout in takeDamage
}
};
// Update run animation
self.updateRunAnimation = function () {
self.animationCounter += self.animationSpeed;
if (self.animationCounter >= 1) {
self.animationCounter = 0;
self.runFrame = (self.runFrame + 1) % self.runAnimation.length;
}
self.sprites[self.runFrame].alpha = 1;
};
self.checkPlatformBelow = function () {
if (!self.currentPlatform) {
return null;
}
// Get current platform height
var currentHeight = self.currentPlatform.y;
// Find the closest platform below current one
var platformBelow = null;
var minDistance = Infinity;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Skip if it's not below us or if it's our current platform
if (platform === self.currentPlatform || platform.y <= currentHeight) {
continue;
}
// Check if we're within platform width bounds
if (Math.abs(platform.x - self.x) < PLATFORM_HALF_WIDTH) {
var distance = platform.y - currentHeight;
if (distance < minDistance) {
minDistance = distance;
platformBelow = platform;
}
}
}
return platformBelow;
};
// Check platform collision - fix to properly detect platforms when falling
self.checkPlatformCollision = function () {
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Increase vertical tolerance from 15 to 20 pixels
// Add larger horizontal buffer (30 pixels) on each side for more forgiving landings
if (self.velocityY > 0 && self.y < platform.y - PLAYER_PLATFORM_OFFSET && self.y + self.velocityY >= platform.y - PLAYER_PLATFORM_OFFSET - 20 && self.x > platform.x - (PLATFORM_HALF_WIDTH + 60) && self.x < platform.x + (PLATFORM_HALF_WIDTH + 60)) {
// Snap to platform more precisely
self.y = platform.y - PLAYER_PLATFORM_OFFSET;
self.velocityY = 0;
self.isJumping = false;
self.isOnGround = true;
self.currentPlatform = platform;
return true;
}
}
return false;
};
// Hide all sprites
self.hideAllSprites = function () {
for (var i = 0; i < self.sprites.length; i++) {
self.sprites[i].alpha = 0;
}
};
self.checkCollectibles = function () {
// Always check for collectibles regardless of state
for (var i = 0; i < coins.length; i++) {
var coin = coins[i];
var itemBounds = {
left: coin.x - 25,
right: coin.x + 25,
top: coin.y - 25,
bottom: coin.y + 25
};
if (GameUtils.checkCollision(self.getBounds(), itemBounds)) {
coin.collect();
}
}
};
// Jump method
self.jump = function () {
if (self.isSliding || self.isShooting && self.bowFrame < 2) {
// Only prevent jumps at start of bow animation
return;
}
if (self.isOnGround) {
self.isJumping = true;
self.isOnGround = false;
self.velocityY = -self.jumpHeight;
self.jumpState = "start";
self.jumpStartTime = Date.now();
LK.getSound('playerjump').play();
self.currentPlatform = null;
} else if (self.isJumping && self.velocityY < 10) {
// Small double-jump to reach higher platforms
self.velocityY = -self.jumpHeight * 0.7;
self.jumpStartTime = Date.now();
}
};
// Add slide method
self.slide = function () {
var currentTime = Date.now();
if (!self.isSliding && self.isOnGround && !self.isJumping && !self.isAttacking && currentTime - self.lastSlideTime > self.slideCooldown) {
self.isSliding = true;
self.slideTimer = self.slideDuration;
self.hitboxHeight = self.slideHitboxHeight;
self.animationCounter = 0;
// Adjust game speed multiplier instead of base speed
gameSpeedMultiplier = self.slideSpeedMultiplier;
}
};
// Attack method
self.attack = function () {
if (!self.isAttacking && !self.isSliding) {
// Add sliding check here
self.isAttacking = true;
self.attackFrame = 0;
self.animationCounter = 0;
LK.getSound('swordslash').play();
}
};
self.fallThrough = function () {
if (self.isOnGround && !self.isJumping && !self.isSliding && !self.isAttacking) {
var platformBelow = self.checkPlatformBelow();
if (platformBelow) {
// Disable platform collision temporarily
self.isOnGround = false;
self.currentPlatform = null;
self.velocityY = 5; // Start with a small downward velocity
// Re-enable platform collision after a short delay
LK.setTimeout(function () {
self.checkPlatformCollision = self.originalCheckPlatformCollision;
}, 250); // Adjust timing as needed
// Store the original collision check and temporarily disable it
self.originalCheckPlatformCollision = self.checkPlatformCollision;
self.checkPlatformCollision = function () {
return null;
};
}
}
};
self.shoot = function () {
if (!self.isShooting && !self.isSliding && self.bowCooldownTimer <= 0) {
if (self.isOnGround || !self.isOnGround) {
// Allow shooting in air or ground
self.isShooting = true;
self.bowFrame = 0;
self.animationCounter = 0;
// If in air, pause vertical movement
if (!self.isOnGround) {
self.velocityY = 0;
}
}
}
};
// Take damage method
self.takeDamage = function () {
if (!self.isInvulnerable && !self.isDying) {
LK.getSound('playerouch').play();
self.currentHealth--;
self.heartContainer.updateHealth(self.currentHealth);
// Show hearts and set visibility timer
self.heartContainer.alpha = 1;
self.heartVisibilityTimer = self.heartVisibilityDuration;
// Visual feedback
self.isInvulnerable = true;
self.invulnerabilityTimer = self.invulnerabilityDuration;
// Flash player red
self.tint = 0xFF0000;
tween(self, {
tint: 0xFFFFFF
}, {
duration: 500,
easing: tween.easeOut
});
// Add red screen flash
LK.effects.flashScreen(0xff0000, 300);
// Add screen shake effect
applyScreenShake();
if (self.currentHealth <= 0) {
// If in the air, find the nearest platform to fall to
if (!self.isOnGround) {
// Start the dying sequence but let them fall first
self.dyingInAir = true;
// Find the closest platform below the player
var closestPlatform = null;
var closestDistance = Infinity;
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
// Check if platform is below player and within horizontal range
if (platform.y > self.y && Math.abs(platform.x - self.x) < PLATFORM_HALF_WIDTH) {
var distance = platform.y - self.y;
if (distance < closestDistance) {
closestDistance = distance;
closestPlatform = platform;
}
}
}
// If we found a platform, let player fall to it naturally
// Otherwise proceed with normal death sequence
if (!closestPlatform) {
startDeathSequence();
}
// Player will continue falling until they hit a platform or go off screen
// The death sequence will be triggered in the update method
} else {
// Start death sequence immediately if already on ground
startDeathSequence();
}
}
}
function startDeathSequence() {
// Start death animation
self.isDying = true;
self.deathTimer = 0;
self.deathFrame = 0;
// Set game speed multiplier to 0 when player dies
gameSpeedMultiplier = 0;
// Flash screen
LK.effects.flashScreen(0xff0000, 1000);
// Apply one final screen shake for dramatic effect
applyScreenShake();
// Schedule game over call after 2 seconds
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
};
// Main update method
self.update = function () {
// Update heart container
self.updateHeartContainer();
// Hide all sprites
self.hideAllSprites();
// If player is dying but still in the air, apply physics to let them fall
if (self.dyingInAir) {
// Apply physics for falling
self.applyPhysics();
// Check if they've landed on a platform
if (self.isOnGround) {
// They've landed, now start the death sequence
self.dyingInAir = false;
self.isDying = true;
self.deathTimer = 0;
self.deathFrame = 0;
gameSpeedMultiplier = 0;
LK.effects.flashScreen(0xff0000, 1000);
applyScreenShake();
LK.setTimeout(function () {
LK.showGameOver();
}, 2000);
}
// Show falling animation
var jumpOffset = self.runAnimation.length;
self.sprites[jumpOffset + 2].alpha = 1; // Show falling frame
return;
}
// If player is dying normally, only update death animation
if (self.isDying) {
self.updateDeathAnimation();
return;
}
// Update bow cooldown
if (self.bowCooldownTimer > 0) {
self.bowCooldownTimer--;
}
// Apply physics FIRST - this now happens regardless of animation state
self.applyPhysics();
// Handle platform collision and falling
self.handlePlatformCollision();
// Handle invulnerability
self.updateInvulnerability();
// Update animations based on state
if (self.isShooting) {
self.updateBowAnimation();
} else if (self.isSliding) {
self.updateSlideAnimation();
self.checkCollectibles(); // Explicitly check during slide
} else if (self.isAttacking) {
if (self.isJumping || !self.isOnGround && !self.isSliding) {
self.updateAirAttackAnimation();
} else {
self.updateAttackAnimation();
}
} else if (self.isJumping || !self.isOnGround) {
self.updateJumpAnimation();
} else if (self.isOnGround) {
self.updateRunAnimation();
}
// Check for falling off screen
if (self.y > GAME_HEIGHT && !self.isDying) {
// Start death animation instead of immediately showing game over
self.isDying = true;
self.deathTimer = 0;
self.deathFrame = 0;
LK.effects.flashScreen(0xff0000, 1000);
}
};
return self;
});
var ScorePopup = Container.expand(function (x, y, amount) {
var self = Container.call(this);
// Create the text
self.text = new Text2('+' + amount, {
size: 80,
fill: 0xFFFFFF,
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(self.text);
self.x = x;
self.y = y;
self.velocityY = -3;
self.lifespan = 45;
self.update = function () {
self.y += self.velocityY;
self.lifespan--;
if (self.lifespan < 15) {
self.alpha -= 0.07;
}
if (self.alpha <= 0 || self.lifespan <= 0) {
self.destroy();
}
};
return self;
});
/****
* Constants
****/
var Torch = Container.expand(function () {
var self = Container.call(this);
// Create base torch sprite
self.base = self.attachAsset('torch', {
anchorX: 0.5,
anchorY: 1
});
// Create flame sprite
self.flame = self.attachAsset('torchflame', {
anchorX: 0.5,
anchorY: 1,
y: -180
});
// Create aura sprite
self.aura = self.attachAsset('torchaura', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.3,
y: -250
});
// Animation properties
self.flameTime = Math.random() * Math.PI * 2;
self.auraTime = Math.random() * Math.PI * 2;
self.flameSpeed = 0.05;
self.auraSpeed = 0.03;
// Update animation
self.update = function () {
// Animate flame scale
self.flameTime += self.flameSpeed;
var flameScale = 1 + Math.sin(self.flameTime) * 0.2;
self.flame.scaleY = flameScale;
// Random flip chance for flame
if (Math.random() < 0.02) {
self.flame.scaleX *= -1;
}
// Animate aura alpha
self.auraTime += self.auraSpeed;
var auraAlpha = 0.3 + Math.sin(self.auraTime) * 0.15;
self.aura.alpha = auraAlpha;
};
return self;
});
var TreasureChest = Container.expand(function () {
var self = Container.call(this);
// Attach chest sprite
self.sprite = self.attachAsset('treasurechest', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as breakable
BreakableBehavior.initBreakable(self);
// Break functionality
self["break"] = function () {
BreakableBehavior.standardBreak(self, TreasureChestPiece, 4, function (chest) {
// Spawn health potion with medium chance
if (Math.random() < 0.25) {
var potion = new HealthPotion();
potion.x = chest.x;
potion.y = chest.y;
potion.velocityX = GameUtils.randomRange(1, 6); // Reduced horizontal velocity
potion.velocityY = -GameUtils.randomRange(8, 16); // Reduced vertical velocity
game.addChild(potion);
coins.push(potion);
}
// Higher chance for arrows (25%)
if (Math.random() < 0.30) {
var arrowCount = Math.floor(GameUtils.randomRange(1, 4));
for (var i = 0; i < arrowCount; i++) {
var arrow = new ArrowPickup();
arrow.x = chest.x;
arrow.y = chest.y;
arrow.velocityX = GameUtils.randomRange(1, 6);
arrow.velocityY = -GameUtils.randomRange(8, 16);
// Set rotation to 90 degrees counterclockwise
arrow.sprite.rotation = -Math.PI / 2;
game.addChild(arrow);
coins.push(arrow);
}
}
// Spawn valuable items
var totalItems = Math.floor(GameUtils.randomRange(3, 9));
for (var i = 0; i < totalItems; i++) {
// Random chance for different gems
var rand = Math.random();
var item;
if (rand < 0.05) {
// 5% diamond
item = new Coin('diamond');
} else if (rand < 0.15) {
// 10% emerald
item = new Coin('emerald');
} else if (rand < 0.30) {
// 15% ruby
item = new Coin('ruby');
} else {
// 70% gold coin
item = new Coin('coin');
}
item.x = chest.x;
item.y = chest.y;
item.velocityX = GameUtils.randomRange(1, 6); // Reduced horizontal velocity
item.velocityY = -GameUtils.randomRange(8, 16); // Reduced vertical velocity
game.addChild(item);
coins.push(item);
}
LK.getSound('woodbreak').play();
});
};
return self;
});
var TreasureChestPiece = Container.expand(function (pieceNum) {
var self = Container.call(this);
// Attach piece sprite
self.sprite = self.attachAsset('treasurechestpiece' + pieceNum, {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xC0C0C0
});
// Initialize as piece
PieceBehavior.initPiece(self);
// Update method
self.update = function () {
PieceBehavior.standardUpdate(self);
};
self.checkPlatformCollision = function () {
return GameUtils.checkPlatformCollision(self, 80, true) != null;
};
return self;
});
/****
* Initialize Game
****/
/****
* Game Initialization
****/
var game = new LK.Game({
backgroundColor: 0x000000
});
/****
* Game Code
****/
/****
* Game Management
****/
// Base collectible behavior for items that can be collected
var CollectibleBehavior = {
initPhysics: function initPhysics(self) {
self.velocityX = 0;
self.velocityY = 0;
self.collected = false;
self.bounceCount = 0;
self.maxBounces = 2;
},
standardUpdate: function standardUpdate(self) {
if (self.collected) {
return;
}
// Apply physics
self.velocityY += 0.5; // gravity
self.x += self.velocityX; // Original throw physics
self.y += self.velocityY;
self.x -= PLATFORM_SPEED * (gameSpeedMultiplier - 1); // Only apply the extra speed from sliding
// Check for platform collision with bounce
if (self.velocityY > 0) {
GameUtils.checkPlatformCollision(self, 80, true);
}
// Check if off screen
if (self.x < -50 || self.y > GAME_HEIGHT) {
self.destroy();
return;
}
// Player collection detection - use slide hitbox height if sliding
var playerBounds = {
left: player.x - player.hitboxWidth / 2,
right: player.x + player.hitboxWidth / 2,
top: player.y - player.hitboxHeight / 2,
bottom: player.y + player.hitboxHeight / 2
};
var itemBounds = {
left: self.x - 25,
right: self.x + 25,
top: self.y - 25,
bottom: self.y + 25
};
if (GameUtils.checkCollision(playerBounds, itemBounds)) {
self.collect();
}
}
};
// Base behavior for breakable objects
var BreakableBehavior = {
initBreakable: function initBreakable(self) {
self.isBreaking = false;
self.currentPlatform = null;
},
standardBreak: function standardBreak(self, pieceClass, pieceCount, itemSpawnCallback) {
if (self.isBreaking) {
return;
}
self.isBreaking = true;
// Spawn pieces
for (var i = 1; i <= pieceCount; i++) {
var piece = new pieceClass(i);
piece.x = self.x;
piece.y = self.y;
piece.velocityX = GameUtils.randomRange(-6, 6);
piece.velocityY = -GameUtils.randomRange(6, 12);
piece.rotationSpeed = GameUtils.randomRange(-0.1, 0.1);
game.addChild(piece);
}
// Call the custom item spawn callback
if (itemSpawnCallback) {
itemSpawnCallback(self);
}
self.destroy();
}
};
// Base behavior for pieces of broken objects
var PieceBehavior = {
initPiece: function initPiece(self) {
self.velocityX = 0;
self.velocityY = 0;
self.rotationSpeed = 0;
self.fadeSpeed = 0.02;
self.bounceCount = 0;
self.maxBounces = 2;
},
standardUpdate: function standardUpdate(self) {
// Apply physics
self.velocityY += 0.5; // gravity
self.x += self.velocityX;
self.y += self.velocityY;
self.rotation += self.rotationSpeed;
// Check for platform collision
var platformCollision = GameUtils.checkPlatformCollision(self, 80, self.bounceCount < self.maxBounces);
if (platformCollision && self.bounceCount >= self.maxBounces) {
// Start fading after max bounces
self.velocityY = 0;
self.velocityX = -PLATFORM_SPEED;
self.alpha -= self.fadeSpeed;
if (self.alpha <= 0) {
self.destroy();
}
}
// Destroy if off screen
if (self.x < -50 || self.y > GAME_HEIGHT) {
self.destroy();
}
}
};
/****
* Constants
****/
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var PLATFORM_WIDTH = 1000;
var PLATFORM_HALF_WIDTH = PLATFORM_WIDTH / 2;
var PLATFORM_OVERLAP = 50;
var PLATFORM_SPEED = 5;
var GAP_START_TIME = 90000; // 1.5 minutes in milliseconds
var ENEMY_PLATFORM_OFFSET = 225;
var PLAYER_PLATFORM_OFFSET = 250;
var MIN_PLATFORMS_IN_SEQUENCE = 2;
var MAX_PLATFORMS_IN_SEQUENCE = 5;
var JUMP_COOLDOWN = 200;
var MOVE_THRESHOLD = 90;
var SLIDE_MOVE_THRESHOLD = 300;
var VERTICAL_DEADZONE = 30;
/****
* Utilities
****/
var GameUtils = {
// Check for collision between two rectangular bounds
checkCollision: function checkCollision(bounds1, bounds2) {
return bounds1.left < bounds2.right && bounds1.right > bounds2.left && bounds1.top < bounds2.bottom && bounds1.bottom > bounds2.top;
},
// Check platform collision with standard offset
checkPlatformCollision: function checkPlatformCollision(obj, offsetY, bounceOnCollision) {
for (var i = 0; i < platforms.length; i++) {
var platform = platforms[i];
if (Math.abs(obj.y - (platform.y - offsetY)) < 10 && obj.x > platform.x - PLATFORM_HALF_WIDTH && obj.x < platform.x + PLATFORM_HALF_WIDTH) {
obj.y = platform.y - offsetY;
if (bounceOnCollision && obj.bounceCount < obj.maxBounces) {
LK.getSound('coinbounce').play();
var impactSpeed = Math.abs(obj.velocityY);
obj.velocityY = -(impactSpeed * 0.5);
obj.velocityX *= 0.8;
obj.bounceCount++;
return true;
} else if (bounceOnCollision) {
obj.velocityY = 0;
obj.velocityX = -PLATFORM_SPEED;
}
return platform;
}
}
return null;
},
// Get random value within range
randomRange: function randomRange(min, max) {
return Math.random() * (max - min) + min;
},
// Check if position is clear for spawning
canSpawnAtPosition: function canSpawnAtPosition(x) {
var safeDistance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 200;
for (var i = 0; i < collectibles.length; i++) {
if (Math.abs(collectibles[i].x - x) < safeDistance) {
return false;
}
}
return true;
}
};
var ScoreManager = function ScoreManager() {
var self = {};
// Initialize score and UI
self.score = 0;
self.scoreText = new Text2('0', {
size: 160,
fill: 0xFFFFFF,
anchorX: 1,
anchorY: 0.5
});
self.coinIcon = LK.getAsset('coin', {
anchorX: 0,
anchorY: 0.5,
scaleX: 2,
scaleY: 2
});
// New ammo display
self.ammoText = new Text2('0x', {
size: 160,
fill: 0xFFFFFF,
anchorX: 1,
anchorY: 0.5
});
self.ammoIcon = LK.getAsset('arrow', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.4,
rotation: -Math.PI / 2 // Rotate 90 degrees counterclockwise
});
// Initialize container
self.container = new Container();
self.container.addChild(self.scoreText);
self.container.addChild(self.coinIcon);
self.container.addChild(self.ammoText);
self.container.addChild(self.ammoIcon);
// Position container
self.container.x = GAME_WIDTH - 100;
self.container.y = 100;
// Position elements
self.scoreText.x = -100;
self.scoreText.y = -100;
self.coinIcon.x = 0;
self.coinIcon.y = 0;
self.ammoText.x = -150;
self.ammoText.y = 60; // Place below coin display
self.ammoIcon.x = 55;
self.ammoIcon.y = 220; // Place below coin display
// Add ammo update method
self.updateAmmo = function (amount) {
self.ammoText.setText(amount + 'x');
// Adjust position based on digit count
if (amount >= 10) {
self.ammoText.x = -200;
} else {
self.ammoText.x = -150;
}
};
// Getter and setter for score
self.getScore = function () {
return self.score;
};
self.setScore = function (newScore) {
self.score = newScore;
self.scoreText.setText(newScore);
// Adjust position based on digit count
if (newScore >= 10 && newScore < 100) {
self.scoreText.x = -200;
} else if (newScore >= 100 && newScore < 1000) {
self.scoreText.x = -300;
} else if (newScore >= 1000) {
self.scoreText.x = -400;
} else {
self.scoreText.x = -100;
}
};
// Add score and display popup
self.addScore = function (amount, x, y) {
self.setScore(self.score + amount);
LK.setScore(self.score);
// Create score popup if position is provided
if (x !== undefined && y !== undefined) {
var popup = new ScorePopup(x, y - 30, amount);
game.addChild(popup);
}
};
return self;
};
/****
* Game Variables
****/
// Containers
var backgroundContainer = game.addChild(new Container());
var midgroundContainer = game.addChild(new Container());
var foregroundContainer = game.addChild(new Container());
var scoreManager = new ScoreManager();
game.addChild(scoreManager.container);
var scoreContainer = game.addChild(new Container());
// Game state
var gameStarted = false;
var titleScreen;
var playButton;
var gameStartTime;
// Platform management
var platforms = [];
var gameSpeedMultiplier = 1.0;
var platformSpawnCounter = 0;
var platformsUntilNextChange = 0;
var lowPlatformHeight = GAME_HEIGHT / 1.5 + PLAYER_PLATFORM_OFFSET - 100;
var highPlatformHeight = lowPlatformHeight - 600;
var HIGHEST_PLATFORM_HEIGHT = highPlatformHeight - 600;
var LOWEST_PLATFORM_HEIGHT = lowPlatformHeight + 600;
var currentPlatformHeight = lowPlatformHeight;
var lastPlatformHeight = lowPlatformHeight;
var lastPlatformX = 0;
var currentPlatformPattern = 'A'; // 'A' for 1&3, 'B' for 2&4
var currentStraightHeight;
// Touch controls
var touchStartX = 0;
var touchStartY = 0;
var touchEndX = 0;
var touchEndY = 0;
var lastMoveY = 0;
var lastJumpTime = 0;
// Game objects
var jars = [];
var coins = [];
var collectibles = [];
var arrows = [];
var jarSpawnCounter = 0;
var jarSpawnInterval = 10;
var chestSpawnCounter = 0;
var chestSpawnInterval = 75;
var enemies = [];
var enemySpawnInterval = 100;
var goblinSpawnCounter = 0;
var goblinSpawnInterval = 100;
var eyeballSpawnCounter = 150;
var eyeballSpawnInterval = 500;
var particleSystem;
var heartContainer = new HeartContainer();
var player;
// Background elements
var bg1 = backgroundContainer.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 1
}));
var bg2 = backgroundContainer.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 1
}));
bg1.y = GAME_HEIGHT;
bg2.y = GAME_HEIGHT;
bg2.x = GAME_WIDTH;
// Midground elements
var mg1 = midgroundContainer.addChild(LK.getAsset('midground', {
anchorX: 0,
anchorY: 1
}));
var mg2 = midgroundContainer.addChild(LK.getAsset('midground', {
anchorX: 0,
anchorY: 1
}));
mg1.y = GAME_HEIGHT;
mg2.y = GAME_HEIGHT;
mg2.x = GAME_WIDTH;
// Foreground elements
var fg1 = foregroundContainer.addChild(LK.getAsset('foreground', {
anchorX: 0,
anchorY: 1
}));
var fg2 = foregroundContainer.addChild(LK.getAsset('foreground', {
anchorX: 0,
anchorY: 1
}));
fg1.y = GAME_HEIGHT * 1.25;
fg2.y = GAME_HEIGHT * 1.25;
fg1.x = 0;
fg2.x = GAME_WIDTH;
/****
* Game Functions
****/
// Screen shake effect function
function applyScreenShake() {
var shakeIntensity = 30;
var shakeDuration = 400;
var shakeCount = 8;
var originalX = game.x;
var originalY = game.y;
// Stop any existing shake
tween.stop(game, {
x: true,
y: true
});
// Reset position to ensure we start from 0,0
game.x = 0;
game.y = 0;
// Recursive function to create multiple shakes
function createShake(count) {
if (count <= 0) {
// Final tween to settle back to original position
tween(game, {
x: originalX,
y: originalY
}, {
duration: shakeDuration / 4,
easing: tween.easeOut
});
return;
}
// Calculate decreasing intensity
var currentIntensity = shakeIntensity * (count / shakeCount);
// Random offset direction
var offsetX = (Math.random() - 0.5) * 2 * currentIntensity;
var offsetY = (Math.random() - 0.5) * 2 * currentIntensity;
// Apply shake movement
tween(game, {
x: offsetX,
y: offsetY
}, {
duration: shakeDuration / shakeCount,
easing: tween.linear,
onFinish: function onFinish() {
createShake(count - 1);
}
});
}
// Start the shake sequence
createShake(shakeCount);
}
// Create the title screen
function createTitleScreen() {
titleScreen = new Container();
game.addChild(titleScreen);
// Add title image with fade-in
var titleImage = titleScreen.attachAsset('title', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
titleImage.x = GAME_WIDTH / 2;
titleImage.y = GAME_HEIGHT / 2.7;
// Fade in title
tween(titleImage, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeIn
});
// Add play button
playButton = titleScreen.attachAsset('playbutton', {
anchorX: 0.5,
anchorY: 0.5
});
playButton.x = GAME_WIDTH / 2;
playButton.y = GAME_HEIGHT / 1.3;
// Add flashing animation
function flashPlayButton() {
tween(playButton, {
alpha: 0
}, {
duration: 250,
easing: tween.linear,
onFinish: function onFinish() {
tween(playButton, {
alpha: 1
}, {
duration: 250,
easing: tween.linear
});
}
});
}
// Flash every 2 seconds
LK.setInterval(flashPlayButton, 2000);
// Initialize torch decorations
initializeTorches();
}
// Place torches in the scene
function initializeTorches() {
// Create torches for the two background sections
var torch1 = new Torch();
torch1.x = 25;
torch1.y = GAME_HEIGHT * 0.7;
midgroundContainer.addChild(torch1);
var torch2 = new Torch();
torch2.x = GAME_WIDTH + 25;
torch2.y = GAME_HEIGHT * 0.7;
midgroundContainer.addChild(torch2);
}
// Initialize game elements
function initializeGame() {
// Create player
player = game.addChild(new Player());
player.x = GAME_WIDTH / 4.5;
player.y = GAME_HEIGHT / 1.5 - 100;
game.addChild(heartContainer);
scoreManager.updateAmmo(player.ammoCount);
// Create initial platforms at the low level
for (var i = 0; i < 5; i++) {
var platform = new Platform();
if (i === 0) {
// First platform centered on player
platform.x = player.x;
} else {
// Position with slight overlap
platform.x = lastPlatformX + PLATFORM_WIDTH - PLATFORM_OVERLAP;
}
platform.y = lowPlatformHeight;
platforms.push(platform);
game.addChild(platform);
lastPlatformX = platform.x;
}
lastPlatformHeight = lowPlatformHeight;
currentPlatformPattern = 'A';
platformsUntilNextChange = 0; // This will trigger new sequence immediately after initial platforms
player.isOnGround = true;
player.currentPlatform = platforms[0];
}
// Start the game
function startGame() {
gameStarted = true;
gameStartTime = Date.now();
titleScreen.destroy();
initializeGame();
// Initialize particle system
particleSystem = new ParticlePool(100);
game.addChild(particleSystem);
// Show health
player.heartContainer.alpha = 1;
player.heartVisibilityTimer = 120;
// Play background music
LK.playMusic('backgroundmusic1', {
fade: {
start: 0,
end: 0.7,
duration: 1000
}
});
}
// Update background layers
function updateBackgrounds() {
// Background layer (slowest)
bg1.x -= PLATFORM_SPEED * 0.3 * gameSpeedMultiplier;
bg2.x -= PLATFORM_SPEED * 0.3 * gameSpeedMultiplier;
if (bg1.x <= -GAME_WIDTH) {
bg1.x = bg2.x + GAME_WIDTH;
}
if (bg2.x <= -GAME_WIDTH) {
bg2.x = bg1.x + GAME_WIDTH;
}
// Midground layer
mg1.x -= PLATFORM_SPEED * 0.6 * gameSpeedMultiplier;
mg2.x -= PLATFORM_SPEED * 0.6 * gameSpeedMultiplier;
if (mg1.x <= -GAME_WIDTH) {
mg1.x = mg2.x + GAME_WIDTH;
}
if (mg2.x <= -GAME_WIDTH) {
mg2.x = mg1.x + GAME_WIDTH;
}
// Foreground layer (fastest)
fg1.x -= PLATFORM_SPEED * gameSpeedMultiplier;
fg2.x -= PLATFORM_SPEED * gameSpeedMultiplier;
if (fg1.x <= -GAME_WIDTH) {
fg1.x = fg2.x + GAME_WIDTH;
}
if (fg2.x <= -GAME_WIDTH) {
fg2.x = fg1.x + GAME_WIDTH;
}
// Update torches
for (var i = 0; i < midgroundContainer.children.length; i++) {
var child = midgroundContainer.children[i];
if (child instanceof Torch) {
child.update();
child.x -= PLATFORM_SPEED * 0.6 * gameSpeedMultiplier;
if (child.x <= -GAME_WIDTH) {
child.x = child.x + GAME_WIDTH * 2;
}
}
}
}
// Update and spawn platforms
function updatePlatforms() {
var lastPlatform = platforms[platforms.length - 1];
if (lastPlatform && lastPlatform.x < GAME_WIDTH + 500) {
// Special case for first platform after initial sequence
if (!currentPlatformPattern) {
currentPlatformPattern = 'A';
platformsUntilNextChange = Math.floor(Math.random() * (MAX_PLATFORMS_IN_SEQUENCE - MIN_PLATFORMS_IN_SEQUENCE + 1)) + MIN_PLATFORMS_IN_SEQUENCE;
}
if (platformsUntilNextChange <= 0) {
// Add chance to switch to pattern C (straight section)
var patternChance = Math.random();
if (patternChance < 0.1) {
currentPlatformPattern = 'C';
platformsUntilNextChange = Math.floor(Math.random() * 4) + 6;
currentStraightHeight = Math.random() < 0.5 ? highPlatformHeight : lowPlatformHeight;
} else {
currentPlatformPattern = currentPlatformPattern === 'A' ? 'B' : 'A';
platformsUntilNextChange = Math.floor(Math.random() * (MAX_PLATFORMS_IN_SEQUENCE - MIN_PLATFORMS_IN_SEQUENCE + 1)) + MIN_PLATFORMS_IN_SEQUENCE;
}
}
// Calculate gap probability
var timeSinceStart = Date.now() - gameStartTime;
var baseGapChance = 0.025;
var additionalChance = Math.min(0.10, timeSinceStart / 600000);
var totalGapChance = baseGapChance + additionalChance;
if (currentPlatformPattern === 'C') {
// Single platform for straight sections, using stored height
var platform = new Platform();
if (Math.random() < totalGapChance) {
platform.x = lastPlatform.x + (PLATFORM_WIDTH + 400);
} else {
platform.x = lastPlatform.x + (PLATFORM_WIDTH - PLATFORM_OVERLAP);
}
platform.y = currentStraightHeight;
platforms.push(platform);
game.addChild(platform);
} else {
// Regular dual platform patterns with possible double gaps
var platformUpper = new Platform();
var platformLower = new Platform();
// Determine gap type with random chance
var gapChance = Math.random();
var hasDoubleGap = gapChance < totalGapChance * 0.3; // 30% of gap chance is double gap
var hasSingleGap = !hasDoubleGap && gapChance < totalGapChance; // Remaining gap chance is single gap
// If single gap, randomly choose which platform gets it
var upperGap = hasDoubleGap || hasSingleGap && Math.random() < 0.5;
var lowerGap = hasDoubleGap || hasSingleGap && !upperGap;
// Position upper platform
if (upperGap) {
platformUpper.x = lastPlatform.x + (PLATFORM_WIDTH + 400);
} else {
platformUpper.x = lastPlatform.x + (PLATFORM_WIDTH - PLATFORM_OVERLAP);
}
// Position lower platform
if (lowerGap) {
platformLower.x = lastPlatform.x + (PLATFORM_WIDTH + 400);
} else {
platformLower.x = lastPlatform.x + (PLATFORM_WIDTH - PLATFORM_OVERLAP);
}
if (currentPlatformPattern === 'A') {
platformUpper.y = HIGHEST_PLATFORM_HEIGHT;
platformLower.y = lowPlatformHeight;
} else {
platformUpper.y = highPlatformHeight;
platformLower.y = LOWEST_PLATFORM_HEIGHT;
}
platforms.push(platformUpper);
platforms.push(platformLower);
game.addChild(platformUpper);
game.addChild(platformLower);
}
platformsUntilNextChange--;
}
// Update platforms
for (var i = platforms.length - 1; i >= 0; i--) {
platforms[i].update();
if (platforms[i].destroyed) {
platforms.splice(i, 1);
}
}
}
// Update and spawn collectibles
function updateCollectibles() {
// Jar spawning
jarSpawnCounter++;
if (jarSpawnCounter >= jarSpawnInterval) {
var availablePlatforms = platforms.filter(function (p) {
return p.x > GAME_WIDTH && p.x < GAME_WIDTH + 300;
});
// Try to spawn on multiple platforms
availablePlatforms.forEach(function (platform) {
if (Math.random() < 0.25 && GameUtils.canSpawnAtPosition(platform.x)) {
// Adjusted probability
var jar = new Jar();
jar.x = platform.x;
jar.y = platform.y - 130;
jar.currentPlatform = platform;
collectibles.push(jar);
game.addChild(jar);
}
});
jarSpawnCounter = 0;
}
// Treasure chest spawning
chestSpawnCounter++;
if (chestSpawnCounter >= chestSpawnInterval) {
var availablePlatforms = platforms.filter(function (p) {
return p.x > GAME_WIDTH && p.x < GAME_WIDTH + 300;
});
availablePlatforms.forEach(function (platform) {
if (Math.random() < 0.12 && GameUtils.canSpawnAtPosition(platform.x)) {
// Adjusted probability
var chest = new TreasureChest();
chest.x = platform.x;
chest.y = platform.y - 130;
chest.currentPlatform = platform;
collectibles.push(chest);
game.addChild(chest);
}
});
chestSpawnCounter = 0;
}
// Update existing collectibles
for (var i = collectibles.length - 1; i >= 0; i--) {
var collectible = collectibles[i];
if (collectible.currentPlatform) {
collectible.x = collectible.currentPlatform.x;
}
var attackBounds = player.getAttackBounds();
var slideAttackBounds = player.getSlideAttackBounds();
var itemBounds = {
left: collectible.x - 50,
right: collectible.x + 50,
top: collectible.y - 75,
bottom: collectible.y + 75
};
if (attackBounds && GameUtils.checkCollision(attackBounds, itemBounds) || slideAttackBounds && GameUtils.checkCollision(slideAttackBounds, itemBounds)) {
collectible["break"]();
collectibles.splice(i, 1);
continue;
}
if (collectible.x < -50) {
collectible.destroy();
collectibles.splice(i, 1);
}
}
// Update coins and other collectibles
for (var i = coins.length - 1; i >= 0; i--) {
coins[i].update();
if (coins[i].destroyed) {
coins.splice(i, 1);
}
}
// Update score popups
for (var i = game.children.length - 1; i >= 0; i--) {
var child = game.children[i];
if (child instanceof ScorePopup) {
child.update();
}
}
}
// Update and spawn enemies
function updateEnemies() {
// Goblin spawning
goblinSpawnCounter++;
if (goblinSpawnCounter >= goblinSpawnInterval) {
var availablePlatforms = platforms.filter(function (p) {
return p.x > GAME_WIDTH - 100 && p.x < GAME_WIDTH + 300;
});
if (availablePlatforms.length > 0) {
var enemy = new Enemy('goblin');
var platform = availablePlatforms[Math.floor(Math.random() * availablePlatforms.length)];
enemy.x = platform.x;
enemy.y = platform.y - ENEMY_PLATFORM_OFFSET;
enemy.currentPlatform = platform;
enemies.push(enemy);
game.addChild(enemy);
goblinSpawnInterval = Math.floor(Math.random() * 150) + 100;
goblinSpawnCounter = 0;
} else {
goblinSpawnCounter = Math.max(0, goblinSpawnCounter - 20);
}
}
// Eyeball spawning - updated height range
eyeballSpawnCounter++;
if (eyeballSpawnCounter >= eyeballSpawnInterval) {
var enemy = new Enemy('eyeball');
var heightRange = LOWEST_PLATFORM_HEIGHT - HIGHEST_PLATFORM_HEIGHT;
var randomHeight = Math.random() * heightRange;
enemy.x = GAME_WIDTH + 100;
enemy.y = HIGHEST_PLATFORM_HEIGHT + randomHeight; // Spawn anywhere between highest and lowest
enemies.push(enemy);
game.addChild(enemy);
eyeballSpawnInterval = Math.floor(Math.random() * 300) + 150;
eyeballSpawnCounter = 0;
}
// Update enemies and check collisions
for (var j = enemies.length - 1; j >= 0; j--) {
enemies[j].update();
// Skip if enemy is far behind player
if (enemies[j].x < player.x - 100) {
continue;
}
var playerBounds = player.getCollisionBounds();
var enemyBounds = enemies[j].getBounds();
var attackBounds = player.getAttackBounds();
var slideAttackBounds = player.getSlideAttackBounds();
// Check for attack collision first
if (attackBounds && !enemies[j].isHit && !enemies[j].isDying) {
if (enemies[j].x > player.x && GameUtils.checkCollision(attackBounds, enemyBounds)) {
enemies[j].hit();
if (enemies[j].type === 'eyeball') {
LK.getSound('eyeballhit').play();
} else {
LK.getSound('enemyhit').play();
}
continue;
}
}
// In the updateEnemies() function, inside the enemy update loop
for (var i = arrows.length - 1; i >= 0; i--) {
var arrow = arrows[i];
if (GameUtils.checkCollision(arrow.getBounds(), enemyBounds)) {
enemies[j].hit();
arrow.destroy();
arrows.splice(i, 1);
if (enemies[j].type === 'eyeball') {
LK.getSound('eyeballhit').play();
} else {
LK.getSound('enemyhit').play();
}
break;
}
}
// Check for slide collision
if (slideAttackBounds && !enemies[j].isHit && !enemies[j].isDying) {
if (enemies[j].x > player.x && GameUtils.checkCollision(slideAttackBounds, enemyBounds)) {
enemies[j].slideHit();
continue;
}
}
if (GameUtils.checkCollision(playerBounds, enemyBounds)) {
if (!enemies[j].isHit && !enemies[j].isDying) {
player.takeDamage();
}
}
}
}
// Create the initial title screen
createTitleScreen();
/****
* Game Loop & Input Handlers
****/
// Main game update loop
game.update = function () {
// Always update backgrounds for visual effect
updateBackgrounds();
// If game hasn't started, don't update gameplay
if (!gameStarted) {
return;
}
// Update game entities
player.update();
if (particleSystem) {
particleSystem.update();
}
// Update arrows
for (var i = arrows.length - 1; i >= 0; i--) {
arrows[i].update();
if (arrows[i].destroyed) {
arrows.splice(i, 1);
}
}
// Update game world
updatePlatforms();
updateCollectibles();
updateEnemies();
};
// Handle touch/click events
game.down = function (x, y, obj) {
touchStartX = x;
touchStartY = y;
};
game.up = function (x, y, obj) {
if (!gameStarted) {
var buttonBounds = {
left: playButton.x - playButton.width / 2,
right: playButton.x + playButton.width / 2,
top: playButton.y - playButton.height / 2,
bottom: playButton.y + playButton.height / 2
};
if (x >= buttonBounds.left && x <= buttonBounds.right && y >= buttonBounds.top && y <= buttonBounds.bottom) {
startGame();
}
return;
}
touchEndX = x;
touchEndY = y;
var deltaY = touchEndY - touchStartY;
var deltaX = touchEndX - touchStartX;
// Ignore very small movements
if (Math.abs(deltaY) < VERTICAL_DEADZONE && Math.abs(deltaX) < VERTICAL_DEADZONE) {
player.attack(); // Just treat as tap/attack
return;
}
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// Horizontal swipe
if (deltaX > 70) {
// Right swipe - trigger slide
player.slide();
} else if (deltaX < -70) {
// Left swipe - trigger bow shot
player.shoot();
}
} else if (deltaY > 120) {
// Downward swipe
player.fallThrough();
} else if (deltaY < -120) {
// Upward swipe - existing jump code
var currentTime = Date.now();
if (currentTime - lastJumpTime > JUMP_COOLDOWN) {
player.jump();
lastJumpTime = currentTime;
}
} else {
// Tap - existing attack code
player.attack();
}
};
game.move = function (x, y, obj) {
if (!gameStarted) {
return;
}
var deltaX = x - touchStartX;
if (Math.abs(deltaX) > Math.abs(y - touchStartY)) {
if (deltaX > SLIDE_MOVE_THRESHOLD) {
player.slide();
} else if (deltaX < -SLIDE_MOVE_THRESHOLD) {
player.shoot();
}
}
// Check for downward swipe
var deltaY = y - touchStartY;
if (deltaY > SLIDE_MOVE_THRESHOLD) {
player.fallThrough();
}
if (lastMoveY !== 0) {
var deltaY = y - lastMoveY;
var currentTime = Date.now();
if (deltaY < -MOVE_THRESHOLD && currentTime - lastJumpTime > JUMP_COOLDOWN) {
player.jump();
lastJumpTime = currentTime;
}
}
lastMoveY = y;
}; ===================================================================
--- original.js
+++ change.js
@@ -43,9 +43,9 @@
var ArrowPickup = Container.expand(function () {
var self = Container.call(this);
self.sprite = self.attachAsset('arrow', {
anchorX: 0.5,
- anchorY: 0.5,
+ anchorY: 0.7,
scaleX: 1.2,
scaleY: 1.2
});
// Initialize physics properties
@@ -1724,12 +1724,12 @@
/****
* Game Code
****/
-// Base collectible behavior for items that can be collected
/****
* Game Management
****/
+// Base collectible behavior for items that can be collected
var CollectibleBehavior = {
initPhysics: function initPhysics(self) {
self.velocityX = 0;
self.velocityY = 0;
2D Single Monster. In-Game asset. 2d. Blank background. High contrast. No shadows..
A gold coin. 8 bit pixel art. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
Dark and moody dungeon background. Infinite repeatable texture. 8 bit pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A ruby. Pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A wooden arrow with white feathers and a steel arrow head. Horizontal. Pixel art. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
A thin crude sword, no pommel. 8 bit pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
An icon of white wings. Pixel art.. Single Game Texture. In-Game asset. 2d. Blank background. High contrast. No shadows
a magnet icon. 8 bit pixel art. In-Game asset. 2d. High contrast. No shadows
An icon of a glowing wooden arrow from a bow trailing pink particles. Pixel art.. In-Game asset. 2d. High contrast. No shadows
backgroundmusic1
Music
playerjump
Sound effect
swordslash
Sound effect
jarbreak
Sound effect
enemyhit
Sound effect
eyeballhit
Sound effect
coincollect
Sound effect
woodbreak
Sound effect
coinbounce
Sound effect
potion
Sound effect
playerouch
Sound effect
bowfiring
Sound effect
arrowfire
Sound effect
arrowpickup
Sound effect
gameover
Sound effect
skeletonhit
Sound effect
gameover2
Sound effect
shopbuy
Sound effect
menuselect
Sound effect
cantbuy
Sound effect
startgame
Sound effect
platformcrumble
Sound effect
rocksfall
Sound effect
airdash
Sound effect
groundimpact
Sound effect
groundsmashfalling
Sound effect
slide
Sound effect
mushroomburst
Sound effect
mushroomhiss
Sound effect
mushroomdie
Sound effect
mushroombounce
Sound effect