User prompt
Gungeon Parry: Blade & Bullet
User prompt
a rougelike topdown shooter like enter the guneon and add a parry system where the player can find swords with fast parries or find hammers with slower parries but stronger the swords and hammers can have different effects that let them all be different the player cannot attack with these swords and hammers they must use the guns they find just like enter the guneon or like another game called ultrakill
User prompt
a rougelike topdown shooter like enter the guneon and add a parry system where the player can find swords with fast parries or find hammers with slower parries but stronger the swords and hammers can have different effects that let them all be different
User prompt
add a parry system where the player can find swords with fast parries or find hammers with slower parries but stronger the swords and hammers can have different effects that let them all be different
Initial prompt
a rougelike topdown shooter like enter the guneon
/**** * 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();
}