User prompt
reduce the boss health ind 7th dungeon
User prompt
make the boss in dungeon 7 die after all of his soldiers die
User prompt
make the dungeon 7 boss die after all of its soldiers die
User prompt
Every time an enemy is killed, the money automatically increases and the amount of money increases depending on the difficulty of the enemy. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Every time a character is killed, the money automatically increases and the amount of money increases depending on the difficulty of the enemy. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
character is going to left by itself right now can you fix it
User prompt
character is going to left by itself right now can you fix it
User prompt
can you fix the caharecter going left by itself
User prompt
Please fix the bug: 'TypeError: Cannot read properties of undefined (reading 'update')' in or related to this line: 'coins[i].update();' Line Number: 5013
User prompt
Center the upgrade menu on the screen so that the entire menu is visible ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Center the upgrade menu on the screen so that the entire menu is visible
User prompt
Put the upgrade menu to top left corner
User prompt
Put the upgrade menu to top left corner
User prompt
Put the upgrade menu to center of the screen
User prompt
put rhe upgrades menu center of the screen
User prompt
rearrange the upgrades buttn and the upgrades ui position for better visibilty
User prompt
- Create an upgrade tree with categories: Combat, Defense, Utility - Combat upgrades: increased damage, faster attacks, knife penetration - Defense upgrades: more health, damage reduction, temporary invincibility - Utility upgrades: faster movement, more starting knives, coin magnetism ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
- **Scouts**: Fast enemies that alert others and flee from combat - **Berserkers**: Enemies that become more dangerous when low on health - **Archers**: Long-range enemies that keep distance and shoot projectiles - **Shields**: Enemies that block projectiles and protect others ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
- Implement group AI where enemies communicate their states - Add flanking behavior where enemies try to surround the player - Create "leader" enemies that buff nearby allies - Implement pack hunting where fast enemies herd the player toward stronger ones ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
- Implement damage numbers that float up from enemies when hit - Add screen flash effects for critical hits or boss phase transitions - Create weapon trail effects for the hero's sword attacks - Add glowing outlines to indicate different enemy states (alerted, enraged, etc.) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
- Expand blood particles with different sizes, speeds, and lifetimes - Add impact sparks when knives hit enemies - Create dust clouds when enemies or player move quickly - Add magical effects for power-ups and special abilities ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
- Create a `ScreenShake` class that applies temporary camera offset modifications - Add shake on: boss attacks, player hits, enemy deaths, knife impacts - Different shake intensities and durations for different events - Smooth shake decay using tween easing functions ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
- Add state machines to bosses: `IDLE`, `PURSUING`, `ATTACKING`, `STUNNED`, `ENRAGED` - Implement predictive targeting where bosses anticipate player movement - Add boss "tells" - visual warnings before major attacks - Create vulnerability windows where bosses can take extra damage after certain attacks ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
can you implement the second part(2. Enhanced Boss Mechanics and Variety (Priority 2)) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
yes can you please implement everything you listed to me ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Base Boss class with common functionality
var BaseBoss = Container.expand(function () {
var self = Container.call(this);
self.health = 50;
self.maxHealth = 50;
self.speed = 3;
self.lastX = 0;
self.lastY = 0;
self.state = 'IDLE'; // AI states: IDLE, PURSUING, ATTACKING, STUNNED, ENRAGED
self.stateTimer = 0;
self.attackCooldown = 0;
self.isInvulnerable = false;
self.bossType = 'base';
self.phase = 1;
self.predictedHeroX = 0;
self.predictedHeroY = 0;
// Predictive targeting system
self.updatePrediction = function () {
var heroVelocityX = hero.x - hero.lastX;
var heroVelocityY = hero.y - hero.lastY;
var predictionFrames = 30; // Predict 0.5 seconds ahead
self.predictedHeroX = hero.x + heroVelocityX * predictionFrames;
self.predictedHeroY = hero.y + heroVelocityY * predictionFrames;
};
// State machine update
self.updateState = function () {
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
self.stateTimer++;
switch (self.state) {
case 'IDLE':
if (distanceToHero < 600) {
self.setState('PURSUING');
}
break;
case 'PURSUING':
if (distanceToHero < 200) {
self.setState('ATTACKING');
} else if (distanceToHero > 800) {
self.setState('IDLE');
}
break;
case 'ATTACKING':
if (distanceToHero > 300) {
self.setState('PURSUING');
}
break;
case 'STUNNED':
if (self.stateTimer > 120) {
// 2 seconds
self.setState('ENRAGED');
}
break;
case 'ENRAGED':
if (self.health > self.maxHealth * 0.5) {
self.setState('PURSUING');
}
break;
}
};
self.setState = function (newState) {
self.state = newState;
self.stateTimer = 0;
self.onStateChange(newState);
};
// Override for BossType1 specific state changes
self.onStateChange = function (newState) {
var graphics = self.getChildAt(0);
if (newState === 'ENRAGED') {
graphics.tint = 0xff4444;
self.speed *= 2.0; // More aggressive speed boost
// Add particle effect for enrage
for (var i = 0; i < 8; i++) {
var particle = game.addChild(getFromPool('bloodParticle'));
particle.reset(self.x + (Math.random() - 0.5) * 100, self.y - 150 + (Math.random() - 0.5) * 100);
var particleGraphics = particle.getChildAt(0);
particleGraphics.tint = 0xff4444;
bloodParticles.push(particle);
}
} else if (newState === 'STUNNED') {
graphics.tint = 0x888888;
self.speed *= 0.3; // More pronounced slowdown
// Vulnerability window - takes extra damage when stunned
self.isInvulnerable = false;
} else if (newState === 'ATTACKING') {
graphics.tint = 0xffaa00; // Orange tint when attacking
} else {
graphics.tint = 0xffffff;
}
};
self.takeDamage = function (fromKnife) {
if (self.isInvulnerable) return;
self.health--;
LK.effects.flashObject(self, 0xffffff, 200);
// Create blood particles
for (var p = 0; p < 15; p++) {
var bloodParticle = game.addChild(getFromPool('bloodParticle'));
bloodParticle.reset(self.x + (Math.random() - 0.5) * 60, self.y - 100 + (Math.random() - 0.5) * 60);
bloodParticles.push(bloodParticle);
}
// State transitions based on damage
if (self.health <= self.maxHealth * 0.25 && self.state !== 'ENRAGED') {
self.setState('ENRAGED');
} else if (fromKnife && Math.random() < 0.3) {
self.setState('STUNNED');
}
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
LK.effects.flashScreen(0xffffff, 2000);
for (var i = 0; i < 10; i++) {
var coin = game.addChild(new Coin());
coin.x = self.x + (Math.random() - 0.5) * 200;
coin.y = self.y + (Math.random() - 0.5) * 200;
coins.push(coin);
}
LK.setScore(LK.getScore() + 1000);
hero.addCombo();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
updateScoreDisplay();
updateEnemiesLeftDisplay();
};
return self;
});
// BossType3: Environmental Manipulator
var BossType3 = BaseBoss.expand(function () {
var self = BaseBoss.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 1.0
});
bossGraphics.tint = 0x00ff88; // Green for environmental
self.bossType = 'environmental';
self.hazardCooldown = 0;
self.wallCooldown = 0;
self.earthquakeCooldown = 0;
self.activeHazards = [];
self.isChanneling = false;
self.update = function () {
self.updatePrediction();
self.updateState();
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
// Phase transitions
if (self.health > self.maxHealth * 0.66) {
self.phase = 1;
} else if (self.health > self.maxHealth * 0.33) {
self.phase = 2;
} else {
self.phase = 3;
}
// Environmental boss positioning based on state
if (self.state === 'PURSUING' || self.state === 'ATTACKING' || self.state === 'ENRAGED') {
// Move to strategic positions based on predicted hero movement
var strategicX = self.state === 'ENRAGED' ? currentLevelData.width / 2 + (self.predictedHeroX > currentLevelData.width / 2 ? -300 : 300) : currentLevelData.width / 2;
var strategicY = 2000;
var dx = strategicX - self.x;
var dy = strategicY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 50) {
self.lastX = self.x;
self.lastY = self.y;
var moveSpeed = self.state === 'ENRAGED' ? self.speed : self.speed * 0.5;
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
}
// Enhanced environmental attacks with state-based timing
if (self.state === 'ATTACKING' && self.hazardCooldown <= 0) {
self.showHazardWarning();
}
if (self.phase >= 2 && self.state === 'ATTACKING' && self.wallCooldown <= 0) {
self.showWallWarning();
}
if (self.phase === 3 && self.state === 'ENRAGED' && self.earthquakeCooldown <= 0) {
self.showEarthquakeWarning();
}
// Cooldown updates
if (self.hazardCooldown > 0) self.hazardCooldown--;
if (self.wallCooldown > 0) self.wallCooldown--;
if (self.earthquakeCooldown > 0) self.earthquakeCooldown--;
};
self.createHazard = function () {
self.isChanneling = true;
tween(bossGraphics, {
tint: 0xff4400
}, {
duration: 300
});
// Create fire hazard near predicted hero position
var hazard = game.addChild(LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
}));
hazard.x = self.predictedHeroX + (Math.random() - 0.5) * 100;
hazard.y = self.predictedHeroY + (Math.random() - 0.5) * 100;
// Warning phase
tween(hazard, {
alpha: 0.5,
scaleX: 2,
scaleY: 2
}, {
duration: 1000
});
// Damage phase
LK.setTimeout(function () {
tween(hazard, {
alpha: 1,
scaleX: 3,
scaleY: 3,
tint: 0xff0000
}, {
duration: 500,
onFinish: function onFinish() {
// Check if hero is in hazard area
var dx = hero.x - hazard.x;
var dy = hero.y - hazard.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
hero.takeDamage();
}
// Remove hazard
hazard.destroy();
for (var i = self.activeHazards.length - 1; i >= 0; i--) {
if (self.activeHazards[i] === hazard) {
self.activeHazards.splice(i, 1);
break;
}
}
}
});
}, 1000);
self.activeHazards.push(hazard);
tween(bossGraphics, {
tint: 0x00ff88
}, {
duration: 300
});
self.isChanneling = false;
self.hazardCooldown = self.phase === 3 ? 120 : 180;
};
self.createWalls = function () {
// Create temporary walls that block movement
for (var i = 0; i < 3; i++) {
var wall = game.addChild(LK.getAsset('backgroundTile', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
tint: 0x666666
}));
wall.x = 300 + i * 400;
wall.y = 1800 + Math.random() * 400;
tween(wall, {
alpha: 0.8,
scaleY: 3
}, {
duration: 500
});
// Remove walls after duration
LK.setTimeout(function () {
tween(wall, {
alpha: 0,
scaleY: 0.1
}, {
duration: 500,
onFinish: function onFinish() {
wall.destroy();
}
});
}, 5000);
}
self.wallCooldown = 420; // 7 seconds
};
self.showHazardWarning = function () {
// Create multiple warning areas using predictive targeting
var warningAreas = [{
x: self.predictedHeroX,
y: self.predictedHeroY
}, {
x: hero.x,
y: hero.y
},
// Current position as backup
{
x: self.predictedHeroX + (Math.random() - 0.5) * 200,
y: self.predictedHeroY + (Math.random() - 0.5) * 200
}];
for (var i = 0; i < warningAreas.length; i++) {
var warning = game.addChild(LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
tint: 0xff4400
}));
warning.x = warningAreas[i].x;
warning.y = warningAreas[i].y;
tween(warning, {
alpha: 0.6,
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 1500,
onFinish: function onFinish() {
warning.destroy();
}
});
}
// Boss visual warning
tween(bossGraphics, {
tint: 0xff4400,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 500,
onFinish: function onFinish() {
self.createHazard();
tween(bossGraphics, {
tint: 0x00ff88,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
}
});
};
self.showWallWarning = function () {
// Show where walls will appear
var wallPositions = [{
x: 300,
y: 1800
}, {
x: 700,
y: 2000
}, {
x: 1100,
y: 1900
}];
for (var i = 0; i < wallPositions.length; i++) {
var warning = game.addChild(LK.getAsset('backgroundTile', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
tint: 0xffaa00,
scaleY: 0.1
}));
warning.x = wallPositions[i].x;
warning.y = wallPositions[i].y;
tween(warning, {
alpha: 0.7,
scaleY: 2
}, {
duration: 1000,
onFinish: function onFinish() {
warning.destroy();
}
});
}
LK.setTimeout(function () {
self.createWalls();
}, 1000);
};
self.showEarthquakeWarning = function () {
// Screen warning for earthquake
LK.effects.flashScreen(0x8b4513, 1500);
// Boss charges up
tween(bossGraphics, {
tint: 0x8b4513,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1500,
onFinish: function onFinish() {
self.earthquake();
}
});
};
self.earthquake = function () {
self.isChanneling = true;
// Create vulnerability window - boss can't move during earthquake
self.speed = 0;
LK.effects.flashScreen(0x8b4513, 2000);
// Intense screen shake for earthquake
triggerScreenShake(25, 1500, tween.easeInOut);
// Screen shake effect simulation through rapid position changes
var originalX = bossGraphics.x;
var originalY = bossGraphics.y;
for (var i = 0; i < 20; i++) {
LK.setTimeout(function () {
bossGraphics.x = originalX + (Math.random() - 0.5) * 10;
bossGraphics.y = originalY + (Math.random() - 0.5) * 10;
}, i * 50);
}
LK.setTimeout(function () {
bossGraphics.x = originalX;
bossGraphics.y = originalY;
self.isChanneling = false;
// Restore speed and brief stun
self.speed = 3;
self.setState('STUNNED');
// Reset visual
tween(bossGraphics, {
tint: 0x00ff88,
scaleX: 1,
scaleY: 1
}, {
duration: 500
});
}, 1000);
// Damage hero if on ground
LK.setTimeout(function () {
if (hero.y > 2200) {
// Near ground level
hero.takeDamage();
}
}, 1000);
self.earthquakeCooldown = 600; // 10 seconds
};
return self;
});
// Legacy Boss class for backward compatibility (delegates to BossType1)
// BossType2: Summoner Boss
var BossType2 = BaseBoss.expand(function () {
var self = BaseBoss.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 1.0
});
bossGraphics.tint = 0x9b59b6; // Purple for summoner
self.bossType = 'summoner';
self.summonCooldown = 0;
self.minionCount = 0;
self.maxMinions = 4;
self.teleportCooldown = 0;
self.shieldActive = false;
self.shieldHealth = 0;
self.update = function () {
self.updatePrediction();
self.updateState();
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
// Phase transitions affect minion count
if (self.health > self.maxHealth * 0.66) {
self.phase = 1;
self.maxMinions = 2;
} else if (self.health > self.maxHealth * 0.33) {
self.phase = 2;
self.maxMinions = 3;
} else {
self.phase = 3;
self.maxMinions = 4;
}
// Enhanced movement based on state with predictive positioning
if (self.state === 'PURSUING' || self.state === 'ATTACKING' || self.state === 'ENRAGED') {
var optimalDistance = self.state === 'ENRAGED' ? 300 : 450;
if (distanceToHero < optimalDistance) {
// Move away from hero, but anticipate their movement
var escapeX = self.x - self.predictedHeroX;
var escapeY = self.y - self.predictedHeroY;
var distance = Math.sqrt(escapeX * escapeX + escapeY * escapeY);
if (distance > 0) {
self.lastX = self.x;
self.lastY = self.y;
var moveSpeed = self.state === 'ENRAGED' ? self.speed * 1.5 : self.speed;
self.x += escapeX / distance * moveSpeed;
self.y += escapeY / distance * moveSpeed;
bossGraphics.scaleX = escapeX > 0 ? -1 : 1; // Face away from hero
}
}
}
// Enhanced summoning with warnings
if (self.state === 'ATTACKING' && self.summonCooldown <= 0 && self.minionCount < self.maxMinions) {
self.showSummonWarning();
}
// Predictive teleport when threatened
if (self.phase >= 2 && self.teleportCooldown <= 0 && distanceToHero < 250) {
self.showTeleportWarning();
}
// Shield ability in phase 3
if (self.phase === 3 && !self.shieldActive && self.health <= self.maxHealth * 0.25) {
self.activateShield();
}
// Cooldown updates
if (self.summonCooldown > 0) self.summonCooldown--;
if (self.teleportCooldown > 0) self.teleportCooldown--;
};
self.summonMinion = function () {
// Create a specialized minion near the boss
var minion = game.addChild(new Minion());
minion.x = self.x + (Math.random() - 0.5) * 200;
minion.y = self.y + (Math.random() - 0.5) * 100;
minion.summoner = self; // Link minion to summoner
// Keep within bounds
minion.x = Math.max(100, Math.min(minion.x, currentLevelData.width - 100));
minion.y = Math.max(1600, Math.min(minion.y, 2400));
// Visual summoning effect
tween(bossGraphics, {
tint: 0xff00ff
}, {
duration: 200
});
tween(bossGraphics, {
tint: 0x9b59b6
}, {
duration: 200
});
LK.effects.flashObject(minion, 0x9b59b6, 500);
enemies.push(minion);
self.minionCount++;
self.summonCooldown = self.phase === 3 ? 180 : 240; // Faster summoning in phase 3
updateEnemiesLeftDisplay();
};
self.teleport = function () {
// Teleport to a safe distance from hero
var teleportDistance = 500;
var angle = Math.random() * Math.PI * 2;
var newX = hero.x + Math.cos(angle) * teleportDistance;
var newY = hero.y + Math.sin(angle) * teleportDistance;
// Keep within bounds
newX = Math.max(200, Math.min(newX, currentLevelData.width - 200));
newY = Math.max(1700, Math.min(newY, 2300));
// Visual teleport effect
tween(bossGraphics, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
onFinish: function onFinish() {
self.x = newX;
self.y = newY;
tween(bossGraphics, {
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 300
});
LK.effects.flashObject(self, 0x9b59b6, 500);
}
});
self.teleportCooldown = 300; // 5 seconds
};
self.activateShield = function () {
self.shieldActive = true;
self.shieldHealth = 10;
self.isInvulnerable = true;
// Visual shield effect
tween(bossGraphics, {
tint: 0x00ffff
}, {
duration: 200
});
LK.effects.flashObject(self, 0x00ffff, 1000);
// Shield lasts until destroyed or timeout
LK.setTimeout(function () {
self.deactivateShield();
}, 10000); // 10 seconds max
};
self.showSummonWarning = function () {
// Create warning circles at summon locations
var summonLocations = [{
x: self.x + 150,
y: self.y
}, {
x: self.x - 150,
y: self.y
}, {
x: self.x,
y: self.y + 100
}];
for (var i = 0; i < summonLocations.length; i++) {
var warning = game.addChild(LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
tint: 0x9b59b6
}));
warning.x = summonLocations[i].x;
warning.y = summonLocations[i].y;
tween(warning, {
alpha: 0.7,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
onFinish: function onFinish() {
warning.destroy();
}
});
}
// Delay actual summon
LK.setTimeout(function () {
if (self.minionCount < self.maxMinions) {
self.summonMinion();
}
}, 1000);
};
self.showTeleportWarning = function () {
// Flash before teleporting
tween(bossGraphics, {
alpha: 0.3,
tint: 0xff00ff
}, {
duration: 300,
onFinish: function onFinish() {
self.teleport();
}
});
};
self.deactivateShield = function () {
self.shieldActive = false;
self.isInvulnerable = false;
// Vulnerability window after shield breaks
self.setState('STUNNED');
tween(bossGraphics, {
tint: 0x9b59b6
}, {
duration: 500
});
};
// Override takeDamage to handle shield
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (fromKnife) {
if (self.shieldActive) {
self.shieldHealth--;
LK.effects.flashObject(self, 0x00ffff, 100);
if (self.shieldHealth <= 0) {
self.deactivateShield();
}
return;
}
// Reduce minion count when boss takes damage
if (self.minionCount > 0) {
self.minionCount--;
}
originalTakeDamage.call(self, fromKnife);
};
return self;
});
// BossType1: Melee/Projectile Boss (original boss enhanced)
var BossType1 = BaseBoss.expand(function () {
var self = BaseBoss.call(this);
var bossGraphics = self.attachAsset('boss', {
anchorX: 0.5,
anchorY: 1.0
});
self.bossType = 'melee';
self.projectileAttackCooldown = 0;
self.chargeAttackCooldown = 0;
self.isCharging = false;
self.chargingTarget = null;
self.spinAttackCooldown = 0;
self.isSpinning = false;
self.update = function () {
self.updatePrediction();
self.updateState();
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
// Phase transitions
if (self.health > self.maxHealth * 0.66) {
self.phase = 1;
} else if (self.health > self.maxHealth * 0.33) {
self.phase = 2;
} else {
self.phase = 3;
}
// Movement based on state
if (self.state === 'PURSUING' || self.state === 'ENRAGED') {
var targetX = self.state === 'ENRAGED' ? self.predictedHeroX : hero.x;
var targetY = self.state === 'ENRAGED' ? self.predictedHeroY : hero.y;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.lastX = self.x;
self.lastY = self.y;
var moveSpeed = self.speed;
// Enraged state moves faster toward predicted position
if (self.state === 'ENRAGED') {
moveSpeed *= 1.8;
}
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
bossGraphics.scaleX = dx > 0 ? 1 : -1;
}
}
// Attacks based on state with visual warnings
if (self.state === 'ATTACKING' && self.attackCooldown <= 0) {
if (distanceToHero < 150) {
self.showMeleeWarning();
} else if (self.phase >= 2 && self.projectileAttackCooldown <= 0) {
self.showProjectileWarning();
}
}
// Special attacks with warnings
if (self.phase >= 2 && self.chargeAttackCooldown <= 0 && distanceToHero > 300 && !self.isCharging) {
self.showChargeWarning();
}
if (self.phase === 3 && self.spinAttackCooldown <= 0 && distanceToHero < 250 && !self.isSpinning) {
self.showSpinWarning();
}
// Cooldown updates
if (self.attackCooldown > 0) self.attackCooldown--;
if (self.projectileAttackCooldown > 0) self.projectileAttackCooldown--;
if (self.chargeAttackCooldown > 0) self.chargeAttackCooldown--;
if (self.spinAttackCooldown > 0) self.spinAttackCooldown--;
};
self.meleeAttack = function () {
hero.takeDamage();
self.attackCooldown = 90;
LK.effects.flashObject(self, 0xffffff, 200);
// Screen shake on boss melee attack
triggerScreenShake(15, 300, tween.easeOut);
};
self.fireProjectile = function () {
tween(bossGraphics, {
tint: 0x8800ff
}, {
duration: 200
});
tween(bossGraphics, {
tint: 0xffffff
}, {
duration: 200
});
// Screen shake on boss projectile attack
triggerScreenShake(8, 200, tween.easeOut);
var projectile = game.addChild(new BossProjectile());
projectile.x = self.x;
projectile.y = self.y - 200;
var dx = self.predictedHeroX - self.x;
var dy = self.predictedHeroY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var speed = self.phase === 3 ? 12 : 8;
projectile.velocityX = dx / distance * speed;
projectile.velocityY = dy / distance * speed;
}
bossProjectiles.push(projectile);
self.projectileAttackCooldown = self.phase === 3 ? 60 : 120;
};
self.chargeAttack = function () {
self.isCharging = true;
self.chargingTarget = {
x: self.predictedHeroX,
y: self.predictedHeroY
};
tween(bossGraphics, {
tint: 0xff4444,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 800,
easing: tween.easeIn
});
LK.setTimeout(function () {
if (!self.chargingTarget) return;
LK.effects.flashObject(self, 0xffffff, 300);
var dx = self.chargingTarget.x - self.x;
var dy = self.chargingTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var chargeDistance = 400;
var targetX = self.x + dx / distance * chargeDistance;
var targetY = self.y + dy / distance * chargeDistance;
targetX = Math.max(100, Math.min(targetX, currentLevelData.width - 100));
targetY = Math.max(1600, Math.min(targetY, 2400));
tween(self, {
x: targetX,
y: targetY
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(bossGraphics, {
tint: 0xffffff,
scaleX: 1,
scaleY: 1
}, {
duration: 500
});
self.isCharging = false;
self.chargingTarget = null;
}
});
LK.setTimeout(function () {
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
if (distanceToHero < 150) {
hero.takeDamage();
// Screen shake on successful charge attack hit
triggerScreenShake(20, 400, tween.easeOut);
}
}, 150);
}
}, 800);
self.chargeAttackCooldown = self.phase === 3 ? 240 : 360;
};
self.showMeleeWarning = function () {
// Flash red warning for 0.5 seconds before attacking
tween(bossGraphics, {
tint: 0xff0000,
scaleX: bossGraphics.scaleX * 1.3,
scaleY: 1.3
}, {
duration: 500,
onFinish: function onFinish() {
self.meleeAttack();
tween(bossGraphics, {
tint: 0xffffff,
scaleX: bossGraphics.scaleX > 0 ? 1 : -1,
scaleY: 1
}, {
duration: 200
});
}
});
};
self.showProjectileWarning = function () {
// Create warning indicator at predicted position
var warningIndicator = game.addChild(LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
tint: 0xff4444
}));
warningIndicator.x = self.predictedHeroX;
warningIndicator.y = self.predictedHeroY;
tween(warningIndicator, {
alpha: 0.8,
scaleX: 2,
scaleY: 2
}, {
duration: 800,
onFinish: function onFinish() {
self.fireProjectile();
warningIndicator.destroy();
}
});
};
self.showChargeWarning = function () {
// Show charge direction warning
var warningLine = game.addChild(LK.getAsset('slashEffect', {
anchorX: 0,
anchorY: 0.5,
alpha: 0,
tint: 0xff4444
}));
var dx = self.predictedHeroX - self.x;
var dy = self.predictedHeroY - self.y;
var angle = Math.atan2(dy, dx);
warningLine.x = self.x;
warningLine.y = self.y - 100;
warningLine.rotation = angle;
warningLine.scaleX = 8;
warningLine.scaleY = 2;
tween(warningLine, {
alpha: 0.7
}, {
duration: 600,
onFinish: function onFinish() {
self.chargeAttack();
warningLine.destroy();
}
});
};
self.showSpinWarning = function () {
// Create expanding red circle warning
var warningCircle = game.addChild(LK.getAsset('hitEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
tint: 0xff0000
}));
warningCircle.x = self.x;
warningCircle.y = self.y - 100;
tween(warningCircle, {
alpha: 0.6,
scaleX: 4,
scaleY: 4
}, {
duration: 1000,
onFinish: function onFinish() {
self.spinAttack();
warningCircle.destroy();
}
});
};
self.spinAttack = function () {
self.isSpinning = true;
// Create vulnerability window - boss takes double damage during spin
self.isInvulnerable = false;
var originalTakeDamage = self.takeDamage;
self.takeDamage = function (fromKnife) {
// Double damage during spin attack
originalTakeDamage.call(self, fromKnife);
if (self.health > 0) {
originalTakeDamage.call(self, fromKnife);
}
};
tween(bossGraphics, {
rotation: Math.PI * 4
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
bossGraphics.rotation = 0;
self.isSpinning = false;
// Restore normal damage after spin
self.takeDamage = originalTakeDamage;
// Brief stunned state after spin
self.setState('STUNNED');
}
});
tween(bossGraphics, {
scaleX: 1.5,
scaleY: 1.5,
tint: 0xff8800
}, {
duration: 750,
easing: tween.easeOut
});
tween(bossGraphics, {
scaleX: 1,
scaleY: 1,
tint: 0xffffff
}, {
duration: 750,
easing: tween.easeIn
});
for (var i = 0; i < 5; i++) {
LK.setTimeout(function () {
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
if (distanceToHero < 200) {
hero.takeDamage();
LK.effects.flashObject(hero, 0xff0000, 200);
// Screen shake on spin attack hit
triggerScreenShake(12, 250, tween.easeOut);
}
}, i * 300);
}
self.spinAttackCooldown = 420;
};
return self;
});
var BloodParticle = Container.expand(function () {
var self = Container.call(this);
var particleGraphics = self.attachAsset('bloodParticle', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = (Math.random() - 0.5) * 8;
self.velocityY = -Math.random() * 6 - 2;
self.gravity = 0.3;
self.lifetime = 60; // 1 second at 60fps
self.reset = function (startX, startY) {
self.x = startX;
self.y = startY;
self.velocityX = (Math.random() - 0.5) * 8;
self.velocityY = -Math.random() * 6 - 2;
self.lifetime = 60;
particleGraphics.alpha = 1;
self.visible = true;
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.velocityY += self.gravity;
// Fade out over time
self.lifetime--;
particleGraphics.alpha = self.lifetime / 60;
if (self.lifetime <= 0) {
// Remove from bloodParticles array
for (var i = bloodParticles.length - 1; i >= 0; i--) {
if (bloodParticles[i] === self) {
bloodParticles.splice(i, 1);
break;
}
}
returnToPool(self, 'bloodParticle');
}
};
return self;
});
var BossProjectile = Container.expand(function () {
var self = Container.call(this);
var projectileGraphics = self.attachAsset('bossProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.lifetime = 300; // 5 seconds
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.lifetime--;
// Set rotation to face movement direction
if (self.velocityX !== 0 || self.velocityY !== 0) {
var angle = Math.atan2(self.velocityY, self.velocityX);
projectileGraphics.rotation = angle;
}
// Check collision with hero
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
if (distanceToHero < 80) {
hero.takeDamage();
self.destroy();
// Remove from bossProjectiles array
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
if (bossProjectiles[i] === self) {
bossProjectiles.splice(i, 1);
break;
}
}
return;
}
// Remove if lifetime expired or off screen
if (self.lifetime <= 0 || self.x < -100 || self.x > currentLevelData.width + 100 || self.y < -100 || self.y > 3000) {
self.destroy();
// Remove from bossProjectiles array
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
if (bossProjectiles[i] === self) {
bossProjectiles.splice(i, 1);
break;
}
}
}
};
return self;
});
var Coin = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('coin', {
anchorX: 0.5,
anchorY: 0.5
});
self.collectTimer = 0;
self.update = function () {
// Auto-collect after short delay
self.collectTimer++;
if (self.collectTimer > 30) {
// 0.5 seconds
self.collect();
}
// Spin animation
coinGraphics.rotation += 0.1;
};
self.collect = function () {
LK.getSound('coin').play();
// Remove from coins array
for (var i = coins.length - 1; i >= 0; i--) {
if (coins[i] === self) {
coins.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Enemy = Container.expand(function (enemyType) {
var self = Container.call(this);
self.enemyType = enemyType || 'basic';
// Choose asset based on enemy type
var assetName = 'enemy';
if (self.enemyType === 'basic') {
assetName = 'enemyBasic';
} else if (self.enemyType === 'strong') {
assetName = 'enemyStrong';
} else if (self.enemyType === 'fast') {
assetName = 'enemyFast';
} else if (self.enemyType === 'tank') {
assetName = 'enemyTank';
} else if (self.enemyType === 'hunter') {
assetName = 'enemyHunter';
} else if (self.enemyType === 'assassin') {
assetName = 'enemyAssassin';
}
var enemyGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 1.0
});
self.health = 2;
self.speed = 1;
self.attackCooldown = 0;
self.fromLeft = true;
self.lastX = 0;
self.lastY = 0;
self.alerted = false;
self.alertedByKnife = false;
self.update = function () {
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
// Different sight ranges for different enemy types
var sightRange = 500;
if (self.enemyType === 'hunter') {
sightRange = 800; // Hunters have better sight
} else if (self.enemyType === 'assassin') {
sightRange = 600; // Assassins have good sight
} else if (self.enemyType === 'tank') {
sightRange = 400; // Tanks have poor sight
}
var canSeeHero = distanceToHero < sightRange;
if (canSeeHero || self.alerted) {
// Chase hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var moveSpeed = self.speed;
// Special movement for assassin type - teleport ability
if (self.enemyType === 'assassin' && distanceToHero > 300 && distanceToHero < 600 && Math.random() < 0.02) {
// Teleport closer to hero
var teleportDistance = 150;
var teleportX = hero.x + (Math.random() - 0.5) * teleportDistance;
var teleportY = hero.y + (Math.random() - 0.5) * teleportDistance;
// Keep within bounds
teleportX = Math.max(100, Math.min(teleportX, currentLevelData.width - 100));
teleportY = Math.max(1600, Math.min(teleportY, 2400));
self.x = teleportX;
self.y = teleportY;
// Flash effect for teleport
LK.effects.flashObject(self, 0x9b59b6, 300);
var newX = self.x;
var newY = self.y;
} else {
// Hunter type - faster when far from hero
if (self.enemyType === 'hunter' && distanceToHero > 400) {
moveSpeed *= 1.5; // 50% speed boost when hunting from distance
}
var newX = self.x + dx / distance * moveSpeed;
var newY = self.y + dy / distance * moveSpeed;
}
// Check collision with other enemies before moving using spatial partitioning
var wouldCollide = false;
var nearbyEnemies = spatialGrid.getNearbyObjects(newX, newY, 100);
for (var i = 0; i < nearbyEnemies.length; i++) {
var otherEnemy = nearbyEnemies[i];
if (otherEnemy === self) continue; // Skip self
var edx = newX - otherEnemy.x;
var edy = newY - otherEnemy.y;
var edistance = Math.sqrt(edx * edx + edy * edy);
if (edistance < 70) {
// Collision threshold between enemies
wouldCollide = true;
break;
}
}
// Only move if no collision would occur
if (!wouldCollide) {
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
}
// Face direction of movement
enemyGraphics.scaleX = dx > 0 ? 1 : -1;
}
// Attack hero if close enough
if (distanceToHero < 100 && self.attackCooldown <= 0) {
hero.takeDamage();
self.attackCooldown = 120; // 2 seconds at 60fps
}
} else if (!self.alerted) {
// Only roam if not alerted - alerted enemies keep chasing even when they can't see hero
if (!self.roamDirection || Math.random() < 0.01) {
self.roamDirection = {
x: (Math.random() - 0.5) * 2,
y: (Math.random() - 0.5) * 2
};
}
var newX = self.x + self.roamDirection.x * self.speed * 0.5;
var newY = self.y + self.roamDirection.y * self.speed * 0.5;
// Check collision with other enemies before roaming
var wouldCollide = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy === self) continue; // Skip self
var edx = newX - otherEnemy.x;
var edy = newY - otherEnemy.y;
var edistance = Math.sqrt(edx * edx + edy * edy);
if (edistance < 70) {
// Collision threshold between enemies
wouldCollide = true;
break;
}
}
// Keep within level bounds and check collisions
if (!wouldCollide && newX > 100 && newX < currentLevelData.width - 100) {
self.lastX = self.x;
self.x = newX;
enemyGraphics.scaleX = self.roamDirection.x > 0 ? 1 : -1;
}
if (!wouldCollide && newY > 1600 && newY < 2400) {
self.lastY = self.y;
self.y = newY;
}
} else {
// Alerted enemies that can't see hero still try to chase in last known direction
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
// Check collision with other enemies before moving
var wouldCollide = false;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy === self) continue; // Skip self
var edx = newX - otherEnemy.x;
var edy = newY - otherEnemy.y;
var edistance = Math.sqrt(edx * edx + edy * edy);
if (edistance < 70) {
// Collision threshold between enemies
wouldCollide = true;
break;
}
}
// Only move if no collision would occur
if (!wouldCollide) {
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
}
// Face direction of movement
enemyGraphics.scaleX = dx > 0 ? 1 : -1;
}
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
};
self.takeDamage = function (fromKnife) {
self.health--;
LK.effects.flashObject(self, 0xffffff, 200);
// Create knockback effect
var knockbackForce = 40;
var knockbackDirection = fromKnife ? 1 : hero.x < self.x ? 1 : -1;
var targetX = self.x + knockbackDirection * knockbackForce;
var targetY = self.y - 20; // Slight upward knockback
// Apply knockback with bounds checking
targetX = Math.max(100, Math.min(targetX, currentLevelData.width - 100));
targetY = Math.max(1600, Math.min(targetY, 2400));
tween(self, {
x: targetX,
y: targetY
}, {
duration: 200,
easing: tween.easeOut
});
// Create blood particles
for (var p = 0; p < 12; p++) {
var bloodParticle = game.addChild(getFromPool('bloodParticle'));
bloodParticle.reset(self.x + (Math.random() - 0.5) * 40, self.y - 60 + (Math.random() - 0.5) * 40);
bloodParticles.push(bloodParticle);
}
if (fromKnife) {
self.alerted = true;
self.alertedByKnife = true;
}
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Screen shake on enemy death (smaller intensity)
triggerScreenShake(6, 150, tween.easeOut);
// Drop coin
var coin = game.addChild(new Coin());
coin.x = self.x;
coin.y = self.y;
coins.push(coin);
// 10% chance to drop health potion
if (Math.random() < 0.1) {
var healthPotion = game.addChild(new HealthPotion());
healthPotion.x = self.x + (Math.random() - 0.5) * 80; // Slight random offset
healthPotion.y = self.y + (Math.random() - 0.5) * 80;
healthPotions.push(healthPotion);
}
// Add score and combo
var baseScore = 10;
var comboMultiplier = Math.floor(hero.comboCount / 5) + 1;
var finalScore = baseScore * comboMultiplier;
LK.setScore(LK.getScore() + finalScore);
hero.addCombo();
// Remove from enemies array
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
updateScoreDisplay();
updateEnemiesLeftDisplay();
};
return self;
});
var EnemyWarning = Container.expand(function () {
var self = Container.call(this);
self.targetEnemy = null;
self.direction = 'left'; // 'left', 'right', 'up', 'down'
self.warningGraphics = null;
self.lastAlpha = 0;
self.isVisible = false;
self.setDirection = function (direction) {
if (self.warningGraphics) {
self.warningGraphics.destroy();
}
var assetName = 'warningArrow' + direction.charAt(0).toUpperCase() + direction.slice(1);
self.warningGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
});
self.direction = direction;
};
self.update = function () {
if (!self.targetEnemy || !self.warningGraphics) return;
// Check if enemy is still alive
var enemyExists = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] === self.targetEnemy) {
enemyExists = true;
break;
}
}
if (!enemyExists) {
self.hide();
return;
}
// Calculate distance from hero to enemy
var distanceToHero = Math.sqrt(Math.pow(self.targetEnemy.x - hero.x, 2) + Math.pow(self.targetEnemy.y - hero.y, 2));
// Check if enemy is visible on screen
var enemyScreenX = self.targetEnemy.x - camera.x;
var enemyScreenY = self.targetEnemy.y - camera.y;
var isOnScreen = enemyScreenX >= -100 && enemyScreenX <= 2148 && enemyScreenY >= -100 && enemyScreenY <= 2832;
if (isOnScreen) {
self.hide();
return;
}
// Calculate warning opacity based on distance (closer = more visible)
var maxDistance = 800;
var minDistance = 300;
var targetAlpha = 0;
if (distanceToHero <= maxDistance) {
var normalizedDistance = Math.max(0, Math.min(1, (maxDistance - distanceToHero) / (maxDistance - minDistance)));
targetAlpha = normalizedDistance * 0.8;
}
// Smooth alpha transition
if (Math.abs(targetAlpha - self.lastAlpha) > 0.01) {
tween.stop(self.warningGraphics, {
alpha: true
});
tween(self.warningGraphics, {
alpha: targetAlpha
}, {
duration: 200
});
self.lastAlpha = targetAlpha;
}
// Position warning at screen edge
var screenCenterX = 1024;
var screenCenterY = 1366;
var dx = self.targetEnemy.x - hero.x;
var dy = self.targetEnemy.y - hero.y;
if (Math.abs(dx) > Math.abs(dy)) {
// Horizontal warning
if (dx > 0) {
self.setDirection('right');
self.x = screenCenterX + 900;
self.y = screenCenterY + Math.max(-600, Math.min(600, dy * 0.5));
} else {
self.setDirection('left');
self.x = screenCenterX - 900;
self.y = screenCenterY + Math.max(-600, Math.min(600, dy * 0.5));
}
} else {
// Vertical warning
if (dy > 0) {
self.setDirection('down');
self.x = screenCenterX + Math.max(-800, Math.min(800, dx * 0.5));
self.y = screenCenterY + 1200;
} else {
self.setDirection('up');
self.x = screenCenterX + Math.max(-800, Math.min(800, dx * 0.5));
self.y = screenCenterY - 1200;
}
}
};
self.hide = function () {
if (self.warningGraphics && self.warningGraphics.alpha > 0) {
tween.stop(self.warningGraphics, {
alpha: true
});
tween(self.warningGraphics, {
alpha: 0
}, {
duration: 300
});
self.lastAlpha = 0;
}
};
return self;
});
var HealthPotion = Container.expand(function () {
var self = Container.call(this);
var potionGraphics = self.attachAsset('healthPotion', {
anchorX: 0.5,
anchorY: 0.5
});
self.collectTimer = 0;
self.update = function () {
// Auto-collect after short delay
self.collectTimer++;
if (self.collectTimer > 30) {
// 0.5 seconds
self.collect();
}
// Gentle floating animation
potionGraphics.y = Math.sin(LK.ticks * 0.1) * 5;
potionGraphics.rotation += 0.05;
};
self.collect = function () {
LK.getSound('powerup').play();
hero.heal();
// Remove from healthPotions array
for (var i = healthPotions.length - 1; i >= 0; i--) {
if (healthPotions[i] === self) {
healthPotions.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var Hero = Container.expand(function () {
var self = Container.call(this);
var heroGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1.0
});
self.maxHealth = 5;
self.health = self.maxHealth;
self.isAttacking = false;
self.invulnerable = false;
self.damageBoost = false;
self.comboCount = 0;
self.slashCooldown = 0;
self.isSlashing = false;
self.slashGraphics = null;
self.lastX = 0;
self.lastY = 0;
self.isWalking = false;
self.walkAnimationActive = false;
self.walkAnimationFrame = 0;
self.walkAnimationTimer = 0;
self.walkAnimationSpeed = 10; // frames between texture changes
self.attack = function (targetX) {
if (self.isAttacking || self.slashCooldown > 0) return;
self.isAttacking = true;
self.isSlashing = true;
self.slashCooldown = 24; // 0.4 seconds at 60fps
// Face direction of attack
if (targetX < self.x) {
heroGraphics.scaleX = -1;
} else {
heroGraphics.scaleX = 1;
}
// Replace hero graphic with slash animation
self.removeChild(heroGraphics);
self.slashGraphics = self.attachAsset('heroSlash', {
anchorX: 0.5,
anchorY: 1.0
});
// Match facing direction
self.slashGraphics.scaleX = heroGraphics.scaleX;
// Attack animation
tween(self.slashGraphics, {
scaleY: 1.2
}, {
duration: 100
});
tween(self.slashGraphics, {
scaleY: 1.0
}, {
duration: 100,
onFinish: function onFinish() {
// Return to normal hero graphic
self.removeChild(self.slashGraphics);
heroGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1.0
});
// Maintain facing direction
heroGraphics.scaleX = self.slashGraphics.scaleX;
self.isAttacking = false;
self.isSlashing = false;
self.slashGraphics = null;
}
});
// Create sword slash effect
var effect = game.addChild(LK.getAsset('slashEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
}));
effect.x = self.x + heroGraphics.scaleX * 120;
effect.y = self.y - 80;
// Set slash rotation and scale based on direction
effect.rotation = heroGraphics.scaleX > 0 ? -0.3 : 0.3; // Diagonal slash
effect.scaleX = heroGraphics.scaleX > 0 ? 1 : -1; // Mirror for left attacks
tween(effect, {
scaleX: effect.scaleX * 4,
scaleY: 4,
alpha: 0
}, {
duration: 150,
onFinish: function onFinish() {
effect.destroy();
}
});
LK.getSound('slash').play();
};
self.takeDamage = function () {
if (self.invulnerable) return;
self.health--;
self.comboCount = 0;
// Flash red when hit
LK.effects.flashObject(self, 0xff0000, 500);
// Screen shake when player is hit
triggerScreenShake(18, 400, tween.easeOut);
// Temporary invulnerability
self.invulnerable = true;
LK.setTimeout(function () {
self.invulnerable = false;
}, 1000);
LK.getSound('hit').play();
updateHealthDisplay();
if (self.health <= 0) {
LK.showGameOver();
}
};
self.heal = function () {
if (self.health < self.maxHealth) {
self.health++;
updateHealthDisplay();
}
};
self.addCombo = function () {
self.comboCount++;
};
self.startWalkAnimation = function () {
if (self.walkAnimationActive) return;
self.walkAnimationActive = true;
self.walkAnimationFrame = 0;
self.walkAnimationTimer = 0;
};
self.stopWalkAnimation = function () {
self.isWalking = false;
self.walkAnimationActive = false;
// Store current scale direction before removing graphics
var currentScaleX = heroGraphics.scaleX;
// Reset to default hero texture
self.removeChild(heroGraphics);
heroGraphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1.0
});
// Maintain the current scale direction
if (currentScaleX < 0) {
heroGraphics.scaleX = -1;
}
};
self.move = function (direction) {
var speed = 8;
var newX = self.x;
var newY = self.y;
var didMove = false;
if (direction === 'left' && self.x > 100) {
newX = self.x - speed;
heroGraphics.scaleX = -1;
didMove = true;
} else if (direction === 'right' && self.x < currentLevelData.width - 100) {
newX = self.x + speed;
heroGraphics.scaleX = 1;
didMove = true;
} else if (direction === 'up' && self.y > 1600) {
newY = self.y - speed;
didMove = true;
} else if (direction === 'down' && self.y < 2400) {
newY = self.y + speed;
didMove = true;
}
// Check collision with enemies before moving using spatial partitioning
var wouldCollide = false;
var nearbyEnemies = spatialGrid.getNearbyObjects(newX, newY, 150);
for (var i = 0; i < nearbyEnemies.length; i++) {
var enemy = nearbyEnemies[i];
var dx = newX - enemy.x;
var dy = newY - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Increased collision threshold to prevent overlap
wouldCollide = true;
break;
}
}
// Only move if no collision would occur
if (!wouldCollide && didMove) {
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
// Start walking animation
if (!self.isWalking) {
self.isWalking = true;
self.startWalkAnimation();
}
// Update walking animation texture cycling
if (self.walkAnimationActive) {
self.walkAnimationTimer++;
if (self.walkAnimationTimer >= self.walkAnimationSpeed) {
self.walkAnimationTimer = 0;
self.walkAnimationFrame = (self.walkAnimationFrame + 1) % 4;
// Cycle through textures: hero, heroWalk1, heroWalk2, heroWalk3
var textureNames = ['hero', 'heroWalk1', 'heroWalk2', 'heroWalk3'];
// Store current scale direction before removing graphics
var currentScaleX = heroGraphics.scaleX;
// Remove current graphics and add new one with correct texture
self.removeChild(heroGraphics);
heroGraphics = self.attachAsset(textureNames[self.walkAnimationFrame], {
anchorX: 0.5,
anchorY: 1.0
});
// Maintain the current scale direction
if (currentScaleX < 0) {
heroGraphics.scaleX = -1;
}
}
}
}
// Update camera target
camera.targetX = self.x - 1024;
camera.targetY = self.y - 1366;
// Clamp camera to level bounds
camera.targetX = Math.max(0, Math.min(camera.targetX, currentLevelData.width - 2048));
camera.targetY = Math.max(0, Math.min(camera.targetY, currentLevelData.height - 2732));
};
self.update = function () {
if (self.slashCooldown > 0) {
self.slashCooldown--;
}
};
return self;
});
var Knife = Container.expand(function () {
var self = Container.call(this);
var knifeGraphics = self.attachAsset('knife', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 15;
self.direction = 1; // 1 for right, -1 for left
self.routePoints = []; // Points to follow along the route
self.currentRouteIndex = 0; // Current target point index
self.velocityX = 0; // Velocity for precise targeting
self.velocityY = 0; // Velocity for precise targeting
self.lastX = 0;
self.lastY = 0;
self.setRoute = function (routePoints) {
self.routePoints = routePoints;
self.currentRouteIndex = 0;
};
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
// Follow route if available
if (self.routePoints.length > 0 && self.currentRouteIndex < self.routePoints.length) {
var targetPoint = self.routePoints[self.currentRouteIndex];
var dx = targetPoint.x - self.x;
var dy = targetPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
// Close enough to current target, move to next point
self.currentRouteIndex++;
} else {
// Move toward current target point
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
// Calculate rotation angle to face target direction
var angle = Math.atan2(dy, dx);
knifeGraphics.rotation = angle;
}
}
} else if (self.velocityX !== 0 || self.velocityY !== 0) {
// Use velocity if set, otherwise use direction-based movement
self.x += self.velocityX;
self.y += self.velocityY;
// Calculate rotation for velocity-based movement
var angle = Math.atan2(self.velocityY, self.velocityX);
knifeGraphics.rotation = angle;
} else {
self.x += self.speed * self.direction;
self.y -= 2; // Slight upward arc
// Calculate rotation for direction-based movement
var angle = Math.atan2(-2, self.speed * self.direction);
knifeGraphics.rotation = angle;
}
// Check if knife went off screen
if (self.x < -100 || self.x > currentLevelData.width + 100 || self.y < -100 || self.y > 2900) {
self.destroy();
// Remove from knives array
for (var i = knives.length - 1; i >= 0; i--) {
if (knives[i] === self) {
knives.splice(i, 1);
break;
}
}
}
// Check collision with enemies (hit enemy center)
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = self.x - enemy.x;
var dy = self.y - (enemy.y - 70); // Target enemy center
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50) {
// Hit enemy center - deal damage only to first enemy encountered
enemy.takeDamage(true);
// Light screen shake on knife impact
triggerScreenShake(4, 100, tween.easeOut);
self.destroy();
// Remove from knives array
for (var j = knives.length - 1; j >= 0; j--) {
if (knives[j] === self) {
knives.splice(j, 1);
break;
}
}
return; // Exit update immediately to prevent further movement or collision checks
}
}
};
return self;
});
var Minion = Container.expand(function () {
var self = Container.call(this);
var minionGraphics = self.attachAsset('enemyFast', {
anchorX: 0.5,
anchorY: 1.0
});
minionGraphics.tint = 0x9b59b6; // Purple tint for minions
minionGraphics.scaleX = 0.8; // Smaller than normal enemies
minionGraphics.scaleY = 0.8;
self.health = 1;
self.speed = 3.5;
self.attackCooldown = 0;
self.lastX = 0;
self.lastY = 0;
self.summoner = null; // Reference to summoning boss
self.lifetime = 1800; // 30 seconds lifetime
self.update = function () {
self.lifetime--;
if (self.lifetime <= 0) {
self.die();
return;
}
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
// Aggressive AI - always chase hero
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Check collision with other enemies before moving
var wouldCollide = false;
var newX = self.x + dx / distance * self.speed;
var newY = self.y + dy / distance * self.speed;
var nearbyEnemies = spatialGrid.getNearbyObjects(newX, newY, 100);
for (var i = 0; i < nearbyEnemies.length; i++) {
var otherEnemy = nearbyEnemies[i];
if (otherEnemy === self) continue;
var edx = newX - otherEnemy.x;
var edy = newY - otherEnemy.y;
var edistance = Math.sqrt(edx * edx + edy * edy);
if (edistance < 60) {
// Smaller collision radius for minions
wouldCollide = true;
break;
}
}
if (!wouldCollide) {
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
}
minionGraphics.scaleX = dx > 0 ? 0.8 : -0.8;
}
// Attack hero if close enough
if (distanceToHero < 80 && self.attackCooldown <= 0) {
hero.takeDamage();
self.attackCooldown = 90; // 1.5 seconds
}
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
};
self.takeDamage = function (fromKnife) {
self.health--;
LK.effects.flashObject(self, 0xffffff, 200);
// Create smaller blood particles
for (var p = 0; p < 8; p++) {
var bloodParticle = game.addChild(getFromPool('bloodParticle'));
bloodParticle.reset(self.x + (Math.random() - 0.5) * 30, self.y - 40 + (Math.random() - 0.5) * 30);
bloodParticles.push(bloodParticle);
}
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Drop small coin
var coin = game.addChild(new Coin());
coin.x = self.x;
coin.y = self.y;
var coinGraphics = coin.getChildAt(0);
coinGraphics.scaleX = 0.7; // Smaller coin
coinGraphics.scaleY = 0.7;
coins.push(coin);
// Reduce summoner's minion count
if (self.summoner && self.summoner.minionCount) {
self.summoner.minionCount--;
}
// Small score bonus
LK.setScore(LK.getScore() + 5);
hero.addCombo();
// Remove from enemies array
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
updateScoreDisplay();
updateEnemiesLeftDisplay();
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
var powerupGraphics = self.attachAsset('powerup', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = 'health'; // 'health', 'invulnerable', 'damage'
self.lifetime = 600; // 10 seconds
self.update = function () {
self.lifetime--;
if (self.lifetime <= 0) {
self.expire();
}
// Check collision with hero
if (self.intersects(hero)) {
self.collect();
}
// Pulse animation
var scale = 1 + Math.sin(LK.ticks * 0.2) * 0.2;
powerupGraphics.scaleX = scale;
powerupGraphics.scaleY = scale;
};
self.collect = function () {
LK.getSound('powerup').play();
if (self.type === 'health') {
hero.heal();
} else if (self.type === 'invulnerable') {
hero.invulnerable = true;
LK.setTimeout(function () {
hero.invulnerable = false;
}, 5000);
} else if (self.type === 'damage') {
hero.damageBoost = true;
LK.setTimeout(function () {
hero.damageBoost = false;
}, 5000);
}
// Remove from powerups array
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] === self) {
powerups.splice(i, 1);
break;
}
}
self.destroy();
};
self.expire = function () {
// Remove from powerups array
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] === self) {
powerups.splice(i, 1);
break;
}
}
self.destroy();
};
return self;
});
var RouteEffect = Container.expand(function () {
var self = Container.call(this);
self.routePoints = [];
self.routePositions = []; // Store just the x,y coordinates for knife to follow
self.targetEnemy = null;
self.createRoute = function (enemy) {
self.targetEnemy = enemy;
self.clearRoute();
// Calculate direct path from hero center to enemy center
var heroStartX = hero.x;
var heroStartY = hero.y - 80; // Middle of hero asset
var enemyMiddleX = enemy.x;
var enemyMiddleY = enemy.y - 70; // Middle of enemy asset (enemy height is 140, so middle is -70 from bottom)
var dx = enemyMiddleX - heroStartX;
var dy = enemyMiddleY - heroStartY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
// Create route points along the path
var numPoints = Math.floor(distance / 50); // Point every 50 pixels
for (var i = 0; i <= numPoints; i++) {
var t = i / numPoints;
var x = heroStartX + dx * t;
var y = heroStartY + dy * t;
var routePoint = self.addChild(LK.getAsset('routeLine', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0
}));
routePoint.x = x;
routePoint.y = y;
self.routePoints.push(routePoint);
// Store position for knife to follow
self.routePositions.push({
x: x,
y: y
});
// Animate route points appearing with delay
tween(routePoint, {
alpha: 0.8,
scaleX: 2,
scaleY: 2
}, {
duration: 100 + i * 20
});
}
// Add impact effect at enemy position
var impactEffect = self.addChild(LK.getAsset('routeEffect', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}));
impactEffect.x = enemyMiddleX;
impactEffect.y = enemyMiddleY;
self.routePoints.push(impactEffect);
// Animate impact effect
tween(impactEffect, {
alpha: 1,
scaleX: 3,
scaleY: 3
}, {
duration: 300,
easing: tween.easeOut
});
// Remove route after 0.5 seconds
LK.setTimeout(function () {
self.destroy();
// Remove from routeEffects array
for (var i = routeEffects.length - 1; i >= 0; i--) {
if (routeEffects[i] === self) {
routeEffects.splice(i, 1);
break;
}
}
}, 500);
}
};
self.clearRoute = function () {
for (var i = self.routePoints.length - 1; i >= 0; i--) {
self.routePoints[i].destroy();
}
self.routePoints = [];
};
self.fadeOut = function () {
for (var i = 0; i < self.routePoints.length; i++) {
tween(self.routePoints[i], {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 200
});
}
LK.setTimeout(function () {
self.destroy();
}, 300);
};
return self;
});
var ScreenShake = Container.expand(function () {
var self = Container.call(this);
self.intensity = 0;
self.duration = 0;
self.remainingTime = 0;
self.offsetX = 0;
self.offsetY = 0;
self.isActive = false;
self.decayEasing = tween.easeOut;
self.shake = function (intensity, duration, easing) {
self.intensity = intensity || 10;
self.duration = duration || 500;
self.remainingTime = self.duration;
self.isActive = true;
self.decayEasing = easing || tween.easeOut;
};
self.update = function () {
if (!self.isActive) {
self.offsetX = 0;
self.offsetY = 0;
return;
}
self.remainingTime -= 16.67; // Approximately 1 frame at 60fps
if (self.remainingTime <= 0) {
self.isActive = false;
self.offsetX = 0;
self.offsetY = 0;
return;
}
// Calculate decay factor using easing function
var progress = 1 - self.remainingTime / self.duration;
var decayFactor = 1 - self.decayEasing(progress);
// Generate random shake offset
var currentIntensity = self.intensity * decayFactor;
self.offsetX = (Math.random() - 0.5) * 2 * currentIntensity;
self.offsetY = (Math.random() - 0.5) * 2 * currentIntensity;
};
self.getOffsetX = function () {
return self.offsetX;
};
self.getOffsetY = function () {
return self.offsetY;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2c3e50,
title: 'Dungeon Crawler'
});
/****
* Game Code
****/
// Legacy Boss class for backward compatibility (delegates to BossType1)
// Game variables
var Boss = BossType1;
var screenShake = new ScreenShake();
// Helper function to trigger screen shake with different intensities
function triggerScreenShake(intensity, duration, easing) {
screenShake.shake(intensity, duration, easing);
}
var hero;
var enemies = [];
var coins = [];
var powerups = [];
var enemyWarnings = [];
var knives = [];
var knivesRemaining = 5;
var routeEffects = [];
var bloodParticles = [];
var bossProjectiles = [];
var currentDungeon = 1;
var dungeonComplete = false;
var hearts = [];
var healthPotions = [];
// Object pooling system
var objectPools = {
bloodParticle: [],
bossProjectile: [],
coin: [],
healthPotion: []
};
// Spatial partitioning system
var spatialGrid = {
cellSize: 200,
grid: {},
clear: function clear() {
this.grid = {};
},
getCellKey: function getCellKey(x, y) {
var cellX = Math.floor(x / this.cellSize);
var cellY = Math.floor(y / this.cellSize);
return cellX + ',' + cellY;
},
addObject: function addObject(obj, x, y) {
var key = this.getCellKey(x, y);
if (!this.grid[key]) {
this.grid[key] = [];
}
this.grid[key].push(obj);
},
getNearbyObjects: function getNearbyObjects(x, y, radius) {
var nearby = [];
var cellRadius = Math.ceil(radius / this.cellSize);
var centerCellX = Math.floor(x / this.cellSize);
var centerCellY = Math.floor(y / this.cellSize);
for (var dx = -cellRadius; dx <= cellRadius; dx++) {
for (var dy = -cellRadius; dy <= cellRadius; dy++) {
var key = centerCellX + dx + ',' + (centerCellY + dy);
if (this.grid[key]) {
nearby = nearby.concat(this.grid[key]);
}
}
}
return nearby;
}
};
function getFromPool(type) {
if (objectPools[type] && objectPools[type].length > 0) {
var obj = objectPools[type].pop();
obj.visible = true;
return obj;
}
// Create new object if pool is empty
if (type === 'bloodParticle') {
return new BloodParticle();
} else if (type === 'bossProjectile') {
return new BossProjectile();
} else if (type === 'coin') {
return new Coin();
} else if (type === 'healthPotion') {
return new HealthPotion();
}
return null;
}
function returnToPool(obj, type) {
if (!objectPools[type]) {
objectPools[type] = [];
}
obj.visible = false;
obj.x = -1000; // Move off screen
obj.y = -1000;
objectPools[type].push(obj);
}
// Movement state tracking
var movementState = {
left: false,
right: false,
up: false,
down: false
};
// Camera system
var camera = {
x: 0,
y: 0,
targetX: 0,
targetY: 0,
smoothing: 0.1
};
// Dungeon configuration
var levels = [{
enemies: [{
type: 'basic',
count: 3
}],
width: 4096,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 5
}, {
type: 'strong',
count: 2
}],
width: 5120,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 7
}, {
type: 'strong',
count: 3
}, {
type: 'fast',
count: 2
}],
width: 6144,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 5
}, {
type: 'strong',
count: 4
}, {
type: 'fast',
count: 3
}, {
type: 'tank',
count: 2
}],
width: 7168,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 8
}, {
type: 'strong',
count: 4
}, {
type: 'fast',
count: 4
}, {
type: 'tank',
count: 2
}, {
type: 'hunter',
count: 3
}],
width: 8192,
height: 2732
}, {
enemies: [{
type: 'boss1',
count: 1
}],
width: 9216,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 8
}, {
type: 'strong',
count: 4
}, {
type: 'fast',
count: 3
}, {
type: 'tank',
count: 2
}, {
type: 'hunter',
count: 3
}, {
type: 'assassin',
count: 2
}, {
type: 'boss2',
count: 1
}],
width: 10240,
height: 2732
}, {
enemies: [{
type: 'basic',
count: 6
}, {
type: 'strong',
count: 4
}, {
type: 'fast',
count: 4
}, {
type: 'tank',
count: 3
}, {
type: 'hunter',
count: 3
}, {
type: 'assassin',
count: 3
}, {
type: 'boss3',
count: 1
}, {
type: 'boss1',
count: 1
}],
width: 12288,
height: 2732
}];
var currentLevelData = levels[0];
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 80,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 150;
scoreText.y = 50;
LK.gui.topLeft.addChild(scoreText);
var levelText = new Text2('Dungeon: 1', {
size: 80,
fill: 0xFFFFFF
});
levelText.anchor.set(0.5, 0);
LK.gui.top.addChild(levelText);
levelText.y = 50;
var comboText = new Text2('Combo: 0x', {
size: 60,
fill: 0xFFFF00
});
comboText.anchor.set(1, 0);
LK.gui.topRight.addChild(comboText);
comboText.x = -50;
comboText.y = 120;
var knivesText = new Text2('Knives: 5', {
size: 60,
fill: 0x8e44ad
});
knivesText.anchor.set(1, 0);
LK.gui.topRight.addChild(knivesText);
knivesText.x = -50;
knivesText.y = 190;
var enemiesLeftText = new Text2('Enemies: 0', {
size: 60,
fill: 0xff4444
});
enemiesLeftText.anchor.set(1, 0);
LK.gui.topRight.addChild(enemiesLeftText);
enemiesLeftText.x = -50;
enemiesLeftText.y = 260;
// Create movement and attack buttons
var leftButton = LK.getAsset('leftButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
});
leftButton.x = 80;
leftButton.y = -300;
LK.gui.bottomLeft.addChild(leftButton);
var rightButton = LK.getAsset('rightButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
});
rightButton.x = 480;
rightButton.y = -300;
LK.gui.bottomLeft.addChild(rightButton);
var upButton = LK.getAsset('upButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
});
upButton.x = 280;
upButton.y = -480;
LK.gui.bottomLeft.addChild(upButton);
var downButton = LK.getAsset('downButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
});
downButton.x = 280;
downButton.y = -120;
LK.gui.bottomLeft.addChild(downButton);
var attackButton = LK.getAsset('attackButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
attackButton.x = -150;
attackButton.y = -200;
LK.gui.bottomRight.addChild(attackButton);
var knifeButton = LK.getAsset('knifeButton', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.9
});
knifeButton.x = -350;
knifeButton.y = -200;
LK.gui.bottomRight.addChild(knifeButton);
// Create background grid
var backgroundTiles = [];
function createBackgroundGrid() {
// Clear existing background tiles
for (var i = backgroundTiles.length - 1; i >= 0; i--) {
backgroundTiles[i].destroy();
}
backgroundTiles = [];
var tileSize = 200;
var tilesX = Math.ceil(currentLevelData.width / tileSize) + 2;
var tilesY = Math.ceil(currentLevelData.height / tileSize) + 2;
for (var x = 0; x < tilesX; x++) {
for (var y = 0; y < tilesY; y++) {
var tile = game.addChild(LK.getAsset('backgroundTile', {
anchorX: 0,
anchorY: 0,
alpha: 0.3
}));
tile.x = x * tileSize;
tile.y = y * tileSize;
// Add subtle pattern variation
if ((x + y) % 2 === 0) {
tile.alpha = 0.2;
}
backgroundTiles.push(tile);
}
}
}
// Add background
var background = game.addChild(LK.getAsset('background', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 1
}));
// Create hero
hero = game.addChild(new Hero());
hero.x = 1024; // Center of screen
hero.y = 2300; // Near bottom
// Create health display
function updateHealthDisplay() {
// Remove existing hearts
for (var i = hearts.length - 1; i >= 0; i--) {
hearts[i].destroy();
}
hearts = [];
// Create new hearts
for (var i = 0; i < hero.health; i++) {
var heart = LK.getAsset('heart', {
anchorX: 0.5,
anchorY: 0.5
});
heart.x = 200 + i * 80;
heart.y = 200;
LK.gui.topLeft.addChild(heart);
hearts.push(heart);
}
}
function updateScoreDisplay() {
scoreText.setText('Score: ' + LK.getScore());
var comboMultiplier = Math.floor(hero.comboCount / 5) + 1;
comboText.setText('Combo: ' + comboMultiplier + 'x');
}
function updateKnivesDisplay() {
knivesText.setText('Knives: ' + knivesRemaining);
// Update button alpha based on availability
knifeButton.alpha = knivesRemaining > 0 ? 0.9 : 0.3;
}
function updateEnemiesLeftDisplay() {
enemiesLeftText.setText('Enemies: ' + enemies.length);
}
function initializeLevel() {
// Show boss fight alert for final dungeon
if (currentDungeon === levels.length) {
LK.setTimeout(function () {
alert('FINAL BOSS APPROACHING! Prepare for the ultimate challenge!');
}, 500);
}
// Clear existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
}
enemies = [];
// Clear existing warnings
for (var i = enemyWarnings.length - 1; i >= 0; i--) {
enemyWarnings[i].destroy();
}
enemyWarnings = [];
// Clear existing knives
for (var i = knives.length - 1; i >= 0; i--) {
knives[i].destroy();
}
knives = [];
// Clear existing route effects
for (var i = routeEffects.length - 1; i >= 0; i--) {
routeEffects[i].destroy();
}
routeEffects = [];
// Clear existing blood particles
for (var i = bloodParticles.length - 1; i >= 0; i--) {
bloodParticles[i].destroy();
}
bloodParticles = [];
// Clear existing boss projectiles
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
bossProjectiles[i].destroy();
}
bossProjectiles = [];
// Clear existing health potions
for (var i = healthPotions.length - 1; i >= 0; i--) {
healthPotions[i].destroy();
}
healthPotions = [];
// Reset knife count
knivesRemaining = 5;
currentLevelData = levels[currentDungeon - 1] || levels[levels.length - 1];
dungeonComplete = false;
// Spawn enemies for this level
for (var j = 0; j < currentLevelData.enemies.length; j++) {
var enemyGroup = currentLevelData.enemies[j];
for (var k = 0; k < enemyGroup.count; k++) {
spawnEnemy(enemyGroup.type);
}
}
levelText.setText('Dungeon: ' + currentDungeon);
updateKnivesDisplay();
updateEnemiesLeftDisplay();
// Create background grid for this level
createBackgroundGrid();
}
function spawnEnemy(type) {
var enemy;
if (type === 'boss') {
// Randomly choose boss type based on dungeon
var bossTypes = [BossType1, BossType2, BossType3];
var BossClass = bossTypes[Math.floor(Math.random() * bossTypes.length)];
enemy = game.addChild(new BossClass());
// Boss spawns at center of level
enemy.x = currentLevelData.width / 2;
enemy.y = 2200;
} else if (type === 'boss1') {
enemy = game.addChild(new BossType1());
enemy.x = currentLevelData.width / 2;
enemy.y = 2200;
} else if (type === 'boss2') {
enemy = game.addChild(new BossType2());
enemy.x = currentLevelData.width / 2;
enemy.y = 2200;
} else if (type === 'boss3') {
enemy = game.addChild(new BossType3());
enemy.x = currentLevelData.width / 2;
enemy.y = 2200;
} else {
enemy = game.addChild(new Enemy(type));
// Find a spawn position that's not too close to the hero
var minDistanceFromPlayer = 500; // Minimum distance from player
var attempts = 0;
var maxAttempts = 20;
var enemyX, enemyY;
do {
// Random spawn position within level bounds
enemyX = 200 + Math.random() * (currentLevelData.width - 400);
enemyY = 1700 + Math.random() * 600;
// Calculate distance from hero
var dx = enemyX - hero.x;
var dy = enemyY - hero.y;
var distanceFromPlayer = Math.sqrt(dx * dx + dy * dy);
attempts++;
// If far enough from player or we've tried too many times, use this position
if (distanceFromPlayer >= minDistanceFromPlayer || attempts >= maxAttempts) {
enemy.x = enemyX;
enemy.y = enemyY;
break;
}
} while (attempts < maxAttempts);
// Set enemy properties based on type
if (type === 'basic') {
enemy.health = 2;
enemy.speed = 2;
} else if (type === 'strong') {
enemy.health = 4;
enemy.speed = 0.8;
} else if (type === 'fast') {
enemy.health = 1;
enemy.speed = 4;
} else if (type === 'tank') {
enemy.health = 8;
enemy.speed = 0.5;
} else if (type === 'hunter') {
enemy.health = 3;
enemy.speed = 2.5;
} else if (type === 'assassin') {
enemy.health = 2;
enemy.speed = 3;
}
}
enemies.push(enemy);
}
function spawnPowerUp() {
var powerup = game.addChild(new PowerUp());
powerup.x = 500 + Math.random() * 1048; // Random x position
powerup.y = 1800 + Math.random() * 400; // Above ground level
// Random powerup type
var types = ['health', 'invulnerable', 'damage'];
powerup.type = types[Math.floor(Math.random() * types.length)];
// Color by type
var powerupGraphics = powerup.getChildAt(0);
if (powerup.type === 'health') {
powerupGraphics.tint = 0x00ff00; // Green
} else if (powerup.type === 'invulnerable') {
powerupGraphics.tint = 0x0088ff; // Blue
} else if (powerup.type === 'damage') {
powerupGraphics.tint = 0xff8800; // Orange
}
powerups.push(powerup);
}
function findNearestEnemy(x, y) {
var nearest = null;
var shortestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var distance = Math.sqrt(Math.pow(enemy.x - x, 2) + Math.pow(enemy.y - y, 2));
if (distance < shortestDistance) {
shortestDistance = distance;
nearest = enemy;
}
}
return nearest;
}
function isVisible(obj, buffer) {
buffer = buffer || 100;
var objScreenX = obj.x - camera.x;
var objScreenY = obj.y - camera.y;
return objScreenX >= -buffer && objScreenX <= 2048 + buffer && objScreenY >= -buffer && objScreenY <= 2732 + buffer;
}
function updateEnemyWarnings() {
// Remove warnings for dead enemies
for (var i = enemyWarnings.length - 1; i >= 0; i--) {
var warning = enemyWarnings[i];
var enemyExists = false;
for (var j = 0; j < enemies.length; j++) {
if (enemies[j] === warning.targetEnemy) {
enemyExists = true;
break;
}
}
if (!enemyExists) {
warning.destroy();
enemyWarnings.splice(i, 1);
}
}
// Create warnings for new enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var hasWarning = false;
for (var j = 0; j < enemyWarnings.length; j++) {
if (enemyWarnings[j].targetEnemy === enemy) {
hasWarning = true;
break;
}
}
if (!hasWarning) {
var warning = LK.gui.center.addChild(new EnemyWarning());
warning.targetEnemy = enemy;
warning.setDirection('left');
enemyWarnings.push(warning);
}
}
}
// Initialize UI
updateHealthDisplay();
updateScoreDisplay();
updateKnivesDisplay();
updateEnemiesLeftDisplay();
// Initialize first level
initializeLevel();
// Create initial background grid
createBackgroundGrid();
// Set initial camera position
camera.targetX = hero.x - 1024;
camera.targetY = hero.y - 1366;
camera.x = camera.targetX;
camera.y = camera.targetY;
// Button event handlers
leftButton.down = function (x, y, obj) {
movementState.left = true;
};
leftButton.up = function (x, y, obj) {
movementState.left = false;
};
// Global function to check which button is under a given position
function getButtonUnderPosition(screenX, screenY) {
// Convert GUI coordinates and check bounds for each button (accounting for 1.5x scale)
var scaledButtonWidth = 200 * 1.5;
var scaledButtonHeight = 200 * 1.5;
var leftBounds = {
x: leftButton.x - scaledButtonWidth / 2,
y: leftButton.y - scaledButtonHeight / 2,
width: scaledButtonWidth,
height: scaledButtonHeight
};
var rightBounds = {
x: rightButton.x - scaledButtonWidth / 2,
y: rightButton.y - scaledButtonHeight / 2,
width: scaledButtonWidth,
height: scaledButtonHeight
};
var upBounds = {
x: upButton.x - scaledButtonWidth / 2,
y: upButton.y - scaledButtonHeight / 2,
width: scaledButtonWidth,
height: scaledButtonHeight
};
var downBounds = {
x: downButton.x - scaledButtonWidth / 2,
y: downButton.y - scaledButtonHeight / 2,
width: scaledButtonWidth,
height: scaledButtonHeight
};
// Adjust screen coordinates relative to bottomLeft GUI
var relativeX = screenX;
var relativeY = screenY - (2732 - 500); // Approximate bottomLeft offset
if (relativeX >= leftBounds.x && relativeX <= leftBounds.x + leftBounds.width && relativeY >= leftBounds.y && relativeY <= leftBounds.y + leftBounds.height) {
return 'left';
}
if (relativeX >= rightBounds.x && relativeX <= rightBounds.x + rightBounds.width && relativeY >= rightBounds.y && relativeY <= rightBounds.y + rightBounds.height) {
return 'right';
}
if (relativeX >= upBounds.x && relativeX <= upBounds.x + upBounds.width && relativeY >= upBounds.y && relativeY <= upBounds.y + upBounds.height) {
return 'up';
}
if (relativeX >= downBounds.x && relativeX <= downBounds.x + downBounds.width && relativeY >= downBounds.y && relativeY <= downBounds.y + downBounds.height) {
return 'down';
}
return null;
}
// Global movement handling function
function handleMovementInput(direction) {
// Reset all movement states first
movementState.left = false;
movementState.right = false;
movementState.up = false;
movementState.down = false;
// Set the active direction
if (direction === 'left') {
movementState.left = true;
} else if (direction === 'right') {
movementState.right = true;
} else if (direction === 'up') {
movementState.up = true;
} else if (direction === 'down') {
movementState.down = true;
}
}
leftButton.move = function (x, y, obj) {
var currentButton = getButtonUnderPosition(x, y);
if (currentButton) {
handleMovementInput(currentButton);
} else {
handleMovementInput('left');
}
};
rightButton.down = function (x, y, obj) {
movementState.right = true;
};
rightButton.up = function (x, y, obj) {
movementState.right = false;
};
rightButton.move = function (x, y, obj) {
var currentButton = getButtonUnderPosition(x, y);
if (currentButton) {
handleMovementInput(currentButton);
} else {
handleMovementInput('right');
}
};
upButton.down = function (x, y, obj) {
movementState.up = true;
};
upButton.up = function (x, y, obj) {
movementState.up = false;
};
upButton.move = function (x, y, obj) {
var currentButton = getButtonUnderPosition(x, y);
if (currentButton) {
handleMovementInput(currentButton);
} else {
handleMovementInput('up');
}
};
downButton.down = function (x, y, obj) {
movementState.down = true;
};
downButton.up = function (x, y, obj) {
movementState.down = false;
};
downButton.move = function (x, y, obj) {
var currentButton = getButtonUnderPosition(x, y);
if (currentButton) {
handleMovementInput(currentButton);
} else {
handleMovementInput('down');
}
};
attackButton.down = function (x, y, obj) {
if (!hero.isAttacking) {
var nearestEnemy = findNearestEnemy(hero.x, hero.y);
if (nearestEnemy) {
hero.attack(nearestEnemy.x);
// Check if attack hits with increased range
var distanceToEnemy = Math.sqrt(Math.pow(hero.x - nearestEnemy.x, 2) + Math.pow(hero.y - nearestEnemy.y, 2));
if (distanceToEnemy < 350) {
// Increased from 250 to 350
var damage = hero.damageBoost ? 2 : 1;
for (var i = 0; i < damage; i++) {
nearestEnemy.takeDamage();
}
}
} else {
// Attack in hero's facing direction
hero.attack(hero.x + (hero.getChildAt(0).scaleX > 0 ? 100 : -100));
}
}
};
knifeButton.down = function (x, y, obj) {
if (knivesRemaining > 0) {
// Find nearest enemy for targeting
var nearestEnemy = findNearestEnemy(hero.x, hero.y);
if (nearestEnemy) {
// Clear existing route effects before creating new one
for (var i = routeEffects.length - 1; i >= 0; i--) {
routeEffects[i].destroy();
routeEffects.splice(i, 1);
}
// Create route visualization
var routeEffect = game.addChild(new RouteEffect());
routeEffect.createRoute(nearestEnemy);
routeEffects.push(routeEffect);
// Throw knife to follow the route
var knife = game.addChild(new Knife());
knife.x = hero.x;
knife.y = hero.y - 80;
// Set the route for the knife to follow
knife.setRoute(routeEffect.routePositions);
// Rotation will be handled automatically in knife update based on target direction
knives.push(knife);
knivesRemaining--;
updateKnivesDisplay();
LK.getSound('knifeThrow').play();
} else {
// No enemy found, throw in hero facing direction
var knife = game.addChild(new Knife());
knife.x = hero.x;
knife.y = hero.y - 80;
var heroGraphics = hero.getChildAt(0);
knife.direction = heroGraphics.scaleX > 0 ? 1 : -1;
// Rotation will be handled automatically in knife update based on movement direction
knives.push(knife);
knivesRemaining--;
updateKnivesDisplay();
LK.getSound('knifeThrow').play();
}
}
};
// Game input (fallback for screen taps outside buttons)
game.down = function (x, y, obj) {
// Convert screen coordinates to world coordinates
var worldX = x + camera.x;
var worldY = y + camera.y;
// Check if tap is for movement or attack
var distanceToHero = Math.sqrt(Math.pow(worldX - hero.x, 2) + Math.pow(worldY - hero.y, 2));
if (distanceToHero > 200) {
// Movement - move toward tap position
var dx = worldX - hero.x;
var dy = worldY - hero.y;
if (Math.abs(dx) > Math.abs(dy)) {
hero.move(dx > 0 ? 'right' : 'left');
} else {
hero.move(dy > 0 ? 'down' : 'up');
}
} else {
// Attack
if (!hero.isAttacking) {
var nearestEnemy = findNearestEnemy(worldX, worldY);
if (nearestEnemy) {
hero.attack(nearestEnemy.x);
// Check if attack hits with increased range
var distanceToEnemy = Math.sqrt(Math.pow(hero.x - nearestEnemy.x, 2) + Math.pow(hero.y - nearestEnemy.y, 2));
if (distanceToEnemy < 350) {
// Increased from 250 to 350
var damage = hero.damageBoost ? 2 : 1;
for (var i = 0; i < damage; i++) {
nearestEnemy.takeDamage();
}
}
} else {
// Attack in direction of tap
hero.attack(worldX);
}
}
}
};
// Main game loop
game.update = function () {
// Track if hero is moving this frame
var wasMoving = hero.isWalking;
var isMovingThisFrame = false;
// Handle continuous movement based on button states
if (movementState.left) {
hero.move('left');
isMovingThisFrame = true;
}
if (movementState.right) {
hero.move('right');
isMovingThisFrame = true;
}
if (movementState.up) {
hero.move('up');
isMovingThisFrame = true;
}
if (movementState.down) {
hero.move('down');
isMovingThisFrame = true;
}
// Stop walking animation if no movement this frame
if (wasMoving && !isMovingThisFrame) {
hero.stopWalkAnimation();
}
// Update screen shake
screenShake.update();
// Update camera position smoothly
camera.x += (camera.targetX - camera.x) * camera.smoothing;
camera.y += (camera.targetY - camera.y) * camera.smoothing;
// Apply camera position to game with screen shake offset
game.x = -camera.x + screenShake.getOffsetX();
game.y = -camera.y + screenShake.getOffsetY();
// Check for hero-enemy collisions and apply push-back using spatial partitioning
var nearbyEnemies = spatialGrid.getNearbyObjects(hero.x, hero.y, 150);
for (var i = 0; i < nearbyEnemies.length; i++) {
var enemy = nearbyEnemies[i];
var dx = hero.x - enemy.x;
var dy = hero.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// If too close, push hero away from enemy
if (distance < 100 && distance > 0) {
var pushForce = (100 - distance) * 0.3;
var pushX = dx / distance * pushForce;
var pushY = dy / distance * pushForce;
// Apply push with bounds checking
var newHeroX = hero.x + pushX;
var newHeroY = hero.y + pushY;
// Keep hero within level bounds
if (newHeroX > 100 && newHeroX < currentLevelData.width - 100) {
hero.x = newHeroX;
}
if (newHeroY > 1600 && newHeroY < 2400) {
hero.y = newHeroY;
}
// Update camera target when hero is pushed
camera.targetX = hero.x - 1024;
camera.targetY = hero.y - 1366;
camera.targetX = Math.max(0, Math.min(camera.targetX, currentLevelData.width - 2048));
camera.targetY = Math.max(0, Math.min(camera.targetY, currentLevelData.height - 2732));
}
}
// Clear and rebuild spatial grid
spatialGrid.clear();
for (var i = 0; i < enemies.length; i++) {
spatialGrid.addObject(enemies[i], enemies[i].x, enemies[i].y);
}
// Update all game objects with culling
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
if (isVisible(enemy, 300)) {
// Only update visible enemies + buffer
enemy.update();
}
}
for (var i = coins.length - 1; i >= 0; i--) {
if (isVisible(coins[i], 100)) {
coins[i].update();
}
}
for (var i = powerups.length - 1; i >= 0; i--) {
if (isVisible(powerups[i], 100)) {
powerups[i].update();
}
}
for (var i = healthPotions.length - 1; i >= 0; i--) {
if (isVisible(healthPotions[i], 100)) {
healthPotions[i].update();
}
}
for (var i = knives.length - 1; i >= 0; i--) {
knives[i].update(); // Always update knives as they move fast
}
for (var i = bloodParticles.length - 1; i >= 0; i--) {
if (isVisible(bloodParticles[i], 50)) {
bloodParticles[i].update();
}
}
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
bossProjectiles[i].update(); // Always update projectiles as they move fast
}
// Clean up destroyed route effects
for (var i = routeEffects.length - 1; i >= 0; i--) {
var routeEffect = routeEffects[i];
if (!routeEffect.parent) {
routeEffects.splice(i, 1);
}
}
// Update enemy warnings
updateEnemyWarnings();
for (var i = 0; i < enemyWarnings.length; i++) {
enemyWarnings[i].update();
}
// Check for dungeon completion
if (!dungeonComplete && enemies.length === 0) {
dungeonComplete = true;
currentDungeon++;
if (currentDungeon <= levels.length) {
// Start next dungeon after delay
LK.setTimeout(function () {
initializeLevel();
}, 2000);
} else {
// All dungeons completed
LK.showYouWin();
}
}
}; ===================================================================
--- original.js
+++ change.js
@@ -394,8 +394,10 @@
self.isChanneling = true;
// Create vulnerability window - boss can't move during earthquake
self.speed = 0;
LK.effects.flashScreen(0x8b4513, 2000);
+ // Intense screen shake for earthquake
+ triggerScreenShake(25, 1500, tween.easeInOut);
// Screen shake effect simulation through rapid position changes
var originalX = bossGraphics.x;
var originalY = bossGraphics.y;
for (var i = 0; i < 20; i++) {
@@ -720,8 +722,10 @@
self.meleeAttack = function () {
hero.takeDamage();
self.attackCooldown = 90;
LK.effects.flashObject(self, 0xffffff, 200);
+ // Screen shake on boss melee attack
+ triggerScreenShake(15, 300, tween.easeOut);
};
self.fireProjectile = function () {
tween(bossGraphics, {
tint: 0x8800ff
@@ -732,8 +736,10 @@
tint: 0xffffff
}, {
duration: 200
});
+ // Screen shake on boss projectile attack
+ triggerScreenShake(8, 200, tween.easeOut);
var projectile = game.addChild(new BossProjectile());
projectile.x = self.x;
projectile.y = self.y - 200;
var dx = self.predictedHeroX - self.x;
@@ -794,8 +800,10 @@
LK.setTimeout(function () {
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
if (distanceToHero < 150) {
hero.takeDamage();
+ // Screen shake on successful charge attack hit
+ triggerScreenShake(20, 400, tween.easeOut);
}
}, 150);
}
}, 800);
@@ -938,8 +946,10 @@
var distanceToHero = Math.sqrt(Math.pow(self.x - hero.x, 2) + Math.pow(self.y - hero.y, 2));
if (distanceToHero < 200) {
hero.takeDamage();
LK.effects.flashObject(hero, 0xff0000, 200);
+ // Screen shake on spin attack hit
+ triggerScreenShake(12, 250, tween.easeOut);
}
}, i * 300);
}
self.spinAttackCooldown = 420;
@@ -1266,8 +1276,10 @@
self.die();
}
};
self.die = function () {
+ // Screen shake on enemy death (smaller intensity)
+ triggerScreenShake(6, 150, tween.easeOut);
// Drop coin
var coin = game.addChild(new Coin());
coin.x = self.x;
coin.y = self.y;
@@ -1530,8 +1542,10 @@
self.health--;
self.comboCount = 0;
// Flash red when hit
LK.effects.flashObject(self, 0xff0000, 500);
+ // Screen shake when player is hit
+ triggerScreenShake(18, 400, tween.easeOut);
// Temporary invulnerability
self.invulnerable = true;
LK.setTimeout(function () {
self.invulnerable = false;
@@ -1728,8 +1742,10 @@
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 50) {
// Hit enemy center - deal damage only to first enemy encountered
enemy.takeDamage(true);
+ // Light screen shake on knife impact
+ triggerScreenShake(4, 100, tween.easeOut);
self.destroy();
// Remove from knives array
for (var j = knives.length - 1; j >= 0; j--) {
if (knives[j] === self) {
@@ -2004,8 +2020,53 @@
}, 300);
};
return self;
});
+var ScreenShake = Container.expand(function () {
+ var self = Container.call(this);
+ self.intensity = 0;
+ self.duration = 0;
+ self.remainingTime = 0;
+ self.offsetX = 0;
+ self.offsetY = 0;
+ self.isActive = false;
+ self.decayEasing = tween.easeOut;
+ self.shake = function (intensity, duration, easing) {
+ self.intensity = intensity || 10;
+ self.duration = duration || 500;
+ self.remainingTime = self.duration;
+ self.isActive = true;
+ self.decayEasing = easing || tween.easeOut;
+ };
+ self.update = function () {
+ if (!self.isActive) {
+ self.offsetX = 0;
+ self.offsetY = 0;
+ return;
+ }
+ self.remainingTime -= 16.67; // Approximately 1 frame at 60fps
+ if (self.remainingTime <= 0) {
+ self.isActive = false;
+ self.offsetX = 0;
+ self.offsetY = 0;
+ return;
+ }
+ // Calculate decay factor using easing function
+ var progress = 1 - self.remainingTime / self.duration;
+ var decayFactor = 1 - self.decayEasing(progress);
+ // Generate random shake offset
+ var currentIntensity = self.intensity * decayFactor;
+ self.offsetX = (Math.random() - 0.5) * 2 * currentIntensity;
+ self.offsetY = (Math.random() - 0.5) * 2 * currentIntensity;
+ };
+ self.getOffsetX = function () {
+ return self.offsetX;
+ };
+ self.getOffsetY = function () {
+ return self.offsetY;
+ };
+ return self;
+});
/****
* Initialize Game
****/
@@ -2019,8 +2080,13 @@
****/
// Legacy Boss class for backward compatibility (delegates to BossType1)
// Game variables
var Boss = BossType1;
+var screenShake = new ScreenShake();
+// Helper function to trigger screen shake with different intensities
+function triggerScreenShake(intensity, duration, easing) {
+ screenShake.shake(intensity, duration, easing);
+}
var hero;
var enemies = [];
var coins = [];
var powerups = [];
@@ -2871,14 +2937,16 @@
// Stop walking animation if no movement this frame
if (wasMoving && !isMovingThisFrame) {
hero.stopWalkAnimation();
}
+ // Update screen shake
+ screenShake.update();
// Update camera position smoothly
camera.x += (camera.targetX - camera.x) * camera.smoothing;
camera.y += (camera.targetY - camera.y) * camera.smoothing;
- // Apply camera position to game
- game.x = -camera.x;
- game.y = -camera.y;
+ // Apply camera position to game with screen shake offset
+ game.x = -camera.x + screenShake.getOffsetX();
+ game.y = -camera.y + screenShake.getOffsetY();
// Check for hero-enemy collisions and apply push-back using spatial partitioning
var nearbyEnemies = spatialGrid.getNearbyObjects(hero.x, hero.y, 150);
for (var i = 0; i < nearbyEnemies.length; i++) {
var enemy = nearbyEnemies[i];