/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('resource', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Remove bomb tint for performance
self.speed = 6;
self.type = 'bomb';
self.direction = {
x: 0,
y: 0
};
self.explosionRadius = 120;
self.update = function () {
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Remove bomb if it goes off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i] === self) {
bombs.splice(i, 1);
break;
}
}
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('bossTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
// Remove boss tint for performance
self.health = 20;
self.maxHealth = 20;
self.speed = 0; // Boss doesn't move
self.damage = 30;
self.type = 'boss';
self.spawnTimer = 0;
self.attackTimer = 0;
self.attackWarning = null;
self.attackCooldown = 300; // 5 seconds between attacks
self.update = function () {
// Boss stays at center
self.x = 1024;
self.y = 1366;
// Attack system
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Special lightning attack when health is low
if (self.health <= 5) {
// Create multiple warnings
var warningPositions = [];
for (var w = 0; w < 5; w++) {
var warning = game.addChild(new Text2('!', {
size: 150,
fill: 0xFFFF00
}));
warning.anchor.set(0.5, 0.5);
warning.x = player.x + (Math.random() - 0.5) * 300;
warning.y = player.y + (Math.random() - 0.5) * 300;
warning.alpha = 1;
warningPositions.push(warning);
// Flash warning
tween(warning, {
alpha: 0.3,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeInOut
});
}
// Lightning attack after 2 seconds
LK.setTimeout(function () {
for (var i = 0; i < warningPositions.length; i++) {
var warning = warningPositions[i];
if (warning) {
// Create lightning effect
var lightning = game.addChild(LK.getAsset('lightningTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
}));
lightning.x = warning.x;
lightning.y = warning.y;
lightning.tint = 0xFFFF00;
// Check if player is in lightning area
var dx = player.x - warning.x;
var dy = player.y - warning.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
playerHealth -= 40;
healthText.setText('Health: ' + playerHealth);
LK.getSound('damage').play();
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Lightning effect lasts 3 seconds
LK.setTimeout(function () {
if (lightning) {
lightning.destroy();
}
}, 3000);
warning.destroy();
}
}
}, 2000);
} else {
// Normal attack
if (self.attackWarning) {
self.attackWarning.destroy();
}
self.attackWarning = game.addChild(LK.getAsset('bossAttackTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 3.0
}));
self.attackWarning.x = player.x;
self.attackWarning.y = player.y;
self.attackWarning.alpha = 1;
self.attackWarning.tint = 0xFF0000;
// Flash warning
tween(self.attackWarning, {
alpha: 0.3,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeInOut
});
// Attack after 2 seconds
LK.setTimeout(function () {
if (self.attackWarning) {
// Check if player is still in attack area
var dx = player.x - self.attackWarning.x;
var dy = player.y - self.attackWarning.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
// Player is in attack area, take damage
// Increase boss damage by 0.5% per night
var damageMultiplier = 1 + currentNight * 0.005;
var actualDamage = Math.floor(self.damage * damageMultiplier);
playerHealth -= actualDamage;
healthText.setText('Health: ' + playerHealth);
LK.getSound('damage').play();
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Create explosion effect
LK.effects.flashObject(self.attackWarning, 0xFF6600, 500);
self.attackWarning.destroy();
self.attackWarning = null;
}
}, 2000);
}
}
// Spawn zombies every 10 seconds (600 frames at 60fps)
self.spawnTimer++;
if (self.spawnTimer >= 600) {
self.spawnTimer = 0;
// Spawn a zombie near the boss
var zombie = new CorruptedScout();
zombie.x = self.x + (Math.random() - 0.5) * 200;
zombie.y = self.y + (Math.random() - 0.5) * 200;
zombie.health = 15; // Weaker zombies
monsters.push(zombie);
game.addChild(zombie);
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bulletTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Remove bullet tint for performance
self.speed = storage.bulletSpeedUpgradePurchased ? 12 : 8; // Apply bullet speed upgrade
self.damage = 1;
self.type = 'bullet';
self.direction = {
x: 0,
y: 0
};
self.update = function () {
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var CorruptedScout = Container.expand(function () {
var self = Container.call(this);
var scoutGraphics = self.attachAsset('corruptedScout', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.maxHealth = 2;
self.speed = 2;
self.damage = 10;
self.type = 'scout';
self.isDead = false;
self.deadTimer = 0;
self.update = function () {
if (self.isDead) {
self.deadTimer++;
if (self.deadTimer >= 120) {
// 2 seconds at 60fps
self.destroy();
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i] === self) {
monsters.splice(i, 1);
break;
}
}
}
return;
}
// Always pursue the player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Calculate intended next position
var nextX = self.x + dx / distance * self.speed;
var nextY = self.y + dy / distance * self.speed;
var blocked = false;
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (wall.health > 0) {
// Simulate monster at next position
var tempDist = Math.sqrt((nextX - wall.x) * (nextX - wall.x) + (nextY - wall.y) * (nextY - wall.y));
if (tempDist < 50) {
blocked = true;
break;
}
}
}
if (!blocked) {
self.x = nextX;
self.y = nextY;
// Minimal animation for performance
if (LK.ticks % 300 === 0) {
self.scaleX = self.scaleX === 1.0 ? 1.02 : 1.0;
}
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
if (self.health <= 0) {
self.isDead = true;
self.alpha = 0.5;
scoutGraphics.rotation = Math.PI / 2; // Rotate to show fallen
return true;
}
return false;
};
return self;
});
var CorruptedSwarm = Container.expand(function () {
var self = Container.call(this);
var swarmGraphics = self.attachAsset('corruptedSwarm', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.maxHealth = 1;
self.speed = 3;
self.damage = 5;
self.type = 'swarm';
self.isDead = false;
self.deadTimer = 0;
self.update = function () {
if (self.isDead) {
self.deadTimer++;
if (self.deadTimer >= 120) {
// 2 seconds at 60fps
self.destroy();
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i] === self) {
monsters.splice(i, 1);
break;
}
}
}
return;
}
// Always pursue the player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Calculate intended next position
var nextX = self.x + dx / distance * self.speed;
var nextY = self.y + dy / distance * self.speed;
var blocked = false;
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (wall.health > 0) {
var tempDist = Math.sqrt((nextX - wall.x) * (nextX - wall.x) + (nextY - wall.y) * (nextY - wall.y));
if (tempDist < 50) {
blocked = true;
break;
}
}
}
if (!blocked) {
self.x = nextX;
self.y = nextY;
// Minimal animation for performance
if (LK.ticks % 450 === 0) {
self.scaleX = self.scaleX === 1.0 ? 1.05 : 1.0;
}
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
if (self.health <= 0) {
self.isDead = true;
self.alpha = 0.5;
swarmGraphics.rotation = Math.PI / 2; // Rotate to show fallen
return true;
}
return false;
};
return self;
});
var CorruptedTank = Container.expand(function () {
var self = Container.call(this);
var tankGraphics = self.attachAsset('corruptedTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.speed = 1;
self.damage = 25;
self.type = 'tank';
self.isDead = false;
self.deadTimer = 0;
self.update = function () {
if (self.isDead) {
self.deadTimer++;
if (self.deadTimer >= 120) {
// 2 seconds at 60fps
self.destroy();
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i] === self) {
monsters.splice(i, 1);
break;
}
}
}
return;
}
// Always pursue the player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Calculate intended next position
var nextX = self.x + dx / distance * self.speed;
var nextY = self.y + dy / distance * self.speed;
var blocked = false;
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (wall.health > 0) {
var tempDist = Math.sqrt((nextX - wall.x) * (nextX - wall.x) + (nextY - wall.y) * (nextY - wall.y));
if (tempDist < 50) {
blocked = true;
break;
}
}
}
if (!blocked) {
self.x = nextX;
self.y = nextY;
// Minimal animation for performance
if (LK.ticks % 600 === 0) {
self.scaleX = self.scaleX === 1.0 ? 1.03 : 1.0;
}
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
if (self.health <= 0) {
self.isDead = true;
self.alpha = 0.5;
tankGraphics.rotation = Math.PI / 2; // Rotate to show fallen
return true;
}
return false;
};
return self;
});
var DualPistol = Container.expand(function () {
var self = Container.call(this);
var pistolGraphics = self.attachAsset('pistolTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Remove dual pistol tint for performance
self.type = 'dualPistol';
self.bullets = 6;
self.maxBullets = 6;
self.isReloading = false;
self.reloadTimer = 0;
self.update = function () {
self.rotation += 0.05;
if (self.isReloading) {
self.reloadTimer--;
if (self.reloadTimer <= 0) {
self.isReloading = false;
self.bullets = self.maxBullets;
}
}
};
self.canShoot = function () {
return self.bullets > 0 && !self.isReloading;
};
self.shoot = function () {
if (self.canShoot()) {
self.bullets--;
if (self.bullets <= 0) {
self.isReloading = true;
self.reloadTimer = 180; // 3 seconds
}
return true;
}
return false;
};
return self;
});
var RandomBoss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('bossTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 3.0
});
// Remove random boss tint for performance
self.health = 30;
self.maxHealth = 30;
self.speed = 1;
self.damage = 40;
self.type = 'randomBoss';
self.attackTimer = 0;
self.attackCooldown = 240; // 4 seconds between attacks
self.update = function () {
// Move towards player slowly
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Attack system
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Spawn corrupted scouts around the boss
for (var i = 0; i < 3; i++) {
var scout = new CorruptedScout();
scout.x = self.x + (Math.random() - 0.5) * 150;
scout.y = self.y + (Math.random() - 0.5) * 150;
scout.health = 3; // Stronger scouts
monsters.push(scout);
game.addChild(scout);
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var Resource = Container.expand(function () {
var self = Container.call(this);
var resourceGraphics = self.attachAsset('resource', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
self.value = 10;
self.type = 'resource';
self.update = function () {
self.rotation += 0.05;
};
self.down = function (x, y, obj) {
// Allow direct tapping on resources to collect them
var resourceValue = storage.resourceValueUpgradePurchased ? Math.floor(self.value * 1.5) : self.value;
playerResources += resourceValue;
resourceText.setText('Resources: ' + playerResources);
self.destroy();
for (var i = resources.length - 1; i >= 0; i--) {
if (resources[i] === self) {
resources.splice(i, 1);
break;
}
}
LK.getSound('collect').play();
};
return self;
});
var TraderNPC = Container.expand(function () {
var self = Container.call(this);
var traderGraphics = self.attachAsset('traderNPC', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
// Remove trader tint for performance
self.type = 'trader';
self.update = function () {
// Trader stays stationary - no movement
};
return self;
});
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapGraphics = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 30;
self.cooldown = 0;
self.type = 'trap';
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
}
};
self.activate = function () {
if (self.cooldown <= 0) {
self.cooldown = 60;
LK.effects.flashObject(self, 0xff6b35, 300);
return self.damage;
}
return 0;
};
return self;
});
var TreeBoss = Container.expand(function () {
var self = Container.call(this);
var treeBossGraphics = self.attachAsset('treeBossTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Tree boss stays at center and shoots projectiles
self.health = 40;
self.maxHealth = 40;
self.speed = 0; // Tree doesn't move
self.damage = 5;
self.type = 'treeBoss';
self.attackTimer = 0;
self.attackCooldown = 180; // 3 seconds between attacks
self.rapidFireActive = false;
self.rapidFireTimer = 0;
self.rapidFireCooldownTimer = 0;
self.lastRapidFireUsed = false;
self.update = function () {
// Tree boss stays at center
self.x = 1024;
self.y = 1366;
// Handle rapid fire ability when at half health
if (self.health <= self.maxHealth / 2 && !self.lastRapidFireUsed) {
if (!self.rapidFireActive && self.rapidFireCooldownTimer <= 0) {
self.rapidFireActive = true;
self.rapidFireTimer = 300; // 5 seconds of rapid fire
self.rapidFireCooldownTimer = 180; // 3 second cooldown after rapid fire ends
}
}
// Update timers
self.attackTimer++;
if (self.rapidFireCooldownTimer > 0) {
self.rapidFireCooldownTimer--;
}
// Rapid fire mode
if (self.rapidFireActive) {
self.rapidFireTimer--;
// Shoot every 10 frames (6 times per second)
if (LK.ticks % 10 === 0) {
self.shootBulletAtPlayer();
}
if (self.rapidFireTimer <= 0) {
self.rapidFireActive = false;
self.lastRapidFireUsed = true;
self.rapidFireCooldownTimer = 180; // 3 second cooldown
}
} else {
// Normal attack pattern - shoot 3 bullets every 3 seconds
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Shoot 3 bullets at player
for (var i = 0; i < 3; i++) {
LK.setTimeout(function () {
self.shootBulletAtPlayer();
}, i * 100); // 100ms delay between each bullet
}
}
}
};
self.shootBulletAtPlayer = function () {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.damage = self.damage;
// Calculate direction to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Normalize direction
bullet.direction.x = dx / distance;
bullet.direction.y = dy / distance;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
var baseHealth = storage.wallHealthUpgradePurchased ? 200 : 100; // Apply wall health upgrade
self.health = baseHealth;
self.maxHealth = baseHealth;
self.type = 'wall';
// Track monster hits
self.hitCount = 0;
self.lastMonsterHitTick = 0; // LK.ticks of last monster hit
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
wallGraphics.alpha = 0.3;
} else {
wallGraphics.alpha = 0.5 + self.health / self.maxHealth * 0.5;
}
};
self.registerMonsterHit = function () {
self.hitCount++;
self.lastMonsterHitTick = LK.ticks;
if (self.hitCount >= 3) {
self.health = 0;
wallGraphics.alpha = 0.3;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Initialize game safely with error handling
try {
// Frames left for placement cooldown (60 = 1s)
// Update UI visibility based on game state
var _updateUIVisibility = function _updateUIVisibility() {
if (gameState === 'mainMenu') {
mainMenuContainer.alpha = 1;
characterMenuContainer.alpha = 0;
gameStateText.alpha = 0;
timerText.alpha = 0;
healthText.alpha = 0;
resourceText.alpha = 0;
nightText.alpha = 0;
joystickBase.alpha = 0;
joystickKnob.alpha = 0;
wallButton.alpha = 0;
trapButton.alpha = 0;
collectButton.alpha = 0;
wallButtonText.alpha = 0;
trapButtonText.alpha = 0;
collectButtonText.alpha = 0;
wallIcon.alpha = 0;
trapIcon.alpha = 0;
defenseText.alpha = 0;
healButton.alpha = 0;
healButtonText.alpha = 0;
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
dayProgressText.alpha = 0;
} else if (gameState === 'characterSelect') {
mainMenuContainer.alpha = 0;
characterMenuContainer.alpha = 1;
gameStateText.alpha = 0;
timerText.alpha = 0;
healthText.alpha = 0;
resourceText.alpha = 0;
nightText.alpha = 0;
joystickBase.alpha = 0;
joystickKnob.alpha = 0;
wallButton.alpha = 0;
trapButton.alpha = 0;
collectButton.alpha = 0;
wallButtonText.alpha = 0;
trapButtonText.alpha = 0;
collectButtonText.alpha = 0;
wallIcon.alpha = 0;
trapIcon.alpha = 0;
defenseText.alpha = 0;
healButton.alpha = 0;
healButtonText.alpha = 0;
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
} else {
mainMenuContainer.alpha = 0;
characterMenuContainer.alpha = 0;
gameStateText.alpha = 1;
timerText.alpha = 1;
healthText.alpha = 1;
resourceText.alpha = 1;
nightText.alpha = 1;
joystickBase.alpha = 0.5;
joystickKnob.alpha = 1;
wallButton.alpha = 1;
trapButton.alpha = 1;
collectButton.alpha = 1;
wallButtonText.alpha = 1;
trapButtonText.alpha = 1;
collectButtonText.alpha = 1;
wallIcon.alpha = 1;
trapIcon.alpha = 1;
defenseText.alpha = gameState === 'day' ? 1 : 0;
dayProgressText.alpha = 1;
}
;
};
// Add backgrounds
var dayBackground = game.addChild(LK.getAsset('dayBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
dayBackground.alpha = 1;
var nightBackground = game.addChild(LK.getAsset('nightBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
nightBackground.alpha = 0;
var gameState = 'mainMenu'; // Start with main menu
var currentNight = 0;
var dayTimer = 1800; // 30 seconds at 60fps
var nightTimer = 0;
var playerHealth = 100;
var playerResources = 50;
var selectedDefense = 'wall';
var walls = [];
var traps = [];
var resources = [];
var monsters = [];
var boss = null;
var randomBoss = null;
var treeBoss = null;
var bullets = [];
var bombs = [];
var playerPistol = true; // Player has pistol
var pistolBullets = 5; // Bullets left before reload
var pistolReloading = false; // Is pistol reloading
var pistolReloadTimer = 0; // Frames left for reload (180 = 3s)
var hasDualPistol = storage.hasDualPistol || false; // Player has dual pistol (persistent)
var hasDefeatedFirstBoss = storage.hasDefeatedFirstBoss || false; // Track if player defeated first boss
var dualPistolWeapon = null; // Dual pistol weapon object
var dualPistolPickup = null; // Dual pistol pickup object
var weaponPickupText = null; // Weapon pickup message
var selectedCharacter = storage.selectedCharacter || 0; // 0=damage, 1=heal, 2=discount
var characterStats = {
damageMultiplier: 1.0,
healPerNight: selectedCharacter === 1 ? 10 : 0,
discountMultiplier: 1.0
};
// Trader system variables
var currentDay = 1; // Always start from day 1
// Don't load from storage to prevent day jumping bugs
storage.currentDay = 1; // Reset storage to day 1
var traderActive = false;
var traderContainer = null;
var weaponUpgradePurchased = storage.weaponUpgradePurchased || false;
var costReductionPurchased = storage.costReductionPurchased || false;
var traderNPC = null;
var isTrading = false;
var playerCanMove = true;
var traderUpgrades = [{
name: "Weapon Damage +10%",
cost: 50,
type: "weapon",
purchased: storage.weaponUpgradePurchased || false
}, {
name: "Build Cost -10%",
cost: 30,
type: "cost",
purchased: storage.costReductionPurchased || false
}, {
name: "Health +20",
cost: 40,
type: "health",
purchased: storage.healthUpgradePurchased || false
}, {
name: "Speed +20%",
cost: 35,
type: "speed",
purchased: storage.speedUpgradePurchased || false
}, {
name: "Reload Speed +30%",
cost: 45,
type: "reload",
purchased: storage.reloadUpgradePurchased || false
}, {
name: "Max Health +30",
cost: 70,
type: "maxHealth",
purchased: storage.maxHealthUpgradePurchased || false
}, {
name: "Damage +25%",
cost: 80,
type: "megaDamage",
purchased: storage.megaDamageUpgradePurchased || false
}, {
name: "Bullet Speed +50%",
cost: 60,
type: "bulletSpeed",
purchased: storage.bulletSpeedUpgradePurchased || false
}, {
name: "Wall Health +100%",
cost: 55,
type: "wallHealth",
purchased: storage.wallHealthUpgradePurchased || false
}, {
name: "Resource Value +50%",
cost: 65,
type: "resourceValue",
purchased: storage.resourceValueUpgradePurchased || false
}];
// Special ability states
var berserkMode = false;
var berserkUsed = false;
var medicReviveUsed = false;
var engineerBoostActive = false;
var engineerBoostUsed = false;
var slowEnemiesActive = false;
var slowEnemiesTimer = 0;
// Heal button for medic character
var healButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -250,
y: 0,
scaleX: 3,
scaleY: 3
});
// Remove heal button tint for performance
healButton.alpha = 0;
var healButtonText = new Text2('HEAL', {
size: 60,
fill: 0xFFFFFF
});
healButtonText.anchor.set(0.5, 0.5);
healButtonText.x = -250;
healButtonText.y = 0;
healButtonText.alpha = 0;
LK.gui.center.addChild(healButton);
LK.gui.center.addChild(healButtonText);
var pistolReloadText = new Text2('', {
size: 80,
fill: 0xFF0000
});
pistolReloadText.anchor.set(0.5, 0.5);
pistolReloadText.x = 0;
pistolReloadText.y = 200;
pistolReloadText.alpha = 0;
LK.gui.center.addChild(pistolReloadText);
// Main Menu UI
var mainMenuContainer = new Container();
// Add epic background to main menu
var epicMenuBg = LK.getAsset('epicBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 1.0,
scaleY: 1.0
});
epicMenuBg.tint = 0x8B008B; // Purple tint for epic background
mainMenuContainer.addChild(epicMenuBg);
// Add textured title
var mainMenuTitle = LK.getAsset('titleTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -300,
scaleX: 2.0,
scaleY: 2.0
});
mainMenuTitle.tint = 0xFFD700; // Gold tint for epic look
mainMenuContainer.addChild(mainMenuTitle);
var playButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scaleX: 4,
scaleY: 2
});
var playButtonText = new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 0;
playButtonText.y = -100;
mainMenuContainer.addChild(playButton);
mainMenuContainer.addChild(playButtonText);
// Add instructions text
var instructionsText = new Text2('Press (C) to collect resources\nand survive the nights!', {
size: 60,
fill: 0xFFD700
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 200;
mainMenuContainer.addChild(instructionsText);
var charactersButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 50,
scaleX: 4,
scaleY: 2
});
var charactersButtonText = new Text2('CHARACTERS', {
size: 80,
fill: 0xFFFFFF
});
charactersButtonText.anchor.set(0.5, 0.5);
charactersButtonText.x = 0;
charactersButtonText.y = 50;
mainMenuContainer.addChild(charactersButton);
mainMenuContainer.addChild(charactersButtonText);
LK.gui.center.addChild(mainMenuContainer);
// Character Selection UI
var characterMenuContainer = new Container();
var characterMenuTitle = new Text2('SELECT CHARACTER', {
size: 100,
fill: 0xFFFFFF
});
characterMenuTitle.anchor.set(0.5, 0.5);
characterMenuTitle.x = 0;
characterMenuTitle.y = -400;
characterMenuContainer.addChild(characterMenuTitle);
// Character 1 - Damage
var char1Button = LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -100,
scaleX: 2,
scaleY: 2
});
var char1Text = new Text2('WARRIOR\n+30% Damage', {
size: 50,
fill: 0xFFFFFF
});
char1Text.anchor.set(0.5, 0.5);
char1Text.x = -300;
char1Text.y = 50;
characterMenuContainer.addChild(char1Button);
characterMenuContainer.addChild(char1Text);
// Character 2 - Heal
var char2Button = LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scaleX: 2,
scaleY: 2
});
var char2Text = new Text2('MEDIC\n+10 Health\nper Night', {
size: 50,
fill: 0xFFFFFF
});
char2Text.anchor.set(0.5, 0.5);
char2Text.x = 0;
char2Text.y = 50;
characterMenuContainer.addChild(char2Button);
characterMenuContainer.addChild(char2Text);
// Character 3 - Discount
var char3Button = LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 300,
y: -100,
scaleX: 2,
scaleY: 2
});
var char3Text = new Text2('ENGINEER\n-20% Build\nCost', {
size: 50,
fill: 0xFFFFFF
});
// Remove character selection visual state tints
char3Text.anchor.set(0.5, 0.5);
char3Text.x = 300;
char3Text.y = 50;
characterMenuContainer.addChild(char3Button);
characterMenuContainer.addChild(char3Text);
var backButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 300,
scaleX: 3,
scaleY: 2
});
var backButtonText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 0;
backButtonText.y = 300;
characterMenuContainer.addChild(backButton);
characterMenuContainer.addChild(backButtonText);
characterMenuContainer.alpha = 0;
LK.gui.center.addChild(characterMenuContainer);
var player = game.addChild(LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Remove character colors - no tint applied
// Button visibility is now handled by the UI buttons
disablePlacement = false;
var gameStateText = new Text2('Day Phase', {
size: 80,
fill: 0xFFFFFF
});
gameStateText.anchor.set(0.5, 0);
LK.gui.top.addChild(gameStateText);
var timerText = new Text2('30s', {
size: 60,
fill: 0xFFFF00
});
timerText.anchor.set(0.5, 0);
timerText.y = 100;
LK.gui.top.addChild(timerText);
var healthText = new Text2('Health: 100', {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = 120;
var resourceText = new Text2('Resources: 50', {
size: 50,
fill: 0xFFD700
});
resourceText.anchor.set(0, 0);
resourceText.y = 60;
LK.gui.topLeft.addChild(resourceText);
resourceText.x = 120;
var nightText = new Text2('Night: 0', {
size: 50,
fill: 0x8B3A8B
});
nightText.anchor.set(1, 0);
LK.gui.topRight.addChild(nightText);
var dayProgressText = new Text2('Day: 1/100', {
size: 50,
fill: 0xFFD700
});
dayProgressText.anchor.set(1, 0);
dayProgressText.y = 60;
LK.gui.topRight.addChild(dayProgressText);
// Boss health bar UI elements
var bossHealthBar = LK.getAsset('healthBarTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 150,
scaleX: 10,
scaleY: 1
});
bossHealthBar.tint = 0x333333;
bossHealthBar.alpha = 0;
LK.gui.top.addChild(bossHealthBar);
var bossHealthBarFill = LK.getAsset('healthBarTexture', {
anchorX: 0.0,
anchorY: 0.5,
x: -300,
y: 150,
scaleX: 10,
scaleY: 1
});
bossHealthBarFill.tint = 0xFF0000;
bossHealthBarFill.alpha = 0;
LK.gui.top.addChild(bossHealthBarFill);
var bossHealthText = new Text2('BOSS', {
size: 50,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0.5);
bossHealthText.x = 0;
bossHealthText.y = 100;
bossHealthText.alpha = 0;
LK.gui.top.addChild(bossHealthText);
// Joystick control elements
var joystickBase = LK.getAsset('joystickTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -300,
scaleX: 2.5,
scaleY: 2.5
});
joystickBase.alpha = 0.5;
LK.gui.bottomLeft.addChild(joystickBase);
var joystickKnob = LK.getAsset('joystickTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -300,
scaleX: 1.5,
scaleY: 1.5
});
joystickKnob.tint = 0xFFFFFF;
LK.gui.bottomLeft.addChild(joystickKnob);
var joystickActive = false;
var joystickCenterX = 200;
var joystickCenterY = -300;
var joystickRadius = 60;
var defenseText = new Text2('Defense: Wall (Cost: 10)', {
size: 40,
fill: 0xFFFFFF
});
defenseText.anchor.set(0.5, 1);
defenseText.y = -120;
defenseText.alpha = 0;
LK.gui.bottom.addChild(defenseText);
// Action buttons on the right side
var wallButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -200,
scaleX: 2.5,
scaleY: 2.5
});
LK.gui.bottomRight.addChild(wallButton);
var wallIcon = LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -200,
scaleX: 1.5,
scaleY: 1.5
});
LK.gui.bottomRight.addChild(wallIcon);
var trapButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -150,
y: -200,
scaleX: 2.5,
scaleY: 2.5
});
LK.gui.bottomRight.addChild(trapButton);
var trapIcon = LK.getAsset('trap', {
anchorX: 0.5,
anchorY: 0.5,
x: -150,
y: -200,
scaleX: 1.5,
scaleY: 1.5
});
LK.gui.bottomRight.addChild(trapIcon);
var collectButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -350,
scaleX: 2.5,
scaleY: 2.5
});
LK.gui.bottomRight.addChild(collectButton);
var wallButtonText = new Text2('W', {
size: 40,
fill: 0xFFFFFF
});
wallButtonText.anchor.set(0.5, 0.5);
wallButtonText.x = -300;
wallButtonText.y = -200;
LK.gui.bottomRight.addChild(wallButtonText);
var trapButtonText = new Text2('T', {
size: 40,
fill: 0xFFFFFF
});
trapButtonText.anchor.set(0.5, 0.5);
trapButtonText.x = -150;
trapButtonText.y = -200;
LK.gui.bottomRight.addChild(trapButtonText);
var collectButtonText = new Text2('C', {
size: 40,
fill: 0xFFFFFF
});
collectButtonText.anchor.set(0.5, 0.5);
collectButtonText.x = -300;
collectButtonText.y = -350;
LK.gui.bottomRight.addChild(collectButtonText);
// Movement control variables
var moveX = 0;
var moveY = 0;
var playerSpeed = 5;
// Pistol graphics
var pistolGraphics = null;
var pistolTimer = 0;
// Game mode state
var gameMode = 'build'; // 'build' or 'collect'
var disablePlacement = false; // Prevents placing objects while a button is pressed
var placementCooldown = 0;
// Initialize game state properly
gameState = 'mainMenu';
playerHealth = 100;
playerResources = 50;
currentNight = 0;
dayTimer = 1800;
nightTimer = 0;
// Ensure all UI elements are properly initialized
mainMenuContainer.alpha = 1;
characterMenuContainer.alpha = 0;
gameStateText.alpha = 0;
timerText.alpha = 0;
healthText.alpha = 0;
resourceText.alpha = 0;
nightText.alpha = 0;
joystickBase.alpha = 0;
joystickKnob.alpha = 0;
wallButton.alpha = 0;
trapButton.alpha = 0;
collectButton.alpha = 0;
wallButtonText.alpha = 0;
trapButtonText.alpha = 0;
collectButtonText.alpha = 0;
wallIcon.alpha = 0;
trapIcon.alpha = 0;
defenseText.alpha = 0;
healButton.alpha = 0;
healButtonText.alpha = 0;
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
// Update UI visibility after all elements are initialized
_updateUIVisibility();
} catch (error) {
console.error('Game initialization error:', error);
// Fallback initialization
gameState = 'mainMenu';
playerHealth = 100;
playerResources = 50;
// Ensure menu is visible even in error case
mainMenuContainer.alpha = 1;
characterMenuContainer.alpha = 0;
}
function createTraderUI() {
traderContainer = new Container();
// Background
var traderBg = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 10,
scaleY: 8
});
traderBg.tint = 0x333333;
traderContainer.addChild(traderBg);
// Title
var traderTitle = new Text2('TRADER - BUFFS', {
size: 80,
fill: 0xFFD700
});
traderTitle.anchor.set(0.5, 0.5);
traderTitle.x = 0;
traderTitle.y = -250;
traderContainer.addChild(traderTitle);
// Show player resources
var resourcesText = new Text2('Resources: ' + playerResources, {
size: 50,
fill: 0xFFFFFF
});
resourcesText.anchor.set(0.5, 0.5);
resourcesText.x = 0;
resourcesText.y = -180;
traderContainer.addChild(resourcesText);
// Create upgrade buttons - show 4 random available upgrades
var availableUpgrades = traderUpgrades.filter(function (upgrade) {
return !upgrade.purchased;
});
// Shuffle available upgrades for randomness
for (var i = availableUpgrades.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = availableUpgrades[i];
availableUpgrades[i] = availableUpgrades[j];
availableUpgrades[j] = temp;
}
// Show up to 4 random upgrades in a 2x2 grid
var upgradesShown = Math.min(4, availableUpgrades.length);
for (var i = 0; i < upgradesShown; i++) {
var upgrade = availableUpgrades[i];
// Calculate position in 2x2 grid
var row = Math.floor(i / 2);
var col = i % 2;
var xPos = (col - 0.5) * 300; // -150 or 150
var yPos = -80 + row * 120; // -80 or 40
var upgradeButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: xPos,
y: yPos,
scaleX: 4,
scaleY: 2
});
// Color button based on affordability
if (playerResources >= upgrade.cost) {
upgradeButton.tint = 0x00AA00; // Green if affordable
} else {
upgradeButton.tint = 0xAA0000; // Red if too expensive
}
upgradeButton.upgradeData = upgrade;
traderContainer.addChild(upgradeButton);
var upgradeText = new Text2(upgrade.name + '\n' + upgrade.cost + ' Resources', {
size: 30,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeText.x = xPos;
upgradeText.y = yPos;
traderContainer.addChild(upgradeText);
}
// Close button
var closeButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 200,
scaleX: 3,
scaleY: 2
});
var closeText = new Text2('CLOSE', {
size: 60,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeText.x = 0;
closeText.y = 200;
traderContainer.addChild(closeButton);
traderContainer.addChild(closeText);
LK.gui.center.addChild(traderContainer);
}
function closeTrader() {
if (traderContainer) {
traderContainer.destroy();
traderContainer = null;
}
traderActive = false;
isTrading = false;
playerCanMove = true;
}
function purchaseUpgrade(upgrade) {
if (playerResources >= upgrade.cost) {
playerResources -= upgrade.cost;
resourceText.setText('Resources: ' + playerResources);
upgrade.purchased = true;
// Apply upgrade effects and save to storage for persistence
switch (upgrade.type) {
case "weapon":
characterStats.damageMultiplier += 0.1;
weaponUpgradePurchased = true;
storage.weaponUpgradePurchased = true;
break;
case "cost":
characterStats.discountMultiplier *= 0.9;
costReductionPurchased = true;
storage.costReductionPurchased = true;
break;
case "health":
playerHealth = Math.min(100, playerHealth + 20);
healthText.setText('Health: ' + playerHealth);
storage.healthUpgradePurchased = true;
break;
case "speed":
playerSpeed *= 1.2;
storage.speedUpgradePurchased = true;
break;
case "reload":
// This will be applied in reload logic
storage.reloadUpgradePurchased = true;
break;
case "maxHealth":
// Increase max health permanently
playerHealth = Math.min(130, playerHealth + 30);
healthText.setText('Health: ' + playerHealth);
storage.maxHealthUpgradePurchased = true;
break;
case "megaDamage":
characterStats.damageMultiplier += 0.25;
storage.megaDamageUpgradePurchased = true;
break;
case "bulletSpeed":
// This will be applied to bullets when created
storage.bulletSpeedUpgradePurchased = true;
break;
case "wallHealth":
// This will be applied to walls when created
storage.wallHealthUpgradePurchased = true;
break;
case "resourceValue":
// This will be applied to resource collection
storage.resourceValueUpgradePurchased = true;
break;
}
closeTrader();
// Remove trader NPC after purchase
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
traderActive = false;
LK.getSound('collect').play();
} else {
// Flash red if player can't afford
LK.effects.flashScreen(0xFF0000, 300);
}
}
var placementCooldownText = new Text2('', {
size: 80,
fill: 0xFF0000
});
placementCooldownText.anchor.set(0.5, 0.5);
placementCooldownText.x = 0;
placementCooldownText.y = 0;
placementCooldownText.alpha = 0;
LK.gui.center.addChild(placementCooldownText);
// Spawn initial resources - 5 resources per day
var resourcesToSpawn = 5;
for (var i = 0; i < resourcesToSpawn; i++) {
var resource = new Resource();
resource.x = 200 + Math.random() * 1648;
resource.y = 200 + Math.random() * 1200;
resources.push(resource);
game.addChild(resource);
}
// Trader NPC will be spawned only every 5 days
var traderNPC = null;
function spawnMonster(type) {
var monster;
switch (type) {
case 'scout':
monster = new CorruptedScout();
break;
case 'tank':
monster = new CorruptedTank();
break;
case 'swarm':
monster = new CorruptedSwarm();
break;
default:
monster = new CorruptedScout();
}
// Apply night scaling to monster stats
var damageMultiplier = 1 + currentNight * 0.005; // 0.5% more damage per night
var speedMultiplier = 1 + currentNight * 0.005; // 0.5% more speed per night
monster.damage = Math.floor(monster.damage * damageMultiplier);
monster.speed = monster.speed * speedMultiplier;
// Spawn from edges
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
monster.x = Math.random() * 2048;
monster.y = -50;
break;
case 1:
// Right
monster.x = 2098;
monster.y = Math.random() * 2732;
break;
case 2:
// Bottom
monster.x = Math.random() * 2048;
monster.y = 2782;
break;
case 3:
// Left
monster.x = -50;
monster.y = Math.random() * 2732;
break;
}
monsters.push(monster);
game.addChild(monster);
}
function switchToNight() {
gameState = 'night';
currentNight++;
nightTimer = 1800 + (currentNight - 1) * 60; // Base 30s + 1s per night
gameStateText.setText('Night ' + currentNight);
timerText.setText(Math.ceil(nightTimer / 60) + 's');
nightText.setText('Night: ' + currentNight);
// Remove trader NPC during night
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
// Close any open trader UI
if (isTrading) {
closeTrader();
}
// Switch to night background
dayBackground.alpha = 0;
nightBackground.alpha = 1;
LK.playMusic('nightfall');
defenseText.setText('Night Phase - Survive!');
// Show heal button for medic character
if (characterStats.healPerNight > 0 && currentNight > 0) {
healButton.alpha = 1;
healButtonText.alpha = 1;
}
// Spawn initial monsters for the night - start with 6 and increase gradually
var initialMonsters = Math.min(6 + Math.floor(currentNight * 0.5), 8); // Start with 6, cap at 8
for (var i = 0; i < initialMonsters; i++) {
spawnMonster('scout'); // Start with scouts for the initial wave
}
// Spawn boss every 5th night
if (currentNight % 5 === 0 && boss === null) {
boss = new Boss();
// Spawn boss at center
boss.x = 1024;
boss.y = 1366;
game.addChild(boss);
// Show boss health bar
bossHealthBar.alpha = 1;
bossHealthBarFill.alpha = 1;
bossHealthText.alpha = 1;
// Epic boss cinematic - zoom camera to boss
playerCanMove = false; // Disable player movement during cinematic
// Store original game scale for restoration
var originalScaleX = game.scaleX || 1;
var originalScaleY = game.scaleY || 1;
var originalX = game.x || 0;
var originalY = game.y || 0;
// Calculate zoom position to center boss on screen
var targetX = -(boss.x - 1024) * 2; // 2x zoom
var targetY = -(boss.y - 1366) * 2; // 2x zoom
// Zoom in to boss with epic animation
tween(game, {
scaleX: 2.0,
scaleY: 2.0,
x: targetX,
y: targetY
}, {
duration: 1000,
easing: tween.easeInOut
});
// Add epic boss entrance animation
tween(boss, {
scaleX: 3.5,
scaleY: 3.5,
rotation: Math.PI * 2
}, {
duration: 1000,
easing: tween.elasticOut
});
// Flash screen red for dramatic effect
LK.effects.flashScreen(0xFF0000, 1500);
// After 2 seconds, zoom back to normal
LK.setTimeout(function () {
// Restore camera to normal
tween(game, {
scaleX: originalScaleX,
scaleY: originalScaleY,
x: originalX,
y: originalY
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Re-enable player movement
playerCanMove = true;
}
});
// Reset boss scale to normal
tween(boss, {
scaleX: 2.5,
scaleY: 2.5,
rotation: 0
}, {
duration: 800,
easing: tween.easeOut
});
}, 2000);
}
// Spawn Tree Boss on night 15
if (currentNight === 15 && boss === null) {
boss = new TreeBoss();
// Spawn boss at center
boss.x = 1024;
boss.y = 1366;
game.addChild(boss);
// Show boss health bar
bossHealthBar.alpha = 1;
bossHealthBarFill.alpha = 1;
bossHealthText.alpha = 1;
// Epic tree boss cinematic
playerCanMove = false;
// Store original game scale for restoration
var originalScaleX = game.scaleX || 1;
var originalScaleY = game.scaleY || 1;
var originalX = game.x || 0;
var originalY = game.y || 0;
// Calculate zoom position to center boss on screen
var targetX = -(boss.x - 1024) * 2;
var targetY = -(boss.y - 1366) * 2;
// Zoom in to tree boss with epic animation
tween(game, {
scaleX: 2.0,
scaleY: 2.0,
x: targetX,
y: targetY
}, {
duration: 1000,
easing: tween.easeInOut
});
// Add epic tree boss entrance animation
tween(boss, {
scaleX: 2.0,
scaleY: 2.0,
rotation: Math.PI * 2
}, {
duration: 1000,
easing: tween.elasticOut
});
// Flash screen green for tree boss
LK.effects.flashScreen(0x00FF00, 1500);
// After 2 seconds, zoom back to normal
LK.setTimeout(function () {
// Restore camera to normal
tween(game, {
scaleX: originalScaleX,
scaleY: originalScaleY,
x: originalX,
y: originalY
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Re-enable player movement
playerCanMove = true;
}
});
// Reset tree boss scale to normal
tween(boss, {
scaleX: 1.5,
scaleY: 1.5,
rotation: 0
}, {
duration: 800,
easing: tween.easeOut
});
}, 2000);
}
}
function switchToDay() {
gameState = 'day';
currentDay++;
// Ensure currentDay doesn't exceed 100
if (currentDay > 100) {
currentDay = 100;
// Show victory message when reaching day 100
LK.showYouWin();
return;
}
// Initialize dual pistol weapon if player already has it
if (hasDualPistol && !dualPistolWeapon) {
dualPistolWeapon = new DualPistol();
}
storage.currentDay = currentDay;
dayTimer = 1800; // 30 seconds per day
gameStateText.setText('Day ' + currentDay);
timerText.setText('30s');
dayProgressText.setText('Day: ' + currentDay + '/100');
// Switch to day background
dayBackground.alpha = 1;
nightBackground.alpha = 0;
LK.stopMusic();
// Reset to default mode
gameMode = 'build';
selectedDefense = 'wall';
defenseText.setText('Defense: Wall (Cost: 10)');
// Clear reload text bug fix
pistolReloadText.alpha = 0;
pistolReloadText.setText('');
wallButtonText.tint = 0x00FF00;
trapButtonText.tint = 0xFFFFFF;
collectButtonText.tint = 0xFFFFFF;
// Initialize wall button as selected
wallButton.tint = 0x00FF00;
trapButton.tint = 0xFFFFFF;
wallIcon.tint = 0x00FF00;
trapIcon.tint = 0xFFFFFF;
// Spawn 5 resources per day
var resourcesToSpawn = 5;
// Only spawn if we have less than 5 resources total
if (resources.length < 5) {
for (var i = 0; i < resourcesToSpawn && resources.length < 5; i++) {
var resource = new Resource();
resource.x = 200 + Math.random() * 1648;
resource.y = 200 + Math.random() * 1200;
resources.push(resource);
game.addChild(resource);
}
}
// Remove any existing trader NPC
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
// Check if trader should appear (every 3 days)
if (currentDay % 3 === 0) {
traderActive = true;
traderNPC = new TraderNPC();
traderNPC.x = 300 + Math.random() * 1400;
traderNPC.y = 300 + Math.random() * 1000;
game.addChild(traderNPC);
} else {
traderActive = false;
}
// Random boss spawning is handled in night phase only
// Removed day-based boss spawning to fix bug where boss appears during day
// Spawn health potion every 5 days
if (currentDay % 5 === 0) {
var healthPotion = new Resource();
healthPotion.x = 200 + Math.random() * 1648;
healthPotion.y = 200 + Math.random() * 1200;
healthPotion.value = 15; // Health value instead of resources
healthPotion.type = 'healthPotion';
// Override the down method to heal instead of giving resources
healthPotion.down = function (x, y, obj) {
playerHealth = Math.min(100, playerHealth + 15);
healthText.setText('Health: ' + playerHealth);
healthPotion.destroy();
for (var i = resources.length - 1; i >= 0; i--) {
if (resources[i] === healthPotion) {
resources.splice(i, 1);
break;
}
}
LK.getSound('collect').play();
};
resources.push(healthPotion);
game.addChild(healthPotion);
}
}
// Joystick movement handler
function updateJoystick(x, y) {
var dx = x - joystickCenterX;
var dy = y - joystickCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > joystickRadius) {
dx = dx / distance * joystickRadius;
dy = dy / distance * joystickRadius;
}
joystickKnob.x = joystickCenterX + dx;
joystickKnob.y = joystickCenterY + dy;
// Calculate movement values (-1 to 1)
moveX = dx / joystickRadius;
moveY = dy / joystickRadius;
}
function resetJoystick() {
joystickKnob.x = joystickCenterX;
joystickKnob.y = joystickCenterY;
moveX = 0;
moveY = 0;
joystickActive = false;
}
// Action button handlers
wallButton.down = function (x, y, obj) {
if (gameState === 'day') {
selectedDefense = 'wall';
gameMode = 'build';
defenseText.setText('Defense: Wall (Cost: 10)');
wallButtonText.tint = 0x00FF00;
trapButtonText.tint = 0xFFFFFF;
collectButtonText.tint = 0xFFFFFF;
// Update button visual states
wallButton.tint = 0x00FF00;
trapButton.tint = 0xFFFFFF;
wallIcon.tint = 0x00FF00;
trapIcon.tint = 0xFFFFFF;
}
disablePlacement = true;
placementCooldown = 60;
placementCooldownText.alpha = 1;
placementCooldownText.setText('1');
};
trapButton.down = function (x, y, obj) {
if (gameState === 'day') {
selectedDefense = 'trap';
gameMode = 'build';
defenseText.setText('Defense: Trap (Cost: 15)');
wallButtonText.tint = 0xFFFFFF;
trapButtonText.tint = 0x00FF00;
collectButtonText.tint = 0xFFFFFF;
// Update button visual states
wallButton.tint = 0xFFFFFF;
trapButton.tint = 0x00FF00;
wallIcon.tint = 0xFFFFFF;
trapIcon.tint = 0x00FF00;
}
disablePlacement = true;
placementCooldown = 60;
placementCooldownText.alpha = 1;
placementCooldownText.setText('1');
};
collectButton.down = function (x, y, obj) {
if (gameState === 'day') {
gameMode = 'collect';
defenseText.setText('Mode: Resource Collection');
wallButtonText.tint = 0xFFFFFF;
trapButtonText.tint = 0xFFFFFF;
collectButtonText.tint = 0x00FF00;
// Reset button visual states
wallButton.tint = 0xFFFFFF;
trapButton.tint = 0xFFFFFF;
wallIcon.tint = 0xFFFFFF;
trapIcon.tint = 0xFFFFFF;
}
disablePlacement = true;
placementCooldown = 60;
placementCooldownText.alpha = 1;
placementCooldownText.setText('1');
};
// Joystick event handlers
game.down = function (x, y, obj) {
if (gameState === 'mainMenu') {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
// Check play button
if (local.x >= -200 && local.x <= 200 && local.y >= -150 && local.y <= -50) {
// Reset all game variables when starting
gameState = 'day';
currentDay = 1;
// Don't save currentDay to storage to prevent day jumping bugs
currentNight = 0;
dayTimer = 1800;
playerHealth = 100;
playerResources = 50;
playerCanMove = true;
berserkMode = false;
berserkUsed = false;
medicReviveUsed = false;
engineerBoostActive = false;
engineerBoostUsed = false;
slowEnemiesActive = false;
slowEnemiesTimer = 0;
pistolReloading = false;
pistolReloadTimer = 0;
pistolBullets = 5;
// Don't reset hasDualPistol - keep it persistent
gameMode = 'build';
selectedDefense = 'wall';
disablePlacement = false;
placementCooldown = 0;
joystickActive = false;
moveX = 0;
moveY = 0;
isTrading = false;
traderActive = false;
// Clear any existing game objects
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i]) {
monsters[i].destroy();
}
monsters.splice(i, 1);
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i]) {
bullets[i].destroy();
}
bullets.splice(i, 1);
}
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i]) {
bombs[i].destroy();
}
bombs.splice(i, 1);
}
for (var i = walls.length - 1; i >= 0; i--) {
if (walls[i]) {
walls[i].destroy();
}
walls.splice(i, 1);
}
for (var i = traps.length - 1; i >= 0; i--) {
if (traps[i]) {
traps[i].destroy();
}
traps.splice(i, 1);
}
for (var i = resources.length - 1; i >= 0; i--) {
if (resources[i]) {
resources[i].destroy();
}
resources.splice(i, 1);
}
// Clear boss objects
if (boss) {
boss.destroy();
boss = null;
}
if (randomBoss) {
randomBoss.destroy();
randomBoss = null;
}
if (treeBoss) {
treeBoss.destroy();
treeBoss = null;
}
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
if (traderContainer) {
traderContainer.destroy();
traderContainer = null;
}
if (dualPistolPickup) {
dualPistolPickup.destroy();
dualPistolPickup = null;
}
if (weaponPickupText) {
weaponPickupText.destroy();
weaponPickupText = null;
}
if (pistolGraphics) {
pistolGraphics.destroy();
pistolGraphics = null;
}
// Reset player position
player.x = 1024;
player.y = 1366;
player.tint = 0xFFFFFF;
player.scaleX = 1.0;
player.scaleY = 1.0;
player.rotation = 0;
player.alpha = 1;
// Reset backgrounds
dayBackground.alpha = 1;
nightBackground.alpha = 0;
// Update UI
healthText.setText('Health: ' + playerHealth);
resourceText.setText('Resources: ' + playerResources);
gameStateText.setText('Day ' + currentDay);
timerText.setText('30s');
nightText.setText('Night: ' + currentNight);
dayProgressText.setText('Day: ' + currentDay + '/100');
defenseText.setText('Defense: Wall (Cost: 10)');
pistolReloadText.alpha = 0;
pistolReloadText.setText('');
placementCooldownText.alpha = 0;
placementCooldownText.setText('');
// Reset joystick
resetJoystick();
// Initialize dual pistol weapon if player already has it
if (hasDualPistol && !dualPistolWeapon) {
dualPistolWeapon = new DualPistol();
}
// Update UI visibility
_updateUIVisibility();
return;
}
// Check characters button
if (local.x >= -200 && local.x <= 200 && local.y >= 0 && local.y <= 100) {
gameState = 'characterSelect';
_updateUIVisibility();
return;
}
return;
}
if (gameState === 'characterSelect') {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
// Check character 1
if (local.x >= -400 && local.x <= -200 && local.y >= -200 && local.y <= 0) {
selectedCharacter = 0;
storage.selectedCharacter = 0;
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.3 : 1.0;
characterStats.healPerNight = 0;
characterStats.discountMultiplier = costReductionPurchased ? 0.8 : 1.0;
// Update character appearance - no tints applied
gameState = 'day';
_updateUIVisibility();
return;
}
// Check character 2
if (local.x >= -100 && local.x <= 100 && local.y >= -200 && local.y <= 0) {
selectedCharacter = 1;
storage.selectedCharacter = 1;
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.1 : 1.0;
characterStats.healPerNight = 10;
characterStats.discountMultiplier = costReductionPurchased ? 0.9 : 1.0;
// Update character appearance - no tints applied
// Update player graphics to use medic texture
player.destroy();
player = game.addChild(LK.getAsset('medicPlayer', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
gameState = 'day';
_updateUIVisibility();
return;
}
// Check character 3
if (local.x >= 200 && local.x <= 400 && local.y >= -200 && local.y <= 0) {
selectedCharacter = 2;
storage.selectedCharacter = 2;
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.1 : 1.0;
characterStats.healPerNight = 0;
characterStats.discountMultiplier = costReductionPurchased ? 0.6 : 1.0;
// Update character appearance - no tints applied
// Update player graphics to use engineer texture
player.destroy();
player = game.addChild(LK.getAsset('engineerPlayer', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
gameState = 'day';
_updateUIVisibility();
return;
}
// Check back button
if (local.x >= -150 && local.x <= 150 && local.y >= 250 && local.y <= 350) {
gameState = 'mainMenu';
_updateUIVisibility();
return;
}
return;
}
// Check if touch is on joystick base
var local = LK.gui.bottomLeft.toLocal({
x: x,
y: y
}, game);
var dx = local.x - joystickCenterX;
var dy = local.y - joystickCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= joystickRadius + 30) {
joystickActive = true;
updateJoystick(local.x, local.y);
return;
}
// Check heal button
if (healButton.alpha > 0) {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
if (local.x >= -370 && local.x <= -130 && local.y >= -120 && local.y <= 120) {
playerHealth = Math.min(100, playerHealth + characterStats.healPerNight);
healthText.setText('Health: ' + playerHealth);
healButton.alpha = 0;
healButtonText.alpha = 0;
LK.getSound('collect').play();
return;
}
}
// Check trader NPC interaction when not already trading
if (!isTrading && traderNPC && gameState === 'day' && traderActive) {
var dx = player.x - traderNPC.x;
var dy = player.y - traderNPC.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120 && gameMode === 'collect') {
// Player is near trader and in collect mode (C pressed)
isTrading = true;
playerCanMove = false;
createTraderUI();
return;
}
}
// Check trader interactions
if (traderActive && traderContainer) {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
// Check upgrade buttons - use simplified bounds checking
for (var i = 0; i < traderContainer.children.length; i++) {
var child = traderContainer.children[i];
if (child.upgradeData) {
// Check if click is within button area (using button position and size)
var buttonHalfWidth = 120; // Approximate button width / 2
var buttonHalfHeight = 60; // Approximate button height / 2
if (local.x >= child.x - buttonHalfWidth && local.x <= child.x + buttonHalfWidth && local.y >= child.y - buttonHalfHeight && local.y <= child.y + buttonHalfHeight) {
purchaseUpgrade(child.upgradeData);
return;
}
}
}
// Check close button
if (local.x >= -90 && local.x <= 90 && local.y >= 140 && local.y <= 260) {
closeTrader();
return;
}
}
// Check if player tapped directly on a resource (works in any mode)
for (var i = resources.length - 1; i >= 0; i--) {
var resource = resources[i];
var dx = x - resource.x;
var dy = y - resource.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200) {
// Direct tap on resource - increased hitbox
playerResources += resource.value;
resourceText.setText('Resources: ' + playerResources);
resource.destroy();
resources.splice(i, 1);
LK.getSound('collect').play();
return; // Exit early after collecting
}
}
if (disablePlacement) {
return; // Prevent placing objects while a button is pressed
}
if (gameState === 'day') {
if (gameMode === 'build') {
var baseCost = selectedDefense === 'wall' ? 10 : 15;
var cost = Math.floor(baseCost * characterStats.discountMultiplier);
if (playerResources >= cost) {
var defense;
if (selectedDefense === 'wall') {
defense = new Wall();
} else {
defense = new Trap();
}
defense.x = x;
defense.y = y;
if (selectedDefense === 'wall') {
walls.push(defense);
} else {
traps.push(defense);
}
game.addChild(defense);
playerResources -= cost;
resourceText.setText('Resources: ' + playerResources);
LK.getSound('build').play();
}
} else if (gameMode === 'collect') {
// Enhanced resource collection in collect mode - collect all resources in range
var resourcesCollected = 0;
for (var i = resources.length - 1; i >= 0; i--) {
var resource = resources[i];
var dx = x - resource.x;
var dy = y - resource.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 400) {
// Collect resources in range
playerResources += resource.value;
resourceText.setText('Resources: ' + playerResources);
resource.destroy();
resources.splice(i, 1);
resourcesCollected++;
// Don't break - collect all resources in range
}
}
if (resourcesCollected > 0) {
LK.getSound('collect').play();
}
}
} else if (gameState === 'night' && hasDualPistol && dualPistolWeapon && dualPistolWeapon.canShoot()) {
// Shoot dual pistol (2 bullets)
if (dualPistolWeapon.shoot()) {
var dx = x - player.x;
var dy = y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Shoot two bullets with slight angle difference
for (var b = 0; b < 2; b++) {
var projectile = new Bullet();
projectile.x = player.x;
projectile.y = player.y;
// Add slight angle variation for dual shots
var angle = Math.atan2(dy, dx) + (b === 0 ? -0.1 : 0.1);
projectile.direction.x = Math.cos(angle);
projectile.direction.y = Math.sin(angle);
bullets.push(projectile);
game.addChild(projectile);
}
// Create dual pistol graphics
if (pistolGraphics) {
pistolGraphics.destroy();
}
pistolGraphics = game.addChild(LK.getAsset('pistolTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
}));
// Remove pistol tint for performance
pistolGraphics.x = player.x + dx / distance * 40;
pistolGraphics.y = player.y + dy / distance * 40;
pistolGraphics.rotation = Math.atan2(dy, dx);
pistolTimer = 20;
// Animate pistol with tween
tween(pistolGraphics, {
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 100,
easing: tween.easeOut
});
LK.getSound('shoot').play();
// Update reload text
if (dualPistolWeapon.isReloading) {
pistolReloadText.alpha = 1;
pistolReloadText.setText('Dual Pistol Reloading...');
}
}
} else if (gameState === 'night' && playerPistol && !pistolReloading && pistolBullets > 0) {
// Shoot bullet or bomb towards click position
var projectile;
if (selectedCharacter === 1) {
// Medic shoots bombs
projectile = new Bomb();
} else {
// Others shoot bullets
projectile = new Bullet();
}
projectile.x = player.x;
projectile.y = player.y;
// Calculate direction to target
var dx = x - player.x;
var dy = y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
projectile.direction.x = dx / distance;
projectile.direction.y = dy / distance;
if (selectedCharacter === 1) {
bombs.push(projectile);
} else {
bullets.push(projectile);
}
game.addChild(projectile);
// Create pistol graphics
if (pistolGraphics) {
pistolGraphics.destroy();
}
pistolGraphics = game.addChild(LK.getAsset('pistolTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
}));
// Remove pistol tint for performance
pistolGraphics.x = player.x + dx / distance * 40;
pistolGraphics.y = player.y + dy / distance * 40;
pistolGraphics.rotation = Math.atan2(dy, dx);
pistolTimer = 20; // Show pistol for 20 frames
// Animate pistol with tween
tween(pistolGraphics, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 100,
easing: tween.easeOut
});
tween(pistolGraphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100,
easing: tween.easeIn
});
LK.getSound('shoot').play();
pistolBullets--;
if (pistolBullets === 0) {
pistolReloading = true;
pistolReloadTimer = 180; // 3 seconds at 60fps
pistolReloadText.alpha = 1;
pistolReloadText.setText('Reloading...');
}
}
// Check for dual pistol pickup
if (dualPistolPickup && !hasDualPistol) {
var dx = x - dualPistolPickup.x;
var dy = y - dualPistolPickup.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
hasDualPistol = true;
storage.hasDualPistol = true; // Save to storage for persistence
dualPistolWeapon = new DualPistol();
dualPistolPickup.destroy();
dualPistolPickup = null;
// Clean up pickup message
if (weaponPickupText) {
weaponPickupText.destroy();
weaponPickupText = null;
}
LK.getSound('collect').play();
}
}
};
game.move = function (x, y, obj) {
if (joystickActive) {
var local = LK.gui.bottomLeft.toLocal({
x: x,
y: y
}, game);
updateJoystick(local.x, local.y);
}
};
game.up = function (x, y, obj) {
if (joystickActive) {
resetJoystick();
}
};
game.update = function () {
// Skip game logic if in menu states
if (gameState === 'mainMenu' || gameState === 'characterSelect') {
return;
}
// Handle joystick movement (only if player can move)
if (playerCanMove && Math.abs(moveX) > 0.1 || Math.abs(moveY) > 0.1) {
player.x = Math.max(50, Math.min(1998, player.x + moveX * playerSpeed));
player.y = Math.max(50, Math.min(2682, player.y + moveY * playerSpeed));
// Epic running animation using tween
if (LK.ticks % 30 === 0) {
// Create bouncing effect
tween(player, {
scaleY: 1.15,
y: player.y - 5
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleY: 1.0,
y: player.y + 5
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Add slight rotation for dynamic movement
var rotationAmount = moveX * 0.1;
tween(player, {
rotation: rotationAmount
}, {
duration: 200,
easing: tween.easeInOut
});
}
}
// Handle berserker mode visual effects
if (berserkMode) {
// Pulsing red effect for berserker mode
if (LK.ticks % 20 === 0) {
tween(player, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
}
} else if (playerCanMove) {
// Epic idle animation when not moving
if (LK.ticks % 120 === 0) {
// Breathing effect
tween(player, {
scaleX: 1.05,
scaleY: 0.98
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
// Reset rotation when idle
if (Math.abs(player.rotation) > 0.01) {
tween(player, {
rotation: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Handle pistol reload timer
if (pistolReloading) {
pistolReloadTimer--;
pistolReloadText.alpha = 1;
pistolReloadText.setText('Reload: ' + Math.ceil(pistolReloadTimer / 60) + 's');
// Apply reload speed upgrade
var reloadTime = 180;
for (var i = 0; i < traderUpgrades.length; i++) {
if (traderUpgrades[i].type === "reload" && traderUpgrades[i].purchased) {
reloadTime = 126; // 30% faster reload
break;
}
}
if (pistolReloadTimer <= 0) {
pistolReloading = false;
pistolBullets = 5;
pistolReloadText.alpha = 0;
}
}
// Handle dual pistol reload and update
if (hasDualPistol && dualPistolWeapon) {
// Update dual pistol weapon
dualPistolWeapon.update();
if (dualPistolWeapon.isReloading) {
pistolReloadText.alpha = 1;
pistolReloadText.setText('Dual Pistol: ' + Math.ceil(dualPistolWeapon.reloadTimer / 60) + 's');
} else {
pistolReloadText.alpha = 0;
}
}
// Handle pistol graphics timer
if (pistolTimer > 0) {
pistolTimer--;
if (pistolTimer <= 0 && pistolGraphics) {
pistolGraphics.destroy();
pistolGraphics = null;
}
}
// Placement cooldown logic
if (placementCooldown > 0) {
placementCooldown--;
if (placementCooldown > 0) {
placementCooldownText.alpha = 1;
placementCooldownText.setText('' + Math.ceil(placementCooldown / 60));
disablePlacement = true;
} else {
placementCooldownText.alpha = 0;
disablePlacement = false;
}
}
if (gameState === 'day') {
dayTimer--;
timerText.setText(Math.ceil(dayTimer / 60) + 's');
if (dayTimer <= 0) {
switchToNight();
}
// Show trader interaction hint when player is near and in collect mode
if (traderNPC && traderActive && gameMode === 'collect') {
var dx = player.x - traderNPC.x;
var dy = player.y - traderNPC.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Make trader glow when nearby in collect mode
traderNPC.alpha = 0.7 + 0.3 * Math.sin(LK.ticks * 0.1);
traderNPC.scaleX = 1.0 + 0.1 * Math.sin(LK.ticks * 0.15);
traderNPC.scaleY = 1.0 + 0.1 * Math.sin(LK.ticks * 0.15);
defenseText.setText('Press C near trader to trade!');
} else {
// Reset trader appearance when not nearby
traderNPC.alpha = 1.0;
traderNPC.scaleX = 1.0;
traderNPC.scaleY = 1.0;
if (gameMode === 'collect') {
defenseText.setText('Mode: Resource Collection');
}
}
}
// Resource collection is now handled only in collect mode or manual interaction
// No automatic pickup when player touches resources
// Simplified resource collection - no visual effects for performance
if (gameMode === 'collect' && LK.ticks % 20 === 0) {
for (var i = resources.length - 1; i >= 0; i--) {
var resource = resources[i];
var dx = player.x - resource.x;
var dy = player.y - resource.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
// Auto collect when player is very close in collect mode
playerResources += resource.value;
resourceText.setText('Resources: ' + playerResources);
resource.destroy();
resources.splice(i, 1);
LK.getSound('collect').play();
}
}
}
} else if (gameState === 'night') {
nightTimer--;
timerText.setText(Math.ceil(nightTimer / 60) + 's');
// Don't spawn monsters when boss is present (unless boss has special spawn ability)
if (boss === null) {
// Spawn more monsters each night with variety
var baseSpawnRate = 1800; // Every 30 seconds base
var spawnRate = Math.max(600, baseSpawnRate - currentNight * 60); // Faster spawning each night, min 10 seconds
var maxMonsters = Math.min(3 + Math.floor(currentNight * 0.5), 8); // More monsters each night, cap at 8
if (LK.ticks % spawnRate === 0 && monsters.length < maxMonsters) {
// Variety of monster types based on night progression
var monsterTypes = ['scout', 'swarm'];
if (currentNight >= 3) {
monsterTypes.push('tank'); // Add tanks from night 3
}
if (currentNight >= 5) {
monsterTypes.push('swarm', 'swarm'); // More swarms later
}
if (currentNight >= 7) {
monsterTypes.push('scout', 'tank'); // More variety later
}
var type = monsterTypes[Math.floor(Math.random() * monsterTypes.length)];
spawnMonster(type);
}
// Additional spawning waves for higher nights
if (currentNight >= 5 && LK.ticks % (spawnRate * 2) === 0 && monsters.length < maxMonsters) {
// Spawn swarm waves
var swarmCount = Math.min(2, Math.floor(currentNight / 3));
for (var s = 0; s < swarmCount && monsters.length < maxMonsters; s++) {
spawnMonster('swarm');
}
}
// Tank spawning for advanced nights
if (currentNight >= 10 && LK.ticks % (spawnRate * 3) === 0 && monsters.length < maxMonsters) {
spawnMonster('tank');
}
}
// Handle slow enemies timer
if (slowEnemiesTimer > 0) {
slowEnemiesTimer--;
if (slowEnemiesTimer <= 0) {
slowEnemiesActive = false;
}
}
// Monster AI and collision - process much less frequently for better performance
var shouldProcessMonsters = LK.ticks % 120 === 0; // Process every 120th frame
var processedMonsters = 0;
var maxMonstersPerFrame = 1; // Limit monsters processed per frame
for (var i = monsters.length - 1; i >= 0; i--) {
var monster = monsters[i];
// Skip if monster doesn't exist
if (!monster) {
monsters.splice(i, 1);
continue;
}
// Skip dead monsters for collision and movement
if (monster.isDead) {
continue;
}
// Skip processing for most monsters to reduce lag
if (i % 5 !== 0) {
continue;
}
// Limit processing to prevent lag
if (processedMonsters >= maxMonstersPerFrame) {
break;
}
processedMonsters++;
// Apply slow effect if active
var originalSpeed = monster.speed;
if (slowEnemiesActive) {
monster.speed = originalSpeed * 0.3; // 70% slower
}
// Skip AI processing for some frames to reduce lag - process only 1 in 5 monsters per frame
if (!shouldProcessMonsters && i % 5 !== 0) {
continue;
}
// Check collision with player
var dx = player.x - monster.x;
var dy = player.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 90) {
// Increase monster damage by 0.5% per night
var damageMultiplier = 1 + currentNight * 0.005;
var actualDamage = Math.floor(monster.damage * damageMultiplier);
playerHealth -= actualDamage;
healthText.setText('Health: ' + playerHealth);
monster.destroy();
monsters.splice(i, 1);
LK.getSound('damage').play();
// Check for special abilities when health drops to 30% (30 health out of 100)
if (playerHealth <= 30) {
if (selectedCharacter === 0 && !berserkUsed) {
// Warrior berserk mode
berserkMode = true;
berserkUsed = true;
playerSpeed = 8; // Increased speed
characterStats.damageMultiplier = 2.0; // Double damage
slowEnemiesActive = true;
slowEnemiesTimer = 600; // 10 seconds
// Add red tint to player for berserker mode
player.tint = 0xFF0000;
LK.effects.flashScreen(0xFF0000, 1000); // Red flash
} else if (selectedCharacter === 1 && !medicReviveUsed) {
// Medic auto-heal
playerHealth = 50;
medicReviveUsed = true;
healthText.setText('Health: ' + playerHealth);
LK.effects.flashScreen(0x00FF00, 1000); // Green flash
} else if (selectedCharacter === 2 && !engineerBoostUsed) {
// Engineer boost
engineerBoostActive = true;
engineerBoostUsed = true;
playerSpeed = 7; // Increased speed
LK.effects.flashScreen(0x0000FF, 1000); // Blue flash
}
}
// Remove berserk mode when health exceeds 30
if (berserkMode && playerHealth > 30) {
berserkMode = false;
playerSpeed = 5; // Reset to normal speed
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.3 : 1.0; // Reset to normal damage
slowEnemiesActive = false;
slowEnemiesTimer = 0;
player.tint = 0xFFFFFF; // Remove red tint
}
if (playerHealth <= 0) {
LK.showGameOver();
}
continue;
}
// Check collision with walls
var hitWall = false;
for (var j = 0; j < walls.length; j++) {
var wall = walls[j];
if (wall.health > 0 && !monster.isDead && monster.intersects(wall)) {
// Monster only attacks wall every 2 seconds (120 ticks)
// Only if player is within 300px or monster is within 300px of wall (simulate "seeing" wall)
var playerDist = Math.sqrt((player.x - monster.x) * (player.x - monster.x) + (player.y - monster.y) * (player.y - monster.y));
var wallDist = Math.sqrt((wall.x - monster.x) * (wall.x - monster.x) + (wall.y - monster.y) * (wall.y - monster.y));
if (playerDist < 300 || wallDist < 300) {
if (LK.ticks - wall.lastMonsterHitTick >= 120) {
wall.registerMonsterHit();
wall.takeDamage(monster.damage);
LK.effects.flashObject(wall, 0xff0000, 200);
// If wall is destroyed after 3 hits, set health to 0 and alpha to 0.3
if (wall.hitCount >= 3 || wall.health <= 0) {
wall.health = 0;
wall.alpha = 0.3;
}
}
}
// Monster stays and keeps attacking, do not destroy monster
hitWall = true;
break;
}
}
if (hitWall) continue;
// Check collision with traps
for (var k = 0; k < traps.length; k++) {
var trap = traps[k];
if (!monster.isDead && monster.intersects(trap)) {
var trapDamage = trap.activate();
if (trapDamage > 0) {
var isDead = monster.takeDamage(trapDamage);
if (isDead) {
// Simple death effect for performance
monster.alpha = 0.3;
monster.scaleX = 1.5;
monster.scaleY = 1.5;
LK.setScore(LK.getScore() + 10);
break;
}
}
}
}
}
// Handle boss combat
if (boss !== null) {
// Update boss health bar
var healthPercent = boss.health / boss.maxHealth;
bossHealthBarFill.scaleX = 10 * healthPercent;
if (boss.type === 'treeBoss') {
bossHealthText.setText('TREE BOSS: ' + boss.health + '/' + boss.maxHealth);
} else {
bossHealthText.setText('BOSS: ' + boss.health + '/' + boss.maxHealth);
}
}
// Handle random boss combat
if (randomBoss !== null) {
// Check collision with player
var dx = player.x - randomBoss.x;
var dy = player.y - randomBoss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
// Increase random boss damage by 0.5% per night
var damageMultiplier = 1 + currentNight * 0.005;
var actualDamage = Math.floor(randomBoss.damage * damageMultiplier);
playerHealth -= actualDamage;
healthText.setText('Health: ' + playerHealth);
LK.getSound('damage').play();
// Check for special abilities when health drops to 30% (30 health out of 100)
if (playerHealth <= 30) {
if (selectedCharacter === 0 && !berserkUsed) {
// Warrior berserk mode
berserkMode = true;
berserkUsed = true;
playerSpeed = 8; // Increased speed
characterStats.damageMultiplier = 2.0; // Double damage
slowEnemiesActive = true;
slowEnemiesTimer = 600; // 10 seconds
// Add red tint to player for berserker mode
player.tint = 0xFF0000;
LK.effects.flashScreen(0xFF0000, 1000); // Red flash
} else if (selectedCharacter === 1 && !medicReviveUsed) {
// Medic auto-heal
playerHealth = 50;
medicReviveUsed = true;
healthText.setText('Health: ' + playerHealth);
LK.effects.flashScreen(0x00FF00, 1000); // Green flash
} else if (selectedCharacter === 2 && !engineerBoostUsed) {
// Engineer boost
engineerBoostActive = true;
engineerBoostUsed = true;
playerSpeed = 7; // Increased speed
LK.effects.flashScreen(0x0000FF, 1000); // Blue flash
}
}
// Remove berserk mode when health exceeds 30 (for random boss collision)
if (berserkMode && playerHealth > 30) {
berserkMode = false;
playerSpeed = 5; // Reset to normal speed
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.3 : 1.0; // Reset to normal damage
slowEnemiesActive = false;
slowEnemiesTimer = 0;
player.tint = 0xFFFFFF; // Remove red tint
}
if (playerHealth <= 0) {
LK.showGameOver();
}
}
}
// Handle bomb collisions and explosions
var processedBombs = 0;
var maxBombsPerFrame = 1; // Limit bombs processed per frame
for (var i = bombs.length - 1; i >= 0; i--) {
var bomb = bombs[i];
// Skip if bomb doesn't exist
if (!bomb) {
bombs.splice(i, 1);
continue;
}
// Skip processing for most bombs to reduce lag
if (i % 2 !== 0) {
continue;
}
// Limit processing to prevent lag
if (processedBombs >= maxBombsPerFrame) {
break;
}
processedBombs++;
var bombHit = false;
// Check collision with monsters
for (var j = monsters.length - 1; j >= 0; j--) {
var monster = monsters[j];
// Skip dead monsters
if (monster.isDead) continue;
var dx = bomb.x - monster.x;
var dy = bomb.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use proper collision radius based on monster type
var monsterRadius = 40; // Base radius
if (monster.type === 'swarm') {
monsterRadius = 25; // Smaller swarm
} else if (monster.type === 'scout') {
monsterRadius = 40; // Standard scout
} else if (monster.type === 'tank') {
monsterRadius = 60; // Larger tank
}
var bombRadius = 40; // Bomb collision radius
if (distance < monsterRadius + bombRadius) {
// Create explosion effect
LK.effects.flashObject(bomb, 0xFF6600, 500);
// Stun and damage all monsters within explosion radius
for (var k = monsters.length - 1; k >= 0; k--) {
var targetMonster = monsters[k];
var expDx = bomb.x - targetMonster.x;
var expDy = bomb.y - targetMonster.y;
var expDistance = Math.sqrt(expDx * expDx + expDy * expDy);
if (expDistance < bomb.explosionRadius) {
// Apply damage based on monster type
var damage = 2; // Base bomb damage
if (targetMonster.type === 'swarm') {
// Corrupted swarm dies in 1 hit from bomb
damage = 1;
} else if (targetMonster.type === 'scout') {
// Corrupted scout dies in 1 hit from bomb (bomb is more powerful)
damage = 2;
} else if (targetMonster.type === 'tank') {
// Corrupted tank takes 2 bombs to die
damage = 2;
}
var isDead = targetMonster.takeDamage(damage);
// Stun effect - reduce speed temporarily
targetMonster.speed = targetMonster.speed * 0.1;
if (isDead) {
// Add kill animation
tween(targetMonster, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 200,
easing: tween.easeOut
});
LK.setScore(LK.getScore() + 10);
}
}
}
bomb.destroy();
bombs.splice(i, 1);
bombHit = true;
break;
}
}
// Check collision with boss
if (!bombHit && boss !== null) {
var dx = bomb.x - boss.x;
var dy = bomb.y - boss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use smaller hitbox for TreeBoss, normal for other bosses
var bossHitRadius = boss.type === 'treeBoss' ? 60 : 80;
if (distance < bossHitRadius) {
// Explosion damage to boss
var isBossDead = boss.takeDamage(3);
if (isBossDead) {
LK.setScore(LK.getScore() + 100);
// Mark first boss as defeated
if (!hasDefeatedFirstBoss) {
hasDefeatedFirstBoss = true;
storage.hasDefeatedFirstBoss = true;
}
// Drop dual pistol weapon only if player defeated first boss
if (!hasDualPistol && !dualPistolPickup && hasDefeatedFirstBoss) {
dualPistolPickup = new DualPistol();
dualPistolPickup.x = boss.x;
dualPistolPickup.y = boss.y;
game.addChild(dualPistolPickup);
// Simple visual effect for performance
dualPistolPickup.scaleX = 1.2;
dualPistolPickup.scaleY = 1.2;
// Add pickup message
weaponPickupText = new Text2('Hacer click para agarrar', {
size: 60,
fill: 0xFFFF00
});
weaponPickupText.anchor.set(0.5, 0.5);
weaponPickupText.x = boss.x;
weaponPickupText.y = boss.y - 100;
game.addChild(weaponPickupText);
}
boss.destroy();
boss = null;
// Hide boss health bar
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
LK.effects.flashScreen(0x00ff00, 500);
}
bomb.destroy();
bombs.splice(i, 1);
}
}
// Check collision with random boss
if (!bombHit && randomBoss !== null) {
var dx = bomb.x - randomBoss.x;
var dy = bomb.y - randomBoss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
var isBossDead = randomBoss.takeDamage(4);
if (isBossDead) {
LK.setScore(LK.getScore() + 150);
playerResources += 50;
resourceText.setText('Resources: ' + playerResources);
randomBoss.destroy();
randomBoss = null;
LK.effects.flashScreen(0xFFD700, 500);
}
bomb.destroy();
bombs.splice(i, 1);
}
}
}
// Handle bullet collisions - process all bullets every frame for proper hit detection
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Skip if bullet doesn't exist
if (!bullet) {
bullets.splice(i, 1);
continue;
}
var bulletHit = false;
// Modify bullet size for engineer
if (engineerBoostActive && selectedCharacter === 2) {
bullet.scaleX = 2.0;
bullet.scaleY = 2.0;
}
// Check collision with monsters
for (var j = monsters.length - 1; j >= 0; j--) {
var monster = monsters[j];
// Skip dead monsters
if (monster.isDead) continue;
var dx = bullet.x - monster.x;
var dy = bullet.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use proper collision radius based on monster type
var monsterRadius = 40; // Base radius
if (monster.type === 'swarm') {
monsterRadius = 25; // Smaller swarm
} else if (monster.type === 'scout') {
monsterRadius = 40; // Standard scout
} else if (monster.type === 'tank') {
monsterRadius = 60; // Larger tank
}
var bulletRadius = engineerBoostActive && selectedCharacter === 2 ? 20 : 10;
if (distance < monsterRadius + bulletRadius) {
// Apply proper damage based on monster type
var damage = bullet.damage;
if (monster.type === 'swarm') {
// Corrupted swarm dies in 1 shot
damage = 1;
} else if (monster.type === 'scout') {
// Corrupted scout dies in 2 shots
damage = 1;
} else if (monster.type === 'tank') {
// Corrupted tank dies in 3 shots
damage = 1;
}
var isDead = monster.takeDamage(damage);
if (isDead) {
// Add kill animation
tween(monster, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 200,
easing: tween.easeOut
});
LK.setScore(LK.getScore() + 10);
}
bullet.destroy();
bullets.splice(i, 1);
bulletHit = true;
break;
}
}
// Check collision with boss
if (!bulletHit && boss !== null) {
var dx = bullet.x - boss.x;
var dy = bullet.y - boss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use smaller hitbox for TreeBoss, normal for other bosses
var bossHitRadius = boss.type === 'treeBoss' ? 60 : 80;
if (distance < bossHitRadius) {
var isBossDead = boss.takeDamage(bullet.damage);
if (isBossDead) {
LK.setScore(LK.getScore() + 100);
// Mark first boss as defeated
if (!hasDefeatedFirstBoss) {
hasDefeatedFirstBoss = true;
storage.hasDefeatedFirstBoss = true;
}
// Drop dual pistol weapon only if player defeated first boss
if (!hasDualPistol && !dualPistolPickup && hasDefeatedFirstBoss) {
dualPistolPickup = new DualPistol();
dualPistolPickup.x = boss.x;
dualPistolPickup.y = boss.y;
game.addChild(dualPistolPickup);
// Add floating animation to make it more visible
tween(dualPistolPickup, {
y: dualPistolPickup.y - 20,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Add pickup message
weaponPickupText = new Text2('Hacer click para agarrar', {
size: 60,
fill: 0xFFFF00
});
weaponPickupText.anchor.set(0.5, 0.5);
weaponPickupText.x = boss.x;
weaponPickupText.y = boss.y - 100;
game.addChild(weaponPickupText);
}
boss.destroy();
boss = null;
// Hide boss health bar
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
LK.effects.flashScreen(0x00ff00, 500); // Green flash for boss kill
}
bullet.destroy();
bullets.splice(i, 1);
}
}
// Check collision with random boss
if (!bulletHit && randomBoss !== null) {
var dx = bullet.x - randomBoss.x;
var dy = bullet.y - randomBoss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
var isBossDead = randomBoss.takeDamage(bullet.damage);
if (isBossDead) {
LK.setScore(LK.getScore() + 150);
playerResources += 50; // Extra resources for random boss
resourceText.setText('Resources: ' + playerResources);
randomBoss.destroy();
randomBoss = null;
LK.effects.flashScreen(0xFFD700, 500); // Gold flash for random boss kill
}
bullet.destroy();
bullets.splice(i, 1);
}
}
}
if (nightTimer <= 0 && boss === null) {
// Clear remaining monsters
for (var m = monsters.length - 1; m >= 0; m--) {
monsters[m].destroy();
monsters.splice(m, 1);
}
// Clear remaining bullets
for (var b = bullets.length - 1; b >= 0; b--) {
bullets[b].destroy();
bullets.splice(b, 1);
}
// Clear remaining bombs
for (var bomb = bombs.length - 1; bomb >= 0; bomb--) {
bombs[bomb].destroy();
bombs.splice(bomb, 1);
}
switchToDay();
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('resource', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8
});
// Remove bomb tint for performance
self.speed = 6;
self.type = 'bomb';
self.direction = {
x: 0,
y: 0
};
self.explosionRadius = 120;
self.update = function () {
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Remove bomb if it goes off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i] === self) {
bombs.splice(i, 1);
break;
}
}
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('bossTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
// Remove boss tint for performance
self.health = 20;
self.maxHealth = 20;
self.speed = 0; // Boss doesn't move
self.damage = 30;
self.type = 'boss';
self.spawnTimer = 0;
self.attackTimer = 0;
self.attackWarning = null;
self.attackCooldown = 300; // 5 seconds between attacks
self.update = function () {
// Boss stays at center
self.x = 1024;
self.y = 1366;
// Attack system
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Special lightning attack when health is low
if (self.health <= 5) {
// Create multiple warnings
var warningPositions = [];
for (var w = 0; w < 5; w++) {
var warning = game.addChild(new Text2('!', {
size: 150,
fill: 0xFFFF00
}));
warning.anchor.set(0.5, 0.5);
warning.x = player.x + (Math.random() - 0.5) * 300;
warning.y = player.y + (Math.random() - 0.5) * 300;
warning.alpha = 1;
warningPositions.push(warning);
// Flash warning
tween(warning, {
alpha: 0.3,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeInOut
});
}
// Lightning attack after 2 seconds
LK.setTimeout(function () {
for (var i = 0; i < warningPositions.length; i++) {
var warning = warningPositions[i];
if (warning) {
// Create lightning effect
var lightning = game.addChild(LK.getAsset('lightningTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
}));
lightning.x = warning.x;
lightning.y = warning.y;
lightning.tint = 0xFFFF00;
// Check if player is in lightning area
var dx = player.x - warning.x;
var dy = player.y - warning.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
playerHealth -= 40;
healthText.setText('Health: ' + playerHealth);
LK.getSound('damage').play();
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Lightning effect lasts 3 seconds
LK.setTimeout(function () {
if (lightning) {
lightning.destroy();
}
}, 3000);
warning.destroy();
}
}
}, 2000);
} else {
// Normal attack
if (self.attackWarning) {
self.attackWarning.destroy();
}
self.attackWarning = game.addChild(LK.getAsset('bossAttackTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 3.0
}));
self.attackWarning.x = player.x;
self.attackWarning.y = player.y;
self.attackWarning.alpha = 1;
self.attackWarning.tint = 0xFF0000;
// Flash warning
tween(self.attackWarning, {
alpha: 0.3,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.easeInOut
});
// Attack after 2 seconds
LK.setTimeout(function () {
if (self.attackWarning) {
// Check if player is still in attack area
var dx = player.x - self.attackWarning.x;
var dy = player.y - self.attackWarning.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 100) {
// Player is in attack area, take damage
// Increase boss damage by 0.5% per night
var damageMultiplier = 1 + currentNight * 0.005;
var actualDamage = Math.floor(self.damage * damageMultiplier);
playerHealth -= actualDamage;
healthText.setText('Health: ' + playerHealth);
LK.getSound('damage').play();
if (playerHealth <= 0) {
LK.showGameOver();
}
}
// Create explosion effect
LK.effects.flashObject(self.attackWarning, 0xFF6600, 500);
self.attackWarning.destroy();
self.attackWarning = null;
}
}, 2000);
}
}
// Spawn zombies every 10 seconds (600 frames at 60fps)
self.spawnTimer++;
if (self.spawnTimer >= 600) {
self.spawnTimer = 0;
// Spawn a zombie near the boss
var zombie = new CorruptedScout();
zombie.x = self.x + (Math.random() - 0.5) * 200;
zombie.y = self.y + (Math.random() - 0.5) * 200;
zombie.health = 15; // Weaker zombies
monsters.push(zombie);
game.addChild(zombie);
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bulletTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Remove bullet tint for performance
self.speed = storage.bulletSpeedUpgradePurchased ? 12 : 8; // Apply bullet speed upgrade
self.damage = 1;
self.type = 'bullet';
self.direction = {
x: 0,
y: 0
};
self.update = function () {
self.x += self.direction.x * self.speed;
self.y += self.direction.y * self.speed;
// Remove bullet if it goes off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var CorruptedScout = Container.expand(function () {
var self = Container.call(this);
var scoutGraphics = self.attachAsset('corruptedScout', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.maxHealth = 2;
self.speed = 2;
self.damage = 10;
self.type = 'scout';
self.isDead = false;
self.deadTimer = 0;
self.update = function () {
if (self.isDead) {
self.deadTimer++;
if (self.deadTimer >= 120) {
// 2 seconds at 60fps
self.destroy();
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i] === self) {
monsters.splice(i, 1);
break;
}
}
}
return;
}
// Always pursue the player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Calculate intended next position
var nextX = self.x + dx / distance * self.speed;
var nextY = self.y + dy / distance * self.speed;
var blocked = false;
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (wall.health > 0) {
// Simulate monster at next position
var tempDist = Math.sqrt((nextX - wall.x) * (nextX - wall.x) + (nextY - wall.y) * (nextY - wall.y));
if (tempDist < 50) {
blocked = true;
break;
}
}
}
if (!blocked) {
self.x = nextX;
self.y = nextY;
// Minimal animation for performance
if (LK.ticks % 300 === 0) {
self.scaleX = self.scaleX === 1.0 ? 1.02 : 1.0;
}
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
if (self.health <= 0) {
self.isDead = true;
self.alpha = 0.5;
scoutGraphics.rotation = Math.PI / 2; // Rotate to show fallen
return true;
}
return false;
};
return self;
});
var CorruptedSwarm = Container.expand(function () {
var self = Container.call(this);
var swarmGraphics = self.attachAsset('corruptedSwarm', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.maxHealth = 1;
self.speed = 3;
self.damage = 5;
self.type = 'swarm';
self.isDead = false;
self.deadTimer = 0;
self.update = function () {
if (self.isDead) {
self.deadTimer++;
if (self.deadTimer >= 120) {
// 2 seconds at 60fps
self.destroy();
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i] === self) {
monsters.splice(i, 1);
break;
}
}
}
return;
}
// Always pursue the player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Calculate intended next position
var nextX = self.x + dx / distance * self.speed;
var nextY = self.y + dy / distance * self.speed;
var blocked = false;
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (wall.health > 0) {
var tempDist = Math.sqrt((nextX - wall.x) * (nextX - wall.x) + (nextY - wall.y) * (nextY - wall.y));
if (tempDist < 50) {
blocked = true;
break;
}
}
}
if (!blocked) {
self.x = nextX;
self.y = nextY;
// Minimal animation for performance
if (LK.ticks % 450 === 0) {
self.scaleX = self.scaleX === 1.0 ? 1.05 : 1.0;
}
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
if (self.health <= 0) {
self.isDead = true;
self.alpha = 0.5;
swarmGraphics.rotation = Math.PI / 2; // Rotate to show fallen
return true;
}
return false;
};
return self;
});
var CorruptedTank = Container.expand(function () {
var self = Container.call(this);
var tankGraphics = self.attachAsset('corruptedTank', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.speed = 1;
self.damage = 25;
self.type = 'tank';
self.isDead = false;
self.deadTimer = 0;
self.update = function () {
if (self.isDead) {
self.deadTimer++;
if (self.deadTimer >= 120) {
// 2 seconds at 60fps
self.destroy();
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i] === self) {
monsters.splice(i, 1);
break;
}
}
}
return;
}
// Always pursue the player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Calculate intended next position
var nextX = self.x + dx / distance * self.speed;
var nextY = self.y + dy / distance * self.speed;
var blocked = false;
for (var i = 0; i < walls.length; i++) {
var wall = walls[i];
if (wall.health > 0) {
var tempDist = Math.sqrt((nextX - wall.x) * (nextX - wall.x) + (nextY - wall.y) * (nextY - wall.y));
if (tempDist < 50) {
blocked = true;
break;
}
}
}
if (!blocked) {
self.x = nextX;
self.y = nextY;
// Minimal animation for performance
if (LK.ticks % 600 === 0) {
self.scaleX = self.scaleX === 1.0 ? 1.03 : 1.0;
}
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
if (self.health <= 0) {
self.isDead = true;
self.alpha = 0.5;
tankGraphics.rotation = Math.PI / 2; // Rotate to show fallen
return true;
}
return false;
};
return self;
});
var DualPistol = Container.expand(function () {
var self = Container.call(this);
var pistolGraphics = self.attachAsset('pistolTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
});
// Remove dual pistol tint for performance
self.type = 'dualPistol';
self.bullets = 6;
self.maxBullets = 6;
self.isReloading = false;
self.reloadTimer = 0;
self.update = function () {
self.rotation += 0.05;
if (self.isReloading) {
self.reloadTimer--;
if (self.reloadTimer <= 0) {
self.isReloading = false;
self.bullets = self.maxBullets;
}
}
};
self.canShoot = function () {
return self.bullets > 0 && !self.isReloading;
};
self.shoot = function () {
if (self.canShoot()) {
self.bullets--;
if (self.bullets <= 0) {
self.isReloading = true;
self.reloadTimer = 180; // 3 seconds
}
return true;
}
return false;
};
return self;
});
var RandomBoss = Container.expand(function () {
var self = Container.call(this);
var bossGraphics = self.attachAsset('bossTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.0,
scaleY: 3.0
});
// Remove random boss tint for performance
self.health = 30;
self.maxHealth = 30;
self.speed = 1;
self.damage = 40;
self.type = 'randomBoss';
self.attackTimer = 0;
self.attackCooldown = 240; // 4 seconds between attacks
self.update = function () {
// Move towards player slowly
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 10) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Attack system
self.attackTimer++;
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Spawn corrupted scouts around the boss
for (var i = 0; i < 3; i++) {
var scout = new CorruptedScout();
scout.x = self.x + (Math.random() - 0.5) * 150;
scout.y = self.y + (Math.random() - 0.5) * 150;
scout.health = 3; // Stronger scouts
monsters.push(scout);
game.addChild(scout);
}
}
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var Resource = Container.expand(function () {
var self = Container.call(this);
var resourceGraphics = self.attachAsset('resource', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2
});
self.value = 10;
self.type = 'resource';
self.update = function () {
self.rotation += 0.05;
};
self.down = function (x, y, obj) {
// Allow direct tapping on resources to collect them
var resourceValue = storage.resourceValueUpgradePurchased ? Math.floor(self.value * 1.5) : self.value;
playerResources += resourceValue;
resourceText.setText('Resources: ' + playerResources);
self.destroy();
for (var i = resources.length - 1; i >= 0; i--) {
if (resources[i] === self) {
resources.splice(i, 1);
break;
}
}
LK.getSound('collect').play();
};
return self;
});
var TraderNPC = Container.expand(function () {
var self = Container.call(this);
var traderGraphics = self.attachAsset('traderNPC', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0
});
// Remove trader tint for performance
self.type = 'trader';
self.update = function () {
// Trader stays stationary - no movement
};
return self;
});
var Trap = Container.expand(function () {
var self = Container.call(this);
var trapGraphics = self.attachAsset('trap', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = 30;
self.cooldown = 0;
self.type = 'trap';
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
}
};
self.activate = function () {
if (self.cooldown <= 0) {
self.cooldown = 60;
LK.effects.flashObject(self, 0xff6b35, 300);
return self.damage;
}
return 0;
};
return self;
});
var TreeBoss = Container.expand(function () {
var self = Container.call(this);
var treeBossGraphics = self.attachAsset('treeBossTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
});
// Tree boss stays at center and shoots projectiles
self.health = 40;
self.maxHealth = 40;
self.speed = 0; // Tree doesn't move
self.damage = 5;
self.type = 'treeBoss';
self.attackTimer = 0;
self.attackCooldown = 180; // 3 seconds between attacks
self.rapidFireActive = false;
self.rapidFireTimer = 0;
self.rapidFireCooldownTimer = 0;
self.lastRapidFireUsed = false;
self.update = function () {
// Tree boss stays at center
self.x = 1024;
self.y = 1366;
// Handle rapid fire ability when at half health
if (self.health <= self.maxHealth / 2 && !self.lastRapidFireUsed) {
if (!self.rapidFireActive && self.rapidFireCooldownTimer <= 0) {
self.rapidFireActive = true;
self.rapidFireTimer = 300; // 5 seconds of rapid fire
self.rapidFireCooldownTimer = 180; // 3 second cooldown after rapid fire ends
}
}
// Update timers
self.attackTimer++;
if (self.rapidFireCooldownTimer > 0) {
self.rapidFireCooldownTimer--;
}
// Rapid fire mode
if (self.rapidFireActive) {
self.rapidFireTimer--;
// Shoot every 10 frames (6 times per second)
if (LK.ticks % 10 === 0) {
self.shootBulletAtPlayer();
}
if (self.rapidFireTimer <= 0) {
self.rapidFireActive = false;
self.lastRapidFireUsed = true;
self.rapidFireCooldownTimer = 180; // 3 second cooldown
}
} else {
// Normal attack pattern - shoot 3 bullets every 3 seconds
if (self.attackTimer >= self.attackCooldown) {
self.attackTimer = 0;
// Shoot 3 bullets at player
for (var i = 0; i < 3; i++) {
LK.setTimeout(function () {
self.shootBulletAtPlayer();
}, i * 100); // 100ms delay between each bullet
}
}
}
};
self.shootBulletAtPlayer = function () {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
bullet.damage = self.damage;
// Calculate direction to player
var dx = player.x - self.x;
var dy = player.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Normalize direction
bullet.direction.x = dx / distance;
bullet.direction.y = dy / distance;
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
};
self.takeDamage = function (damage) {
var actualDamage = Math.floor(damage * characterStats.damageMultiplier);
self.health -= actualDamage;
LK.effects.flashObject(self, 0xff0000, 200);
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
var baseHealth = storage.wallHealthUpgradePurchased ? 200 : 100; // Apply wall health upgrade
self.health = baseHealth;
self.maxHealth = baseHealth;
self.type = 'wall';
// Track monster hits
self.hitCount = 0;
self.lastMonsterHitTick = 0; // LK.ticks of last monster hit
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
wallGraphics.alpha = 0.3;
} else {
wallGraphics.alpha = 0.5 + self.health / self.maxHealth * 0.5;
}
};
self.registerMonsterHit = function () {
self.hitCount++;
self.lastMonsterHitTick = LK.ticks;
if (self.hitCount >= 3) {
self.health = 0;
wallGraphics.alpha = 0.3;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a1a2e
});
/****
* Game Code
****/
// Initialize game safely with error handling
try {
// Frames left for placement cooldown (60 = 1s)
// Update UI visibility based on game state
var _updateUIVisibility = function _updateUIVisibility() {
if (gameState === 'mainMenu') {
mainMenuContainer.alpha = 1;
characterMenuContainer.alpha = 0;
gameStateText.alpha = 0;
timerText.alpha = 0;
healthText.alpha = 0;
resourceText.alpha = 0;
nightText.alpha = 0;
joystickBase.alpha = 0;
joystickKnob.alpha = 0;
wallButton.alpha = 0;
trapButton.alpha = 0;
collectButton.alpha = 0;
wallButtonText.alpha = 0;
trapButtonText.alpha = 0;
collectButtonText.alpha = 0;
wallIcon.alpha = 0;
trapIcon.alpha = 0;
defenseText.alpha = 0;
healButton.alpha = 0;
healButtonText.alpha = 0;
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
dayProgressText.alpha = 0;
} else if (gameState === 'characterSelect') {
mainMenuContainer.alpha = 0;
characterMenuContainer.alpha = 1;
gameStateText.alpha = 0;
timerText.alpha = 0;
healthText.alpha = 0;
resourceText.alpha = 0;
nightText.alpha = 0;
joystickBase.alpha = 0;
joystickKnob.alpha = 0;
wallButton.alpha = 0;
trapButton.alpha = 0;
collectButton.alpha = 0;
wallButtonText.alpha = 0;
trapButtonText.alpha = 0;
collectButtonText.alpha = 0;
wallIcon.alpha = 0;
trapIcon.alpha = 0;
defenseText.alpha = 0;
healButton.alpha = 0;
healButtonText.alpha = 0;
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
} else {
mainMenuContainer.alpha = 0;
characterMenuContainer.alpha = 0;
gameStateText.alpha = 1;
timerText.alpha = 1;
healthText.alpha = 1;
resourceText.alpha = 1;
nightText.alpha = 1;
joystickBase.alpha = 0.5;
joystickKnob.alpha = 1;
wallButton.alpha = 1;
trapButton.alpha = 1;
collectButton.alpha = 1;
wallButtonText.alpha = 1;
trapButtonText.alpha = 1;
collectButtonText.alpha = 1;
wallIcon.alpha = 1;
trapIcon.alpha = 1;
defenseText.alpha = gameState === 'day' ? 1 : 0;
dayProgressText.alpha = 1;
}
;
};
// Add backgrounds
var dayBackground = game.addChild(LK.getAsset('dayBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
dayBackground.alpha = 1;
var nightBackground = game.addChild(LK.getAsset('nightBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
}));
nightBackground.alpha = 0;
var gameState = 'mainMenu'; // Start with main menu
var currentNight = 0;
var dayTimer = 1800; // 30 seconds at 60fps
var nightTimer = 0;
var playerHealth = 100;
var playerResources = 50;
var selectedDefense = 'wall';
var walls = [];
var traps = [];
var resources = [];
var monsters = [];
var boss = null;
var randomBoss = null;
var treeBoss = null;
var bullets = [];
var bombs = [];
var playerPistol = true; // Player has pistol
var pistolBullets = 5; // Bullets left before reload
var pistolReloading = false; // Is pistol reloading
var pistolReloadTimer = 0; // Frames left for reload (180 = 3s)
var hasDualPistol = storage.hasDualPistol || false; // Player has dual pistol (persistent)
var hasDefeatedFirstBoss = storage.hasDefeatedFirstBoss || false; // Track if player defeated first boss
var dualPistolWeapon = null; // Dual pistol weapon object
var dualPistolPickup = null; // Dual pistol pickup object
var weaponPickupText = null; // Weapon pickup message
var selectedCharacter = storage.selectedCharacter || 0; // 0=damage, 1=heal, 2=discount
var characterStats = {
damageMultiplier: 1.0,
healPerNight: selectedCharacter === 1 ? 10 : 0,
discountMultiplier: 1.0
};
// Trader system variables
var currentDay = 1; // Always start from day 1
// Don't load from storage to prevent day jumping bugs
storage.currentDay = 1; // Reset storage to day 1
var traderActive = false;
var traderContainer = null;
var weaponUpgradePurchased = storage.weaponUpgradePurchased || false;
var costReductionPurchased = storage.costReductionPurchased || false;
var traderNPC = null;
var isTrading = false;
var playerCanMove = true;
var traderUpgrades = [{
name: "Weapon Damage +10%",
cost: 50,
type: "weapon",
purchased: storage.weaponUpgradePurchased || false
}, {
name: "Build Cost -10%",
cost: 30,
type: "cost",
purchased: storage.costReductionPurchased || false
}, {
name: "Health +20",
cost: 40,
type: "health",
purchased: storage.healthUpgradePurchased || false
}, {
name: "Speed +20%",
cost: 35,
type: "speed",
purchased: storage.speedUpgradePurchased || false
}, {
name: "Reload Speed +30%",
cost: 45,
type: "reload",
purchased: storage.reloadUpgradePurchased || false
}, {
name: "Max Health +30",
cost: 70,
type: "maxHealth",
purchased: storage.maxHealthUpgradePurchased || false
}, {
name: "Damage +25%",
cost: 80,
type: "megaDamage",
purchased: storage.megaDamageUpgradePurchased || false
}, {
name: "Bullet Speed +50%",
cost: 60,
type: "bulletSpeed",
purchased: storage.bulletSpeedUpgradePurchased || false
}, {
name: "Wall Health +100%",
cost: 55,
type: "wallHealth",
purchased: storage.wallHealthUpgradePurchased || false
}, {
name: "Resource Value +50%",
cost: 65,
type: "resourceValue",
purchased: storage.resourceValueUpgradePurchased || false
}];
// Special ability states
var berserkMode = false;
var berserkUsed = false;
var medicReviveUsed = false;
var engineerBoostActive = false;
var engineerBoostUsed = false;
var slowEnemiesActive = false;
var slowEnemiesTimer = 0;
// Heal button for medic character
var healButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -250,
y: 0,
scaleX: 3,
scaleY: 3
});
// Remove heal button tint for performance
healButton.alpha = 0;
var healButtonText = new Text2('HEAL', {
size: 60,
fill: 0xFFFFFF
});
healButtonText.anchor.set(0.5, 0.5);
healButtonText.x = -250;
healButtonText.y = 0;
healButtonText.alpha = 0;
LK.gui.center.addChild(healButton);
LK.gui.center.addChild(healButtonText);
var pistolReloadText = new Text2('', {
size: 80,
fill: 0xFF0000
});
pistolReloadText.anchor.set(0.5, 0.5);
pistolReloadText.x = 0;
pistolReloadText.y = 200;
pistolReloadText.alpha = 0;
LK.gui.center.addChild(pistolReloadText);
// Main Menu UI
var mainMenuContainer = new Container();
// Add epic background to main menu
var epicMenuBg = LK.getAsset('epicBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 1.0,
scaleY: 1.0
});
epicMenuBg.tint = 0x8B008B; // Purple tint for epic background
mainMenuContainer.addChild(epicMenuBg);
// Add textured title
var mainMenuTitle = LK.getAsset('titleTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -300,
scaleX: 2.0,
scaleY: 2.0
});
mainMenuTitle.tint = 0xFFD700; // Gold tint for epic look
mainMenuContainer.addChild(mainMenuTitle);
var playButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scaleX: 4,
scaleY: 2
});
var playButtonText = new Text2('PLAY', {
size: 80,
fill: 0xFFFFFF
});
playButtonText.anchor.set(0.5, 0.5);
playButtonText.x = 0;
playButtonText.y = -100;
mainMenuContainer.addChild(playButton);
mainMenuContainer.addChild(playButtonText);
// Add instructions text
var instructionsText = new Text2('Press (C) to collect resources\nand survive the nights!', {
size: 60,
fill: 0xFFD700
});
instructionsText.anchor.set(0.5, 0.5);
instructionsText.x = 0;
instructionsText.y = 200;
mainMenuContainer.addChild(instructionsText);
var charactersButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 50,
scaleX: 4,
scaleY: 2
});
var charactersButtonText = new Text2('CHARACTERS', {
size: 80,
fill: 0xFFFFFF
});
charactersButtonText.anchor.set(0.5, 0.5);
charactersButtonText.x = 0;
charactersButtonText.y = 50;
mainMenuContainer.addChild(charactersButton);
mainMenuContainer.addChild(charactersButtonText);
LK.gui.center.addChild(mainMenuContainer);
// Character Selection UI
var characterMenuContainer = new Container();
var characterMenuTitle = new Text2('SELECT CHARACTER', {
size: 100,
fill: 0xFFFFFF
});
characterMenuTitle.anchor.set(0.5, 0.5);
characterMenuTitle.x = 0;
characterMenuTitle.y = -400;
characterMenuContainer.addChild(characterMenuTitle);
// Character 1 - Damage
var char1Button = LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -100,
scaleX: 2,
scaleY: 2
});
var char1Text = new Text2('WARRIOR\n+30% Damage', {
size: 50,
fill: 0xFFFFFF
});
char1Text.anchor.set(0.5, 0.5);
char1Text.x = -300;
char1Text.y = 50;
characterMenuContainer.addChild(char1Button);
characterMenuContainer.addChild(char1Text);
// Character 2 - Heal
var char2Button = LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -100,
scaleX: 2,
scaleY: 2
});
var char2Text = new Text2('MEDIC\n+10 Health\nper Night', {
size: 50,
fill: 0xFFFFFF
});
char2Text.anchor.set(0.5, 0.5);
char2Text.x = 0;
char2Text.y = 50;
characterMenuContainer.addChild(char2Button);
characterMenuContainer.addChild(char2Text);
// Character 3 - Discount
var char3Button = LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 300,
y: -100,
scaleX: 2,
scaleY: 2
});
var char3Text = new Text2('ENGINEER\n-20% Build\nCost', {
size: 50,
fill: 0xFFFFFF
});
// Remove character selection visual state tints
char3Text.anchor.set(0.5, 0.5);
char3Text.x = 300;
char3Text.y = 50;
characterMenuContainer.addChild(char3Button);
characterMenuContainer.addChild(char3Text);
var backButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 300,
scaleX: 3,
scaleY: 2
});
var backButtonText = new Text2('BACK', {
size: 60,
fill: 0xFFFFFF
});
backButtonText.anchor.set(0.5, 0.5);
backButtonText.x = 0;
backButtonText.y = 300;
characterMenuContainer.addChild(backButton);
characterMenuContainer.addChild(backButtonText);
characterMenuContainer.alpha = 0;
LK.gui.center.addChild(characterMenuContainer);
var player = game.addChild(LK.getAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
// Remove character colors - no tint applied
// Button visibility is now handled by the UI buttons
disablePlacement = false;
var gameStateText = new Text2('Day Phase', {
size: 80,
fill: 0xFFFFFF
});
gameStateText.anchor.set(0.5, 0);
LK.gui.top.addChild(gameStateText);
var timerText = new Text2('30s', {
size: 60,
fill: 0xFFFF00
});
timerText.anchor.set(0.5, 0);
timerText.y = 100;
LK.gui.top.addChild(timerText);
var healthText = new Text2('Health: 100', {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(0, 0);
LK.gui.topLeft.addChild(healthText);
healthText.x = 120;
var resourceText = new Text2('Resources: 50', {
size: 50,
fill: 0xFFD700
});
resourceText.anchor.set(0, 0);
resourceText.y = 60;
LK.gui.topLeft.addChild(resourceText);
resourceText.x = 120;
var nightText = new Text2('Night: 0', {
size: 50,
fill: 0x8B3A8B
});
nightText.anchor.set(1, 0);
LK.gui.topRight.addChild(nightText);
var dayProgressText = new Text2('Day: 1/100', {
size: 50,
fill: 0xFFD700
});
dayProgressText.anchor.set(1, 0);
dayProgressText.y = 60;
LK.gui.topRight.addChild(dayProgressText);
// Boss health bar UI elements
var bossHealthBar = LK.getAsset('healthBarTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 150,
scaleX: 10,
scaleY: 1
});
bossHealthBar.tint = 0x333333;
bossHealthBar.alpha = 0;
LK.gui.top.addChild(bossHealthBar);
var bossHealthBarFill = LK.getAsset('healthBarTexture', {
anchorX: 0.0,
anchorY: 0.5,
x: -300,
y: 150,
scaleX: 10,
scaleY: 1
});
bossHealthBarFill.tint = 0xFF0000;
bossHealthBarFill.alpha = 0;
LK.gui.top.addChild(bossHealthBarFill);
var bossHealthText = new Text2('BOSS', {
size: 50,
fill: 0xFF0000
});
bossHealthText.anchor.set(0.5, 0.5);
bossHealthText.x = 0;
bossHealthText.y = 100;
bossHealthText.alpha = 0;
LK.gui.top.addChild(bossHealthText);
// Joystick control elements
var joystickBase = LK.getAsset('joystickTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -300,
scaleX: 2.5,
scaleY: 2.5
});
joystickBase.alpha = 0.5;
LK.gui.bottomLeft.addChild(joystickBase);
var joystickKnob = LK.getAsset('joystickTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 200,
y: -300,
scaleX: 1.5,
scaleY: 1.5
});
joystickKnob.tint = 0xFFFFFF;
LK.gui.bottomLeft.addChild(joystickKnob);
var joystickActive = false;
var joystickCenterX = 200;
var joystickCenterY = -300;
var joystickRadius = 60;
var defenseText = new Text2('Defense: Wall (Cost: 10)', {
size: 40,
fill: 0xFFFFFF
});
defenseText.anchor.set(0.5, 1);
defenseText.y = -120;
defenseText.alpha = 0;
LK.gui.bottom.addChild(defenseText);
// Action buttons on the right side
var wallButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -200,
scaleX: 2.5,
scaleY: 2.5
});
LK.gui.bottomRight.addChild(wallButton);
var wallIcon = LK.getAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -200,
scaleX: 1.5,
scaleY: 1.5
});
LK.gui.bottomRight.addChild(wallIcon);
var trapButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -150,
y: -200,
scaleX: 2.5,
scaleY: 2.5
});
LK.gui.bottomRight.addChild(trapButton);
var trapIcon = LK.getAsset('trap', {
anchorX: 0.5,
anchorY: 0.5,
x: -150,
y: -200,
scaleX: 1.5,
scaleY: 1.5
});
LK.gui.bottomRight.addChild(trapIcon);
var collectButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: -300,
y: -350,
scaleX: 2.5,
scaleY: 2.5
});
LK.gui.bottomRight.addChild(collectButton);
var wallButtonText = new Text2('W', {
size: 40,
fill: 0xFFFFFF
});
wallButtonText.anchor.set(0.5, 0.5);
wallButtonText.x = -300;
wallButtonText.y = -200;
LK.gui.bottomRight.addChild(wallButtonText);
var trapButtonText = new Text2('T', {
size: 40,
fill: 0xFFFFFF
});
trapButtonText.anchor.set(0.5, 0.5);
trapButtonText.x = -150;
trapButtonText.y = -200;
LK.gui.bottomRight.addChild(trapButtonText);
var collectButtonText = new Text2('C', {
size: 40,
fill: 0xFFFFFF
});
collectButtonText.anchor.set(0.5, 0.5);
collectButtonText.x = -300;
collectButtonText.y = -350;
LK.gui.bottomRight.addChild(collectButtonText);
// Movement control variables
var moveX = 0;
var moveY = 0;
var playerSpeed = 5;
// Pistol graphics
var pistolGraphics = null;
var pistolTimer = 0;
// Game mode state
var gameMode = 'build'; // 'build' or 'collect'
var disablePlacement = false; // Prevents placing objects while a button is pressed
var placementCooldown = 0;
// Initialize game state properly
gameState = 'mainMenu';
playerHealth = 100;
playerResources = 50;
currentNight = 0;
dayTimer = 1800;
nightTimer = 0;
// Ensure all UI elements are properly initialized
mainMenuContainer.alpha = 1;
characterMenuContainer.alpha = 0;
gameStateText.alpha = 0;
timerText.alpha = 0;
healthText.alpha = 0;
resourceText.alpha = 0;
nightText.alpha = 0;
joystickBase.alpha = 0;
joystickKnob.alpha = 0;
wallButton.alpha = 0;
trapButton.alpha = 0;
collectButton.alpha = 0;
wallButtonText.alpha = 0;
trapButtonText.alpha = 0;
collectButtonText.alpha = 0;
wallIcon.alpha = 0;
trapIcon.alpha = 0;
defenseText.alpha = 0;
healButton.alpha = 0;
healButtonText.alpha = 0;
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
// Update UI visibility after all elements are initialized
_updateUIVisibility();
} catch (error) {
console.error('Game initialization error:', error);
// Fallback initialization
gameState = 'mainMenu';
playerHealth = 100;
playerResources = 50;
// Ensure menu is visible even in error case
mainMenuContainer.alpha = 1;
characterMenuContainer.alpha = 0;
}
function createTraderUI() {
traderContainer = new Container();
// Background
var traderBg = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 0,
scaleX: 10,
scaleY: 8
});
traderBg.tint = 0x333333;
traderContainer.addChild(traderBg);
// Title
var traderTitle = new Text2('TRADER - BUFFS', {
size: 80,
fill: 0xFFD700
});
traderTitle.anchor.set(0.5, 0.5);
traderTitle.x = 0;
traderTitle.y = -250;
traderContainer.addChild(traderTitle);
// Show player resources
var resourcesText = new Text2('Resources: ' + playerResources, {
size: 50,
fill: 0xFFFFFF
});
resourcesText.anchor.set(0.5, 0.5);
resourcesText.x = 0;
resourcesText.y = -180;
traderContainer.addChild(resourcesText);
// Create upgrade buttons - show 4 random available upgrades
var availableUpgrades = traderUpgrades.filter(function (upgrade) {
return !upgrade.purchased;
});
// Shuffle available upgrades for randomness
for (var i = availableUpgrades.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = availableUpgrades[i];
availableUpgrades[i] = availableUpgrades[j];
availableUpgrades[j] = temp;
}
// Show up to 4 random upgrades in a 2x2 grid
var upgradesShown = Math.min(4, availableUpgrades.length);
for (var i = 0; i < upgradesShown; i++) {
var upgrade = availableUpgrades[i];
// Calculate position in 2x2 grid
var row = Math.floor(i / 2);
var col = i % 2;
var xPos = (col - 0.5) * 300; // -150 or 150
var yPos = -80 + row * 120; // -80 or 40
var upgradeButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: xPos,
y: yPos,
scaleX: 4,
scaleY: 2
});
// Color button based on affordability
if (playerResources >= upgrade.cost) {
upgradeButton.tint = 0x00AA00; // Green if affordable
} else {
upgradeButton.tint = 0xAA0000; // Red if too expensive
}
upgradeButton.upgradeData = upgrade;
traderContainer.addChild(upgradeButton);
var upgradeText = new Text2(upgrade.name + '\n' + upgrade.cost + ' Resources', {
size: 30,
fill: 0xFFFFFF
});
upgradeText.anchor.set(0.5, 0.5);
upgradeText.x = xPos;
upgradeText.y = yPos;
traderContainer.addChild(upgradeText);
}
// Close button
var closeButton = LK.getAsset('buttonTexture', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: 200,
scaleX: 3,
scaleY: 2
});
var closeText = new Text2('CLOSE', {
size: 60,
fill: 0xFFFFFF
});
closeText.anchor.set(0.5, 0.5);
closeText.x = 0;
closeText.y = 200;
traderContainer.addChild(closeButton);
traderContainer.addChild(closeText);
LK.gui.center.addChild(traderContainer);
}
function closeTrader() {
if (traderContainer) {
traderContainer.destroy();
traderContainer = null;
}
traderActive = false;
isTrading = false;
playerCanMove = true;
}
function purchaseUpgrade(upgrade) {
if (playerResources >= upgrade.cost) {
playerResources -= upgrade.cost;
resourceText.setText('Resources: ' + playerResources);
upgrade.purchased = true;
// Apply upgrade effects and save to storage for persistence
switch (upgrade.type) {
case "weapon":
characterStats.damageMultiplier += 0.1;
weaponUpgradePurchased = true;
storage.weaponUpgradePurchased = true;
break;
case "cost":
characterStats.discountMultiplier *= 0.9;
costReductionPurchased = true;
storage.costReductionPurchased = true;
break;
case "health":
playerHealth = Math.min(100, playerHealth + 20);
healthText.setText('Health: ' + playerHealth);
storage.healthUpgradePurchased = true;
break;
case "speed":
playerSpeed *= 1.2;
storage.speedUpgradePurchased = true;
break;
case "reload":
// This will be applied in reload logic
storage.reloadUpgradePurchased = true;
break;
case "maxHealth":
// Increase max health permanently
playerHealth = Math.min(130, playerHealth + 30);
healthText.setText('Health: ' + playerHealth);
storage.maxHealthUpgradePurchased = true;
break;
case "megaDamage":
characterStats.damageMultiplier += 0.25;
storage.megaDamageUpgradePurchased = true;
break;
case "bulletSpeed":
// This will be applied to bullets when created
storage.bulletSpeedUpgradePurchased = true;
break;
case "wallHealth":
// This will be applied to walls when created
storage.wallHealthUpgradePurchased = true;
break;
case "resourceValue":
// This will be applied to resource collection
storage.resourceValueUpgradePurchased = true;
break;
}
closeTrader();
// Remove trader NPC after purchase
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
traderActive = false;
LK.getSound('collect').play();
} else {
// Flash red if player can't afford
LK.effects.flashScreen(0xFF0000, 300);
}
}
var placementCooldownText = new Text2('', {
size: 80,
fill: 0xFF0000
});
placementCooldownText.anchor.set(0.5, 0.5);
placementCooldownText.x = 0;
placementCooldownText.y = 0;
placementCooldownText.alpha = 0;
LK.gui.center.addChild(placementCooldownText);
// Spawn initial resources - 5 resources per day
var resourcesToSpawn = 5;
for (var i = 0; i < resourcesToSpawn; i++) {
var resource = new Resource();
resource.x = 200 + Math.random() * 1648;
resource.y = 200 + Math.random() * 1200;
resources.push(resource);
game.addChild(resource);
}
// Trader NPC will be spawned only every 5 days
var traderNPC = null;
function spawnMonster(type) {
var monster;
switch (type) {
case 'scout':
monster = new CorruptedScout();
break;
case 'tank':
monster = new CorruptedTank();
break;
case 'swarm':
monster = new CorruptedSwarm();
break;
default:
monster = new CorruptedScout();
}
// Apply night scaling to monster stats
var damageMultiplier = 1 + currentNight * 0.005; // 0.5% more damage per night
var speedMultiplier = 1 + currentNight * 0.005; // 0.5% more speed per night
monster.damage = Math.floor(monster.damage * damageMultiplier);
monster.speed = monster.speed * speedMultiplier;
// Spawn from edges
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
monster.x = Math.random() * 2048;
monster.y = -50;
break;
case 1:
// Right
monster.x = 2098;
monster.y = Math.random() * 2732;
break;
case 2:
// Bottom
monster.x = Math.random() * 2048;
monster.y = 2782;
break;
case 3:
// Left
monster.x = -50;
monster.y = Math.random() * 2732;
break;
}
monsters.push(monster);
game.addChild(monster);
}
function switchToNight() {
gameState = 'night';
currentNight++;
nightTimer = 1800 + (currentNight - 1) * 60; // Base 30s + 1s per night
gameStateText.setText('Night ' + currentNight);
timerText.setText(Math.ceil(nightTimer / 60) + 's');
nightText.setText('Night: ' + currentNight);
// Remove trader NPC during night
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
// Close any open trader UI
if (isTrading) {
closeTrader();
}
// Switch to night background
dayBackground.alpha = 0;
nightBackground.alpha = 1;
LK.playMusic('nightfall');
defenseText.setText('Night Phase - Survive!');
// Show heal button for medic character
if (characterStats.healPerNight > 0 && currentNight > 0) {
healButton.alpha = 1;
healButtonText.alpha = 1;
}
// Spawn initial monsters for the night - start with 6 and increase gradually
var initialMonsters = Math.min(6 + Math.floor(currentNight * 0.5), 8); // Start with 6, cap at 8
for (var i = 0; i < initialMonsters; i++) {
spawnMonster('scout'); // Start with scouts for the initial wave
}
// Spawn boss every 5th night
if (currentNight % 5 === 0 && boss === null) {
boss = new Boss();
// Spawn boss at center
boss.x = 1024;
boss.y = 1366;
game.addChild(boss);
// Show boss health bar
bossHealthBar.alpha = 1;
bossHealthBarFill.alpha = 1;
bossHealthText.alpha = 1;
// Epic boss cinematic - zoom camera to boss
playerCanMove = false; // Disable player movement during cinematic
// Store original game scale for restoration
var originalScaleX = game.scaleX || 1;
var originalScaleY = game.scaleY || 1;
var originalX = game.x || 0;
var originalY = game.y || 0;
// Calculate zoom position to center boss on screen
var targetX = -(boss.x - 1024) * 2; // 2x zoom
var targetY = -(boss.y - 1366) * 2; // 2x zoom
// Zoom in to boss with epic animation
tween(game, {
scaleX: 2.0,
scaleY: 2.0,
x: targetX,
y: targetY
}, {
duration: 1000,
easing: tween.easeInOut
});
// Add epic boss entrance animation
tween(boss, {
scaleX: 3.5,
scaleY: 3.5,
rotation: Math.PI * 2
}, {
duration: 1000,
easing: tween.elasticOut
});
// Flash screen red for dramatic effect
LK.effects.flashScreen(0xFF0000, 1500);
// After 2 seconds, zoom back to normal
LK.setTimeout(function () {
// Restore camera to normal
tween(game, {
scaleX: originalScaleX,
scaleY: originalScaleY,
x: originalX,
y: originalY
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Re-enable player movement
playerCanMove = true;
}
});
// Reset boss scale to normal
tween(boss, {
scaleX: 2.5,
scaleY: 2.5,
rotation: 0
}, {
duration: 800,
easing: tween.easeOut
});
}, 2000);
}
// Spawn Tree Boss on night 15
if (currentNight === 15 && boss === null) {
boss = new TreeBoss();
// Spawn boss at center
boss.x = 1024;
boss.y = 1366;
game.addChild(boss);
// Show boss health bar
bossHealthBar.alpha = 1;
bossHealthBarFill.alpha = 1;
bossHealthText.alpha = 1;
// Epic tree boss cinematic
playerCanMove = false;
// Store original game scale for restoration
var originalScaleX = game.scaleX || 1;
var originalScaleY = game.scaleY || 1;
var originalX = game.x || 0;
var originalY = game.y || 0;
// Calculate zoom position to center boss on screen
var targetX = -(boss.x - 1024) * 2;
var targetY = -(boss.y - 1366) * 2;
// Zoom in to tree boss with epic animation
tween(game, {
scaleX: 2.0,
scaleY: 2.0,
x: targetX,
y: targetY
}, {
duration: 1000,
easing: tween.easeInOut
});
// Add epic tree boss entrance animation
tween(boss, {
scaleX: 2.0,
scaleY: 2.0,
rotation: Math.PI * 2
}, {
duration: 1000,
easing: tween.elasticOut
});
// Flash screen green for tree boss
LK.effects.flashScreen(0x00FF00, 1500);
// After 2 seconds, zoom back to normal
LK.setTimeout(function () {
// Restore camera to normal
tween(game, {
scaleX: originalScaleX,
scaleY: originalScaleY,
x: originalX,
y: originalY
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Re-enable player movement
playerCanMove = true;
}
});
// Reset tree boss scale to normal
tween(boss, {
scaleX: 1.5,
scaleY: 1.5,
rotation: 0
}, {
duration: 800,
easing: tween.easeOut
});
}, 2000);
}
}
function switchToDay() {
gameState = 'day';
currentDay++;
// Ensure currentDay doesn't exceed 100
if (currentDay > 100) {
currentDay = 100;
// Show victory message when reaching day 100
LK.showYouWin();
return;
}
// Initialize dual pistol weapon if player already has it
if (hasDualPistol && !dualPistolWeapon) {
dualPistolWeapon = new DualPistol();
}
storage.currentDay = currentDay;
dayTimer = 1800; // 30 seconds per day
gameStateText.setText('Day ' + currentDay);
timerText.setText('30s');
dayProgressText.setText('Day: ' + currentDay + '/100');
// Switch to day background
dayBackground.alpha = 1;
nightBackground.alpha = 0;
LK.stopMusic();
// Reset to default mode
gameMode = 'build';
selectedDefense = 'wall';
defenseText.setText('Defense: Wall (Cost: 10)');
// Clear reload text bug fix
pistolReloadText.alpha = 0;
pistolReloadText.setText('');
wallButtonText.tint = 0x00FF00;
trapButtonText.tint = 0xFFFFFF;
collectButtonText.tint = 0xFFFFFF;
// Initialize wall button as selected
wallButton.tint = 0x00FF00;
trapButton.tint = 0xFFFFFF;
wallIcon.tint = 0x00FF00;
trapIcon.tint = 0xFFFFFF;
// Spawn 5 resources per day
var resourcesToSpawn = 5;
// Only spawn if we have less than 5 resources total
if (resources.length < 5) {
for (var i = 0; i < resourcesToSpawn && resources.length < 5; i++) {
var resource = new Resource();
resource.x = 200 + Math.random() * 1648;
resource.y = 200 + Math.random() * 1200;
resources.push(resource);
game.addChild(resource);
}
}
// Remove any existing trader NPC
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
// Check if trader should appear (every 3 days)
if (currentDay % 3 === 0) {
traderActive = true;
traderNPC = new TraderNPC();
traderNPC.x = 300 + Math.random() * 1400;
traderNPC.y = 300 + Math.random() * 1000;
game.addChild(traderNPC);
} else {
traderActive = false;
}
// Random boss spawning is handled in night phase only
// Removed day-based boss spawning to fix bug where boss appears during day
// Spawn health potion every 5 days
if (currentDay % 5 === 0) {
var healthPotion = new Resource();
healthPotion.x = 200 + Math.random() * 1648;
healthPotion.y = 200 + Math.random() * 1200;
healthPotion.value = 15; // Health value instead of resources
healthPotion.type = 'healthPotion';
// Override the down method to heal instead of giving resources
healthPotion.down = function (x, y, obj) {
playerHealth = Math.min(100, playerHealth + 15);
healthText.setText('Health: ' + playerHealth);
healthPotion.destroy();
for (var i = resources.length - 1; i >= 0; i--) {
if (resources[i] === healthPotion) {
resources.splice(i, 1);
break;
}
}
LK.getSound('collect').play();
};
resources.push(healthPotion);
game.addChild(healthPotion);
}
}
// Joystick movement handler
function updateJoystick(x, y) {
var dx = x - joystickCenterX;
var dy = y - joystickCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > joystickRadius) {
dx = dx / distance * joystickRadius;
dy = dy / distance * joystickRadius;
}
joystickKnob.x = joystickCenterX + dx;
joystickKnob.y = joystickCenterY + dy;
// Calculate movement values (-1 to 1)
moveX = dx / joystickRadius;
moveY = dy / joystickRadius;
}
function resetJoystick() {
joystickKnob.x = joystickCenterX;
joystickKnob.y = joystickCenterY;
moveX = 0;
moveY = 0;
joystickActive = false;
}
// Action button handlers
wallButton.down = function (x, y, obj) {
if (gameState === 'day') {
selectedDefense = 'wall';
gameMode = 'build';
defenseText.setText('Defense: Wall (Cost: 10)');
wallButtonText.tint = 0x00FF00;
trapButtonText.tint = 0xFFFFFF;
collectButtonText.tint = 0xFFFFFF;
// Update button visual states
wallButton.tint = 0x00FF00;
trapButton.tint = 0xFFFFFF;
wallIcon.tint = 0x00FF00;
trapIcon.tint = 0xFFFFFF;
}
disablePlacement = true;
placementCooldown = 60;
placementCooldownText.alpha = 1;
placementCooldownText.setText('1');
};
trapButton.down = function (x, y, obj) {
if (gameState === 'day') {
selectedDefense = 'trap';
gameMode = 'build';
defenseText.setText('Defense: Trap (Cost: 15)');
wallButtonText.tint = 0xFFFFFF;
trapButtonText.tint = 0x00FF00;
collectButtonText.tint = 0xFFFFFF;
// Update button visual states
wallButton.tint = 0xFFFFFF;
trapButton.tint = 0x00FF00;
wallIcon.tint = 0xFFFFFF;
trapIcon.tint = 0x00FF00;
}
disablePlacement = true;
placementCooldown = 60;
placementCooldownText.alpha = 1;
placementCooldownText.setText('1');
};
collectButton.down = function (x, y, obj) {
if (gameState === 'day') {
gameMode = 'collect';
defenseText.setText('Mode: Resource Collection');
wallButtonText.tint = 0xFFFFFF;
trapButtonText.tint = 0xFFFFFF;
collectButtonText.tint = 0x00FF00;
// Reset button visual states
wallButton.tint = 0xFFFFFF;
trapButton.tint = 0xFFFFFF;
wallIcon.tint = 0xFFFFFF;
trapIcon.tint = 0xFFFFFF;
}
disablePlacement = true;
placementCooldown = 60;
placementCooldownText.alpha = 1;
placementCooldownText.setText('1');
};
// Joystick event handlers
game.down = function (x, y, obj) {
if (gameState === 'mainMenu') {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
// Check play button
if (local.x >= -200 && local.x <= 200 && local.y >= -150 && local.y <= -50) {
// Reset all game variables when starting
gameState = 'day';
currentDay = 1;
// Don't save currentDay to storage to prevent day jumping bugs
currentNight = 0;
dayTimer = 1800;
playerHealth = 100;
playerResources = 50;
playerCanMove = true;
berserkMode = false;
berserkUsed = false;
medicReviveUsed = false;
engineerBoostActive = false;
engineerBoostUsed = false;
slowEnemiesActive = false;
slowEnemiesTimer = 0;
pistolReloading = false;
pistolReloadTimer = 0;
pistolBullets = 5;
// Don't reset hasDualPistol - keep it persistent
gameMode = 'build';
selectedDefense = 'wall';
disablePlacement = false;
placementCooldown = 0;
joystickActive = false;
moveX = 0;
moveY = 0;
isTrading = false;
traderActive = false;
// Clear any existing game objects
for (var i = monsters.length - 1; i >= 0; i--) {
if (monsters[i]) {
monsters[i].destroy();
}
monsters.splice(i, 1);
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i]) {
bullets[i].destroy();
}
bullets.splice(i, 1);
}
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i]) {
bombs[i].destroy();
}
bombs.splice(i, 1);
}
for (var i = walls.length - 1; i >= 0; i--) {
if (walls[i]) {
walls[i].destroy();
}
walls.splice(i, 1);
}
for (var i = traps.length - 1; i >= 0; i--) {
if (traps[i]) {
traps[i].destroy();
}
traps.splice(i, 1);
}
for (var i = resources.length - 1; i >= 0; i--) {
if (resources[i]) {
resources[i].destroy();
}
resources.splice(i, 1);
}
// Clear boss objects
if (boss) {
boss.destroy();
boss = null;
}
if (randomBoss) {
randomBoss.destroy();
randomBoss = null;
}
if (treeBoss) {
treeBoss.destroy();
treeBoss = null;
}
if (traderNPC) {
traderNPC.destroy();
traderNPC = null;
}
if (traderContainer) {
traderContainer.destroy();
traderContainer = null;
}
if (dualPistolPickup) {
dualPistolPickup.destroy();
dualPistolPickup = null;
}
if (weaponPickupText) {
weaponPickupText.destroy();
weaponPickupText = null;
}
if (pistolGraphics) {
pistolGraphics.destroy();
pistolGraphics = null;
}
// Reset player position
player.x = 1024;
player.y = 1366;
player.tint = 0xFFFFFF;
player.scaleX = 1.0;
player.scaleY = 1.0;
player.rotation = 0;
player.alpha = 1;
// Reset backgrounds
dayBackground.alpha = 1;
nightBackground.alpha = 0;
// Update UI
healthText.setText('Health: ' + playerHealth);
resourceText.setText('Resources: ' + playerResources);
gameStateText.setText('Day ' + currentDay);
timerText.setText('30s');
nightText.setText('Night: ' + currentNight);
dayProgressText.setText('Day: ' + currentDay + '/100');
defenseText.setText('Defense: Wall (Cost: 10)');
pistolReloadText.alpha = 0;
pistolReloadText.setText('');
placementCooldownText.alpha = 0;
placementCooldownText.setText('');
// Reset joystick
resetJoystick();
// Initialize dual pistol weapon if player already has it
if (hasDualPistol && !dualPistolWeapon) {
dualPistolWeapon = new DualPistol();
}
// Update UI visibility
_updateUIVisibility();
return;
}
// Check characters button
if (local.x >= -200 && local.x <= 200 && local.y >= 0 && local.y <= 100) {
gameState = 'characterSelect';
_updateUIVisibility();
return;
}
return;
}
if (gameState === 'characterSelect') {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
// Check character 1
if (local.x >= -400 && local.x <= -200 && local.y >= -200 && local.y <= 0) {
selectedCharacter = 0;
storage.selectedCharacter = 0;
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.3 : 1.0;
characterStats.healPerNight = 0;
characterStats.discountMultiplier = costReductionPurchased ? 0.8 : 1.0;
// Update character appearance - no tints applied
gameState = 'day';
_updateUIVisibility();
return;
}
// Check character 2
if (local.x >= -100 && local.x <= 100 && local.y >= -200 && local.y <= 0) {
selectedCharacter = 1;
storage.selectedCharacter = 1;
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.1 : 1.0;
characterStats.healPerNight = 10;
characterStats.discountMultiplier = costReductionPurchased ? 0.9 : 1.0;
// Update character appearance - no tints applied
// Update player graphics to use medic texture
player.destroy();
player = game.addChild(LK.getAsset('medicPlayer', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
gameState = 'day';
_updateUIVisibility();
return;
}
// Check character 3
if (local.x >= 200 && local.x <= 400 && local.y >= -200 && local.y <= 0) {
selectedCharacter = 2;
storage.selectedCharacter = 2;
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.1 : 1.0;
characterStats.healPerNight = 0;
characterStats.discountMultiplier = costReductionPurchased ? 0.6 : 1.0;
// Update character appearance - no tints applied
// Update player graphics to use engineer texture
player.destroy();
player = game.addChild(LK.getAsset('engineerPlayer', {
anchorX: 0.5,
anchorY: 0.5,
x: 1024,
y: 1366
}));
gameState = 'day';
_updateUIVisibility();
return;
}
// Check back button
if (local.x >= -150 && local.x <= 150 && local.y >= 250 && local.y <= 350) {
gameState = 'mainMenu';
_updateUIVisibility();
return;
}
return;
}
// Check if touch is on joystick base
var local = LK.gui.bottomLeft.toLocal({
x: x,
y: y
}, game);
var dx = local.x - joystickCenterX;
var dy = local.y - joystickCenterY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= joystickRadius + 30) {
joystickActive = true;
updateJoystick(local.x, local.y);
return;
}
// Check heal button
if (healButton.alpha > 0) {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
if (local.x >= -370 && local.x <= -130 && local.y >= -120 && local.y <= 120) {
playerHealth = Math.min(100, playerHealth + characterStats.healPerNight);
healthText.setText('Health: ' + playerHealth);
healButton.alpha = 0;
healButtonText.alpha = 0;
LK.getSound('collect').play();
return;
}
}
// Check trader NPC interaction when not already trading
if (!isTrading && traderNPC && gameState === 'day' && traderActive) {
var dx = player.x - traderNPC.x;
var dy = player.y - traderNPC.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120 && gameMode === 'collect') {
// Player is near trader and in collect mode (C pressed)
isTrading = true;
playerCanMove = false;
createTraderUI();
return;
}
}
// Check trader interactions
if (traderActive && traderContainer) {
var local = LK.gui.center.toLocal({
x: x,
y: y
}, game);
// Check upgrade buttons - use simplified bounds checking
for (var i = 0; i < traderContainer.children.length; i++) {
var child = traderContainer.children[i];
if (child.upgradeData) {
// Check if click is within button area (using button position and size)
var buttonHalfWidth = 120; // Approximate button width / 2
var buttonHalfHeight = 60; // Approximate button height / 2
if (local.x >= child.x - buttonHalfWidth && local.x <= child.x + buttonHalfWidth && local.y >= child.y - buttonHalfHeight && local.y <= child.y + buttonHalfHeight) {
purchaseUpgrade(child.upgradeData);
return;
}
}
}
// Check close button
if (local.x >= -90 && local.x <= 90 && local.y >= 140 && local.y <= 260) {
closeTrader();
return;
}
}
// Check if player tapped directly on a resource (works in any mode)
for (var i = resources.length - 1; i >= 0; i--) {
var resource = resources[i];
var dx = x - resource.x;
var dy = y - resource.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 200) {
// Direct tap on resource - increased hitbox
playerResources += resource.value;
resourceText.setText('Resources: ' + playerResources);
resource.destroy();
resources.splice(i, 1);
LK.getSound('collect').play();
return; // Exit early after collecting
}
}
if (disablePlacement) {
return; // Prevent placing objects while a button is pressed
}
if (gameState === 'day') {
if (gameMode === 'build') {
var baseCost = selectedDefense === 'wall' ? 10 : 15;
var cost = Math.floor(baseCost * characterStats.discountMultiplier);
if (playerResources >= cost) {
var defense;
if (selectedDefense === 'wall') {
defense = new Wall();
} else {
defense = new Trap();
}
defense.x = x;
defense.y = y;
if (selectedDefense === 'wall') {
walls.push(defense);
} else {
traps.push(defense);
}
game.addChild(defense);
playerResources -= cost;
resourceText.setText('Resources: ' + playerResources);
LK.getSound('build').play();
}
} else if (gameMode === 'collect') {
// Enhanced resource collection in collect mode - collect all resources in range
var resourcesCollected = 0;
for (var i = resources.length - 1; i >= 0; i--) {
var resource = resources[i];
var dx = x - resource.x;
var dy = y - resource.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 400) {
// Collect resources in range
playerResources += resource.value;
resourceText.setText('Resources: ' + playerResources);
resource.destroy();
resources.splice(i, 1);
resourcesCollected++;
// Don't break - collect all resources in range
}
}
if (resourcesCollected > 0) {
LK.getSound('collect').play();
}
}
} else if (gameState === 'night' && hasDualPistol && dualPistolWeapon && dualPistolWeapon.canShoot()) {
// Shoot dual pistol (2 bullets)
if (dualPistolWeapon.shoot()) {
var dx = x - player.x;
var dy = y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Shoot two bullets with slight angle difference
for (var b = 0; b < 2; b++) {
var projectile = new Bullet();
projectile.x = player.x;
projectile.y = player.y;
// Add slight angle variation for dual shots
var angle = Math.atan2(dy, dx) + (b === 0 ? -0.1 : 0.1);
projectile.direction.x = Math.cos(angle);
projectile.direction.y = Math.sin(angle);
bullets.push(projectile);
game.addChild(projectile);
}
// Create dual pistol graphics
if (pistolGraphics) {
pistolGraphics.destroy();
}
pistolGraphics = game.addChild(LK.getAsset('pistolTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 2.0
}));
// Remove pistol tint for performance
pistolGraphics.x = player.x + dx / distance * 40;
pistolGraphics.y = player.y + dy / distance * 40;
pistolGraphics.rotation = Math.atan2(dy, dx);
pistolTimer = 20;
// Animate pistol with tween
tween(pistolGraphics, {
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 100,
easing: tween.easeOut
});
LK.getSound('shoot').play();
// Update reload text
if (dualPistolWeapon.isReloading) {
pistolReloadText.alpha = 1;
pistolReloadText.setText('Dual Pistol Reloading...');
}
}
} else if (gameState === 'night' && playerPistol && !pistolReloading && pistolBullets > 0) {
// Shoot bullet or bomb towards click position
var projectile;
if (selectedCharacter === 1) {
// Medic shoots bombs
projectile = new Bomb();
} else {
// Others shoot bullets
projectile = new Bullet();
}
projectile.x = player.x;
projectile.y = player.y;
// Calculate direction to target
var dx = x - player.x;
var dy = y - player.y;
var distance = Math.sqrt(dx * dx + dy * dy);
projectile.direction.x = dx / distance;
projectile.direction.y = dy / distance;
if (selectedCharacter === 1) {
bombs.push(projectile);
} else {
bullets.push(projectile);
}
game.addChild(projectile);
// Create pistol graphics
if (pistolGraphics) {
pistolGraphics.destroy();
}
pistolGraphics = game.addChild(LK.getAsset('pistolTexture', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5
}));
// Remove pistol tint for performance
pistolGraphics.x = player.x + dx / distance * 40;
pistolGraphics.y = player.y + dy / distance * 40;
pistolGraphics.rotation = Math.atan2(dy, dx);
pistolTimer = 20; // Show pistol for 20 frames
// Animate pistol with tween
tween(pistolGraphics, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 100,
easing: tween.easeOut
});
tween(pistolGraphics, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 100,
easing: tween.easeIn
});
LK.getSound('shoot').play();
pistolBullets--;
if (pistolBullets === 0) {
pistolReloading = true;
pistolReloadTimer = 180; // 3 seconds at 60fps
pistolReloadText.alpha = 1;
pistolReloadText.setText('Reloading...');
}
}
// Check for dual pistol pickup
if (dualPistolPickup && !hasDualPistol) {
var dx = x - dualPistolPickup.x;
var dy = y - dualPistolPickup.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
hasDualPistol = true;
storage.hasDualPistol = true; // Save to storage for persistence
dualPistolWeapon = new DualPistol();
dualPistolPickup.destroy();
dualPistolPickup = null;
// Clean up pickup message
if (weaponPickupText) {
weaponPickupText.destroy();
weaponPickupText = null;
}
LK.getSound('collect').play();
}
}
};
game.move = function (x, y, obj) {
if (joystickActive) {
var local = LK.gui.bottomLeft.toLocal({
x: x,
y: y
}, game);
updateJoystick(local.x, local.y);
}
};
game.up = function (x, y, obj) {
if (joystickActive) {
resetJoystick();
}
};
game.update = function () {
// Skip game logic if in menu states
if (gameState === 'mainMenu' || gameState === 'characterSelect') {
return;
}
// Handle joystick movement (only if player can move)
if (playerCanMove && Math.abs(moveX) > 0.1 || Math.abs(moveY) > 0.1) {
player.x = Math.max(50, Math.min(1998, player.x + moveX * playerSpeed));
player.y = Math.max(50, Math.min(2682, player.y + moveY * playerSpeed));
// Epic running animation using tween
if (LK.ticks % 30 === 0) {
// Create bouncing effect
tween(player, {
scaleY: 1.15,
y: player.y - 5
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleY: 1.0,
y: player.y + 5
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Add slight rotation for dynamic movement
var rotationAmount = moveX * 0.1;
tween(player, {
rotation: rotationAmount
}, {
duration: 200,
easing: tween.easeInOut
});
}
}
// Handle berserker mode visual effects
if (berserkMode) {
// Pulsing red effect for berserker mode
if (LK.ticks % 20 === 0) {
tween(player, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
}
} else if (playerCanMove) {
// Epic idle animation when not moving
if (LK.ticks % 120 === 0) {
// Breathing effect
tween(player, {
scaleX: 1.05,
scaleY: 0.98
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(player, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
// Reset rotation when idle
if (Math.abs(player.rotation) > 0.01) {
tween(player, {
rotation: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// Handle pistol reload timer
if (pistolReloading) {
pistolReloadTimer--;
pistolReloadText.alpha = 1;
pistolReloadText.setText('Reload: ' + Math.ceil(pistolReloadTimer / 60) + 's');
// Apply reload speed upgrade
var reloadTime = 180;
for (var i = 0; i < traderUpgrades.length; i++) {
if (traderUpgrades[i].type === "reload" && traderUpgrades[i].purchased) {
reloadTime = 126; // 30% faster reload
break;
}
}
if (pistolReloadTimer <= 0) {
pistolReloading = false;
pistolBullets = 5;
pistolReloadText.alpha = 0;
}
}
// Handle dual pistol reload and update
if (hasDualPistol && dualPistolWeapon) {
// Update dual pistol weapon
dualPistolWeapon.update();
if (dualPistolWeapon.isReloading) {
pistolReloadText.alpha = 1;
pistolReloadText.setText('Dual Pistol: ' + Math.ceil(dualPistolWeapon.reloadTimer / 60) + 's');
} else {
pistolReloadText.alpha = 0;
}
}
// Handle pistol graphics timer
if (pistolTimer > 0) {
pistolTimer--;
if (pistolTimer <= 0 && pistolGraphics) {
pistolGraphics.destroy();
pistolGraphics = null;
}
}
// Placement cooldown logic
if (placementCooldown > 0) {
placementCooldown--;
if (placementCooldown > 0) {
placementCooldownText.alpha = 1;
placementCooldownText.setText('' + Math.ceil(placementCooldown / 60));
disablePlacement = true;
} else {
placementCooldownText.alpha = 0;
disablePlacement = false;
}
}
if (gameState === 'day') {
dayTimer--;
timerText.setText(Math.ceil(dayTimer / 60) + 's');
if (dayTimer <= 0) {
switchToNight();
}
// Show trader interaction hint when player is near and in collect mode
if (traderNPC && traderActive && gameMode === 'collect') {
var dx = player.x - traderNPC.x;
var dy = player.y - traderNPC.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 120) {
// Make trader glow when nearby in collect mode
traderNPC.alpha = 0.7 + 0.3 * Math.sin(LK.ticks * 0.1);
traderNPC.scaleX = 1.0 + 0.1 * Math.sin(LK.ticks * 0.15);
traderNPC.scaleY = 1.0 + 0.1 * Math.sin(LK.ticks * 0.15);
defenseText.setText('Press C near trader to trade!');
} else {
// Reset trader appearance when not nearby
traderNPC.alpha = 1.0;
traderNPC.scaleX = 1.0;
traderNPC.scaleY = 1.0;
if (gameMode === 'collect') {
defenseText.setText('Mode: Resource Collection');
}
}
}
// Resource collection is now handled only in collect mode or manual interaction
// No automatic pickup when player touches resources
// Simplified resource collection - no visual effects for performance
if (gameMode === 'collect' && LK.ticks % 20 === 0) {
for (var i = resources.length - 1; i >= 0; i--) {
var resource = resources[i];
var dx = player.x - resource.x;
var dy = player.y - resource.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 150) {
// Auto collect when player is very close in collect mode
playerResources += resource.value;
resourceText.setText('Resources: ' + playerResources);
resource.destroy();
resources.splice(i, 1);
LK.getSound('collect').play();
}
}
}
} else if (gameState === 'night') {
nightTimer--;
timerText.setText(Math.ceil(nightTimer / 60) + 's');
// Don't spawn monsters when boss is present (unless boss has special spawn ability)
if (boss === null) {
// Spawn more monsters each night with variety
var baseSpawnRate = 1800; // Every 30 seconds base
var spawnRate = Math.max(600, baseSpawnRate - currentNight * 60); // Faster spawning each night, min 10 seconds
var maxMonsters = Math.min(3 + Math.floor(currentNight * 0.5), 8); // More monsters each night, cap at 8
if (LK.ticks % spawnRate === 0 && monsters.length < maxMonsters) {
// Variety of monster types based on night progression
var monsterTypes = ['scout', 'swarm'];
if (currentNight >= 3) {
monsterTypes.push('tank'); // Add tanks from night 3
}
if (currentNight >= 5) {
monsterTypes.push('swarm', 'swarm'); // More swarms later
}
if (currentNight >= 7) {
monsterTypes.push('scout', 'tank'); // More variety later
}
var type = monsterTypes[Math.floor(Math.random() * monsterTypes.length)];
spawnMonster(type);
}
// Additional spawning waves for higher nights
if (currentNight >= 5 && LK.ticks % (spawnRate * 2) === 0 && monsters.length < maxMonsters) {
// Spawn swarm waves
var swarmCount = Math.min(2, Math.floor(currentNight / 3));
for (var s = 0; s < swarmCount && monsters.length < maxMonsters; s++) {
spawnMonster('swarm');
}
}
// Tank spawning for advanced nights
if (currentNight >= 10 && LK.ticks % (spawnRate * 3) === 0 && monsters.length < maxMonsters) {
spawnMonster('tank');
}
}
// Handle slow enemies timer
if (slowEnemiesTimer > 0) {
slowEnemiesTimer--;
if (slowEnemiesTimer <= 0) {
slowEnemiesActive = false;
}
}
// Monster AI and collision - process much less frequently for better performance
var shouldProcessMonsters = LK.ticks % 120 === 0; // Process every 120th frame
var processedMonsters = 0;
var maxMonstersPerFrame = 1; // Limit monsters processed per frame
for (var i = monsters.length - 1; i >= 0; i--) {
var monster = monsters[i];
// Skip if monster doesn't exist
if (!monster) {
monsters.splice(i, 1);
continue;
}
// Skip dead monsters for collision and movement
if (monster.isDead) {
continue;
}
// Skip processing for most monsters to reduce lag
if (i % 5 !== 0) {
continue;
}
// Limit processing to prevent lag
if (processedMonsters >= maxMonstersPerFrame) {
break;
}
processedMonsters++;
// Apply slow effect if active
var originalSpeed = monster.speed;
if (slowEnemiesActive) {
monster.speed = originalSpeed * 0.3; // 70% slower
}
// Skip AI processing for some frames to reduce lag - process only 1 in 5 monsters per frame
if (!shouldProcessMonsters && i % 5 !== 0) {
continue;
}
// Check collision with player
var dx = player.x - monster.x;
var dy = player.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 90) {
// Increase monster damage by 0.5% per night
var damageMultiplier = 1 + currentNight * 0.005;
var actualDamage = Math.floor(monster.damage * damageMultiplier);
playerHealth -= actualDamage;
healthText.setText('Health: ' + playerHealth);
monster.destroy();
monsters.splice(i, 1);
LK.getSound('damage').play();
// Check for special abilities when health drops to 30% (30 health out of 100)
if (playerHealth <= 30) {
if (selectedCharacter === 0 && !berserkUsed) {
// Warrior berserk mode
berserkMode = true;
berserkUsed = true;
playerSpeed = 8; // Increased speed
characterStats.damageMultiplier = 2.0; // Double damage
slowEnemiesActive = true;
slowEnemiesTimer = 600; // 10 seconds
// Add red tint to player for berserker mode
player.tint = 0xFF0000;
LK.effects.flashScreen(0xFF0000, 1000); // Red flash
} else if (selectedCharacter === 1 && !medicReviveUsed) {
// Medic auto-heal
playerHealth = 50;
medicReviveUsed = true;
healthText.setText('Health: ' + playerHealth);
LK.effects.flashScreen(0x00FF00, 1000); // Green flash
} else if (selectedCharacter === 2 && !engineerBoostUsed) {
// Engineer boost
engineerBoostActive = true;
engineerBoostUsed = true;
playerSpeed = 7; // Increased speed
LK.effects.flashScreen(0x0000FF, 1000); // Blue flash
}
}
// Remove berserk mode when health exceeds 30
if (berserkMode && playerHealth > 30) {
berserkMode = false;
playerSpeed = 5; // Reset to normal speed
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.3 : 1.0; // Reset to normal damage
slowEnemiesActive = false;
slowEnemiesTimer = 0;
player.tint = 0xFFFFFF; // Remove red tint
}
if (playerHealth <= 0) {
LK.showGameOver();
}
continue;
}
// Check collision with walls
var hitWall = false;
for (var j = 0; j < walls.length; j++) {
var wall = walls[j];
if (wall.health > 0 && !monster.isDead && monster.intersects(wall)) {
// Monster only attacks wall every 2 seconds (120 ticks)
// Only if player is within 300px or monster is within 300px of wall (simulate "seeing" wall)
var playerDist = Math.sqrt((player.x - monster.x) * (player.x - monster.x) + (player.y - monster.y) * (player.y - monster.y));
var wallDist = Math.sqrt((wall.x - monster.x) * (wall.x - monster.x) + (wall.y - monster.y) * (wall.y - monster.y));
if (playerDist < 300 || wallDist < 300) {
if (LK.ticks - wall.lastMonsterHitTick >= 120) {
wall.registerMonsterHit();
wall.takeDamage(monster.damage);
LK.effects.flashObject(wall, 0xff0000, 200);
// If wall is destroyed after 3 hits, set health to 0 and alpha to 0.3
if (wall.hitCount >= 3 || wall.health <= 0) {
wall.health = 0;
wall.alpha = 0.3;
}
}
}
// Monster stays and keeps attacking, do not destroy monster
hitWall = true;
break;
}
}
if (hitWall) continue;
// Check collision with traps
for (var k = 0; k < traps.length; k++) {
var trap = traps[k];
if (!monster.isDead && monster.intersects(trap)) {
var trapDamage = trap.activate();
if (trapDamage > 0) {
var isDead = monster.takeDamage(trapDamage);
if (isDead) {
// Simple death effect for performance
monster.alpha = 0.3;
monster.scaleX = 1.5;
monster.scaleY = 1.5;
LK.setScore(LK.getScore() + 10);
break;
}
}
}
}
}
// Handle boss combat
if (boss !== null) {
// Update boss health bar
var healthPercent = boss.health / boss.maxHealth;
bossHealthBarFill.scaleX = 10 * healthPercent;
if (boss.type === 'treeBoss') {
bossHealthText.setText('TREE BOSS: ' + boss.health + '/' + boss.maxHealth);
} else {
bossHealthText.setText('BOSS: ' + boss.health + '/' + boss.maxHealth);
}
}
// Handle random boss combat
if (randomBoss !== null) {
// Check collision with player
var dx = player.x - randomBoss.x;
var dy = player.y - randomBoss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
// Increase random boss damage by 0.5% per night
var damageMultiplier = 1 + currentNight * 0.005;
var actualDamage = Math.floor(randomBoss.damage * damageMultiplier);
playerHealth -= actualDamage;
healthText.setText('Health: ' + playerHealth);
LK.getSound('damage').play();
// Check for special abilities when health drops to 30% (30 health out of 100)
if (playerHealth <= 30) {
if (selectedCharacter === 0 && !berserkUsed) {
// Warrior berserk mode
berserkMode = true;
berserkUsed = true;
playerSpeed = 8; // Increased speed
characterStats.damageMultiplier = 2.0; // Double damage
slowEnemiesActive = true;
slowEnemiesTimer = 600; // 10 seconds
// Add red tint to player for berserker mode
player.tint = 0xFF0000;
LK.effects.flashScreen(0xFF0000, 1000); // Red flash
} else if (selectedCharacter === 1 && !medicReviveUsed) {
// Medic auto-heal
playerHealth = 50;
medicReviveUsed = true;
healthText.setText('Health: ' + playerHealth);
LK.effects.flashScreen(0x00FF00, 1000); // Green flash
} else if (selectedCharacter === 2 && !engineerBoostUsed) {
// Engineer boost
engineerBoostActive = true;
engineerBoostUsed = true;
playerSpeed = 7; // Increased speed
LK.effects.flashScreen(0x0000FF, 1000); // Blue flash
}
}
// Remove berserk mode when health exceeds 30 (for random boss collision)
if (berserkMode && playerHealth > 30) {
berserkMode = false;
playerSpeed = 5; // Reset to normal speed
characterStats.damageMultiplier = weaponUpgradePurchased ? 1.3 : 1.0; // Reset to normal damage
slowEnemiesActive = false;
slowEnemiesTimer = 0;
player.tint = 0xFFFFFF; // Remove red tint
}
if (playerHealth <= 0) {
LK.showGameOver();
}
}
}
// Handle bomb collisions and explosions
var processedBombs = 0;
var maxBombsPerFrame = 1; // Limit bombs processed per frame
for (var i = bombs.length - 1; i >= 0; i--) {
var bomb = bombs[i];
// Skip if bomb doesn't exist
if (!bomb) {
bombs.splice(i, 1);
continue;
}
// Skip processing for most bombs to reduce lag
if (i % 2 !== 0) {
continue;
}
// Limit processing to prevent lag
if (processedBombs >= maxBombsPerFrame) {
break;
}
processedBombs++;
var bombHit = false;
// Check collision with monsters
for (var j = monsters.length - 1; j >= 0; j--) {
var monster = monsters[j];
// Skip dead monsters
if (monster.isDead) continue;
var dx = bomb.x - monster.x;
var dy = bomb.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use proper collision radius based on monster type
var monsterRadius = 40; // Base radius
if (monster.type === 'swarm') {
monsterRadius = 25; // Smaller swarm
} else if (monster.type === 'scout') {
monsterRadius = 40; // Standard scout
} else if (monster.type === 'tank') {
monsterRadius = 60; // Larger tank
}
var bombRadius = 40; // Bomb collision radius
if (distance < monsterRadius + bombRadius) {
// Create explosion effect
LK.effects.flashObject(bomb, 0xFF6600, 500);
// Stun and damage all monsters within explosion radius
for (var k = monsters.length - 1; k >= 0; k--) {
var targetMonster = monsters[k];
var expDx = bomb.x - targetMonster.x;
var expDy = bomb.y - targetMonster.y;
var expDistance = Math.sqrt(expDx * expDx + expDy * expDy);
if (expDistance < bomb.explosionRadius) {
// Apply damage based on monster type
var damage = 2; // Base bomb damage
if (targetMonster.type === 'swarm') {
// Corrupted swarm dies in 1 hit from bomb
damage = 1;
} else if (targetMonster.type === 'scout') {
// Corrupted scout dies in 1 hit from bomb (bomb is more powerful)
damage = 2;
} else if (targetMonster.type === 'tank') {
// Corrupted tank takes 2 bombs to die
damage = 2;
}
var isDead = targetMonster.takeDamage(damage);
// Stun effect - reduce speed temporarily
targetMonster.speed = targetMonster.speed * 0.1;
if (isDead) {
// Add kill animation
tween(targetMonster, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 200,
easing: tween.easeOut
});
LK.setScore(LK.getScore() + 10);
}
}
}
bomb.destroy();
bombs.splice(i, 1);
bombHit = true;
break;
}
}
// Check collision with boss
if (!bombHit && boss !== null) {
var dx = bomb.x - boss.x;
var dy = bomb.y - boss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use smaller hitbox for TreeBoss, normal for other bosses
var bossHitRadius = boss.type === 'treeBoss' ? 60 : 80;
if (distance < bossHitRadius) {
// Explosion damage to boss
var isBossDead = boss.takeDamage(3);
if (isBossDead) {
LK.setScore(LK.getScore() + 100);
// Mark first boss as defeated
if (!hasDefeatedFirstBoss) {
hasDefeatedFirstBoss = true;
storage.hasDefeatedFirstBoss = true;
}
// Drop dual pistol weapon only if player defeated first boss
if (!hasDualPistol && !dualPistolPickup && hasDefeatedFirstBoss) {
dualPistolPickup = new DualPistol();
dualPistolPickup.x = boss.x;
dualPistolPickup.y = boss.y;
game.addChild(dualPistolPickup);
// Simple visual effect for performance
dualPistolPickup.scaleX = 1.2;
dualPistolPickup.scaleY = 1.2;
// Add pickup message
weaponPickupText = new Text2('Hacer click para agarrar', {
size: 60,
fill: 0xFFFF00
});
weaponPickupText.anchor.set(0.5, 0.5);
weaponPickupText.x = boss.x;
weaponPickupText.y = boss.y - 100;
game.addChild(weaponPickupText);
}
boss.destroy();
boss = null;
// Hide boss health bar
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
LK.effects.flashScreen(0x00ff00, 500);
}
bomb.destroy();
bombs.splice(i, 1);
}
}
// Check collision with random boss
if (!bombHit && randomBoss !== null) {
var dx = bomb.x - randomBoss.x;
var dy = bomb.y - randomBoss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
var isBossDead = randomBoss.takeDamage(4);
if (isBossDead) {
LK.setScore(LK.getScore() + 150);
playerResources += 50;
resourceText.setText('Resources: ' + playerResources);
randomBoss.destroy();
randomBoss = null;
LK.effects.flashScreen(0xFFD700, 500);
}
bomb.destroy();
bombs.splice(i, 1);
}
}
}
// Handle bullet collisions - process all bullets every frame for proper hit detection
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
// Skip if bullet doesn't exist
if (!bullet) {
bullets.splice(i, 1);
continue;
}
var bulletHit = false;
// Modify bullet size for engineer
if (engineerBoostActive && selectedCharacter === 2) {
bullet.scaleX = 2.0;
bullet.scaleY = 2.0;
}
// Check collision with monsters
for (var j = monsters.length - 1; j >= 0; j--) {
var monster = monsters[j];
// Skip dead monsters
if (monster.isDead) continue;
var dx = bullet.x - monster.x;
var dy = bullet.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use proper collision radius based on monster type
var monsterRadius = 40; // Base radius
if (monster.type === 'swarm') {
monsterRadius = 25; // Smaller swarm
} else if (monster.type === 'scout') {
monsterRadius = 40; // Standard scout
} else if (monster.type === 'tank') {
monsterRadius = 60; // Larger tank
}
var bulletRadius = engineerBoostActive && selectedCharacter === 2 ? 20 : 10;
if (distance < monsterRadius + bulletRadius) {
// Apply proper damage based on monster type
var damage = bullet.damage;
if (monster.type === 'swarm') {
// Corrupted swarm dies in 1 shot
damage = 1;
} else if (monster.type === 'scout') {
// Corrupted scout dies in 2 shots
damage = 1;
} else if (monster.type === 'tank') {
// Corrupted tank dies in 3 shots
damage = 1;
}
var isDead = monster.takeDamage(damage);
if (isDead) {
// Add kill animation
tween(monster, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0.3
}, {
duration: 200,
easing: tween.easeOut
});
LK.setScore(LK.getScore() + 10);
}
bullet.destroy();
bullets.splice(i, 1);
bulletHit = true;
break;
}
}
// Check collision with boss
if (!bulletHit && boss !== null) {
var dx = bullet.x - boss.x;
var dy = bullet.y - boss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Use smaller hitbox for TreeBoss, normal for other bosses
var bossHitRadius = boss.type === 'treeBoss' ? 60 : 80;
if (distance < bossHitRadius) {
var isBossDead = boss.takeDamage(bullet.damage);
if (isBossDead) {
LK.setScore(LK.getScore() + 100);
// Mark first boss as defeated
if (!hasDefeatedFirstBoss) {
hasDefeatedFirstBoss = true;
storage.hasDefeatedFirstBoss = true;
}
// Drop dual pistol weapon only if player defeated first boss
if (!hasDualPistol && !dualPistolPickup && hasDefeatedFirstBoss) {
dualPistolPickup = new DualPistol();
dualPistolPickup.x = boss.x;
dualPistolPickup.y = boss.y;
game.addChild(dualPistolPickup);
// Add floating animation to make it more visible
tween(dualPistolPickup, {
y: dualPistolPickup.y - 20,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 1000,
easing: tween.easeInOut,
repeat: -1,
yoyo: true
});
// Add pickup message
weaponPickupText = new Text2('Hacer click para agarrar', {
size: 60,
fill: 0xFFFF00
});
weaponPickupText.anchor.set(0.5, 0.5);
weaponPickupText.x = boss.x;
weaponPickupText.y = boss.y - 100;
game.addChild(weaponPickupText);
}
boss.destroy();
boss = null;
// Hide boss health bar
bossHealthBar.alpha = 0;
bossHealthBarFill.alpha = 0;
bossHealthText.alpha = 0;
LK.effects.flashScreen(0x00ff00, 500); // Green flash for boss kill
}
bullet.destroy();
bullets.splice(i, 1);
}
}
// Check collision with random boss
if (!bulletHit && randomBoss !== null) {
var dx = bullet.x - randomBoss.x;
var dy = bullet.y - randomBoss.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
var isBossDead = randomBoss.takeDamage(bullet.damage);
if (isBossDead) {
LK.setScore(LK.getScore() + 150);
playerResources += 50; // Extra resources for random boss
resourceText.setText('Resources: ' + playerResources);
randomBoss.destroy();
randomBoss = null;
LK.effects.flashScreen(0xFFD700, 500); // Gold flash for random boss kill
}
bullet.destroy();
bullets.splice(i, 1);
}
}
}
if (nightTimer <= 0 && boss === null) {
// Clear remaining monsters
for (var m = monsters.length - 1; m >= 0; m--) {
monsters[m].destroy();
monsters.splice(m, 1);
}
// Clear remaining bullets
for (var b = bullets.length - 1; b >= 0; b--) {
bullets[b].destroy();
bullets.splice(b, 1);
}
// Clear remaining bombs
for (var bomb = bombs.length - 1; bomb >= 0; bomb--) {
bombs[bomb].destroy();
bombs.splice(bomb, 1);
}
switchToDay();
}
}
};
Crea picos de madera, que todos estén unidos en una tablilla por abajo y que arriba estén los picos. In-Game asset. 2d. High contrast. No shadows
Crea un botón de color azul pixel. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de lava pixel. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un guerrero pixel, sin armas, y que tenga la cabeza un poco más grande que el cuerpo tipo pixel. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un monstruo corrupto morado de piedra qué no sea tan terrorífico, y que sea pixel.Y qué tenga manos grandes, y torso y que la cabeza no sea tan terrorífico, y no le ponga ojos perturbadores, y que su cabeza sea de un tamaño moderado no muy grande, que algo que si sea grande sea sus brazos, y que tenga algas moradas y que tenga algunas piedras moradas y otras piedras que no sean moradas qué sean grises In-Game asset. 2d. High contrast. No shadows.
Genera una imagen verde solo verde oscuro. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de una avispa no terroríficas, pixel y que sean moradas. Que sea corrupta In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un corrupto pixel, y que no sea terrorífico y que sea morado. In-Game asset. 2d. High contrast. No shadows
Genera una imagen morado oscuro solo morado oscuro In-Game asset. 2d. High contrast. No shadows
Genera una imagen de una roca morada sonriente. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un círculo verde pixel. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un destello brillante morado pixel. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de una pistola pixel. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de una bala pixel. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un médico guerrero pixel, y que su cabeza sea un poco más grande que el cuerpo. Y que no tenga armas In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un albañil guerrero pixel, que su cabeza sea un poco más grande que su cuerpo, y que no tenga armas. In-Game asset. 2d. High contrast. No shadows
Genera una imagen de un comerciante pixel y que su cabeza sea un poco más grande que su cuerpo. In-Game asset. 2d. High contrast. No shadows
Crea una imagen de un texto que diga "The corrupt survival" qué sea pixel y en la palabra "i" ponle una espada pixel. In-Game asset. 2d. High contrast. No shadows