User prompt
If our character stands in front of a skeleton, let the skeleton stand still.
User prompt
If our character encounters a skeleton, the skeleton should stay where it is.
User prompt
No skeleton spawn where our character is born
User prompt
The kill button should appear when you get close to the skeleton.
User prompt
Sword swinging is currently buggy, when you click once there should be a very short waiting time and the sword should not rotate around itself continuously. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
add sword swing effect ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Skeletons have a very large hitbox and move very fast.
User prompt
Adjust skeletons hitbox properly
User prompt
character can kill monster with kill button not with double click
User prompt
If the character can kill all the skeletons he can open the chest
User prompt
add key and chest and door must open with key
User prompt
give the character a sword
User prompt
If the character double clicks, he can damage the skeletons.
User prompt
make a start screen
User prompt
player can walk with mouse
Code edit (1 edits merged)
Please save this source code
User prompt
Dungeon Dash
Initial prompt
make me a dungeon type game
/**** * 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