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 enhanced blood particles with variety for (var p = 0; p < 10; p++) { var bloodParticle = game.addChild(getFromPool('bloodParticle')); var particleType = Math.random() < 0.3 ? 'large' : Math.random() < 0.5 ? 'small' : 'normal'; bloodParticle.reset(self.x + (Math.random() - 0.5) * 60, self.y - 100 + (Math.random() - 0.5) * 60, particleType); bloodParticles.push(bloodParticle); } // Add spray particles for dramatic effect for (var p = 0; p < 8; p++) { var sprayParticle = game.addChild(getFromPool('bloodParticle')); sprayParticle.reset(self.x + (Math.random() - 0.5) * 40, self.y - 120 + (Math.random() - 0.5) * 40, 'spray'); bloodParticles.push(sprayParticle); } // 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.particleType = 'normal'; // normal, large, small, spray self.reset = function (startX, startY, type) { self.x = startX; self.y = startY; self.particleType = type || 'normal'; // Different sizes, speeds, and lifetimes based on type if (self.particleType === 'large') { self.velocityX = (Math.random() - 0.5) * 6; self.velocityY = -Math.random() * 4 - 1; self.lifetime = 90; // Larger particles last longer particleGraphics.scaleX = 1.5; particleGraphics.scaleY = 1.5; particleGraphics.tint = 0x8b0000; // Dark red } else if (self.particleType === 'small') { self.velocityX = (Math.random() - 0.5) * 12; // Faster small particles self.velocityY = -Math.random() * 8 - 3; self.lifetime = 30; // Short-lived particleGraphics.scaleX = 0.5; particleGraphics.scaleY = 0.5; particleGraphics.tint = 0xff4444; // Bright red } else if (self.particleType === 'spray') { self.velocityX = (Math.random() - 0.5) * 16; // Very fast spray self.velocityY = -Math.random() * 10 - 1; self.lifetime = 45; particleGraphics.scaleX = 0.7; particleGraphics.scaleY = 0.7; particleGraphics.tint = 0xcc2222; // Medium red } else { // Normal particles self.velocityX = (Math.random() - 0.5) * 8; self.velocityY = -Math.random() * 6 - 2; self.lifetime = 60; particleGraphics.scaleX = 1; particleGraphics.scaleY = 1; particleGraphics.tint = 0x8b0000; } particleGraphics.alpha = 1; self.visible = true; }; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; self.velocityY += self.gravity; // Different fade patterns based on type var maxLifetime = 60; if (self.particleType === 'large') maxLifetime = 90;else if (self.particleType === 'small') maxLifetime = 30;else if (self.particleType === 'spray') maxLifetime = 45; // Fade out over time self.lifetime--; particleGraphics.alpha = self.lifetime / maxLifetime; 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 DustCloud = Container.expand(function () { var self = Container.call(this); var dustGraphics = self.attachAsset('bloodParticle', { anchorX: 0.5, anchorY: 0.5 }); dustGraphics.tint = 0x8b7355; // Brown dust color dustGraphics.scaleX = 0.8; dustGraphics.scaleY = 0.8; self.velocityX = 0; self.velocityY = 0; self.lifetime = 40; // Medium lifetime self.expandRate = 0.02; // How fast dust expands self.reset = function (startX, startY, direction) { self.x = startX; self.y = startY; // Dust moves opposite to movement direction var baseSpeed = 1 + Math.random() * 2; if (direction === 'left') { self.velocityX = baseSpeed; } else if (direction === 'right') { self.velocityX = -baseSpeed; } else if (direction === 'up') { self.velocityY = baseSpeed * 0.5; } else if (direction === 'down') { self.velocityY = -baseSpeed * 0.5; } else { // Random direction for general dust var angle = Math.random() * Math.PI * 2; self.velocityX = Math.cos(angle) * baseSpeed; self.velocityY = Math.sin(angle) * baseSpeed; } self.velocityY -= Math.random() * 1; // Slight upward drift dustGraphics.alpha = 0.6; dustGraphics.scaleX = 0.4 + Math.random() * 0.4; dustGraphics.scaleY = 0.4 + Math.random() * 0.4; self.visible = true; }; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Dust slows down and expands self.velocityX *= 0.98; self.velocityY *= 0.98; dustGraphics.scaleX += self.expandRate; dustGraphics.scaleY += self.expandRate; // Fade out over time self.lifetime--; dustGraphics.alpha = self.lifetime / 40 * 0.6; if (self.lifetime <= 0) { // Remove from dustClouds array for (var i = dustClouds.length - 1; i >= 0; i--) { if (dustClouds[i] === self) { dustClouds.splice(i, 1); break; } } returnToPool(self, 'dustCloud'); } }; 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 enhanced blood particles with variety for (var p = 0; p < 8; p++) { var bloodParticle = game.addChild(getFromPool('bloodParticle')); var particleType = Math.random() < 0.2 ? 'large' : Math.random() < 0.6 ? 'small' : 'normal'; bloodParticle.reset(self.x + (Math.random() - 0.5) * 40, self.y - 60 + (Math.random() - 0.5) * 40, particleType); bloodParticles.push(bloodParticle); } // Add impact sparks when hit by knife if (fromKnife) { for (var s = 0; s < 6; s++) { var spark = game.addChild(getFromPool('impactSpark')); var sparkType = Math.random() < 0.3 ? 'bright' : Math.random() < 0.5 ? 'orange' : 'normal'; spark.reset(self.x + (Math.random() - 0.5) * 30, self.y - 70 + (Math.random() - 0.5) * 30, sparkType); impactSparks.push(spark); } 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; // Create dust clouds when moving (occasional) if (Math.random() < 0.3) { var dustCloud = game.addChild(getFromPool('dustCloud')); dustCloud.reset(self.x + (Math.random() - 0.5) * 30, self.y - 10 + (Math.random() - 0.5) * 20, direction); dustClouds.push(dustCloud); } // 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 ImpactSpark = Container.expand(function () { var self = Container.call(this); var sparkGraphics = self.attachAsset('bloodParticle', { anchorX: 0.5, anchorY: 0.5 }); sparkGraphics.tint = 0xffff44; // Yellow-white sparks sparkGraphics.scaleX = 0.3; sparkGraphics.scaleY = 0.3; self.velocityX = 0; self.velocityY = 0; self.lifetime = 20; // Short-lived sparks self.sparkType = 'normal'; // normal, bright, orange self.reset = function (startX, startY, type) { self.x = startX; self.y = startY; self.sparkType = type || 'normal'; // Random direction and speed for sparks var angle = Math.random() * Math.PI * 2; var speed = 3 + Math.random() * 5; self.velocityX = Math.cos(angle) * speed; self.velocityY = Math.sin(angle) * speed; if (self.sparkType === 'bright') { sparkGraphics.tint = 0xffffff; // White sparks sparkGraphics.scaleX = 0.4; sparkGraphics.scaleY = 0.4; self.lifetime = 30; } else if (self.sparkType === 'orange') { sparkGraphics.tint = 0xff8844; // Orange sparks sparkGraphics.scaleX = 0.2; sparkGraphics.scaleY = 0.2; self.lifetime = 15; } else { sparkGraphics.tint = 0xffff44; // Yellow sparks sparkGraphics.scaleX = 0.3; sparkGraphics.scaleY = 0.3; self.lifetime = 20; } sparkGraphics.alpha = 1; self.visible = true; }; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; // Sparks slow down over time self.velocityX *= 0.95; self.velocityY *= 0.95; // Fade out quickly self.lifetime--; sparkGraphics.alpha = self.lifetime / 20; if (self.lifetime <= 0) { // Remove from impactSparks array for (var i = impactSparks.length - 1; i >= 0; i--) { if (impactSparks[i] === self) { impactSparks.splice(i, 1); break; } } returnToPool(self, 'impactSpark'); } }; 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 MagicalEffect = Container.expand(function () { var self = Container.call(this); var effectGraphics = self.attachAsset('bloodParticle', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.lifetime = 60; self.effectType = 'sparkle'; // sparkle, healing, power, shield self.rotationSpeed = 0; self.reset = function (startX, startY, type) { self.x = startX; self.y = startY; self.effectType = type || 'sparkle'; if (self.effectType === 'sparkle') { effectGraphics.tint = 0xffff88; // Golden sparkle effectGraphics.scaleX = 0.6; effectGraphics.scaleY = 0.6; var angle = Math.random() * Math.PI * 2; var speed = 2 + Math.random() * 3; self.velocityX = Math.cos(angle) * speed; self.velocityY = Math.sin(angle) * speed - 2; // Float upward self.rotationSpeed = (Math.random() - 0.5) * 0.2; self.lifetime = 80; } else if (self.effectType === 'healing') { effectGraphics.tint = 0x44ff44; // Green healing effectGraphics.scaleX = 0.8; effectGraphics.scaleY = 0.8; self.velocityX = (Math.random() - 0.5) * 2; self.velocityY = -3 - Math.random() * 2; // Rise upward self.rotationSpeed = 0.1; self.lifetime = 60; } else if (self.effectType === 'power') { effectGraphics.tint = 0xff4488; // Purple power effectGraphics.scaleX = 1.0; effectGraphics.scaleY = 1.0; self.velocityX = (Math.random() - 0.5) * 4; self.velocityY = -1 - Math.random() * 3; self.rotationSpeed = 0.15; self.lifetime = 70; } else if (self.effectType === 'shield') { effectGraphics.tint = 0x44ddff; // Blue shield effectGraphics.scaleX = 0.5; effectGraphics.scaleY = 0.5; var angle = Math.random() * Math.PI * 2; var radius = 50 + Math.random() * 30; self.velocityX = Math.cos(angle) * 2; self.velocityY = Math.sin(angle) * 2; self.rotationSpeed = 0.08; self.lifetime = 90; } effectGraphics.alpha = 1; self.visible = true; }; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; effectGraphics.rotation += self.rotationSpeed; // Different behaviors per effect type if (self.effectType === 'sparkle') { self.velocityY -= 0.05; // Continue floating up } else if (self.effectType === 'healing') { self.velocityX *= 0.99; // Slow down horizontally } else if (self.effectType === 'power') { // Pulsing effect var pulseScale = 1.0 + Math.sin(LK.ticks * 0.3) * 0.2; effectGraphics.scaleX = pulseScale; effectGraphics.scaleY = pulseScale; } // Fade out over time self.lifetime--; var maxLifetime = 60; if (self.effectType === 'sparkle') maxLifetime = 80;else if (self.effectType === 'power') maxLifetime = 70;else if (self.effectType === 'shield') maxLifetime = 90; effectGraphics.alpha = self.lifetime / maxLifetime; if (self.lifetime <= 0) { // Remove from magicalEffects array for (var i = magicalEffects.length - 1; i >= 0; i--) { if (magicalEffects[i] === self) { magicalEffects.splice(i, 1); break; } } returnToPool(self, 'magicalEffect'); } }; 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(); // Create magical effects based on power-up type var effectType = 'sparkle'; if (self.type === 'health') { hero.heal(); effectType = 'healing'; } else if (self.type === 'invulnerable') { hero.invulnerable = true; effectType = 'shield'; LK.setTimeout(function () { hero.invulnerable = false; }, 5000); } else if (self.type === 'damage') { hero.damageBoost = true; effectType = 'power'; LK.setTimeout(function () { hero.damageBoost = false; }, 5000); } // Create magical particle effects for (var e = 0; e < 12; e++) { var magicalEffect = game.addChild(getFromPool('magicalEffect')); magicalEffect.reset(self.x + (Math.random() - 0.5) * 60, self.y + (Math.random() - 0.5) * 60, effectType); magicalEffects.push(magicalEffect); } // 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 ****/ // Game variables // Legacy Boss class for backward compatibility (delegates to BossType1) 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 impactSparks = []; var dustClouds = []; var magicalEffects = []; var currentDungeon = 1; var dungeonComplete = false; var hearts = []; var healthPotions = []; // Object pooling system var objectPools = { bloodParticle: [], bossProjectile: [], coin: [], healthPotion: [], impactSpark: [], dustCloud: [], magicalEffect: [] }; // 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(); } else if (type === 'impactSpark') { return new ImpactSpark(); } else if (type === 'dustCloud') { return new DustCloud(); } else if (type === 'magicalEffect') { return new MagicalEffect(); } 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 impact sparks for (var i = impactSparks.length - 1; i >= 0; i--) { impactSparks[i].destroy(); } impactSparks = []; // Clear existing dust clouds for (var i = dustClouds.length - 1; i >= 0; i--) { dustClouds[i].destroy(); } dustClouds = []; // Clear existing magical effects for (var i = magicalEffects.length - 1; i >= 0; i--) { magicalEffects[i].destroy(); } magicalEffects = []; // 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 = impactSparks.length - 1; i >= 0; i--) { if (isVisible(impactSparks[i], 50)) { impactSparks[i].update(); } } for (var i = dustClouds.length - 1; i >= 0; i--) { if (isVisible(dustClouds[i], 100)) { dustClouds[i].update(); } } for (var i = magicalEffects.length - 1; i >= 0; i--) { if (isVisible(magicalEffects[i], 100)) { magicalEffects[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
@@ -98,14 +98,21 @@
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++) {
+ // Create enhanced blood particles with variety
+ for (var p = 0; p < 10; p++) {
var bloodParticle = game.addChild(getFromPool('bloodParticle'));
- bloodParticle.reset(self.x + (Math.random() - 0.5) * 60, self.y - 100 + (Math.random() - 0.5) * 60);
+ var particleType = Math.random() < 0.3 ? 'large' : Math.random() < 0.5 ? 'small' : 'normal';
+ bloodParticle.reset(self.x + (Math.random() - 0.5) * 60, self.y - 100 + (Math.random() - 0.5) * 60, particleType);
bloodParticles.push(bloodParticle);
}
+ // Add spray particles for dramatic effect
+ for (var p = 0; p < 8; p++) {
+ var sprayParticle = game.addChild(getFromPool('bloodParticle'));
+ sprayParticle.reset(self.x + (Math.random() - 0.5) * 40, self.y - 120 + (Math.random() - 0.5) * 40, 'spray');
+ bloodParticles.push(sprayParticle);
+ }
// 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) {
@@ -965,24 +972,57 @@
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.particleType = 'normal'; // normal, large, small, spray
+ self.reset = function (startX, startY, type) {
self.x = startX;
self.y = startY;
- self.velocityX = (Math.random() - 0.5) * 8;
- self.velocityY = -Math.random() * 6 - 2;
- self.lifetime = 60;
+ self.particleType = type || 'normal';
+ // Different sizes, speeds, and lifetimes based on type
+ if (self.particleType === 'large') {
+ self.velocityX = (Math.random() - 0.5) * 6;
+ self.velocityY = -Math.random() * 4 - 1;
+ self.lifetime = 90; // Larger particles last longer
+ particleGraphics.scaleX = 1.5;
+ particleGraphics.scaleY = 1.5;
+ particleGraphics.tint = 0x8b0000; // Dark red
+ } else if (self.particleType === 'small') {
+ self.velocityX = (Math.random() - 0.5) * 12; // Faster small particles
+ self.velocityY = -Math.random() * 8 - 3;
+ self.lifetime = 30; // Short-lived
+ particleGraphics.scaleX = 0.5;
+ particleGraphics.scaleY = 0.5;
+ particleGraphics.tint = 0xff4444; // Bright red
+ } else if (self.particleType === 'spray') {
+ self.velocityX = (Math.random() - 0.5) * 16; // Very fast spray
+ self.velocityY = -Math.random() * 10 - 1;
+ self.lifetime = 45;
+ particleGraphics.scaleX = 0.7;
+ particleGraphics.scaleY = 0.7;
+ particleGraphics.tint = 0xcc2222; // Medium red
+ } else {
+ // Normal particles
+ self.velocityX = (Math.random() - 0.5) * 8;
+ self.velocityY = -Math.random() * 6 - 2;
+ self.lifetime = 60;
+ particleGraphics.scaleX = 1;
+ particleGraphics.scaleY = 1;
+ particleGraphics.tint = 0x8b0000;
+ }
particleGraphics.alpha = 1;
self.visible = true;
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
self.velocityY += self.gravity;
+ // Different fade patterns based on type
+ var maxLifetime = 60;
+ if (self.particleType === 'large') maxLifetime = 90;else if (self.particleType === 'small') maxLifetime = 30;else if (self.particleType === 'spray') maxLifetime = 45;
// Fade out over time
self.lifetime--;
- particleGraphics.alpha = self.lifetime / 60;
+ particleGraphics.alpha = self.lifetime / maxLifetime;
if (self.lifetime <= 0) {
// Remove from bloodParticles array
for (var i = bloodParticles.length - 1; i >= 0; i--) {
if (bloodParticles[i] === self) {
@@ -1070,8 +1110,70 @@
self.destroy();
};
return self;
});
+var DustCloud = Container.expand(function () {
+ var self = Container.call(this);
+ var dustGraphics = self.attachAsset('bloodParticle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ dustGraphics.tint = 0x8b7355; // Brown dust color
+ dustGraphics.scaleX = 0.8;
+ dustGraphics.scaleY = 0.8;
+ self.velocityX = 0;
+ self.velocityY = 0;
+ self.lifetime = 40; // Medium lifetime
+ self.expandRate = 0.02; // How fast dust expands
+ self.reset = function (startX, startY, direction) {
+ self.x = startX;
+ self.y = startY;
+ // Dust moves opposite to movement direction
+ var baseSpeed = 1 + Math.random() * 2;
+ if (direction === 'left') {
+ self.velocityX = baseSpeed;
+ } else if (direction === 'right') {
+ self.velocityX = -baseSpeed;
+ } else if (direction === 'up') {
+ self.velocityY = baseSpeed * 0.5;
+ } else if (direction === 'down') {
+ self.velocityY = -baseSpeed * 0.5;
+ } else {
+ // Random direction for general dust
+ var angle = Math.random() * Math.PI * 2;
+ self.velocityX = Math.cos(angle) * baseSpeed;
+ self.velocityY = Math.sin(angle) * baseSpeed;
+ }
+ self.velocityY -= Math.random() * 1; // Slight upward drift
+ dustGraphics.alpha = 0.6;
+ dustGraphics.scaleX = 0.4 + Math.random() * 0.4;
+ dustGraphics.scaleY = 0.4 + Math.random() * 0.4;
+ self.visible = true;
+ };
+ self.update = function () {
+ self.x += self.velocityX;
+ self.y += self.velocityY;
+ // Dust slows down and expands
+ self.velocityX *= 0.98;
+ self.velocityY *= 0.98;
+ dustGraphics.scaleX += self.expandRate;
+ dustGraphics.scaleY += self.expandRate;
+ // Fade out over time
+ self.lifetime--;
+ dustGraphics.alpha = self.lifetime / 40 * 0.6;
+ if (self.lifetime <= 0) {
+ // Remove from dustClouds array
+ for (var i = dustClouds.length - 1; i >= 0; i--) {
+ if (dustClouds[i] === self) {
+ dustClouds.splice(i, 1);
+ break;
+ }
+ }
+ returnToPool(self, 'dustCloud');
+ }
+ };
+ return self;
+});
var Enemy = Container.expand(function (enemyType) {
var self = Container.call(this);
self.enemyType = enemyType || 'basic';
// Choose asset based on enemy type
@@ -1261,15 +1363,23 @@
}, {
duration: 200,
easing: tween.easeOut
});
- // Create blood particles
- for (var p = 0; p < 12; p++) {
+ // Create enhanced blood particles with variety
+ for (var p = 0; p < 8; p++) {
var bloodParticle = game.addChild(getFromPool('bloodParticle'));
- bloodParticle.reset(self.x + (Math.random() - 0.5) * 40, self.y - 60 + (Math.random() - 0.5) * 40);
+ var particleType = Math.random() < 0.2 ? 'large' : Math.random() < 0.6 ? 'small' : 'normal';
+ bloodParticle.reset(self.x + (Math.random() - 0.5) * 40, self.y - 60 + (Math.random() - 0.5) * 40, particleType);
bloodParticles.push(bloodParticle);
}
+ // Add impact sparks when hit by knife
if (fromKnife) {
+ for (var s = 0; s < 6; s++) {
+ var spark = game.addChild(getFromPool('impactSpark'));
+ var sparkType = Math.random() < 0.3 ? 'bright' : Math.random() < 0.5 ? 'orange' : 'normal';
+ spark.reset(self.x + (Math.random() - 0.5) * 30, self.y - 70 + (Math.random() - 0.5) * 30, sparkType);
+ impactSparks.push(spark);
+ }
self.alerted = true;
self.alertedByKnife = true;
}
if (self.health <= 0) {
@@ -1626,8 +1736,14 @@
self.lastX = self.x;
self.lastY = self.y;
self.x = newX;
self.y = newY;
+ // Create dust clouds when moving (occasional)
+ if (Math.random() < 0.3) {
+ var dustCloud = game.addChild(getFromPool('dustCloud'));
+ dustCloud.reset(self.x + (Math.random() - 0.5) * 30, self.y - 10 + (Math.random() - 0.5) * 20, direction);
+ dustClouds.push(dustCloud);
+ }
// Start walking animation
if (!self.isWalking) {
self.isWalking = true;
self.startWalkAnimation();
@@ -1668,8 +1784,71 @@
}
};
return self;
});
+var ImpactSpark = Container.expand(function () {
+ var self = Container.call(this);
+ var sparkGraphics = self.attachAsset('bloodParticle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ sparkGraphics.tint = 0xffff44; // Yellow-white sparks
+ sparkGraphics.scaleX = 0.3;
+ sparkGraphics.scaleY = 0.3;
+ self.velocityX = 0;
+ self.velocityY = 0;
+ self.lifetime = 20; // Short-lived sparks
+ self.sparkType = 'normal'; // normal, bright, orange
+ self.reset = function (startX, startY, type) {
+ self.x = startX;
+ self.y = startY;
+ self.sparkType = type || 'normal';
+ // Random direction and speed for sparks
+ var angle = Math.random() * Math.PI * 2;
+ var speed = 3 + Math.random() * 5;
+ self.velocityX = Math.cos(angle) * speed;
+ self.velocityY = Math.sin(angle) * speed;
+ if (self.sparkType === 'bright') {
+ sparkGraphics.tint = 0xffffff; // White sparks
+ sparkGraphics.scaleX = 0.4;
+ sparkGraphics.scaleY = 0.4;
+ self.lifetime = 30;
+ } else if (self.sparkType === 'orange') {
+ sparkGraphics.tint = 0xff8844; // Orange sparks
+ sparkGraphics.scaleX = 0.2;
+ sparkGraphics.scaleY = 0.2;
+ self.lifetime = 15;
+ } else {
+ sparkGraphics.tint = 0xffff44; // Yellow sparks
+ sparkGraphics.scaleX = 0.3;
+ sparkGraphics.scaleY = 0.3;
+ self.lifetime = 20;
+ }
+ sparkGraphics.alpha = 1;
+ self.visible = true;
+ };
+ self.update = function () {
+ self.x += self.velocityX;
+ self.y += self.velocityY;
+ // Sparks slow down over time
+ self.velocityX *= 0.95;
+ self.velocityY *= 0.95;
+ // Fade out quickly
+ self.lifetime--;
+ sparkGraphics.alpha = self.lifetime / 20;
+ if (self.lifetime <= 0) {
+ // Remove from impactSparks array
+ for (var i = impactSparks.length - 1; i >= 0; i--) {
+ if (impactSparks[i] === self) {
+ impactSparks.splice(i, 1);
+ break;
+ }
+ }
+ returnToPool(self, 'impactSpark');
+ }
+ };
+ return self;
+});
var Knife = Container.expand(function () {
var self = Container.call(this);
var knifeGraphics = self.attachAsset('knife', {
anchorX: 0.5,
@@ -1758,8 +1937,96 @@
}
};
return self;
});
+var MagicalEffect = Container.expand(function () {
+ var self = Container.call(this);
+ var effectGraphics = self.attachAsset('bloodParticle', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ self.velocityX = 0;
+ self.velocityY = 0;
+ self.lifetime = 60;
+ self.effectType = 'sparkle'; // sparkle, healing, power, shield
+ self.rotationSpeed = 0;
+ self.reset = function (startX, startY, type) {
+ self.x = startX;
+ self.y = startY;
+ self.effectType = type || 'sparkle';
+ if (self.effectType === 'sparkle') {
+ effectGraphics.tint = 0xffff88; // Golden sparkle
+ effectGraphics.scaleX = 0.6;
+ effectGraphics.scaleY = 0.6;
+ var angle = Math.random() * Math.PI * 2;
+ var speed = 2 + Math.random() * 3;
+ self.velocityX = Math.cos(angle) * speed;
+ self.velocityY = Math.sin(angle) * speed - 2; // Float upward
+ self.rotationSpeed = (Math.random() - 0.5) * 0.2;
+ self.lifetime = 80;
+ } else if (self.effectType === 'healing') {
+ effectGraphics.tint = 0x44ff44; // Green healing
+ effectGraphics.scaleX = 0.8;
+ effectGraphics.scaleY = 0.8;
+ self.velocityX = (Math.random() - 0.5) * 2;
+ self.velocityY = -3 - Math.random() * 2; // Rise upward
+ self.rotationSpeed = 0.1;
+ self.lifetime = 60;
+ } else if (self.effectType === 'power') {
+ effectGraphics.tint = 0xff4488; // Purple power
+ effectGraphics.scaleX = 1.0;
+ effectGraphics.scaleY = 1.0;
+ self.velocityX = (Math.random() - 0.5) * 4;
+ self.velocityY = -1 - Math.random() * 3;
+ self.rotationSpeed = 0.15;
+ self.lifetime = 70;
+ } else if (self.effectType === 'shield') {
+ effectGraphics.tint = 0x44ddff; // Blue shield
+ effectGraphics.scaleX = 0.5;
+ effectGraphics.scaleY = 0.5;
+ var angle = Math.random() * Math.PI * 2;
+ var radius = 50 + Math.random() * 30;
+ self.velocityX = Math.cos(angle) * 2;
+ self.velocityY = Math.sin(angle) * 2;
+ self.rotationSpeed = 0.08;
+ self.lifetime = 90;
+ }
+ effectGraphics.alpha = 1;
+ self.visible = true;
+ };
+ self.update = function () {
+ self.x += self.velocityX;
+ self.y += self.velocityY;
+ effectGraphics.rotation += self.rotationSpeed;
+ // Different behaviors per effect type
+ if (self.effectType === 'sparkle') {
+ self.velocityY -= 0.05; // Continue floating up
+ } else if (self.effectType === 'healing') {
+ self.velocityX *= 0.99; // Slow down horizontally
+ } else if (self.effectType === 'power') {
+ // Pulsing effect
+ var pulseScale = 1.0 + Math.sin(LK.ticks * 0.3) * 0.2;
+ effectGraphics.scaleX = pulseScale;
+ effectGraphics.scaleY = pulseScale;
+ }
+ // Fade out over time
+ self.lifetime--;
+ var maxLifetime = 60;
+ if (self.effectType === 'sparkle') maxLifetime = 80;else if (self.effectType === 'power') maxLifetime = 70;else if (self.effectType === 'shield') maxLifetime = 90;
+ effectGraphics.alpha = self.lifetime / maxLifetime;
+ if (self.lifetime <= 0) {
+ // Remove from magicalEffects array
+ for (var i = magicalEffects.length - 1; i >= 0; i--) {
+ if (magicalEffects[i] === self) {
+ magicalEffects.splice(i, 1);
+ break;
+ }
+ }
+ returnToPool(self, 'magicalEffect');
+ }
+ };
+ return self;
+});
var Minion = Container.expand(function () {
var self = Container.call(this);
var minionGraphics = self.attachAsset('enemyFast', {
anchorX: 0.5,
@@ -1886,21 +2153,32 @@
powerupGraphics.scaleY = scale;
};
self.collect = function () {
LK.getSound('powerup').play();
+ // Create magical effects based on power-up type
+ var effectType = 'sparkle';
if (self.type === 'health') {
hero.heal();
+ effectType = 'healing';
} else if (self.type === 'invulnerable') {
hero.invulnerable = true;
+ effectType = 'shield';
LK.setTimeout(function () {
hero.invulnerable = false;
}, 5000);
} else if (self.type === 'damage') {
hero.damageBoost = true;
+ effectType = 'power';
LK.setTimeout(function () {
hero.damageBoost = false;
}, 5000);
}
+ // Create magical particle effects
+ for (var e = 0; e < 12; e++) {
+ var magicalEffect = game.addChild(getFromPool('magicalEffect'));
+ magicalEffect.reset(self.x + (Math.random() - 0.5) * 60, self.y + (Math.random() - 0.5) * 60, effectType);
+ magicalEffects.push(magicalEffect);
+ }
// Remove from powerups array
for (var i = powerups.length - 1; i >= 0; i--) {
if (powerups[i] === self) {
powerups.splice(i, 1);
@@ -2077,10 +2355,10 @@
/****
* Game Code
****/
-// Legacy Boss class for backward compatibility (delegates to BossType1)
// Game variables
+// Legacy Boss class for backward compatibility (delegates to BossType1)
var Boss = BossType1;
var screenShake = new ScreenShake();
// Helper function to trigger screen shake with different intensities
function triggerScreenShake(intensity, duration, easing) {
@@ -2095,8 +2373,11 @@
var knivesRemaining = 5;
var routeEffects = [];
var bloodParticles = [];
var bossProjectiles = [];
+var impactSparks = [];
+var dustClouds = [];
+var magicalEffects = [];
var currentDungeon = 1;
var dungeonComplete = false;
var hearts = [];
var healthPotions = [];
@@ -2104,9 +2385,12 @@
var objectPools = {
bloodParticle: [],
bossProjectile: [],
coin: [],
- healthPotion: []
+ healthPotion: [],
+ impactSpark: [],
+ dustCloud: [],
+ magicalEffect: []
};
// Spatial partitioning system
var spatialGrid = {
cellSize: 200,
@@ -2156,8 +2440,14 @@
} else if (type === 'coin') {
return new Coin();
} else if (type === 'healthPotion') {
return new HealthPotion();
+ } else if (type === 'impactSpark') {
+ return new ImpactSpark();
+ } else if (type === 'dustCloud') {
+ return new DustCloud();
+ } else if (type === 'magicalEffect') {
+ return new MagicalEffect();
}
return null;
}
function returnToPool(obj, type) {
@@ -2512,8 +2802,23 @@
for (var i = bloodParticles.length - 1; i >= 0; i--) {
bloodParticles[i].destroy();
}
bloodParticles = [];
+ // Clear existing impact sparks
+ for (var i = impactSparks.length - 1; i >= 0; i--) {
+ impactSparks[i].destroy();
+ }
+ impactSparks = [];
+ // Clear existing dust clouds
+ for (var i = dustClouds.length - 1; i >= 0; i--) {
+ dustClouds[i].destroy();
+ }
+ dustClouds = [];
+ // Clear existing magical effects
+ for (var i = magicalEffects.length - 1; i >= 0; i--) {
+ magicalEffects[i].destroy();
+ }
+ magicalEffects = [];
// Clear existing boss projectiles
for (var i = bossProjectiles.length - 1; i >= 0; i--) {
bossProjectiles[i].destroy();
}
@@ -3010,8 +3315,23 @@
if (isVisible(bloodParticles[i], 50)) {
bloodParticles[i].update();
}
}
+ for (var i = impactSparks.length - 1; i >= 0; i--) {
+ if (isVisible(impactSparks[i], 50)) {
+ impactSparks[i].update();
+ }
+ }
+ for (var i = dustClouds.length - 1; i >= 0; i--) {
+ if (isVisible(dustClouds[i], 100)) {
+ dustClouds[i].update();
+ }
+ }
+ for (var i = magicalEffects.length - 1; i >= 0; i--) {
+ if (isVisible(magicalEffects[i], 100)) {
+ magicalEffects[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