/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Bullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('bullet', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 8; self.damage = 1; self.targetX = 0; self.targetY = 0; self.update = function () { var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { self.x = self.targetX; self.y = self.targetY; self.isAtTarget = true; } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; return self; }); var Defender = Container.expand(function () { var self = Container.call(this); self.range = 200; self.fireRate = 60; self.lastFireTime = 0; self.damage = 1; self.isPlaced = false; self.canFire = function () { return LK.ticks - self.lastFireTime > self.fireRate; }; self.findTarget = function () { var closestEnemy = null; var closestDistance = Infinity; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; if (enemy.isDead) { continue; } var dx = enemy.x - self.x; var dy = enemy.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= self.range && distance < closestDistance) { closestEnemy = enemy; closestDistance = distance; } } return closestEnemy; }; self.shoot = function (target) { if (!self.canFire()) { return; } var bullet = new Bullet(); bullet.x = self.x; bullet.y = self.y; bullet.targetX = target.x; bullet.targetY = target.y; bullet.damage = self.damage; bullets.push(bullet); game.addChild(bullet); self.lastFireTime = LK.ticks; LK.getSound('shoot').play(); }; self.update = function () { if (!self.isPlaced) { return; } var target = self.findTarget(); if (target) { self.shoot(target); } }; return self; }); var Raccoon = Defender.expand(function () { var self = Defender.call(this); var graphics = self.attachAsset('raccoon', { anchorX: 0.5, anchorY: 0.5 }); self.range = 350; self.fireRate = 40; self.damage = 2.5; return self; }); var Cat = Defender.expand(function () { var self = Defender.call(this); var graphics = self.attachAsset('cat', { anchorX: 0.5, anchorY: 0.5 }); self.range = 400; self.fireRate = 50; self.damage = 1.5; return self; }); var Bird = Defender.expand(function () { var self = Defender.call(this); var graphics = self.attachAsset('bird', { anchorX: 0.5, anchorY: 0.5 }); self.range = 450; self.fireRate = 80; self.damage = 0.5; return self; }); var Bear = Defender.expand(function () { var self = Defender.call(this); var graphics = self.attachAsset('bear', { anchorX: 0.5, anchorY: 0.5 }); self.range = 300; self.fireRate = 70; self.damage = 4; return self; }); var Enemy = Container.expand(function () { var self = Container.call(this); self.speed = 2; self.health = 1; self.maxHealth = 1; self.isDead = false; self.targetX = 0; self.targetY = 0; self.takeDamage = function (damage) { self.health -= damage; if (self.health <= 0) { self.isDead = true; LK.setScore(LK.getScore() + 10); money += self.coinReward || 1; updateMoneyDisplay(); LK.getSound('enemyHit').play(); } }; self.update = function () { if (self.isDead) { return; } var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < self.speed) { self.x = self.targetX; self.y = self.targetY; self.hasReachedMeat = true; } else { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } }; return self; }); var Worm = Enemy.expand(function () { var self = Enemy.call(this); var graphics = self.attachAsset('worm', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1.5; self.health = 1; self.maxHealth = 1; self.coinReward = 1; return self; }); var Snake = Enemy.expand(function () { var self = Enemy.call(this); var graphics = self.attachAsset('snake', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1.8; self.health = 3; self.maxHealth = 3; self.coinReward = 3; return self; }); var Mouse = Enemy.expand(function () { var self = Enemy.call(this); var graphics = self.attachAsset('mouse', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2.5; self.health = 2; self.maxHealth = 2; self.coinReward = 2; return self; }); var Ant = Enemy.expand(function () { var self = Enemy.call(this); var graphics = self.attachAsset('ant', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; self.health = 1; self.maxHealth = 1; self.coinReward = 1; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x228B22 }); /**** * Game Code ****/ var grassBackground = game.addChild(LK.getAsset('grass', { anchorX: 0, anchorY: 0, x: 0, y: 0 })); var meat = game.addChild(LK.getAsset('meat', { anchorX: 0.5, anchorY: 0.5, x: 1024, y: 1366, scaleX: 1.5, scaleY: 1.5 })); var enemies = []; var defenders = []; var bullets = []; var money = 7; var selectedDefenderType = 'bird'; var rangeIndicator = null; var showingRange = false; var waveNumber = 1; var enemiesSpawned = 0; var enemiesPerWave = 5; var spawnTimer = 0; var spawnInterval = 120; var defenderCounts = { 'bird': 0, 'cat': 0, 'raccoon': 0, 'bear': 0 }; var defenderLimits = { 'bird': 5, 'cat': 4, 'raccoon': 3, 'bear': 2 }; var waveTxt = new Text2('Wave: 1', { size: 50, fill: 0xFFFFFF }); waveTxt.anchor.set(0, 0); waveTxt.x = 200; waveTxt.y = 100; LK.gui.topLeft.addChild(waveTxt); var moneyTxt = new Text2('Money: 7', { size: 50, fill: 0xFFD700 }); moneyTxt.anchor.set(0, 0); moneyTxt.x = 200; moneyTxt.y = 160; LK.gui.topLeft.addChild(moneyTxt); var selectedDefenderType = 'bird'; var defenderTypes = ['bird', 'cat', 'raccoon', 'bear']; var defenderCosts = { 'bird': 1, 'cat': 4, 'raccoon': 8, 'bear': 12 }; var defenderButtons = []; function updateWave() { waveTxt.setText('Wave: ' + waveNumber); } function updateMoneyDisplay() { moneyTxt.setText('Money: ' + money); updateDefenderButtonStates(); } function createDefenderButtons() { var buttonSize = 180; var spacing = 60; var totalWidth = defenderTypes.length * buttonSize + (defenderTypes.length - 1) * spacing; var startX = (2048 - totalWidth) / 2; var y = 2732 - buttonSize - 50; for (var i = 0; i < defenderTypes.length; i++) { var defenderType = defenderTypes[i]; var cost = defenderCosts[defenderType]; // Create button container var button = new Container(); button.defenderType = defenderType; button.cost = cost; button.x = startX + i * (buttonSize + spacing); button.y = y; button.width = buttonSize; button.height = buttonSize + 60; // Extra height for text // Background removed for cleaner look // Add defender image var defenderImage = LK.getAsset(defenderType, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); button.addChild(defenderImage); // Add cost text var costText = new Text2('$' + cost, { size: 60, fill: 0xFFD700 }); costText.anchor.set(0.5, 0); costText.x = 0; costText.y = buttonSize / 2 + 15; button.addChild(costText); button.costText = costText; // Add count/limit text var countText = new Text2('0/' + defenderLimits[defenderType], { size: 40, fill: 0xFFFFFF }); countText.anchor.set(0.5, 0); countText.x = 0; countText.y = buttonSize / 2 + 75; button.addChild(countText); button.countText = countText; // Add selection indicator var selectionIndicator = LK.getAsset('rangeIndicator', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7, alpha: 0.6 }); selectionIndicator.visible = false; button.addChild(selectionIndicator); button.selectionIndicator = selectionIndicator; defenderButtons.push(button); game.addChild(button); } updateDefenderButtonStates(); } function updateDefenderButtonStates() { for (var i = 0; i < defenderButtons.length; i++) { var button = defenderButtons[i]; var canAfford = money >= button.cost; var isSelected = button.defenderType === selectedDefenderType; var hasReachedLimit = defenderCounts[button.defenderType] >= defenderLimits[button.defenderType]; // Update affordability colors and limits if (hasReachedLimit) { button.costText.fill = 0x888888; // Gray for limit reached button.alpha = 0.3; } else { button.costText.fill = canAfford ? 0xFFD700 : 0xFF0000; button.alpha = canAfford ? 1.0 : 0.5; } // Update selection indicator button.selectionIndicator.visible = isSelected; // Update count text button.countText.setText(defenderCounts[button.defenderType] + '/' + defenderLimits[button.defenderType]); } } createDefenderButtons(); var snakeSpawnedThisWave = false; function spawnEnemy() { var enemyTypes = [Worm, Ant, Mouse]; if (waveNumber < 5) { // Before wave 5: Snake can spawn once per wave if (!snakeSpawnedThisWave && Math.random() < 0.2) { enemyTypes.push(Snake); } } else { // Wave 5 and after: Snake can spawn unlimited enemyTypes.push(Snake); } var EnemyClass = enemyTypes[Math.floor(Math.random() * enemyTypes.length)]; if (EnemyClass === Snake && waveNumber < 5) { snakeSpawnedThisWave = true; } var enemy = new EnemyClass(); // Boost enemy health after wave 5 if (waveNumber > 5) { var healthBoost = Math.floor((waveNumber - 5) / 2) + 1; // +1 HP every 2 waves after wave 5 enemy.health += healthBoost; enemy.maxHealth += healthBoost; } var spawnSides = [{ x: 0, y: Math.random() * 2732, flipX: 1 // Left side - face right (flip) }, { x: 2048, y: Math.random() * 2732, flipX: 0 // Right side - face left (no flip) }, { x: Math.random() * 2048, y: 0, flipX: 0 // Top side - no flip }, { x: Math.random() * 2048, y: 2732, flipX: 0 // Bottom side - no flip }]; var spawn = spawnSides[Math.floor(Math.random() * spawnSides.length)]; enemy.x = spawn.x; enemy.y = spawn.y; enemy.targetX = meat.x; enemy.targetY = meat.y; // Apply horizontal flip based on spawn side and position relative to meat if (spawn.flipX === 1) { enemy.scaleX = -1; } else if (spawn.x === 0 || spawn.x === 2048) { // Left or right spawn - already handled above } else { // Top or bottom spawn - face towards meat horizontally if (enemy.x > meat.x) { // Enemy is to the right of meat - face left enemy.scaleX = -1; } else { // Enemy is to the left of meat - face right enemy.scaleX = 1; } } enemies.push(enemy); game.addChild(enemy); enemiesSpawned++; } function startNextWave() { waveNumber++; enemiesSpawned = 0; enemiesPerWave = Math.min((5 + Math.floor(waveNumber / 2)) * 2, 30); spawnInterval = Math.max(60 - waveNumber * 5, 20); snakeSpawnedThisWave = false; updateWave(); } function showRangeIndicator(x, y) { if (rangeIndicator) { rangeIndicator.destroy(); } rangeIndicator = LK.getAsset('rangeIndicator', { anchorX: 0.5, anchorY: 0.5, x: x, y: y, alpha: 0.3 }); var range = 450; if (selectedDefenderType === 'bird') { range = 450; } else if (selectedDefenderType === 'bear') { range = 300; } else if (selectedDefenderType === 'cat') { range = 400; } else if (selectedDefenderType === 'raccoon') { range = 350; } rangeIndicator.width = range * 2; rangeIndicator.height = range * 2; game.addChild(rangeIndicator); showingRange = true; } function hideRangeIndicator() { if (rangeIndicator) { rangeIndicator.destroy(); rangeIndicator = null; } showingRange = false; } game.down = function (x, y, obj) { // Check if clicked on a defender button for (var i = 0; i < defenderButtons.length; i++) { var button = defenderButtons[i]; var buttonCenterX = button.x; var buttonCenterY = button.y; var buttonWidth = 180; var buttonHeight = 240; // Including text area // Check if click is within button bounds if (x >= buttonCenterX - buttonWidth / 2 && x <= buttonCenterX + buttonWidth / 2 && y >= buttonCenterY - buttonHeight / 2 && y <= buttonCenterY + buttonHeight / 2) { // Check if player can afford this defender and hasn't reached limit if (money >= button.cost && defenderCounts[button.defenderType] < defenderLimits[button.defenderType]) { selectedDefenderType = button.defenderType; updateDefenderButtonStates(); } return; } } // Check if valid placement area (not too close to edges or meat) if (y < 150 || y > 2732 - 200) { return; } // Too close to top or bottom if (x < 100 || x > 2048 - 100) { return; } // Too close to sides // Check if too close to meat var distanceToMeat = Math.sqrt((x - meat.x) * (x - meat.x) + (y - meat.y) * (y - meat.y)); if (distanceToMeat < 150) { return; } // Too close to meat // Check if space is already occupied by another defender var spaceTaken = false; for (var i = 0; i < defenders.length; i++) { var defender = defenders[i]; var distance = Math.sqrt((x - defender.x) * (x - defender.x) + (y - defender.y) * (y - defender.y)); if (distance < 100) { spaceTaken = true; break; } } if (spaceTaken) { return; } // Check if player has enough money and hasn't reached limit var cost = defenderCosts[selectedDefenderType]; if (money < cost) { return; } if (defenderCounts[selectedDefenderType] >= defenderLimits[selectedDefenderType]) { return; } // Place defender var DefenderClass; if (selectedDefenderType === 'bird') { DefenderClass = Bird; } else if (selectedDefenderType === 'bear') { DefenderClass = Bear; } else if (selectedDefenderType === 'cat') { DefenderClass = Cat; } else if (selectedDefenderType === 'raccoon') { DefenderClass = Raccoon; } var newDefender = new DefenderClass(); newDefender.x = x; newDefender.y = y; newDefender.isPlaced = true; // Face defender towards meat if (x > meat.x) { // Right side - face left (flip) newDefender.scaleX = -1; } else { // Left side - face right (no flip) newDefender.scaleX = 1; } defenders.push(newDefender); game.addChild(newDefender); // Deduct money and increment count money -= cost; defenderCounts[selectedDefenderType]++; updateMoneyDisplay(); // Show range indicator briefly showRangeIndicator(x, y); LK.setTimeout(function () { hideRangeIndicator(); }, 1000); }; game.update = function () { if (enemiesSpawned < enemiesPerWave) { spawnTimer++; if (spawnTimer >= spawnInterval) { spawnEnemy(); spawnTimer = 0; } } for (var i = bullets.length - 1; i >= 0; i--) { var bullet = bullets[i]; if (bullet.isAtTarget) { bullet.destroy(); bullets.splice(i, 1); continue; } var hitEnemy = false; for (var j = 0; j < enemies.length; j++) { var enemy = enemies[j]; if (enemy.isDead) { continue; } if (bullet.intersects(enemy)) { enemy.takeDamage(bullet.damage); hitEnemy = true; break; } } if (hitEnemy) { bullet.destroy(); bullets.splice(i, 1); } } for (var i = enemies.length - 1; i >= 0; i--) { var enemy = enemies[i]; if (enemy.isDead) { enemy.destroy(); enemies.splice(i, 1); continue; } if (enemy.hasReachedMeat) { LK.getSound('enemyReachesMeat').play(); LK.effects.flashScreen(0xFF0000, 1000); LK.showGameOver(); return; } // Check for collision between enemy and defenders for (var j = defenders.length - 1; j >= 0; j--) { var defender = defenders[j]; if (enemy.intersects(defender)) { // Remove the defender and decrement count var defenderType = null; if (defender instanceof Bird) { defenderType = 'bird'; } else if (defender instanceof Cat) { defenderType = 'cat'; } else if (defender instanceof Raccoon) { defenderType = 'raccoon'; } else if (defender instanceof Bear) { defenderType = 'bear'; } if (defenderType) { defenderCounts[defenderType]--; } defender.destroy(); defenders.splice(j, 1); updateDefenderButtonStates(); break; // Exit inner loop since defender is destroyed } } } // Update all defenders so they can shoot for (var i = 0; i < defenders.length; i++) { var defender = defenders[i]; defender.update(); } if (enemiesSpawned >= enemiesPerWave && enemies.length === 0) { startNextWave(); } if (waveNumber > 10) { LK.showYouWin(); } };
===================================================================
--- original.js
+++ change.js
@@ -5,2675 +5,648 @@
/****
* Classes
****/
-var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) {
+var Bullet = Container.expand(function () {
var self = Container.call(this);
- self.targetEnemy = targetEnemy;
- self.damage = damage || 10;
- self.speed = speed || 5;
- self.x = startX;
- self.y = startY;
- var bulletGraphics = self.attachAsset('bullet', {
+ var graphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
+ self.speed = 8;
+ self.damage = 1;
+ self.targetX = 0;
+ self.targetY = 0;
self.update = function () {
- if (!self.targetEnemy || !self.targetEnemy.parent) {
- self.destroy();
- return;
- }
- var dx = self.targetEnemy.x - self.x;
- var dy = self.targetEnemy.y - self.y;
+ var dx = self.targetX - self.x;
+ var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.speed) {
- // Apply damage to target enemy
- self.targetEnemy.health -= self.damage;
- if (self.targetEnemy.health <= 0) {
- self.targetEnemy.health = 0;
- } else {
- self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70;
- }
- // Apply special effects based on bullet type
- if (self.type === 'splash') {
- // Create visual splash effect
- var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash');
- game.addChild(splashEffect);
- // Splash damage to nearby enemies
- var splashRadius = CELL_SIZE * 1.5;
- for (var i = 0; i < enemies.length; i++) {
- var otherEnemy = enemies[i];
- if (otherEnemy !== self.targetEnemy) {
- var splashDx = otherEnemy.x - self.targetEnemy.x;
- var splashDy = otherEnemy.y - self.targetEnemy.y;
- var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
- if (splashDistance <= splashRadius) {
- // Apply splash damage (50% of original damage)
- otherEnemy.health -= self.damage * 0.5;
- if (otherEnemy.health <= 0) {
- otherEnemy.health = 0;
- } else {
- otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70;
- }
- }
- }
- }
- } else if (self.type === 'slow') {
- // Prevent slow effect on immune enemies
- if (!self.targetEnemy.isImmune) {
- // Create visual slow effect
- var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow');
- game.addChild(slowEffect);
- // Apply slow effect
- // Make slow percentage scale with tower level (default 50%, up to 80% at max level)
- var slowPct = 0.5;
- if (self.sourceTowerLevel !== undefined) {
- // Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6
- var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8];
- var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1));
- slowPct = slowLevels[idx];
- }
- if (!self.targetEnemy.slowed) {
- self.targetEnemy.originalSpeed = self.targetEnemy.speed;
- self.targetEnemy.speed *= 1 - slowPct; // Slow by X%
- self.targetEnemy.slowed = true;
- self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS
- } else {
- self.targetEnemy.slowDuration = 180; // Reset duration
- }
- }
- } else if (self.type === 'poison') {
- // Prevent poison effect on immune enemies
- if (!self.targetEnemy.isImmune) {
- // Create visual poison effect
- var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
- game.addChild(poisonEffect);
- // Apply poison effect
- self.targetEnemy.poisoned = true;
- self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick
- self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS
- }
- } else if (self.type === 'sniper') {
- // Create visual critical hit effect for sniper
- var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper');
- game.addChild(sniperEffect);
- }
- self.destroy();
+ self.x = self.targetX;
+ self.y = self.targetY;
+ self.isAtTarget = true;
} else {
- var angle = Math.atan2(dy, dx);
- self.x += Math.cos(angle) * self.speed;
- self.y += Math.sin(angle) * self.speed;
+ self.x += dx / distance * self.speed;
+ self.y += dy / distance * self.speed;
}
};
return self;
});
-var DebugCell = Container.expand(function () {
+var Defender = Container.expand(function () {
var self = Container.call(this);
- var cellGraphics = self.attachAsset('cell', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- cellGraphics.tint = Math.random() * 0xffffff;
- var debugArrows = [];
- var numberLabel = new Text2('0', {
- size: 30,
- fill: 0xFFFFFF,
- weight: 800
- });
- numberLabel.anchor.set(.5, .5);
- self.addChild(numberLabel);
- self.update = function () {};
- self.down = function () {
- return;
- if (self.cell.type == 0 || self.cell.type == 1) {
- self.cell.type = self.cell.type == 1 ? 0 : 1;
- if (grid.pathFind()) {
- self.cell.type = self.cell.type == 1 ? 0 : 1;
- grid.pathFind();
- var notification = game.addChild(new Notification("Path is blocked!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- }
- grid.renderDebug();
- }
- };
- self.removeArrows = function () {
- while (debugArrows.length) {
- self.removeChild(debugArrows.pop());
- }
- };
- self.render = function (data) {
- switch (data.type) {
- case 0:
- case 2:
- {
- if (data.pathId != pathId) {
- self.removeArrows();
- numberLabel.setText("-");
- cellGraphics.tint = 0x880000;
- return;
- }
- numberLabel.visible = true;
- var tint = Math.floor(data.score / maxScore * 0x88);
- var towerInRangeHighlight = false;
- if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) {
- towerInRangeHighlight = true;
- cellGraphics.tint = 0x0088ff;
- } else {
- cellGraphics.tint = 0x88 - tint << 8 | tint;
- }
- while (debugArrows.length > data.targets.length) {
- self.removeChild(debugArrows.pop());
- }
- for (var a = 0; a < data.targets.length; a++) {
- var destination = data.targets[a];
- var ox = destination.x - data.x;
- var oy = destination.y - data.y;
- var angle = Math.atan2(oy, ox);
- if (!debugArrows[a]) {
- debugArrows[a] = LK.getAsset('arrow', {
- anchorX: -.5,
- anchorY: 0.5
- });
- debugArrows[a].alpha = .5;
- self.addChildAt(debugArrows[a], 1);
- }
- debugArrows[a].rotation = angle;
- }
- break;
- }
- case 1:
- {
- self.removeArrows();
- cellGraphics.tint = 0xaaaaaa;
- numberLabel.visible = false;
- break;
- }
- case 3:
- {
- self.removeArrows();
- cellGraphics.tint = 0x008800;
- numberLabel.visible = false;
- break;
- }
- }
- numberLabel.setText(Math.floor(data.score / 1000) / 10);
- };
-});
-// This update method was incorrectly placed here and should be removed
-var EffectIndicator = Container.expand(function (x, y, type) {
- var self = Container.call(this);
- self.x = x;
- self.y = y;
- var effectGraphics = self.attachAsset('rangeCircle', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- effectGraphics.blendMode = 1;
- switch (type) {
- case 'splash':
- effectGraphics.tint = 0x33CC00;
- effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5;
- break;
- case 'slow':
- effectGraphics.tint = 0x9900FF;
- effectGraphics.width = effectGraphics.height = CELL_SIZE;
- break;
- case 'poison':
- effectGraphics.tint = 0x00FFAA;
- effectGraphics.width = effectGraphics.height = CELL_SIZE;
- break;
- case 'sniper':
- effectGraphics.tint = 0xFF5500;
- effectGraphics.width = effectGraphics.height = CELL_SIZE;
- break;
- }
- effectGraphics.alpha = 0.7;
- self.alpha = 0;
- // Animate the effect
- tween(self, {
- alpha: 0.8,
- scaleX: 1.5,
- scaleY: 1.5
- }, {
- duration: 200,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(self, {
- alpha: 0,
- scaleX: 2,
- scaleY: 2
- }, {
- duration: 300,
- easing: tween.easeIn,
- onFinish: function onFinish() {
- self.destroy();
- }
- });
- }
- });
- return self;
-});
-// Base enemy class for common functionality
-var Enemy = Container.expand(function (type) {
- var self = Container.call(this);
- self.type = type || 'normal';
- self.speed = .01;
- self.cellX = 0;
- self.cellY = 0;
- self.currentCellX = 0;
- self.currentCellY = 0;
- self.currentTarget = undefined;
- self.maxHealth = 100;
- self.health = self.maxHealth;
- self.bulletsTargetingThis = [];
- self.waveNumber = currentWave;
- self.isFlying = false;
- self.isImmune = false;
- self.isBoss = false;
- // Check if this is a boss wave
- // Check if this is a boss wave
- // Apply different stats based on enemy type
- switch (self.type) {
- case 'fast':
- self.speed *= 2; // Twice as fast
- self.maxHealth = 100;
- break;
- case 'immune':
- self.isImmune = true;
- self.maxHealth = 80;
- break;
- case 'flying':
- self.isFlying = true;
- self.maxHealth = 80;
- break;
- case 'swarm':
- self.maxHealth = 50; // Weaker enemies
- break;
- case 'normal':
- default:
- // Normal enemy uses default values
- break;
- }
- if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') {
- self.isBoss = true;
- // Boss enemies have 20x health and are larger
- self.maxHealth *= 20;
- // Slower speed for bosses
- self.speed = self.speed * 0.7;
- }
- self.health = self.maxHealth;
- // Get appropriate asset for this enemy type
- var assetId = 'enemy';
- if (self.type !== 'normal') {
- assetId = 'enemy_' + self.type;
- }
- var enemyGraphics = self.attachAsset(assetId, {
- anchorX: 0.5,
- anchorY: 0.5
- });
- // Scale up boss enemies
- if (self.isBoss) {
- enemyGraphics.scaleX = 1.8;
- enemyGraphics.scaleY = 1.8;
- }
- // Fall back to regular enemy asset if specific type asset not found
- // Apply tint to differentiate enemy types
- /*switch (self.type) {
- case 'fast':
- enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies
- break;
- case 'immune':
- enemyGraphics.tint = 0xAA0000; // Red for immune enemies
- break;
- case 'flying':
- enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies
- break;
- case 'swarm':
- enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies
- break;
- }*/
- // Create shadow for flying enemies
- if (self.isFlying) {
- // Create a shadow container that will be added to the shadow layer
- self.shadow = new Container();
- // Clone the enemy graphics for the shadow
- var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- // Apply shadow effect
- shadowGraphics.tint = 0x000000; // Black shadow
- shadowGraphics.alpha = 0.4; // Semi-transparent
- // If this is a boss, scale up the shadow to match
- if (self.isBoss) {
- shadowGraphics.scaleX = 1.8;
- shadowGraphics.scaleY = 1.8;
- }
- // Position shadow slightly offset
- self.shadow.x = 20; // Offset right
- self.shadow.y = 20; // Offset down
- // Ensure shadow has the same rotation as the enemy
- shadowGraphics.rotation = enemyGraphics.rotation;
- }
- var healthBarOutline = self.attachAsset('healthBarOutline', {
- anchorX: 0,
- anchorY: 0.5
- });
- var healthBarBG = self.attachAsset('healthBar', {
- anchorX: 0,
- anchorY: 0.5
- });
- var healthBar = self.attachAsset('healthBar', {
- anchorX: 0,
- anchorY: 0.5
- });
- healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10;
- healthBarOutline.x = -healthBarOutline.width / 2;
- healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5;
- healthBar.tint = 0x00ff00;
- healthBarBG.tint = 0xff0000;
- self.healthBar = healthBar;
- self.update = function () {
- if (self.health <= 0) {
- self.health = 0;
- self.healthBar.width = 0;
- }
- // Handle slow effect
- if (self.isImmune) {
- // Immune enemies cannot be slowed or poisoned, clear any such effects
- self.slowed = false;
- self.slowEffect = false;
- self.poisoned = false;
- self.poisonEffect = false;
- // Reset speed to original if needed
- if (self.originalSpeed !== undefined) {
- self.speed = self.originalSpeed;
- }
- } else {
- // Handle slow effect
- if (self.slowed) {
- // Visual indication of slowed status
- if (!self.slowEffect) {
- self.slowEffect = true;
- }
- self.slowDuration--;
- if (self.slowDuration <= 0) {
- self.speed = self.originalSpeed;
- self.slowed = false;
- self.slowEffect = false;
- // Only reset tint if not poisoned
- if (!self.poisoned) {
- enemyGraphics.tint = 0xFFFFFF; // Reset tint
- }
- }
- }
- // Handle poison effect
- if (self.poisoned) {
- // Visual indication of poisoned status
- if (!self.poisonEffect) {
- self.poisonEffect = true;
- }
- // Apply poison damage every 30 frames (twice per second)
- if (LK.ticks % 30 === 0) {
- self.health -= self.poisonDamage;
- if (self.health <= 0) {
- self.health = 0;
- }
- self.healthBar.width = self.health / self.maxHealth * 70;
- }
- self.poisonDuration--;
- if (self.poisonDuration <= 0) {
- self.poisoned = false;
- self.poisonEffect = false;
- // Only reset tint if not slowed
- if (!self.slowed) {
- enemyGraphics.tint = 0xFFFFFF; // Reset tint
- }
- }
- }
- }
- // Set tint based on effect status
- if (self.isImmune) {
- enemyGraphics.tint = 0xFFFFFF;
- } else if (self.poisoned && self.slowed) {
- // Combine poison (0x00FFAA) and slow (0x9900FF) colors
- // Simple average: R: (0+153)/2=76, G: (255+0)/2=127, B: (170+255)/2=212
- enemyGraphics.tint = 0x4C7FD4;
- } else if (self.poisoned) {
- enemyGraphics.tint = 0x00FFAA;
- } else if (self.slowed) {
- enemyGraphics.tint = 0x9900FF;
- } else {
- enemyGraphics.tint = 0xFFFFFF;
- }
- if (self.currentTarget) {
- var ox = self.currentTarget.x - self.currentCellX;
- var oy = self.currentTarget.y - self.currentCellY;
- if (ox !== 0 || oy !== 0) {
- var angle = Math.atan2(oy, ox);
- if (enemyGraphics.targetRotation === undefined) {
- enemyGraphics.targetRotation = angle;
- enemyGraphics.rotation = angle;
- } else {
- if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) {
- tween.stop(enemyGraphics, {
- rotation: true
- });
- // Calculate the shortest angle to rotate
- var currentRotation = enemyGraphics.rotation;
- var angleDiff = angle - currentRotation;
- // Normalize angle difference to -PI to PI range for shortest path
- while (angleDiff > Math.PI) {
- angleDiff -= Math.PI * 2;
- }
- while (angleDiff < -Math.PI) {
- angleDiff += Math.PI * 2;
- }
- enemyGraphics.targetRotation = angle;
- tween(enemyGraphics, {
- rotation: currentRotation + angleDiff
- }, {
- duration: 250,
- easing: tween.easeOut
- });
- }
- }
- }
- }
- healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10;
- };
- return self;
-});
-var GoldIndicator = Container.expand(function (value, x, y) {
- var self = Container.call(this);
- var shadowText = new Text2("+" + value, {
- size: 45,
- fill: 0x000000,
- weight: 800
- });
- shadowText.anchor.set(0.5, 0.5);
- shadowText.x = 2;
- shadowText.y = 2;
- self.addChild(shadowText);
- var goldText = new Text2("+" + value, {
- size: 45,
- fill: 0xFFD700,
- weight: 800
- });
- goldText.anchor.set(0.5, 0.5);
- self.addChild(goldText);
- self.x = x;
- self.y = y;
- self.alpha = 0;
- self.scaleX = 0.5;
- self.scaleY = 0.5;
- tween(self, {
- alpha: 1,
- scaleX: 1.2,
- scaleY: 1.2,
- y: y - 40
- }, {
- duration: 50,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(self, {
- alpha: 0,
- scaleX: 1.5,
- scaleY: 1.5,
- y: y - 80
- }, {
- duration: 600,
- easing: tween.easeIn,
- delay: 800,
- onFinish: function onFinish() {
- self.destroy();
- }
- });
- }
- });
- return self;
-});
-var Grid = Container.expand(function (gridWidth, gridHeight) {
- var self = Container.call(this);
- self.cells = [];
- self.spawns = [];
- self.goals = [];
- for (var i = 0; i < gridWidth; i++) {
- self.cells[i] = [];
- for (var j = 0; j < gridHeight; j++) {
- self.cells[i][j] = {
- score: 0,
- pathId: 0,
- towersInRange: []
- };
- }
- }
- /*
- Cell Types
- 0: Transparent floor
- 1: Wall
- 2: Spawn
- 3: Goal
- */
- for (var i = 0; i < gridWidth; i++) {
- for (var j = 0; j < gridHeight; j++) {
- var cell = self.cells[i][j];
- var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0;
- if (i > 11 - 3 && i <= 11 + 3) {
- if (j === 0) {
- cellType = 2;
- self.spawns.push(cell);
- } else if (j <= 4) {
- cellType = 0;
- } else if (j === gridHeight - 1) {
- cellType = 3;
- self.goals.push(cell);
- } else if (j >= gridHeight - 4) {
- cellType = 0;
- }
- }
- cell.type = cellType;
- cell.x = i;
- cell.y = j;
- cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1];
- cell.up = self.cells[i - 1] && self.cells[i - 1][j];
- cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1];
- cell.left = self.cells[i][j - 1];
- cell.right = self.cells[i][j + 1];
- cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1];
- cell.down = self.cells[i + 1] && self.cells[i + 1][j];
- cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1];
- cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left];
- cell.targets = [];
- if (j > 3 && j <= gridHeight - 4) {
- var debugCell = new DebugCell();
- self.addChild(debugCell);
- debugCell.cell = cell;
- debugCell.x = i * CELL_SIZE;
- debugCell.y = j * CELL_SIZE;
- cell.debugCell = debugCell;
- }
- }
- }
- self.getCell = function (x, y) {
- return self.cells[x] && self.cells[x][y];
- };
- self.pathFind = function () {
- var before = new Date().getTime();
- var toProcess = self.goals.concat([]);
- maxScore = 0;
- pathId += 1;
- for (var a = 0; a < toProcess.length; a++) {
- toProcess[a].pathId = pathId;
- }
- function processNode(node, targetValue, targetNode) {
- if (node && node.type != 1) {
- if (node.pathId < pathId || targetValue < node.score) {
- node.targets = [targetNode];
- } else if (node.pathId == pathId && targetValue == node.score) {
- node.targets.push(targetNode);
- }
- if (node.pathId < pathId || targetValue < node.score) {
- node.score = targetValue;
- if (node.pathId != pathId) {
- toProcess.push(node);
- }
- node.pathId = pathId;
- if (targetValue > maxScore) {
- maxScore = targetValue;
- }
- }
- }
- }
- while (toProcess.length) {
- var nodes = toProcess;
- toProcess = [];
- for (var a = 0; a < nodes.length; a++) {
- var node = nodes[a];
- var targetScore = node.score + 14142;
- if (node.up && node.left && node.up.type != 1 && node.left.type != 1) {
- processNode(node.upLeft, targetScore, node);
- }
- if (node.up && node.right && node.up.type != 1 && node.right.type != 1) {
- processNode(node.upRight, targetScore, node);
- }
- if (node.down && node.right && node.down.type != 1 && node.right.type != 1) {
- processNode(node.downRight, targetScore, node);
- }
- if (node.down && node.left && node.down.type != 1 && node.left.type != 1) {
- processNode(node.downLeft, targetScore, node);
- }
- targetScore = node.score + 10000;
- processNode(node.up, targetScore, node);
- processNode(node.right, targetScore, node);
- processNode(node.down, targetScore, node);
- processNode(node.left, targetScore, node);
- }
- }
- for (var a = 0; a < self.spawns.length; a++) {
- if (self.spawns[a].pathId != pathId) {
- console.warn("Spawn blocked");
- return true;
- }
- }
- for (var a = 0; a < enemies.length; a++) {
- var enemy = enemies[a];
- // Skip enemies that haven't entered the viewable area yet
- if (enemy.currentCellY < 4) {
- continue;
- }
- // Skip flying enemies from path check as they can fly over obstacles
- if (enemy.isFlying) {
- continue;
- }
- var target = self.getCell(enemy.cellX, enemy.cellY);
- if (enemy.currentTarget) {
- if (enemy.currentTarget.pathId != pathId) {
- if (!target || target.pathId != pathId) {
- console.warn("Enemy blocked 1 ");
- return true;
- }
- }
- } else if (!target || target.pathId != pathId) {
- console.warn("Enemy blocked 2");
- return true;
- }
- }
- console.log("Speed", new Date().getTime() - before);
- };
- self.renderDebug = function () {
- for (var i = 0; i < gridWidth; i++) {
- for (var j = 0; j < gridHeight; j++) {
- var debugCell = self.cells[i][j].debugCell;
- if (debugCell) {
- debugCell.render(self.cells[i][j]);
- }
- }
- }
- };
- self.updateEnemy = function (enemy) {
- var cell = grid.getCell(enemy.cellX, enemy.cellY);
- if (cell.type == 3) {
- return true;
- }
- if (enemy.isFlying && enemy.shadow) {
- enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset
- enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset
- // Match shadow rotation with enemy rotation
- if (enemy.children[0] && enemy.shadow.children[0]) {
- enemy.shadow.children[0].rotation = enemy.children[0].rotation;
- }
- }
- // Check if the enemy has reached the entry area (y position is at least 5)
- var hasReachedEntryArea = enemy.currentCellY >= 4;
- // If enemy hasn't reached the entry area yet, just move down vertically
- if (!hasReachedEntryArea) {
- // Move directly downward
- enemy.currentCellY += enemy.speed;
- // Rotate enemy graphic to face downward (PI/2 radians = 90 degrees)
- var angle = Math.PI / 2;
- if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
- enemy.children[0].targetRotation = angle;
- enemy.children[0].rotation = angle;
- } else if (enemy.children[0]) {
- if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
- tween.stop(enemy.children[0], {
- rotation: true
- });
- // Calculate the shortest angle to rotate
- var currentRotation = enemy.children[0].rotation;
- var angleDiff = angle - currentRotation;
- // Normalize angle difference to -PI to PI range for shortest path
- while (angleDiff > Math.PI) {
- angleDiff -= Math.PI * 2;
- }
- while (angleDiff < -Math.PI) {
- angleDiff += Math.PI * 2;
- }
- // Set target rotation and animate to it
- enemy.children[0].targetRotation = angle;
- tween(enemy.children[0], {
- rotation: currentRotation + angleDiff
- }, {
- duration: 250,
- easing: tween.easeOut
- });
- }
- }
- // Update enemy's position
- enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
- enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
- // If enemy has now reached the entry area, update cell coordinates
- if (enemy.currentCellY >= 4) {
- enemy.cellX = Math.round(enemy.currentCellX);
- enemy.cellY = Math.round(enemy.currentCellY);
- }
- return false;
- }
- // After reaching entry area, handle flying enemies differently
- if (enemy.isFlying) {
- // Flying enemies head straight to the closest goal
- if (!enemy.flyingTarget) {
- // Set flying target to the closest goal
- enemy.flyingTarget = self.goals[0];
- // Find closest goal if there are multiple
- if (self.goals.length > 1) {
- var closestDist = Infinity;
- for (var i = 0; i < self.goals.length; i++) {
- var goal = self.goals[i];
- var dx = goal.x - enemy.cellX;
- var dy = goal.y - enemy.cellY;
- var dist = dx * dx + dy * dy;
- if (dist < closestDist) {
- closestDist = dist;
- enemy.flyingTarget = goal;
- }
- }
- }
- }
- // Move directly toward the goal
- var ox = enemy.flyingTarget.x - enemy.currentCellX;
- var oy = enemy.flyingTarget.y - enemy.currentCellY;
- var dist = Math.sqrt(ox * ox + oy * oy);
- if (dist < enemy.speed) {
- // Reached the goal
- return true;
- }
- var angle = Math.atan2(oy, ox);
- // Rotate enemy graphic to match movement direction
- if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
- enemy.children[0].targetRotation = angle;
- enemy.children[0].rotation = angle;
- } else if (enemy.children[0]) {
- if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
- tween.stop(enemy.children[0], {
- rotation: true
- });
- // Calculate the shortest angle to rotate
- var currentRotation = enemy.children[0].rotation;
- var angleDiff = angle - currentRotation;
- // Normalize angle difference to -PI to PI range for shortest path
- while (angleDiff > Math.PI) {
- angleDiff -= Math.PI * 2;
- }
- while (angleDiff < -Math.PI) {
- angleDiff += Math.PI * 2;
- }
- // Set target rotation and animate to it
- enemy.children[0].targetRotation = angle;
- tween(enemy.children[0], {
- rotation: currentRotation + angleDiff
- }, {
- duration: 250,
- easing: tween.easeOut
- });
- }
- }
- // Update the cell position to track where the flying enemy is
- enemy.cellX = Math.round(enemy.currentCellX);
- enemy.cellY = Math.round(enemy.currentCellY);
- enemy.currentCellX += Math.cos(angle) * enemy.speed;
- enemy.currentCellY += Math.sin(angle) * enemy.speed;
- enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
- enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
- // Update shadow position if this is a flying enemy
- return false;
- }
- // Handle normal pathfinding enemies
- if (!enemy.currentTarget) {
- enemy.currentTarget = cell.targets[0];
- }
- if (enemy.currentTarget) {
- if (cell.score < enemy.currentTarget.score) {
- enemy.currentTarget = cell;
- }
- var ox = enemy.currentTarget.x - enemy.currentCellX;
- var oy = enemy.currentTarget.y - enemy.currentCellY;
- var dist = Math.sqrt(ox * ox + oy * oy);
- if (dist < enemy.speed) {
- enemy.cellX = Math.round(enemy.currentCellX);
- enemy.cellY = Math.round(enemy.currentCellY);
- enemy.currentTarget = undefined;
- return;
- }
- var angle = Math.atan2(oy, ox);
- enemy.currentCellX += Math.cos(angle) * enemy.speed;
- enemy.currentCellY += Math.sin(angle) * enemy.speed;
- }
- enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
- enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
- };
-});
-var NextWaveButton = Container.expand(function () {
- var self = Container.call(this);
- var buttonBackground = self.attachAsset('notification', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- buttonBackground.width = 300;
- buttonBackground.height = 100;
- buttonBackground.tint = 0x0088FF;
- var buttonText = new Text2("Next Wave", {
- size: 50,
- fill: 0xFFFFFF,
- weight: 800
- });
- buttonText.anchor.set(0.5, 0.5);
- self.addChild(buttonText);
- self.enabled = false;
- self.visible = false;
- self.update = function () {
- if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) {
- self.enabled = true;
- self.visible = true;
- buttonBackground.tint = 0x0088FF;
- self.alpha = 1;
- } else {
- self.enabled = false;
- self.visible = false;
- buttonBackground.tint = 0x888888;
- self.alpha = 0.7;
- }
- };
- self.down = function () {
- if (!self.enabled) {
- return;
- }
- if (waveIndicator.gameStarted && currentWave < totalWaves) {
- currentWave++; // Increment to the next wave directly
- waveTimer = 0; // Reset wave timer
- waveInProgress = true;
- waveSpawned = false;
- // Get the type of the current wave (which is now the next wave)
- var waveType = waveIndicator.getWaveTypeName(currentWave);
- var enemyCount = waveIndicator.getEnemyCount(currentWave);
- var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 150;
- }
- };
- return self;
-});
-var Notification = Container.expand(function (message) {
- var self = Container.call(this);
- var notificationGraphics = self.attachAsset('notification', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- var notificationText = new Text2(message, {
- size: 50,
- fill: 0x000000,
- weight: 800
- });
- notificationText.anchor.set(0.5, 0.5);
- notificationGraphics.width = notificationText.width + 30;
- self.addChild(notificationText);
- self.alpha = 1;
- var fadeOutTime = 120;
- self.update = function () {
- if (fadeOutTime > 0) {
- fadeOutTime--;
- self.alpha = Math.min(fadeOutTime / 120 * 2, 1);
- } else {
- self.destroy();
- }
- };
- return self;
-});
-var SourceTower = Container.expand(function (towerType) {
- var self = Container.call(this);
- self.towerType = towerType || 'default';
- // Increase size of base for easier touch
- var baseGraphics = self.attachAsset('tower', {
- anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 1.3,
- scaleY: 1.3
- });
- switch (self.towerType) {
- case 'rapid':
- baseGraphics.tint = 0x00AAFF;
- break;
- case 'sniper':
- baseGraphics.tint = 0xFF5500;
- break;
- case 'splash':
- baseGraphics.tint = 0x33CC00;
- break;
- case 'slow':
- baseGraphics.tint = 0x9900FF;
- break;
- case 'poison':
- baseGraphics.tint = 0x00FFAA;
- break;
- default:
- baseGraphics.tint = 0xAAAAAA;
- }
- var towerCost = getTowerCost(self.towerType);
- // Add shadow for tower type label
- var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
- size: 50,
- fill: 0x000000,
- weight: 800
- });
- typeLabelShadow.anchor.set(0.5, 0.5);
- typeLabelShadow.x = 4;
- typeLabelShadow.y = -20 + 4;
- self.addChild(typeLabelShadow);
- // Add tower type label
- var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
- size: 50,
- fill: 0xFFFFFF,
- weight: 800
- });
- typeLabel.anchor.set(0.5, 0.5);
- typeLabel.y = -20; // Position above center of tower
- self.addChild(typeLabel);
- // Add cost shadow
- var costLabelShadow = new Text2(towerCost, {
- size: 50,
- fill: 0x000000,
- weight: 800
- });
- costLabelShadow.anchor.set(0.5, 0.5);
- costLabelShadow.x = 4;
- costLabelShadow.y = 24 + 12;
- self.addChild(costLabelShadow);
- // Add cost label
- var costLabel = new Text2(towerCost, {
- size: 50,
- fill: 0xFFD700,
- weight: 800
- });
- costLabel.anchor.set(0.5, 0.5);
- costLabel.y = 20 + 12;
- self.addChild(costLabel);
- self.update = function () {
- // Check if player can afford this tower
- var canAfford = gold >= getTowerCost(self.towerType);
- // Set opacity based on affordability
- self.alpha = canAfford ? 1 : 0.5;
- };
- return self;
-});
-var Tower = Container.expand(function (id) {
- var self = Container.call(this);
- self.id = id || 'default';
- self.level = 1;
- self.maxLevel = 6;
- self.gridX = 0;
- self.gridY = 0;
- self.range = 3 * CELL_SIZE;
- // Standardized method to get the current range of the tower
- self.getRange = function () {
- // Always calculate range based on tower type and level
- switch (self.id) {
- case 'sniper':
- // Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost
- if (self.level === self.maxLevel) {
- return 12 * CELL_SIZE; // Significantly increased range for max level
- }
- return (5 + (self.level - 1) * 0.8) * CELL_SIZE;
- case 'splash':
- // Splash: base 2, +0.2 per level (max ~4 blocks at max level)
- return (2 + (self.level - 1) * 0.2) * CELL_SIZE;
- case 'rapid':
- // Rapid: base 2.5, +0.5 per level
- return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE;
- case 'slow':
- // Slow: base 3.5, +0.5 per level
- return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE;
- case 'poison':
- // Poison: base 3.2, +0.5 per level
- return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
- default:
- // Default: base 3, +0.5 per level
- return (3 + (self.level - 1) * 0.5) * CELL_SIZE;
- }
- };
- self.cellsInRange = [];
+ self.range = 200;
self.fireRate = 60;
- self.bulletSpeed = 5;
- self.damage = 10;
- self.lastFired = 0;
- self.targetEnemy = null;
- switch (self.id) {
- case 'rapid':
- self.fireRate = 30;
- self.damage = 5;
- self.range = 2.5 * CELL_SIZE;
- self.bulletSpeed = 7;
- break;
- case 'sniper':
- self.fireRate = 90;
- self.damage = 25;
- self.range = 5 * CELL_SIZE;
- self.bulletSpeed = 25;
- break;
- case 'splash':
- self.fireRate = 75;
- self.damage = 15;
- self.range = 2 * CELL_SIZE;
- self.bulletSpeed = 4;
- break;
- case 'slow':
- self.fireRate = 50;
- self.damage = 8;
- self.range = 3.5 * CELL_SIZE;
- self.bulletSpeed = 5;
- break;
- case 'poison':
- self.fireRate = 70;
- self.damage = 12;
- self.range = 3.2 * CELL_SIZE;
- self.bulletSpeed = 5;
- break;
- }
- var baseGraphics = self.attachAsset('tower', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- switch (self.id) {
- case 'rapid':
- baseGraphics.tint = 0x00AAFF;
- break;
- case 'sniper':
- baseGraphics.tint = 0xFF5500;
- break;
- case 'splash':
- baseGraphics.tint = 0x33CC00;
- break;
- case 'slow':
- baseGraphics.tint = 0x9900FF;
- break;
- case 'poison':
- baseGraphics.tint = 0x00FFAA;
- break;
- default:
- baseGraphics.tint = 0xAAAAAA;
- }
- var levelIndicators = [];
- var maxDots = self.maxLevel;
- var dotSpacing = baseGraphics.width / (maxDots + 1);
- var dotSize = CELL_SIZE / 6;
- for (var i = 0; i < maxDots; i++) {
- var dot = new Container();
- var outlineCircle = dot.attachAsset('towerLevelIndicator', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- outlineCircle.width = dotSize + 4;
- outlineCircle.height = dotSize + 4;
- outlineCircle.tint = 0x000000;
- var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- towerLevelIndicator.width = dotSize;
- towerLevelIndicator.height = dotSize;
- towerLevelIndicator.tint = 0xCCCCCC;
- dot.x = -CELL_SIZE + dotSpacing * (i + 1);
- dot.y = CELL_SIZE * 0.7;
- self.addChild(dot);
- levelIndicators.push(dot);
- }
- var gunContainer = new Container();
- self.addChild(gunContainer);
- var gunGraphics = gunContainer.attachAsset('defense', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- self.updateLevelIndicators = function () {
- for (var i = 0; i < maxDots; i++) {
- var dot = levelIndicators[i];
- var towerLevelIndicator = dot.children[1];
- if (i < self.level) {
- towerLevelIndicator.tint = 0xFFFFFF;
- } else {
- switch (self.id) {
- case 'rapid':
- towerLevelIndicator.tint = 0x00AAFF;
- break;
- case 'sniper':
- towerLevelIndicator.tint = 0xFF5500;
- break;
- case 'splash':
- towerLevelIndicator.tint = 0x33CC00;
- break;
- case 'slow':
- towerLevelIndicator.tint = 0x9900FF;
- break;
- case 'poison':
- towerLevelIndicator.tint = 0x00FFAA;
- break;
- default:
- towerLevelIndicator.tint = 0xAAAAAA;
- }
- }
- }
+ self.lastFireTime = 0;
+ self.damage = 1;
+ self.isPlaced = false;
+ self.canFire = function () {
+ return LK.ticks - self.lastFireTime > self.fireRate;
};
- self.updateLevelIndicators();
- self.refreshCellsInRange = function () {
- for (var i = 0; i < self.cellsInRange.length; i++) {
- var cell = self.cellsInRange[i];
- var towerIndex = cell.towersInRange.indexOf(self);
- if (towerIndex !== -1) {
- cell.towersInRange.splice(towerIndex, 1);
- }
- }
- self.cellsInRange = [];
- var rangeRadius = self.getRange() / CELL_SIZE;
- var centerX = self.gridX + 1;
- var centerY = self.gridY + 1;
- var minI = Math.floor(centerX - rangeRadius - 0.5);
- var maxI = Math.ceil(centerX + rangeRadius + 0.5);
- var minJ = Math.floor(centerY - rangeRadius - 0.5);
- var maxJ = Math.ceil(centerY + rangeRadius + 0.5);
- for (var i = minI; i <= maxI; i++) {
- for (var j = minJ; j <= maxJ; j++) {
- var closestX = Math.max(i, Math.min(centerX, i + 1));
- var closestY = Math.max(j, Math.min(centerY, j + 1));
- var deltaX = closestX - centerX;
- var deltaY = closestY - centerY;
- var distanceSquared = deltaX * deltaX + deltaY * deltaY;
- if (distanceSquared <= rangeRadius * rangeRadius) {
- var cell = grid.getCell(i, j);
- if (cell) {
- self.cellsInRange.push(cell);
- cell.towersInRange.push(self);
- }
- }
- }
- }
- grid.renderDebug();
- };
- self.getTotalValue = function () {
- var baseTowerCost = getTowerCost(self.id);
- var totalInvestment = baseTowerCost;
- var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost
- for (var i = 1; i < self.level; i++) {
- totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1));
- }
- return totalInvestment;
- };
- self.upgrade = function () {
- if (self.level < self.maxLevel) {
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
- var baseUpgradeCost = getTowerCost(self.id);
- var upgradeCost;
- // Make last upgrade level extra expensive
- if (self.level === self.maxLevel - 1) {
- upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2); // Half the cost for final upgrade
- } else {
- upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1));
- }
- if (gold >= upgradeCost) {
- setGold(gold - upgradeCost);
- self.level++;
- // No need to update self.range here; getRange() is now the source of truth
- // Apply tower-specific upgrades based on type
- if (self.id === 'rapid') {
- if (self.level === self.maxLevel) {
- // Extra powerful last upgrade (double the effect)
- self.fireRate = Math.max(4, 30 - self.level * 9); // double the effect
- self.damage = 5 + self.level * 10; // double the effect
- self.bulletSpeed = 7 + self.level * 2.4; // double the effect
- } else {
- self.fireRate = Math.max(15, 30 - self.level * 3); // Fast tower gets faster with upgrades
- self.damage = 5 + self.level * 3;
- self.bulletSpeed = 7 + self.level * 0.7;
- }
- } else {
- if (self.level === self.maxLevel) {
- // Extra powerful last upgrade for all other towers (double the effect)
- self.fireRate = Math.max(5, 60 - self.level * 24); // double the effect
- self.damage = 10 + self.level * 20; // double the effect
- self.bulletSpeed = 5 + self.level * 2.4; // double the effect
- } else {
- self.fireRate = Math.max(20, 60 - self.level * 8);
- self.damage = 10 + self.level * 5;
- self.bulletSpeed = 5 + self.level * 0.5;
- }
- }
- self.refreshCellsInRange();
- self.updateLevelIndicators();
- if (self.level > 1) {
- var levelDot = levelIndicators[self.level - 1].children[1];
- tween(levelDot, {
- scaleX: 1.5,
- scaleY: 1.5
- }, {
- duration: 300,
- easing: tween.elasticOut,
- onFinish: function onFinish() {
- tween(levelDot, {
- scaleX: 1,
- scaleY: 1
- }, {
- duration: 200,
- easing: tween.easeOut
- });
- }
- });
- }
- return true;
- } else {
- var notification = game.addChild(new Notification("Not enough gold to upgrade!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- return false;
- }
- }
- return false;
- };
self.findTarget = function () {
var closestEnemy = null;
- var closestScore = Infinity;
+ var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
+ if (enemy.isDead) {
+ continue;
+ }
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
- // Check if enemy is in range
- if (distance <= self.getRange()) {
- // Handle flying enemies differently - they can be targeted regardless of path
- if (enemy.isFlying) {
- // For flying enemies, prioritize by distance to the goal
- if (enemy.flyingTarget) {
- var goalX = enemy.flyingTarget.x;
- var goalY = enemy.flyingTarget.y;
- var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY));
- // Use distance to goal as score
- if (distToGoal < closestScore) {
- closestScore = distToGoal;
- closestEnemy = enemy;
- }
- } else {
- // If no flying target yet (shouldn't happen), prioritize by distance to tower
- if (distance < closestScore) {
- closestScore = distance;
- closestEnemy = enemy;
- }
- }
- } else {
- // For ground enemies, use the original path-based targeting
- // Get the cell for this enemy
- var cell = grid.getCell(enemy.cellX, enemy.cellY);
- if (cell && cell.pathId === pathId) {
- // Use the cell's score (distance to exit) for prioritization
- // Lower score means closer to exit
- if (cell.score < closestScore) {
- closestScore = cell.score;
- closestEnemy = enemy;
- }
- }
- }
+ if (distance <= self.range && distance < closestDistance) {
+ closestEnemy = enemy;
+ closestDistance = distance;
}
}
- if (!closestEnemy) {
- self.targetEnemy = null;
- }
return closestEnemy;
};
- self.update = function () {
- self.targetEnemy = self.findTarget();
- if (self.targetEnemy) {
- var dx = self.targetEnemy.x - self.x;
- var dy = self.targetEnemy.y - self.y;
- var angle = Math.atan2(dy, dx);
- gunContainer.rotation = angle;
- if (LK.ticks - self.lastFired >= self.fireRate) {
- self.fire();
- self.lastFired = LK.ticks;
- }
+ self.shoot = function (target) {
+ if (!self.canFire()) {
+ return;
}
+ var bullet = new Bullet();
+ bullet.x = self.x;
+ bullet.y = self.y;
+ bullet.targetX = target.x;
+ bullet.targetY = target.y;
+ bullet.damage = self.damage;
+ bullets.push(bullet);
+ game.addChild(bullet);
+ self.lastFireTime = LK.ticks;
+ LK.getSound('shoot').play();
};
- self.down = function (x, y, obj) {
- var existingMenus = game.children.filter(function (child) {
- return child instanceof UpgradeMenu;
- });
- var hasOwnMenu = false;
- var rangeCircle = null;
- for (var i = 0; i < game.children.length; i++) {
- if (game.children[i].isTowerRange && game.children[i].tower === self) {
- rangeCircle = game.children[i];
- break;
- }
- }
- for (var i = 0; i < existingMenus.length; i++) {
- if (existingMenus[i].tower === self) {
- hasOwnMenu = true;
- break;
- }
- }
- if (hasOwnMenu) {
- for (var i = 0; i < existingMenus.length; i++) {
- if (existingMenus[i].tower === self) {
- hideUpgradeMenu(existingMenus[i]);
- }
- }
- if (rangeCircle) {
- game.removeChild(rangeCircle);
- }
- selectedTower = null;
- grid.renderDebug();
+ self.update = function () {
+ if (!self.isPlaced) {
return;
}
- for (var i = 0; i < existingMenus.length; i++) {
- existingMenus[i].destroy();
+ var target = self.findTarget();
+ if (target) {
+ self.shoot(target);
}
- for (var i = game.children.length - 1; i >= 0; i--) {
- if (game.children[i].isTowerRange) {
- game.removeChild(game.children[i]);
- }
- }
- selectedTower = self;
- var rangeIndicator = new Container();
- rangeIndicator.isTowerRange = true;
- rangeIndicator.tower = self;
- game.addChild(rangeIndicator);
- rangeIndicator.x = self.x;
- rangeIndicator.y = self.y;
- var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- rangeGraphics.width = rangeGraphics.height = self.getRange() * 2;
- rangeGraphics.alpha = 0.3;
- var upgradeMenu = new UpgradeMenu(self);
- game.addChild(upgradeMenu);
- upgradeMenu.x = 2048 / 2;
- tween(upgradeMenu, {
- y: 2732 - 225
- }, {
- duration: 200,
- easing: tween.backOut
- });
- grid.renderDebug();
};
- self.isInRange = function (enemy) {
- if (!enemy) {
- return false;
- }
- var dx = enemy.x - self.x;
- var dy = enemy.y - self.y;
- var distance = Math.sqrt(dx * dx + dy * dy);
- return distance <= self.getRange();
- };
- self.fire = function () {
- if (self.targetEnemy) {
- var potentialDamage = 0;
- for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) {
- potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage;
- }
- if (self.targetEnemy.health > potentialDamage) {
- var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
- var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
- var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed);
- // Set bullet type based on tower type
- bullet.type = self.id;
- // For slow tower, pass level for scaling slow effect
- if (self.id === 'slow') {
- bullet.sourceTowerLevel = self.level;
- }
- // Customize bullet appearance based on tower type
- switch (self.id) {
- case 'rapid':
- bullet.children[0].tint = 0x00AAFF;
- bullet.children[0].width = 20;
- bullet.children[0].height = 20;
- break;
- case 'sniper':
- bullet.children[0].tint = 0xFF5500;
- bullet.children[0].width = 15;
- bullet.children[0].height = 15;
- break;
- case 'splash':
- bullet.children[0].tint = 0x33CC00;
- bullet.children[0].width = 40;
- bullet.children[0].height = 40;
- break;
- case 'slow':
- bullet.children[0].tint = 0x9900FF;
- bullet.children[0].width = 35;
- bullet.children[0].height = 35;
- break;
- case 'poison':
- bullet.children[0].tint = 0x00FFAA;
- bullet.children[0].width = 35;
- bullet.children[0].height = 35;
- break;
- }
- game.addChild(bullet);
- bullets.push(bullet);
- self.targetEnemy.bulletsTargetingThis.push(bullet);
- // --- Fire recoil effect for gunContainer ---
- // Stop any ongoing recoil tweens before starting a new one
- tween.stop(gunContainer, {
- x: true,
- y: true,
- scaleX: true,
- scaleY: true
- });
- // Always use the original resting position for recoil, never accumulate offset
- if (gunContainer._restX === undefined) {
- gunContainer._restX = 0;
- }
- if (gunContainer._restY === undefined) {
- gunContainer._restY = 0;
- }
- if (gunContainer._restScaleX === undefined) {
- gunContainer._restScaleX = 1;
- }
- if (gunContainer._restScaleY === undefined) {
- gunContainer._restScaleY = 1;
- }
- // Reset to resting position before animating (in case of interrupted tweens)
- gunContainer.x = gunContainer._restX;
- gunContainer.y = gunContainer._restY;
- gunContainer.scaleX = gunContainer._restScaleX;
- gunContainer.scaleY = gunContainer._restScaleY;
- // Calculate recoil offset (recoil back along the gun's rotation)
- var recoilDistance = 8;
- var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance;
- var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance;
- // Animate recoil back from the resting position
- tween(gunContainer, {
- x: gunContainer._restX + recoilX,
- y: gunContainer._restY + recoilY
- }, {
- duration: 60,
- easing: tween.cubicOut,
- onFinish: function onFinish() {
- // Animate return to original position/scale
- tween(gunContainer, {
- x: gunContainer._restX,
- y: gunContainer._restY
- }, {
- duration: 90,
- easing: tween.cubicIn
- });
- }
- });
- }
- }
- };
- self.placeOnGrid = function (gridX, gridY) {
- self.gridX = gridX;
- self.gridY = gridY;
- self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
- self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
- for (var i = 0; i < 2; i++) {
- for (var j = 0; j < 2; j++) {
- var cell = grid.getCell(gridX + i, gridY + j);
- if (cell) {
- cell.type = 1;
- }
- }
- }
- self.refreshCellsInRange();
- };
return self;
});
-var TowerPreview = Container.expand(function () {
- var self = Container.call(this);
- var towerRange = 3;
- var rangeInPixels = towerRange * CELL_SIZE;
- self.towerType = 'default';
- self.hasEnoughGold = true;
- var rangeIndicator = new Container();
- self.addChild(rangeIndicator);
- var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
+var Raccoon = Defender.expand(function () {
+ var self = Defender.call(this);
+ var graphics = self.attachAsset('raccoon', {
anchorX: 0.5,
anchorY: 0.5
});
- rangeGraphics.alpha = 0.3;
- var previewGraphics = self.attachAsset('towerpreview', {
+ self.range = 350;
+ self.fireRate = 40;
+ self.damage = 2.5;
+ return self;
+});
+var Cat = Defender.expand(function () {
+ var self = Defender.call(this);
+ var graphics = self.attachAsset('cat', {
anchorX: 0.5,
anchorY: 0.5
});
- previewGraphics.width = CELL_SIZE * 2;
- previewGraphics.height = CELL_SIZE * 2;
- self.canPlace = false;
- self.gridX = 0;
- self.gridY = 0;
- self.blockedByEnemy = false;
- self.update = function () {
- var previousHasEnoughGold = self.hasEnoughGold;
- self.hasEnoughGold = gold >= getTowerCost(self.towerType);
- // Only update appearance if the affordability status has changed
- if (previousHasEnoughGold !== self.hasEnoughGold) {
- self.updateAppearance();
- }
- };
- self.updateAppearance = function () {
- // Use Tower class to get the source of truth for range
- var tempTower = new Tower(self.towerType);
- var previewRange = tempTower.getRange();
- // Clean up tempTower to avoid memory leaks
- if (tempTower && tempTower.destroy) {
- tempTower.destroy();
- }
- // Set range indicator using unified range logic
- rangeGraphics.width = rangeGraphics.height = previewRange * 2;
- switch (self.towerType) {
- case 'rapid':
- previewGraphics.tint = 0x00AAFF;
- break;
- case 'sniper':
- previewGraphics.tint = 0xFF5500;
- break;
- case 'splash':
- previewGraphics.tint = 0x33CC00;
- break;
- case 'slow':
- previewGraphics.tint = 0x9900FF;
- break;
- case 'poison':
- previewGraphics.tint = 0x00FFAA;
- break;
- default:
- previewGraphics.tint = 0xAAAAAA;
- }
- if (!self.canPlace || !self.hasEnoughGold) {
- previewGraphics.tint = 0xFF0000;
- }
- };
- self.updatePlacementStatus = function () {
- var validGridPlacement = true;
- if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) {
- validGridPlacement = false;
- } else {
- for (var i = 0; i < 2; i++) {
- for (var j = 0; j < 2; j++) {
- var cell = grid.getCell(self.gridX + i, self.gridY + j);
- if (!cell || cell.type !== 0) {
- validGridPlacement = false;
- break;
- }
- }
- if (!validGridPlacement) {
- break;
- }
- }
- }
- self.blockedByEnemy = false;
- if (validGridPlacement) {
- for (var i = 0; i < enemies.length; i++) {
- var enemy = enemies[i];
- if (enemy.currentCellY < 4) {
- continue;
- }
- // Only check non-flying enemies, flying enemies can pass over towers
- if (!enemy.isFlying) {
- if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
- self.blockedByEnemy = true;
- break;
- }
- if (enemy.currentTarget) {
- var targetX = enemy.currentTarget.x;
- var targetY = enemy.currentTarget.y;
- if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) {
- self.blockedByEnemy = true;
- break;
- }
- }
- }
- }
- }
- self.canPlace = validGridPlacement && !self.blockedByEnemy;
- self.hasEnoughGold = gold >= getTowerCost(self.towerType);
- self.updateAppearance();
- };
- self.checkPlacement = function () {
- self.updatePlacementStatus();
- };
- self.snapToGrid = function (x, y) {
- var gridPosX = x - grid.x;
- var gridPosY = y - grid.y;
- self.gridX = Math.floor(gridPosX / CELL_SIZE);
- self.gridY = Math.floor(gridPosY / CELL_SIZE);
- self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2;
- self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2;
- self.checkPlacement();
- };
+ self.range = 400;
+ self.fireRate = 50;
+ self.damage = 1.5;
return self;
});
-var UpgradeMenu = Container.expand(function (tower) {
- var self = Container.call(this);
- self.tower = tower;
- self.y = 2732 + 225;
- var menuBackground = self.attachAsset('notification', {
+var Bird = Defender.expand(function () {
+ var self = Defender.call(this);
+ var graphics = self.attachAsset('bird', {
anchorX: 0.5,
anchorY: 0.5
});
- menuBackground.width = 2048;
- menuBackground.height = 500;
- menuBackground.tint = 0x444444;
- menuBackground.alpha = 0.9;
- var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', {
- size: 80,
- fill: 0xFFFFFF,
- weight: 800
- });
- towerTypeText.anchor.set(0, 0);
- towerTypeText.x = -840;
- towerTypeText.y = -160;
- self.addChild(towerTypeText);
- var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', {
- size: 70,
- fill: 0xFFFFFF,
- weight: 400
- });
- statsText.anchor.set(0, 0.5);
- statsText.x = -840;
- statsText.y = 50;
- self.addChild(statsText);
- var buttonsContainer = new Container();
- buttonsContainer.x = 500;
- self.addChild(buttonsContainer);
- var upgradeButton = new Container();
- buttonsContainer.addChild(upgradeButton);
- var buttonBackground = upgradeButton.attachAsset('notification', {
+ self.range = 450;
+ self.fireRate = 80;
+ self.damage = 0.5;
+ return self;
+});
+var Bear = Defender.expand(function () {
+ var self = Defender.call(this);
+ var graphics = self.attachAsset('bear', {
anchorX: 0.5,
anchorY: 0.5
});
- buttonBackground.width = 500;
- buttonBackground.height = 150;
- var isMaxLevel = self.tower.level >= self.tower.maxLevel;
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
- var baseUpgradeCost = getTowerCost(self.tower.id);
- var upgradeCost;
- if (isMaxLevel) {
- upgradeCost = 0;
- } else if (self.tower.level === self.tower.maxLevel - 1) {
- upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
- } else {
- upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
- }
- buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888;
- var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', {
- size: 60,
- fill: 0xFFFFFF,
- weight: 800
- });
- buttonText.anchor.set(0.5, 0.5);
- upgradeButton.addChild(buttonText);
- var sellButton = new Container();
- buttonsContainer.addChild(sellButton);
- var sellButtonBackground = sellButton.attachAsset('notification', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- sellButtonBackground.width = 500;
- sellButtonBackground.height = 150;
- sellButtonBackground.tint = 0xCC0000;
- var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
- var sellValue = getTowerSellValue(totalInvestment);
- var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', {
- size: 60,
- fill: 0xFFFFFF,
- weight: 800
- });
- sellButtonText.anchor.set(0.5, 0.5);
- sellButton.addChild(sellButtonText);
- upgradeButton.y = -85;
- sellButton.y = 85;
- var closeButton = new Container();
- self.addChild(closeButton);
- var closeBackground = closeButton.attachAsset('notification', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- closeBackground.width = 90;
- closeBackground.height = 90;
- closeBackground.tint = 0xAA0000;
- var closeText = new Text2('X', {
- size: 68,
- fill: 0xFFFFFF,
- weight: 800
- });
- closeText.anchor.set(0.5, 0.5);
- closeButton.addChild(closeText);
- closeButton.x = menuBackground.width / 2 - 57;
- closeButton.y = -menuBackground.height / 2 + 57;
- upgradeButton.down = function (x, y, obj) {
- if (self.tower.level >= self.tower.maxLevel) {
- var notification = game.addChild(new Notification("Tower is already at max level!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- return;
+ self.range = 300;
+ self.fireRate = 70;
+ self.damage = 4;
+ return self;
+});
+var Enemy = Container.expand(function () {
+ var self = Container.call(this);
+ self.speed = 2;
+ self.health = 1;
+ self.maxHealth = 1;
+ self.isDead = false;
+ self.targetX = 0;
+ self.targetY = 0;
+ self.takeDamage = function (damage) {
+ self.health -= damage;
+ if (self.health <= 0) {
+ self.isDead = true;
+ LK.setScore(LK.getScore() + 10);
+ money += self.coinReward || 1;
+ updateMoneyDisplay();
+ LK.getSound('enemyHit').play();
}
- if (self.tower.upgrade()) {
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
- var baseUpgradeCost = getTowerCost(self.tower.id);
- if (self.tower.level >= self.tower.maxLevel) {
- upgradeCost = 0;
- } else if (self.tower.level === self.tower.maxLevel - 1) {
- upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
- } else {
- upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
- }
- statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s');
- buttonText.setText('Upgrade: ' + upgradeCost + ' gold');
- var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
- var sellValue = Math.floor(totalInvestment * 0.6);
- sellButtonText.setText('Sell: +' + sellValue + ' gold');
- if (self.tower.level >= self.tower.maxLevel) {
- buttonBackground.tint = 0x888888;
- buttonText.setText('Max Level');
- }
- var rangeCircle = null;
- for (var i = 0; i < game.children.length; i++) {
- if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
- rangeCircle = game.children[i];
- break;
- }
- }
- if (rangeCircle) {
- var rangeGraphics = rangeCircle.children[0];
- rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2;
- } else {
- var newRangeIndicator = new Container();
- newRangeIndicator.isTowerRange = true;
- newRangeIndicator.tower = self.tower;
- game.addChildAt(newRangeIndicator, 0);
- newRangeIndicator.x = self.tower.x;
- newRangeIndicator.y = self.tower.y;
- var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2;
- rangeGraphics.alpha = 0.3;
- }
- tween(self, {
- scaleX: 1.05,
- scaleY: 1.05
- }, {
- duration: 100,
- easing: tween.easeOut,
- onFinish: function onFinish() {
- tween(self, {
- scaleX: 1,
- scaleY: 1
- }, {
- duration: 100,
- easing: tween.easeIn
- });
- }
- });
- }
};
- sellButton.down = function (x, y, obj) {
- var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
- var sellValue = getTowerSellValue(totalInvestment);
- setGold(gold + sellValue);
- var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- var gridX = self.tower.gridX;
- var gridY = self.tower.gridY;
- for (var i = 0; i < 2; i++) {
- for (var j = 0; j < 2; j++) {
- var cell = grid.getCell(gridX + i, gridY + j);
- if (cell) {
- cell.type = 0;
- var towerIndex = cell.towersInRange.indexOf(self.tower);
- if (towerIndex !== -1) {
- cell.towersInRange.splice(towerIndex, 1);
- }
- }
- }
- }
- if (selectedTower === self.tower) {
- selectedTower = null;
- }
- var towerIndex = towers.indexOf(self.tower);
- if (towerIndex !== -1) {
- towers.splice(towerIndex, 1);
- }
- towerLayer.removeChild(self.tower);
- grid.pathFind();
- grid.renderDebug();
- self.destroy();
- for (var i = 0; i < game.children.length; i++) {
- if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
- game.removeChild(game.children[i]);
- break;
- }
- }
- };
- closeButton.down = function (x, y, obj) {
- hideUpgradeMenu(self);
- selectedTower = null;
- grid.renderDebug();
- };
self.update = function () {
- if (self.tower.level >= self.tower.maxLevel) {
- if (buttonText.text !== 'Max Level') {
- buttonText.setText('Max Level');
- buttonBackground.tint = 0x888888;
- }
+ if (self.isDead) {
return;
}
- // Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
- var baseUpgradeCost = getTowerCost(self.tower.id);
- var currentUpgradeCost;
- if (self.tower.level >= self.tower.maxLevel) {
- currentUpgradeCost = 0;
- } else if (self.tower.level === self.tower.maxLevel - 1) {
- currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
+ var dx = self.targetX - self.x;
+ var dy = self.targetY - self.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance < self.speed) {
+ self.x = self.targetX;
+ self.y = self.targetY;
+ self.hasReachedMeat = true;
} else {
- currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
+ self.x += dx / distance * self.speed;
+ self.y += dy / distance * self.speed;
}
- var canAfford = gold >= currentUpgradeCost;
- buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888;
- var newText = 'Upgrade: ' + currentUpgradeCost + ' gold';
- if (buttonText.text !== newText) {
- buttonText.setText(newText);
- }
};
return self;
});
-var WaveIndicator = Container.expand(function () {
- var self = Container.call(this);
- self.gameStarted = false;
- self.waveMarkers = [];
- self.waveTypes = [];
- self.enemyCounts = [];
- self.indicatorWidth = 0;
- self.lastBossType = null; // Track the last boss type to avoid repeating
- var blockWidth = 400;
- var totalBlocksWidth = blockWidth * totalWaves;
- var startMarker = new Container();
- var startBlock = startMarker.attachAsset('notification', {
+var Worm = Enemy.expand(function () {
+ var self = Enemy.call(this);
+ var graphics = self.attachAsset('worm', {
anchorX: 0.5,
anchorY: 0.5
});
- startBlock.width = blockWidth - 10;
- startBlock.height = 70 * 2;
- startBlock.tint = 0x00AA00;
- // Add shadow for start text
- var startTextShadow = new Text2("Start Game", {
- size: 50,
- fill: 0x000000,
- weight: 800
- });
- startTextShadow.anchor.set(0.5, 0.5);
- startTextShadow.x = 4;
- startTextShadow.y = 4;
- startMarker.addChild(startTextShadow);
- var startText = new Text2("Start Game", {
- size: 50,
- fill: 0xFFFFFF,
- weight: 800
- });
- startText.anchor.set(0.5, 0.5);
- startMarker.addChild(startText);
- startMarker.x = -self.indicatorWidth;
- self.addChild(startMarker);
- self.waveMarkers.push(startMarker);
- startMarker.down = function () {
- if (!self.gameStarted) {
- self.gameStarted = true;
- currentWave = 0;
- waveTimer = nextWaveTime;
- startBlock.tint = 0x00FF00;
- startText.setText("Started!");
- startTextShadow.setText("Started!");
- // Make sure shadow position remains correct after text change
- startTextShadow.x = 4;
- startTextShadow.y = 4;
- var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 150;
- }
- };
- for (var i = 0; i < totalWaves; i++) {
- var marker = new Container();
- var block = marker.attachAsset('notification', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- block.width = blockWidth - 10;
- block.height = 70 * 2;
- // --- Begin new unified wave logic ---
- var waveType = "normal";
- var enemyType = "normal";
- var enemyCount = 10;
- var isBossWave = (i + 1) % 10 === 0;
- // Ensure all types appear in early waves
- if (i === 0) {
- block.tint = 0xAAAAAA;
- waveType = "Normal";
- enemyType = "normal";
- enemyCount = 10;
- } else if (i === 1) {
- block.tint = 0x00AAFF;
- waveType = "Fast";
- enemyType = "fast";
- enemyCount = 10;
- } else if (i === 2) {
- block.tint = 0xAA0000;
- waveType = "Immune";
- enemyType = "immune";
- enemyCount = 10;
- } else if (i === 3) {
- block.tint = 0xFFFF00;
- waveType = "Flying";
- enemyType = "flying";
- enemyCount = 10;
- } else if (i === 4) {
- block.tint = 0xFF00FF;
- waveType = "Swarm";
- enemyType = "swarm";
- enemyCount = 30;
- } else if (isBossWave) {
- // Boss waves: cycle through all boss types, last boss is always flying
- var bossTypes = ['normal', 'fast', 'immune', 'flying'];
- var bossTypeIndex = Math.floor((i + 1) / 10) - 1;
- if (i === totalWaves - 1) {
- // Last boss is always flying
- enemyType = 'flying';
- waveType = "Boss Flying";
- block.tint = 0xFFFF00;
- } else {
- enemyType = bossTypes[bossTypeIndex % bossTypes.length];
- switch (enemyType) {
- case 'normal':
- block.tint = 0xAAAAAA;
- waveType = "Boss Normal";
- break;
- case 'fast':
- block.tint = 0x00AAFF;
- waveType = "Boss Fast";
- break;
- case 'immune':
- block.tint = 0xAA0000;
- waveType = "Boss Immune";
- break;
- case 'flying':
- block.tint = 0xFFFF00;
- waveType = "Boss Flying";
- break;
- }
- }
- enemyCount = 1;
- // Make the wave indicator for boss waves stand out
- // Set boss wave color to the color of the wave type
- switch (enemyType) {
- case 'normal':
- block.tint = 0xAAAAAA;
- break;
- case 'fast':
- block.tint = 0x00AAFF;
- break;
- case 'immune':
- block.tint = 0xAA0000;
- break;
- case 'flying':
- block.tint = 0xFFFF00;
- break;
- default:
- block.tint = 0xFF0000;
- break;
- }
- } else if ((i + 1) % 5 === 0) {
- // Every 5th non-boss wave is fast
- block.tint = 0x00AAFF;
- waveType = "Fast";
- enemyType = "fast";
- enemyCount = 10;
- } else if ((i + 1) % 4 === 0) {
- // Every 4th non-boss wave is immune
- block.tint = 0xAA0000;
- waveType = "Immune";
- enemyType = "immune";
- enemyCount = 10;
- } else if ((i + 1) % 7 === 0) {
- // Every 7th non-boss wave is flying
- block.tint = 0xFFFF00;
- waveType = "Flying";
- enemyType = "flying";
- enemyCount = 10;
- } else if ((i + 1) % 3 === 0) {
- // Every 3rd non-boss wave is swarm
- block.tint = 0xFF00FF;
- waveType = "Swarm";
- enemyType = "swarm";
- enemyCount = 30;
- } else {
- block.tint = 0xAAAAAA;
- waveType = "Normal";
- enemyType = "normal";
- enemyCount = 10;
- }
- // --- End new unified wave logic ---
- // Mark boss waves with a special visual indicator
- if (isBossWave && enemyType !== 'swarm') {
- // Add a crown or some indicator to the wave marker for boss waves
- var bossIndicator = marker.attachAsset('towerLevelIndicator', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- bossIndicator.width = 30;
- bossIndicator.height = 30;
- bossIndicator.tint = 0xFFD700; // Gold color
- bossIndicator.y = -block.height / 2 - 15;
- // Change the wave type text to indicate boss
- waveType = "BOSS";
- }
- // Store the wave type and enemy count
- self.waveTypes[i] = enemyType;
- self.enemyCounts[i] = enemyCount;
- // Add shadow for wave type - 30% smaller than before
- var waveTypeShadow = new Text2(waveType, {
- size: 56,
- fill: 0x000000,
- weight: 800
- });
- waveTypeShadow.anchor.set(0.5, 0.5);
- waveTypeShadow.x = 4;
- waveTypeShadow.y = 4;
- marker.addChild(waveTypeShadow);
- // Add wave type text - 30% smaller than before
- var waveTypeText = new Text2(waveType, {
- size: 56,
- fill: 0xFFFFFF,
- weight: 800
- });
- waveTypeText.anchor.set(0.5, 0.5);
- waveTypeText.y = 0;
- marker.addChild(waveTypeText);
- // Add shadow for wave number - 20% larger than before
- var waveNumShadow = new Text2((i + 1).toString(), {
- size: 48,
- fill: 0x000000,
- weight: 800
- });
- waveNumShadow.anchor.set(1.0, 1.0);
- waveNumShadow.x = blockWidth / 2 - 16 + 5;
- waveNumShadow.y = block.height / 2 - 12 + 5;
- marker.addChild(waveNumShadow);
- // Main wave number text - 20% larger than before
- var waveNum = new Text2((i + 1).toString(), {
- size: 48,
- fill: 0xFFFFFF,
- weight: 800
- });
- waveNum.anchor.set(1.0, 1.0);
- waveNum.x = blockWidth / 2 - 16;
- waveNum.y = block.height / 2 - 12;
- marker.addChild(waveNum);
- marker.x = -self.indicatorWidth + (i + 1) * blockWidth;
- self.addChild(marker);
- self.waveMarkers.push(marker);
- }
- // Get wave type for a specific wave number
- self.getWaveType = function (waveNumber) {
- if (waveNumber < 1 || waveNumber > totalWaves) {
- return "normal";
- }
- // If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType
- // then we should return a different boss type
- var waveType = self.waveTypes[waveNumber - 1];
- return waveType;
- };
- // Get enemy count for a specific wave number
- self.getEnemyCount = function (waveNumber) {
- if (waveNumber < 1 || waveNumber > totalWaves) {
- return 10;
- }
- return self.enemyCounts[waveNumber - 1];
- };
- // Get display name for a wave type
- self.getWaveTypeName = function (waveNumber) {
- var type = self.getWaveType(waveNumber);
- var typeName = type.charAt(0).toUpperCase() + type.slice(1);
- // Add boss prefix for boss waves (every 10th wave)
- if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') {
- typeName = "BOSS";
- }
- return typeName;
- };
- self.positionIndicator = new Container();
- var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', {
+ self.speed = 1.5;
+ self.health = 1;
+ self.maxHealth = 1;
+ self.coinReward = 1;
+ return self;
+});
+var Snake = Enemy.expand(function () {
+ var self = Enemy.call(this);
+ var graphics = self.attachAsset('snake', {
anchorX: 0.5,
anchorY: 0.5
});
- indicator.width = blockWidth - 10;
- indicator.height = 16;
- indicator.tint = 0xffad0e;
- indicator.y = -65;
- var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', {
+ self.speed = 1.8;
+ self.health = 3;
+ self.maxHealth = 3;
+ self.coinReward = 3;
+ return self;
+});
+var Mouse = Enemy.expand(function () {
+ var self = Enemy.call(this);
+ var graphics = self.attachAsset('mouse', {
anchorX: 0.5,
anchorY: 0.5
});
- indicator2.width = blockWidth - 10;
- indicator2.height = 16;
- indicator2.tint = 0xffad0e;
- indicator2.y = 65;
- var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
+ self.speed = 2.5;
+ self.health = 2;
+ self.maxHealth = 2;
+ self.coinReward = 2;
+ return self;
+});
+var Ant = Enemy.expand(function () {
+ var self = Enemy.call(this);
+ var graphics = self.attachAsset('ant', {
anchorX: 0.5,
anchorY: 0.5
});
- leftWall.width = 16;
- leftWall.height = 146;
- leftWall.tint = 0xffad0e;
- leftWall.x = -(blockWidth - 16) / 2;
- var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- rightWall.width = 16;
- rightWall.height = 146;
- rightWall.tint = 0xffad0e;
- rightWall.x = (blockWidth - 16) / 2;
- self.addChild(self.positionIndicator);
- self.update = function () {
- var progress = waveTimer / nextWaveTime;
- var moveAmount = (progress + currentWave) * blockWidth;
- for (var i = 0; i < self.waveMarkers.length; i++) {
- var marker = self.waveMarkers[i];
- marker.x = -moveAmount + i * blockWidth;
- }
- self.positionIndicator.x = 0;
- for (var i = 0; i < totalWaves + 1; i++) {
- var marker = self.waveMarkers[i];
- if (i === 0) {
- continue;
- }
- var block = marker.children[0];
- if (i - 1 < currentWave) {
- block.alpha = .5;
- }
- }
- self.handleWaveProgression = function () {
- if (!self.gameStarted) {
- return;
- }
- if (currentWave < totalWaves) {
- waveTimer++;
- if (waveTimer >= nextWaveTime) {
- waveTimer = 0;
- currentWave++;
- waveInProgress = true;
- waveSpawned = false;
- if (currentWave != 1) {
- var waveType = self.getWaveTypeName(currentWave);
- var enemyCount = self.getEnemyCount(currentWave);
- var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 150;
- }
- }
- }
- };
- self.handleWaveProgression();
- };
+ self.speed = 2;
+ self.health = 1;
+ self.maxHealth = 1;
+ self.coinReward = 1;
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
- backgroundColor: 0x333333
+ backgroundColor: 0x228B22
});
/****
* Game Code
****/
-var isHidingUpgradeMenu = false;
-function hideUpgradeMenu(menu) {
- if (isHidingUpgradeMenu) {
- return;
- }
- isHidingUpgradeMenu = true;
- tween(menu, {
- y: 2732 + 225
- }, {
- duration: 150,
- easing: tween.easeIn,
- onFinish: function onFinish() {
- menu.destroy();
- isHidingUpgradeMenu = false;
- }
- });
-}
-var CELL_SIZE = 76;
-var pathId = 1;
-var maxScore = 0;
+var grassBackground = game.addChild(LK.getAsset('grass', {
+ anchorX: 0,
+ anchorY: 0,
+ x: 0,
+ y: 0
+}));
+var meat = game.addChild(LK.getAsset('meat', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: 1024,
+ y: 1366,
+ scaleX: 1.5,
+ scaleY: 1.5
+}));
var enemies = [];
-var towers = [];
+var defenders = [];
var bullets = [];
-var defenses = [];
-var selectedTower = null;
-var gold = 80;
-var lives = 20;
-var score = 0;
-var currentWave = 0;
-var totalWaves = 50;
-var waveTimer = 0;
-var waveInProgress = false;
-var waveSpawned = false;
-var nextWaveTime = 12000 / 2;
-var sourceTower = null;
-var enemiesToSpawn = 10; // Default number of enemies per wave
-var goldText = new Text2('Gold: ' + gold, {
- size: 60,
- fill: 0xFFD700,
- weight: 800
+var money = 7;
+var selectedDefenderType = 'bird';
+var rangeIndicator = null;
+var showingRange = false;
+var waveNumber = 1;
+var enemiesSpawned = 0;
+var enemiesPerWave = 5;
+var spawnTimer = 0;
+var spawnInterval = 120;
+var defenderCounts = {
+ 'bird': 0,
+ 'cat': 0,
+ 'raccoon': 0,
+ 'bear': 0
+};
+var defenderLimits = {
+ 'bird': 5,
+ 'cat': 4,
+ 'raccoon': 3,
+ 'bear': 2
+};
+var waveTxt = new Text2('Wave: 1', {
+ size: 50,
+ fill: 0xFFFFFF
});
-goldText.anchor.set(0.5, 0.5);
-var livesText = new Text2('Lives: ' + lives, {
- size: 60,
- fill: 0x00FF00,
- weight: 800
+waveTxt.anchor.set(0, 0);
+waveTxt.x = 200;
+waveTxt.y = 100;
+LK.gui.topLeft.addChild(waveTxt);
+var moneyTxt = new Text2('Money: 7', {
+ size: 50,
+ fill: 0xFFD700
});
-livesText.anchor.set(0.5, 0.5);
-var scoreText = new Text2('Score: ' + score, {
- size: 60,
- fill: 0xFF0000,
- weight: 800
-});
-scoreText.anchor.set(0.5, 0.5);
-var topMargin = 50;
-var centerX = 2048 / 2;
-var spacing = 400;
-LK.gui.top.addChild(goldText);
-LK.gui.top.addChild(livesText);
-LK.gui.top.addChild(scoreText);
-livesText.x = 0;
-livesText.y = topMargin;
-goldText.x = -spacing;
-goldText.y = topMargin;
-scoreText.x = spacing;
-scoreText.y = topMargin;
-function updateUI() {
- goldText.setText('Gold: ' + gold);
- livesText.setText('Lives: ' + lives);
- scoreText.setText('Score: ' + score);
+moneyTxt.anchor.set(0, 0);
+moneyTxt.x = 200;
+moneyTxt.y = 160;
+LK.gui.topLeft.addChild(moneyTxt);
+var selectedDefenderType = 'bird';
+var defenderTypes = ['bird', 'cat', 'raccoon', 'bear'];
+var defenderCosts = {
+ 'bird': 1,
+ 'cat': 4,
+ 'raccoon': 8,
+ 'bear': 12
+};
+var defenderButtons = [];
+function updateWave() {
+ waveTxt.setText('Wave: ' + waveNumber);
}
-function setGold(value) {
- gold = value;
- updateUI();
+function updateMoneyDisplay() {
+ moneyTxt.setText('Money: ' + money);
+ updateDefenderButtonStates();
}
-var debugLayer = new Container();
-var towerLayer = new Container();
-// Create three separate layers for enemy hierarchy
-var enemyLayerBottom = new Container(); // For normal enemies
-var enemyLayerMiddle = new Container(); // For shadows
-var enemyLayerTop = new Container(); // For flying enemies
-var enemyLayer = new Container(); // Main container to hold all enemy layers
-// Add layers in correct order (bottom first, then middle for shadows, then top)
-enemyLayer.addChild(enemyLayerBottom);
-enemyLayer.addChild(enemyLayerMiddle);
-enemyLayer.addChild(enemyLayerTop);
-var grid = new Grid(24, 29 + 6);
-grid.x = 150;
-grid.y = 200 - CELL_SIZE * 4;
-grid.pathFind();
-grid.renderDebug();
-debugLayer.addChild(grid);
-game.addChild(debugLayer);
-game.addChild(towerLayer);
-game.addChild(enemyLayer);
-var offset = 0;
-var towerPreview = new TowerPreview();
-game.addChild(towerPreview);
-towerPreview.visible = false;
-var isDragging = false;
-function wouldBlockPath(gridX, gridY) {
- var cells = [];
- for (var i = 0; i < 2; i++) {
- for (var j = 0; j < 2; j++) {
- var cell = grid.getCell(gridX + i, gridY + j);
- if (cell) {
- cells.push({
- cell: cell,
- originalType: cell.type
- });
- cell.type = 1;
- }
+function createDefenderButtons() {
+ var buttonSize = 180;
+ var spacing = 60;
+ var totalWidth = defenderTypes.length * buttonSize + (defenderTypes.length - 1) * spacing;
+ var startX = (2048 - totalWidth) / 2;
+ var y = 2732 - buttonSize - 50;
+ for (var i = 0; i < defenderTypes.length; i++) {
+ var defenderType = defenderTypes[i];
+ var cost = defenderCosts[defenderType];
+ // Create button container
+ var button = new Container();
+ button.defenderType = defenderType;
+ button.cost = cost;
+ button.x = startX + i * (buttonSize + spacing);
+ button.y = y;
+ button.width = buttonSize;
+ button.height = buttonSize + 60; // Extra height for text
+ // Background removed for cleaner look
+ // Add defender image
+ var defenderImage = LK.getAsset(defenderType, {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 0.6,
+ scaleY: 0.6
+ });
+ button.addChild(defenderImage);
+ // Add cost text
+ var costText = new Text2('$' + cost, {
+ size: 60,
+ fill: 0xFFD700
+ });
+ costText.anchor.set(0.5, 0);
+ costText.x = 0;
+ costText.y = buttonSize / 2 + 15;
+ button.addChild(costText);
+ button.costText = costText;
+ // Add count/limit text
+ var countText = new Text2('0/' + defenderLimits[defenderType], {
+ size: 40,
+ fill: 0xFFFFFF
+ });
+ countText.anchor.set(0.5, 0);
+ countText.x = 0;
+ countText.y = buttonSize / 2 + 75;
+ button.addChild(countText);
+ button.countText = countText;
+ // Add selection indicator
+ var selectionIndicator = LK.getAsset('rangeIndicator', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ scaleX: 0.7,
+ scaleY: 0.7,
+ alpha: 0.6
+ });
+ selectionIndicator.visible = false;
+ button.addChild(selectionIndicator);
+ button.selectionIndicator = selectionIndicator;
+ defenderButtons.push(button);
+ game.addChild(button);
+ }
+ updateDefenderButtonStates();
+}
+function updateDefenderButtonStates() {
+ for (var i = 0; i < defenderButtons.length; i++) {
+ var button = defenderButtons[i];
+ var canAfford = money >= button.cost;
+ var isSelected = button.defenderType === selectedDefenderType;
+ var hasReachedLimit = defenderCounts[button.defenderType] >= defenderLimits[button.defenderType];
+ // Update affordability colors and limits
+ if (hasReachedLimit) {
+ button.costText.fill = 0x888888; // Gray for limit reached
+ button.alpha = 0.3;
+ } else {
+ button.costText.fill = canAfford ? 0xFFD700 : 0xFF0000;
+ button.alpha = canAfford ? 1.0 : 0.5;
}
+ // Update selection indicator
+ button.selectionIndicator.visible = isSelected;
+ // Update count text
+ button.countText.setText(defenderCounts[button.defenderType] + '/' + defenderLimits[button.defenderType]);
}
- var blocked = grid.pathFind();
- for (var i = 0; i < cells.length; i++) {
- cells[i].cell.type = cells[i].originalType;
- }
- grid.pathFind();
- grid.renderDebug();
- return blocked;
}
-function getTowerCost(towerType) {
- var cost = 5;
- switch (towerType) {
- case 'rapid':
- cost = 15;
- break;
- case 'sniper':
- cost = 25;
- break;
- case 'splash':
- cost = 35;
- break;
- case 'slow':
- cost = 45;
- break;
- case 'poison':
- cost = 55;
- break;
+createDefenderButtons();
+var snakeSpawnedThisWave = false;
+function spawnEnemy() {
+ var enemyTypes = [Worm, Ant, Mouse];
+ if (waveNumber < 5) {
+ // Before wave 5: Snake can spawn once per wave
+ if (!snakeSpawnedThisWave && Math.random() < 0.2) {
+ enemyTypes.push(Snake);
+ }
+ } else {
+ // Wave 5 and after: Snake can spawn unlimited
+ enemyTypes.push(Snake);
}
- return cost;
+ var EnemyClass = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
+ if (EnemyClass === Snake && waveNumber < 5) {
+ snakeSpawnedThisWave = true;
+ }
+ var enemy = new EnemyClass();
+ // Boost enemy health after wave 5
+ if (waveNumber > 5) {
+ var healthBoost = Math.floor((waveNumber - 5) / 2) + 1; // +1 HP every 2 waves after wave 5
+ enemy.health += healthBoost;
+ enemy.maxHealth += healthBoost;
+ }
+ var spawnSides = [{
+ x: 0,
+ y: Math.random() * 2732,
+ flipX: 1 // Left side - face right (flip)
+ }, {
+ x: 2048,
+ y: Math.random() * 2732,
+ flipX: 0 // Right side - face left (no flip)
+ }, {
+ x: Math.random() * 2048,
+ y: 0,
+ flipX: 0 // Top side - no flip
+ }, {
+ x: Math.random() * 2048,
+ y: 2732,
+ flipX: 0 // Bottom side - no flip
+ }];
+ var spawn = spawnSides[Math.floor(Math.random() * spawnSides.length)];
+ enemy.x = spawn.x;
+ enemy.y = spawn.y;
+ enemy.targetX = meat.x;
+ enemy.targetY = meat.y;
+ // Apply horizontal flip based on spawn side and position relative to meat
+ if (spawn.flipX === 1) {
+ enemy.scaleX = -1;
+ } else if (spawn.x === 0 || spawn.x === 2048) {
+ // Left or right spawn - already handled above
+ } else {
+ // Top or bottom spawn - face towards meat horizontally
+ if (enemy.x > meat.x) {
+ // Enemy is to the right of meat - face left
+ enemy.scaleX = -1;
+ } else {
+ // Enemy is to the left of meat - face right
+ enemy.scaleX = 1;
+ }
+ }
+ enemies.push(enemy);
+ game.addChild(enemy);
+ enemiesSpawned++;
}
-function getTowerSellValue(totalValue) {
- return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue;
+function startNextWave() {
+ waveNumber++;
+ enemiesSpawned = 0;
+ enemiesPerWave = Math.min((5 + Math.floor(waveNumber / 2)) * 2, 30);
+ spawnInterval = Math.max(60 - waveNumber * 5, 20);
+ snakeSpawnedThisWave = false;
+ updateWave();
}
-function placeTower(gridX, gridY, towerType) {
- var towerCost = getTowerCost(towerType);
- if (gold >= towerCost) {
- var tower = new Tower(towerType || 'default');
- tower.placeOnGrid(gridX, gridY);
- towerLayer.addChild(tower);
- towers.push(tower);
- setGold(gold - towerCost);
- grid.pathFind();
- grid.renderDebug();
- return true;
- } else {
- var notification = game.addChild(new Notification("Not enough gold!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- return false;
+function showRangeIndicator(x, y) {
+ if (rangeIndicator) {
+ rangeIndicator.destroy();
}
+ rangeIndicator = LK.getAsset('rangeIndicator', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ x: x,
+ y: y,
+ alpha: 0.3
+ });
+ var range = 450;
+ if (selectedDefenderType === 'bird') {
+ range = 450;
+ } else if (selectedDefenderType === 'bear') {
+ range = 300;
+ } else if (selectedDefenderType === 'cat') {
+ range = 400;
+ } else if (selectedDefenderType === 'raccoon') {
+ range = 350;
+ }
+ rangeIndicator.width = range * 2;
+ rangeIndicator.height = range * 2;
+ game.addChild(rangeIndicator);
+ showingRange = true;
}
+function hideRangeIndicator() {
+ if (rangeIndicator) {
+ rangeIndicator.destroy();
+ rangeIndicator = null;
+ }
+ showingRange = false;
+}
game.down = function (x, y, obj) {
- var upgradeMenuVisible = game.children.some(function (child) {
- return child instanceof UpgradeMenu;
- });
- if (upgradeMenuVisible) {
- return;
+ // Check if clicked on a defender button
+ for (var i = 0; i < defenderButtons.length; i++) {
+ var button = defenderButtons[i];
+ var buttonCenterX = button.x;
+ var buttonCenterY = button.y;
+ var buttonWidth = 180;
+ var buttonHeight = 240; // Including text area
+ // Check if click is within button bounds
+ if (x >= buttonCenterX - buttonWidth / 2 && x <= buttonCenterX + buttonWidth / 2 && y >= buttonCenterY - buttonHeight / 2 && y <= buttonCenterY + buttonHeight / 2) {
+ // Check if player can afford this defender and hasn't reached limit
+ if (money >= button.cost && defenderCounts[button.defenderType] < defenderLimits[button.defenderType]) {
+ selectedDefenderType = button.defenderType;
+ updateDefenderButtonStates();
+ }
+ return;
+ }
}
- for (var i = 0; i < sourceTowers.length; i++) {
- var tower = sourceTowers[i];
- if (x >= tower.x - tower.width / 2 && x <= tower.x + tower.width / 2 && y >= tower.y - tower.height / 2 && y <= tower.y + tower.height / 2) {
- towerPreview.visible = true;
- isDragging = true;
- towerPreview.towerType = tower.towerType;
- towerPreview.updateAppearance();
- // Apply the same offset as in move handler to ensure consistency when starting drag
- towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
+ // Check if valid placement area (not too close to edges or meat)
+ if (y < 150 || y > 2732 - 200) {
+ return;
+ } // Too close to top or bottom
+ if (x < 100 || x > 2048 - 100) {
+ return;
+ } // Too close to sides
+ // Check if too close to meat
+ var distanceToMeat = Math.sqrt((x - meat.x) * (x - meat.x) + (y - meat.y) * (y - meat.y));
+ if (distanceToMeat < 150) {
+ return;
+ } // Too close to meat
+ // Check if space is already occupied by another defender
+ var spaceTaken = false;
+ for (var i = 0; i < defenders.length; i++) {
+ var defender = defenders[i];
+ var distance = Math.sqrt((x - defender.x) * (x - defender.x) + (y - defender.y) * (y - defender.y));
+ if (distance < 100) {
+ spaceTaken = true;
break;
}
}
-};
-game.move = function (x, y, obj) {
- if (isDragging) {
- // Shift the y position upward by 1.5 tiles to show preview above finger
- towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
+ if (spaceTaken) {
+ return;
}
-};
-game.up = function (x, y, obj) {
- var clickedOnTower = false;
- for (var i = 0; i < towers.length; i++) {
- var tower = towers[i];
- var towerLeft = tower.x - tower.width / 2;
- var towerRight = tower.x + tower.width / 2;
- var towerTop = tower.y - tower.height / 2;
- var towerBottom = tower.y + tower.height / 2;
- if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
- clickedOnTower = true;
- break;
- }
+ // Check if player has enough money and hasn't reached limit
+ var cost = defenderCosts[selectedDefenderType];
+ if (money < cost) {
+ return;
}
- var upgradeMenus = game.children.filter(function (child) {
- return child instanceof UpgradeMenu;
- });
- if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) {
- var clickedOnMenu = false;
- for (var i = 0; i < upgradeMenus.length; i++) {
- var menu = upgradeMenus[i];
- var menuWidth = 2048;
- var menuHeight = 450;
- var menuLeft = menu.x - menuWidth / 2;
- var menuRight = menu.x + menuWidth / 2;
- var menuTop = menu.y - menuHeight / 2;
- var menuBottom = menu.y + menuHeight / 2;
- if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) {
- clickedOnMenu = true;
- break;
- }
- }
- if (!clickedOnMenu) {
- for (var i = 0; i < upgradeMenus.length; i++) {
- var menu = upgradeMenus[i];
- hideUpgradeMenu(menu);
- }
- for (var i = game.children.length - 1; i >= 0; i--) {
- if (game.children[i].isTowerRange) {
- game.removeChild(game.children[i]);
- }
- }
- selectedTower = null;
- grid.renderDebug();
- }
+ if (defenderCounts[selectedDefenderType] >= defenderLimits[selectedDefenderType]) {
+ return;
}
- if (isDragging) {
- isDragging = false;
- if (towerPreview.canPlace) {
- if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) {
- placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType);
- } else {
- var notification = game.addChild(new Notification("Tower would block the path!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- }
- } else if (towerPreview.blockedByEnemy) {
- var notification = game.addChild(new Notification("Cannot build: Enemy in the way!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- } else if (towerPreview.visible) {
- var notification = game.addChild(new Notification("Cannot build here!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 50;
- }
- towerPreview.visible = false;
- if (isDragging) {
- var upgradeMenus = game.children.filter(function (child) {
- return child instanceof UpgradeMenu;
- });
- for (var i = 0; i < upgradeMenus.length; i++) {
- upgradeMenus[i].destroy();
- }
- }
+ // Place defender
+ var DefenderClass;
+ if (selectedDefenderType === 'bird') {
+ DefenderClass = Bird;
+ } else if (selectedDefenderType === 'bear') {
+ DefenderClass = Bear;
+ } else if (selectedDefenderType === 'cat') {
+ DefenderClass = Cat;
+ } else if (selectedDefenderType === 'raccoon') {
+ DefenderClass = Raccoon;
}
+ var newDefender = new DefenderClass();
+ newDefender.x = x;
+ newDefender.y = y;
+ newDefender.isPlaced = true;
+ // Face defender towards meat
+ if (x > meat.x) {
+ // Right side - face left (flip)
+ newDefender.scaleX = -1;
+ } else {
+ // Left side - face right (no flip)
+ newDefender.scaleX = 1;
+ }
+ defenders.push(newDefender);
+ game.addChild(newDefender);
+ // Deduct money and increment count
+ money -= cost;
+ defenderCounts[selectedDefenderType]++;
+ updateMoneyDisplay();
+ // Show range indicator briefly
+ showRangeIndicator(x, y);
+ LK.setTimeout(function () {
+ hideRangeIndicator();
+ }, 1000);
};
-var waveIndicator = new WaveIndicator();
-waveIndicator.x = 2048 / 2;
-waveIndicator.y = 2732 - 80;
-game.addChild(waveIndicator);
-var nextWaveButtonContainer = new Container();
-var nextWaveButton = new NextWaveButton();
-nextWaveButton.x = 2048 - 200;
-nextWaveButton.y = 2732 - 100 + 20;
-nextWaveButtonContainer.addChild(nextWaveButton);
-game.addChild(nextWaveButtonContainer);
-var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison'];
-var sourceTowers = [];
-var towerSpacing = 300; // Increase spacing for larger towers
-var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2;
-var towerY = 2732 - CELL_SIZE * 3 - 90;
-for (var i = 0; i < towerTypes.length; i++) {
- var tower = new SourceTower(towerTypes[i]);
- tower.x = startX + i * towerSpacing;
- tower.y = towerY;
- towerLayer.addChild(tower);
- sourceTowers.push(tower);
-}
-sourceTower = null;
-enemiesToSpawn = 10;
game.update = function () {
- if (waveInProgress) {
- if (!waveSpawned) {
- waveSpawned = true;
- // Get wave type and enemy count from the wave indicator
- var waveType = waveIndicator.getWaveType(currentWave);
- var enemyCount = waveIndicator.getEnemyCount(currentWave);
- // Check if this is a boss wave
- var isBossWave = currentWave % 10 === 0 && currentWave > 0;
- if (isBossWave && waveType !== 'swarm') {
- // Boss waves have just 1 enemy regardless of what the wave indicator says
- enemyCount = 1;
- // Show boss announcement
- var notification = game.addChild(new Notification("ā ļø BOSS WAVE! ā ļø"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 200;
- }
- // Spawn the appropriate number of enemies
- for (var i = 0; i < enemyCount; i++) {
- var enemy = new Enemy(waveType);
- // Add enemy to the appropriate layer based on type
- if (enemy.isFlying) {
- // Add flying enemy to the top layer
- enemyLayerTop.addChild(enemy);
- // If it's a flying enemy, add its shadow to the middle layer
- if (enemy.shadow) {
- enemyLayerMiddle.addChild(enemy.shadow);
- }
- } else {
- // Add normal/ground enemies to the bottom layer
- enemyLayerBottom.addChild(enemy);
- }
- // Scale difficulty with wave number but don't apply to boss
- // as bosses already have their health multiplier
- // Use exponential scaling for health
- var healthMultiplier = Math.pow(1.12, currentWave); // ~20% increase per wave
- enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier);
- enemy.health = enemy.maxHealth;
- // Increment speed slightly with wave number
- //enemy.speed = enemy.speed + currentWave * 0.002;
- // All enemy types now spawn in the middle 6 tiles at the top spacing
- var gridWidth = 24;
- var midPoint = Math.floor(gridWidth / 2); // 12
- // Find a column that isn't occupied by another enemy that's not yet in view
- var availableColumns = [];
- for (var col = midPoint - 3; col < midPoint + 3; col++) {
- var columnOccupied = false;
- // Check if any enemy is already in this column but not yet in view
- for (var e = 0; e < enemies.length; e++) {
- if (enemies[e].cellX === col && enemies[e].currentCellY < 4) {
- columnOccupied = true;
- break;
- }
- }
- if (!columnOccupied) {
- availableColumns.push(col);
- }
- }
- // If all columns are occupied, use original random method
- var spawnX;
- if (availableColumns.length > 0) {
- // Choose a random unoccupied column
- spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)];
- } else {
- // Fallback to random if all columns are occupied
- spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14
- }
- var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading
- enemy.cellX = spawnX;
- enemy.cellY = 5; // Position after entry
- enemy.currentCellX = spawnX;
- enemy.currentCellY = spawnY;
- enemy.waveNumber = currentWave;
- enemies.push(enemy);
- }
+ if (enemiesSpawned < enemiesPerWave) {
+ spawnTimer++;
+ if (spawnTimer >= spawnInterval) {
+ spawnEnemy();
+ spawnTimer = 0;
}
- var currentWaveEnemiesRemaining = false;
- for (var i = 0; i < enemies.length; i++) {
- if (enemies[i].waveNumber === currentWave) {
- currentWaveEnemiesRemaining = true;
+ }
+ for (var i = bullets.length - 1; i >= 0; i--) {
+ var bullet = bullets[i];
+ if (bullet.isAtTarget) {
+ bullet.destroy();
+ bullets.splice(i, 1);
+ continue;
+ }
+ var hitEnemy = false;
+ for (var j = 0; j < enemies.length; j++) {
+ var enemy = enemies[j];
+ if (enemy.isDead) {
+ continue;
+ }
+ if (bullet.intersects(enemy)) {
+ enemy.takeDamage(bullet.damage);
+ hitEnemy = true;
break;
}
}
- if (waveSpawned && !currentWaveEnemiesRemaining) {
- waveInProgress = false;
- waveSpawned = false;
+ if (hitEnemy) {
+ bullet.destroy();
+ bullets.splice(i, 1);
}
}
- for (var a = enemies.length - 1; a >= 0; a--) {
- var enemy = enemies[a];
- if (enemy.health <= 0) {
- for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) {
- var bullet = enemy.bulletsTargetingThis[i];
- bullet.targetEnemy = null;
- }
- // Boss enemies give more gold and score
- var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5);
- var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y);
- game.addChild(goldIndicator);
- setGold(gold + goldEarned);
- // Give more score for defeating a boss
- var scoreValue = enemy.isBoss ? 100 : 5;
- score += scoreValue;
- // Add a notification for boss defeat
- if (enemy.isBoss) {
- var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!"));
- notification.x = 2048 / 2;
- notification.y = grid.height - 150;
- }
- updateUI();
- // Clean up shadow if it's a flying enemy
- if (enemy.isFlying && enemy.shadow) {
- enemyLayerMiddle.removeChild(enemy.shadow);
- enemy.shadow = null;
- }
- // Remove enemy from the appropriate layer
- if (enemy.isFlying) {
- enemyLayerTop.removeChild(enemy);
- } else {
- enemyLayerBottom.removeChild(enemy);
- }
- enemies.splice(a, 1);
+ for (var i = enemies.length - 1; i >= 0; i--) {
+ var enemy = enemies[i];
+ if (enemy.isDead) {
+ enemy.destroy();
+ enemies.splice(i, 1);
continue;
}
- if (grid.updateEnemy(enemy)) {
- // Clean up shadow if it's a flying enemy
- if (enemy.isFlying && enemy.shadow) {
- enemyLayerMiddle.removeChild(enemy.shadow);
- enemy.shadow = null;
- }
- // Remove enemy from the appropriate layer
- if (enemy.isFlying) {
- enemyLayerTop.removeChild(enemy);
- } else {
- enemyLayerBottom.removeChild(enemy);
- }
- enemies.splice(a, 1);
- lives = Math.max(0, lives - 1);
- updateUI();
- if (lives <= 0) {
- LK.showGameOver();
- }
+ if (enemy.hasReachedMeat) {
+ LK.getSound('enemyReachesMeat').play();
+ LK.effects.flashScreen(0xFF0000, 1000);
+ LK.showGameOver();
+ return;
}
- }
- for (var i = bullets.length - 1; i >= 0; i--) {
- if (!bullets[i].parent) {
- if (bullets[i].targetEnemy) {
- var targetEnemy = bullets[i].targetEnemy;
- var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]);
- if (bulletIndex !== -1) {
- targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
+ // Check for collision between enemy and defenders
+ for (var j = defenders.length - 1; j >= 0; j--) {
+ var defender = defenders[j];
+ if (enemy.intersects(defender)) {
+ // Remove the defender and decrement count
+ var defenderType = null;
+ if (defender instanceof Bird) {
+ defenderType = 'bird';
+ } else if (defender instanceof Cat) {
+ defenderType = 'cat';
+ } else if (defender instanceof Raccoon) {
+ defenderType = 'raccoon';
+ } else if (defender instanceof Bear) {
+ defenderType = 'bear';
}
+ if (defenderType) {
+ defenderCounts[defenderType]--;
+ }
+ defender.destroy();
+ defenders.splice(j, 1);
+ updateDefenderButtonStates();
+ break; // Exit inner loop since defender is destroyed
}
- bullets.splice(i, 1);
}
}
- if (towerPreview.visible) {
- towerPreview.checkPlacement();
+ // Update all defenders so they can shoot
+ for (var i = 0; i < defenders.length; i++) {
+ var defender = defenders[i];
+ defender.update();
}
- if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) {
+ if (enemiesSpawned >= enemiesPerWave && enemies.length === 0) {
+ startNextWave();
+ }
+ if (waveNumber > 10) {
LK.showYouWin();
}
};
\ No newline at end of file
ant without background. In-Game asset. 2d. High contrast. No shadows
bear no background. In-Game asset. 2d. High contrast. No shadows
bird no background. In-Game asset. 2d. High contrast. No shadows
cat no background. In-Game asset. 2d. High contrast. No shadows
meat no background. In-Game asset. 2d. High contrast. No shadows
mouse. In-Game asset. 2d. High contrast. No shadows
raccoon no background. In-Game asset. 2d. High contrast. No shadows
snake no background. In-Game asset. 2d. High contrast. No shadows
worm no background. In-Game asset. 2d. High contrast. No shadows
A background with grass details, shot from above, and 4 dirt roads towards the middle, merging from right to left, bottom to top in the middle. In-Game asset. 2d. High contrast. No shadows