/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ // Chest class var Chest = Container.expand(function () { var self = Container.call(this); // Attach chest asset (brown box) var chestSprite = self.attachAsset('chest', { anchorX: 0.5, anchorY: 0.5 }); self.width = chestSprite.width; self.height = chestSprite.height; self.opened = false; return self; }); // Dark Mage - Room 3 var DarkMage = Container.expand(function () { var self = Container.call(this); // Use mage asset instead of monster asset var mageSprite = self.attachAsset('mage', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.1, scaleY: 1.1 }); // Set larger hitbox for easier collision detection self.width = mageSprite.width * 0.4; self.height = mageSprite.height * 0.4; // Monster health system self.maxHealth = 2; self.health = self.maxHealth; self.speed = 0.6; // Create heart-based health display self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0x654321; heart.x = startX + h * heartSpacing; heart.y = -mageSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } // Update heart display self.updateHealthBar = function () { // Update heart visibility based on current health for (var h = 0; h < self.hearts.length; h++) { if (h < self.health) { self.hearts[h].visible = true; self.hearts[h].tint = 0x654321; // Brown for full hearts self.hearts[h].alpha = 1.0; } else { self.hearts[h].visible = true; self.hearts[h].tint = 0x444444; // Dark gray for empty hearts self.hearts[h].alpha = 0.5; } } }; // Take damage function with animation self.takeDamage = function () { self.health -= 1; self.updateHealthBar(); // Flash damage animation tween(mageSprite, { tint: 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(mageSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Shake animation var originalX = self.x; tween(self, { x: originalX + 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX - 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX }, { duration: 50 }); } }); } }); }; // Movement direction self.vx = 0; self.vy = 0; // Set random direction self.setRandomDirection = function () { var angle = Math.random() * Math.PI * 2; self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); // Shooting timer self.shootTimer = 0; self.shootInterval = 90; // Shoot every 1.5 seconds at 60fps self.shootCooldown = 0; // Cooldown timer after shooting self.cooldownDuration = 120; // 2 seconds at 60fps // Called every tick self.update = function () { // Check if hero is close to this monster FIRST (within encounter distance) var dx = hero.x - self.x; var dy = hero.y - self.y; var distToHero = Math.sqrt(dx * dx + dy * dy); // If hero is within encounter distance, trigger freeze and stop moving if (distToHero < 120) { // Reduced from 200 to 120 for less aggressive encounter if (!gameFrozen) { // Trigger freeze effect gameFrozen = true; freezeTimer = FREEZE_DURATION; freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME); // Apply freeze visual effect to all characters tween(hero, { tint: 0x8888ff }, { duration: 300 }); tween(self, { tint: 0x8888ff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { if (monsters[m] !== self) { tween(monsters[m], { tint: 0x8888ff }, { duration: 300 }); } } } // Monster stays still when encountering hero return; } // Don't move if game is frozen if (gameFrozen) { return; } // Update cooldown timer if (self.shootCooldown > 0) { self.shootCooldown--; } // Mage shooting logic - shoot projectiles at hero (only if cooldown is 0) if (self.shootCooldown <= 0) { self.shootTimer++; if (self.shootTimer >= self.shootInterval) { self.shootTimer = 0; self.shootCooldown = self.cooldownDuration; // Start 2-second cooldown // Create magic ball projectile var ball = new MageBall(); ball.x = self.x; ball.y = self.y; ball.setDirection(hero.x, hero.y); game.addChild(ball); mageBalls.push(ball); // Visual effect when shooting tween(mageSprite, { scaleX: 1.3, scaleY: 1.3 }, { duration: 200, onFinish: function onFinish() { tween(mageSprite, { scaleX: 1.1, scaleY: 1.1 }, { duration: 200 }); } }); } } else { // Reset shoot timer while on cooldown to prevent immediate shooting after cooldown ends self.shootTimer = 0; } self.x += self.vx; self.y += self.vy; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; // Update hearts for new health self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0x654321; heart.x = startX + h * heartSpacing; heart.y = -mageSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } self.updateHealthBar(); return self; }); // Door class (for next room) var Door = Container.expand(function () { var self = Container.call(this); // Attach door asset (purple box) var doorSprite = self.attachAsset('door', { anchorX: 0.5, anchorY: 0.5 }); self.width = doorSprite.width; self.height = doorSprite.height; return self; }); // Dragon Lord - Final Boss Room 5 var DragonLord = Container.expand(function () { var self = Container.call(this); // Use dragon asset instead of monster asset var dragonSprite = self.attachAsset('dragon', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.0, scaleY: 2.0 }); // Set larger hitbox for easier collision detection self.width = dragonSprite.width * 0.4; self.height = dragonSprite.height * 0.4; // Monster health system self.maxHealth = 3; self.health = self.maxHealth; self.speed = 0.2; // Create heart-based health display self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -dragonSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } // Update heart display self.updateHealthBar = function () { // Update heart visibility based on current health for (var h = 0; h < self.hearts.length; h++) { if (h < self.health) { self.hearts[h].visible = true; self.hearts[h].tint = 0xFF1744; // Red for full hearts self.hearts[h].alpha = 1.0; } else { self.hearts[h].visible = true; self.hearts[h].tint = 0x444444; // Dark gray for empty hearts self.hearts[h].alpha = 0.5; } } }; // Take damage function with animation self.takeDamage = function () { self.health -= 1; self.updateHealthBar(); // Flash damage animation tween(dragonSprite, { tint: 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(dragonSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Shake animation var originalX = self.x; tween(self, { x: originalX + 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX - 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX }, { duration: 50 }); } }); } }); }; // Movement direction self.vx = 0; self.vy = 0; // Set random direction self.setRandomDirection = function () { var angle = Math.random() * Math.PI * 2; self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); // Called every tick self.update = function () { // Check if hero is close to this monster FIRST (within encounter distance) var dx = hero.x - self.x; var dy = hero.y - self.y; var distToHero = Math.sqrt(dx * dx + dy * dy); // If hero is within encounter distance, trigger freeze and stop moving if (distToHero < 120) { // Reduced from 200 to 120 for less aggressive encounter if (!gameFrozen) { // Trigger freeze effect gameFrozen = true; freezeTimer = FREEZE_DURATION; freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME); // Apply freeze visual effect to all characters tween(hero, { tint: 0x8888ff }, { duration: 300 }); tween(self, { tint: 0x8888ff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { if (monsters[m] !== self) { tween(monsters[m], { tint: 0x8888ff }, { duration: 300 }); } } } // Monster stays still when encountering hero return; } // Don't move if game is frozen if (gameFrozen) { return; } self.x += self.vx; self.y += self.vy; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; // Update hearts for new health self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -dragonSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } self.updateHealthBar(); return self; }); // Fire Demon - Room 4 var FireDemon = Container.expand(function () { var self = Container.call(this); // Use demon asset instead of monster asset var demonSprite = self.attachAsset('demon', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.6, scaleY: 1.6 }); // Set larger hitbox for easier collision detection self.width = demonSprite.width * 0.4; self.height = demonSprite.height * 0.4; // Monster health system self.maxHealth = 4; self.health = self.maxHealth; self.speed = 0.3; // Create heart-based health display self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -demonSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } // Update heart display self.updateHealthBar = function () { // Update heart visibility based on current health for (var h = 0; h < self.hearts.length; h++) { if (h < self.health) { self.hearts[h].visible = true; self.hearts[h].tint = 0xFF1744; // Red for full hearts self.hearts[h].alpha = 1.0; } else { self.hearts[h].visible = true; self.hearts[h].tint = 0x444444; // Dark gray for empty hearts self.hearts[h].alpha = 0.5; } } }; // Take damage function with animation self.takeDamage = function () { self.health -= 1; self.updateHealthBar(); // Flash damage animation tween(demonSprite, { tint: 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(demonSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Shake animation var originalX = self.x; tween(self, { x: originalX + 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX - 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX }, { duration: 50 }); } }); } }); }; // Movement direction self.vx = 0; self.vy = 0; // Set random direction self.setRandomDirection = function () { var angle = Math.random() * Math.PI * 2; self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); // Fire shooting timer self.fireTimer = 0; self.fireInterval = 180; // Shoot fire every 3 seconds self.safeZoneTimer = 0; self.safeZoneDuration = 300; // 5 seconds safe zone self.currentSafeZone = null; // Called every tick self.update = function () { // Check if hero is close to this monster FIRST (within encounter distance) var dx = hero.x - self.x; var dy = hero.y - self.y; var distToHero = Math.sqrt(dx * dx + dy * dy); // If hero is within encounter distance, trigger freeze and stop moving if (distToHero < 120) { // Reduced from 200 to 120 for less aggressive encounter if (!gameFrozen) { // Trigger freeze effect gameFrozen = true; freezeTimer = FREEZE_DURATION; freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME); // Apply freeze visual effect to all characters tween(hero, { tint: 0x8888ff }, { duration: 300 }); tween(self, { tint: 0x8888ff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { if (monsters[m] !== self) { tween(monsters[m], { tint: 0x8888ff }, { duration: 300 }); } } } // Monster stays still when encountering hero return; } // Don't move if game is frozen if (gameFrozen) { return; } // Fire demon shooting logic - alternates between ground fire and stun orbs self.fireTimer++; if (self.fireTimer >= self.fireInterval) { self.fireTimer = 0; // Alternate between ground fire and stun orb attacks if (Math.random() < 0.4) { // 40% chance for stun orb // Create stun orb projectile var stunOrb = new StunOrb(); stunOrb.x = self.x; stunOrb.y = self.y; stunOrb.setDirection(hero.x, hero.y); game.addChild(stunOrb); stunOrbs.push(stunOrb); // Visual effect when shooting stun orb tween(demonSprite, { tint: 0x9932CC }, { duration: 300, onFinish: function onFinish() { tween(demonSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); } else { // Create random safe zone self.currentSafeZone = { x: roomBounds.x + 200 + Math.random() * (roomBounds.width - 400), y: roomBounds.y + 200 + Math.random() * (roomBounds.height - 400), radius: 150 }; self.safeZoneTimer = self.safeZoneDuration; // Create ground fires across the room for (var f = 0; f < 15; f++) { var fireX = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200); var fireY = roomBounds.y + 200 + Math.random() * (roomBounds.height - 300); // Skip if in safe zone var dx = fireX - self.currentSafeZone.x; var dy = fireY - self.currentSafeZone.y; var distToSafe = Math.sqrt(dx * dx + dy * dy); if (distToSafe < self.currentSafeZone.radius) continue; var groundFire = new GroundFire(); groundFire.x = fireX; groundFire.y = fireY; game.addChild(groundFire); groundFires.push(groundFire); } // Visual indicator for safe zone var safeIndicator = LK.getAsset('trap', { width: self.currentSafeZone.radius * 2, height: self.currentSafeZone.radius * 2, anchorX: 0.5, anchorY: 0.5 }); safeIndicator.tint = 0x00FF00; // Green safe zone safeIndicator.alpha = 0.3; safeIndicator.x = self.currentSafeZone.x; safeIndicator.y = self.currentSafeZone.y; game.addChild(safeIndicator); self.safeIndicator = safeIndicator; } // End ground fire attack } // Update safe zone timer if (self.safeZoneTimer > 0) { self.safeZoneTimer--; // Check if hero is in safe zone if (self.currentSafeZone) { var dx = hero.x - self.currentSafeZone.x; var dy = hero.y - self.currentSafeZone.y; var distToSafe = Math.sqrt(dx * dx + dy * dy); if (distToSafe < self.currentSafeZone.radius) { // Hero is in safe zone, damage demon self.takeDamage(); // Flash effect to show demon taking damage LK.effects.flashObject(self, 0xFF4444, 300); if (self.health <= 0) { // Play win sound for boss defeat LK.playMusic('win'); // Remove demon for (var m = 0; m < monsters.length; m++) { if (monsters[m] === self) { monsters.splice(m, 1); break; } } monstersDefeated++; updateScoreDisplay(); self.destroy(); } } } } else { // Clean up safe zone indicator if (self.safeIndicator) { self.safeIndicator.destroy(); self.safeIndicator = null; } self.currentSafeZone = null; } self.x += self.vx; self.y += self.vy; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; // Update hearts for new health self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -demonSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } self.updateHealthBar(); return self; }); // Ground Fire class for Fire Demon attacks var GroundFire = Container.expand(function () { var self = Container.call(this); // Create fire visual using trap asset var fireSprite = self.attachAsset('trap', { anchorX: 0.5, anchorY: 0.5, width: 80, height: 80 }); fireSprite.tint = 0xFF4400; // Orange-red fire color self.width = fireSprite.width; self.height = fireSprite.height; // Fire properties self.lifetime = 120; // 2 seconds at 60fps self.damageDealt = false; // Update fire animation self.update = function () { self.lifetime--; // Animate fire flickering fireSprite.rotation += 0.3; fireSprite.scaleX = 0.8 + Math.sin(LK.ticks * 0.2) * 0.2; fireSprite.scaleY = 0.8 + Math.cos(LK.ticks * 0.15) * 0.3; // Fade out near end of lifetime if (self.lifetime < 30) { fireSprite.alpha = self.lifetime / 30; } }; return self; }); // Hero class var Hero = Container.expand(function () { var self = Container.call(this); // Attach hero asset (red box) var heroSprite = self.attachAsset('hero', { anchorX: 0.5, anchorY: 0.5 }); self.width = heroSprite.width; self.height = heroSprite.height; // Attach sword to hero var swordSprite = self.attachAsset('sword', { anchorX: 0.5, anchorY: 0.5 }); swordSprite.x = -heroSprite.width * 0.3; // Position sword to the left of hero swordSprite.y = 0; // Center vertically with hero swordSprite.rotation = -Math.PI / 4; // Angle the sword pointing left // Hero stats self.maxHealth = 3; self.health = self.maxHealth; self.invincible = false; self.invincibleTimer = 0; // Create health bar above character // Health bar border (white outline) self.healthBarBorder = LK.getAsset('trap', { width: 200, height: 40, anchorX: 0.5, anchorY: 0.5 }); self.healthBarBorder.tint = 0xFFFFFF; // White border self.healthBarBorder.x = 0; self.healthBarBorder.y = -heroSprite.height / 2 - 50; // Position above character self.addChild(self.healthBarBorder); // Health bar background (dark) self.healthBarBackground = LK.getAsset('trap', { width: 180, height: 24, anchorX: 0.5, anchorY: 0.5 }); self.healthBarBackground.tint = 0x222222; // Dark gray background self.healthBarBackground.x = 0; self.healthBarBackground.y = -heroSprite.height / 2 - 50; // Position above character self.addChild(self.healthBarBackground); // Health bar fill (red color) self.healthBarFill = LK.getAsset('trap', { width: 180, height: 24, anchorX: 0.0, anchorY: 0.5 }); self.healthBarFill.tint = 0xFF0000; // Red for health bar self.healthBarFill.x = -90; // Left-aligned within background self.healthBarFill.y = -heroSprite.height / 2 - 50; // Position above character self.addChild(self.healthBarFill); // Update health bar display self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; self.healthBarFill.scaleX = healthPercent; // Keep health bar red color self.healthBarFill.tint = 0xFF0000; // Always red // Add pulsing effect when health is critical if (healthPercent <= 0.25) { tween(self.healthBarFill, { alpha: 0.5 }, { duration: 500, loop: true, pingpong: true }); } else { tween.stop(self.healthBarFill, { alpha: true }); self.healthBarFill.alpha = 1.0; } }; // Initialize health bar self.updateHealthBar(); // Flash when hit self.flash = function () { tween(heroSprite, { tint: 0xffffff }, { duration: 100, onFinish: function onFinish() { tween(heroSprite, { tint: 0xd83318 }, { duration: 200 }); } }); }; // Take damage self.takeDamage = function () { if (self.invincible) return; self.health -= 1; self.flash(); self.invincible = true; self.invincibleTimer = 60; // 1 second at 60fps LK.effects.flashObject(self, 0xff0000, 300); self.updateHealthBar(); // Create blood flow effect for (var b = 0; b < 8; b++) { var bloodDrop = LK.getAsset('trap', { width: 12 + Math.random() * 8, height: 12 + Math.random() * 8, anchorX: 0.5, anchorY: 0.5 }); bloodDrop.tint = 0xAA0000; // Dark red blood color bloodDrop.x = self.x + (Math.random() - 0.5) * 60; // Random spread around hero bloodDrop.y = self.y + (Math.random() - 0.5) * 40; bloodDrop.alpha = 0.8 + Math.random() * 0.2; game.addChild(bloodDrop); // Animate blood drop falling and fading var fallDistance = 80 + Math.random() * 120; var fallDuration = 800 + Math.random() * 400; tween(bloodDrop, { y: bloodDrop.y + fallDistance, alpha: 0, scaleX: 0.3, scaleY: 0.3 }, { duration: fallDuration, easing: tween.easeIn, onFinish: function onFinish() { bloodDrop.destroy(); } }); } if (self.health <= 0) { // Play scream sound when hero dies LK.getSound('scream').play(); LK.effects.flashScreen(0xff0000, 800); // Create dungeon-themed game over overlay var dungeonGameOverOverlay = new Container(); dungeonGameOverOverlay.zIndex = 10000; // ensure on top // Dark background var background = LK.getAsset('trap', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0 }); background.tint = 0x000000; background.alpha = 0.8; dungeonGameOverOverlay.addChild(background); // Main message var gameOverText = new Text2("You couldn't pass the dungeon...", { size: 120, fill: 0xFF4444, font: "Impact, Arial Black, Tahoma" }); gameOverText.anchor.set(0.5, 0.5); gameOverText.x = 2048 / 2; gameOverText.y = 1200; dungeonGameOverOverlay.addChild(gameOverText); // Subtitle var subtitleText = new Text2("The darkness consumed you in Room " + currentRoom, { size: 80, fill: 0x888888 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 2048 / 2; subtitleText.y = 1400; dungeonGameOverOverlay.addChild(subtitleText); // Score display var finalScoreText = new Text2("Monsters Defeated: " + monstersDefeated, { size: 70, fill: 0xFFFF88 }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 1600; dungeonGameOverOverlay.addChild(finalScoreText); game.addChild(dungeonGameOverOverlay); // Auto restart after 3 seconds LK.setTimeout(function () { LK.showGameOver(); }, 3000); } }; // Heal self.heal = function () { if (self.health < self.maxHealth) { self.health += 1; self.updateHealthBar(); LK.effects.flashObject(self, 0x83de44, 300); } }; // Called every tick self.update = function () { if (self.invincible) { self.invincibleTimer--; if (self.invincibleTimer <= 0) { self.invincible = false; } } }; return self; }); /**** * Asset Initialization ****/ // Hero: red box // Monster: green ellipse // Trap: yellow box // Treasure: blue ellipse // Door: purple box // Key class var Key = Container.expand(function () { var self = Container.call(this); // Attach key asset (gold ellipse) var keySprite = self.attachAsset('key', { anchorX: 0.5, anchorY: 0.5 }); self.width = keySprite.width; self.height = keySprite.height; return self; }); // Mage Ball projectile class var MageBall = Container.expand(function () { var self = Container.call(this); // Create ball visual using trap asset var ballSprite = self.attachAsset('trap', { anchorX: 0.5, anchorY: 0.5, width: 60, height: 60 }); ballSprite.tint = 0x8A2BE2; // Purple magic ball self.width = ballSprite.width; self.height = ballSprite.height; // Ball properties self.vx = 0; self.vy = 0; self.speed = 4; self.lifetime = 300; // 5 seconds at 60fps // Set direction toward hero self.setDirection = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.vx = dx / dist * self.speed; self.vy = dy / dist * self.speed; } }; // Update ball movement self.update = function () { self.x += self.vx; self.y += self.vy; self.lifetime--; // Add magical sparkle effect ballSprite.rotation += 0.2; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; return self; }); // Base Monster class var Monster = Container.expand(function () { var self = Container.call(this); // Attach monster asset (green ellipse) var monsterSprite = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Set larger hitbox for easier collision detection self.width = monsterSprite.width * 0.4; self.height = monsterSprite.height * 0.4; // Monster health system self.maxHealth = 1; self.health = self.maxHealth; // Create heart-based health display self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; // Red heart color heart.x = startX + h * heartSpacing; heart.y = -monsterSprite.height / 2 - 40; // Position above monster self.addChild(heart); self.hearts.push(heart); } // Update heart display self.updateHealthBar = function () { // Update heart visibility based on current health for (var h = 0; h < self.hearts.length; h++) { if (h < self.health) { self.hearts[h].visible = true; self.hearts[h].tint = 0xFF1744; // Red for full hearts self.hearts[h].alpha = 1.0; } else { self.hearts[h].visible = true; self.hearts[h].tint = 0x444444; // Dark gray for empty hearts self.hearts[h].alpha = 0.5; } } }; // Initialize heart display self.updateHealthBar(); // Take damage function with animation self.takeDamage = function () { self.health -= 1; self.updateHealthBar(); // Flash damage animation tween(monsterSprite, { tint: 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(monsterSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Shake animation var originalX = self.x; tween(self, { x: originalX + 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX - 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX }, { duration: 50 }); } }); } }); }; // Movement direction self.vx = 0; self.vy = 0; self.speed = 0.3 + Math.random() * 0.4; // Reduced speed from 1-2 to 0.3-0.7 // Set random direction self.setRandomDirection = function () { var angle = Math.random() * Math.PI * 2; self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); // Called every tick self.update = function () { // Check if hero is close to this monster FIRST (within encounter distance) var dx = hero.x - self.x; var dy = hero.y - self.y; var distToHero = Math.sqrt(dx * dx + dy * dy); // If hero is within encounter distance, trigger freeze and stop moving if (distToHero < 120) { // Reduced from 200 to 120 for less aggressive encounter if (!gameFrozen) { // Trigger freeze effect gameFrozen = true; freezeTimer = FREEZE_DURATION; freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME); // Apply freeze visual effect to all characters tween(hero, { tint: 0x8888ff }, { duration: 300 }); tween(self, { tint: 0x8888ff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { if (monsters[m] !== self) { tween(monsters[m], { tint: 0x8888ff }, { duration: 300 }); } } } // Monster stays still when encountering hero return; } // Don't move if game is frozen if (gameFrozen) { return; } self.x += self.vx; self.y += self.vy; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; return self; }); // NPC Wizard class - appears in first room to wish good luck var NPCWizard = Container.expand(function () { var self = Container.call(this); // Attach wizard sprite var wizardSprite = self.attachAsset('wizard', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); self.width = wizardSprite.width; self.height = wizardSprite.height; // Speech bubble properties self.showingSpeech = false; self.speechBubble = null; self.speechText = null; // Show speech bubble with good luck message self.showSpeech = function () { if (self.showingSpeech) return; self.showingSpeech = true; // Create speech bubble background self.speechBubble = LK.getAsset('door', { width: 400, height: 180, anchorX: 0.5, anchorY: 1.0 }); self.speechBubble.tint = 0xFFFFFF; self.speechBubble.alpha = 0.9; self.speechBubble.x = 0; self.speechBubble.y = -wizardSprite.height / 2 - 20; self.addChild(self.speechBubble); // Create speech text self.speechText = new Text2("Good luck in the dungeon,\nbrave adventurer!", { size: 45, fill: 0xFFFFFF, font: "Impact, Arial Black, Tahoma" }); self.speechText.anchor.set(0.5, 0.5); self.speechText.x = 0; self.speechText.y = -wizardSprite.height / 2 - 110; self.addChild(self.speechText); // Auto-hide speech after 4 seconds LK.setTimeout(function () { self.hideSpeech(); }, 4000); }; // Hide speech bubble self.hideSpeech = function () { if (!self.showingSpeech) return; self.showingSpeech = false; if (self.speechBubble) { self.speechBubble.destroy(); self.speechBubble = null; } if (self.speechText) { self.speechText.destroy(); self.speechText = null; } }; return self; }); // Orc Warrior - Room 2 var OrcWarrior = Container.expand(function () { var self = Container.call(this); // Use orc asset instead of monster asset var orcSprite = self.attachAsset('orc', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.4, scaleY: 1.4 }); // Set larger hitbox for easier collision detection self.width = orcSprite.width * 0.4; self.height = orcSprite.height * 0.4; // Monster health system self.maxHealth = 2; self.health = self.maxHealth; self.speed = 0.4; // Create heart-based health display self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -orcSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } // Update heart display self.updateHealthBar = function () { // Update heart visibility based on current health for (var h = 0; h < self.hearts.length; h++) { if (h < self.health) { self.hearts[h].visible = true; self.hearts[h].tint = 0xFF1744; // Red for full hearts self.hearts[h].alpha = 1.0; } else { self.hearts[h].visible = true; self.hearts[h].tint = 0x444444; // Dark gray for empty hearts self.hearts[h].alpha = 0.5; } } }; // Take damage function with animation self.takeDamage = function () { self.health -= 1; self.updateHealthBar(); // Flash damage animation tween(orcSprite, { tint: 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(orcSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Shake animation var originalX = self.x; tween(self, { x: originalX + 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX - 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX }, { duration: 50 }); } }); } }); }; // Movement direction self.vx = 0; self.vy = 0; // Set random direction self.setRandomDirection = function () { var angle = Math.random() * Math.PI * 2; self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); // Called every tick self.update = function () { // Check if hero is close to this monster FIRST (within encounter distance) var dx = hero.x - self.x; var dy = hero.y - self.y; var distToHero = Math.sqrt(dx * dx + dy * dy); // If hero is within encounter distance, trigger freeze and stop moving if (distToHero < 120) { // Reduced from 200 to 120 for less aggressive encounter if (!gameFrozen) { // Trigger freeze effect gameFrozen = true; freezeTimer = FREEZE_DURATION; freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME); // Apply freeze visual effect to all characters tween(hero, { tint: 0x8888ff }, { duration: 300 }); tween(self, { tint: 0x8888ff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { if (monsters[m] !== self) { tween(monsters[m], { tint: 0x8888ff }, { duration: 300 }); } } } // Monster stays still when encountering hero return; } // Don't move if game is frozen if (gameFrozen) { return; } self.x += self.vx; self.y += self.vy; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; // Update hearts for new health self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -orcSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } self.updateHealthBar(); return self; }); // Skeleton Boss - Room 1 var SkeletonBoss = Container.expand(function () { var self = Container.call(this); // Use skeleton asset instead of monster asset var skeletonSprite = self.attachAsset('skeleton', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2 }); // Set larger hitbox for easier collision detection self.width = skeletonSprite.width * 0.4; self.height = skeletonSprite.height * 0.4; // Monster health system self.maxHealth = 1; self.health = self.maxHealth; self.speed = 0.5; // Create heart-based health display self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -skeletonSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } // Update heart display self.updateHealthBar = function () { // Update heart visibility based on current health for (var h = 0; h < self.hearts.length; h++) { if (h < self.health) { self.hearts[h].visible = true; self.hearts[h].tint = 0xFF1744; // Red for full hearts self.hearts[h].alpha = 1.0; } else { self.hearts[h].visible = true; self.hearts[h].tint = 0x444444; // Dark gray for empty hearts self.hearts[h].alpha = 0.5; } } }; // Take damage function with animation self.takeDamage = function () { self.health -= 1; self.updateHealthBar(); // Flash damage animation tween(skeletonSprite, { tint: 0xFF0000 }, { duration: 150, onFinish: function onFinish() { tween(skeletonSprite, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Shake animation var originalX = self.x; tween(self, { x: originalX + 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX - 15 }, { duration: 50, onFinish: function onFinish() { tween(self, { x: originalX }, { duration: 50 }); } }); } }); }; // Movement direction self.vx = 0; self.vy = 0; // Set random direction self.setRandomDirection = function () { var angle = Math.random() * Math.PI * 2; self.vx = Math.cos(angle) * self.speed; self.vy = Math.sin(angle) * self.speed; }; self.setRandomDirection(); // Shooting timer self.shootTimer = 0; self.shootInterval = 180; // Shoot every 3 seconds at 60fps (reduced frequency) self.shootCooldown = 0; // Cooldown timer after shooting self.cooldownDuration = 120; // 2 seconds at 60fps // Called every tick self.update = function () { // Check if hero is close to this monster FIRST (within encounter distance) var dx = hero.x - self.x; var dy = hero.y - self.y; var distToHero = Math.sqrt(dx * dx + dy * dy); // If hero is within encounter distance, trigger freeze and stop moving if (distToHero < 120) { // Reduced from 200 to 120 for less aggressive encounter if (!gameFrozen) { // Trigger freeze effect gameFrozen = true; freezeTimer = FREEZE_DURATION; freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME); // Apply freeze visual effect to all characters tween(hero, { tint: 0x8888ff }, { duration: 300 }); tween(self, { tint: 0x8888ff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { if (monsters[m] !== self) { tween(monsters[m], { tint: 0x8888ff }, { duration: 300 }); } } } // Monster stays still when encountering hero return; } // Don't move if game is frozen if (gameFrozen) { return; } // Update cooldown timer if (self.shootCooldown > 0) { self.shootCooldown--; } // Skeleton boss no longer shoots red projectiles - shooting behavior disabled if (self.shootCooldown <= 0) { self.shootTimer++; if (self.shootTimer >= self.shootInterval) { self.shootTimer = 0; self.shootCooldown = self.cooldownDuration; // Start 2-second cooldown // Stone projectile creation removed - boss no longer shoots // Visual effect when would have shot tween(skeletonSprite, { scaleX: 1.4, scaleY: 1.4 }, { duration: 250, onFinish: function onFinish() { tween(skeletonSprite, { scaleX: 1.2, scaleY: 1.2 }, { duration: 250 }); } }); } } self.x += self.vx; self.y += self.vy; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; // Skeleton stats self.maxHealth = 1; self.health = self.maxHealth; self.speed = 0.5; // Update hearts for new health self.hearts = []; var heartSize = 30; var heartSpacing = 35; var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize; var startX = -totalHeartsWidth / 2; for (var h = 0; h < self.maxHealth; h++) { var heart = LK.getAsset('trap', { width: heartSize, height: heartSize, anchorX: 0.5, anchorY: 0.5 }); heart.tint = 0xFF1744; heart.x = startX + h * heartSpacing; heart.y = -skeletonSprite.height / 2 - 40; self.addChild(heart); self.hearts.push(heart); } self.updateHealthBar(); return self; }); // Stone projectile class for skeleton boss var SkeletonStone = Container.expand(function () { var self = Container.call(this); // Create stone visual using trap asset var stoneSprite = self.attachAsset('trap', { anchorX: 0.5, anchorY: 0.5, width: 50, height: 50 }); stoneSprite.tint = 0x555555; // Gray stone color self.width = stoneSprite.width; self.height = stoneSprite.height; // Stone properties self.vx = 0; self.vy = 0; self.speed = 3.5; self.lifetime = 350; // Slightly longer than mage balls // Set direction toward hero self.setDirection = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.vx = dx / dist * self.speed; self.vy = dy / dist * self.speed; } }; // Update stone movement self.update = function () { self.x += self.vx; self.y += self.vy; self.lifetime--; // Add stone spinning effect stoneSprite.rotation += 0.15; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; return self; }); // Stun Orb projectile class for Fire Demon stun attacks var StunOrb = Container.expand(function () { var self = Container.call(this); // Create orb visual using trap asset var orbSprite = self.attachAsset('trap', { anchorX: 0.5, anchorY: 0.5, width: 70, height: 70 }); orbSprite.tint = 0x9932CC; // Dark purple stun orb self.width = orbSprite.width; self.height = orbSprite.height; // Orb properties self.vx = 0; self.vy = 0; self.speed = 3; self.lifetime = 400; // ~6.7 seconds at 60fps // Set direction toward hero self.setDirection = function (targetX, targetY) { var dx = targetX - self.x; var dy = targetY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { self.vx = dx / dist * self.speed; self.vy = dy / dist * self.speed; } }; // Update orb movement self.update = function () { self.x += self.vx; self.y += self.vy; self.lifetime--; // Add magical pulsing effect orbSprite.rotation += 0.2; orbSprite.scaleX = 0.9 + Math.sin(LK.ticks * 0.3) * 0.1; orbSprite.scaleY = 0.9 + Math.cos(LK.ticks * 0.25) * 0.1; // Bounce off room walls if (self.x < roomBounds.x + self.width / 2) { self.x = roomBounds.x + self.width / 2; self.vx *= -1; } if (self.x > roomBounds.x + roomBounds.width - self.width / 2) { self.x = roomBounds.x + roomBounds.width - self.width / 2; self.vx *= -1; } if (self.y < roomBounds.y + self.height / 2) { self.y = roomBounds.y + self.height / 2; self.vy *= -1; } if (self.y > roomBounds.y + roomBounds.height - self.height / 2) { self.y = roomBounds.y + roomBounds.height - self.height / 2; self.vy *= -1; } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x2C1810 }); /**** * Game Code ****/ // Create dungeon floor texture with pixel-by-pixel stone pattern var dungeonFloor = new Container(); var stoneColors = [0x4A4A4A, 0x3A3A3A, 0x5A5A5A, 0x2A2A2A, 0x6A6A6A]; // Various gray stone colors var mossColors = [0x2D4A2D, 0x1F3A1F, 0x3D5A3D]; // Mossy accents var dirtColors = [0x4A3A2A, 0x5A4A3A, 0x3A2A1A]; // Dirt between stones // Create pixelated stone floor for (var row = 0; row < 68; row++) { // Cover full height (2732/40 = ~68 rows) for (var col = 0; col < 52; col++) { // Cover full width (2048/40 = ~52 cols) var pixelSize = 40; var x = col * pixelSize; var y = row * pixelSize; // Determine pixel type based on position for realistic stone pattern var isStone = (row + col) % 3 !== 0; // Most pixels are stone var isMoss = Math.random() < 0.05; // 5% chance for moss var isDirt = Math.random() < 0.08; // 8% chance for dirt var color; if (isMoss) { color = mossColors[Math.floor(Math.random() * mossColors.length)]; } else if (isDirt) { color = dirtColors[Math.floor(Math.random() * dirtColors.length)]; } else { color = stoneColors[Math.floor(Math.random() * stoneColors.length)]; } var pixel = LK.getAsset('trap', { width: pixelSize, height: pixelSize, anchorX: 0, anchorY: 0, x: x, y: y }); pixel.tint = color; dungeonFloor.addChild(pixel); } // Update lighting to follow hero if (lightingOverlay && hero) { // Update light position to follow hero var playerLight = lightingOverlay.children[1]; // Second child is the main light var innerLight = lightingOverlay.children[2]; // Third child is the inner light if (playerLight) { playerLight.x = hero.x; playerLight.y = hero.y; } if (innerLight) { innerLight.x = hero.x; innerLight.y = hero.y; } } } ; // Add dungeon floor as background layer game.addChildAt(dungeonFloor, 0); // Add at bottom layer // Create lighting system - dark overlay with light behind player var lightingOverlay = new Container(); lightingOverlay.zIndex = 1000; // Above most game elements but below UI // Create dark background overlay var darkOverlay = LK.getAsset('trap', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0 }); darkOverlay.tint = 0x000000; // Black overlay darkOverlay.alpha = 0.7; // Semi-transparent to create darkness lightingOverlay.addChild(darkOverlay); // Create light circle behind player var playerLight = LK.getAsset('trap', { width: 600, height: 600, anchorX: 0.5, anchorY: 0.5 }); playerLight.tint = 0xFFFFFF; // White light playerLight.alpha = 0.8; // Light intensity // Create soft light gradient effect var innerLight = LK.getAsset('trap', { width: 300, height: 300, anchorX: 0.5, anchorY: 0.5 }); innerLight.tint = 0xFFFFFF; innerLight.alpha = 0.9; lightingOverlay.addChild(playerLight); lightingOverlay.addChild(innerLight); // Add lighting overlay to game game.addChild(lightingOverlay); // Tween plugin for animations // Language system var selectedLanguage = storage.language || null; // Get saved language or null if first time var languageTexts = { english: { welcome: "Welcome, Hero!", title: "Brave Adventurer", story: "Enter the dark dungeon and face legendary monsters.\nDefeat bosses, collect treasures, and prove your courage!", subtitle: "Tap to move and attack. Collect keys from chests to proceed!", startButton: "START ADVENTURE", instructions: "Tap the button above to begin your quest", attack: "ATTACK", heal: "HEAL", ultimate: "ULTIMATE", openChest: "OPEN CHEST", score: "Score: ", room: "Room ", health: "Health: Full", noHeals: "NO HEALS", gameOver: "You couldn't pass the dungeon...", gameOverSubtitle: "The darkness consumed you in Room ", monstersDefeated: "Monsters Defeated: ", selectLanguage: "Select Language", languageInstructions: "Choose your preferred language" }, turkish: { welcome: "Hoş Geldin, Kahraman!", title: "Cesur Maceracı", story: "Karanlık zindana gir ve efsanevi canavarlarla yüzleş.\nBosları yen, hazineler topla ve cesaretini kanıtla!", subtitle: "Hareket etmek ve saldırmak için dokun. İlerlemek için sandıklardan anahtarları topla!", startButton: "MACERAYA BAŞLA", instructions: "Maceranıza başlamak için yukarıdaki düğmeye dokunun", attack: "SALDIRI", heal: "İYİLEŞ", ultimate: "ÜST GÜÇLER", openChest: "SANDIĞI AÇ", score: "Skor: ", room: "Oda ", health: "Sağlık: Tam", noHeals: "İYİLEŞME YOK", gameOver: "Zindandan geçemedin...", gameOverSubtitle: "Karanlık seni Oda ", monstersDefeated: "Yenilen Canavarlar: ", selectLanguage: "Dil Seçin", languageInstructions: "Tercih ettiğiniz dili seçin" } }; var currentTexts = selectedLanguage ? languageTexts[selectedLanguage] : languageTexts.english; // Language Selection Screen (only shown if no language is saved) var languageSelectionOverlay = null; if (!selectedLanguage) { languageSelectionOverlay = new Container(); languageSelectionOverlay.zIndex = 15000; // Above start screen // Dark background var langBackground = LK.getAsset('trap', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0 }); langBackground.tint = 0x000000; langBackground.alpha = 0.9; languageSelectionOverlay.addChild(langBackground); // Title var langTitle = new Text2("Select Language / Dil Seçin", { size: 140, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); langTitle.anchor.set(0.5, 0.5); langTitle.x = 2048 / 2; langTitle.y = 600; languageSelectionOverlay.addChild(langTitle); // Instructions var langInstructions = new Text2("Choose your preferred language\nTercih ettiğiniz dili seçin", { size: 80, fill: 0xCCCCCC, font: "Impact, Arial Black, Tahoma" }); langInstructions.anchor.set(0.5, 0.5); langInstructions.x = 2048 / 2; langInstructions.y = 900; languageSelectionOverlay.addChild(langInstructions); // English Button var englishButton = LK.getAsset('door', { width: 600, height: 200, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 - 350, y: 1400 }); englishButton.tint = 0x4A8B3B; languageSelectionOverlay.addChild(englishButton); var englishButtonText = new Text2("ENGLISH", { size: 100, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); englishButtonText.anchor.set(0.5, 0.5); englishButtonText.x = 2048 / 2 - 350; englishButtonText.y = 1400; languageSelectionOverlay.addChild(englishButtonText); // Turkish Button var turkishButton = LK.getAsset('door', { width: 600, height: 200, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2 + 350, y: 1400 }); turkishButton.tint = 0x8B3A3A; languageSelectionOverlay.addChild(turkishButton); var turkishButtonText = new Text2("TÜRKÇE", { size: 100, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); turkishButtonText.anchor.set(0.5, 0.5); turkishButtonText.x = 2048 / 2 + 350; turkishButtonText.y = 1400; languageSelectionOverlay.addChild(turkishButtonText); // Add animations tween(englishButton, { scaleX: 1.05, scaleY: 1.05 }, { duration: 1000, loop: true, pingpong: true }); tween(turkishButton, { scaleX: 1.05, scaleY: 1.05 }, { duration: 1000, loop: true, pingpong: true, delay: 500 }); game.addChild(languageSelectionOverlay); } // --- Start Screen Overlay --- var startScreenOverlay = new Container(); startScreenOverlay.zIndex = 10000; // ensure on top // Dark background for better text readability var startBackground = LK.getAsset('trap', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0 }); startBackground.tint = 0x000000; startBackground.alpha = 0.85; startScreenOverlay.addChild(startBackground); // Welcome message var welcomeText = new Text2(currentTexts.welcome, { size: 120, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); welcomeText.anchor.set(0.5, 0.5); welcomeText.x = 2048 / 2; welcomeText.y = 600; startScreenOverlay.addChild(welcomeText); // Title var titleText = new Text2(currentTexts.title, { size: 180, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); titleText.anchor.set(0.5, 0.5); titleText.x = 2048 / 2; titleText.y = 800; startScreenOverlay.addChild(titleText); // Story introduction var storyText = new Text2(currentTexts.story, { size: 60, fill: 0xCCCCCC, font: "Impact, Arial Black, Tahoma" }); storyText.anchor.set(0.5, 0.5); storyText.x = 2048 / 2; storyText.y = 1000; startScreenOverlay.addChild(storyText); // Subtitle var subtitleText = new Text2(currentTexts.subtitle, { size: 70, fill: 0xB8B031 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 2048 / 2; subtitleText.y = 1200; startScreenOverlay.addChild(subtitleText); // Play Button var playBtnWidth = 600; var playBtnHeight = 180; var playBtn = LK.getAsset('door', { width: playBtnWidth, height: playBtnHeight, anchorX: 0.5, anchorY: 0.5, x: 2048 / 2, y: 1600 }); playBtn.tint = 0x4A8B3B; // Green tint for play button startScreenOverlay.addChild(playBtn); var playBtnText = new Text2(currentTexts.startButton, { size: 90, fill: "#fff", font: "Impact, Arial Black, Tahoma" }); playBtnText.anchor.set(0.5, 0.5); playBtnText.x = 2048 / 2; playBtnText.y = 1600; startScreenOverlay.addChild(playBtnText); // Add pulsing animation to play button tween(playBtn, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, loop: true, pingpong: true }); tween(playBtnText, { scaleX: 1.1, scaleY: 1.1 }, { duration: 800, loop: true, pingpong: true }); // Instructions text var instructionsText = new Text2(currentTexts.instructions, { size: 50, fill: 0x888888, font: "Impact, Arial Black, Tahoma" }); instructionsText.anchor.set(0.5, 0.5); instructionsText.x = 2048 / 2; instructionsText.y = 1800; startScreenOverlay.addChild(instructionsText); // Add overlay to game game.addChild(startScreenOverlay); // Block game input until started var gameStarted = false; var languageSelected = selectedLanguage ? true : false; // Language selection handler function selectLanguage(lang) { if (languageSelected) return; selectedLanguage = lang; storage.language = lang; // Save language preference languageSelected = true; currentTexts = languageTexts[lang]; // Update all text elements with new language welcomeText.setText(currentTexts.welcome); titleText.setText(currentTexts.title); storyText.setText(currentTexts.story); subtitleText.setText(currentTexts.subtitle); playBtnText.setText(currentTexts.startButton); instructionsText.setText(currentTexts.instructions); // Update UI elements killButtonText.setText(currentTexts.attack); healButtonText.setText(currentTexts.heal); ultimateButtonText.setText(currentTexts.ultimate); openChestText.setText(currentTexts.openChest); scoreTxt.setText(currentTexts.score + "0"); roomTxt.setText(currentTexts.room + "1"); healthTxt.setText(currentTexts.health); // Fade out language selection screen if (languageSelectionOverlay && languageSelectionOverlay.visible !== false) { tween(languageSelectionOverlay, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { languageSelectionOverlay.visible = false; } }); } // Flash screen to indicate language selection LK.effects.flashScreen(0x4A8B3B, 300); } // Start game handler function startGame() { if (gameStarted || !languageSelected) return; gameStarted = true; // Fade out start screen with smooth transition tween(startScreenOverlay, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { startScreenOverlay.visible = false; startScreenOverlay.alpha = 1; // Reset for potential restart } }); // Flash screen to indicate game start LK.effects.flashScreen(0x4A8B3B, 300); } // Listen for tap/click on language buttons if (languageSelectionOverlay) { languageSelectionOverlay.down = function (x, y, obj) { // Check English button var englishLocal = englishButton.toLocal(languageSelectionOverlay.toGlobal({ x: x, y: y })); if (englishLocal.x > -300 && englishLocal.x < 300 && englishLocal.y > -100 && englishLocal.y < 100) { selectLanguage('english'); return; } // Check Turkish button var turkishLocal = turkishButton.toLocal(languageSelectionOverlay.toGlobal({ x: x, y: y })); if (turkishLocal.x > -300 && turkishLocal.x < 300 && turkishLocal.y > -100 && turkishLocal.y < 100) { selectLanguage('turkish'); return; } }; } // Listen for tap/click on play button startScreenOverlay.down = function (x, y, obj) { // Check if tap is inside play button var local = playBtn.toLocal(startScreenOverlay.toGlobal({ x: x, y: y })); if (local.x > -playBtnWidth / 2 && local.x < playBtnWidth / 2 && local.y > -playBtnHeight / 2 && local.y < playBtnHeight / 2) { startGame(); } }; // Store original game handlers before any interception var originalGameDown = null; var originalGameMove = null; var originalGameUp = null; // Intercept all input until game started game.down = function (x, y, obj) { if (!languageSelected && languageSelectionOverlay && languageSelectionOverlay.visible) { if (typeof languageSelectionOverlay.down === "function") languageSelectionOverlay.down(x, y, obj); return; } if (!gameStarted && startScreenOverlay && startScreenOverlay.visible) { if (typeof startScreenOverlay.down === "function") startScreenOverlay.down(x, y, obj); return; } // Call the actual game down handler defined later if (typeof originalGameDown === "function") originalGameDown(x, y, obj); }; game.move = function (x, y, obj) { if (!languageSelected && languageSelectionOverlay && languageSelectionOverlay.visible || !gameStarted && startScreenOverlay && startScreenOverlay.visible) return; // Call the actual game move handler defined later if (typeof originalGameMove === "function") originalGameMove(x, y, obj); }; game.up = function (x, y, obj) { if (!languageSelected && languageSelectionOverlay && languageSelectionOverlay.visible || !gameStarted && startScreenOverlay && startScreenOverlay.visible) return; // Call the actual game up handler defined later if (typeof originalGameUp === "function") originalGameUp(x, y, obj); }; // Room bounds (centered, with margin) var roomMargin = 120; var roomBounds = { x: roomMargin, y: roomMargin + 100, // leave top 100px for menu width: 2048 - roomMargin * 2, height: 2732 - roomMargin * 2 - 100 }; // Game state var hero; var monsters = []; var mageBalls = []; // Track mage projectiles var skeletonStones = []; // Track skeleton stone projectiles var groundFires = []; // Track ground fire projectiles var stunOrbs = []; // Track demon stun orbs var door; var keys = []; var chests = []; var hasKey = false; var draggingHero = false; var lastMoveX = 0, lastMoveY = 0; var currentRoom = 1; var maxRooms = 5; var monstersDefeated = 0; var roomCleared = false; var gameFrozen = false; var freezeTimer = 0; var FREEZE_DURATION = 120; // 2 seconds at 60fps var freezeBuffer = 0; // Buffer to prevent immediate damage after freeze var FREEZE_BUFFER_TIME = 30; // 30 frames buffer after freeze starts (0.5 seconds) var isStunned = false; // Hero stun state var stunTimer = 0; // Stun timer var STUN_DURATION = 60; // 1 second at 60fps var stunnedText = null; // Reference to stunned text display var healUsesRemaining = 3; // Player can use heal 3 times total var ultimateTimer = 0; // Timer for ultimate button availability var ultimateInterval = 600; // 10 seconds at 60fps var ultimateAvailable = false; // Whether ultimate button should be shown // GUI elements var scoreTxt = new Text2('Score: 0', { size: 90, fill: "#fff" }); scoreTxt.anchor.set(0.5, 0); scoreTxt.y = 20; LK.gui.top.addChild(scoreTxt); // Room indicator var roomTxt = new Text2('Room 1', { size: 80, fill: 0xFFD700 }); roomTxt.anchor.set(0, 0); roomTxt.x = 120; roomTxt.y = 20; LK.gui.topLeft.addChild(roomTxt); // Health indicator text var healthTxt = new Text2('Health: Full', { size: 70, fill: 0xFF4444 }); healthTxt.anchor.set(1, 0); healthTxt.x = -20; healthTxt.y = 20; LK.gui.topRight.addChild(healthTxt); // Helper to update health display - now updates the health bar on the character function updateHealthDisplay() { // This will be handled by the health bar on the character } // Helper to update score display function updateScoreDisplay() { var score = monstersDefeated * 5; scoreTxt.setText(currentTexts.score + score); } // Helper to update room display function updateRoomDisplay() { roomTxt.setText(currentTexts.room + currentRoom); } // Helper to update health display // Helper to clear room function clearRoom() { for (var i = 0; i < monsters.length; i++) monsters[i].destroy(); for (var i = 0; i < keys.length; i++) keys[i].destroy(); for (var i = 0; i < chests.length; i++) chests[i].destroy(); for (var i = 0; i < mageBalls.length; i++) mageBalls[i].destroy(); for (var i = 0; i < skeletonStones.length; i++) skeletonStones[i].destroy(); for (var i = 0; i < groundFires.length; i++) groundFires[i].destroy(); for (var i = 0; i < stunOrbs.length; i++) stunOrbs[i].destroy(); monsters = []; keys = []; chests = []; mageBalls = []; skeletonStones = []; groundFires = []; stunOrbs = []; if (door) { door.destroy(); door = null; } // Clear NPC reference if it exists if (game.npcWizard) { game.npcWizard.destroy(); game.npcWizard = null; } roomCleared = false; hasKey = false; } // Helper to generate a room function generateRoom(roomNum) { clearRoom(); // Play appropriate music based on room number if (roomNum === 1) { LK.playMusic('dungeon'); } else { LK.playMusic('rock'); } // Place hero at entrance (bottom center) hero.x = roomBounds.x + roomBounds.width / 2; hero.y = roomBounds.y + roomBounds.height - hero.height; // Helper function to check if position is safe from hero spawn area function isPositionSafeFromHeroSpawn(x, y, heroStartX, heroStartY, safeDistance) { var dx = x - heroStartX; var dy = y - heroStartY; var dist = Math.sqrt(dx * dx + dy * dy); return dist > safeDistance; } // Place monsters - exactly 1 boss monster per room based on room number var monsterCount = 1; var heroStartX = roomBounds.x + roomBounds.width / 2; var heroStartY = roomBounds.y + roomBounds.height - hero.height; var heroSafeDistance = 400; // Increased safe distance around hero spawn for (var i = 0; i < monsterCount; i++) { var m; // Create different boss types based on room number if (roomNum === 1) { m = new SkeletonBoss(); } else if (roomNum === 2) { m = new OrcWarrior(); } else if (roomNum === 3) { m = new DarkMage(); } else if (roomNum === 4) { m = new FireDemon(); } else if (roomNum === 5) { m = new DragonLord(); } else { // Fallback for any additional rooms m = new Monster(); } var validPosition = false; var attempts = 0; // Try to find a position that's not too close to hero spawn, door, chest, or NPC while (!validPosition && attempts < 50) { m.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200); m.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400); // Check distance from hero spawn var safeFromHero = isPositionSafeFromHeroSpawn(m.x, m.y, heroStartX, heroStartY, heroSafeDistance); // Check distance from door position (top center) var doorX = roomBounds.x + roomBounds.width / 2; var doorY = roomBounds.y + 150; // Door height/2 + some margin var doorSafeDistance = 300; // Safe distance from door var safeFromDoor = isPositionSafeFromHeroSpawn(m.x, m.y, doorX, doorY, doorSafeDistance); // Check distance from chest position (will be placed later, but use same safe distance) var chestSafeDistance = 350; // Safe distance from chest var safeFromChest = true; // Will check against actual chest position after it's placed // Check distance from NPC position (only in room 1) var safeFromNPC = true; if (roomNum === 1) { var npcX = roomBounds.x + 200; // NPC wizard position var npcY = roomBounds.y + roomBounds.height / 2; var npcSafeDistance = 400; // Safe distance from NPC safeFromNPC = isPositionSafeFromHeroSpawn(m.x, m.y, npcX, npcY, npcSafeDistance); } if (safeFromHero && safeFromDoor && safeFromNPC) { validPosition = true; } attempts++; } monsters.push(m); game.addChild(m); } // Key will come from chest after killing all monsters, so don't place it randomly // Place chest (always one per room) var c = new Chest(); var validPosition = false; var attempts = 0; while (!validPosition && attempts < 50) { c.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200); c.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400); // Check distance from hero spawn var safeFromHero = isPositionSafeFromHeroSpawn(c.x, c.y, heroStartX, heroStartY, heroSafeDistance); // Check distance from all existing monsters var safeFromMonsters = true; for (var m = 0; m < monsters.length; m++) { var dx = c.x - monsters[m].x; var dy = c.y - monsters[m].y; var distToMonster = Math.sqrt(dx * dx + dy * dy); if (distToMonster < 350) { // Safe distance from monsters safeFromMonsters = false; break; } } // Check distance from NPC (only in room 1) var safeFromNPC = true; if (roomNum === 1) { var npcX = roomBounds.x + 200; var npcY = roomBounds.y + roomBounds.height / 2; safeFromNPC = isPositionSafeFromHeroSpawn(c.x, c.y, npcX, npcY, 300); } if (safeFromHero && safeFromMonsters && safeFromNPC) { validPosition = true; } attempts++; } chests.push(c); game.addChild(c); // Place door (top center) door = new Door(); door.x = roomBounds.x + roomBounds.width / 2; door.y = roomBounds.y + door.height / 2; game.addChild(door); door.visible = false; // Only show when room is cleared // Place NPC wizard only in first room if (roomNum === 1) { var npcWizard = new NPCWizard(); npcWizard.x = roomBounds.x + 200; // Position near left wall npcWizard.y = roomBounds.y + roomBounds.height / 2; // Center vertically game.addChild(npcWizard); // Store reference for proximity check game.npcWizard = npcWizard; } roomCleared = false; updateRoomDisplay(); updateHealthDisplay(); } // Create hero hero = new Hero(); game.addChild(hero); // Start first room generateRoom(currentRoom); updateScoreDisplay(); // Store this as the original game up handler originalGameUp = function originalGameUp(x, y, obj) { draggingHero = false; }; function clamp(val, min, max) { return Math.max(min, Math.min(max, val)); } // Walk-to destination variables var walkToActive = false; var walkToX = 0; var walkToY = 0; var walkToSpeed = 22; // Attack button for attacking monsters - round dungeon-themed button var killButton = LK.getAsset('monster', { width: 280, height: 280, anchorX: 0.5, anchorY: 0.5, x: 2048 - 160, y: 2732 - 160 }); killButton.tint = 0x4A4A4A; // Dark steel gray for shield-like appearance killButton.alpha = 0.9; // Slightly transparent for better integration game.addChild(killButton); var killButtonText = new Text2("ATTACK", { size: 60, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); killButtonText.anchor.set(0.5, 0.5); killButtonText.x = 2048 - 160; killButtonText.y = 2732 - 160; game.addChild(killButtonText); // Heal pot button next to attack button var healButton = LK.getAsset('chest', { width: 280, height: 280, anchorX: 0.5, anchorY: 0.5, x: 2048 - 460, // Position to the left of attack button y: 2732 - 160 }); healButton.tint = 0xFF4444; // Red tint for health potion healButton.alpha = 0.9; game.addChild(healButton); var healButtonText = new Text2("HEAL", { size: 60, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); healButtonText.anchor.set(0.5, 0.5); healButtonText.x = 2048 - 460; healButtonText.y = 2732 - 160; game.addChild(healButtonText); // Ultimate button next to heal button var ultimateButton = LK.getAsset('sword', { width: 280, height: 280, anchorX: 0.5, anchorY: 0.5, x: 2048 - 760, // Position to the left of heal button y: 2732 - 160 }); ultimateButton.tint = 0xFF8800; // Orange tint for ultimate ability ultimateButton.alpha = 0.9; ultimateButton.visible = false; // Initially hidden game.addChild(ultimateButton); var ultimateButtonText = new Text2("ULTIMATE", { size: 50, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); ultimateButtonText.anchor.set(0.5, 0.5); ultimateButtonText.x = 2048 - 760; ultimateButtonText.y = 2732 - 160; ultimateButtonText.visible = false; // Initially hidden game.addChild(ultimateButtonText); // Open chest text var openChestText = new Text2("OPEN CHEST", { size: 80, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); openChestText.anchor.set(0.5, 0.5); openChestText.x = 2048 / 2; openChestText.y = 2732 - 200; openChestText.visible = false; game.addChild(openChestText); // Sword swing state management var swordSwinging = false; var swordCooldown = 0; var SWORD_COOLDOWN_TIME = 15; // frames (0.25 seconds at 60fps) // Store this as the original game move handler originalGameMove = function originalGameMove(x, y, obj) { if (draggingHero && !isStunned) { // Prevent movement when stunned // Clamp hero inside room bounds var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2); var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2); hero.x = hx; hero.y = hy; walkToActive = false; // Cancel walk-to if dragging } }; // Initialize originalGameDown properly before it's used if (!originalGameDown) { originalGameDown = function originalGameDown(x, y, obj) { // Don't allow any actions when stunned if (isStunned) return; // Only start drag if touch is on hero var local = hero.toLocal(game.toGlobal({ x: x, y: y })); // Check if heal button was tapped var healButtonLocal = healButton.toLocal(game.toGlobal({ x: x, y: y })); if (healButtonLocal.x > -healButton.width / 2 && healButtonLocal.x < healButton.width / 2 && healButtonLocal.y > -healButton.height / 2 && healButtonLocal.y < healButton.height / 2) { // Check if we have heal uses remaining if (healUsesRemaining > 0) { // Use heal function from hero hero.heal(); healUsesRemaining--; } return; // Don't process other touch logic } // Check if ultimate button was tapped var ultimateButtonLocal = ultimateButton.toLocal(game.toGlobal({ x: x, y: y })); if (ultimateAvailable && ultimateButtonLocal.x > -ultimateButton.width / 2 && ultimateButtonLocal.x < ultimateButton.width / 2 && ultimateButtonLocal.y > -ultimateButton.height / 2 && ultimateButtonLocal.y < ultimateButton.height / 2) { // Use ultimate ability - damage all monsters on screen and heal hero for (var i = monsters.length - 1; i >= 0; i--) { var m = monsters[i]; m.takeDamage(); // Check if monster is dead if (m.health <= 0) { // Play win sound for boss defeat LK.playMusic('win'); // Remove monster m.destroy(); monsters.splice(i, 1); monstersDefeated++; updateScoreDisplay(); } } // Heal hero to full health hero.health = hero.maxHealth; hero.updateHealthBar(); // Visual effects LK.effects.flashScreen(0xFFD700, 500); LK.effects.flashObject(hero, 0xFFD700, 500); // Reset ultimate timer ultimateTimer = 0; ultimateAvailable = false; ultimateButton.visible = false; ultimateButtonText.visible = false; return; // Don't process other touch logic } // Check if kill button was tapped var killButtonLocal = killButton.toLocal(game.toGlobal({ x: x, y: y })); if (killButtonLocal.x > -killButton.width / 2 && killButtonLocal.x < killButton.width / 2 && killButtonLocal.y > -killButton.height / 2 && killButtonLocal.y < killButton.height / 2) { // Check if sword is available (not swinging and cooldown expired) if (!swordSwinging && swordCooldown <= 0) { // Get sword sprite from hero for animation var swordSprite = hero.children[1]; // Sword is the second child (index 1) // Stop any existing tweens on the sword tween.stop(swordSprite, { rotation: true }); // Set swinging state swordSwinging = true; swordCooldown = SWORD_COOLDOWN_TIME; // Create sword swing effect var originalRotation = swordSprite.rotation; tween(swordSprite, { rotation: originalRotation + Math.PI / 2 }, { duration: 150, easing: tween.easeOut, onFinish: function onFinish() { tween(swordSprite, { rotation: originalRotation }, { duration: 200, easing: tween.easeIn, onFinish: function onFinish() { swordSwinging = false; } }); } }); // Kill button tapped - damage all monsters within 350px of hero for (var i = monsters.length - 1; i >= 0; i--) { var m = monsters[i]; var dx = hero.x - m.x; var dy = hero.y - m.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 500) { // Deal damage to monster m.takeDamage(); // Optional: flash hero for feedback LK.effects.flashObject(hero, 0xffffff, 120); // Check if monster is dead if (m.health <= 0) { // Play win sound for boss defeat LK.playMusic('win'); // Remove monster m.destroy(); monsters.splice(i, 1); monstersDefeated++; updateScoreDisplay(); } // Unfreeze game when monster dies if (gameFrozen) { gameFrozen = false; freezeTimer = 0; // Remove freeze visual effects tween(hero, { tint: 0xffffff }, { duration: 300 }); for (var j = 0; j < monsters.length; j++) { tween(monsters[j], { tint: 0xffffff }, { duration: 300 }); } } } } } return; // Don't process other touch logic } if (local.x > -hero.width / 2 && local.x < hero.width / 2 && local.y > -hero.height / 2 && local.y < hero.height / 2) { draggingHero = true; lastMoveX = x; lastMoveY = y; walkToActive = false; } else { // Set walk-to destination if tap is inside room var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2); var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2); walkToX = hx; walkToY = hy; walkToActive = true; } // Original handler logic is now properly stored and called }; } // In update, move hero toward walk-to destination if active var oldGameUpdate = game.update; game.update = function () { // Walk-to logic if (walkToActive && !draggingHero && !isStunned) { // Prevent walk-to when stunned var dx = walkToX - hero.x; var dy = walkToY - hero.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > walkToSpeed) { hero.x += dx / dist * walkToSpeed; hero.y += dy / dist * walkToSpeed; } else { hero.x = walkToX; hero.y = walkToY; walkToActive = false; } } if (typeof oldGameUpdate === "function") oldGameUpdate(); }; // Play background music LK.playMusic('dungeon'); // Main update loop game.update = function () { if (!gameStarted) return; // Handle freeze timer if (gameFrozen) { freezeTimer--; if (freezeTimer <= 0) { // Unfreeze game gameFrozen = false; // Remove freeze visual effects tween(hero, { tint: 0xffffff }, { duration: 300 }); for (var m = 0; m < monsters.length; m++) { tween(monsters[m], { tint: 0xffffff }, { duration: 300 }); } } } // Update sword cooldown if (swordCooldown > 0) { swordCooldown--; } // Update ultimate timer if (!ultimateAvailable) { ultimateTimer++; if (ultimateTimer >= ultimateInterval) { ultimateAvailable = true; ultimateButton.visible = true; ultimateButtonText.visible = true; // Add pulsing animation to indicate availability tween(ultimateButton, { scaleX: 1.2, scaleY: 1.2 }, { duration: 600, loop: true, pingpong: true }); tween(ultimateButtonText, { scaleX: 1.2, scaleY: 1.2 }, { duration: 600, loop: true, pingpong: true }); } } // Update freeze buffer if (freezeBuffer > 0) { freezeBuffer--; } // Update stun timer if (isStunned) { stunTimer--; if (stunTimer <= 0) { isStunned = false; // Remove stunned text if (stunnedText) { stunnedText.destroy(); stunnedText = null; } } // Update stunned text position if it exists if (stunnedText) { stunnedText.x = hero.x; stunnedText.y = hero.y - hero.height - 50; } } // Update hero (only if not frozen) if (!gameFrozen) { hero.update(); } // Update monsters for (var i = 0; i < monsters.length; i++) { monsters[i].update(); } // Update mage balls for (var i = mageBalls.length - 1; i >= 0; i--) { var ball = mageBalls[i]; ball.update(); // Remove ball if lifetime expired if (ball.lifetime <= 0) { ball.destroy(); mageBalls.splice(i, 1); continue; } // Check collision with hero (only when not invincible and no freeze buffer) if (!hero.invincible && freezeBuffer <= 0 && hero.intersects(ball)) { hero.takeDamage(); // Destroy ball on hit ball.destroy(); mageBalls.splice(i, 1); // Create impact effect LK.effects.flashObject(hero, 0x8A2BE2, 200); } } // Update skeleton stones for (var i = skeletonStones.length - 1; i >= 0; i--) { var stone = skeletonStones[i]; stone.update(); // Remove stone if lifetime expired if (stone.lifetime <= 0) { stone.destroy(); skeletonStones.splice(i, 1); continue; } // Check collision with hero (only when not invincible and no freeze buffer) if (!hero.invincible && freezeBuffer <= 0 && hero.intersects(stone)) { hero.takeDamage(); // Destroy stone on hit stone.destroy(); skeletonStones.splice(i, 1); // Create impact effect LK.effects.flashObject(hero, 0x555555, 200); } } // Update ground fires for (var i = groundFires.length - 1; i >= 0; i--) { var fire = groundFires[i]; fire.update(); // Remove fire if lifetime expired if (fire.lifetime <= 0) { fire.destroy(); groundFires.splice(i, 1); continue; } // Check collision with hero (only when not invincible and no freeze buffer) if (!hero.invincible && freezeBuffer <= 0 && !fire.damageDealt && hero.intersects(fire)) { hero.takeDamage(); fire.damageDealt = true; // Prevent multiple damage from same fire // Create fire impact effect LK.effects.flashObject(hero, 0xFF4400, 200); } } // Update stun orbs for (var i = stunOrbs.length - 1; i >= 0; i--) { var orb = stunOrbs[i]; orb.update(); // Remove orb if lifetime expired if (orb.lifetime <= 0) { orb.destroy(); stunOrbs.splice(i, 1); continue; } // Check collision with hero (only when not invincible and no freeze buffer) if (!hero.invincible && freezeBuffer <= 0 && !isStunned && hero.intersects(orb)) { // Apply stun effect isStunned = true; stunTimer = STUN_DURATION; // Create stunned text above hero stunnedText = new Text2("STUNNED", { size: 80, fill: 0xFF0000, font: "Impact, Arial Black, Tahoma" }); stunnedText.anchor.set(0.5, 0.5); stunnedText.x = hero.x; stunnedText.y = hero.y - hero.height - 50; game.addChild(stunnedText); // Apply visual stun effect to hero tween(hero, { tint: 0x9932CC }, { duration: 200, onFinish: function onFinish() { tween(hero, { tint: 0xFFFFFF }, { duration: 200 }); } }); // Destroy orb on hit orb.destroy(); stunOrbs.splice(i, 1); // Create stun impact effect LK.effects.flashObject(hero, 0x9932CC, 300); } } // Attack button is always visible killButton.visible = true; killButtonText.visible = true; // Heal button visibility and text update based on remaining uses if (healUsesRemaining > 0) { healButton.visible = true; healButtonText.visible = true; healButton.alpha = 0.9; healButtonText.setText(currentTexts.heal + " (" + healUsesRemaining + ")"); } else { healButton.visible = true; healButtonText.visible = true; healButton.alpha = 0.3; // Make button appear disabled healButtonText.setText(currentTexts.noHeals); } // Check if hero is close to chest and all monsters are defeated var nearChest = false; if (monsters.length === 0) { for (var i = 0; i < chests.length; i++) { var c = chests[i]; if (!c.opened) { var dx = hero.x - c.x; var dy = hero.y - c.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { nearChest = true; break; } } } } // Show/hide open chest text based on proximity and game state openChestText.visible = nearChest; // Check collisions: hero vs monsters (only when game is not frozen, no freeze buffer, and hero is not invincible) if (!gameFrozen && freezeBuffer <= 0 && !hero.invincible) { for (var i = monsters.length - 1; i >= 0; i--) { var m = monsters[i]; if (hero.intersects(m)) { hero.takeDamage(); // No knockback - hero stays in place when touching monsters // Hero can only kill monsters with the kill button, not by collision } } } // Check collisions: hero vs keys for (var i = keys.length - 1; i >= 0; i--) { var k = keys[i]; if (hero.intersects(k)) { k.destroy(); keys.splice(i, 1); hasKey = true; LK.effects.flashObject(hero, 0xFFD700, 300); } } // Check collisions: hero vs chests for (var i = 0; i < chests.length; i++) { var c = chests[i]; if (hero.intersects(c) && monsters.length === 0 && !c.opened) { c.opened = true; LK.effects.flashObject(c, 0x83de44, 600); updateScoreDisplay(); // Spawn key from chest after all monsters are defeated var k = new Key(); k.x = c.x; k.y = c.y; keys.push(k); game.addChild(k); } } // Room clear check - need to defeat all monsters first, then open chest, then collect key if (!roomCleared && monsters.length === 0 && chests.length > 0 && chests[0].opened && hasKey) { roomCleared = true; door.visible = true; LK.effects.flashObject(door, 0x83de44, 600); } // Check if hero is near NPC wizard in first room if (currentRoom === 1 && game.npcWizard && !game.npcWizard.showingSpeech) { var dx = hero.x - game.npcWizard.x; var dy = hero.y - game.npcWizard.y; var distToNPC = Math.sqrt(dx * dx + dy * dy); if (distToNPC < 250) { // Show speech when hero gets close game.npcWizard.showSpeech(); } } // Check hero at door to next room if (roomCleared && door && hero.intersects(door)) { if (currentRoom < maxRooms) { currentRoom++; generateRoom(currentRoom); } else { // Win! LK.setScore(monstersDefeated * 5); // Create dungeon completion overlay instead of default win screen var dungeonCompletionOverlay = new Container(); dungeonCompletionOverlay.zIndex = 10000; // ensure on top // Dark background var background = LK.getAsset('trap', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0 }); background.tint = 0x000000; background.alpha = 0.8; dungeonCompletionOverlay.addChild(background); // Main completion message var completionText = new Text2("You completed the dungeon!", { size: 140, fill: 0xFFD700, font: "Impact, Arial Black, Tahoma" }); completionText.anchor.set(0.5, 0.5); completionText.x = 2048 / 2; completionText.y = 1200; dungeonCompletionOverlay.addChild(completionText); // Subtitle var subtitleText = new Text2("The brave adventurer has conquered all " + maxRooms + " rooms!", { size: 80, fill: 0x88FF88 }); subtitleText.anchor.set(0.5, 0.5); subtitleText.x = 2048 / 2; subtitleText.y = 1400; dungeonCompletionOverlay.addChild(subtitleText); // Score display var finalScoreText = new Text2("Final Score: " + monstersDefeated * 5, { size: 90, fill: 0xFFFF88 }); finalScoreText.anchor.set(0.5, 0.5); finalScoreText.x = 2048 / 2; finalScoreText.y = 1600; dungeonCompletionOverlay.addChild(finalScoreText); // Monsters defeated display var monstersText = new Text2("Monsters Defeated: " + monstersDefeated, { size: 70, fill: 0xFFFFFF }); monstersText.anchor.set(0.5, 0.5); monstersText.x = 2048 / 2; monstersText.y = 1800; dungeonCompletionOverlay.addChild(monstersText); game.addChild(dungeonCompletionOverlay); // Auto show you win after 3 seconds to register the win LK.setTimeout(function () { LK.showYouWin(); }, 3000); } } };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
// Chest class
var Chest = Container.expand(function () {
var self = Container.call(this);
// Attach chest asset (brown box)
var chestSprite = self.attachAsset('chest', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = chestSprite.width;
self.height = chestSprite.height;
self.opened = false;
return self;
});
// Dark Mage - Room 3
var DarkMage = Container.expand(function () {
var self = Container.call(this);
// Use mage asset instead of monster asset
var mageSprite = self.attachAsset('mage', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.1,
scaleY: 1.1
});
// Set larger hitbox for easier collision detection
self.width = mageSprite.width * 0.4;
self.height = mageSprite.height * 0.4;
// Monster health system
self.maxHealth = 2;
self.health = self.maxHealth;
self.speed = 0.6;
// Create heart-based health display
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0x654321;
heart.x = startX + h * heartSpacing;
heart.y = -mageSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
// Update heart display
self.updateHealthBar = function () {
// Update heart visibility based on current health
for (var h = 0; h < self.hearts.length; h++) {
if (h < self.health) {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x654321; // Brown for full hearts
self.hearts[h].alpha = 1.0;
} else {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x444444; // Dark gray for empty hearts
self.hearts[h].alpha = 0.5;
}
}
};
// Take damage function with animation
self.takeDamage = function () {
self.health -= 1;
self.updateHealthBar();
// Flash damage animation
tween(mageSprite, {
tint: 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(mageSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Shake animation
var originalX = self.x;
tween(self, {
x: originalX + 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX - 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX
}, {
duration: 50
});
}
});
}
});
};
// Movement direction
self.vx = 0;
self.vy = 0;
// Set random direction
self.setRandomDirection = function () {
var angle = Math.random() * Math.PI * 2;
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
// Shooting timer
self.shootTimer = 0;
self.shootInterval = 90; // Shoot every 1.5 seconds at 60fps
self.shootCooldown = 0; // Cooldown timer after shooting
self.cooldownDuration = 120; // 2 seconds at 60fps
// Called every tick
self.update = function () {
// Check if hero is close to this monster FIRST (within encounter distance)
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distToHero = Math.sqrt(dx * dx + dy * dy);
// If hero is within encounter distance, trigger freeze and stop moving
if (distToHero < 120) {
// Reduced from 200 to 120 for less aggressive encounter
if (!gameFrozen) {
// Trigger freeze effect
gameFrozen = true;
freezeTimer = FREEZE_DURATION;
freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage
console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME);
// Apply freeze visual effect to all characters
tween(hero, {
tint: 0x8888ff
}, {
duration: 300
});
tween(self, {
tint: 0x8888ff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] !== self) {
tween(monsters[m], {
tint: 0x8888ff
}, {
duration: 300
});
}
}
}
// Monster stays still when encountering hero
return;
}
// Don't move if game is frozen
if (gameFrozen) {
return;
}
// Update cooldown timer
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Mage shooting logic - shoot projectiles at hero (only if cooldown is 0)
if (self.shootCooldown <= 0) {
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
self.shootTimer = 0;
self.shootCooldown = self.cooldownDuration; // Start 2-second cooldown
// Create magic ball projectile
var ball = new MageBall();
ball.x = self.x;
ball.y = self.y;
ball.setDirection(hero.x, hero.y);
game.addChild(ball);
mageBalls.push(ball);
// Visual effect when shooting
tween(mageSprite, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
onFinish: function onFinish() {
tween(mageSprite, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200
});
}
});
}
} else {
// Reset shoot timer while on cooldown to prevent immediate shooting after cooldown ends
self.shootTimer = 0;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
// Update hearts for new health
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0x654321;
heart.x = startX + h * heartSpacing;
heart.y = -mageSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
self.updateHealthBar();
return self;
});
// Door class (for next room)
var Door = Container.expand(function () {
var self = Container.call(this);
// Attach door asset (purple box)
var doorSprite = self.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = doorSprite.width;
self.height = doorSprite.height;
return self;
});
// Dragon Lord - Final Boss Room 5
var DragonLord = Container.expand(function () {
var self = Container.call(this);
// Use dragon asset instead of monster asset
var dragonSprite = self.attachAsset('dragon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Set larger hitbox for easier collision detection
self.width = dragonSprite.width * 0.4;
self.height = dragonSprite.height * 0.4;
// Monster health system
self.maxHealth = 3;
self.health = self.maxHealth;
self.speed = 0.2;
// Create heart-based health display
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -dragonSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
// Update heart display
self.updateHealthBar = function () {
// Update heart visibility based on current health
for (var h = 0; h < self.hearts.length; h++) {
if (h < self.health) {
self.hearts[h].visible = true;
self.hearts[h].tint = 0xFF1744; // Red for full hearts
self.hearts[h].alpha = 1.0;
} else {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x444444; // Dark gray for empty hearts
self.hearts[h].alpha = 0.5;
}
}
};
// Take damage function with animation
self.takeDamage = function () {
self.health -= 1;
self.updateHealthBar();
// Flash damage animation
tween(dragonSprite, {
tint: 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(dragonSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Shake animation
var originalX = self.x;
tween(self, {
x: originalX + 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX - 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX
}, {
duration: 50
});
}
});
}
});
};
// Movement direction
self.vx = 0;
self.vy = 0;
// Set random direction
self.setRandomDirection = function () {
var angle = Math.random() * Math.PI * 2;
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
// Called every tick
self.update = function () {
// Check if hero is close to this monster FIRST (within encounter distance)
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distToHero = Math.sqrt(dx * dx + dy * dy);
// If hero is within encounter distance, trigger freeze and stop moving
if (distToHero < 120) {
// Reduced from 200 to 120 for less aggressive encounter
if (!gameFrozen) {
// Trigger freeze effect
gameFrozen = true;
freezeTimer = FREEZE_DURATION;
freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage
console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME);
// Apply freeze visual effect to all characters
tween(hero, {
tint: 0x8888ff
}, {
duration: 300
});
tween(self, {
tint: 0x8888ff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] !== self) {
tween(monsters[m], {
tint: 0x8888ff
}, {
duration: 300
});
}
}
}
// Monster stays still when encountering hero
return;
}
// Don't move if game is frozen
if (gameFrozen) {
return;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
// Update hearts for new health
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -dragonSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
self.updateHealthBar();
return self;
});
// Fire Demon - Room 4
var FireDemon = Container.expand(function () {
var self = Container.call(this);
// Use demon asset instead of monster asset
var demonSprite = self.attachAsset('demon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.6,
scaleY: 1.6
});
// Set larger hitbox for easier collision detection
self.width = demonSprite.width * 0.4;
self.height = demonSprite.height * 0.4;
// Monster health system
self.maxHealth = 4;
self.health = self.maxHealth;
self.speed = 0.3;
// Create heart-based health display
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -demonSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
// Update heart display
self.updateHealthBar = function () {
// Update heart visibility based on current health
for (var h = 0; h < self.hearts.length; h++) {
if (h < self.health) {
self.hearts[h].visible = true;
self.hearts[h].tint = 0xFF1744; // Red for full hearts
self.hearts[h].alpha = 1.0;
} else {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x444444; // Dark gray for empty hearts
self.hearts[h].alpha = 0.5;
}
}
};
// Take damage function with animation
self.takeDamage = function () {
self.health -= 1;
self.updateHealthBar();
// Flash damage animation
tween(demonSprite, {
tint: 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(demonSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Shake animation
var originalX = self.x;
tween(self, {
x: originalX + 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX - 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX
}, {
duration: 50
});
}
});
}
});
};
// Movement direction
self.vx = 0;
self.vy = 0;
// Set random direction
self.setRandomDirection = function () {
var angle = Math.random() * Math.PI * 2;
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
// Fire shooting timer
self.fireTimer = 0;
self.fireInterval = 180; // Shoot fire every 3 seconds
self.safeZoneTimer = 0;
self.safeZoneDuration = 300; // 5 seconds safe zone
self.currentSafeZone = null;
// Called every tick
self.update = function () {
// Check if hero is close to this monster FIRST (within encounter distance)
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distToHero = Math.sqrt(dx * dx + dy * dy);
// If hero is within encounter distance, trigger freeze and stop moving
if (distToHero < 120) {
// Reduced from 200 to 120 for less aggressive encounter
if (!gameFrozen) {
// Trigger freeze effect
gameFrozen = true;
freezeTimer = FREEZE_DURATION;
freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage
console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME);
// Apply freeze visual effect to all characters
tween(hero, {
tint: 0x8888ff
}, {
duration: 300
});
tween(self, {
tint: 0x8888ff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] !== self) {
tween(monsters[m], {
tint: 0x8888ff
}, {
duration: 300
});
}
}
}
// Monster stays still when encountering hero
return;
}
// Don't move if game is frozen
if (gameFrozen) {
return;
}
// Fire demon shooting logic - alternates between ground fire and stun orbs
self.fireTimer++;
if (self.fireTimer >= self.fireInterval) {
self.fireTimer = 0;
// Alternate between ground fire and stun orb attacks
if (Math.random() < 0.4) {
// 40% chance for stun orb
// Create stun orb projectile
var stunOrb = new StunOrb();
stunOrb.x = self.x;
stunOrb.y = self.y;
stunOrb.setDirection(hero.x, hero.y);
game.addChild(stunOrb);
stunOrbs.push(stunOrb);
// Visual effect when shooting stun orb
tween(demonSprite, {
tint: 0x9932CC
}, {
duration: 300,
onFinish: function onFinish() {
tween(demonSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
} else {
// Create random safe zone
self.currentSafeZone = {
x: roomBounds.x + 200 + Math.random() * (roomBounds.width - 400),
y: roomBounds.y + 200 + Math.random() * (roomBounds.height - 400),
radius: 150
};
self.safeZoneTimer = self.safeZoneDuration;
// Create ground fires across the room
for (var f = 0; f < 15; f++) {
var fireX = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200);
var fireY = roomBounds.y + 200 + Math.random() * (roomBounds.height - 300);
// Skip if in safe zone
var dx = fireX - self.currentSafeZone.x;
var dy = fireY - self.currentSafeZone.y;
var distToSafe = Math.sqrt(dx * dx + dy * dy);
if (distToSafe < self.currentSafeZone.radius) continue;
var groundFire = new GroundFire();
groundFire.x = fireX;
groundFire.y = fireY;
game.addChild(groundFire);
groundFires.push(groundFire);
}
// Visual indicator for safe zone
var safeIndicator = LK.getAsset('trap', {
width: self.currentSafeZone.radius * 2,
height: self.currentSafeZone.radius * 2,
anchorX: 0.5,
anchorY: 0.5
});
safeIndicator.tint = 0x00FF00; // Green safe zone
safeIndicator.alpha = 0.3;
safeIndicator.x = self.currentSafeZone.x;
safeIndicator.y = self.currentSafeZone.y;
game.addChild(safeIndicator);
self.safeIndicator = safeIndicator;
} // End ground fire attack
}
// Update safe zone timer
if (self.safeZoneTimer > 0) {
self.safeZoneTimer--;
// Check if hero is in safe zone
if (self.currentSafeZone) {
var dx = hero.x - self.currentSafeZone.x;
var dy = hero.y - self.currentSafeZone.y;
var distToSafe = Math.sqrt(dx * dx + dy * dy);
if (distToSafe < self.currentSafeZone.radius) {
// Hero is in safe zone, damage demon
self.takeDamage();
// Flash effect to show demon taking damage
LK.effects.flashObject(self, 0xFF4444, 300);
if (self.health <= 0) {
// Play win sound for boss defeat
LK.playMusic('win');
// Remove demon
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] === self) {
monsters.splice(m, 1);
break;
}
}
monstersDefeated++;
updateScoreDisplay();
self.destroy();
}
}
}
} else {
// Clean up safe zone indicator
if (self.safeIndicator) {
self.safeIndicator.destroy();
self.safeIndicator = null;
}
self.currentSafeZone = null;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
// Update hearts for new health
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -demonSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
self.updateHealthBar();
return self;
});
// Ground Fire class for Fire Demon attacks
var GroundFire = Container.expand(function () {
var self = Container.call(this);
// Create fire visual using trap asset
var fireSprite = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 80
});
fireSprite.tint = 0xFF4400; // Orange-red fire color
self.width = fireSprite.width;
self.height = fireSprite.height;
// Fire properties
self.lifetime = 120; // 2 seconds at 60fps
self.damageDealt = false;
// Update fire animation
self.update = function () {
self.lifetime--;
// Animate fire flickering
fireSprite.rotation += 0.3;
fireSprite.scaleX = 0.8 + Math.sin(LK.ticks * 0.2) * 0.2;
fireSprite.scaleY = 0.8 + Math.cos(LK.ticks * 0.15) * 0.3;
// Fade out near end of lifetime
if (self.lifetime < 30) {
fireSprite.alpha = self.lifetime / 30;
}
};
return self;
});
// Hero class
var Hero = Container.expand(function () {
var self = Container.call(this);
// Attach hero asset (red box)
var heroSprite = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = heroSprite.width;
self.height = heroSprite.height;
// Attach sword to hero
var swordSprite = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5
});
swordSprite.x = -heroSprite.width * 0.3; // Position sword to the left of hero
swordSprite.y = 0; // Center vertically with hero
swordSprite.rotation = -Math.PI / 4; // Angle the sword pointing left
// Hero stats
self.maxHealth = 3;
self.health = self.maxHealth;
self.invincible = false;
self.invincibleTimer = 0;
// Create health bar above character
// Health bar border (white outline)
self.healthBarBorder = LK.getAsset('trap', {
width: 200,
height: 40,
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBorder.tint = 0xFFFFFF; // White border
self.healthBarBorder.x = 0;
self.healthBarBorder.y = -heroSprite.height / 2 - 50; // Position above character
self.addChild(self.healthBarBorder);
// Health bar background (dark)
self.healthBarBackground = LK.getAsset('trap', {
width: 180,
height: 24,
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBackground.tint = 0x222222; // Dark gray background
self.healthBarBackground.x = 0;
self.healthBarBackground.y = -heroSprite.height / 2 - 50; // Position above character
self.addChild(self.healthBarBackground);
// Health bar fill (red color)
self.healthBarFill = LK.getAsset('trap', {
width: 180,
height: 24,
anchorX: 0.0,
anchorY: 0.5
});
self.healthBarFill.tint = 0xFF0000; // Red for health bar
self.healthBarFill.x = -90; // Left-aligned within background
self.healthBarFill.y = -heroSprite.height / 2 - 50; // Position above character
self.addChild(self.healthBarFill);
// Update health bar display
self.updateHealthBar = function () {
var healthPercent = self.health / self.maxHealth;
self.healthBarFill.scaleX = healthPercent;
// Keep health bar red color
self.healthBarFill.tint = 0xFF0000; // Always red
// Add pulsing effect when health is critical
if (healthPercent <= 0.25) {
tween(self.healthBarFill, {
alpha: 0.5
}, {
duration: 500,
loop: true,
pingpong: true
});
} else {
tween.stop(self.healthBarFill, {
alpha: true
});
self.healthBarFill.alpha = 1.0;
}
};
// Initialize health bar
self.updateHealthBar();
// Flash when hit
self.flash = function () {
tween(heroSprite, {
tint: 0xffffff
}, {
duration: 100,
onFinish: function onFinish() {
tween(heroSprite, {
tint: 0xd83318
}, {
duration: 200
});
}
});
};
// Take damage
self.takeDamage = function () {
if (self.invincible) return;
self.health -= 1;
self.flash();
self.invincible = true;
self.invincibleTimer = 60; // 1 second at 60fps
LK.effects.flashObject(self, 0xff0000, 300);
self.updateHealthBar();
// Create blood flow effect
for (var b = 0; b < 8; b++) {
var bloodDrop = LK.getAsset('trap', {
width: 12 + Math.random() * 8,
height: 12 + Math.random() * 8,
anchorX: 0.5,
anchorY: 0.5
});
bloodDrop.tint = 0xAA0000; // Dark red blood color
bloodDrop.x = self.x + (Math.random() - 0.5) * 60; // Random spread around hero
bloodDrop.y = self.y + (Math.random() - 0.5) * 40;
bloodDrop.alpha = 0.8 + Math.random() * 0.2;
game.addChild(bloodDrop);
// Animate blood drop falling and fading
var fallDistance = 80 + Math.random() * 120;
var fallDuration = 800 + Math.random() * 400;
tween(bloodDrop, {
y: bloodDrop.y + fallDistance,
alpha: 0,
scaleX: 0.3,
scaleY: 0.3
}, {
duration: fallDuration,
easing: tween.easeIn,
onFinish: function onFinish() {
bloodDrop.destroy();
}
});
}
if (self.health <= 0) {
// Play scream sound when hero dies
LK.getSound('scream').play();
LK.effects.flashScreen(0xff0000, 800);
// Create dungeon-themed game over overlay
var dungeonGameOverOverlay = new Container();
dungeonGameOverOverlay.zIndex = 10000; // ensure on top
// Dark background
var background = LK.getAsset('trap', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
background.tint = 0x000000;
background.alpha = 0.8;
dungeonGameOverOverlay.addChild(background);
// Main message
var gameOverText = new Text2("You couldn't pass the dungeon...", {
size: 120,
fill: 0xFF4444,
font: "Impact, Arial Black, Tahoma"
});
gameOverText.anchor.set(0.5, 0.5);
gameOverText.x = 2048 / 2;
gameOverText.y = 1200;
dungeonGameOverOverlay.addChild(gameOverText);
// Subtitle
var subtitleText = new Text2("The darkness consumed you in Room " + currentRoom, {
size: 80,
fill: 0x888888
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 1400;
dungeonGameOverOverlay.addChild(subtitleText);
// Score display
var finalScoreText = new Text2("Monsters Defeated: " + monstersDefeated, {
size: 70,
fill: 0xFFFF88
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 1600;
dungeonGameOverOverlay.addChild(finalScoreText);
game.addChild(dungeonGameOverOverlay);
// Auto restart after 3 seconds
LK.setTimeout(function () {
LK.showGameOver();
}, 3000);
}
};
// Heal
self.heal = function () {
if (self.health < self.maxHealth) {
self.health += 1;
self.updateHealthBar();
LK.effects.flashObject(self, 0x83de44, 300);
}
};
// Called every tick
self.update = function () {
if (self.invincible) {
self.invincibleTimer--;
if (self.invincibleTimer <= 0) {
self.invincible = false;
}
}
};
return self;
});
/****
* Asset Initialization
****/
// Hero: red box
// Monster: green ellipse
// Trap: yellow box
// Treasure: blue ellipse
// Door: purple box
// Key class
var Key = Container.expand(function () {
var self = Container.call(this);
// Attach key asset (gold ellipse)
var keySprite = self.attachAsset('key', {
anchorX: 0.5,
anchorY: 0.5
});
self.width = keySprite.width;
self.height = keySprite.height;
return self;
});
// Mage Ball projectile class
var MageBall = Container.expand(function () {
var self = Container.call(this);
// Create ball visual using trap asset
var ballSprite = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 60
});
ballSprite.tint = 0x8A2BE2; // Purple magic ball
self.width = ballSprite.width;
self.height = ballSprite.height;
// Ball properties
self.vx = 0;
self.vy = 0;
self.speed = 4;
self.lifetime = 300; // 5 seconds at 60fps
// Set direction toward hero
self.setDirection = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.vx = dx / dist * self.speed;
self.vy = dy / dist * self.speed;
}
};
// Update ball movement
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.lifetime--;
// Add magical sparkle effect
ballSprite.rotation += 0.2;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
return self;
});
// Base Monster class
var Monster = Container.expand(function () {
var self = Container.call(this);
// Attach monster asset (green ellipse)
var monsterSprite = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Set larger hitbox for easier collision detection
self.width = monsterSprite.width * 0.4;
self.height = monsterSprite.height * 0.4;
// Monster health system
self.maxHealth = 1;
self.health = self.maxHealth;
// Create heart-based health display
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744; // Red heart color
heart.x = startX + h * heartSpacing;
heart.y = -monsterSprite.height / 2 - 40; // Position above monster
self.addChild(heart);
self.hearts.push(heart);
}
// Update heart display
self.updateHealthBar = function () {
// Update heart visibility based on current health
for (var h = 0; h < self.hearts.length; h++) {
if (h < self.health) {
self.hearts[h].visible = true;
self.hearts[h].tint = 0xFF1744; // Red for full hearts
self.hearts[h].alpha = 1.0;
} else {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x444444; // Dark gray for empty hearts
self.hearts[h].alpha = 0.5;
}
}
};
// Initialize heart display
self.updateHealthBar();
// Take damage function with animation
self.takeDamage = function () {
self.health -= 1;
self.updateHealthBar();
// Flash damage animation
tween(monsterSprite, {
tint: 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(monsterSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Shake animation
var originalX = self.x;
tween(self, {
x: originalX + 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX - 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX
}, {
duration: 50
});
}
});
}
});
};
// Movement direction
self.vx = 0;
self.vy = 0;
self.speed = 0.3 + Math.random() * 0.4; // Reduced speed from 1-2 to 0.3-0.7
// Set random direction
self.setRandomDirection = function () {
var angle = Math.random() * Math.PI * 2;
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
// Called every tick
self.update = function () {
// Check if hero is close to this monster FIRST (within encounter distance)
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distToHero = Math.sqrt(dx * dx + dy * dy);
// If hero is within encounter distance, trigger freeze and stop moving
if (distToHero < 120) {
// Reduced from 200 to 120 for less aggressive encounter
if (!gameFrozen) {
// Trigger freeze effect
gameFrozen = true;
freezeTimer = FREEZE_DURATION;
freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage
console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME);
// Apply freeze visual effect to all characters
tween(hero, {
tint: 0x8888ff
}, {
duration: 300
});
tween(self, {
tint: 0x8888ff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] !== self) {
tween(monsters[m], {
tint: 0x8888ff
}, {
duration: 300
});
}
}
}
// Monster stays still when encountering hero
return;
}
// Don't move if game is frozen
if (gameFrozen) {
return;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
return self;
});
// NPC Wizard class - appears in first room to wish good luck
var NPCWizard = Container.expand(function () {
var self = Container.call(this);
// Attach wizard sprite
var wizardSprite = self.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
self.width = wizardSprite.width;
self.height = wizardSprite.height;
// Speech bubble properties
self.showingSpeech = false;
self.speechBubble = null;
self.speechText = null;
// Show speech bubble with good luck message
self.showSpeech = function () {
if (self.showingSpeech) return;
self.showingSpeech = true;
// Create speech bubble background
self.speechBubble = LK.getAsset('door', {
width: 400,
height: 180,
anchorX: 0.5,
anchorY: 1.0
});
self.speechBubble.tint = 0xFFFFFF;
self.speechBubble.alpha = 0.9;
self.speechBubble.x = 0;
self.speechBubble.y = -wizardSprite.height / 2 - 20;
self.addChild(self.speechBubble);
// Create speech text
self.speechText = new Text2("Good luck in the dungeon,\nbrave adventurer!", {
size: 45,
fill: 0xFFFFFF,
font: "Impact, Arial Black, Tahoma"
});
self.speechText.anchor.set(0.5, 0.5);
self.speechText.x = 0;
self.speechText.y = -wizardSprite.height / 2 - 110;
self.addChild(self.speechText);
// Auto-hide speech after 4 seconds
LK.setTimeout(function () {
self.hideSpeech();
}, 4000);
};
// Hide speech bubble
self.hideSpeech = function () {
if (!self.showingSpeech) return;
self.showingSpeech = false;
if (self.speechBubble) {
self.speechBubble.destroy();
self.speechBubble = null;
}
if (self.speechText) {
self.speechText.destroy();
self.speechText = null;
}
};
return self;
});
// Orc Warrior - Room 2
var OrcWarrior = Container.expand(function () {
var self = Container.call(this);
// Use orc asset instead of monster asset
var orcSprite = self.attachAsset('orc', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.4,
scaleY: 1.4
});
// Set larger hitbox for easier collision detection
self.width = orcSprite.width * 0.4;
self.height = orcSprite.height * 0.4;
// Monster health system
self.maxHealth = 2;
self.health = self.maxHealth;
self.speed = 0.4;
// Create heart-based health display
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -orcSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
// Update heart display
self.updateHealthBar = function () {
// Update heart visibility based on current health
for (var h = 0; h < self.hearts.length; h++) {
if (h < self.health) {
self.hearts[h].visible = true;
self.hearts[h].tint = 0xFF1744; // Red for full hearts
self.hearts[h].alpha = 1.0;
} else {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x444444; // Dark gray for empty hearts
self.hearts[h].alpha = 0.5;
}
}
};
// Take damage function with animation
self.takeDamage = function () {
self.health -= 1;
self.updateHealthBar();
// Flash damage animation
tween(orcSprite, {
tint: 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(orcSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Shake animation
var originalX = self.x;
tween(self, {
x: originalX + 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX - 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX
}, {
duration: 50
});
}
});
}
});
};
// Movement direction
self.vx = 0;
self.vy = 0;
// Set random direction
self.setRandomDirection = function () {
var angle = Math.random() * Math.PI * 2;
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
// Called every tick
self.update = function () {
// Check if hero is close to this monster FIRST (within encounter distance)
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distToHero = Math.sqrt(dx * dx + dy * dy);
// If hero is within encounter distance, trigger freeze and stop moving
if (distToHero < 120) {
// Reduced from 200 to 120 for less aggressive encounter
if (!gameFrozen) {
// Trigger freeze effect
gameFrozen = true;
freezeTimer = FREEZE_DURATION;
freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage
console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME);
// Apply freeze visual effect to all characters
tween(hero, {
tint: 0x8888ff
}, {
duration: 300
});
tween(self, {
tint: 0x8888ff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] !== self) {
tween(monsters[m], {
tint: 0x8888ff
}, {
duration: 300
});
}
}
}
// Monster stays still when encountering hero
return;
}
// Don't move if game is frozen
if (gameFrozen) {
return;
}
self.x += self.vx;
self.y += self.vy;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
// Update hearts for new health
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -orcSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
self.updateHealthBar();
return self;
});
// Skeleton Boss - Room 1
var SkeletonBoss = Container.expand(function () {
var self = Container.call(this);
// Use skeleton asset instead of monster asset
var skeletonSprite = self.attachAsset('skeleton', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
// Set larger hitbox for easier collision detection
self.width = skeletonSprite.width * 0.4;
self.height = skeletonSprite.height * 0.4;
// Monster health system
self.maxHealth = 1;
self.health = self.maxHealth;
self.speed = 0.5;
// Create heart-based health display
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -skeletonSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
// Update heart display
self.updateHealthBar = function () {
// Update heart visibility based on current health
for (var h = 0; h < self.hearts.length; h++) {
if (h < self.health) {
self.hearts[h].visible = true;
self.hearts[h].tint = 0xFF1744; // Red for full hearts
self.hearts[h].alpha = 1.0;
} else {
self.hearts[h].visible = true;
self.hearts[h].tint = 0x444444; // Dark gray for empty hearts
self.hearts[h].alpha = 0.5;
}
}
};
// Take damage function with animation
self.takeDamage = function () {
self.health -= 1;
self.updateHealthBar();
// Flash damage animation
tween(skeletonSprite, {
tint: 0xFF0000
}, {
duration: 150,
onFinish: function onFinish() {
tween(skeletonSprite, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Shake animation
var originalX = self.x;
tween(self, {
x: originalX + 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX - 15
}, {
duration: 50,
onFinish: function onFinish() {
tween(self, {
x: originalX
}, {
duration: 50
});
}
});
}
});
};
// Movement direction
self.vx = 0;
self.vy = 0;
// Set random direction
self.setRandomDirection = function () {
var angle = Math.random() * Math.PI * 2;
self.vx = Math.cos(angle) * self.speed;
self.vy = Math.sin(angle) * self.speed;
};
self.setRandomDirection();
// Shooting timer
self.shootTimer = 0;
self.shootInterval = 180; // Shoot every 3 seconds at 60fps (reduced frequency)
self.shootCooldown = 0; // Cooldown timer after shooting
self.cooldownDuration = 120; // 2 seconds at 60fps
// Called every tick
self.update = function () {
// Check if hero is close to this monster FIRST (within encounter distance)
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distToHero = Math.sqrt(dx * dx + dy * dy);
// If hero is within encounter distance, trigger freeze and stop moving
if (distToHero < 120) {
// Reduced from 200 to 120 for less aggressive encounter
if (!gameFrozen) {
// Trigger freeze effect
gameFrozen = true;
freezeTimer = FREEZE_DURATION;
freezeBuffer = FREEZE_BUFFER_TIME; // Set buffer to prevent immediate damage
console.log("Freeze triggered! Buffer set to:", FREEZE_BUFFER_TIME);
// Apply freeze visual effect to all characters
tween(hero, {
tint: 0x8888ff
}, {
duration: 300
});
tween(self, {
tint: 0x8888ff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
if (monsters[m] !== self) {
tween(monsters[m], {
tint: 0x8888ff
}, {
duration: 300
});
}
}
}
// Monster stays still when encountering hero
return;
}
// Don't move if game is frozen
if (gameFrozen) {
return;
}
// Update cooldown timer
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Skeleton boss no longer shoots red projectiles - shooting behavior disabled
if (self.shootCooldown <= 0) {
self.shootTimer++;
if (self.shootTimer >= self.shootInterval) {
self.shootTimer = 0;
self.shootCooldown = self.cooldownDuration; // Start 2-second cooldown
// Stone projectile creation removed - boss no longer shoots
// Visual effect when would have shot
tween(skeletonSprite, {
scaleX: 1.4,
scaleY: 1.4
}, {
duration: 250,
onFinish: function onFinish() {
tween(skeletonSprite, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 250
});
}
});
}
}
self.x += self.vx;
self.y += self.vy;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
// Skeleton stats
self.maxHealth = 1;
self.health = self.maxHealth;
self.speed = 0.5;
// Update hearts for new health
self.hearts = [];
var heartSize = 30;
var heartSpacing = 35;
var totalHeartsWidth = (self.maxHealth - 1) * heartSpacing + heartSize;
var startX = -totalHeartsWidth / 2;
for (var h = 0; h < self.maxHealth; h++) {
var heart = LK.getAsset('trap', {
width: heartSize,
height: heartSize,
anchorX: 0.5,
anchorY: 0.5
});
heart.tint = 0xFF1744;
heart.x = startX + h * heartSpacing;
heart.y = -skeletonSprite.height / 2 - 40;
self.addChild(heart);
self.hearts.push(heart);
}
self.updateHealthBar();
return self;
});
// Stone projectile class for skeleton boss
var SkeletonStone = Container.expand(function () {
var self = Container.call(this);
// Create stone visual using trap asset
var stoneSprite = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
stoneSprite.tint = 0x555555; // Gray stone color
self.width = stoneSprite.width;
self.height = stoneSprite.height;
// Stone properties
self.vx = 0;
self.vy = 0;
self.speed = 3.5;
self.lifetime = 350; // Slightly longer than mage balls
// Set direction toward hero
self.setDirection = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.vx = dx / dist * self.speed;
self.vy = dy / dist * self.speed;
}
};
// Update stone movement
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.lifetime--;
// Add stone spinning effect
stoneSprite.rotation += 0.15;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
return self;
});
// Stun Orb projectile class for Fire Demon stun attacks
var StunOrb = Container.expand(function () {
var self = Container.call(this);
// Create orb visual using trap asset
var orbSprite = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 70
});
orbSprite.tint = 0x9932CC; // Dark purple stun orb
self.width = orbSprite.width;
self.height = orbSprite.height;
// Orb properties
self.vx = 0;
self.vy = 0;
self.speed = 3;
self.lifetime = 400; // ~6.7 seconds at 60fps
// Set direction toward hero
self.setDirection = function (targetX, targetY) {
var dx = targetX - self.x;
var dy = targetY - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
self.vx = dx / dist * self.speed;
self.vy = dy / dist * self.speed;
}
};
// Update orb movement
self.update = function () {
self.x += self.vx;
self.y += self.vy;
self.lifetime--;
// Add magical pulsing effect
orbSprite.rotation += 0.2;
orbSprite.scaleX = 0.9 + Math.sin(LK.ticks * 0.3) * 0.1;
orbSprite.scaleY = 0.9 + Math.cos(LK.ticks * 0.25) * 0.1;
// Bounce off room walls
if (self.x < roomBounds.x + self.width / 2) {
self.x = roomBounds.x + self.width / 2;
self.vx *= -1;
}
if (self.x > roomBounds.x + roomBounds.width - self.width / 2) {
self.x = roomBounds.x + roomBounds.width - self.width / 2;
self.vx *= -1;
}
if (self.y < roomBounds.y + self.height / 2) {
self.y = roomBounds.y + self.height / 2;
self.vy *= -1;
}
if (self.y > roomBounds.y + roomBounds.height - self.height / 2) {
self.y = roomBounds.y + roomBounds.height - self.height / 2;
self.vy *= -1;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2C1810
});
/****
* Game Code
****/
// Create dungeon floor texture with pixel-by-pixel stone pattern
var dungeonFloor = new Container();
var stoneColors = [0x4A4A4A, 0x3A3A3A, 0x5A5A5A, 0x2A2A2A, 0x6A6A6A]; // Various gray stone colors
var mossColors = [0x2D4A2D, 0x1F3A1F, 0x3D5A3D]; // Mossy accents
var dirtColors = [0x4A3A2A, 0x5A4A3A, 0x3A2A1A]; // Dirt between stones
// Create pixelated stone floor
for (var row = 0; row < 68; row++) {
// Cover full height (2732/40 = ~68 rows)
for (var col = 0; col < 52; col++) {
// Cover full width (2048/40 = ~52 cols)
var pixelSize = 40;
var x = col * pixelSize;
var y = row * pixelSize;
// Determine pixel type based on position for realistic stone pattern
var isStone = (row + col) % 3 !== 0; // Most pixels are stone
var isMoss = Math.random() < 0.05; // 5% chance for moss
var isDirt = Math.random() < 0.08; // 8% chance for dirt
var color;
if (isMoss) {
color = mossColors[Math.floor(Math.random() * mossColors.length)];
} else if (isDirt) {
color = dirtColors[Math.floor(Math.random() * dirtColors.length)];
} else {
color = stoneColors[Math.floor(Math.random() * stoneColors.length)];
}
var pixel = LK.getAsset('trap', {
width: pixelSize,
height: pixelSize,
anchorX: 0,
anchorY: 0,
x: x,
y: y
});
pixel.tint = color;
dungeonFloor.addChild(pixel);
}
// Update lighting to follow hero
if (lightingOverlay && hero) {
// Update light position to follow hero
var playerLight = lightingOverlay.children[1]; // Second child is the main light
var innerLight = lightingOverlay.children[2]; // Third child is the inner light
if (playerLight) {
playerLight.x = hero.x;
playerLight.y = hero.y;
}
if (innerLight) {
innerLight.x = hero.x;
innerLight.y = hero.y;
}
}
}
;
// Add dungeon floor as background layer
game.addChildAt(dungeonFloor, 0); // Add at bottom layer
// Create lighting system - dark overlay with light behind player
var lightingOverlay = new Container();
lightingOverlay.zIndex = 1000; // Above most game elements but below UI
// Create dark background overlay
var darkOverlay = LK.getAsset('trap', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
darkOverlay.tint = 0x000000; // Black overlay
darkOverlay.alpha = 0.7; // Semi-transparent to create darkness
lightingOverlay.addChild(darkOverlay);
// Create light circle behind player
var playerLight = LK.getAsset('trap', {
width: 600,
height: 600,
anchorX: 0.5,
anchorY: 0.5
});
playerLight.tint = 0xFFFFFF; // White light
playerLight.alpha = 0.8; // Light intensity
// Create soft light gradient effect
var innerLight = LK.getAsset('trap', {
width: 300,
height: 300,
anchorX: 0.5,
anchorY: 0.5
});
innerLight.tint = 0xFFFFFF;
innerLight.alpha = 0.9;
lightingOverlay.addChild(playerLight);
lightingOverlay.addChild(innerLight);
// Add lighting overlay to game
game.addChild(lightingOverlay);
// Tween plugin for animations
// Language system
var selectedLanguage = storage.language || null; // Get saved language or null if first time
var languageTexts = {
english: {
welcome: "Welcome, Hero!",
title: "Brave Adventurer",
story: "Enter the dark dungeon and face legendary monsters.\nDefeat bosses, collect treasures, and prove your courage!",
subtitle: "Tap to move and attack. Collect keys from chests to proceed!",
startButton: "START ADVENTURE",
instructions: "Tap the button above to begin your quest",
attack: "ATTACK",
heal: "HEAL",
ultimate: "ULTIMATE",
openChest: "OPEN CHEST",
score: "Score: ",
room: "Room ",
health: "Health: Full",
noHeals: "NO HEALS",
gameOver: "You couldn't pass the dungeon...",
gameOverSubtitle: "The darkness consumed you in Room ",
monstersDefeated: "Monsters Defeated: ",
selectLanguage: "Select Language",
languageInstructions: "Choose your preferred language"
},
turkish: {
welcome: "Hoş Geldin, Kahraman!",
title: "Cesur Maceracı",
story: "Karanlık zindana gir ve efsanevi canavarlarla yüzleş.\nBosları yen, hazineler topla ve cesaretini kanıtla!",
subtitle: "Hareket etmek ve saldırmak için dokun. İlerlemek için sandıklardan anahtarları topla!",
startButton: "MACERAYA BAŞLA",
instructions: "Maceranıza başlamak için yukarıdaki düğmeye dokunun",
attack: "SALDIRI",
heal: "İYİLEŞ",
ultimate: "ÜST GÜÇLER",
openChest: "SANDIĞI AÇ",
score: "Skor: ",
room: "Oda ",
health: "Sağlık: Tam",
noHeals: "İYİLEŞME YOK",
gameOver: "Zindandan geçemedin...",
gameOverSubtitle: "Karanlık seni Oda ",
monstersDefeated: "Yenilen Canavarlar: ",
selectLanguage: "Dil Seçin",
languageInstructions: "Tercih ettiğiniz dili seçin"
}
};
var currentTexts = selectedLanguage ? languageTexts[selectedLanguage] : languageTexts.english;
// Language Selection Screen (only shown if no language is saved)
var languageSelectionOverlay = null;
if (!selectedLanguage) {
languageSelectionOverlay = new Container();
languageSelectionOverlay.zIndex = 15000; // Above start screen
// Dark background
var langBackground = LK.getAsset('trap', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
langBackground.tint = 0x000000;
langBackground.alpha = 0.9;
languageSelectionOverlay.addChild(langBackground);
// Title
var langTitle = new Text2("Select Language / Dil Seçin", {
size: 140,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
langTitle.anchor.set(0.5, 0.5);
langTitle.x = 2048 / 2;
langTitle.y = 600;
languageSelectionOverlay.addChild(langTitle);
// Instructions
var langInstructions = new Text2("Choose your preferred language\nTercih ettiğiniz dili seçin", {
size: 80,
fill: 0xCCCCCC,
font: "Impact, Arial Black, Tahoma"
});
langInstructions.anchor.set(0.5, 0.5);
langInstructions.x = 2048 / 2;
langInstructions.y = 900;
languageSelectionOverlay.addChild(langInstructions);
// English Button
var englishButton = LK.getAsset('door', {
width: 600,
height: 200,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 - 350,
y: 1400
});
englishButton.tint = 0x4A8B3B;
languageSelectionOverlay.addChild(englishButton);
var englishButtonText = new Text2("ENGLISH", {
size: 100,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
englishButtonText.anchor.set(0.5, 0.5);
englishButtonText.x = 2048 / 2 - 350;
englishButtonText.y = 1400;
languageSelectionOverlay.addChild(englishButtonText);
// Turkish Button
var turkishButton = LK.getAsset('door', {
width: 600,
height: 200,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2 + 350,
y: 1400
});
turkishButton.tint = 0x8B3A3A;
languageSelectionOverlay.addChild(turkishButton);
var turkishButtonText = new Text2("TÜRKÇE", {
size: 100,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
turkishButtonText.anchor.set(0.5, 0.5);
turkishButtonText.x = 2048 / 2 + 350;
turkishButtonText.y = 1400;
languageSelectionOverlay.addChild(turkishButtonText);
// Add animations
tween(englishButton, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 1000,
loop: true,
pingpong: true
});
tween(turkishButton, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 1000,
loop: true,
pingpong: true,
delay: 500
});
game.addChild(languageSelectionOverlay);
}
// --- Start Screen Overlay ---
var startScreenOverlay = new Container();
startScreenOverlay.zIndex = 10000; // ensure on top
// Dark background for better text readability
var startBackground = LK.getAsset('trap', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
startBackground.tint = 0x000000;
startBackground.alpha = 0.85;
startScreenOverlay.addChild(startBackground);
// Welcome message
var welcomeText = new Text2(currentTexts.welcome, {
size: 120,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
welcomeText.anchor.set(0.5, 0.5);
welcomeText.x = 2048 / 2;
welcomeText.y = 600;
startScreenOverlay.addChild(welcomeText);
// Title
var titleText = new Text2(currentTexts.title, {
size: 180,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 2048 / 2;
titleText.y = 800;
startScreenOverlay.addChild(titleText);
// Story introduction
var storyText = new Text2(currentTexts.story, {
size: 60,
fill: 0xCCCCCC,
font: "Impact, Arial Black, Tahoma"
});
storyText.anchor.set(0.5, 0.5);
storyText.x = 2048 / 2;
storyText.y = 1000;
startScreenOverlay.addChild(storyText);
// Subtitle
var subtitleText = new Text2(currentTexts.subtitle, {
size: 70,
fill: 0xB8B031
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 1200;
startScreenOverlay.addChild(subtitleText);
// Play Button
var playBtnWidth = 600;
var playBtnHeight = 180;
var playBtn = LK.getAsset('door', {
width: playBtnWidth,
height: playBtnHeight,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 1600
});
playBtn.tint = 0x4A8B3B; // Green tint for play button
startScreenOverlay.addChild(playBtn);
var playBtnText = new Text2(currentTexts.startButton, {
size: 90,
fill: "#fff",
font: "Impact, Arial Black, Tahoma"
});
playBtnText.anchor.set(0.5, 0.5);
playBtnText.x = 2048 / 2;
playBtnText.y = 1600;
startScreenOverlay.addChild(playBtnText);
// Add pulsing animation to play button
tween(playBtn, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
loop: true,
pingpong: true
});
tween(playBtnText, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 800,
loop: true,
pingpong: true
});
// Instructions text
var instructionsText = new Text2(currentTexts.instructions, {
size: 50,
fill: 0x888888,
font: "Impact, Arial Black, Tahoma"
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 2048 / 2;
instructionsText.y = 1800;
startScreenOverlay.addChild(instructionsText);
// Add overlay to game
game.addChild(startScreenOverlay);
// Block game input until started
var gameStarted = false;
var languageSelected = selectedLanguage ? true : false;
// Language selection handler
function selectLanguage(lang) {
if (languageSelected) return;
selectedLanguage = lang;
storage.language = lang; // Save language preference
languageSelected = true;
currentTexts = languageTexts[lang];
// Update all text elements with new language
welcomeText.setText(currentTexts.welcome);
titleText.setText(currentTexts.title);
storyText.setText(currentTexts.story);
subtitleText.setText(currentTexts.subtitle);
playBtnText.setText(currentTexts.startButton);
instructionsText.setText(currentTexts.instructions);
// Update UI elements
killButtonText.setText(currentTexts.attack);
healButtonText.setText(currentTexts.heal);
ultimateButtonText.setText(currentTexts.ultimate);
openChestText.setText(currentTexts.openChest);
scoreTxt.setText(currentTexts.score + "0");
roomTxt.setText(currentTexts.room + "1");
healthTxt.setText(currentTexts.health);
// Fade out language selection screen
if (languageSelectionOverlay && languageSelectionOverlay.visible !== false) {
tween(languageSelectionOverlay, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
languageSelectionOverlay.visible = false;
}
});
}
// Flash screen to indicate language selection
LK.effects.flashScreen(0x4A8B3B, 300);
}
// Start game handler
function startGame() {
if (gameStarted || !languageSelected) return;
gameStarted = true;
// Fade out start screen with smooth transition
tween(startScreenOverlay, {
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
startScreenOverlay.visible = false;
startScreenOverlay.alpha = 1; // Reset for potential restart
}
});
// Flash screen to indicate game start
LK.effects.flashScreen(0x4A8B3B, 300);
}
// Listen for tap/click on language buttons
if (languageSelectionOverlay) {
languageSelectionOverlay.down = function (x, y, obj) {
// Check English button
var englishLocal = englishButton.toLocal(languageSelectionOverlay.toGlobal({
x: x,
y: y
}));
if (englishLocal.x > -300 && englishLocal.x < 300 && englishLocal.y > -100 && englishLocal.y < 100) {
selectLanguage('english');
return;
}
// Check Turkish button
var turkishLocal = turkishButton.toLocal(languageSelectionOverlay.toGlobal({
x: x,
y: y
}));
if (turkishLocal.x > -300 && turkishLocal.x < 300 && turkishLocal.y > -100 && turkishLocal.y < 100) {
selectLanguage('turkish');
return;
}
};
}
// Listen for tap/click on play button
startScreenOverlay.down = function (x, y, obj) {
// Check if tap is inside play button
var local = playBtn.toLocal(startScreenOverlay.toGlobal({
x: x,
y: y
}));
if (local.x > -playBtnWidth / 2 && local.x < playBtnWidth / 2 && local.y > -playBtnHeight / 2 && local.y < playBtnHeight / 2) {
startGame();
}
};
// Store original game handlers before any interception
var originalGameDown = null;
var originalGameMove = null;
var originalGameUp = null;
// Intercept all input until game started
game.down = function (x, y, obj) {
if (!languageSelected && languageSelectionOverlay && languageSelectionOverlay.visible) {
if (typeof languageSelectionOverlay.down === "function") languageSelectionOverlay.down(x, y, obj);
return;
}
if (!gameStarted && startScreenOverlay && startScreenOverlay.visible) {
if (typeof startScreenOverlay.down === "function") startScreenOverlay.down(x, y, obj);
return;
}
// Call the actual game down handler defined later
if (typeof originalGameDown === "function") originalGameDown(x, y, obj);
};
game.move = function (x, y, obj) {
if (!languageSelected && languageSelectionOverlay && languageSelectionOverlay.visible || !gameStarted && startScreenOverlay && startScreenOverlay.visible) return;
// Call the actual game move handler defined later
if (typeof originalGameMove === "function") originalGameMove(x, y, obj);
};
game.up = function (x, y, obj) {
if (!languageSelected && languageSelectionOverlay && languageSelectionOverlay.visible || !gameStarted && startScreenOverlay && startScreenOverlay.visible) return;
// Call the actual game up handler defined later
if (typeof originalGameUp === "function") originalGameUp(x, y, obj);
};
// Room bounds (centered, with margin)
var roomMargin = 120;
var roomBounds = {
x: roomMargin,
y: roomMargin + 100,
// leave top 100px for menu
width: 2048 - roomMargin * 2,
height: 2732 - roomMargin * 2 - 100
};
// Game state
var hero;
var monsters = [];
var mageBalls = []; // Track mage projectiles
var skeletonStones = []; // Track skeleton stone projectiles
var groundFires = []; // Track ground fire projectiles
var stunOrbs = []; // Track demon stun orbs
var door;
var keys = [];
var chests = [];
var hasKey = false;
var draggingHero = false;
var lastMoveX = 0,
lastMoveY = 0;
var currentRoom = 1;
var maxRooms = 5;
var monstersDefeated = 0;
var roomCleared = false;
var gameFrozen = false;
var freezeTimer = 0;
var FREEZE_DURATION = 120; // 2 seconds at 60fps
var freezeBuffer = 0; // Buffer to prevent immediate damage after freeze
var FREEZE_BUFFER_TIME = 30; // 30 frames buffer after freeze starts (0.5 seconds)
var isStunned = false; // Hero stun state
var stunTimer = 0; // Stun timer
var STUN_DURATION = 60; // 1 second at 60fps
var stunnedText = null; // Reference to stunned text display
var healUsesRemaining = 3; // Player can use heal 3 times total
var ultimateTimer = 0; // Timer for ultimate button availability
var ultimateInterval = 600; // 10 seconds at 60fps
var ultimateAvailable = false; // Whether ultimate button should be shown
// GUI elements
var scoreTxt = new Text2('Score: 0', {
size: 90,
fill: "#fff"
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.y = 20;
LK.gui.top.addChild(scoreTxt);
// Room indicator
var roomTxt = new Text2('Room 1', {
size: 80,
fill: 0xFFD700
});
roomTxt.anchor.set(0, 0);
roomTxt.x = 120;
roomTxt.y = 20;
LK.gui.topLeft.addChild(roomTxt);
// Health indicator text
var healthTxt = new Text2('Health: Full', {
size: 70,
fill: 0xFF4444
});
healthTxt.anchor.set(1, 0);
healthTxt.x = -20;
healthTxt.y = 20;
LK.gui.topRight.addChild(healthTxt);
// Helper to update health display - now updates the health bar on the character
function updateHealthDisplay() {
// This will be handled by the health bar on the character
}
// Helper to update score display
function updateScoreDisplay() {
var score = monstersDefeated * 5;
scoreTxt.setText(currentTexts.score + score);
}
// Helper to update room display
function updateRoomDisplay() {
roomTxt.setText(currentTexts.room + currentRoom);
}
// Helper to update health display
// Helper to clear room
function clearRoom() {
for (var i = 0; i < monsters.length; i++) monsters[i].destroy();
for (var i = 0; i < keys.length; i++) keys[i].destroy();
for (var i = 0; i < chests.length; i++) chests[i].destroy();
for (var i = 0; i < mageBalls.length; i++) mageBalls[i].destroy();
for (var i = 0; i < skeletonStones.length; i++) skeletonStones[i].destroy();
for (var i = 0; i < groundFires.length; i++) groundFires[i].destroy();
for (var i = 0; i < stunOrbs.length; i++) stunOrbs[i].destroy();
monsters = [];
keys = [];
chests = [];
mageBalls = [];
skeletonStones = [];
groundFires = [];
stunOrbs = [];
if (door) {
door.destroy();
door = null;
}
// Clear NPC reference if it exists
if (game.npcWizard) {
game.npcWizard.destroy();
game.npcWizard = null;
}
roomCleared = false;
hasKey = false;
}
// Helper to generate a room
function generateRoom(roomNum) {
clearRoom();
// Play appropriate music based on room number
if (roomNum === 1) {
LK.playMusic('dungeon');
} else {
LK.playMusic('rock');
}
// Place hero at entrance (bottom center)
hero.x = roomBounds.x + roomBounds.width / 2;
hero.y = roomBounds.y + roomBounds.height - hero.height;
// Helper function to check if position is safe from hero spawn area
function isPositionSafeFromHeroSpawn(x, y, heroStartX, heroStartY, safeDistance) {
var dx = x - heroStartX;
var dy = y - heroStartY;
var dist = Math.sqrt(dx * dx + dy * dy);
return dist > safeDistance;
}
// Place monsters - exactly 1 boss monster per room based on room number
var monsterCount = 1;
var heroStartX = roomBounds.x + roomBounds.width / 2;
var heroStartY = roomBounds.y + roomBounds.height - hero.height;
var heroSafeDistance = 400; // Increased safe distance around hero spawn
for (var i = 0; i < monsterCount; i++) {
var m;
// Create different boss types based on room number
if (roomNum === 1) {
m = new SkeletonBoss();
} else if (roomNum === 2) {
m = new OrcWarrior();
} else if (roomNum === 3) {
m = new DarkMage();
} else if (roomNum === 4) {
m = new FireDemon();
} else if (roomNum === 5) {
m = new DragonLord();
} else {
// Fallback for any additional rooms
m = new Monster();
}
var validPosition = false;
var attempts = 0;
// Try to find a position that's not too close to hero spawn, door, chest, or NPC
while (!validPosition && attempts < 50) {
m.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200);
m.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400);
// Check distance from hero spawn
var safeFromHero = isPositionSafeFromHeroSpawn(m.x, m.y, heroStartX, heroStartY, heroSafeDistance);
// Check distance from door position (top center)
var doorX = roomBounds.x + roomBounds.width / 2;
var doorY = roomBounds.y + 150; // Door height/2 + some margin
var doorSafeDistance = 300; // Safe distance from door
var safeFromDoor = isPositionSafeFromHeroSpawn(m.x, m.y, doorX, doorY, doorSafeDistance);
// Check distance from chest position (will be placed later, but use same safe distance)
var chestSafeDistance = 350; // Safe distance from chest
var safeFromChest = true; // Will check against actual chest position after it's placed
// Check distance from NPC position (only in room 1)
var safeFromNPC = true;
if (roomNum === 1) {
var npcX = roomBounds.x + 200; // NPC wizard position
var npcY = roomBounds.y + roomBounds.height / 2;
var npcSafeDistance = 400; // Safe distance from NPC
safeFromNPC = isPositionSafeFromHeroSpawn(m.x, m.y, npcX, npcY, npcSafeDistance);
}
if (safeFromHero && safeFromDoor && safeFromNPC) {
validPosition = true;
}
attempts++;
}
monsters.push(m);
game.addChild(m);
}
// Key will come from chest after killing all monsters, so don't place it randomly
// Place chest (always one per room)
var c = new Chest();
var validPosition = false;
var attempts = 0;
while (!validPosition && attempts < 50) {
c.x = roomBounds.x + 100 + Math.random() * (roomBounds.width - 200);
c.y = roomBounds.y + 200 + Math.random() * (roomBounds.height - 400);
// Check distance from hero spawn
var safeFromHero = isPositionSafeFromHeroSpawn(c.x, c.y, heroStartX, heroStartY, heroSafeDistance);
// Check distance from all existing monsters
var safeFromMonsters = true;
for (var m = 0; m < monsters.length; m++) {
var dx = c.x - monsters[m].x;
var dy = c.y - monsters[m].y;
var distToMonster = Math.sqrt(dx * dx + dy * dy);
if (distToMonster < 350) {
// Safe distance from monsters
safeFromMonsters = false;
break;
}
}
// Check distance from NPC (only in room 1)
var safeFromNPC = true;
if (roomNum === 1) {
var npcX = roomBounds.x + 200;
var npcY = roomBounds.y + roomBounds.height / 2;
safeFromNPC = isPositionSafeFromHeroSpawn(c.x, c.y, npcX, npcY, 300);
}
if (safeFromHero && safeFromMonsters && safeFromNPC) {
validPosition = true;
}
attempts++;
}
chests.push(c);
game.addChild(c);
// Place door (top center)
door = new Door();
door.x = roomBounds.x + roomBounds.width / 2;
door.y = roomBounds.y + door.height / 2;
game.addChild(door);
door.visible = false; // Only show when room is cleared
// Place NPC wizard only in first room
if (roomNum === 1) {
var npcWizard = new NPCWizard();
npcWizard.x = roomBounds.x + 200; // Position near left wall
npcWizard.y = roomBounds.y + roomBounds.height / 2; // Center vertically
game.addChild(npcWizard);
// Store reference for proximity check
game.npcWizard = npcWizard;
}
roomCleared = false;
updateRoomDisplay();
updateHealthDisplay();
}
// Create hero
hero = new Hero();
game.addChild(hero);
// Start first room
generateRoom(currentRoom);
updateScoreDisplay();
// Store this as the original game up handler
originalGameUp = function originalGameUp(x, y, obj) {
draggingHero = false;
};
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
// Walk-to destination variables
var walkToActive = false;
var walkToX = 0;
var walkToY = 0;
var walkToSpeed = 22;
// Attack button for attacking monsters - round dungeon-themed button
var killButton = LK.getAsset('monster', {
width: 280,
height: 280,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 160,
y: 2732 - 160
});
killButton.tint = 0x4A4A4A; // Dark steel gray for shield-like appearance
killButton.alpha = 0.9; // Slightly transparent for better integration
game.addChild(killButton);
var killButtonText = new Text2("ATTACK", {
size: 60,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
killButtonText.anchor.set(0.5, 0.5);
killButtonText.x = 2048 - 160;
killButtonText.y = 2732 - 160;
game.addChild(killButtonText);
// Heal pot button next to attack button
var healButton = LK.getAsset('chest', {
width: 280,
height: 280,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 460,
// Position to the left of attack button
y: 2732 - 160
});
healButton.tint = 0xFF4444; // Red tint for health potion
healButton.alpha = 0.9;
game.addChild(healButton);
var healButtonText = new Text2("HEAL", {
size: 60,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
healButtonText.anchor.set(0.5, 0.5);
healButtonText.x = 2048 - 460;
healButtonText.y = 2732 - 160;
game.addChild(healButtonText);
// Ultimate button next to heal button
var ultimateButton = LK.getAsset('sword', {
width: 280,
height: 280,
anchorX: 0.5,
anchorY: 0.5,
x: 2048 - 760,
// Position to the left of heal button
y: 2732 - 160
});
ultimateButton.tint = 0xFF8800; // Orange tint for ultimate ability
ultimateButton.alpha = 0.9;
ultimateButton.visible = false; // Initially hidden
game.addChild(ultimateButton);
var ultimateButtonText = new Text2("ULTIMATE", {
size: 50,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
ultimateButtonText.anchor.set(0.5, 0.5);
ultimateButtonText.x = 2048 - 760;
ultimateButtonText.y = 2732 - 160;
ultimateButtonText.visible = false; // Initially hidden
game.addChild(ultimateButtonText);
// Open chest text
var openChestText = new Text2("OPEN CHEST", {
size: 80,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
openChestText.anchor.set(0.5, 0.5);
openChestText.x = 2048 / 2;
openChestText.y = 2732 - 200;
openChestText.visible = false;
game.addChild(openChestText);
// Sword swing state management
var swordSwinging = false;
var swordCooldown = 0;
var SWORD_COOLDOWN_TIME = 15; // frames (0.25 seconds at 60fps)
// Store this as the original game move handler
originalGameMove = function originalGameMove(x, y, obj) {
if (draggingHero && !isStunned) {
// Prevent movement when stunned
// Clamp hero inside room bounds
var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2);
var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2);
hero.x = hx;
hero.y = hy;
walkToActive = false; // Cancel walk-to if dragging
}
};
// Initialize originalGameDown properly before it's used
if (!originalGameDown) {
originalGameDown = function originalGameDown(x, y, obj) {
// Don't allow any actions when stunned
if (isStunned) return;
// Only start drag if touch is on hero
var local = hero.toLocal(game.toGlobal({
x: x,
y: y
}));
// Check if heal button was tapped
var healButtonLocal = healButton.toLocal(game.toGlobal({
x: x,
y: y
}));
if (healButtonLocal.x > -healButton.width / 2 && healButtonLocal.x < healButton.width / 2 && healButtonLocal.y > -healButton.height / 2 && healButtonLocal.y < healButton.height / 2) {
// Check if we have heal uses remaining
if (healUsesRemaining > 0) {
// Use heal function from hero
hero.heal();
healUsesRemaining--;
}
return; // Don't process other touch logic
}
// Check if ultimate button was tapped
var ultimateButtonLocal = ultimateButton.toLocal(game.toGlobal({
x: x,
y: y
}));
if (ultimateAvailable && ultimateButtonLocal.x > -ultimateButton.width / 2 && ultimateButtonLocal.x < ultimateButton.width / 2 && ultimateButtonLocal.y > -ultimateButton.height / 2 && ultimateButtonLocal.y < ultimateButton.height / 2) {
// Use ultimate ability - damage all monsters on screen and heal hero
for (var i = monsters.length - 1; i >= 0; i--) {
var m = monsters[i];
m.takeDamage();
// Check if monster is dead
if (m.health <= 0) {
// Play win sound for boss defeat
LK.playMusic('win');
// Remove monster
m.destroy();
monsters.splice(i, 1);
monstersDefeated++;
updateScoreDisplay();
}
}
// Heal hero to full health
hero.health = hero.maxHealth;
hero.updateHealthBar();
// Visual effects
LK.effects.flashScreen(0xFFD700, 500);
LK.effects.flashObject(hero, 0xFFD700, 500);
// Reset ultimate timer
ultimateTimer = 0;
ultimateAvailable = false;
ultimateButton.visible = false;
ultimateButtonText.visible = false;
return; // Don't process other touch logic
}
// Check if kill button was tapped
var killButtonLocal = killButton.toLocal(game.toGlobal({
x: x,
y: y
}));
if (killButtonLocal.x > -killButton.width / 2 && killButtonLocal.x < killButton.width / 2 && killButtonLocal.y > -killButton.height / 2 && killButtonLocal.y < killButton.height / 2) {
// Check if sword is available (not swinging and cooldown expired)
if (!swordSwinging && swordCooldown <= 0) {
// Get sword sprite from hero for animation
var swordSprite = hero.children[1]; // Sword is the second child (index 1)
// Stop any existing tweens on the sword
tween.stop(swordSprite, {
rotation: true
});
// Set swinging state
swordSwinging = true;
swordCooldown = SWORD_COOLDOWN_TIME;
// Create sword swing effect
var originalRotation = swordSprite.rotation;
tween(swordSprite, {
rotation: originalRotation + Math.PI / 2
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(swordSprite, {
rotation: originalRotation
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
swordSwinging = false;
}
});
}
});
// Kill button tapped - damage all monsters within 350px of hero
for (var i = monsters.length - 1; i >= 0; i--) {
var m = monsters[i];
var dx = hero.x - m.x;
var dy = hero.y - m.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 500) {
// Deal damage to monster
m.takeDamage();
// Optional: flash hero for feedback
LK.effects.flashObject(hero, 0xffffff, 120);
// Check if monster is dead
if (m.health <= 0) {
// Play win sound for boss defeat
LK.playMusic('win');
// Remove monster
m.destroy();
monsters.splice(i, 1);
monstersDefeated++;
updateScoreDisplay();
}
// Unfreeze game when monster dies
if (gameFrozen) {
gameFrozen = false;
freezeTimer = 0;
// Remove freeze visual effects
tween(hero, {
tint: 0xffffff
}, {
duration: 300
});
for (var j = 0; j < monsters.length; j++) {
tween(monsters[j], {
tint: 0xffffff
}, {
duration: 300
});
}
}
}
}
}
return; // Don't process other touch logic
}
if (local.x > -hero.width / 2 && local.x < hero.width / 2 && local.y > -hero.height / 2 && local.y < hero.height / 2) {
draggingHero = true;
lastMoveX = x;
lastMoveY = y;
walkToActive = false;
} else {
// Set walk-to destination if tap is inside room
var hx = clamp(x, roomBounds.x + hero.width / 2, roomBounds.x + roomBounds.width - hero.width / 2);
var hy = clamp(y, roomBounds.y + hero.height / 2, roomBounds.y + roomBounds.height - hero.height / 2);
walkToX = hx;
walkToY = hy;
walkToActive = true;
}
// Original handler logic is now properly stored and called
};
}
// In update, move hero toward walk-to destination if active
var oldGameUpdate = game.update;
game.update = function () {
// Walk-to logic
if (walkToActive && !draggingHero && !isStunned) {
// Prevent walk-to when stunned
var dx = walkToX - hero.x;
var dy = walkToY - hero.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > walkToSpeed) {
hero.x += dx / dist * walkToSpeed;
hero.y += dy / dist * walkToSpeed;
} else {
hero.x = walkToX;
hero.y = walkToY;
walkToActive = false;
}
}
if (typeof oldGameUpdate === "function") oldGameUpdate();
};
// Play background music
LK.playMusic('dungeon');
// Main update loop
game.update = function () {
if (!gameStarted) return;
// Handle freeze timer
if (gameFrozen) {
freezeTimer--;
if (freezeTimer <= 0) {
// Unfreeze game
gameFrozen = false;
// Remove freeze visual effects
tween(hero, {
tint: 0xffffff
}, {
duration: 300
});
for (var m = 0; m < monsters.length; m++) {
tween(monsters[m], {
tint: 0xffffff
}, {
duration: 300
});
}
}
}
// Update sword cooldown
if (swordCooldown > 0) {
swordCooldown--;
}
// Update ultimate timer
if (!ultimateAvailable) {
ultimateTimer++;
if (ultimateTimer >= ultimateInterval) {
ultimateAvailable = true;
ultimateButton.visible = true;
ultimateButtonText.visible = true;
// Add pulsing animation to indicate availability
tween(ultimateButton, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
loop: true,
pingpong: true
});
tween(ultimateButtonText, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
loop: true,
pingpong: true
});
}
}
// Update freeze buffer
if (freezeBuffer > 0) {
freezeBuffer--;
}
// Update stun timer
if (isStunned) {
stunTimer--;
if (stunTimer <= 0) {
isStunned = false;
// Remove stunned text
if (stunnedText) {
stunnedText.destroy();
stunnedText = null;
}
}
// Update stunned text position if it exists
if (stunnedText) {
stunnedText.x = hero.x;
stunnedText.y = hero.y - hero.height - 50;
}
}
// Update hero (only if not frozen)
if (!gameFrozen) {
hero.update();
}
// Update monsters
for (var i = 0; i < monsters.length; i++) {
monsters[i].update();
}
// Update mage balls
for (var i = mageBalls.length - 1; i >= 0; i--) {
var ball = mageBalls[i];
ball.update();
// Remove ball if lifetime expired
if (ball.lifetime <= 0) {
ball.destroy();
mageBalls.splice(i, 1);
continue;
}
// Check collision with hero (only when not invincible and no freeze buffer)
if (!hero.invincible && freezeBuffer <= 0 && hero.intersects(ball)) {
hero.takeDamage();
// Destroy ball on hit
ball.destroy();
mageBalls.splice(i, 1);
// Create impact effect
LK.effects.flashObject(hero, 0x8A2BE2, 200);
}
}
// Update skeleton stones
for (var i = skeletonStones.length - 1; i >= 0; i--) {
var stone = skeletonStones[i];
stone.update();
// Remove stone if lifetime expired
if (stone.lifetime <= 0) {
stone.destroy();
skeletonStones.splice(i, 1);
continue;
}
// Check collision with hero (only when not invincible and no freeze buffer)
if (!hero.invincible && freezeBuffer <= 0 && hero.intersects(stone)) {
hero.takeDamage();
// Destroy stone on hit
stone.destroy();
skeletonStones.splice(i, 1);
// Create impact effect
LK.effects.flashObject(hero, 0x555555, 200);
}
}
// Update ground fires
for (var i = groundFires.length - 1; i >= 0; i--) {
var fire = groundFires[i];
fire.update();
// Remove fire if lifetime expired
if (fire.lifetime <= 0) {
fire.destroy();
groundFires.splice(i, 1);
continue;
}
// Check collision with hero (only when not invincible and no freeze buffer)
if (!hero.invincible && freezeBuffer <= 0 && !fire.damageDealt && hero.intersects(fire)) {
hero.takeDamage();
fire.damageDealt = true; // Prevent multiple damage from same fire
// Create fire impact effect
LK.effects.flashObject(hero, 0xFF4400, 200);
}
}
// Update stun orbs
for (var i = stunOrbs.length - 1; i >= 0; i--) {
var orb = stunOrbs[i];
orb.update();
// Remove orb if lifetime expired
if (orb.lifetime <= 0) {
orb.destroy();
stunOrbs.splice(i, 1);
continue;
}
// Check collision with hero (only when not invincible and no freeze buffer)
if (!hero.invincible && freezeBuffer <= 0 && !isStunned && hero.intersects(orb)) {
// Apply stun effect
isStunned = true;
stunTimer = STUN_DURATION;
// Create stunned text above hero
stunnedText = new Text2("STUNNED", {
size: 80,
fill: 0xFF0000,
font: "Impact, Arial Black, Tahoma"
});
stunnedText.anchor.set(0.5, 0.5);
stunnedText.x = hero.x;
stunnedText.y = hero.y - hero.height - 50;
game.addChild(stunnedText);
// Apply visual stun effect to hero
tween(hero, {
tint: 0x9932CC
}, {
duration: 200,
onFinish: function onFinish() {
tween(hero, {
tint: 0xFFFFFF
}, {
duration: 200
});
}
});
// Destroy orb on hit
orb.destroy();
stunOrbs.splice(i, 1);
// Create stun impact effect
LK.effects.flashObject(hero, 0x9932CC, 300);
}
}
// Attack button is always visible
killButton.visible = true;
killButtonText.visible = true;
// Heal button visibility and text update based on remaining uses
if (healUsesRemaining > 0) {
healButton.visible = true;
healButtonText.visible = true;
healButton.alpha = 0.9;
healButtonText.setText(currentTexts.heal + " (" + healUsesRemaining + ")");
} else {
healButton.visible = true;
healButtonText.visible = true;
healButton.alpha = 0.3; // Make button appear disabled
healButtonText.setText(currentTexts.noHeals);
}
// Check if hero is close to chest and all monsters are defeated
var nearChest = false;
if (monsters.length === 0) {
for (var i = 0; i < chests.length; i++) {
var c = chests[i];
if (!c.opened) {
var dx = hero.x - c.x;
var dy = hero.y - c.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200) {
nearChest = true;
break;
}
}
}
}
// Show/hide open chest text based on proximity and game state
openChestText.visible = nearChest;
// Check collisions: hero vs monsters (only when game is not frozen, no freeze buffer, and hero is not invincible)
if (!gameFrozen && freezeBuffer <= 0 && !hero.invincible) {
for (var i = monsters.length - 1; i >= 0; i--) {
var m = monsters[i];
if (hero.intersects(m)) {
hero.takeDamage();
// No knockback - hero stays in place when touching monsters
// Hero can only kill monsters with the kill button, not by collision
}
}
}
// Check collisions: hero vs keys
for (var i = keys.length - 1; i >= 0; i--) {
var k = keys[i];
if (hero.intersects(k)) {
k.destroy();
keys.splice(i, 1);
hasKey = true;
LK.effects.flashObject(hero, 0xFFD700, 300);
}
}
// Check collisions: hero vs chests
for (var i = 0; i < chests.length; i++) {
var c = chests[i];
if (hero.intersects(c) && monsters.length === 0 && !c.opened) {
c.opened = true;
LK.effects.flashObject(c, 0x83de44, 600);
updateScoreDisplay();
// Spawn key from chest after all monsters are defeated
var k = new Key();
k.x = c.x;
k.y = c.y;
keys.push(k);
game.addChild(k);
}
}
// Room clear check - need to defeat all monsters first, then open chest, then collect key
if (!roomCleared && monsters.length === 0 && chests.length > 0 && chests[0].opened && hasKey) {
roomCleared = true;
door.visible = true;
LK.effects.flashObject(door, 0x83de44, 600);
}
// Check if hero is near NPC wizard in first room
if (currentRoom === 1 && game.npcWizard && !game.npcWizard.showingSpeech) {
var dx = hero.x - game.npcWizard.x;
var dy = hero.y - game.npcWizard.y;
var distToNPC = Math.sqrt(dx * dx + dy * dy);
if (distToNPC < 250) {
// Show speech when hero gets close
game.npcWizard.showSpeech();
}
}
// Check hero at door to next room
if (roomCleared && door && hero.intersects(door)) {
if (currentRoom < maxRooms) {
currentRoom++;
generateRoom(currentRoom);
} else {
// Win!
LK.setScore(monstersDefeated * 5);
// Create dungeon completion overlay instead of default win screen
var dungeonCompletionOverlay = new Container();
dungeonCompletionOverlay.zIndex = 10000; // ensure on top
// Dark background
var background = LK.getAsset('trap', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
background.tint = 0x000000;
background.alpha = 0.8;
dungeonCompletionOverlay.addChild(background);
// Main completion message
var completionText = new Text2("You completed the dungeon!", {
size: 140,
fill: 0xFFD700,
font: "Impact, Arial Black, Tahoma"
});
completionText.anchor.set(0.5, 0.5);
completionText.x = 2048 / 2;
completionText.y = 1200;
dungeonCompletionOverlay.addChild(completionText);
// Subtitle
var subtitleText = new Text2("The brave adventurer has conquered all " + maxRooms + " rooms!", {
size: 80,
fill: 0x88FF88
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.x = 2048 / 2;
subtitleText.y = 1400;
dungeonCompletionOverlay.addChild(subtitleText);
// Score display
var finalScoreText = new Text2("Final Score: " + monstersDefeated * 5, {
size: 90,
fill: 0xFFFF88
});
finalScoreText.anchor.set(0.5, 0.5);
finalScoreText.x = 2048 / 2;
finalScoreText.y = 1600;
dungeonCompletionOverlay.addChild(finalScoreText);
// Monsters defeated display
var monstersText = new Text2("Monsters Defeated: " + monstersDefeated, {
size: 70,
fill: 0xFFFFFF
});
monstersText.anchor.set(0.5, 0.5);
monstersText.x = 2048 / 2;
monstersText.y = 1800;
dungeonCompletionOverlay.addChild(monstersText);
game.addChild(dungeonCompletionOverlay);
// Auto show you win after 3 seconds to register the win
LK.setTimeout(function () {
LK.showYouWin();
}, 3000);
}
}
};
dungeon door. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
png
dungeon hero sword. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon key. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon chest. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon demon. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
dungeon wizard. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a mage who throws fireballs. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
big dungeon snake. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat
a spider. No background. Transparent background. Blank background. No shadows. 2d. In-Game asset. flat