/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var BlueEnemy = Container.expand(function (roomLevel) { var self = Container.call(this); var enemyGraphics = self.attachAsset('blueEnemy', { anchorX: 0.5, anchorY: 0.5 }); // Set blue enemy health to 5 HP self.health = 5; self.speed = Math.min(1.5 + roomLevel * 0.2, 3.5); self.shootTimer = 0; self.shootCooldown = Math.max(90 - roomLevel * 3, 50); self.active = true; self.lastPlayerX = 0; self.lastPlayerY = 0; self.stunned = false; self.stunTimer = 0; self.isBlueEnemy = true; self.update = function () { if (!self.active || !player || isLevelingUp) return; // Handle stunning if (self.stunned) { self.stunTimer--; if (self.stunTimer <= 0) { self.stunned = false; } return; // Don't move or shoot while stunned } // Track last position for intersection detection self.lastPlayerX = player.x; self.lastPlayerY = player.y; // Move aggressively towards player for close range shotgun attack var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var optimalDistance = 120; // Very close for shotgun effectiveness var minDistance = 80; // Get very close // Apply slowdown effect if active var currentSpeed = self.speed; if (slowdownActive) { currentSpeed = self.speed * slowdownEffect; } if (distance > optimalDistance) { // Move closer aggressively self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; } else if (distance < minDistance) { // Too close - back away slightly self.x -= dx / distance * currentSpeed * 0.5; self.y -= dx / distance * currentSpeed * 0.5; } // Shooting logic - shoots when close to player self.shootTimer++; if (self.shootTimer >= self.shootCooldown && distance < 200) { self.shoot(); self.shootTimer = 0; } }; self.shoot = function () { // Shotgun spread attack - fires 3 bullets in a spread var baseAngle = Math.atan2(player.y - self.y, player.x - self.x); for (var i = -1; i <= 1; i++) { var bullet = new Bullet(false); bullet.direction = baseAngle + i * 0.4; // Wide spread bullet.x = self.x; bullet.y = self.y; bullet.startX = self.x; bullet.startY = self.y; bullet.speed = 12; bullet.damage = 3; // Increased damage per pellet for stronger shotgun enemyBullets.push(bullet); game.addChild(bullet); } }; self.stun = function (duration) { self.stunned = true; self.stunTimer = duration; // Visual feedback for stunned enemy LK.effects.flashObject(self, 0xfff700, duration * 16.67); }; self.takeDamage = function (damage, isFireDamage) { self.health -= damage; if (isFireDamage && hasUpgradePistol) { // Fire effect - red-orange flash that fades over time LK.effects.flashObject(self, 0xff4500, 1000); // Add burning visual effect with tween tween(self, { tint: 0xff6b35 }, { duration: 500, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 300 }); } }); } else { LK.effects.flashObject(self, 0xFFFFFF, 200); } if (self.health <= 0) { self.active = false; LK.setScore(LK.getScore() + 15); // Higher score for blue enemies gainXP(75); // More XP for blue enemies gainCash(1); // Gain $1 per enemy killed LK.getSound('enemyHit').play(); // Reduce slowdown cooldown by 5 seconds (300 ticks) if (slowdownCooldown > 0) { slowdownCooldown = Math.max(0, slowdownCooldown - 300); } // 100% chance to drop sniper weapon var sniperPickup = new Pickup('gun', self.x, self.y); sniperPickup.weapon = 'sniper'; // Force it to be sniper pickups.push(sniperPickup); game.addChild(sniperPickup); // 25% chance to drop weapon pickup (higher than normal) if (Math.random() < 0.25) { spawnPickup(self.x, self.y); } // 15% chance to drop shotgun ammo if (Math.random() < 0.15) { var ammoPickup = new ShotgunAmmo(self.x, self.y); shotgunAmmoPickups.push(ammoPickup); game.addChild(ammoPickup); } } }; return self; }); var Boss = Container.expand(function (roomLevel) { var self = Container.call(this); // Create boss graphics - big and purple var bossGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3, tint: 0x8e44ad // Purple color }); // Boss stats scale with room level self.maxHealth = 50 + Math.floor((roomLevel - 10) / 10) * 50; self.health = self.maxHealth; self.speed = Math.min(0.8 + roomLevel * 0.1, 2); self.shootTimer = 0; self.shootCooldown = Math.max(90 - roomLevel * 2, 45); self.active = true; self.isBoss = true; self.update = function () { if (!self.active || !player || isLevelingUp) return; // Move towards player but maintain distance var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var optimalDistance = 300; var minDistance = 200; // Apply slowdown effect if active var currentSpeed = self.speed; if (slowdownActive) { currentSpeed = self.speed * slowdownEffect; } if (distance < minDistance) { self.x -= dx / distance * currentSpeed; self.y -= dy / distance * currentSpeed; } else if (distance > optimalDistance + 150) { self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; } // Shooting logic - bosses shoot more frequently self.shootTimer++; if (self.shootTimer >= self.shootCooldown && distance < 600) { self.shoot(); self.shootTimer = 0; } }; self.shoot = function () { // Bosses shoot 3 bullets in a spread var baseAngle = Math.atan2(player.y - self.y, player.x - self.x); for (var i = -1; i <= 1; i++) { var bullet = new Bullet(false); bullet.direction = baseAngle + i * 0.3; bullet.x = self.x; bullet.y = self.y; bullet.startX = self.x; bullet.startY = self.y; bullet.speed = 10; enemyBullets.push(bullet); game.addChild(bullet); } }; self.takeDamage = function (damage, isFireDamage) { self.health -= damage; if (isFireDamage && hasUpgradePistol) { // Fire effect - red-orange flash that fades over time LK.effects.flashObject(self, 0xff4500, 1000); // Add burning visual effect with tween tween(self, { tint: 0xff6b35 }, { duration: 500, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 300 }); } }); } else { LK.effects.flashObject(self, 0xFFFFFF, 200); } updateBossHealthBar(); if (self.health <= 0) { self.active = false; LK.setScore(LK.getScore() + 100); gainXP(200); gainCash(1); // Gain $1 per enemy killed LK.getSound('enemyHit').play(); // Hide boss health bar if (bossHealthBarBg) { bossHealthBarBg.alpha = 0; bossHealthBar.alpha = 0; } } }; return self; }); var Bullet = Container.expand(function (isPlayer, gunType) { var self = Container.call(this); self.isPlayer = isPlayer || false; self.gunType = gunType || 'pistol'; var bulletGraphics = self.attachAsset(self.isPlayer ? 'bullet' : 'enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); // Different bullet properties based on gun type if (self.isPlayer) { switch (self.gunType) { case 'pistol': self.speed = 12; self.damage = 5 + damageBonus; self.maxRange = 800 * rangeBonus; // Large range break; case 'rifle': self.speed = 18; self.damage = 2 + damageBonus; self.maxRange = 1000 * rangeBonus; // Large range break; case 'shotgun': self.speed = 10; self.damage = 15 + damageBonus; // Increased from 10 to 15 self.maxRange = 300; // Short range break; case 'sniper': self.speed = 25; self.damage = 20 + damageBonus; self.maxRange = 1200 * rangeBonus; // Very long range self.pierceCount = 0; // Track how many enemies pierced self.maxPierce = 3 + piercingBonus; // Can pierce through 3 + bonus enemies break; case 'rpg': self.speed = 8; self.damage = 40; // High damage self.maxRange = 600; self.isExplosive = true; break; } } else { self.speed = 8; self.damage = 1; self.maxRange = 400; } self.direction = 0; self.active = true; self.startX = 0; self.startY = 0; self.distanceTraveled = 0; self.update = function () { if (!self.active) return; self.x += Math.cos(self.direction) * self.speed; self.y += Math.sin(self.direction) * self.speed; // Track distance traveled var dx = self.x - self.startX; var dy = self.y - self.startY; self.distanceTraveled = Math.sqrt(dx * dx + dy * dy); // Check range limit if (self.maxRange && self.distanceTraveled > self.maxRange) { self.active = false; } // Check bounds if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.active = false; } }; return self; }); var Enemy = Container.expand(function (roomLevel) { var self = Container.call(this); var enemyGraphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); // Set enemy health to 10 HP self.health = 10; self.speed = Math.min(1 + roomLevel * 0.2, 3); self.shootTimer = 0; self.shootCooldown = Math.max(120 - roomLevel * 5, 60); self.active = true; self.lastPlayerX = 0; self.lastPlayerY = 0; self.stunned = false; self.stunTimer = 0; self.update = function () { if (!self.active || !player || isLevelingUp) return; // Handle stunning if (self.stunned) { self.stunTimer--; if (self.stunTimer <= 0) { self.stunned = false; } return; // Don't move or shoot while stunned } // Track last position for intersection detection self.lastPlayerX = player.x; self.lastPlayerY = player.y; // Move to maintain optimal shooting distance var dx = player.x - self.x; var dy = player.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var optimalDistance = 250; // Preferred shooting distance var minDistance = 150; // Minimum distance to maintain // Apply slowdown effect if active var currentSpeed = self.speed; if (slowdownActive) { currentSpeed = self.speed * slowdownEffect; } if (distance < minDistance) { // Too close - back away from player self.x -= dx / distance * currentSpeed; self.y -= dy / distance * currentSpeed; } else if (distance > optimalDistance + 100) { // Too far - move closer but stop at optimal distance self.x += dx / distance * currentSpeed; self.y += dy / distance * currentSpeed; } // If between minDistance and optimalDistance+100, stay in place // Shooting logic self.shootTimer++; if (self.shootTimer >= self.shootCooldown && distance < 500) { self.shoot(); self.shootTimer = 0; } }; self.shoot = function () { var bullet = new Bullet(false); var angle = Math.atan2(player.y - self.y, player.x - self.x); bullet.direction = angle; bullet.x = self.x; bullet.y = self.y; bullet.startX = self.x; bullet.startY = self.y; enemyBullets.push(bullet); game.addChild(bullet); }; self.stun = function (duration) { self.stunned = true; self.stunTimer = duration; // Visual feedback for stunned enemy LK.effects.flashObject(self, 0xfff700, duration * 16.67); }; self.takeDamage = function (damage, isFireDamage) { self.health -= damage; if (isFireDamage && hasUpgradePistol) { // Fire effect - red-orange flash that fades over time LK.effects.flashObject(self, 0xff4500, 1000); // Add burning visual effect with tween tween(self, { tint: 0xff6b35 }, { duration: 500, onFinish: function onFinish() { tween(self, { tint: 0xffffff }, { duration: 300 }); } }); } else { LK.effects.flashObject(self, 0xFFFFFF, 200); } if (self.health <= 0) { self.active = false; LK.setScore(LK.getScore() + 10); gainXP(50); // Gain 50 XP per enemy killed gainCash(1); // Gain $1 per enemy killed LK.getSound('enemyHit').play(); // Reduce slowdown cooldown by 5 seconds (300 ticks) if (slowdownCooldown > 0) { slowdownCooldown = Math.max(0, slowdownCooldown - 300); } // 40% chance to drop sniper weapon if (Math.random() < 0.4) { var sniperPickup = new Pickup('gun', self.x, self.y); sniperPickup.weapon = 'sniper'; // Force it to be sniper pickups.push(sniperPickup); game.addChild(sniperPickup); } // 20% chance to drop weapon pickup if (Math.random() < 0.2) { spawnPickup(self.x, self.y); } // 10% chance to drop shotgun ammo if (Math.random() < 0.1) { var ammoPickup = new ShotgunAmmo(self.x, self.y); shotgunAmmoPickups.push(ammoPickup); game.addChild(ammoPickup); } } }; return self; }); var Pickup = Container.expand(function (type, x, y) { var self = Container.call(this); self.type = type; // 'gun' or 'melee' self.x = x; self.y = y; self.active = true; self.bobOffset = Math.random() * Math.PI * 2; self.startY = y; var pickupGraphics = self.attachAsset(type === 'gun' ? 'gunPickup' : 'meleePickup', { anchorX: 0.5, anchorY: 0.5 }); // Determine what weapon this pickup contains if (type === 'gun') { var guns = ['pistol', 'rifle', 'shotgun', 'sniper']; self.weapon = guns[Math.floor(Math.random() * guns.length)]; } else { var melees = ['sword', 'hammer']; self.weapon = melees[Math.floor(Math.random() * melees.length)]; } self.update = function () { if (!self.active) return; // Bobbing animation self.y = self.startY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5; // Check pickup collision with player if (self.intersects(player)) { self.active = false; if (self.type === 'gun') { if (addWeaponToHotbar(self.weapon, 'gun')) { // Successfully added to hotbar LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 5); } } else { if (addWeaponToHotbar(self.weapon, 'melee')) { // Successfully added to hotbar LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 5); } } self.destroy(); } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); var playerGraphics = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5 }); // Gun display var gunGraphics = self.attachAsset('pistol', { anchorX: 0, anchorY: 0.5, x: 20, y: 0 }); // Melee weapon display var meleeGraphics = self.attachAsset('sword', { anchorX: 0.5, anchorY: 1, x: -15, y: -10 }); self.health = 10; self.maxHealth = 10; self.speed = 5; self.shootCooldown = 0; self.parryCooldown = 0; self.parryWindow = 0; self.invincibilityFrames = 0; // Weapon systems self.currentGun = 'pistol'; self.currentMelee = 'sword'; // Ammo system self.shotgunAmmo = 5; self.sniperAmmo = 5; self.rpgAmmo = 0; // Gun properties self.gunStats = { pistol: { cooldown: 12, spread: 0 }, rifle: { cooldown: 8, spread: 0.1 }, shotgun: { cooldown: 30, spread: 0.3, pellets: 3 }, sniper: { cooldown: 60, spread: 0, ammo: 5 }, rpg: { cooldown: 120, spread: 0, ammo: 1 } }; // Melee properties self.meleeStats = { sword: { parryWindow: 8, cooldown: 25, effect: 'reflect', color: 0x1abc9c }, hammer: { parryWindow: 15, cooldown: 45, effect: 'shockwave', color: 0xe74c3c } }; self.shoot = function (targetX, targetY) { if (self.shootCooldown > 0) return; // Check ammo for shotgun and sniper if (self.currentGun === 'shotgun' && self.shotgunAmmo <= 0) return; if (self.currentGun === 'sniper' && self.sniperAmmo <= 0) { // Remove sniper from hotbar when out of ammo removeWeaponFromHotbar('sniper'); return; } if (self.currentGun === 'rpg' && self.rpgAmmo <= 0) { // Remove RPG from hotbar when out of ammo removeWeaponFromHotbar('rpg'); return; } var gunStat = self.gunStats[self.currentGun]; var baseAngle = Math.atan2(targetY - self.y, targetX - self.x); if (self.currentGun === 'shotgun') { // Shotgun fires multiple pellets and consumes ammo self.shotgunAmmo--; for (var i = 0; i < gunStat.pellets; i++) { var bullet = new Bullet(true, self.currentGun); bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread; bullet.x = self.x; bullet.y = self.y; bullet.startX = self.x; bullet.startY = self.y; playerBullets.push(bullet); game.addChild(bullet); } } else if (self.currentGun === 'sniper') { // Sniper fires single high-damage piercing bullet and consumes ammo self.sniperAmmo--; var bullet = new Bullet(true, self.currentGun); bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread; bullet.x = self.x; bullet.y = self.y; bullet.startX = self.x; bullet.startY = self.y; playerBullets.push(bullet); game.addChild(bullet); } else if (self.currentGun === 'rpg') { // RPG fires explosive rocket and consumes ammo self.rpgAmmo--; var bullet = new Bullet(true, self.currentGun); bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread; bullet.x = self.x; bullet.y = self.y; bullet.startX = self.x; bullet.startY = self.y; playerBullets.push(bullet); game.addChild(bullet); } else { var bullet = new Bullet(true, self.currentGun); bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread; bullet.x = self.x; bullet.y = self.y; bullet.startX = self.x; bullet.startY = self.y; playerBullets.push(bullet); game.addChild(bullet); } LK.getSound('shoot').play(); self.shootCooldown = gunStat.cooldown; }; self.parry = function () { if (self.parryCooldown > 0) return; var meleeStat = self.meleeStats[self.currentMelee]; self.parryWindow = 180; // 3 seconds at 60fps self.parryCooldown = meleeStat.cooldown; // Visual feedback var parryFX = game.attachAsset('parryEffect', { anchorX: 0.5, anchorY: 0.5, x: self.x, y: self.y, alpha: 0.8, tint: meleeStat.color }); tween(parryFX, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 3000, // 3 seconds onFinish: function onFinish() { parryFX.destroy(); } }); LK.getSound('parry').play(); }; self.switchGun = function (newGun) { if (newGun && self.gunStats[newGun]) { self.currentGun = newGun; gunGraphics.destroy(); gunGraphics = self.attachAsset(newGun, { anchorX: 0, anchorY: 0.5, x: 20, y: 0 }); } }; self.switchMelee = function (newMelee) { if (newMelee && self.meleeStats[newMelee]) { self.currentMelee = newMelee; meleeGraphics.destroy(); meleeGraphics = self.attachAsset(newMelee, { anchorX: 0.5, anchorY: 1, x: -15, y: -10 }); } }; self.takeDamage = function () { self.health--; LK.effects.flashObject(self, 0xFF0000, 500); LK.getSound('playerHit').play(); if (self.health <= 0) { LK.showGameOver(); } }; self.heal = function (amount) { self.health = Math.min(self.health + amount, self.maxHealth); }; self.update = function () { if (self.shootCooldown > 0) self.shootCooldown--; if (self.parryCooldown > 0) self.parryCooldown--; if (self.invincibilityFrames > 0) self.invincibilityFrames--; if (self.parryWindow > 0) { self.parryWindow--; // Check if parry window just expired without being used if (self.parryWindow === 0) { // Parry was missed, add 2 second cooldown self.parryCooldown = 120; // 2 seconds at 60fps } } }; return self; }); var RPG = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; self.active = true; self.bobOffset = Math.random() * Math.PI * 2; self.startY = y; var rpgGraphics = self.attachAsset('pistol', { anchorX: 0.5, anchorY: 0.5, tint: 0xe74c3c, scaleX: 2.0, scaleY: 2.0 }); self.update = function () { if (!self.active) return; // Bobbing animation self.y = self.startY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5; // Check pickup collision with player if (self.intersects(player)) { self.active = false; if (addWeaponToHotbar('rpg', 'gun')) { LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 5); } self.destroy(); } }; return self; }); var Room = Container.expand(function (level) { var self = Container.call(this); self.level = level || 1; self.enemies = []; self.pickups = []; self.cleared = false; self.door = null; self.generateRoom = function () { // Create border walls var wallSize = 60; var roomWidth = 2048; var roomHeight = 2732; var wallsX = Math.floor(roomWidth / wallSize); var wallsY = Math.floor(roomHeight / wallSize); // Top and bottom walls for (var i = 0; i < wallsX; i++) { var topWall = self.attachAsset('wall', { x: i * wallSize, y: 0 }); var bottomWall = self.attachAsset('wall', { x: i * wallSize, y: roomHeight - wallSize }); } // Left and right walls for (var j = 1; j < wallsY - 1; j++) { var leftWall = self.attachAsset('wall', { x: 0, y: j * wallSize }); var rightWall = self.attachAsset('wall', { x: roomWidth - wallSize, y: j * wallSize }); } // Check if this is a boss room (every 10 rooms) if (self.level % 10 === 0) { // Boss fight var boss = new Boss(self.level); boss.x = roomWidth / 2; boss.y = roomHeight / 3; self.enemies.push(boss); self.addChild(boss); currentBoss = boss; // Show boss health bar bossHealthBarBg.alpha = 1; bossHealthBar.alpha = 1; bossNameText.alpha = 1; updateBossHealthBar(); } else { // Generate regular enemies var enemyCount = Math.min(2 + self.level, 8); var blueEnemyCount = 0; // Blue enemies appear starting from wave 5 if (self.level >= 5) { blueEnemyCount = Math.min(1 + Math.floor((self.level - 5) / 3), 3); // 1-3 blue enemies enemyCount = Math.max(1, enemyCount - blueEnemyCount); // Reduce regular enemies to maintain balance } // After wave 5, convert percentage of normal enemies to blue enemies var blueConversionRate = 0; if (self.level > 5) { // Start at 10% after wave 5, increase by 5% each wave, cap at 50% blueConversionRate = Math.min(0.1 + (self.level - 5) * 0.05, 0.5); } // Spawn regular enemies for (var k = 0; k < enemyCount; k++) { // Check if this enemy should be converted to blue if (self.level > 5 && Math.random() < blueConversionRate) { var convertedBlueEnemy = new BlueEnemy(self.level); convertedBlueEnemy.x = 200 + Math.random() * (roomWidth - 400); convertedBlueEnemy.y = 200 + Math.random() * (roomHeight - 400); self.enemies.push(convertedBlueEnemy); self.addChild(convertedBlueEnemy); } else { var enemy = new Enemy(self.level); enemy.x = 200 + Math.random() * (roomWidth - 400); enemy.y = 200 + Math.random() * (roomHeight - 400); self.enemies.push(enemy); self.addChild(enemy); } } // Spawn blue enemies starting from wave 5 for (var b = 0; b < blueEnemyCount; b++) { var blueEnemy = new BlueEnemy(self.level); blueEnemy.x = 200 + Math.random() * (roomWidth - 400); blueEnemy.y = 200 + Math.random() * (roomHeight - 400); self.enemies.push(blueEnemy); self.addChild(blueEnemy); } } // Create exit door (initially locked) self.door = self.attachAsset('door', { anchorX: 0.5, anchorY: 0.5, x: roomWidth / 2, y: wallSize / 2, tint: 0xc0392b }); // Spawn shotgun pickup in room spawnShotgunPickup(); }; self.checkCleared = function () { var activeEnemies = 0; for (var i = 0; i < self.enemies.length; i++) { if (self.enemies[i].active) { activeEnemies++; } } if (activeEnemies === 0 && !self.cleared) { self.cleared = true; // Unlock door if (self.door) { tween(self.door, { tint: 0x27ae60 }, { duration: 500 }); } } return self.cleared; }; return self; }); var ShotgunAmmo = Container.expand(function (x, y) { var self = Container.call(this); self.x = x; self.y = y; self.active = true; self.bobOffset = Math.random() * Math.PI * 2; self.startY = y; var ammoGraphics = self.attachAsset('shotgunAmmo', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.active) return; // Bobbing animation self.y = self.startY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5; // Check pickup collision with player if (self.intersects(player)) { self.active = false; player.shotgunAmmo += 3; // Give 3 ammo directly LK.getSound('pickup').play(); LK.setScore(LK.getScore() + 2); updateUI(); // Update ammo display self.destroy(); } }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x1a1a1a }); /**** * Game Code ****/ var player; var currentRoom; var playerBullets = []; var enemyBullets = []; var pickups = []; var shotgunAmmoPickups = []; var roomLevel = 1; var dragStartX = 0; var dragStartY = 0; var isDragging = false; var draggedWeapon = null; var draggedSlot = null; var trashIcon = null; var selectedWeapon = null; var selectedSlot = null; // XP System var playerXP = 0; var playerLevel = 1; var xpToNextLevel = 100; var isLevelingUp = false; var levelUpChoices = null; var damageBonus = 0; var rangeBonus = 1; var piercingBonus = 0; // Cash System var playerCash = storage.playerCash || 0; var isShopOpen = false; var shopChoices = null; var hasUpgradePistol = storage.hasUpgradePistol || false; // UI Elements var scoreText = new Text2('Score: 0', { size: 40, fill: '#ffffff' }); scoreText.anchor.set(0.5, 0); LK.gui.top.addChild(scoreText); // Health bar instead of text var healthBarBg = LK.gui.topRight.attachAsset('wall', { width: 150, height: 20, anchorX: 1, anchorY: 0, tint: 0x7f8c8d }); var healthBar = LK.gui.topRight.attachAsset('wall', { width: 150, height: 20, anchorX: 1, anchorY: 0, tint: 0xe74c3c }); var roomText = new Text2('Room: 1', { size: 30, fill: '#3498db' }); roomText.anchor.set(0, 0); roomText.x = 120; LK.gui.topLeft.addChild(roomText); var gunText = new Text2('Gun: Pistol', { size: 28, fill: '#f39c12' }); gunText.anchor.set(0, 1); LK.gui.bottomLeft.addChild(gunText); var meleeText = new Text2('Melee: Sword', { size: 28, fill: '#9b59b6' }); meleeText.anchor.set(1, 1); LK.gui.bottomRight.addChild(meleeText); // Hotbar system var hotbar = new Container(); hotbar.x = 1024; // Center of screen hotbar.y = 2600; // Bottom of screen game.addChild(hotbar); // Cash counter to the left of hotbar var cashText = new Text2('$0', { size: 40, fill: '#2ecc71' }); cashText.anchor.set(1, 0.5); cashText.x = -300; // Position to the left of hotbar cashText.y = -80; hotbar.addChild(cashText); // Ammo counter to the left of hotbar var ammoText = new Text2('Shotgun: 5', { size: 32, fill: '#f39c12' }); ammoText.anchor.set(1, 0.5); ammoText.x = -300; // Position to the left of hotbar ammoText.y = 0; hotbar.addChild(ammoText); // Sniper ammo counter var sniperAmmoText = new Text2('Sniper: 5', { size: 32, fill: '#8b4513' }); sniperAmmoText.anchor.set(1, 0.5); sniperAmmoText.x = -300; sniperAmmoText.y = -40; hotbar.addChild(sniperAmmoText); // XP label above hotbar var xpLabel = new Text2('XP', { size: 48, fill: '#ffffff' }); xpLabel.anchor.set(0.5, 1); xpLabel.x = 0; xpLabel.y = -150; hotbar.addChild(xpLabel); // XP bar background above hotbar var xpBarBg = hotbar.attachAsset('wall', { width: 400, height: 30, anchorX: 0.5, anchorY: 1, x: 0, y: -100, tint: 0x7f8c8d }); // XP bar fill above hotbar var xpBar = hotbar.attachAsset('wall', { width: 400, height: 30, anchorX: 0.5, anchorY: 1, x: 0, y: -100, tint: 0xe67e22 }); // Level display next to XP bar var levelText = new Text2('Level 1', { size: 36, fill: '#ffffff' }); levelText.anchor.set(0, 0.5); levelText.x = 220; levelText.y = -85; hotbar.addChild(levelText); // Boss health bar (initially hidden) var bossHealthBarBg = LK.gui.top.attachAsset('wall', { width: 800, height: 30, anchorX: 0.5, anchorY: 0, y: 60, tint: 0x7f8c8d, alpha: 0 }); var bossHealthBar = LK.gui.top.attachAsset('wall', { width: 800, height: 30, anchorX: 0.5, anchorY: 0, y: 60, tint: 0x8e44ad, alpha: 0 }); var bossNameText = new Text2('BOSS', { size: 36, fill: '#8e44ad' }); bossNameText.anchor.set(0.5, 1); bossNameText.y = 55; bossNameText.alpha = 0; LK.gui.top.addChild(bossNameText); var hotbarSlots = []; var slotWidth = 80; var slotHeight = 80; var slotSpacing = 100; // Create 5 hotbar slots for (var h = 0; h < 5; h++) { var slot = hotbar.attachAsset('wall', { width: slotWidth, height: slotHeight, anchorX: 0.5, anchorY: 0.5, x: (h - 2) * slotSpacing, y: 0, tint: 0x2c3e50 }); slot.slotIndex = h; slot.weapon = null; slot.weaponType = null; // 'gun' or 'melee' // Visual weapon display slot.weaponGraphics = null; slot.down = function (x, y, obj) { if (this.weapon) { // Select weapon and light up trash selectedWeapon = this.weapon; selectedSlot = this; // Highlight the selected slot tween(this, { tint: 0x3498db }, { duration: 100 }); // Light up trash icon tween(trashIcon, { tint: 0xff6b6b }, { duration: 100 }); } }; slot.up = function (x, y, obj) { // Switch to this weapon if it's selected but not the same as current selection if (selectedWeapon && selectedSlot === this) { if (this.weaponType === 'gun') { player.switchGun(this.weapon); } else if (this.weaponType === 'melee') { player.switchMelee(this.weapon); } updateUI(); } }; hotbarSlots.push(slot); } // Create trash icon next to hotbar trashIcon = hotbar.attachAsset('trashIcon', { width: 60, height: 60, anchorX: 0.5, anchorY: 0.5, x: 350, // Position to the right of hotbar y: 0, tint: 0xc0392b }); // Add click handler to trash icon trashIcon.down = function (x, y, obj) { if (selectedWeapon && selectedSlot) { // Delete weapon from selected slot selectedSlot.weapon = null; selectedSlot.weaponType = null; if (selectedSlot.weaponGraphics) { selectedSlot.weaponGraphics.destroy(); selectedSlot.weaponGraphics = null; } // Reset slot color tween(selectedSlot, { tint: 0x2c3e50 }, { duration: 100 }); // Flash trash icon to show deletion tween(trashIcon, { tint: 0xff0000 }, { duration: 200, onFinish: function onFinish() { tween(trashIcon, { tint: 0xc0392b }, { duration: 200 }); } }); // Clear selection selectedWeapon = null; selectedSlot = null; } }; // Add visual indicator for trash (X symbol) var trashX1 = hotbar.attachAsset('wall', { width: 30, height: 4, anchorX: 0.5, anchorY: 0.5, x: 350, y: 0, rotation: Math.PI / 4, tint: 0xffffff }); var trashX2 = hotbar.attachAsset('wall', { width: 30, height: 4, anchorX: 0.5, anchorY: 0.5, x: 350, y: 0, rotation: -Math.PI / 4, tint: 0xffffff }); // Create parry button var parryButton = game.attachAsset('parryButton', { anchorX: 0.5, anchorY: 0.5, x: 200, y: 2500, scaleX: 2, scaleY: 2 }); // Create slowdown button var slowdownButton = game.attachAsset('parryButton', { anchorX: 0.5, anchorY: 0.5, x: 1848, // Mirror parry button position on right side y: 2500, // Same Y position as parry button tint: 0x3498db, scaleX: 2, scaleY: 2 }); // Slowdown system variables var slowdownCooldown = 0; var slowdownActive = false; var slowdownDuration = 0; var maxSlowdownCooldown = 3000; // 50 seconds at 60fps var slowdownEffect = 0.3; // 70% slow (30% of original speed) // Add parry button text var parryButtonText = new Text2('PARRY', { size: 48, fill: '#ffffff' }); parryButtonText.anchor.set(0.5, 0.5); parryButtonText.x = 200; parryButtonText.y = 2500; game.addChild(parryButtonText); // Add slowdown button text var slowdownButtonText = new Text2('SLOW', { size: 48, fill: '#ffffff' }); slowdownButtonText.anchor.set(0.5, 0.5); slowdownButtonText.x = 1848; slowdownButtonText.y = 2500; game.addChild(slowdownButtonText); // Add slowdown cooldown text var slowdownCooldownText = new Text2('', { size: 24, fill: '#ffffff' }); slowdownCooldownText.anchor.set(0.5, 0.5); slowdownCooldownText.x = 1848; slowdownCooldownText.y = 2400; game.addChild(slowdownCooldownText); // Add parry button click handler parryButton.down = function (x, y, obj) { player.parry(); // Visual feedback on button press tween(parryButton, { scaleX: 0.9, scaleY: 0.9, tint: 0x16a085 }, { duration: 100, onFinish: function onFinish() { tween(parryButton, { scaleX: 1, scaleY: 1, tint: 0x1abc9c }, { duration: 100 }); } }); }; // Add slowdown button click handler slowdownButton.down = function (x, y, obj) { if (slowdownCooldown <= 0 && !slowdownActive) { // Activate slowdown slowdownActive = true; slowdownDuration = 600; // 10 seconds at 60fps slowdownCooldown = maxSlowdownCooldown; // Visual feedback on button press tween(slowdownButton, { scaleX: 0.9, scaleY: 0.9, tint: 0x2980b9 }, { duration: 100, onFinish: function onFinish() { tween(slowdownButton, { scaleX: 1, scaleY: 1, tint: 0xe74c3c // Red when on cooldown }, { duration: 100 }); } }); } }; // Set initial weapons in hotbar hotbarSlots[0].weapon = 'pistol'; hotbarSlots[0].weaponType = 'gun'; hotbarSlots[0].weaponGraphics = hotbarSlots[0].attachAsset('pistol', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); hotbarSlots[1].weapon = 'sword'; hotbarSlots[1].weaponType = 'melee'; hotbarSlots[1].weaponGraphics = hotbarSlots[1].attachAsset('sword', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); hotbarSlots[2].weapon = 'rifle'; hotbarSlots[2].weaponType = 'gun'; hotbarSlots[2].weaponGraphics = hotbarSlots[2].attachAsset('rifle', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); function updateUI() { scoreText.setText('Score: ' + LK.getScore()); // Update health bar var healthPercent = player.health / player.maxHealth; healthBar.width = 150 * healthPercent; roomText.setText('Room: ' + roomLevel); gunText.setText('Gun: ' + player.currentGun.charAt(0).toUpperCase() + player.currentGun.slice(1)); meleeText.setText('Melee: ' + player.currentMelee.charAt(0).toUpperCase() + player.currentMelee.slice(1)); // Update ammo counter ammoText.setText('Shotgun: ' + player.shotgunAmmo); // Update sniper ammo counter sniperAmmoText.setText('Sniper: ' + player.sniperAmmo); // Update XP bar display var xpPercent = playerXP / xpToNextLevel; xpBar.width = 400 * xpPercent; levelText.setText('Level ' + playerLevel); // Update cash display cashText.setText('$' + playerCash); } function updateBossHealthBar() { if (currentBoss && currentBoss.active) { var healthPercent = currentBoss.health / currentBoss.maxHealth; bossHealthBar.width = 800 * healthPercent; } } function gainXP(amount) { playerXP += amount; if (playerXP >= xpToNextLevel && !isLevelingUp) { levelUp(); } } function gainCash(amount) { playerCash += amount; storage.playerCash = playerCash; } function levelUp() { isLevelingUp = true; playerLevel++; playerXP -= xpToNextLevel; xpToNextLevel = Math.floor(xpToNextLevel * 1.5); // Create level up choices overlay showLevelUpChoices(); } function showLevelUpChoices() { // Create overlay background var overlay = game.attachAsset('wall', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.8, tint: 0x000000 }); // Randomly select 3 different upgrade options var availableUpgrades = [{ id: 'hp', text: '+5 Max HP', color: 0x27ae60 }, { id: 'damage', text: '+2 Damage', color: 0xe74c3c }, { id: 'range', text: '+Range', color: 0x3498db }, { id: 'piercing', text: '+1 Piercing', color: 0xf39c12 }]; // Add fire pistol if not owned if (!hasUpgradePistol) { availableUpgrades.push({ id: 'upgradePistol', text: 'Fire Pistol', color: 0xff6b35 }); } // Shuffle and pick 3 random upgrades var selectedUpgrades = []; var tempUpgrades = availableUpgrades.slice(); // Create copy for (var i = 0; i < 3; i++) { var randomIndex = Math.floor(Math.random() * tempUpgrades.length); selectedUpgrades.push(tempUpgrades[randomIndex]); tempUpgrades.splice(randomIndex, 1); } // Create choice buttons var choiceY = 1366; // Center Y var choiceSpacing = 400; var choices = []; var choiceTexts = []; for (var j = 0; j < 3; j++) { var upgrade = selectedUpgrades[j]; var xPos = 1024 + (j - 1) * choiceSpacing; var choice = game.attachAsset('wall', { width: 300, height: 100, anchorX: 0.5, anchorY: 0.5, x: xPos, y: choiceY, tint: upgrade.color }); var choiceText = new Text2(upgrade.text, { size: 36, fill: '#ffffff' }); choiceText.anchor.set(0.5, 0.5); choiceText.x = xPos; choiceText.y = choiceY; game.addChild(choiceText); // Store upgrade id on the choice button choice.upgradeId = upgrade.id; choice.down = function () { chooseLevelUpOption(this.upgradeId); }; choices.push(choice); choiceTexts.push(choiceText); } // Store references for cleanup levelUpChoices = { overlay: overlay, choices: choices, choiceTexts: choiceTexts }; } function chooseLevelUpOption(choice) { if (!isLevelingUp || !levelUpChoices) return; // Apply the chosen upgrade switch (choice) { case 'damage': damageBonus += 2; break; case 'hp': player.maxHealth += 5; player.health += 5; break; case 'range': rangeBonus += 0.2; break; case 'piercing': piercingBonus++; break; case 'upgradePistol': hasUpgradePistol = true; storage.hasUpgradePistol = true; // Upgrade pistol stats player.gunStats.pistol.cooldown = 8; player.gunStats.pistol.damage = 8; break; } // Clean up level up UI levelUpChoices.overlay.destroy(); for (var i = 0; i < levelUpChoices.choices.length; i++) { levelUpChoices.choices[i].destroy(); levelUpChoices.choiceTexts[i].destroy(); } levelUpChoices = null; isLevelingUp = false; updateUI(); } function spawnPickup(x, y) { var pickupType = Math.random() < 0.6 ? 'gun' : 'melee'; var pickup = new Pickup(pickupType, x, y); pickups.push(pickup); game.addChild(pickup); } function addWeaponToHotbar(weapon, weaponType) { // Find first empty slot for (var i = 0; i < hotbarSlots.length; i++) { var slot = hotbarSlots[i]; if (!slot.weapon) { slot.weapon = weapon; slot.weaponType = weaponType; // Remove old graphics if any if (slot.weaponGraphics) { slot.weaponGraphics.destroy(); } // Add new weapon graphics slot.weaponGraphics = slot.attachAsset(weapon, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); return true; } } return false; // No empty slots } function removeWeaponFromHotbar(weapon) { // Find and remove the weapon from hotbar for (var i = 0; i < hotbarSlots.length; i++) { var slot = hotbarSlots[i]; if (slot.weapon === weapon) { // If this was the current weapon, switch to pistol if (player.currentGun === weapon) { player.switchGun('pistol'); } // Clear the slot slot.weapon = null; slot.weaponType = null; if (slot.weaponGraphics) { slot.weaponGraphics.destroy(); slot.weaponGraphics = null; } // Reset slot color slot.tint = 0x2c3e50; // Clear selection if this weapon was selected if (selectedWeapon === weapon && selectedSlot === slot) { selectedWeapon = null; selectedSlot = null; } updateUI(); return true; } } return false; } // Spawn shotgun pickup in each room function spawnShotgunPickup() { var shotgunPickup = new Pickup('gun', 300 + Math.random() * 1400, 400 + Math.random() * 1800); shotgunPickup.weapon = 'shotgun'; // Force it to be shotgun pickups.push(shotgunPickup); game.addChild(shotgunPickup); } function generateNewRoom() { // Check if shop should appear (every 4 waves) if (roomLevel > 0 && roomLevel % 4 === 0 && !isShopOpen) { showShop(); return; } // Clear current room if (currentRoom) { currentRoom.destroy(); } // Clear boss reference and hide boss health bar currentBoss = null; if (bossHealthBarBg) { bossHealthBarBg.alpha = 0; bossHealthBar.alpha = 0; bossNameText.alpha = 0; } // Clear bullets and pickups for (var i = 0; i < playerBullets.length; i++) { playerBullets[i].destroy(); } for (var j = 0; j < enemyBullets.length; j++) { enemyBullets[j].destroy(); } for (var k = 0; k < pickups.length; k++) { pickups[k].destroy(); } for (var l = 0; l < shotgunAmmoPickups.length; l++) { shotgunAmmoPickups[l].destroy(); } playerBullets = []; enemyBullets = []; pickups = []; shotgunAmmoPickups = []; // Create new room roomLevel++; currentRoom = new Room(roomLevel); currentRoom.generateRoom(); game.addChild(currentRoom); // Reset player position player.x = 1024; player.y = 2400; game.addChild(player); // Regenerate HP when entering new room player.heal(1); // Heal 1 HP when entering a new room updateUI(); } // Game state management var gameStarted = false; var weaponSelectionUI = null; var currentBoss = null; // Show weapon selection screen function showWeaponSelection() { // Create overlay background var overlay = game.attachAsset('wall', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.9, tint: 0x2c3e50 }); // Title text var titleText = new Text2('Choose Your Weapon', { size: 60, fill: '#ffffff' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 800; game.addChild(titleText); // Create weapon choice buttons var choiceY = 1366; // Center Y var choiceSpacing = 400; // Hammer choice var hammerChoice = game.attachAsset('wall', { width: 300, height: 200, anchorX: 0.5, anchorY: 0.5, x: 1024 - choiceSpacing, y: choiceY, tint: 0x8e44ad }); var hammerIcon = game.attachAsset('hammer', { anchorX: 0.5, anchorY: 0.5, x: 1024 - choiceSpacing, y: choiceY - 20, scaleX: 3, scaleY: 3 }); var hammerText = new Text2('HAMMER', { size: 32, fill: '#ffffff' }); hammerText.anchor.set(0.5, 0.5); hammerText.x = 1024 - choiceSpacing; hammerText.y = choiceY + 50; game.addChild(hammerText); var hammerDesc = new Text2('Kills enemies\nwhen parrying', { size: 24, fill: '#ecf0f1' }); hammerDesc.anchor.set(0.5, 0.5); hammerDesc.x = 1024 - choiceSpacing; hammerDesc.y = choiceY + 80; game.addChild(hammerDesc); // Sword choice var swordChoice = game.attachAsset('wall', { width: 300, height: 200, anchorX: 0.5, anchorY: 0.5, x: 1024 + choiceSpacing, y: choiceY, tint: 0xe67e22 }); var swordIcon = game.attachAsset('sword', { anchorX: 0.5, anchorY: 0.5, x: 1024 + choiceSpacing, y: choiceY - 20, scaleX: 3, scaleY: 3 }); var swordText = new Text2('SWORD', { size: 32, fill: '#ffffff' }); swordText.anchor.set(0.5, 0.5); swordText.x = 1024 + choiceSpacing; swordText.y = choiceY + 50; game.addChild(swordText); var swordDesc = new Text2('Gives invincibility\nframes when parrying', { size: 24, fill: '#ecf0f1' }); swordDesc.anchor.set(0.5, 0.5); swordDesc.x = 1024 + choiceSpacing; swordDesc.y = choiceY + 80; game.addChild(swordDesc); // Store references for cleanup weaponSelectionUI = { overlay: overlay, titleText: titleText, hammerChoice: hammerChoice, hammerIcon: hammerIcon, hammerText: hammerText, hammerDesc: hammerDesc, swordChoice: swordChoice, swordIcon: swordIcon, swordText: swordText, swordDesc: swordDesc }; // Add click handlers hammerChoice.down = function () { chooseWeapon('hammer'); }; swordChoice.down = function () { chooseWeapon('sword'); }; } // Handle weapon selection function chooseWeapon(weaponType) { if (!weaponSelectionUI) return; // Clean up weapon selection UI weaponSelectionUI.overlay.destroy(); weaponSelectionUI.titleText.destroy(); weaponSelectionUI.hammerChoice.destroy(); weaponSelectionUI.hammerIcon.destroy(); weaponSelectionUI.hammerText.destroy(); weaponSelectionUI.hammerDesc.destroy(); weaponSelectionUI.swordChoice.destroy(); weaponSelectionUI.swordIcon.destroy(); weaponSelectionUI.swordText.destroy(); weaponSelectionUI.swordDesc.destroy(); weaponSelectionUI = null; // Start the game with chosen weapon startGameWithWeapon(weaponType); } // Initialize game with chosen weapon function startGameWithWeapon(startingWeapon) { gameStarted = true; // Initialize game currentRoom = new Room(roomLevel); currentRoom.generateRoom(); game.addChild(currentRoom); player = new Player(); player.x = 1024; player.y = 2400; game.addChild(player); // Set the chosen starting weapon player.switchMelee(startingWeapon); // Update hotbar to reflect chosen weapon hotbarSlots[1].weapon = startingWeapon; hotbarSlots[1].weaponType = 'melee'; if (hotbarSlots[1].weaponGraphics) { hotbarSlots[1].weaponGraphics.destroy(); } hotbarSlots[1].weaponGraphics = hotbarSlots[1].attachAsset(startingWeapon, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); updateUI(); } // Show weapon selection at start showWeaponSelection(); // Touch controls game.down = function (x, y, obj) { isDragging = true; dragStartX = x; dragStartY = y; }; game.move = function (x, y, obj) { // Update aim position for rifle full auto if (isRightClickHeld && player && player.currentGun === 'rifle') { dragStartX = x; dragStartY = y; } }; game.up = function (x, y, obj) { if (isDragging && !isRightClickHeld) { var dx = x - dragStartX; var dy = y - dragStartY; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 30) { // Check if this is a potential parry (tap in bottom area of screen or near parry button) var parryButtonDistance = Math.sqrt((x - 200) * (x - 200) + (y - 2500) * (y - 2500)); if (y > 2200 || parryButtonDistance < 120) { // Tap in bottom area or near parry button - parry player.parry(); } else { // Tap elsewhere - shoot if (player) { player.shoot(x, y); } } } } isDragging = false; isRightClickHeld = false; rightClickDetected = false; }; // Double click variables for movement system var lastClickTime = 0; var doubleClickThreshold = 300; // milliseconds var rightClickDetected = false; var isRightClickHeld = false; var fullAutoTimer = 0; var fullAutoInterval = 8; // Fire every 8 ticks for rifle full auto // Function to handle double click movement function handleDoubleClick(x, y) { // Move player to clicked position with smooth movement var targetX = Math.max(80, Math.min(1968, x)); var targetY = Math.max(80, Math.min(2652, y)); // Use tween for smooth movement tween(player, { x: targetX, y: targetY }, { duration: 500 }); } // Override the existing down handler to detect double clicks and right clicks game.down = function (x, y, obj) { var currentTime = Date.now(); // Check for right click (simulate with touch hold detection) // We'll detect right click as a hold that lasts longer than normal tap LK.setTimeout(function () { if (isDragging && !rightClickDetected) { // This is a right click hold isRightClickHeld = true; rightClickDetected = true; fullAutoTimer = 0; } }, 200); // 200ms to detect hold if (currentTime - lastClickTime < doubleClickThreshold) { // Double click detected handleDoubleClick(x, y); lastClickTime = 0; // Reset to prevent triple click } else { // Single click - start drag isDragging = true; dragStartX = x; dragStartY = y; lastClickTime = currentTime; } }; // Main game loop game.update = function () { if (!gameStarted || !player || !currentRoom || isLevelingUp || isShopOpen) return; // Update player player.update(); // Update player bullets for (var i = playerBullets.length - 1; i >= 0; i--) { var bullet = playerBullets[i]; bullet.update(); if (!bullet.active) { bullet.destroy(); playerBullets.splice(i, 1); continue; } // Check collision with enemies for (var j = 0; j < currentRoom.enemies.length; j++) { var enemy = currentRoom.enemies[j]; if (enemy.active && bullet.intersects(enemy)) { // Handle RPG explosion if (bullet.gunType === 'rpg' && bullet.isExplosive) { // Explosion damages all enemies on screen for (var exp = 0; exp < currentRoom.enemies.length; exp++) { var expEnemy = currentRoom.enemies[exp]; if (expEnemy.active) { var explosionDamage = expEnemy.isBoss ? 30 : 40; expEnemy.takeDamage(explosionDamage); } } // Visual explosion effect LK.effects.flashScreen(0xff6b35, 500); bullet.active = false; bullet.destroy(); playerBullets.splice(i, 1); break; } else { // Check if this is fire damage from upgraded pistol var isFireDamage = bullet.gunType === 'pistol' && hasUpgradePistol; enemy.takeDamage(bullet.damage, isFireDamage); // Handle sniper piercing if (bullet.gunType === 'sniper' && bullet.pierceCount < bullet.maxPierce) { bullet.pierceCount++; // Continue through enemy, don't destroy bullet } else { bullet.active = false; bullet.destroy(); playerBullets.splice(i, 1); } break; } } } } // Update enemy bullets for (var k = enemyBullets.length - 1; k >= 0; k--) { var enemyBullet = enemyBullets[k]; enemyBullet.update(); if (!enemyBullet.active) { enemyBullet.destroy(); enemyBullets.splice(k, 1); continue; } // Check collision with player if (enemyBullet.intersects(player)) { if (player.parryWindow > 0) { // Successful parry - find the enemy that shot this bullet and stun them var bulletShooter = null; var shortestDistance = Infinity; for (var bulletCheck = 0; bulletCheck < currentRoom.enemies.length; bulletCheck++) { var checkEnemy = currentRoom.enemies[bulletCheck]; if (checkEnemy.active) { var checkDx = checkEnemy.x - enemyBullet.x; var checkDy = checkEnemy.y - enemyBullet.y; var checkDistance = Math.sqrt(checkDx * checkDx + checkDy * checkDy); if (checkDistance < shortestDistance) { shortestDistance = checkDistance; bulletShooter = checkEnemy; } } } // Stun the enemy that shot the bullet if (bulletShooter) { if (bulletShooter.isBoss) { // Boss takes 5 damage when parried, cannot be stunned or killed by parry bulletShooter.takeDamage(5); } else if (player.currentMelee === 'hammer') { // Hammer kills the enemy instead of stunning bulletShooter.takeDamage(bulletShooter.health); } else if (player.currentMelee === 'sword') { // Sword stuns enemy for 5 seconds (works on both regular and blue enemies) bulletShooter.stun(300); // Stun for 5 seconds (300 ticks at 60fps) } else { // Other weapons stun the enemy for 2 seconds bulletShooter.stun(120); // Stun for 2 seconds (120 ticks at 60fps) } } var meleeStat = player.meleeStats[player.currentMelee]; // Give sword users 5 seconds of invincibility frames if (player.currentMelee === 'sword') { player.invincibilityFrames = 300; // 5 seconds at 60fps // Visual feedback for invincibility tween(player, { alpha: 0.5 }, { duration: 5000, onFinish: function onFinish() { player.alpha = 1; } }); } if (meleeStat.effect === 'reflect') { // Reflect bullet back var reflectedBullet = new Bullet(true, 'pistol'); reflectedBullet.x = enemyBullet.x; reflectedBullet.y = enemyBullet.y; reflectedBullet.startX = enemyBullet.x; reflectedBullet.startY = enemyBullet.y; reflectedBullet.direction = enemyBullet.direction + Math.PI; reflectedBullet.speed = 15; playerBullets.push(reflectedBullet); game.addChild(reflectedBullet); } else if (meleeStat.effect === 'shockwave') { // Damage nearby enemies for (var l = 0; l < currentRoom.enemies.length; l++) { var nearbyEnemy = currentRoom.enemies[l]; var dx = nearbyEnemy.x - player.x; var dy = nearbyEnemy.y - player.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 150 && nearbyEnemy.active) { if (nearbyEnemy.isBoss) { // Boss takes 5 damage from shockwave, cannot be killed nearbyEnemy.takeDamage(5); } else if (player.currentMelee === 'hammer') { // Hammer kills nearby enemies (works on both regular and blue enemies) nearbyEnemy.takeDamage(nearbyEnemy.health); } else { // Other weapons damage nearby enemies nearbyEnemy.takeDamage(2); } } } } LK.effects.flashObject(player, meleeStat.color, 300); LK.setScore(LK.getScore() + 3); } else { // Player takes damage (unless they have invincibility frames) if (!player.invincibilityFrames || player.invincibilityFrames <= 0) { player.takeDamage(); } } enemyBullet.active = false; enemyBullet.destroy(); enemyBullets.splice(k, 1); } } // Update enemies for (var m = 0; m < currentRoom.enemies.length; m++) { var enemy = currentRoom.enemies[m]; if (enemy.active) { enemy.update(); } } // Update pickups for (var n = pickups.length - 1; n >= 0; n--) { var pickup = pickups[n]; pickup.update(); if (!pickup.active) { pickups.splice(n, 1); } } // Update shotgun ammo pickups for (var o = shotgunAmmoPickups.length - 1; o >= 0; o--) { var ammoPickup = shotgunAmmoPickups[o]; ammoPickup.update(); if (!ammoPickup.active) { shotgunAmmoPickups.splice(o, 1); } } // Check room completion if (currentRoom.checkCleared()) { // Check if player reaches door if (currentRoom.door && player.intersects(currentRoom.door)) { generateNewRoom(); } } // Reset trash icon when no weapon is selected if (!selectedWeapon && trashIcon.tint !== 0xc0392b) { tween(trashIcon, { tint: 0xc0392b }, { duration: 100 }); } // Handle aimed shooting for rifle while held if (isRightClickHeld && player && player.currentGun === 'rifle') { fullAutoTimer++; if (fullAutoTimer >= fullAutoInterval) { // Use current mouse/touch position for aiming var currentX = dragStartX; // This gets updated in move handler var currentY = dragStartY; // This gets updated in move handler player.shoot(currentX, currentY); fullAutoTimer = 0; } } // Update slowdown system if (slowdownCooldown > 0) { slowdownCooldown--; } if (slowdownDuration > 0) { slowdownDuration--; if (slowdownDuration <= 0) { slowdownActive = false; // Reset button color when slowdown ends tween(slowdownButton, { tint: 0x3498db }, { duration: 200 }); } } // Update slowdown button display if (slowdownCooldown > 0) { var secondsLeft = Math.ceil(slowdownCooldown / 60); slowdownCooldownText.setText(secondsLeft + 's'); slowdownButton.tint = 0xe74c3c; // Red when on cooldown } else { slowdownCooldownText.setText(''); if (!slowdownActive) { slowdownButton.tint = 0x3498db; // Blue when ready } } // Update UI periodically if (LK.ticks % 30 === 0) { updateUI(); } }; function showShop() { isShopOpen = true; // Create overlay background var overlay = game.attachAsset('wall', { width: 2048, height: 2732, anchorX: 0, anchorY: 0, x: 0, y: 0, alpha: 0.9, tint: 0x2c3e50 }); // Title text var titleText = new Text2('SHOPKEEPER', { size: 60, fill: '#f39c12' }); titleText.anchor.set(0.5, 0.5); titleText.x = 1024; titleText.y = 600; game.addChild(titleText); // Available shop items var shopItems = []; // Always include upgrade pistol if not owned if (!hasUpgradePistol) { shopItems.push({ id: 'upgradePistol', name: 'Upgraded Pistol', price: 15, description: 'Better damage\nand fire rate' }); } // Add random items var randomItems = [{ id: 'health', name: '+5 Max HP', price: 10, description: 'Increase health' }, { id: 'damage', name: '+3 Damage', price: 12, description: 'More damage' }, { id: 'rpg', name: 'RPG Launcher', price: 30, description: '40 damage explosion\n30 on bosses' }]; // Add 2-3 random items var itemsToAdd = Math.min(3 - shopItems.length, randomItems.length); for (var i = 0; i < itemsToAdd; i++) { if (randomItems.length > 0) { var randomIndex = Math.floor(Math.random() * randomItems.length); shopItems.push(randomItems[randomIndex]); randomItems.splice(randomIndex, 1); } } // Create shop item buttons var choiceY = 1366; var choiceSpacing = 300; var choices = []; var choiceTexts = []; var choiceDescTexts = []; for (var j = 0; j < shopItems.length; j++) { var item = shopItems[j]; var xPos = 1024 + (j - Math.floor(shopItems.length / 2)) * choiceSpacing; var choice = game.attachAsset('wall', { width: 350, height: 200, anchorX: 0.5, anchorY: 0.5, x: xPos, y: choiceY, tint: playerCash >= item.price ? 0x27ae60 : 0xe74c3c }); var nameText = new Text2(item.name, { size: 36, fill: '#ffffff' }); nameText.anchor.set(0.5, 0.5); nameText.x = xPos; nameText.y = choiceY - 40; game.addChild(nameText); var priceText = new Text2('$' + item.price, { size: 44, fill: '#f39c12' }); priceText.anchor.set(0.5, 0.5); priceText.x = xPos; priceText.y = choiceY + 10; game.addChild(priceText); var descText = new Text2(item.description, { size: 28, fill: '#ecf0f1' }); descText.anchor.set(0.5, 0.5); descText.x = xPos; descText.y = choiceY + 60; game.addChild(descText); choice.shopItem = item; choice.down = function () { buyItem(this.shopItem); }; choices.push(choice); choiceTexts.push(nameText); choiceTexts.push(priceText); choiceDescTexts.push(descText); } // Continue button var continueButton = game.attachAsset('wall', { width: 200, height: 80, anchorX: 0.5, anchorY: 0.5, x: 1024, y: 2000, tint: 0x3498db }); var continueText = new Text2('CONTINUE', { size: 36, fill: '#ffffff' }); continueText.anchor.set(0.5, 0.5); continueText.x = 1024; continueText.y = 2000; game.addChild(continueText); continueButton.down = function () { closeShop(); }; // Store references for cleanup shopChoices = { overlay: overlay, titleText: titleText, choices: choices, choiceTexts: choiceTexts, choiceDescTexts: choiceDescTexts, continueButton: continueButton, continueText: continueText }; } function buyItem(item) { if (playerCash < item.price) return; playerCash -= item.price; storage.playerCash = playerCash; switch (item.id) { case 'upgradePistol': hasUpgradePistol = true; storage.hasUpgradePistol = true; // Upgrade pistol stats player.gunStats.pistol.cooldown = 8; player.gunStats.pistol.damage = 8; break; case 'health': player.maxHealth += 5; player.health += 5; break; case 'damage': damageBonus += 3; break; case 'rpg': player.rpgAmmo = 1; if (!addWeaponToHotbar('rpg', 'gun')) { // If hotbar full, replace oldest gun weapon with RPG for (var slot = 0; slot < hotbarSlots.length; slot++) { if (hotbarSlots[slot].weaponType === 'gun' && hotbarSlots[slot].weapon !== 'rpg') { // Remove old weapon graphics if (hotbarSlots[slot].weaponGraphics) { hotbarSlots[slot].weaponGraphics.destroy(); } // Add RPG to this slot hotbarSlots[slot].weapon = 'rpg'; hotbarSlots[slot].weaponType = 'gun'; hotbarSlots[slot].weaponGraphics = hotbarSlots[slot].attachAsset('pistol', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2, tint: 0xe74c3c }); break; } } } break; } updateUI(); closeShop(); } function closeShop() { if (!shopChoices) return; // Clean up shop UI shopChoices.overlay.destroy(); shopChoices.titleText.destroy(); shopChoices.continueButton.destroy(); shopChoices.continueText.destroy(); for (var i = 0; i < shopChoices.choices.length; i++) { shopChoices.choices[i].destroy(); } for (var j = 0; j < shopChoices.choiceTexts.length; j++) { shopChoices.choiceTexts[j].destroy(); } for (var k = 0; k < shopChoices.choiceDescTexts.length; k++) { shopChoices.choiceDescTexts[k].destroy(); } shopChoices = null; isShopOpen = false; // Continue to next room continueToNextRoom(); } function continueToNextRoom() { // Clear current room if (currentRoom) { currentRoom.destroy(); } // Clear boss reference and hide boss health bar currentBoss = null; if (bossHealthBarBg) { bossHealthBarBg.alpha = 0; bossHealthBar.alpha = 0; bossNameText.alpha = 0; } // Clear bullets and pickups for (var i = 0; i < playerBullets.length; i++) { playerBullets[i].destroy(); } for (var j = 0; j < enemyBullets.length; j++) { enemyBullets[j].destroy(); } for (var k = 0; k < pickups.length; k++) { pickups[k].destroy(); } for (var l = 0; l < shotgunAmmoPickups.length; l++) { shotgunAmmoPickups[l].destroy(); } playerBullets = []; enemyBullets = []; pickups = []; shotgunAmmoPickups = []; // Create new room roomLevel++; currentRoom = new Room(roomLevel); currentRoom.generateRoom(); game.addChild(currentRoom); // Reset player position player.x = 1024; player.y = 2400; game.addChild(player); // Regenerate HP when entering new room player.heal(1); updateUI(); }
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var BlueEnemy = Container.expand(function (roomLevel) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('blueEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Set blue enemy health to 5 HP
self.health = 5;
self.speed = Math.min(1.5 + roomLevel * 0.2, 3.5);
self.shootTimer = 0;
self.shootCooldown = Math.max(90 - roomLevel * 3, 50);
self.active = true;
self.lastPlayerX = 0;
self.lastPlayerY = 0;
self.stunned = false;
self.stunTimer = 0;
self.isBlueEnemy = true;
self.update = function () {
if (!self.active || !player || isLevelingUp) return;
// Handle stunning
if (self.stunned) {
self.stunTimer--;
if (self.stunTimer <= 0) {
self.stunned = false;
}
return; // Don't move or shoot while stunned
}
// Track last position for intersection detection
self.lastPlayerX = player.x;
self.lastPlayerY = player.y;
// Move aggressively towards player for close range shotgun attack
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var optimalDistance = 120; // Very close for shotgun effectiveness
var minDistance = 80; // Get very close
// Apply slowdown effect if active
var currentSpeed = self.speed;
if (slowdownActive) {
currentSpeed = self.speed * slowdownEffect;
}
if (distance > optimalDistance) {
// Move closer aggressively
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
} else if (distance < minDistance) {
// Too close - back away slightly
self.x -= dx / distance * currentSpeed * 0.5;
self.y -= dx / distance * currentSpeed * 0.5;
}
// Shooting logic - shoots when close to player
self.shootTimer++;
if (self.shootTimer >= self.shootCooldown && distance < 200) {
self.shoot();
self.shootTimer = 0;
}
};
self.shoot = function () {
// Shotgun spread attack - fires 3 bullets in a spread
var baseAngle = Math.atan2(player.y - self.y, player.x - self.x);
for (var i = -1; i <= 1; i++) {
var bullet = new Bullet(false);
bullet.direction = baseAngle + i * 0.4; // Wide spread
bullet.x = self.x;
bullet.y = self.y;
bullet.startX = self.x;
bullet.startY = self.y;
bullet.speed = 12;
bullet.damage = 3; // Increased damage per pellet for stronger shotgun
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.stun = function (duration) {
self.stunned = true;
self.stunTimer = duration;
// Visual feedback for stunned enemy
LK.effects.flashObject(self, 0xfff700, duration * 16.67);
};
self.takeDamage = function (damage, isFireDamage) {
self.health -= damage;
if (isFireDamage && hasUpgradePistol) {
// Fire effect - red-orange flash that fades over time
LK.effects.flashObject(self, 0xff4500, 1000);
// Add burning visual effect with tween
tween(self, {
tint: 0xff6b35
}, {
duration: 500,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else {
LK.effects.flashObject(self, 0xFFFFFF, 200);
}
if (self.health <= 0) {
self.active = false;
LK.setScore(LK.getScore() + 15); // Higher score for blue enemies
gainXP(75); // More XP for blue enemies
gainCash(1); // Gain $1 per enemy killed
LK.getSound('enemyHit').play();
// Reduce slowdown cooldown by 5 seconds (300 ticks)
if (slowdownCooldown > 0) {
slowdownCooldown = Math.max(0, slowdownCooldown - 300);
}
// 100% chance to drop sniper weapon
var sniperPickup = new Pickup('gun', self.x, self.y);
sniperPickup.weapon = 'sniper'; // Force it to be sniper
pickups.push(sniperPickup);
game.addChild(sniperPickup);
// 25% chance to drop weapon pickup (higher than normal)
if (Math.random() < 0.25) {
spawnPickup(self.x, self.y);
}
// 15% chance to drop shotgun ammo
if (Math.random() < 0.15) {
var ammoPickup = new ShotgunAmmo(self.x, self.y);
shotgunAmmoPickups.push(ammoPickup);
game.addChild(ammoPickup);
}
}
};
return self;
});
var Boss = Container.expand(function (roomLevel) {
var self = Container.call(this);
// Create boss graphics - big and purple
var bossGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3,
tint: 0x8e44ad // Purple color
});
// Boss stats scale with room level
self.maxHealth = 50 + Math.floor((roomLevel - 10) / 10) * 50;
self.health = self.maxHealth;
self.speed = Math.min(0.8 + roomLevel * 0.1, 2);
self.shootTimer = 0;
self.shootCooldown = Math.max(90 - roomLevel * 2, 45);
self.active = true;
self.isBoss = true;
self.update = function () {
if (!self.active || !player || isLevelingUp) return;
// Move towards player but maintain distance
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var optimalDistance = 300;
var minDistance = 200;
// Apply slowdown effect if active
var currentSpeed = self.speed;
if (slowdownActive) {
currentSpeed = self.speed * slowdownEffect;
}
if (distance < minDistance) {
self.x -= dx / distance * currentSpeed;
self.y -= dy / distance * currentSpeed;
} else if (distance > optimalDistance + 150) {
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
}
// Shooting logic - bosses shoot more frequently
self.shootTimer++;
if (self.shootTimer >= self.shootCooldown && distance < 600) {
self.shoot();
self.shootTimer = 0;
}
};
self.shoot = function () {
// Bosses shoot 3 bullets in a spread
var baseAngle = Math.atan2(player.y - self.y, player.x - self.x);
for (var i = -1; i <= 1; i++) {
var bullet = new Bullet(false);
bullet.direction = baseAngle + i * 0.3;
bullet.x = self.x;
bullet.y = self.y;
bullet.startX = self.x;
bullet.startY = self.y;
bullet.speed = 10;
enemyBullets.push(bullet);
game.addChild(bullet);
}
};
self.takeDamage = function (damage, isFireDamage) {
self.health -= damage;
if (isFireDamage && hasUpgradePistol) {
// Fire effect - red-orange flash that fades over time
LK.effects.flashObject(self, 0xff4500, 1000);
// Add burning visual effect with tween
tween(self, {
tint: 0xff6b35
}, {
duration: 500,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else {
LK.effects.flashObject(self, 0xFFFFFF, 200);
}
updateBossHealthBar();
if (self.health <= 0) {
self.active = false;
LK.setScore(LK.getScore() + 100);
gainXP(200);
gainCash(1); // Gain $1 per enemy killed
LK.getSound('enemyHit').play();
// Hide boss health bar
if (bossHealthBarBg) {
bossHealthBarBg.alpha = 0;
bossHealthBar.alpha = 0;
}
}
};
return self;
});
var Bullet = Container.expand(function (isPlayer, gunType) {
var self = Container.call(this);
self.isPlayer = isPlayer || false;
self.gunType = gunType || 'pistol';
var bulletGraphics = self.attachAsset(self.isPlayer ? 'bullet' : 'enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
// Different bullet properties based on gun type
if (self.isPlayer) {
switch (self.gunType) {
case 'pistol':
self.speed = 12;
self.damage = 5 + damageBonus;
self.maxRange = 800 * rangeBonus; // Large range
break;
case 'rifle':
self.speed = 18;
self.damage = 2 + damageBonus;
self.maxRange = 1000 * rangeBonus; // Large range
break;
case 'shotgun':
self.speed = 10;
self.damage = 15 + damageBonus; // Increased from 10 to 15
self.maxRange = 300; // Short range
break;
case 'sniper':
self.speed = 25;
self.damage = 20 + damageBonus;
self.maxRange = 1200 * rangeBonus; // Very long range
self.pierceCount = 0; // Track how many enemies pierced
self.maxPierce = 3 + piercingBonus; // Can pierce through 3 + bonus enemies
break;
case 'rpg':
self.speed = 8;
self.damage = 40; // High damage
self.maxRange = 600;
self.isExplosive = true;
break;
}
} else {
self.speed = 8;
self.damage = 1;
self.maxRange = 400;
}
self.direction = 0;
self.active = true;
self.startX = 0;
self.startY = 0;
self.distanceTraveled = 0;
self.update = function () {
if (!self.active) return;
self.x += Math.cos(self.direction) * self.speed;
self.y += Math.sin(self.direction) * self.speed;
// Track distance traveled
var dx = self.x - self.startX;
var dy = self.y - self.startY;
self.distanceTraveled = Math.sqrt(dx * dx + dy * dy);
// Check range limit
if (self.maxRange && self.distanceTraveled > self.maxRange) {
self.active = false;
}
// Check bounds
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.active = false;
}
};
return self;
});
var Enemy = Container.expand(function (roomLevel) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Set enemy health to 10 HP
self.health = 10;
self.speed = Math.min(1 + roomLevel * 0.2, 3);
self.shootTimer = 0;
self.shootCooldown = Math.max(120 - roomLevel * 5, 60);
self.active = true;
self.lastPlayerX = 0;
self.lastPlayerY = 0;
self.stunned = false;
self.stunTimer = 0;
self.update = function () {
if (!self.active || !player || isLevelingUp) return;
// Handle stunning
if (self.stunned) {
self.stunTimer--;
if (self.stunTimer <= 0) {
self.stunned = false;
}
return; // Don't move or shoot while stunned
}
// Track last position for intersection detection
self.lastPlayerX = player.x;
self.lastPlayerY = player.y;
// Move to maintain optimal shooting distance
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var optimalDistance = 250; // Preferred shooting distance
var minDistance = 150; // Minimum distance to maintain
// Apply slowdown effect if active
var currentSpeed = self.speed;
if (slowdownActive) {
currentSpeed = self.speed * slowdownEffect;
}
if (distance < minDistance) {
// Too close - back away from player
self.x -= dx / distance * currentSpeed;
self.y -= dy / distance * currentSpeed;
} else if (distance > optimalDistance + 100) {
// Too far - move closer but stop at optimal distance
self.x += dx / distance * currentSpeed;
self.y += dy / distance * currentSpeed;
}
// If between minDistance and optimalDistance+100, stay in place
// Shooting logic
self.shootTimer++;
if (self.shootTimer >= self.shootCooldown && distance < 500) {
self.shoot();
self.shootTimer = 0;
}
};
self.shoot = function () {
var bullet = new Bullet(false);
var angle = Math.atan2(player.y - self.y, player.x - self.x);
bullet.direction = angle;
bullet.x = self.x;
bullet.y = self.y;
bullet.startX = self.x;
bullet.startY = self.y;
enemyBullets.push(bullet);
game.addChild(bullet);
};
self.stun = function (duration) {
self.stunned = true;
self.stunTimer = duration;
// Visual feedback for stunned enemy
LK.effects.flashObject(self, 0xfff700, duration * 16.67);
};
self.takeDamage = function (damage, isFireDamage) {
self.health -= damage;
if (isFireDamage && hasUpgradePistol) {
// Fire effect - red-orange flash that fades over time
LK.effects.flashObject(self, 0xff4500, 1000);
// Add burning visual effect with tween
tween(self, {
tint: 0xff6b35
}, {
duration: 500,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 300
});
}
});
} else {
LK.effects.flashObject(self, 0xFFFFFF, 200);
}
if (self.health <= 0) {
self.active = false;
LK.setScore(LK.getScore() + 10);
gainXP(50); // Gain 50 XP per enemy killed
gainCash(1); // Gain $1 per enemy killed
LK.getSound('enemyHit').play();
// Reduce slowdown cooldown by 5 seconds (300 ticks)
if (slowdownCooldown > 0) {
slowdownCooldown = Math.max(0, slowdownCooldown - 300);
}
// 40% chance to drop sniper weapon
if (Math.random() < 0.4) {
var sniperPickup = new Pickup('gun', self.x, self.y);
sniperPickup.weapon = 'sniper'; // Force it to be sniper
pickups.push(sniperPickup);
game.addChild(sniperPickup);
}
// 20% chance to drop weapon pickup
if (Math.random() < 0.2) {
spawnPickup(self.x, self.y);
}
// 10% chance to drop shotgun ammo
if (Math.random() < 0.1) {
var ammoPickup = new ShotgunAmmo(self.x, self.y);
shotgunAmmoPickups.push(ammoPickup);
game.addChild(ammoPickup);
}
}
};
return self;
});
var Pickup = Container.expand(function (type, x, y) {
var self = Container.call(this);
self.type = type; // 'gun' or 'melee'
self.x = x;
self.y = y;
self.active = true;
self.bobOffset = Math.random() * Math.PI * 2;
self.startY = y;
var pickupGraphics = self.attachAsset(type === 'gun' ? 'gunPickup' : 'meleePickup', {
anchorX: 0.5,
anchorY: 0.5
});
// Determine what weapon this pickup contains
if (type === 'gun') {
var guns = ['pistol', 'rifle', 'shotgun', 'sniper'];
self.weapon = guns[Math.floor(Math.random() * guns.length)];
} else {
var melees = ['sword', 'hammer'];
self.weapon = melees[Math.floor(Math.random() * melees.length)];
}
self.update = function () {
if (!self.active) return;
// Bobbing animation
self.y = self.startY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Check pickup collision with player
if (self.intersects(player)) {
self.active = false;
if (self.type === 'gun') {
if (addWeaponToHotbar(self.weapon, 'gun')) {
// Successfully added to hotbar
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 5);
}
} else {
if (addWeaponToHotbar(self.weapon, 'melee')) {
// Successfully added to hotbar
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 5);
}
}
self.destroy();
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
var playerGraphics = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5
});
// Gun display
var gunGraphics = self.attachAsset('pistol', {
anchorX: 0,
anchorY: 0.5,
x: 20,
y: 0
});
// Melee weapon display
var meleeGraphics = self.attachAsset('sword', {
anchorX: 0.5,
anchorY: 1,
x: -15,
y: -10
});
self.health = 10;
self.maxHealth = 10;
self.speed = 5;
self.shootCooldown = 0;
self.parryCooldown = 0;
self.parryWindow = 0;
self.invincibilityFrames = 0;
// Weapon systems
self.currentGun = 'pistol';
self.currentMelee = 'sword';
// Ammo system
self.shotgunAmmo = 5;
self.sniperAmmo = 5;
self.rpgAmmo = 0;
// Gun properties
self.gunStats = {
pistol: {
cooldown: 12,
spread: 0
},
rifle: {
cooldown: 8,
spread: 0.1
},
shotgun: {
cooldown: 30,
spread: 0.3,
pellets: 3
},
sniper: {
cooldown: 60,
spread: 0,
ammo: 5
},
rpg: {
cooldown: 120,
spread: 0,
ammo: 1
}
};
// Melee properties
self.meleeStats = {
sword: {
parryWindow: 8,
cooldown: 25,
effect: 'reflect',
color: 0x1abc9c
},
hammer: {
parryWindow: 15,
cooldown: 45,
effect: 'shockwave',
color: 0xe74c3c
}
};
self.shoot = function (targetX, targetY) {
if (self.shootCooldown > 0) return;
// Check ammo for shotgun and sniper
if (self.currentGun === 'shotgun' && self.shotgunAmmo <= 0) return;
if (self.currentGun === 'sniper' && self.sniperAmmo <= 0) {
// Remove sniper from hotbar when out of ammo
removeWeaponFromHotbar('sniper');
return;
}
if (self.currentGun === 'rpg' && self.rpgAmmo <= 0) {
// Remove RPG from hotbar when out of ammo
removeWeaponFromHotbar('rpg');
return;
}
var gunStat = self.gunStats[self.currentGun];
var baseAngle = Math.atan2(targetY - self.y, targetX - self.x);
if (self.currentGun === 'shotgun') {
// Shotgun fires multiple pellets and consumes ammo
self.shotgunAmmo--;
for (var i = 0; i < gunStat.pellets; i++) {
var bullet = new Bullet(true, self.currentGun);
bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread;
bullet.x = self.x;
bullet.y = self.y;
bullet.startX = self.x;
bullet.startY = self.y;
playerBullets.push(bullet);
game.addChild(bullet);
}
} else if (self.currentGun === 'sniper') {
// Sniper fires single high-damage piercing bullet and consumes ammo
self.sniperAmmo--;
var bullet = new Bullet(true, self.currentGun);
bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread;
bullet.x = self.x;
bullet.y = self.y;
bullet.startX = self.x;
bullet.startY = self.y;
playerBullets.push(bullet);
game.addChild(bullet);
} else if (self.currentGun === 'rpg') {
// RPG fires explosive rocket and consumes ammo
self.rpgAmmo--;
var bullet = new Bullet(true, self.currentGun);
bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread;
bullet.x = self.x;
bullet.y = self.y;
bullet.startX = self.x;
bullet.startY = self.y;
playerBullets.push(bullet);
game.addChild(bullet);
} else {
var bullet = new Bullet(true, self.currentGun);
bullet.direction = baseAngle + (Math.random() - 0.5) * gunStat.spread;
bullet.x = self.x;
bullet.y = self.y;
bullet.startX = self.x;
bullet.startY = self.y;
playerBullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
self.shootCooldown = gunStat.cooldown;
};
self.parry = function () {
if (self.parryCooldown > 0) return;
var meleeStat = self.meleeStats[self.currentMelee];
self.parryWindow = 180; // 3 seconds at 60fps
self.parryCooldown = meleeStat.cooldown;
// Visual feedback
var parryFX = game.attachAsset('parryEffect', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
alpha: 0.8,
tint: meleeStat.color
});
tween(parryFX, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 3000,
// 3 seconds
onFinish: function onFinish() {
parryFX.destroy();
}
});
LK.getSound('parry').play();
};
self.switchGun = function (newGun) {
if (newGun && self.gunStats[newGun]) {
self.currentGun = newGun;
gunGraphics.destroy();
gunGraphics = self.attachAsset(newGun, {
anchorX: 0,
anchorY: 0.5,
x: 20,
y: 0
});
}
};
self.switchMelee = function (newMelee) {
if (newMelee && self.meleeStats[newMelee]) {
self.currentMelee = newMelee;
meleeGraphics.destroy();
meleeGraphics = self.attachAsset(newMelee, {
anchorX: 0.5,
anchorY: 1,
x: -15,
y: -10
});
}
};
self.takeDamage = function () {
self.health--;
LK.effects.flashObject(self, 0xFF0000, 500);
LK.getSound('playerHit').play();
if (self.health <= 0) {
LK.showGameOver();
}
};
self.heal = function (amount) {
self.health = Math.min(self.health + amount, self.maxHealth);
};
self.update = function () {
if (self.shootCooldown > 0) self.shootCooldown--;
if (self.parryCooldown > 0) self.parryCooldown--;
if (self.invincibilityFrames > 0) self.invincibilityFrames--;
if (self.parryWindow > 0) {
self.parryWindow--;
// Check if parry window just expired without being used
if (self.parryWindow === 0) {
// Parry was missed, add 2 second cooldown
self.parryCooldown = 120; // 2 seconds at 60fps
}
}
};
return self;
});
var RPG = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.active = true;
self.bobOffset = Math.random() * Math.PI * 2;
self.startY = y;
var rpgGraphics = self.attachAsset('pistol', {
anchorX: 0.5,
anchorY: 0.5,
tint: 0xe74c3c,
scaleX: 2.0,
scaleY: 2.0
});
self.update = function () {
if (!self.active) return;
// Bobbing animation
self.y = self.startY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Check pickup collision with player
if (self.intersects(player)) {
self.active = false;
if (addWeaponToHotbar('rpg', 'gun')) {
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 5);
}
self.destroy();
}
};
return self;
});
var Room = Container.expand(function (level) {
var self = Container.call(this);
self.level = level || 1;
self.enemies = [];
self.pickups = [];
self.cleared = false;
self.door = null;
self.generateRoom = function () {
// Create border walls
var wallSize = 60;
var roomWidth = 2048;
var roomHeight = 2732;
var wallsX = Math.floor(roomWidth / wallSize);
var wallsY = Math.floor(roomHeight / wallSize);
// Top and bottom walls
for (var i = 0; i < wallsX; i++) {
var topWall = self.attachAsset('wall', {
x: i * wallSize,
y: 0
});
var bottomWall = self.attachAsset('wall', {
x: i * wallSize,
y: roomHeight - wallSize
});
}
// Left and right walls
for (var j = 1; j < wallsY - 1; j++) {
var leftWall = self.attachAsset('wall', {
x: 0,
y: j * wallSize
});
var rightWall = self.attachAsset('wall', {
x: roomWidth - wallSize,
y: j * wallSize
});
}
// Check if this is a boss room (every 10 rooms)
if (self.level % 10 === 0) {
// Boss fight
var boss = new Boss(self.level);
boss.x = roomWidth / 2;
boss.y = roomHeight / 3;
self.enemies.push(boss);
self.addChild(boss);
currentBoss = boss;
// Show boss health bar
bossHealthBarBg.alpha = 1;
bossHealthBar.alpha = 1;
bossNameText.alpha = 1;
updateBossHealthBar();
} else {
// Generate regular enemies
var enemyCount = Math.min(2 + self.level, 8);
var blueEnemyCount = 0;
// Blue enemies appear starting from wave 5
if (self.level >= 5) {
blueEnemyCount = Math.min(1 + Math.floor((self.level - 5) / 3), 3); // 1-3 blue enemies
enemyCount = Math.max(1, enemyCount - blueEnemyCount); // Reduce regular enemies to maintain balance
}
// After wave 5, convert percentage of normal enemies to blue enemies
var blueConversionRate = 0;
if (self.level > 5) {
// Start at 10% after wave 5, increase by 5% each wave, cap at 50%
blueConversionRate = Math.min(0.1 + (self.level - 5) * 0.05, 0.5);
}
// Spawn regular enemies
for (var k = 0; k < enemyCount; k++) {
// Check if this enemy should be converted to blue
if (self.level > 5 && Math.random() < blueConversionRate) {
var convertedBlueEnemy = new BlueEnemy(self.level);
convertedBlueEnemy.x = 200 + Math.random() * (roomWidth - 400);
convertedBlueEnemy.y = 200 + Math.random() * (roomHeight - 400);
self.enemies.push(convertedBlueEnemy);
self.addChild(convertedBlueEnemy);
} else {
var enemy = new Enemy(self.level);
enemy.x = 200 + Math.random() * (roomWidth - 400);
enemy.y = 200 + Math.random() * (roomHeight - 400);
self.enemies.push(enemy);
self.addChild(enemy);
}
}
// Spawn blue enemies starting from wave 5
for (var b = 0; b < blueEnemyCount; b++) {
var blueEnemy = new BlueEnemy(self.level);
blueEnemy.x = 200 + Math.random() * (roomWidth - 400);
blueEnemy.y = 200 + Math.random() * (roomHeight - 400);
self.enemies.push(blueEnemy);
self.addChild(blueEnemy);
}
}
// Create exit door (initially locked)
self.door = self.attachAsset('door', {
anchorX: 0.5,
anchorY: 0.5,
x: roomWidth / 2,
y: wallSize / 2,
tint: 0xc0392b
});
// Spawn shotgun pickup in room
spawnShotgunPickup();
};
self.checkCleared = function () {
var activeEnemies = 0;
for (var i = 0; i < self.enemies.length; i++) {
if (self.enemies[i].active) {
activeEnemies++;
}
}
if (activeEnemies === 0 && !self.cleared) {
self.cleared = true;
// Unlock door
if (self.door) {
tween(self.door, {
tint: 0x27ae60
}, {
duration: 500
});
}
}
return self.cleared;
};
return self;
});
var ShotgunAmmo = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.active = true;
self.bobOffset = Math.random() * Math.PI * 2;
self.startY = y;
var ammoGraphics = self.attachAsset('shotgunAmmo', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.active) return;
// Bobbing animation
self.y = self.startY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Check pickup collision with player
if (self.intersects(player)) {
self.active = false;
player.shotgunAmmo += 3; // Give 3 ammo directly
LK.getSound('pickup').play();
LK.setScore(LK.getScore() + 2);
updateUI(); // Update ammo display
self.destroy();
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a1a
});
/****
* Game Code
****/
var player;
var currentRoom;
var playerBullets = [];
var enemyBullets = [];
var pickups = [];
var shotgunAmmoPickups = [];
var roomLevel = 1;
var dragStartX = 0;
var dragStartY = 0;
var isDragging = false;
var draggedWeapon = null;
var draggedSlot = null;
var trashIcon = null;
var selectedWeapon = null;
var selectedSlot = null;
// XP System
var playerXP = 0;
var playerLevel = 1;
var xpToNextLevel = 100;
var isLevelingUp = false;
var levelUpChoices = null;
var damageBonus = 0;
var rangeBonus = 1;
var piercingBonus = 0;
// Cash System
var playerCash = storage.playerCash || 0;
var isShopOpen = false;
var shopChoices = null;
var hasUpgradePistol = storage.hasUpgradePistol || false;
// UI Elements
var scoreText = new Text2('Score: 0', {
size: 40,
fill: '#ffffff'
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
// Health bar instead of text
var healthBarBg = LK.gui.topRight.attachAsset('wall', {
width: 150,
height: 20,
anchorX: 1,
anchorY: 0,
tint: 0x7f8c8d
});
var healthBar = LK.gui.topRight.attachAsset('wall', {
width: 150,
height: 20,
anchorX: 1,
anchorY: 0,
tint: 0xe74c3c
});
var roomText = new Text2('Room: 1', {
size: 30,
fill: '#3498db'
});
roomText.anchor.set(0, 0);
roomText.x = 120;
LK.gui.topLeft.addChild(roomText);
var gunText = new Text2('Gun: Pistol', {
size: 28,
fill: '#f39c12'
});
gunText.anchor.set(0, 1);
LK.gui.bottomLeft.addChild(gunText);
var meleeText = new Text2('Melee: Sword', {
size: 28,
fill: '#9b59b6'
});
meleeText.anchor.set(1, 1);
LK.gui.bottomRight.addChild(meleeText);
// Hotbar system
var hotbar = new Container();
hotbar.x = 1024; // Center of screen
hotbar.y = 2600; // Bottom of screen
game.addChild(hotbar);
// Cash counter to the left of hotbar
var cashText = new Text2('$0', {
size: 40,
fill: '#2ecc71'
});
cashText.anchor.set(1, 0.5);
cashText.x = -300; // Position to the left of hotbar
cashText.y = -80;
hotbar.addChild(cashText);
// Ammo counter to the left of hotbar
var ammoText = new Text2('Shotgun: 5', {
size: 32,
fill: '#f39c12'
});
ammoText.anchor.set(1, 0.5);
ammoText.x = -300; // Position to the left of hotbar
ammoText.y = 0;
hotbar.addChild(ammoText);
// Sniper ammo counter
var sniperAmmoText = new Text2('Sniper: 5', {
size: 32,
fill: '#8b4513'
});
sniperAmmoText.anchor.set(1, 0.5);
sniperAmmoText.x = -300;
sniperAmmoText.y = -40;
hotbar.addChild(sniperAmmoText);
// XP label above hotbar
var xpLabel = new Text2('XP', {
size: 48,
fill: '#ffffff'
});
xpLabel.anchor.set(0.5, 1);
xpLabel.x = 0;
xpLabel.y = -150;
hotbar.addChild(xpLabel);
// XP bar background above hotbar
var xpBarBg = hotbar.attachAsset('wall', {
width: 400,
height: 30,
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -100,
tint: 0x7f8c8d
});
// XP bar fill above hotbar
var xpBar = hotbar.attachAsset('wall', {
width: 400,
height: 30,
anchorX: 0.5,
anchorY: 1,
x: 0,
y: -100,
tint: 0xe67e22
});
// Level display next to XP bar
var levelText = new Text2('Level 1', {
size: 36,
fill: '#ffffff'
});
levelText.anchor.set(0, 0.5);
levelText.x = 220;
levelText.y = -85;
hotbar.addChild(levelText);
// Boss health bar (initially hidden)
var bossHealthBarBg = LK.gui.top.attachAsset('wall', {
width: 800,
height: 30,
anchorX: 0.5,
anchorY: 0,
y: 60,
tint: 0x7f8c8d,
alpha: 0
});
var bossHealthBar = LK.gui.top.attachAsset('wall', {
width: 800,
height: 30,
anchorX: 0.5,
anchorY: 0,
y: 60,
tint: 0x8e44ad,
alpha: 0
});
var bossNameText = new Text2('BOSS', {
size: 36,
fill: '#8e44ad'
});
bossNameText.anchor.set(0.5, 1);
bossNameText.y = 55;
bossNameText.alpha = 0;
LK.gui.top.addChild(bossNameText);
var hotbarSlots = [];
var slotWidth = 80;
var slotHeight = 80;
var slotSpacing = 100;
// Create 5 hotbar slots
for (var h = 0; h < 5; h++) {
var slot = hotbar.attachAsset('wall', {
width: slotWidth,
height: slotHeight,
anchorX: 0.5,
anchorY: 0.5,
x: (h - 2) * slotSpacing,
y: 0,
tint: 0x2c3e50
});
slot.slotIndex = h;
slot.weapon = null;
slot.weaponType = null; // 'gun' or 'melee'
// Visual weapon display
slot.weaponGraphics = null;
slot.down = function (x, y, obj) {
if (this.weapon) {
// Select weapon and light up trash
selectedWeapon = this.weapon;
selectedSlot = this;
// Highlight the selected slot
tween(this, {
tint: 0x3498db
}, {
duration: 100
});
// Light up trash icon
tween(trashIcon, {
tint: 0xff6b6b
}, {
duration: 100
});
}
};
slot.up = function (x, y, obj) {
// Switch to this weapon if it's selected but not the same as current selection
if (selectedWeapon && selectedSlot === this) {
if (this.weaponType === 'gun') {
player.switchGun(this.weapon);
} else if (this.weaponType === 'melee') {
player.switchMelee(this.weapon);
}
updateUI();
}
};
hotbarSlots.push(slot);
}
// Create trash icon next to hotbar
trashIcon = hotbar.attachAsset('trashIcon', {
width: 60,
height: 60,
anchorX: 0.5,
anchorY: 0.5,
x: 350,
// Position to the right of hotbar
y: 0,
tint: 0xc0392b
});
// Add click handler to trash icon
trashIcon.down = function (x, y, obj) {
if (selectedWeapon && selectedSlot) {
// Delete weapon from selected slot
selectedSlot.weapon = null;
selectedSlot.weaponType = null;
if (selectedSlot.weaponGraphics) {
selectedSlot.weaponGraphics.destroy();
selectedSlot.weaponGraphics = null;
}
// Reset slot color
tween(selectedSlot, {
tint: 0x2c3e50
}, {
duration: 100
});
// Flash trash icon to show deletion
tween(trashIcon, {
tint: 0xff0000
}, {
duration: 200,
onFinish: function onFinish() {
tween(trashIcon, {
tint: 0xc0392b
}, {
duration: 200
});
}
});
// Clear selection
selectedWeapon = null;
selectedSlot = null;
}
};
// Add visual indicator for trash (X symbol)
var trashX1 = hotbar.attachAsset('wall', {
width: 30,
height: 4,
anchorX: 0.5,
anchorY: 0.5,
x: 350,
y: 0,
rotation: Math.PI / 4,
tint: 0xffffff
});
var trashX2 = hotbar.attachAsset('wall', {
width: 30,
height: 4,
anchorX: 0.5,
anchorY: 0.5,
x: 350,
y: 0,
rotation: -Math.PI / 4,
tint: 0xffffff
});
// Create parry button
var parryButton = game.attachAsset('parryButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: 2500,
scaleX: 2,
scaleY: 2
});
// Create slowdown button
var slowdownButton = game.attachAsset('parryButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 1848,
// Mirror parry button position on right side
y: 2500,
// Same Y position as parry button
tint: 0x3498db,
scaleX: 2,
scaleY: 2
});
// Slowdown system variables
var slowdownCooldown = 0;
var slowdownActive = false;
var slowdownDuration = 0;
var maxSlowdownCooldown = 3000; // 50 seconds at 60fps
var slowdownEffect = 0.3; // 70% slow (30% of original speed)
// Add parry button text
var parryButtonText = new Text2('PARRY', {
size: 48,
fill: '#ffffff'
});
parryButtonText.anchor.set(0.5, 0.5);
parryButtonText.x = 200;
parryButtonText.y = 2500;
game.addChild(parryButtonText);
// Add slowdown button text
var slowdownButtonText = new Text2('SLOW', {
size: 48,
fill: '#ffffff'
});
slowdownButtonText.anchor.set(0.5, 0.5);
slowdownButtonText.x = 1848;
slowdownButtonText.y = 2500;
game.addChild(slowdownButtonText);
// Add slowdown cooldown text
var slowdownCooldownText = new Text2('', {
size: 24,
fill: '#ffffff'
});
slowdownCooldownText.anchor.set(0.5, 0.5);
slowdownCooldownText.x = 1848;
slowdownCooldownText.y = 2400;
game.addChild(slowdownCooldownText);
// Add parry button click handler
parryButton.down = function (x, y, obj) {
player.parry();
// Visual feedback on button press
tween(parryButton, {
scaleX: 0.9,
scaleY: 0.9,
tint: 0x16a085
}, {
duration: 100,
onFinish: function onFinish() {
tween(parryButton, {
scaleX: 1,
scaleY: 1,
tint: 0x1abc9c
}, {
duration: 100
});
}
});
};
// Add slowdown button click handler
slowdownButton.down = function (x, y, obj) {
if (slowdownCooldown <= 0 && !slowdownActive) {
// Activate slowdown
slowdownActive = true;
slowdownDuration = 600; // 10 seconds at 60fps
slowdownCooldown = maxSlowdownCooldown;
// Visual feedback on button press
tween(slowdownButton, {
scaleX: 0.9,
scaleY: 0.9,
tint: 0x2980b9
}, {
duration: 100,
onFinish: function onFinish() {
tween(slowdownButton, {
scaleX: 1,
scaleY: 1,
tint: 0xe74c3c // Red when on cooldown
}, {
duration: 100
});
}
});
}
};
// Set initial weapons in hotbar
hotbarSlots[0].weapon = 'pistol';
hotbarSlots[0].weaponType = 'gun';
hotbarSlots[0].weaponGraphics = hotbarSlots[0].attachAsset('pistol', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
hotbarSlots[1].weapon = 'sword';
hotbarSlots[1].weaponType = 'melee';
hotbarSlots[1].weaponGraphics = hotbarSlots[1].attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
hotbarSlots[2].weapon = 'rifle';
hotbarSlots[2].weaponType = 'gun';
hotbarSlots[2].weaponGraphics = hotbarSlots[2].attachAsset('rifle', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
function updateUI() {
scoreText.setText('Score: ' + LK.getScore());
// Update health bar
var healthPercent = player.health / player.maxHealth;
healthBar.width = 150 * healthPercent;
roomText.setText('Room: ' + roomLevel);
gunText.setText('Gun: ' + player.currentGun.charAt(0).toUpperCase() + player.currentGun.slice(1));
meleeText.setText('Melee: ' + player.currentMelee.charAt(0).toUpperCase() + player.currentMelee.slice(1));
// Update ammo counter
ammoText.setText('Shotgun: ' + player.shotgunAmmo);
// Update sniper ammo counter
sniperAmmoText.setText('Sniper: ' + player.sniperAmmo);
// Update XP bar display
var xpPercent = playerXP / xpToNextLevel;
xpBar.width = 400 * xpPercent;
levelText.setText('Level ' + playerLevel);
// Update cash display
cashText.setText('$' + playerCash);
}
function updateBossHealthBar() {
if (currentBoss && currentBoss.active) {
var healthPercent = currentBoss.health / currentBoss.maxHealth;
bossHealthBar.width = 800 * healthPercent;
}
}
function gainXP(amount) {
playerXP += amount;
if (playerXP >= xpToNextLevel && !isLevelingUp) {
levelUp();
}
}
function gainCash(amount) {
playerCash += amount;
storage.playerCash = playerCash;
}
function levelUp() {
isLevelingUp = true;
playerLevel++;
playerXP -= xpToNextLevel;
xpToNextLevel = Math.floor(xpToNextLevel * 1.5);
// Create level up choices overlay
showLevelUpChoices();
}
function showLevelUpChoices() {
// Create overlay background
var overlay = game.attachAsset('wall', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.8,
tint: 0x000000
});
// Randomly select 3 different upgrade options
var availableUpgrades = [{
id: 'hp',
text: '+5 Max HP',
color: 0x27ae60
}, {
id: 'damage',
text: '+2 Damage',
color: 0xe74c3c
}, {
id: 'range',
text: '+Range',
color: 0x3498db
}, {
id: 'piercing',
text: '+1 Piercing',
color: 0xf39c12
}];
// Add fire pistol if not owned
if (!hasUpgradePistol) {
availableUpgrades.push({
id: 'upgradePistol',
text: 'Fire Pistol',
color: 0xff6b35
});
}
// Shuffle and pick 3 random upgrades
var selectedUpgrades = [];
var tempUpgrades = availableUpgrades.slice(); // Create copy
for (var i = 0; i < 3; i++) {
var randomIndex = Math.floor(Math.random() * tempUpgrades.length);
selectedUpgrades.push(tempUpgrades[randomIndex]);
tempUpgrades.splice(randomIndex, 1);
}
// Create choice buttons
var choiceY = 1366; // Center Y
var choiceSpacing = 400;
var choices = [];
var choiceTexts = [];
for (var j = 0; j < 3; j++) {
var upgrade = selectedUpgrades[j];
var xPos = 1024 + (j - 1) * choiceSpacing;
var choice = game.attachAsset('wall', {
width: 300,
height: 100,
anchorX: 0.5,
anchorY: 0.5,
x: xPos,
y: choiceY,
tint: upgrade.color
});
var choiceText = new Text2(upgrade.text, {
size: 36,
fill: '#ffffff'
});
choiceText.anchor.set(0.5, 0.5);
choiceText.x = xPos;
choiceText.y = choiceY;
game.addChild(choiceText);
// Store upgrade id on the choice button
choice.upgradeId = upgrade.id;
choice.down = function () {
chooseLevelUpOption(this.upgradeId);
};
choices.push(choice);
choiceTexts.push(choiceText);
}
// Store references for cleanup
levelUpChoices = {
overlay: overlay,
choices: choices,
choiceTexts: choiceTexts
};
}
function chooseLevelUpOption(choice) {
if (!isLevelingUp || !levelUpChoices) return;
// Apply the chosen upgrade
switch (choice) {
case 'damage':
damageBonus += 2;
break;
case 'hp':
player.maxHealth += 5;
player.health += 5;
break;
case 'range':
rangeBonus += 0.2;
break;
case 'piercing':
piercingBonus++;
break;
case 'upgradePistol':
hasUpgradePistol = true;
storage.hasUpgradePistol = true;
// Upgrade pistol stats
player.gunStats.pistol.cooldown = 8;
player.gunStats.pistol.damage = 8;
break;
}
// Clean up level up UI
levelUpChoices.overlay.destroy();
for (var i = 0; i < levelUpChoices.choices.length; i++) {
levelUpChoices.choices[i].destroy();
levelUpChoices.choiceTexts[i].destroy();
}
levelUpChoices = null;
isLevelingUp = false;
updateUI();
}
function spawnPickup(x, y) {
var pickupType = Math.random() < 0.6 ? 'gun' : 'melee';
var pickup = new Pickup(pickupType, x, y);
pickups.push(pickup);
game.addChild(pickup);
}
function addWeaponToHotbar(weapon, weaponType) {
// Find first empty slot
for (var i = 0; i < hotbarSlots.length; i++) {
var slot = hotbarSlots[i];
if (!slot.weapon) {
slot.weapon = weapon;
slot.weaponType = weaponType;
// Remove old graphics if any
if (slot.weaponGraphics) {
slot.weaponGraphics.destroy();
}
// Add new weapon graphics
slot.weaponGraphics = slot.attachAsset(weapon, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
return true;
}
}
return false; // No empty slots
}
function removeWeaponFromHotbar(weapon) {
// Find and remove the weapon from hotbar
for (var i = 0; i < hotbarSlots.length; i++) {
var slot = hotbarSlots[i];
if (slot.weapon === weapon) {
// If this was the current weapon, switch to pistol
if (player.currentGun === weapon) {
player.switchGun('pistol');
}
// Clear the slot
slot.weapon = null;
slot.weaponType = null;
if (slot.weaponGraphics) {
slot.weaponGraphics.destroy();
slot.weaponGraphics = null;
}
// Reset slot color
slot.tint = 0x2c3e50;
// Clear selection if this weapon was selected
if (selectedWeapon === weapon && selectedSlot === slot) {
selectedWeapon = null;
selectedSlot = null;
}
updateUI();
return true;
}
}
return false;
}
// Spawn shotgun pickup in each room
function spawnShotgunPickup() {
var shotgunPickup = new Pickup('gun', 300 + Math.random() * 1400, 400 + Math.random() * 1800);
shotgunPickup.weapon = 'shotgun'; // Force it to be shotgun
pickups.push(shotgunPickup);
game.addChild(shotgunPickup);
}
function generateNewRoom() {
// Check if shop should appear (every 4 waves)
if (roomLevel > 0 && roomLevel % 4 === 0 && !isShopOpen) {
showShop();
return;
}
// Clear current room
if (currentRoom) {
currentRoom.destroy();
}
// Clear boss reference and hide boss health bar
currentBoss = null;
if (bossHealthBarBg) {
bossHealthBarBg.alpha = 0;
bossHealthBar.alpha = 0;
bossNameText.alpha = 0;
}
// Clear bullets and pickups
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].destroy();
}
for (var j = 0; j < enemyBullets.length; j++) {
enemyBullets[j].destroy();
}
for (var k = 0; k < pickups.length; k++) {
pickups[k].destroy();
}
for (var l = 0; l < shotgunAmmoPickups.length; l++) {
shotgunAmmoPickups[l].destroy();
}
playerBullets = [];
enemyBullets = [];
pickups = [];
shotgunAmmoPickups = [];
// Create new room
roomLevel++;
currentRoom = new Room(roomLevel);
currentRoom.generateRoom();
game.addChild(currentRoom);
// Reset player position
player.x = 1024;
player.y = 2400;
game.addChild(player);
// Regenerate HP when entering new room
player.heal(1); // Heal 1 HP when entering a new room
updateUI();
}
// Game state management
var gameStarted = false;
var weaponSelectionUI = null;
var currentBoss = null;
// Show weapon selection screen
function showWeaponSelection() {
// Create overlay background
var overlay = game.attachAsset('wall', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.9,
tint: 0x2c3e50
});
// Title text
var titleText = new Text2('Choose Your Weapon', {
size: 60,
fill: '#ffffff'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 800;
game.addChild(titleText);
// Create weapon choice buttons
var choiceY = 1366; // Center Y
var choiceSpacing = 400;
// Hammer choice
var hammerChoice = game.attachAsset('wall', {
width: 300,
height: 200,
anchorX: 0.5,
anchorY: 0.5,
x: 1024 - choiceSpacing,
y: choiceY,
tint: 0x8e44ad
});
var hammerIcon = game.attachAsset('hammer', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024 - choiceSpacing,
y: choiceY - 20,
scaleX: 3,
scaleY: 3
});
var hammerText = new Text2('HAMMER', {
size: 32,
fill: '#ffffff'
});
hammerText.anchor.set(0.5, 0.5);
hammerText.x = 1024 - choiceSpacing;
hammerText.y = choiceY + 50;
game.addChild(hammerText);
var hammerDesc = new Text2('Kills enemies\nwhen parrying', {
size: 24,
fill: '#ecf0f1'
});
hammerDesc.anchor.set(0.5, 0.5);
hammerDesc.x = 1024 - choiceSpacing;
hammerDesc.y = choiceY + 80;
game.addChild(hammerDesc);
// Sword choice
var swordChoice = game.attachAsset('wall', {
width: 300,
height: 200,
anchorX: 0.5,
anchorY: 0.5,
x: 1024 + choiceSpacing,
y: choiceY,
tint: 0xe67e22
});
var swordIcon = game.attachAsset('sword', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024 + choiceSpacing,
y: choiceY - 20,
scaleX: 3,
scaleY: 3
});
var swordText = new Text2('SWORD', {
size: 32,
fill: '#ffffff'
});
swordText.anchor.set(0.5, 0.5);
swordText.x = 1024 + choiceSpacing;
swordText.y = choiceY + 50;
game.addChild(swordText);
var swordDesc = new Text2('Gives invincibility\nframes when parrying', {
size: 24,
fill: '#ecf0f1'
});
swordDesc.anchor.set(0.5, 0.5);
swordDesc.x = 1024 + choiceSpacing;
swordDesc.y = choiceY + 80;
game.addChild(swordDesc);
// Store references for cleanup
weaponSelectionUI = {
overlay: overlay,
titleText: titleText,
hammerChoice: hammerChoice,
hammerIcon: hammerIcon,
hammerText: hammerText,
hammerDesc: hammerDesc,
swordChoice: swordChoice,
swordIcon: swordIcon,
swordText: swordText,
swordDesc: swordDesc
};
// Add click handlers
hammerChoice.down = function () {
chooseWeapon('hammer');
};
swordChoice.down = function () {
chooseWeapon('sword');
};
}
// Handle weapon selection
function chooseWeapon(weaponType) {
if (!weaponSelectionUI) return;
// Clean up weapon selection UI
weaponSelectionUI.overlay.destroy();
weaponSelectionUI.titleText.destroy();
weaponSelectionUI.hammerChoice.destroy();
weaponSelectionUI.hammerIcon.destroy();
weaponSelectionUI.hammerText.destroy();
weaponSelectionUI.hammerDesc.destroy();
weaponSelectionUI.swordChoice.destroy();
weaponSelectionUI.swordIcon.destroy();
weaponSelectionUI.swordText.destroy();
weaponSelectionUI.swordDesc.destroy();
weaponSelectionUI = null;
// Start the game with chosen weapon
startGameWithWeapon(weaponType);
}
// Initialize game with chosen weapon
function startGameWithWeapon(startingWeapon) {
gameStarted = true;
// Initialize game
currentRoom = new Room(roomLevel);
currentRoom.generateRoom();
game.addChild(currentRoom);
player = new Player();
player.x = 1024;
player.y = 2400;
game.addChild(player);
// Set the chosen starting weapon
player.switchMelee(startingWeapon);
// Update hotbar to reflect chosen weapon
hotbarSlots[1].weapon = startingWeapon;
hotbarSlots[1].weaponType = 'melee';
if (hotbarSlots[1].weaponGraphics) {
hotbarSlots[1].weaponGraphics.destroy();
}
hotbarSlots[1].weaponGraphics = hotbarSlots[1].attachAsset(startingWeapon, {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
updateUI();
}
// Show weapon selection at start
showWeaponSelection();
// Touch controls
game.down = function (x, y, obj) {
isDragging = true;
dragStartX = x;
dragStartY = y;
};
game.move = function (x, y, obj) {
// Update aim position for rifle full auto
if (isRightClickHeld && player && player.currentGun === 'rifle') {
dragStartX = x;
dragStartY = y;
}
};
game.up = function (x, y, obj) {
if (isDragging && !isRightClickHeld) {
var dx = x - dragStartX;
var dy = y - dragStartY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 30) {
// Check if this is a potential parry (tap in bottom area of screen or near parry button)
var parryButtonDistance = Math.sqrt((x - 200) * (x - 200) + (y - 2500) * (y - 2500));
if (y > 2200 || parryButtonDistance < 120) {
// Tap in bottom area or near parry button - parry
player.parry();
} else {
// Tap elsewhere - shoot
if (player) {
player.shoot(x, y);
}
}
}
}
isDragging = false;
isRightClickHeld = false;
rightClickDetected = false;
};
// Double click variables for movement system
var lastClickTime = 0;
var doubleClickThreshold = 300; // milliseconds
var rightClickDetected = false;
var isRightClickHeld = false;
var fullAutoTimer = 0;
var fullAutoInterval = 8; // Fire every 8 ticks for rifle full auto
// Function to handle double click movement
function handleDoubleClick(x, y) {
// Move player to clicked position with smooth movement
var targetX = Math.max(80, Math.min(1968, x));
var targetY = Math.max(80, Math.min(2652, y));
// Use tween for smooth movement
tween(player, {
x: targetX,
y: targetY
}, {
duration: 500
});
}
// Override the existing down handler to detect double clicks and right clicks
game.down = function (x, y, obj) {
var currentTime = Date.now();
// Check for right click (simulate with touch hold detection)
// We'll detect right click as a hold that lasts longer than normal tap
LK.setTimeout(function () {
if (isDragging && !rightClickDetected) {
// This is a right click hold
isRightClickHeld = true;
rightClickDetected = true;
fullAutoTimer = 0;
}
}, 200); // 200ms to detect hold
if (currentTime - lastClickTime < doubleClickThreshold) {
// Double click detected
handleDoubleClick(x, y);
lastClickTime = 0; // Reset to prevent triple click
} else {
// Single click - start drag
isDragging = true;
dragStartX = x;
dragStartY = y;
lastClickTime = currentTime;
}
};
// Main game loop
game.update = function () {
if (!gameStarted || !player || !currentRoom || isLevelingUp || isShopOpen) return;
// Update player
player.update();
// Update player bullets
for (var i = playerBullets.length - 1; i >= 0; i--) {
var bullet = playerBullets[i];
bullet.update();
if (!bullet.active) {
bullet.destroy();
playerBullets.splice(i, 1);
continue;
}
// Check collision with enemies
for (var j = 0; j < currentRoom.enemies.length; j++) {
var enemy = currentRoom.enemies[j];
if (enemy.active && bullet.intersects(enemy)) {
// Handle RPG explosion
if (bullet.gunType === 'rpg' && bullet.isExplosive) {
// Explosion damages all enemies on screen
for (var exp = 0; exp < currentRoom.enemies.length; exp++) {
var expEnemy = currentRoom.enemies[exp];
if (expEnemy.active) {
var explosionDamage = expEnemy.isBoss ? 30 : 40;
expEnemy.takeDamage(explosionDamage);
}
}
// Visual explosion effect
LK.effects.flashScreen(0xff6b35, 500);
bullet.active = false;
bullet.destroy();
playerBullets.splice(i, 1);
break;
} else {
// Check if this is fire damage from upgraded pistol
var isFireDamage = bullet.gunType === 'pistol' && hasUpgradePistol;
enemy.takeDamage(bullet.damage, isFireDamage);
// Handle sniper piercing
if (bullet.gunType === 'sniper' && bullet.pierceCount < bullet.maxPierce) {
bullet.pierceCount++;
// Continue through enemy, don't destroy bullet
} else {
bullet.active = false;
bullet.destroy();
playerBullets.splice(i, 1);
}
break;
}
}
}
}
// Update enemy bullets
for (var k = enemyBullets.length - 1; k >= 0; k--) {
var enemyBullet = enemyBullets[k];
enemyBullet.update();
if (!enemyBullet.active) {
enemyBullet.destroy();
enemyBullets.splice(k, 1);
continue;
}
// Check collision with player
if (enemyBullet.intersects(player)) {
if (player.parryWindow > 0) {
// Successful parry - find the enemy that shot this bullet and stun them
var bulletShooter = null;
var shortestDistance = Infinity;
for (var bulletCheck = 0; bulletCheck < currentRoom.enemies.length; bulletCheck++) {
var checkEnemy = currentRoom.enemies[bulletCheck];
if (checkEnemy.active) {
var checkDx = checkEnemy.x - enemyBullet.x;
var checkDy = checkEnemy.y - enemyBullet.y;
var checkDistance = Math.sqrt(checkDx * checkDx + checkDy * checkDy);
if (checkDistance < shortestDistance) {
shortestDistance = checkDistance;
bulletShooter = checkEnemy;
}
}
}
// Stun the enemy that shot the bullet
if (bulletShooter) {
if (bulletShooter.isBoss) {
// Boss takes 5 damage when parried, cannot be stunned or killed by parry
bulletShooter.takeDamage(5);
} else if (player.currentMelee === 'hammer') {
// Hammer kills the enemy instead of stunning
bulletShooter.takeDamage(bulletShooter.health);
} else if (player.currentMelee === 'sword') {
// Sword stuns enemy for 5 seconds (works on both regular and blue enemies)
bulletShooter.stun(300); // Stun for 5 seconds (300 ticks at 60fps)
} else {
// Other weapons stun the enemy for 2 seconds
bulletShooter.stun(120); // Stun for 2 seconds (120 ticks at 60fps)
}
}
var meleeStat = player.meleeStats[player.currentMelee];
// Give sword users 5 seconds of invincibility frames
if (player.currentMelee === 'sword') {
player.invincibilityFrames = 300; // 5 seconds at 60fps
// Visual feedback for invincibility
tween(player, {
alpha: 0.5
}, {
duration: 5000,
onFinish: function onFinish() {
player.alpha = 1;
}
});
}
if (meleeStat.effect === 'reflect') {
// Reflect bullet back
var reflectedBullet = new Bullet(true, 'pistol');
reflectedBullet.x = enemyBullet.x;
reflectedBullet.y = enemyBullet.y;
reflectedBullet.startX = enemyBullet.x;
reflectedBullet.startY = enemyBullet.y;
reflectedBullet.direction = enemyBullet.direction + Math.PI;
reflectedBullet.speed = 15;
playerBullets.push(reflectedBullet);
game.addChild(reflectedBullet);
} else if (meleeStat.effect === 'shockwave') {
// Damage nearby enemies
for (var l = 0; l < currentRoom.enemies.length; l++) {
var nearbyEnemy = currentRoom.enemies[l];
var dx = nearbyEnemy.x - player.x;
var dy = nearbyEnemy.y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150 && nearbyEnemy.active) {
if (nearbyEnemy.isBoss) {
// Boss takes 5 damage from shockwave, cannot be killed
nearbyEnemy.takeDamage(5);
} else if (player.currentMelee === 'hammer') {
// Hammer kills nearby enemies (works on both regular and blue enemies)
nearbyEnemy.takeDamage(nearbyEnemy.health);
} else {
// Other weapons damage nearby enemies
nearbyEnemy.takeDamage(2);
}
}
}
}
LK.effects.flashObject(player, meleeStat.color, 300);
LK.setScore(LK.getScore() + 3);
} else {
// Player takes damage (unless they have invincibility frames)
if (!player.invincibilityFrames || player.invincibilityFrames <= 0) {
player.takeDamage();
}
}
enemyBullet.active = false;
enemyBullet.destroy();
enemyBullets.splice(k, 1);
}
}
// Update enemies
for (var m = 0; m < currentRoom.enemies.length; m++) {
var enemy = currentRoom.enemies[m];
if (enemy.active) {
enemy.update();
}
}
// Update pickups
for (var n = pickups.length - 1; n >= 0; n--) {
var pickup = pickups[n];
pickup.update();
if (!pickup.active) {
pickups.splice(n, 1);
}
}
// Update shotgun ammo pickups
for (var o = shotgunAmmoPickups.length - 1; o >= 0; o--) {
var ammoPickup = shotgunAmmoPickups[o];
ammoPickup.update();
if (!ammoPickup.active) {
shotgunAmmoPickups.splice(o, 1);
}
}
// Check room completion
if (currentRoom.checkCleared()) {
// Check if player reaches door
if (currentRoom.door && player.intersects(currentRoom.door)) {
generateNewRoom();
}
}
// Reset trash icon when no weapon is selected
if (!selectedWeapon && trashIcon.tint !== 0xc0392b) {
tween(trashIcon, {
tint: 0xc0392b
}, {
duration: 100
});
}
// Handle aimed shooting for rifle while held
if (isRightClickHeld && player && player.currentGun === 'rifle') {
fullAutoTimer++;
if (fullAutoTimer >= fullAutoInterval) {
// Use current mouse/touch position for aiming
var currentX = dragStartX; // This gets updated in move handler
var currentY = dragStartY; // This gets updated in move handler
player.shoot(currentX, currentY);
fullAutoTimer = 0;
}
}
// Update slowdown system
if (slowdownCooldown > 0) {
slowdownCooldown--;
}
if (slowdownDuration > 0) {
slowdownDuration--;
if (slowdownDuration <= 0) {
slowdownActive = false;
// Reset button color when slowdown ends
tween(slowdownButton, {
tint: 0x3498db
}, {
duration: 200
});
}
}
// Update slowdown button display
if (slowdownCooldown > 0) {
var secondsLeft = Math.ceil(slowdownCooldown / 60);
slowdownCooldownText.setText(secondsLeft + 's');
slowdownButton.tint = 0xe74c3c; // Red when on cooldown
} else {
slowdownCooldownText.setText('');
if (!slowdownActive) {
slowdownButton.tint = 0x3498db; // Blue when ready
}
}
// Update UI periodically
if (LK.ticks % 30 === 0) {
updateUI();
}
};
function showShop() {
isShopOpen = true;
// Create overlay background
var overlay = game.attachAsset('wall', {
width: 2048,
height: 2732,
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
alpha: 0.9,
tint: 0x2c3e50
});
// Title text
var titleText = new Text2('SHOPKEEPER', {
size: 60,
fill: '#f39c12'
});
titleText.anchor.set(0.5, 0.5);
titleText.x = 1024;
titleText.y = 600;
game.addChild(titleText);
// Available shop items
var shopItems = [];
// Always include upgrade pistol if not owned
if (!hasUpgradePistol) {
shopItems.push({
id: 'upgradePistol',
name: 'Upgraded Pistol',
price: 15,
description: 'Better damage\nand fire rate'
});
}
// Add random items
var randomItems = [{
id: 'health',
name: '+5 Max HP',
price: 10,
description: 'Increase health'
}, {
id: 'damage',
name: '+3 Damage',
price: 12,
description: 'More damage'
}, {
id: 'rpg',
name: 'RPG Launcher',
price: 30,
description: '40 damage explosion\n30 on bosses'
}];
// Add 2-3 random items
var itemsToAdd = Math.min(3 - shopItems.length, randomItems.length);
for (var i = 0; i < itemsToAdd; i++) {
if (randomItems.length > 0) {
var randomIndex = Math.floor(Math.random() * randomItems.length);
shopItems.push(randomItems[randomIndex]);
randomItems.splice(randomIndex, 1);
}
}
// Create shop item buttons
var choiceY = 1366;
var choiceSpacing = 300;
var choices = [];
var choiceTexts = [];
var choiceDescTexts = [];
for (var j = 0; j < shopItems.length; j++) {
var item = shopItems[j];
var xPos = 1024 + (j - Math.floor(shopItems.length / 2)) * choiceSpacing;
var choice = game.attachAsset('wall', {
width: 350,
height: 200,
anchorX: 0.5,
anchorY: 0.5,
x: xPos,
y: choiceY,
tint: playerCash >= item.price ? 0x27ae60 : 0xe74c3c
});
var nameText = new Text2(item.name, {
size: 36,
fill: '#ffffff'
});
nameText.anchor.set(0.5, 0.5);
nameText.x = xPos;
nameText.y = choiceY - 40;
game.addChild(nameText);
var priceText = new Text2('$' + item.price, {
size: 44,
fill: '#f39c12'
});
priceText.anchor.set(0.5, 0.5);
priceText.x = xPos;
priceText.y = choiceY + 10;
game.addChild(priceText);
var descText = new Text2(item.description, {
size: 28,
fill: '#ecf0f1'
});
descText.anchor.set(0.5, 0.5);
descText.x = xPos;
descText.y = choiceY + 60;
game.addChild(descText);
choice.shopItem = item;
choice.down = function () {
buyItem(this.shopItem);
};
choices.push(choice);
choiceTexts.push(nameText);
choiceTexts.push(priceText);
choiceDescTexts.push(descText);
}
// Continue button
var continueButton = game.attachAsset('wall', {
width: 200,
height: 80,
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 2000,
tint: 0x3498db
});
var continueText = new Text2('CONTINUE', {
size: 36,
fill: '#ffffff'
});
continueText.anchor.set(0.5, 0.5);
continueText.x = 1024;
continueText.y = 2000;
game.addChild(continueText);
continueButton.down = function () {
closeShop();
};
// Store references for cleanup
shopChoices = {
overlay: overlay,
titleText: titleText,
choices: choices,
choiceTexts: choiceTexts,
choiceDescTexts: choiceDescTexts,
continueButton: continueButton,
continueText: continueText
};
}
function buyItem(item) {
if (playerCash < item.price) return;
playerCash -= item.price;
storage.playerCash = playerCash;
switch (item.id) {
case 'upgradePistol':
hasUpgradePistol = true;
storage.hasUpgradePistol = true;
// Upgrade pistol stats
player.gunStats.pistol.cooldown = 8;
player.gunStats.pistol.damage = 8;
break;
case 'health':
player.maxHealth += 5;
player.health += 5;
break;
case 'damage':
damageBonus += 3;
break;
case 'rpg':
player.rpgAmmo = 1;
if (!addWeaponToHotbar('rpg', 'gun')) {
// If hotbar full, replace oldest gun weapon with RPG
for (var slot = 0; slot < hotbarSlots.length; slot++) {
if (hotbarSlots[slot].weaponType === 'gun' && hotbarSlots[slot].weapon !== 'rpg') {
// Remove old weapon graphics
if (hotbarSlots[slot].weaponGraphics) {
hotbarSlots[slot].weaponGraphics.destroy();
}
// Add RPG to this slot
hotbarSlots[slot].weapon = 'rpg';
hotbarSlots[slot].weaponType = 'gun';
hotbarSlots[slot].weaponGraphics = hotbarSlots[slot].attachAsset('pistol', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
tint: 0xe74c3c
});
break;
}
}
}
break;
}
updateUI();
closeShop();
}
function closeShop() {
if (!shopChoices) return;
// Clean up shop UI
shopChoices.overlay.destroy();
shopChoices.titleText.destroy();
shopChoices.continueButton.destroy();
shopChoices.continueText.destroy();
for (var i = 0; i < shopChoices.choices.length; i++) {
shopChoices.choices[i].destroy();
}
for (var j = 0; j < shopChoices.choiceTexts.length; j++) {
shopChoices.choiceTexts[j].destroy();
}
for (var k = 0; k < shopChoices.choiceDescTexts.length; k++) {
shopChoices.choiceDescTexts[k].destroy();
}
shopChoices = null;
isShopOpen = false;
// Continue to next room
continueToNextRoom();
}
function continueToNextRoom() {
// Clear current room
if (currentRoom) {
currentRoom.destroy();
}
// Clear boss reference and hide boss health bar
currentBoss = null;
if (bossHealthBarBg) {
bossHealthBarBg.alpha = 0;
bossHealthBar.alpha = 0;
bossNameText.alpha = 0;
}
// Clear bullets and pickups
for (var i = 0; i < playerBullets.length; i++) {
playerBullets[i].destroy();
}
for (var j = 0; j < enemyBullets.length; j++) {
enemyBullets[j].destroy();
}
for (var k = 0; k < pickups.length; k++) {
pickups[k].destroy();
}
for (var l = 0; l < shotgunAmmoPickups.length; l++) {
shotgunAmmoPickups[l].destroy();
}
playerBullets = [];
enemyBullets = [];
pickups = [];
shotgunAmmoPickups = [];
// Create new room
roomLevel++;
currentRoom = new Room(roomLevel);
currentRoom.generateRoom();
game.addChild(currentRoom);
// Reset player position
player.x = 1024;
player.y = 2400;
game.addChild(player);
// Regenerate HP when entering new room
player.heal(1);
updateUI();
}