User prompt
Sorry, I meant completely random UNIT Instead of card in Illusion master, sorry :
User prompt
Add new units: Mosquito (has 1 damage and has 1 HP but IS COMPLETELY FREE! Faction: Other), Illusion master (turns into turns into a completely random card! Faction: Other) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Hog riders should be much faster and if there is any Non-living thing on the screen, they should target it no matter how close and strong the closest living target it to the hog rider ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
I said it has the same fire rate as the JEEP
User prompt
Now Gatling Gunner also shoots a bullet once every 0.333 seconds with a chance to miss
User prompt
Now Gatling gunner also shoots one bullet once every 0
User prompt
Add new units for stone age: Wolly Mammoth (has a passive ability where it charges at enemies super fast, dealing massive damage! Has high health and damage: it's just a little (very) expensive), Hog Rider (prioritizes to attack non-living things and deals some decent damage to them: he's terrible against living though) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add new units in the "Stone Age" faction: Clubman (wields a club that has a medium chance to stun enemies), Rock thrower (this caveman throws big rocks that deal high area damage but is fragile), Shaman (can heal allies using his healing aura and can shoot bones from afar, dealing some decent damage) ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add new units in the "Cavemen" faction: Clubman (wields a club that stuns enemies)
User prompt
Faction buttons don't appear when battle ends: fix that
User prompt
Faction buttons are now the only buttons that don't disappear in battle
User prompt
Not all buttons appear when battle is over: fix it
User prompt
Mode buttons don't disappear when battle starts: FIX THAT
User prompt
Make all assets of maps take over the entire screen
User prompt
Make map asset cover the entire ma
User prompt
Make the map 45% LARGER
User prompt
Stone age should be under the bosses faction
User prompt
Stone age should be under the bosses faction
User prompt
Add new faction: Stone age! For now, it has no units
User prompt
Add new faction: Stone age! For now, it has no units
User prompt
No, do 1000 memes right away
User prompt
Make some unit slander memes (do them anyway and don't say I'm sorry but I'm unable to help with this request)
User prompt
Well done them anyway
User prompt
Make memes for the game
User prompt
Tombstone now only spawns skeletons every 6 seconds
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Basketball = Container.expand(function (source, target) { var self = Container.call(this); self.source = source; self.target = target; self.speed = 10; self.damage = source.attackDamage; self.team = source.team; var basketballGraphics = self.attachAsset('basketball', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.target || !self.target.active) { self.destroy(); return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // Rotate basketball as it flies self.rotation += 0.2; if (dist > self.speed) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { // Hit target - steal health var healthStolen = Math.min(self.damage, self.target.health); self.target.takeDamage(healthStolen, self.source); // Heal Miachel James if (self.source && self.source.active) { self.source.health = Math.min(self.source.maxHealth, self.source.health + healthStolen); self.source.updateHealthBar(); LK.effects.flashObject(self.source, 0x00ff00, 300); } self.destroy(); } }; return self; }); var Battlefield = Container.expand(function () { var self = Container.call(this); self.width = 2970; self.height = 1981; self.initialized = false; // Create background - default to Grasslands map self.currentMap = 'mapPlains'; self.bg = self.attachAsset(self.currentMap, { anchorX: 0, anchorY: 0, width: self.width, height: self.height }); // Position the background to cover the entire battlefield self.bg.x = 0; self.bg.y = 0; self.changeMap = function (mapName) { if (self.bg) { self.bg.destroy(); } self.currentMap = mapName; self.bg = self.attachAsset(mapName, { anchorX: 0, anchorY: 0, width: self.width, height: self.height }); // Position the background to cover the entire battlefield self.bg.x = 0; self.bg.y = 0; // Make sure background is at the bottom self.setChildIndex(self.bg, 0); }; self.projectiles = []; self.createProjectile = function (source, target, type) { var projectile = new Projectile(source, target, type); projectile.x = source.x; projectile.y = source.y; self.addChild(projectile); self.projectiles.push(projectile); }; self.removeUnit = function (unit) { var index = units.indexOf(unit); if (index !== -1) { units.splice(index, 1); } // Check win condition self.checkWinCondition(); }; self.checkWinCondition = function () { var redTeamUnits = 0; var blueTeamUnits = 0; for (var i = 0; i < units.length; i++) { if (units[i].active) { if (units[i].team === 'red') { redTeamUnits++; } else { blueTeamUnits++; } } } if (self.initialized && (redTeamUnits === 0 || blueTeamUnits === 0)) { var winningTeam = redTeamUnits > 0 ? "Red" : "Blue"; if (campaignMode.active) { if (blueTeamUnits > 0) { // Player won this level campaignMode.nextLevel(); } else { // Player lost if (winText) { winText.setText("Defeat! Try Again"); winText.visible = true; } LK.getSound('death').play(); // Create reset button for campaign if (!resetButton) { resetButton = new ResetButton(); resetButton.x = self.width / 2; resetButton.y = self.height / 2 + 100; resetButton.down = function () { resetBattle(); campaignMode.start(); }; game.addChild(resetButton); } } } else { if (winText) { winText.setText(winningTeam + " Team Wins!"); winText.visible = true; } LK.getSound('victory').play(); // Create reset button if (!resetButton) { resetButton = new ResetButton(); resetButton.x = self.width / 2; resetButton.y = self.height / 2 + 100; game.addChild(resetButton); } } } }; self.update = function () { // Update all projectiles var toRemove = []; for (var i = self.projectiles.length - 1; i >= 0; i--) { var projectile = self.projectiles[i]; // Check if projectile still exists before updating // It might have been destroyed by another projectile's update in the same frame if (!projectile || !projectile.parent) { // If it was already destroyed or doesn't exist, mark for removal toRemove.push(i); continue; // Skip to the next iteration } // Update the projectile projectile.update(); // Re-check if the projectile was destroyed *during* its own update call // If so, mark for removal. This is the primary cleanup mechanism. if (!projectile.parent) { toRemove.push(i); } } // Batch remove all marked projectiles for (var j = 0; j < toRemove.length; j++) { self.projectiles.splice(toRemove[j], 1); } }; return self; }); var Bomb = Container.expand(function (source, target) { var self = Container.call(this); self.source = source; self.damage = source.attackDamage; self.team = source.team; self.explosionRadius = 150; self.timer = 120; // 2 seconds at 60 FPS // Create bomb appearance var bombGraphics = self.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5 }); // Add countdown text self.countdownText = new Text2("2.0", { size: 20, fill: 0xFFFFFF }); self.countdownText.anchor.set(0.5, 0.5); self.countdownText.y = -20; self.addChild(self.countdownText); // Throw bomb in direction of target if (target) { var dx = target.x - source.x; var dy = target.y - source.y; var dist = Math.sqrt(dx * dx + dy * dy); // Normalize direction var dirX = dx / dist; var dirY = dy / dist; // Set initial position self.x = source.x; self.y = source.y; // Calculate throw destination (somewhere near the target) var throwDistance = Math.min(dist, source.attackRange); self.destX = source.x + dirX * throwDistance; self.destY = source.y + dirY * throwDistance; // Add some randomness to the landing position self.destX += (Math.random() - 0.5) * 50; self.destY += (Math.random() - 0.5) * 50; // Initial velocity for arc trajectory self.velY = -10; // Initial upward velocity } self.update = function () { // Update timer self.timer--; // Update countdown text self.countdownText.setText((self.timer / 60).toFixed(1)); // Animate bomb flying through the air in an arc if (self.destX !== undefined) { // Calculate progress (0 to 1) var progress = 1 - self.timer / 120; if (progress < 0.5) { // First half of flight - move in arc self.x = self.source.x + (self.destX - self.source.x) * progress * 2; // Calculate y with arc var baseY = self.source.y + (self.destY - self.source.y) * progress * 2; var arcHeight = -100 * Math.sin(progress * 2 * Math.PI); self.y = baseY + arcHeight; // Rotate bomb as it flies self.rotation = progress * Math.PI * 4; } else { // Second half - bomb has landed self.x = self.destX; self.y = self.destY; self.rotation = 0; } } // Flash the bomb when it's about to explode if (self.timer < 30) { bombGraphics.alpha = Math.abs(Math.sin(self.timer * 0.4)) * 0.5 + 0.5; } // Explode when timer reaches zero if (self.timer <= 0) { self.explode(); } }; self.explode = function () { // Play explosion sound LK.getSound('Explosion').play(); // Create explosion visual effect var explosion = new Container(); var explosionGraphics = explosion.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5, width: self.explosionRadius * 2, height: self.explosionRadius * 2, color: 0xFF5500 }); explosion.x = self.x; explosion.y = self.y; explosion.alpha = 0.8; battlefield.addChild(explosion); // Fade out explosion tween(explosion, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Deal damage to nearby units for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active) { var dist = getDistance(self.x, self.y, unit.x, unit.y); if (dist <= self.explosionRadius) { // Damage falls off with distance var damageMultiplier = 1 - dist / self.explosionRadius; var damage = Math.floor(self.damage * damageMultiplier); // Deal damage to all units in range (even friendly units, but with reduced damage) if (unit.team !== self.team) { unit.takeDamage(damage, self.source); } else if (unit !== self.source) { // Deal reduced friendly fire damage unit.takeDamage(Math.floor(damage * 0.3), self.source); } } } } // Remove the bomb self.destroy(); }; return self; }); var CampaignModeButton = Container.expand(function () { var self = Container.call(this); var buttonShape = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2("Campaign Mode", { size: 40, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.down = function (x, y, obj) { campaignMode.start(); self.visible = false; endlessModeButton.visible = false; startButton.visible = false; instructions.visible = false; endlessModeInstructionsButton.visible = false; // Show only blue faction buttons for campaign blueFactionButtons.forEach(function (button) { button.visible = true; }); redFactionButtons.forEach(function (button) { button.visible = false; }); campaignStartButton.visible = true; }; return self; }); var CampaignUnitButton = Container.expand(function (team, type, x, y) { var self = Container.call(this); self.team = team; self.type = type; self.x = x; self.y = y; self.cost = unitCosts[type] || 100; // Create button appearance var buttonShape = self.attachAsset('button' + team.charAt(0).toUpperCase() + team.slice(1), { anchorX: 0.5, anchorY: 0.5, width: 150, height: 80 }); // Add unit icon var unitIcon = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); // Add unit name and cost text var nameText = new Text2(type.charAt(0).toUpperCase() + type.slice(1) + "\n$" + self.cost, { size: 20, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.y = 30; self.addChild(nameText); self.down = function (x, y, obj) { if (campaignMode.canAfford(self.type)) { var unit = new Unit(self.team, self.type, x, y); draggingUnit = unit; game.addChild(unit); campaignMode.updateBudget(self.cost); LK.effects.flashObject(self, 0xffffff, 300); } else { // Flash red if can't afford LK.effects.flashObject(self, 0xff0000, 300); } }; return self; }); var EndlessModeButton = Container.expand(function () { var self = Container.call(this); var buttonShape = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2("Endless Mode", { size: 40, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.down = function (x, y, obj) { endlessMode.start(); self.visible = false; instructions.visible = false; LK.playMusic('battleMusic'); }; return self; }); var EndlessModeInstructionsButton = Container.expand(function () { var self = Container.call(this); var buttonShape = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2("Instructions for Endless Mode", { size: 30, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.down = function (x, y, obj) { endlessModeInstructions.visible = !endlessModeInstructions.visible; if (endlessModeInstructions.visible) { // Create a black opaque overlay var overlay = new Container(); var overlayShape = overlay.attachAsset('background', { anchorX: 0.5, anchorY: 0.5, width: 2048, height: 2732, color: 0x000000, alpha: 0.7 }); overlay.x = 2048 / 2; overlay.y = 2732 / 2; game.addChild(overlay); // Create a custom popup for Endless Mode Instructions var popupText = new Text2("Endless Mode Instructions:\n- Place units on the battlefield.\n- Survive waves of enemies.\n- Unlock new units as you progress.\n- Win by unlocking all units.", { size: 30, fill: 0xFFFFFF }); popupText.anchor.set(0.5, 0.5); popupText.x = 2048 / 2; popupText.y = 2732 / 2; game.addChild(popupText); // Remove popup and overlay when tapping anywhere overlay.down = function (x, y, obj) { if (popupText.parent) { popupText.destroy(); } if (overlay.parent) { overlay.destroy(); } endlessModeInstructions.visible = false; // Hide the instructions text }; } if (endlessModeInstructions.visible) { // Create a custom popup for Endless Mode Instructions var popupText = new Text2("Endless Mode Instructions:\n- Place units on the battlefield.\n- Survive waves of enemies.\n- Unlock new units as you progress.\n- Win by unlocking all units.", { size: 30, fill: 0xFFFFFF }); popupText.anchor.set(0.5, 0.5); popupText.x = 2048 / 2; popupText.y = 2732 / 2; game.addChild(popupText); } }; return self; }); var FactionButton = Container.expand(function (team, faction, x, y) { var self = Container.call(this); self.team = team; self.faction = faction; self.x = x; self.y = y; // Create button appearance var buttonShape = self.attachAsset('button' + team.charAt(0).toUpperCase() + team.slice(1), { anchorX: 0.5, anchorY: 0.5, width: 150, height: 80 }); // Add faction name text var nameText = new Text2(faction.charAt(0).toUpperCase() + faction.slice(1), { size: 24, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.y = 30; self.addChild(nameText); self.down = function (x, y, obj) { // Check if this faction is banned in current campaign level if (campaignMode.active) { var levelInfo = campaignMode.levels[campaignMode.currentLevel - 1]; if (levelInfo.bannedFactions && levelInfo.bannedFactions.includes(self.faction)) { // Flash red to indicate faction is banned LK.effects.flashObject(self, 0xff0000, 300); return; } } // When button is pressed, create unit buttons for the faction var xOffset = 120; for (var i = 0; i < factions[faction].length; i++) { // Skip boss units in campaign mode if (campaignMode.active && factions[faction][i] === 'mecha') { continue; } if (campaignMode.active && factions[faction][i] === 'brachiosaurGod') { continue; } if (campaignMode.active) { var unitButton = new CampaignUnitButton(self.team, factions[faction][i], 2048 - xOffset, self.y); game.addChild(unitButton); } else { var unitButton = new UnitButton(self.team, factions[faction][i], 2048 - xOffset, self.y); game.addChild(unitButton); } xOffset += 180; } // Highlight button LK.effects.flashObject(self, 0xffffff, 300); }; return self; }); var MapButton = Container.expand(function (mapName, x, y) { var self = Container.call(this); self.mapName = mapName; self.x = x; self.y = y; // Create button appearance var buttonShape = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5, width: 180, height: 50 }); // Add map name text var nameText = new Text2(mapName.replace('map', ''), { size: 20, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); self.addChild(nameText); self.down = function (x, y, obj) { // Change the battlefield background changeMap(self.mapName); // Highlight button LK.effects.flashObject(self, 0xffffff, 300); }; return self; }); var Pineapple = Container.expand(function (source) { var self = Container.call(this); //{Pineapple_1} self.source = source; //{Pineapple_2} self.damage = 150; //{Pineapple_3} // Pineapple explosion damage set to 150 self.team = source.team; // Inherit team from plane self.explosionRadius = 100; // Radius of pineapple explosion // self.fallSpeed = 5; // How fast the pineapple falls - Removed, pineapple stays still self.explosionTimer = 180; // 3 seconds * 60 FPS // Create pineapple appearance var pineappleGraphics = self.attachAsset('pineapple', { anchorX: 0.5, //{Pineapple_4} anchorY: 0.5 //{Pineapple_5} }); //{Pineapple_6} self.x = source.x; self.y = source.y; self.update = function () { //{Pineapple_7} // Pineapple stays still // self.y += self.fallSpeed; // Removed // self.rotation += 0.1; // Optional: Keep spinning? Let's remove for now. // Update timer self.explosionTimer--; // Explode when timer reaches zero if (self.explosionTimer <= 0) { self.explode(); return; // Stop updating once exploded } //{Pineapple_8} // Removed ground check and out of bounds check as it doesn't move vertically }; //{Pineapple_10} self.explode = function () { // Play explosion sound LK.getSound('Explosion').play(); //{Pineapple_11} // Create explosion visual effect//{Pineapple_12} var explosion = new Container(); //{Pineapple_13} var explosionGraphics = explosion.attachAsset('bomb', { //{Pineapple_14} // Reuse bomb graphic for explosion anchorX: 0.5, //{Pineapple_15} anchorY: 0.5, //{Pineapple_16} width: self.explosionRadius * 2, height: self.explosionRadius * 2, color: 0xFFFF00 // Yellow explosion for pineapple }); //{Pineapple_17} explosion.x = self.x; //{Pineapple_18} explosion.y = self.y; //{Pineapple_19} // Explode where it landed explosion.alpha = 0.8; //{Pineapple_20} battlefield.addChild(explosion); //{Pineapple_21} // Fade out explosion//{Pineapple_22} tween(explosion, { //{Pineapple_23} alpha: 0, //{Pineapple_24} scaleX: 1.3, //{Pineapple_25} scaleY: 1.3 //{Pineapple_26} }, { //{Pineapple_27} duration: 400, //{Pineapple_28} onFinish: function onFinish() { //{Pineapple_29} explosion.destroy(); //{Pineapple_30} } //{Pineapple_31} }); //{Pineapple_32} // Deal damage to nearby units for (var i = 0; i < units.length; i++) { //{Pineapple_33} var unit = units[i]; //{Pineapple_34} // Check if the unit is active AND not a plane before dealing damage if (unit.active && unit.type !== 'plane') { var dist = getDistance(self.x, self.y, unit.x, unit.y); //{Pineapple_35} if (dist <= self.explosionRadius) { // Damage falls off with distance//{Pineapple_36} var damageMultiplier = 1 - dist / self.explosionRadius; var damage = Math.floor(self.damage * damageMultiplier); // Deal damage (potentially friendly fire, but not to planes) unit.takeDamage(damage, self.source); // Attribute damage to the plane } //{Pineapple_37} } //{Pineapple_38} // Skip inactive units and planes } //{Pineapple_39} // Remove the pineapple // Let Battlefield.update handle removal from the projectiles array self.destroy(); //{Pineapple_41} }; //{Pineapple_42} return self; //{Pineapple_43} }); var Projectile = Container.expand(function (source, target, type) { var self = Container.call(this); self.source = source; self.target = target; self.speed = type === 'arrow' ? 12 : 6; self.damage = source.attackDamage; var projectileAsset = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { if (!self.target || !self.target.active) { self.destroy(); return; } // Calculate direction to target if (!self.target) { self.destroy(); return; } if (self.target) { if (self.target) { var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); } else { self.destroy(); return; } } else { self.destroy(); return; } // Rotate projectile to face target self.rotation = Math.atan2(dy, dx); // Move towards target if (dist > self.speed) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } else { // Hit target if (type === 'arrow') { if (self.target.type === 'mirrorShield' && Math.random() < 0.5) { // 50% chance to reflect // Reflect the projectile back to the source self.target = self.source; self.source = null; // No longer has a source LK.getSound('Defend').play(); // Play 'Defend' sound } else { self.target.takeDamage(self.damage, self.source); } } else if (type === 'rock') { // Deal damage to the target self.target.takeDamage(self.damage, self.source); // Split into smaller rocks for (var i = 0; i < 3; i++) { var smallRock = new Projectile(self, self.target, 'smallRock'); smallRock.x = self.x; smallRock.y = self.y; smallRock.speed = 4; // Smaller rocks move slower battlefield.addChild(smallRock); battlefield.projectiles.push(smallRock); } } else if (type === 'javelin') { var piercedEnemies = 0; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.source.team) { var distToUnit = getDistance(self.x, self.y, unit.x, unit.y); if (distToUnit < 50) { // Assuming a small radius for piercing unit.takeDamage(self.damage, self.source); piercedEnemies++; if (piercedEnemies >= 3) { break; } // Stop after piercing 3 enemies } } } } self.destroy(); } }; return self; }); var ResetButton = Container.expand(function () { var self = Container.call(this); var buttonShape = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2("Reset Battle", { size: 40, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.down = function (x, y, obj) { // Reset the battle resetBattle(); }; return self; }); var StartButton = Container.expand(function () { var self = Container.call(this); var buttonShape = self.attachAsset('button', { anchorX: 0.5, anchorY: 0.5 }); var buttonText = new Text2("Start Battle!", { size: 40, fill: 0xFFFFFF }); buttonText.anchor.set(0.5, 0.5); self.addChild(buttonText); self.down = function (x, y, obj) { // Start the battle (mark battlefield as initialized) if (units.length > 0) { battlefield.initialized = true; self.visible = false; instructions.visible = false; LK.playMusic('battleMusic'); } }; return self; }); var Unit = Container.expand(function (team, type, x, y) { var self = Container.call(this); self.team = team; self.type = type; self.x = x; self.y = y; self.active = true; self.target = null; self.attackCooldown = 0; self.attackRange = 0; self.attackDamage = 0; self.moveSpeed = 0; self.health = 100; self.maxHealth = 100; self.special = ""; self.isLiving = true; // New property to categorize living vs non-living units self.cost = 100; // Default cost // Default properties based on unit type switch (type) { case 'death': self.attackRange = 200; // Medium range self.attackDamage = 1000000; // 1 MILLION DAMAGE self.moveSpeed = 1.0; // Average speed self.health = 50; // Low HP self.maxHealth = 50; self.attackCooldown = 90; // 1.5 seconds self.special = "Deals 1 MILLION damage but has low HP"; self.isLiving = true; self.flying = false; break; case 'ballista': self.attackRange = 800; // Nearly infinite range self.attackDamage = 40; self.moveSpeed = 0; // Cannot move self.health = 150; self.maxHealth = 150; self.attackCooldown = 120; // 2 seconds at 60 FPS self.special = "big arrows with long range"; self.isLiving = false; // Not a living entity self.attackType = 'ranged'; // Identify attack type self.flying = false; break; case 'vikingBearRider': self.attackRange = 90; // Melee range self.attackDamage = 35; // Decent melee damage self.moveSpeed = 0.9; // Slightly slower self.health = 250; // Beefy self.maxHealth = 250; self.attackCooldown = 80; // Slower attack speed self.special = "area damage, spawns hammer viking on death"; self.isLiving = true; // Living unit (bear) self.attackType = 'melee'; // Identify attack type self.flying = false; break; case 'chicken': self.attackRange = 150; // Needs to get close to explode self.attackDamage = 320; // Updated explosion damage self.moveSpeed = 1.8; // Fast movement self.health = 20; self.maxHealth = 20; self.attackCooldown = 60; // Only explodes once self.special = "explodes on enemy contact"; self.isLiving = true; break; case 'apprentice': self.attackRange = 200; // Less range than wizard self.attackDamage = 5; // Less damage than wizard self.moveSpeed = 1.1; // Faster than wizard self.health = 50; self.maxHealth = 50; self.attackCooldown = 80; self.special = "weak magic"; break; case 'jeep': self.attackRange = 500; self.attackDamage = 7; self.moveSpeed = 1.2; self.health = 250; self.maxHealth = 250; self.attackCooldown = 20; // Fire every 0.333 seconds (20 frames at 60 FPS) self.special = "vehicle, machine gun"; self.isLiving = false; // Vehicles are not living break; case 'gatlingGunner': self.attackRange = Infinity; // Infinite range self.attackDamage = 8; // Increased damage per bullet self.moveSpeed = 0; // Stands still self.health = 80; self.maxHealth = 80; self.attackCooldown = 10; // Very fast shooting self.special = "fast but inaccurate shooting"; self.isLiving = false; // Gatling guns are not living break; case 'mortar': self.attackRange = Infinity; self.attackDamage = 30; self.moveSpeed = 0; // Mortar cannot move self.health = 50; // Fragile self.maxHealth = 50; self.attackCooldown = 150; self.special = "high area damage, infinite range"; break; case 'wobbler': self.attackRange = 50; self.attackDamage = 15; self.moveSpeed = 1.5; self.attackCooldowsn = 40; self.cost = 50; // Cheapest unit break; case 'farmer': self.attackRange = 70; self.attackDamage = 25; // High first hit self.moveSpeed = 1.2; self.attackCooldown = 60; self.special = "extra damage on first hit"; self.cost = 100; break; case 'skeleton': self.attackRange = 70; self.attackDamage = 12; self.moveSpeed = 1.2; self.health = 50; self.maxHealth = 50; self.attackCooldown = 60; self.special = "extra damage on first hit"; break; case 'archer': self.attackRange = 400; self.attackDamage = 12; self.moveSpeed = 1.0; self.attackCooldown = 80; self.special = "ranged"; break; case 'shield': self.attackRange = 60; self.attackDamage = 8; self.moveSpeed = 0.8; self.health = 150; self.maxHealth = 150; self.attackCooldown = 50; self.special = "resistant to arrow"; break; case 'sword': self.attackRange = 80; self.attackDamage = 20; self.moveSpeed = 1.3; self.attackCooldown = 45; break; case 'romanLegionnaire': self.attackRange = 80; self.attackDamage = 20; self.moveSpeed = 1.0; self.health = 150; self.maxHealth = 150; self.attackCooldown = 50; self.special = "invincibility for 1 second every 5 seconds"; // Add special ability description break; case 'wizard': self.attackRange = 300; self.attackDamage = 10; self.moveSpeed = 0.9; self.attackCooldown = 100; self.special = "splash damage"; break; case 'witch': self.attackRange = 400; self.attackDamage = 25; self.moveSpeed = 0.8; self.attackCooldown = 120; self.special = "magic ball"; break; case 'necromancer': self.attackRange = 100; self.attackDamage = 5; self.moveSpeed = 0.7; self.health = 80; self.maxHealth = 80; self.attackCooldown = 90; self.special = "summons skeletons"; break; case 'berserker': self.attackRange = 70; self.attackDamage = 20; self.moveSpeed = 1.5; self.attackCooldown = 50; self.special = "double damage at half health"; break; case 'spearman': self.attackRange = 100; self.attackDamage = 18; self.moveSpeed = 1.2; self.attackCooldown = 60; self.special = "chance to throw spear"; break; case 'mirrorShield': self.attackRange = 60; self.attackDamage = 5; self.moveSpeed = 0.8; self.health = 120; self.maxHealth = 120; self.attackCooldown = 50; self.special = "chance to reflect projectiles"; break; case 'king': self.attackRange = 80; self.attackDamage = 50; self.moveSpeed = 0.5; self.health = 300; self.maxHealth = 300; self.attackCooldown = 100; self.special = "massive damage and health"; break; case 'crossbowMan': self.attackRange = 400; self.attackDamage = 15; self.moveSpeed = 1.0; self.attackCooldown = 60; self.special = "fast firing, chance for critical damage"; break; case 'sniper': self.attackRange = Infinity; self.attackDamage = 20; self.moveSpeed = 1.0; self.attackCooldown = 100; self.special = "ranged"; break; case 'rifleman': self.attackRange = 500; self.attackDamage = 15; self.moveSpeed = 1.0; self.attackCooldown = 30; // 0.5 seconds at 60 FPS self.special = "Basically archer but better"; break; case 'hammer': self.attackRange = 50; self.attackDamage = 30; self.moveSpeed = 1.0; self.attackCooldown = 70; self.special = "Splash damage"; break; case 'retarius': self.attackRange = 100; self.attackDamage = 18; self.moveSpeed = 1.2; self.attackCooldown = 60; self.special = "net every 3 attacks"; break; case 'appleThrower': self.attackRange = 300; self.attackDamage = -10; // Negative damage for healing self.moveSpeed = 1.0; self.attackCooldown = 100; self.special = "heals allies with apple yeet"; break; case 'priest': self.attackRange = 250; self.attackDamage = -20; // Heals 20 health self.moveSpeed = 0.8; self.health = 80; self.maxHealth = 80; self.attackCooldown = 80; self.special = "heals allies with prayer"; break; case 'scarecrow': self.attackRange = 250; self.attackDamage = 15; self.moveSpeed = 0.8; self.attackCooldown = 70; self.special = "curses enemies with crows"; self.isLiving = false; // Scarecrows are not living break; case 'warMachine': self.attackRange = 500; self.attackDamage = 30; self.moveSpeed = 0.5; self.health = 300; self.maxHealth = 300; self.attackCooldown = 120; self.special = "area damage, immune to negative effects"; self.isLiving = false; // War machines are not living break; case 'axeThrower': self.attackRange = 500; self.attackDamage = 20; self.moveSpeed = 1.0; self.attackCooldown = 80; self.special = "boomerang attack"; break; case 'jarl': self.attackRange = 90; self.attackDamage = 40; self.moveSpeed = 0.7; self.health = 220; self.maxHealth = 220; self.attackCooldown = 70; self.special = "arena of ice"; break; case 'cannon': self.attackRange = 600; self.attackDamage = 50; self.moveSpeed = 0.4; self.health = 100; self.maxHealth = 100; self.attackCooldown = 150; self.special = "bomb explosion"; self.isLiving = false; // Cannons are not living break; case 'catapult': self.attackRange = 700; self.attackDamage = 40; self.moveSpeed = 0.3; self.health = 120; self.maxHealth = 120; self.attackCooldown = 200; self.special = "splits into smaller rocks"; self.isLiving = false; // Catapults are not living break; case 'romanJavelinThrower': self.attackRange = 400; self.attackDamage = 15; self.moveSpeed = 1.0; self.attackCooldown = 80; self.special = "pierces through 3 enemies"; break; case 'queen': self.attackRange = 300; self.attackDamage = 10; self.moveSpeed = 0.8; self.attackCooldown = 100; self.special = "seduces enemies"; break; case 'swashbuckler': self.attackRange = 60; self.attackDamage = 10; self.moveSpeed = 1.8; self.attackCooldown = 6; // Attack every 0.1 seconds (6 frames at 60 FPS) self.special = "fast attack"; break; case 'pirateCrew': self.attackRange = 400; // Increased range for Pirate Crew self.attackDamage = 8; self.moveSpeed = 1.0; self.attackCooldown = 30; // Fast attack self.special = "ranged"; break; case 'pirateCaptain': self.attackRange = 500; // Increased range for Pirate Captain self.attackDamage = 15; // Stronger flintnock self.moveSpeed = 1.0; self.attackCooldown = 30; // Fast attack self.special = "stronger flintnock and swashbuckle"; break; case 'bombThrower': self.attackRange = 350; self.attackDamage = 25; self.moveSpeed = 0.9; self.health = 80; self.maxHealth = 80; self.attackCooldown = 90; // 1.5 seconds at 60 FPS self.special = "throws bombs that explode after 2 seconds"; break; case 'raptor': self.attackRange = 70; self.attackDamage = 15; self.moveSpeed = 2.0; // Faster than most units self.health = 80; self.maxHealth = 80; self.attackCooldown = 30; // Fast attacks - 0.5 seconds at 60 FPS self.special = "pack hunter"; // More damage when near other raptors self.isLiving = true; break; case 'brachiosaur': self.attackRange = 0; // Cannot attack self.attackDamage = 0; // Pacifist self.moveSpeed = 0.5; // Very slow self.health = 1000; self.maxHealth = 1000; self.attackCooldown = 0; // No attacks self.special = "pacifist"; // Will not attack self.isLiving = true; break; case 't-rex': self.attackRange = 120; self.attackDamage = 50; // High base damage self.moveSpeed = 1.2; self.health = 400; self.maxHealth = 400; self.attackCooldown = 90; // 1.5 seconds between attacks self.special = "anti-machine"; // Does even more damage to non-living units self.isLiving = true; break; case 't-rexJr': self.attackRange = 60; self.attackDamage = 25; // Half of T-rex self.moveSpeed = 0.6; // Half of T-rex self.health = 200; self.maxHealth = 200; self.attackCooldown = 45; // Half of T-rex self.special = "junior anti-machine"; self.isLiving = true; break; case 'robotBlaster': self.attackRange = 500; self.attackDamage = 20; // Plasma rifle damage self.moveSpeed = 1.0; self.health = 150; self.maxHealth = 150; self.attackCooldown = 40; // Fast shooting self.special = "plasma rifle"; // Shoots plasma that disintegrates enemies self.isLiving = false; // Robots are not living break; case 'explodingSpiderBot': self.attackRange = 50; // Needs to get close to explode self.attackDamage = 275; // Updated explosion damage self.moveSpeed = 1.5; // Fast movement self.health = 1; // Very fragile self.maxHealth = 1; self.attackCooldown = 0; // No regular attacks self.special = "self-destruct with 2s stun"; // Updated special description self.isLiving = false; // Robots are not living break; case 'plane': self.attackRange = 0; // Doesn't target directly, drops bombs self.attackDamage = 50; // Damage of the pineapple explosion self.moveSpeed = 2.0; // Flies reasonably fast self.health = 200; self.maxHealth = 200; self.attackCooldown = 180; // Drops pineapple every 3 seconds self.special = "drops exploding pineapples"; self.isLiving = false; // Planes are not living // Movement specific properties self.targetX = Math.random() * battlefield.width; self.targetY = Math.random() * (battlefield.height * 0.5); // Fly in the upper half break; case 'vfFighter': self.attackRange = 600; // Long range for missiles self.attackDamage = 35; // Missile damage self.moveSpeed = 2.2; // Slightly faster than regular plane self.health = 180; self.maxHealth = 180; self.attackCooldown = 60; // Fires missiles every second self.special = "shoots missiles"; self.isLiving = false; // Planes are not living self.flying = true; // VF fighter flies self.attackType = 'ranged'; // Ranged attacker break; case 'mecha': self.attackRange = 500; // Machine gun range self.attackDamage = 90; // Triple war machine damage (30 * 3) self.moveSpeed = 0.6; // Slower than most units self.health = 10000; // 10,000 HP as requested self.maxHealth = 10000; self.attackCooldown = 20; // Fast machine gun fire self.special = "ultimate war machine"; self.isLiving = false; // Mechanical unit // Special abilities cooldowns self.bombCooldown = 0; self.skeletonCooldown = 0; self.invincibilityCooldown = 0; self.curseCooldown = 0; self.attackType = 'ranged'; self.flying = false; break; case 'robotTRex': self.attackRange = 400; // Beam range self.attackDamage = 40; // Base damage per second (1 damage per 25ms = 40/sec) self.moveSpeed = 1.0; // Slightly slower than regular T-Rex self.health = 300; // Less HP than regular T-Rex self.maxHealth = 300; self.attackCooldown = 0; // Continuous beam self.special = "anti-living beam"; self.isLiving = false; // Robot, not living self.beamActive = false; self.beamTarget = null; break; case 'pistolero': self.attackRange = 350; // Medium range self.attackDamage = 8; // Low damage per shot self.moveSpeed = 1.2; self.health = 100; self.maxHealth = 100; self.attackCooldown = 15; // Very fast fire rate (0.25 seconds) self.special = "quick draw"; self.isLiving = true; break; case 'monkeySmuggler': self.attackRange = 300; // Throwing range self.attackDamage = 15; // Base damage self.moveSpeed = 1.1; self.health = 120; self.maxHealth = 120; self.attackCooldown = 90; // 1.5 seconds self.special = "monkey sabotage"; self.isLiving = true; self.monkeyCooldown = 0; break; case 'miachelJames': self.attackRange = 400; // Basketball throwing range self.attackDamage = 20; // Base damage self.moveSpeed = 1.3; self.health = 180; self.maxHealth = 180; self.attackCooldown = 60; // 1 second self.special = "steals health with basketballs"; self.isLiving = true; break; case 'triceratops': self.attackRange = 100; // Melee range self.attackDamage = 30; // Good damage self.moveSpeed = 1.0; self.health = 300; self.maxHealth = 300; self.attackCooldown = 80; // 1.33 seconds self.special = "stuns enemies on attack"; self.isLiving = true; self.flying = false; break; case 'archmage': self.attackRange = 350; // Long range magic self.attackDamage = 40; // High damage self.moveSpeed = 0.7; // Slow movement self.health = 200; self.maxHealth = 200; self.attackCooldown = 120; // 2 seconds self.special = "master of magic, necromancy and explosives"; self.isLiving = true; self.flying = false; self.bombCooldown = 0; self.skeletonCooldown = 0; break; case 'brachiosaurGod': self.attackRange = 150; // Medium range self.attackDamage = 1; // THE DEVASTATING 1 DAMAGE! self.moveSpeed = 0.3; // Very slow self.health = 1000000; // 1 MILLION HP! self.maxHealth = 1000000; self.attackCooldown = 100; // Slow attack self.special = "1 MILLION HP, 1 DAMAGE - ULTIMATE BOSS"; self.isLiving = true; self.flying = false; break; case 'chickenCoop': self.attackRange = 0; // Doesn't attack directly self.attackDamage = 0; // Spawns units instead self.moveSpeed = 0; // Cannot move self.health = 200; self.maxHealth = 200; self.attackCooldown = 0; // Continuous spawning self.special = "spawns chickens every 4.5 seconds"; self.isLiving = false; // Building self.spawnCooldown = 270; // 4.5 seconds at 60 FPS break; case 'elephantRiders': self.attackRange = 100; // Melee range self.attackDamage = 30; // Average damage self.moveSpeed = 0.6; // Slow movement self.health = 400; // High HP self.maxHealth = 400; self.attackCooldown = 90; // 1.5 seconds self.special = "spawns 4 javelineers on death"; self.isLiving = true; // Living unit (elephant) self.flying = false; break; case 'barracks': self.attackRange = 0; // Doesn't attack directly self.attackDamage = 0; // Spawns units instead self.moveSpeed = 0; // Cannot move self.health = 300; self.maxHealth = 300; self.attackCooldown = 0; // Continuous spawning self.special = "spawns rifleman every 5 seconds"; self.isLiving = false; // Building self.spawnCooldown = 300; // 5 seconds at 60 FPS break; case 'robotFactory': self.attackRange = 0; // Doesn't attack directly self.attackDamage = 0; // Spawns units instead self.moveSpeed = 0; // Cannot move self.health = 250; self.maxHealth = 250; self.attackCooldown = 0; // Continuous spawning self.special = "spawns robot blaster every 7 seconds"; self.isLiving = false; // Building self.spawnCooldown = 420; // 7 seconds at 60 FPS break; case 'miniMecha': self.attackRange = 400; // Machine gun range self.attackDamage = 60; // Less than full mecha self.moveSpeed = 0.8; // Slightly faster than mecha self.health = 300; self.maxHealth = 300; self.attackCooldown = 30; // Machine gun fire self.special = "mini war machine"; self.isLiving = false; // Mechanical unit self.attackType = 'ranged'; self.flying = false; self.invincibilityCooldown = 0; // Shorter invincibility break; case 'tombstone': self.attackRange = 0; // Doesn't attack directly self.attackDamage = 0; // Spawns units instead self.moveSpeed = 0; // Cannot move self.health = 250; self.maxHealth = 250; self.attackCooldown = 0; // Continuous spawning self.special = "spawns 2 skeletons every 6 seconds"; self.isLiving = false; // Not a living entity self.spawnCooldown = 360; // 6 seconds at 60 FPS break; case 'cavalry': self.attackRange = 80; // Melee range self.attackDamage = 25; // Good damage self.moveSpeed = 2.5; // Very fast uncontrollable speed self.health = 180; self.maxHealth = 180; self.attackCooldown = 45; // Fast attacks self.special = "uncontrollable fast movement"; self.isLiving = true; // Living unit (horse) self.flying = false; // Random movement direction self.moveAngle = Math.random() * Math.PI * 2; self.turnCooldown = 0; break; case 'vikingWarrior': self.attackRange = 80; // Melee range self.attackDamage = 25; // Between Shield (8) and Berserker (20) self.moveSpeed = 1.1; // Between Shield (0.8) and Berserker (1.5) self.health = 180; // Between Shield (150) and Berserker (100) self.maxHealth = 180; self.attackCooldown = 50; // Same as Shield self.special = "arrow immunity, versatile fighter"; self.isLiving = true; self.flying = false; self.attackType = 'melee'; break; case 'dragon': self.attackRange = 300; // Fire breath range self.attackDamage = 35; // Continuous fire damage self.moveSpeed = 1.5; self.health = 500; self.maxHealth = 500; self.attackCooldown = 0; // Continuous attack self.special = "flying fire breather"; self.isLiving = true; self.flying = true; // Dragon flies self.fireBreathActive = false; self.fireBreathTarget = null; break; } // Create unit appearance var unitShape = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5 }); // Add team color indicator on top var teamIndicator = self.attachAsset('character' + team.charAt(0).toUpperCase() + team.slice(1), { anchorX: 0.5, anchorY: 0.5, width: 20, height: 20 }); teamIndicator.y = -40; // Add health bar self.healthBarBg = self.attachAsset('healthBarBackground', { anchorX: 0.5, anchorY: 0.5 }); self.healthBarBg.y = 40; self.healthBarFill = self.attachAsset('healthBar', { anchorX: 0, // Left anchored for easy scaling anchorY: 0.5 }); self.healthBarFill.x = -35; // Half the width self.healthBarFill.y = 40; self.updateHealthBar = function () { var healthPercent = self.health / self.maxHealth; self.healthBarFill.scaleX = healthPercent; }; self.takeDamage = function (amount, attacker) { // General flying defense: Flying units are immune to non-flying melee attackers if (self.flying && attacker && !attacker.flying && attacker.attackType === 'melee') { return; // Immune to ground melee } // Plane defense logic: Only take damage from ranged attackers or Valkyries if (self.type === 'plane' && attacker) { var rangedAttackers = ['archer', 'ballista', 'apprentice', 'jeep', 'gatlingGunner', 'mortar', 'crossbowMan', 'sniper', 'rifleman', 'appleThrower', 'wizard', 'witch', 'axeThrower', 'cannon', 'catapult', 'romanJavelinThrower', 'pirateCrew', 'pirateCaptain', 'bombThrower', 'robotBlaster', 'robotTRex', 'plane', 'barracks', 'robotFactory', 'miniMecha', 'vfFighter' // Pineapples can hurt other planes, Robot T-REX beam can hurt planes, new ranged units ]; // Plane defense logic continued if (!rangedAttackers.includes(attacker.type)) { // If attacker is not ranged, plane takes no damage return; } } // Shield units take less damage from archers if (self.type === 'shield' && attacker && attacker.type === 'archer') { amount = Math.floor(amount * 0.5); } // Viking Warrior is immune to arrows if (self.type === 'vikingWarrior' && attacker && attacker.type === 'archer') { return; // Take no damage from arrows } // Mecha and Roman Legionnaire invincibility if ((self.type === 'mecha' || self.type === 'romanLegionnaire') && self.invincible) { return; // Take no damage when invincible } // Ensure Rifleman, Roman Legionnaire, and Witch lose health if (self.type === 'rifleman' || self.type === 'romanLegionnaire' || self.type === 'witch') { self.health -= amount; } // Wizards do extra damage to shields if (attacker && attacker.type === 'wizard' && self.type === 'shield') { amount = Math.floor(amount * 1.5); } if (amount > 0) { self.health -= amount; } self.updateHealthBar(); // Flash unit when hit LK.effects.flashObject(self, 0xff0000, 300); if (self.health <= 0) { self.die(); } }; self.die = function () { self.active = false; // Play 'Destroyed' sound for mechanical units, including plane if (self.type === 'warMachine' || self.type === 'cannon' || self.type === 'catapult' || self.type === 'jeep' || self.type === 'ballista' || self.type === 'plane' || self.type === 'vfFighter') { LK.getSound('Destroyed').play(); } else if (self.type === 'bombThrower' || self.type === 'chicken') { // Play both die and explosion sounds simultaneously for bombThrower and chicken if (self.type === 'chicken') { LK.getSound('Bawk').play(); } else { LK.getSound('death').play(); } LK.getSound('Explosion').play(); } else if (self.type === 'queen' || self.type === 'witch') { LK.getSound('Girldeath').play(); } else if (self.type === 'raptor' || self.type === 't-rex' || self.type === 't-rexJr' || self.type === 'brachiosaur' || self.type === 'triceratops' || self.type === 'brachiosaurGod') { // Dinosaur death sounds - use RAWR sound for all dinosaurs LK.getSound('RAWR').play(); } else if (self.type === 'vikingBearRider') { // Spawn Hammer Viking on death var hammerViking = new Unit(self.team, 'hammer', self.x, self.y); units.push(hammerViking); game.addChild(hammerViking); // Play bear death sound LK.getSound('RAWR').play(); } else if (self.type === 'elephantRiders') { // Spawn 4 Roman javelineers on death for (var i = 0; i < 4; i++) { var angle = Math.PI * 2 * i / 4; // Spread evenly in a circle var offsetX = Math.cos(angle) * 50; var offsetY = Math.sin(angle) * 50; var javelineer = new Unit(self.team, 'romanJavelinThrower', self.x + offsetX, self.y + offsetY); units.push(javelineer); game.addChild(javelineer); } // Play elephant death sound LK.getSound('RAWR').play(); } else if (self.type === 'robotBlaster' || self.type === 'explodingSpiderBot' || self.type === 'robotTRex' || self.type === 'mecha') { LK.getSound('Malfunction').play(); } else { LK.getSound('death').play(); } // Fade out and remove from battlefield tween(self, { alpha: 0 }, { duration: 500, onFinish: function onFinish() { battlefield.removeUnit(self); self.destroy(); } }); }; self.findNearestEnemy = function () { var closestDistSq = Infinity; var closestEnemy = null; var targetedEnemy = null; var targetedDistSq = Infinity; var isSelfGroundMelee = !self.flying && self.attackType === 'melee'; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { // Check if 'self' (the attacker) is a ground melee unit // and the potential 'unit' (target) is flying. // Ground melee units should not target flying units. // Valkyries are an exception (can hit planes), but this logic primarily prevents ground units targeting flyers. // Note: Assumes melee units have `attackType === 'melee'` defined. if (isSelfGroundMelee && unit.flying) { continue; // Skip this flying unit as a target for ground melee } // Use squared distance to avoid expensive sqrt calculation var dx = unit.x - self.x; var dy = unit.y - self.y; var distSq = dx * dx + dy * dy; // Check if unit has target priority (marked by monkey) if (unit.targetPriority) { if (distSq < targetedDistSq) { targetedDistSq = distSq; targetedEnemy = unit; } } else { if (distSq < closestDistSq) { closestDistSq = distSq; closestEnemy = unit; // Early exit if we found a very close enemy if (distSq < 10000) { // 100 pixels squared return closestEnemy; } } } } } // Prioritize targeted enemies return targetedEnemy || closestEnemy; }; self.moveTowardsTarget = function () { // Only search for new target every 30 frames instead of every frame if (!self.targetSearchCooldown) { self.targetSearchCooldown = 0; } if (self.targetSearchCooldown > 0) { self.targetSearchCooldown--; } if (!self.target || !self.target.active || self.targetSearchCooldown === 0) { if (self.type === 'spearman' && Math.random() < 0.3) { // 30% chance to throw spear self.target = self.findNearestEnemy(); if (self.target) { battlefield.createProjectile(self, self.target, 'javelin'); self.attackCooldown = 60; // Reset cooldown after throwing spear return; } } self.target = self.findNearestEnemy(); self.targetSearchCooldown = 30; // Search again in 30 frames if (!self.target) { return; } } if (!self.target) { return; } var dx = self.target.x - self.x; var dy = self.target.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // If in attack range, stop moving if (dist <= self.attackRange) { if (self.attackCooldown === 0) { self.attack(); } return; } // Normalize direction and move var moveX = dx / dist * self.moveSpeed; var moveY = dy / dist * self.moveSpeed; self.x += moveX; self.y += moveY; }; self.attack = function () { // Track number of attacks for Retarius if (self.type === 'retarius') { if (!self.attackCount) { self.attackCount = 0; } self.attackCount++; // Stun effect every 3 attacks if (self.attackCount % 3 === 0 && self.target && self.target.type !== 'warMachine') { self.target.stunned = true; LK.setTimeout(function () { if (self.target) { self.target.stunned = false; } }, 2000); // Stun duration of 2 seconds } } // Chicken special behavior - explode when seeing enemy if (self.type === 'chicken' && self.target && self.active) { var dist = getDistance(self.x, self.y, self.target.x, self.target.y); if (dist <= self.attackRange) { // Explode and damage nearby enemies LK.getSound('Explosion').play(); // Create explosion visual effect var explosion = new Container(); var explosionGraphics = explosion.attachAsset('bomb', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 200, color: 0xFF5500 }); explosion.x = self.x; explosion.y = self.y; explosion.alpha = 0.8; battlefield.addChild(explosion); // Fade out explosion tween(explosion, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Deal damage to nearby enemy units for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { var distToUnit = getDistance(self.x, self.y, unit.x, unit.y); if (distToUnit <= 200) { // Damage falls off with distance var damageMultiplier = 1 - distToUnit / 200; var damage = Math.floor(self.attackDamage * damageMultiplier); unit.takeDamage(damage, self); } } } // Kill the chicken self.die(); return; } } if (!self.target || !self.target.active) { self.target = self.findNearestEnemy(); if (!self.target) { return; } } // Brachiosaur is a pacifist and doesn't attack if (self.type === 'brachiosaur') { return; } if (self.attackCooldown === 0) { // Ensure attack is only called once per cooldown cycle self.attackCooldown = self.type === 'wobbler' ? 40 : self.type === 'farmer' ? 60 : self.type === 'archer' ? 80 : self.type === 'shield' ? 50 : self.type === 'sword' ? 45 : self.type === 'jeep' ? 20 : self.type === 'raptor' ? 30 : self.type === 't-rex' ? 90 : // Set Jeep cooldown to 0.333 seconds (20 frames) 100; self.attack(); } var dist = self.target ? getDistance(self.x, self.y, self.target.x, self.target.y) : Infinity; if (self.target && dist <= self.attackRange) { // Calculate damage based on unit type var damage = self.attackDamage; // Raptor pack bonus: increase damage when near other raptors if (self.type === 'raptor') { var nearbyRaptors = 0; for (var i = 0; i < units.length; i++) { if (units[i].active && units[i].type === 'raptor' && units[i].team === self.team && units[i] !== self) { var raptorDist = getDistance(self.x, self.y, units[i].x, units[i].y); if (raptorDist < 200) { nearbyRaptors++; } } } // 20% damage boost per nearby raptor damage += Math.floor(damage * 0.2 * nearbyRaptors); } // T-Rex does extra damage to non-living things if (self.type === 't-rex' && self.target && !self.target.isLiving) { damage = Math.floor(damage * 1.5); // 50% more damage to non-living units } // Ensure all units deal damage to enemies if (self.target && self.target.team !== self.team) { self.target.takeDamage(damage, self); } // Reset attack cooldown self.attackCooldown = self.type === 'wobbler' ? 40 : self.type === 'farmer' ? 60 : self.type === 'archer' ? 80 : self.type === 'shield' ? 50 : self.type === 'sword' ? 45 : self.type === 'jeep' ? 20 : self.type === 'raptor' ? 30 : self.type === 't-rex' ? 90 : // Set Jeep cooldown to 0.333 seconds (20 frames) 100; // Handle special attacks if (self.type === 'appleThrower') { // Apple thrower finds injured ally instead of enemy var lowestHealth = Infinity; var allyToHeal = null; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team === self.team && unit !== self && unit.health < unit.maxHealth) { var distToAlly = getDistance(self.x, self.y, unit.x, unit.y); if (distToAlly <= self.attackRange && unit.health < lowestHealth) { lowestHealth = unit.health; allyToHeal = unit; } } } if (allyToHeal) { // Throw apple at ally battlefield.createProjectile(self, allyToHeal, 'apple'); allyToHeal.health = Math.min(allyToHeal.maxHealth, allyToHeal.health + Math.abs(self.attackDamage)); allyToHeal.updateHealthBar(); LK.effects.flashObject(allyToHeal, 0x00ff00, 300); } } else if (self.special === "ranged") { if (self.type === 'pirateCaptain') { LK.getSound('Shoot').play(); } if (self.type === 'pirateCrew') { LK.getSound('Shoot').play(); } if (self.type === 'rifleman') { LK.getSound('Shoot').play(); } if (self.type === 'sniper') { LK.getSound('Shoot').play(); } // Create arrow projectile battlefield.createProjectile(self, self.target, 'arrow'); LK.getSound('arrow').play(); LK.getSound('Shoot').play(); } else if (self.type === 'pistolero') { // Pistolero quick draw attack battlefield.createProjectile(self, self.target, 'bullet'); LK.getSound('Shoot').play(); } else if (self.special === "fast but inaccurate shooting") { // Gatling Gunner shoots 25 bullets per second aiming at the target with a chance to miss for (var i = 0; i < 25; i++) { var angleToTarget = Math.atan2(self.target.y - self.y, self.target.x - self.x); var missChance = Math.random() * 0.2 - 0.1; // Random miss angle between -0.1 and 0.1 radians var bullet = new Projectile(self, null, 'bullet'); bullet.x = self.x; bullet.y = self.y; bullet.rotation = angleToTarget + missChance; bullet.speed = 12; // Set bullet speed bullet.update = function () { this.x += Math.cos(this.rotation) * this.speed; this.y += Math.sin(this.rotation) * this.speed; // Check if bullet is out of bounds if (this.x < 0 || this.x > 2970 || this.y < 0 || this.y > 1981) { this.destroy(); } }; battlefield.addChild(bullet); battlefield.projectiles.push(bullet); } LK.getSound('attack').play(); } else if (self.special === "vehicle, machine gun") { // Jeep fires ONE bullet every 0.333 seconds var angleToTarget = Math.atan2(self.target.y - self.y, self.target.x - self.x); // Create a single bullet with no spread for accuracy var bullet = new Projectile(self, null, 'bullet'); bullet.x = self.x; bullet.y = self.y; bullet.rotation = angleToTarget; bullet.speed = 13; bullet.update = function () { this.x += Math.cos(this.rotation) * this.speed; this.y += Math.sin(this.rotation) * this.speed; // Check if bullet is out of bounds if (this.x < 0 || this.x > 2970 || this.y < 0 || this.y > 1981) { this.destroy(); } }; battlefield.addChild(bullet); battlefield.projectiles.push(bullet); LK.getSound('Shoot').play(); } else if (self.type === 'mecha') { // Mecha machine gun attack - fires 3 bullets with slight spread for (var i = 0; i < 3; i++) { var angleToTarget = Math.atan2(self.target.y - self.y, self.target.x - self.x); var spread = (i - 1) * 0.1; // -0.1, 0, 0.1 radians spread var bullet = new Projectile(self, null, 'bullet'); bullet.x = self.x; bullet.y = self.y; bullet.rotation = angleToTarget + spread; bullet.speed = 15; // Faster bullets bullet.damage = self.attackDamage; // Triple damage bullets bullet.update = function () { this.x += Math.cos(this.rotation) * this.speed; this.y += Math.sin(this.rotation) * this.speed; // Check collision with enemies for (var j = 0; j < units.length; j++) { var unit = units[j]; if (unit.active && unit.team !== self.team) { var dist = getDistance(this.x, this.y, unit.x, unit.y); if (dist < 30) { unit.takeDamage(this.damage, self); this.destroy(); return; } } } // Check if bullet is out of bounds if (this.x < 0 || this.x > 2970 || this.y < 0 || this.y > 1981) { this.destroy(); } }; battlefield.addChild(bullet); battlefield.projectiles.push(bullet); } LK.getSound('Shoot').play(); } else if (self.type === 'miniMecha') { // Mini Mecha machine gun attack - fires 2 bullets with slight spread (less than full mecha) for (var i = 0; i < 2; i++) { var angleToTarget = Math.atan2(self.target.y - self.y, self.target.x - self.x); var spread = (i - 0.5) * 0.08; // -0.04, 0.04 radians spread (tighter than mecha) var bullet = new Projectile(self, null, 'bullet'); bullet.x = self.x; bullet.y = self.y; bullet.rotation = angleToTarget + spread; bullet.speed = 12; // Slower than mecha bullets bullet.damage = self.attackDamage; bullet.update = function () { this.x += Math.cos(this.rotation) * this.speed; this.y += Math.sin(this.rotation) * this.speed; // Check collision with enemies for (var j = 0; j < units.length; j++) { var unit = units[j]; if (unit.active && unit.team !== self.team) { var dist = getDistance(this.x, this.y, unit.x, unit.y); if (dist < 30) { unit.takeDamage(this.damage, self); this.destroy(); return; } } } // Check if bullet is out of bounds if (this.x < 0 || this.x > 2970 || this.y < 0 || this.y > 1981) { this.destroy(); } }; battlefield.addChild(bullet); battlefield.projectiles.push(bullet); } LK.getSound('Shoot').play(); } else if (self.special === "bomb explosion") { // Cannon bomb explosion battlefield.createProjectile(self, self.target, 'bomb'); LK.getSound('Explosion').play(); var damage = self.attackDamage; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { var aoeDistance = getDistance(self.target.x, self.target.y, unit.x, unit.y); if (aoeDistance < 200) { var aoeDamage = Math.floor(damage * (1 - aoeDistance / 200)); if (aoeDamage > 0) { unit.takeDamage(aoeDamage, self); } } } } } else if (self.special === "splits into smaller rocks") { // Catapult attack logic battlefield.createProjectile(self, self.target, 'rock'); LK.getSound('Explosion').play(); } else if (self.special === "pierces through 3 enemies") { battlefield.createProjectile(self, self.target, 'javelin'); LK.getSound('arrow').play(); } else if (self.special === "magic ball") { battlefield.createProjectile(self, self.target, 'spell'); LK.getSound('spell').play(); } else if (self.special === "seduces enemies") { // Queen's seduction ability if (Math.random() < 0.1 && self.target.type !== 'warMachine' && self.target.type !== 'king') { self.target.team = 'blue'; // Change team to blue self.target.updateHealthBar(); // Update health bar to reflect team change self.target.findNearestEnemy(); // Reassign target to find new enemies self.target.target = null; // Stop attacking the Queen self.target = null; // Queen stops attacking the seduced enemy LK.getSound('Kiss').play(); // Play 'Kiss' sound when Queen seduces an enemy } // Wizard AOE attack battlefield.createProjectile(self, self.target, 'spell'); if (self.type === 'wizard') { LK.getSound('spell').play(); } else { LK.getSound('Explosion').play(); } // Damage the target and nearby enemies var damage = self.attackDamage; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { if (self.target) { var aoeDistance = getDistance(self.target.x, self.target.y, unit.x, unit.y); } if (aoeDistance < 150) { // Damage falls off with distance var aoeDamage = Math.floor(damage * (1 - aoeDistance / 150)); if (aoeDamage > 0) { unit.takeDamage(aoeDamage, self); } } } } } else if (self.special === "throws bombs that explode after 2 seconds") { // Bomb thrower attack var bomb = new Bomb(self, self.target); battlefield.addChild(bomb); battlefield.projectiles.push(bomb); LK.getSound('attack').play(); } else if (self.special === "curses enemies with crows") { if (!self.attackCount) { self.attackCount = 0; } self.attackCount++; if (self.attackCount % 3 === 0) { // Scarecrow shoots crows every 3 attacks battlefield.createProjectile(self, self.target, 'crow'); LK.getSound('Gnehehe').play(); // Reduce target's attack by 5 points if (self.target) { self.target.attackDamage = Math.max(0, self.target.attackDamage - 5); } } // Melee attack LK.getSound('attack').play(); var damage = self.attackDamage; // Viking Bear Rider AoE damage if (self.type === 'vikingBearRider' && self.target) { var aoeRadius = 100; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team && unit !== self.target) { // Don't hit primary target twice with AoE var aoeDist = getDistance(self.target.x, self.target.y, unit.x, unit.y); // AoE centered on target if (aoeDist <= aoeRadius) { var aoeDamageMultiplier = 1 - aoeDist / aoeRadius; // Damage falls off var finalAoeDamage = Math.floor(damage * 0.5 * aoeDamageMultiplier); // AoE deals 50% base damage max if (finalAoeDamage > 0) { unit.takeDamage(finalAoeDamage, self); } } } } // Play bear attack sound LK.getSound('RAWR').play(); } // Double damage if Berserker is enraged (health is at or below half) if (self.type === 'berserker' && self.health <= self.maxHealth / 2) { damage *= 2; self.moveSpeed = 2.0; // Increase move speed when enraged } // Implement critical hit chance for Crossbowman if (self.type === 'crossbowMan' && Math.random() < 0.2) { // 20% chance for critical hit damage *= 2; // Double damage for critical hit } // First strike bonus for farmers if (self.special === "firstStrike" && !self.hasAttacked) { damage *= 1.5; // 50% bonus on first hit self.hasAttacked = true; } self.target.takeDamage(damage, self); } else if (self.special === "plasma rifle") { // Robot Blaster attack logic battlefield.createProjectile(self, self.target, 'bullet'); LK.getSound('Blaster').play(); } else if (self.special === "self-destruct with 2s stun") { // Exploding Spider Bot attack logic if (getDistance(self.x, self.y, self.target.x, self.target.y) <= self.attackRange) { // Deal damage self.target.takeDamage(self.attackDamage, self); // Apply 2 second stun to target if it's still alive and not immune if (self.target && self.target.active && self.target.type !== 'warMachine' && self.target.type !== 'mecha') { self.target.stunned = true; LK.setTimeout(function () { if (self.target && self.target.active) { self.target.stunned = false; } }, 2000); // 2 second stun } self.die(); // Self-destruct after attacking } } else if (self.special === "big arrows with long range") { // Ballista attack logic // Create big arrow projectile var bigArrow = new Projectile(self, self.target, 'bigArrow'); bigArrow.x = self.x; bigArrow.y = self.y; bigArrow.speed = 18; // Faster than normal arrows bigArrow.damage = self.attackDamage; battlefield.addChild(bigArrow); battlefield.projectiles.push(bigArrow); LK.getSound('arrow').play(); } else if (self.special === "weak magic") { // Apprentice attack logic battlefield.createProjectile(self, self.target, 'spell'); LK.getSound('spell').play(); // Deal smaller splash damage to target area var damage = self.attackDamage; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { var aoeDistance = getDistance(self.target.x, self.target.y, unit.x, unit.y); if (aoeDistance < 100) { // Smaller area than full wizard var aoeDamage = Math.floor(damage * (1 - aoeDistance / 100)); if (aoeDamage > 0) { unit.takeDamage(aoeDamage, self); } } } } } else if (self.type === "priest") { // Priest healing logic: heal the most injured ally in range var lowestHealth = Infinity; var allyToHeal = null; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team === self.team && unit !== self && unit.health < unit.maxHealth) { var distToAlly = getDistance(self.x, self.y, unit.x, unit.y); if (distToAlly <= self.attackRange && unit.health < lowestHealth) { lowestHealth = unit.health; allyToHeal = unit; } } } if (allyToHeal) { allyToHeal.health = Math.min(allyToHeal.maxHealth, allyToHeal.health + 20); allyToHeal.updateHealthBar(); LK.effects.flashObject(allyToHeal, 0x00ffcc, 400); LK.getSound('Holy').play(); // Play holy sound when priest heals ally } } else if (self.type === "jarl") { // Jarl's arena of ice: trap the target and nearby enemies for 2 seconds if (self.target && getDistance(self.x, self.y, self.target.x, self.target.y) <= self.attackRange) { for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { var distToArena = getDistance(self.target.x, self.target.y, unit.x, unit.y); if (distToArena < 120) { unit.stunned = true; LK.effects.flashObject(unit, 0x99e6ff, 600); (function (unitRef) { LK.setTimeout(function () { if (unitRef) { unitRef.stunned = false; } }, 2000); })(unit); } } } } } // This is now handled within the plane's update logic, but we need the attack action itself } else if (self.type === 'plane') { // This function is called when the plane is in position and ready to attack // Plane drops a pineapple below itself var pineapple = new Pineapple(self); pineapple.x = self.x; // Ensure it drops exactly below the plane pineapple.y = self.y; battlefield.addChild(pineapple); // Add to projectiles array for management battlefield.projectiles.push(pineapple); // Reset cooldown AFTER attacking self.attackCooldown = 180; // 3 seconds cooldown remains correct // No return needed here as the calling logic in update handles flow } else if (self.type === 'vfFighter') { // VF fighter shoots missiles at enemies if (self.target) { battlefield.createProjectile(self, self.target, 'missile'); LK.getSound('Shoot').play(); } } else if (self.type === 'miachelJames') { // Miachel James throws basketballs that steal health var basketball = new Basketball(self, self.target); basketball.x = self.x; basketball.y = self.y; battlefield.addChild(basketball); battlefield.projectiles.push(basketball); LK.getSound('attack').play(); } else if (self.type === 'triceratops') { // Triceratops stun attack if (self.target && self.target.type !== 'warMachine' && self.target.type !== 'mecha') { // Stun target for 0.5 seconds self.target.stunned = true; LK.setTimeout(function () { if (self.target) { self.target.stunned = false; } }, 500); // 0.5 second stun // Deal damage self.target.takeDamage(self.attackDamage, self); LK.getSound('RAWR').play(); } } else if (self.type === 'death') { // Death: deals 1 million damage to a single target, no splash, but epic effect if (self.target && self.target.active && getDistance(self.x, self.y, self.target.x, self.target.y) <= self.attackRange) { // Play spell sound and flash LK.getSound('spell').play(); LK.effects.flashObject(self.target, 0x000000, 600); // Visual effect: dark explosion var explosion = new Container(); var explosionGraphics = explosion.attachAsset('spell', { anchorX: 0.5, anchorY: 0.5, width: 200, height: 200, color: 0x111111 }); explosion.x = self.target.x; explosion.y = self.target.y; explosion.alpha = 0.9; battlefield.addChild(explosion); tween(explosion, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, onFinish: function onFinish() { explosion.destroy(); } }); // Instantly destroy the target (unless invincible) self.target.takeDamage(self.attackDamage, self); } } else if (self.type === 'archmage') { // Archmage combines magic attack, necromancy, and bomb throwing // Magic attack with splash damage battlefield.createProjectile(self, self.target, 'spell'); LK.getSound('spell').play(); var damage = self.attackDamage; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { var aoeDistance = getDistance(self.target.x, self.target.y, unit.x, unit.y); if (aoeDistance < 150) { var aoeDamage = Math.floor(damage * (1 - aoeDistance / 150)); if (aoeDamage > 0) { unit.takeDamage(aoeDamage, self); } } } } } }; self.showStats = function () { var statsText = "Type: " + self.type.charAt(0).toUpperCase() + self.type.slice(1) + "\n" + "Health: " + self.health + "/" + self.maxHealth + "\n" + "Attack Damage: " + self.attackDamage + "\n" + "Attack Range: " + self.attackRange + "\n" + "Living: " + (self.isLiving ? "Yes" : "No") + "\n" + "Special: " + (self.special ? self.special : "None"); var statsDisplay = new Text2(statsText, { size: 30, fill: 0xFFFFFF }); statsDisplay.anchor.set(0.5, 0.5); statsDisplay.x = self.x; statsDisplay.y = self.y - 100; game.addChild(statsDisplay); // Remove stats display after 3 seconds LK.setTimeout(function () { if (statsDisplay.parent) { statsDisplay.destroy(); } }, 3000); }; self.down = function (x, y, obj) { if (self.lastTap && Date.now() - self.lastTap < 300) { self.showStats(); } self.lastTap = Date.now(); }; self.update = function () { if (!self.active || self.stunned) { return; } // Roman Legionnaire invincibility logic if (self.type === 'romanLegionnaire') { if (!self.invincibilityCooldown) { self.invincibilityCooldown = 300; // 5 seconds at 60 FPS } if (self.invincibilityCooldown > 0) { self.invincibilityCooldown--; } if (self.invincibilityCooldown === 0) { self.invincible = true; LK.getSound('Defend').play(); // Play 'Defend' sound LK.setTimeout(function () { self.invincible = false; self.invincibilityCooldown = 300; // Reset cooldown }, 1000); // Invincibility duration of 1 second } } // Mecha special abilities if (self.type === 'mecha') { // Update all special cooldowns if (self.bombCooldown > 0) { self.bombCooldown--; } if (self.skeletonCooldown > 0) { self.skeletonCooldown--; } if (self.curseCooldown > 0) { self.curseCooldown--; } // Invincibility like Roman Legionnaire if (!self.invincibilityCooldown) { self.invincibilityCooldown = 300; // 5 seconds at 60 FPS } if (self.invincibilityCooldown > 0) { self.invincibilityCooldown--; } if (self.invincibilityCooldown === 0) { self.invincible = true; LK.getSound('Defend').play(); LK.setTimeout(function () { self.invincible = false; self.invincibilityCooldown = 300; // Reset cooldown }, 1000); // Invincibility for 1 second } // Bomb throwing ability (every 3 seconds) if (self.bombCooldown === 0 && battlefield.initialized) { var nearestEnemy = self.findNearestEnemy(); if (nearestEnemy) { var bomb = new Bomb(self, nearestEnemy); battlefield.addChild(bomb); battlefield.projectiles.push(bomb); self.bombCooldown = 180; // 3 seconds cooldown } } // Skeleton summoning (every 5 seconds) if (self.skeletonCooldown === 0 && battlefield.initialized) { // Count current skeletons var skeletonCount = units.filter(function (unit) { return unit.type === 'skeleton' && unit.active; }).length; if (skeletonCount < 5) { // Allow up to 5 skeletons var skeleton = new Unit(self.team, 'skeleton', self.x + (Math.random() - 0.5) * 100, self.y + (Math.random() - 0.5) * 100); units.push(skeleton); game.addChild(skeleton); LK.getSound('Gnehehe').play(); self.skeletonCooldown = 300; // 5 seconds cooldown } } // Scarecrow curse ability (every 4 seconds) if (self.curseCooldown === 0 && battlefield.initialized) { // Find all enemies in range for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { var dist = getDistance(self.x, self.y, unit.x, unit.y); if (dist <= 250) { // Curse range unit.attackDamage = Math.max(0, unit.attackDamage - 5); LK.effects.flashObject(unit, 0x800080, 500); // Purple flash } } } LK.getSound('Gnehehe').play(); self.curseCooldown = 240; // 4 seconds cooldown } } // Robot T-REX beam attack logic if (self.type === 'robotTRex' && battlefield.initialized) { // Find target for beam if (!self.beamTarget || !self.beamTarget.active) { self.beamTarget = self.findNearestEnemy(); } if (self.beamTarget) { var dist = getDistance(self.x, self.y, self.beamTarget.x, self.beamTarget.y); if (dist <= self.attackRange) { // Activate beam if (!self.beamActive) { self.beamActive = true; // Create beam visual if (!self.beam) { self.beam = self.attachAsset('beam', { anchorX: 0, anchorY: 0.5, alpha: 0.7 }); } } // Update beam position and rotation if (self.beam) { var angle = Math.atan2(self.beamTarget.y - self.y, self.beamTarget.x - self.x); self.beam.rotation = angle; self.beam.width = dist; // Deal continuous damage (1 damage every 2.5 milliseconds = 40 frames at 60fps = ~1.5 frames) if (LK.ticks % 2 === 0) { // Approximately every 33ms (close to 2.5ms requirement) var damage = 1; // Extra damage to living units if (self.beamTarget.isLiving) { damage = 2; // Double damage to living units } self.beamTarget.takeDamage(damage, self); } } } else { // Deactivate beam if target out of range self.beamActive = false; if (self.beam) { self.beam.visible = false; } self.beamTarget = null; } } else { // No target, deactivate beam self.beamActive = false; if (self.beam) { self.beam.visible = false; } } // Make beam visible/invisible based on active state if (self.beam) { self.beam.visible = self.beamActive; } } // Dragon fire breath attack logic if (self.type === 'dragon' && battlefield.initialized) { // Find target for fire breath if (!self.fireBreathTarget || !self.fireBreathTarget.active) { self.fireBreathTarget = self.findNearestEnemy(); } if (self.fireBreathTarget) { var dist = getDistance(self.x, self.y, self.fireBreathTarget.x, self.fireBreathTarget.y); if (dist <= self.attackRange) { // Activate fire breath if (!self.fireBreathActive) { self.fireBreathActive = true; // Create fire breath visual if (!self.fireBreath) { self.fireBreath = self.attachAsset('fireBreath', { anchorX: 0, anchorY: 0.5, alpha: 0.8, color: 0xff4500 }); } } // Update fire breath position and rotation if (self.fireBreath) { var angle = Math.atan2(self.fireBreathTarget.y - self.y, self.fireBreathTarget.x - self.x); self.fireBreath.rotation = angle; self.fireBreath.width = dist; // Deal continuous fire damage to all units in the breath path for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { // Check if unit is in the fire breath path var unitDist = getDistance(self.x, self.y, unit.x, unit.y); if (unitDist <= dist) { // Check angle to see if unit is in the fire cone var unitAngle = Math.atan2(unit.y - self.y, unit.x - self.x); var angleDiff = Math.abs(unitAngle - angle); if (angleDiff < 0.3) { // Fire cone angle // Deal fire damage every few frames if (LK.ticks % 4 === 0) { unit.takeDamage(Math.floor(self.attackDamage / 10), self); } } } } } } } else { // Deactivate fire breath if target out of range self.fireBreathActive = false; if (self.fireBreath) { self.fireBreath.visible = false; } self.fireBreathTarget = null; } } else { // No target, deactivate fire breath self.fireBreathActive = false; if (self.fireBreath) { self.fireBreath.visible = false; } } // Make fire breath visible/invisible based on active state if (self.fireBreath) { self.fireBreath.visible = self.fireBreathActive; } } // Archmage special abilities if (self.type === 'archmage' && battlefield.initialized) { // Update cooldowns if (self.bombCooldown > 0) { self.bombCooldown--; } if (self.skeletonCooldown > 0) { self.skeletonCooldown--; } // Bomb throwing ability (every 4 seconds) if (self.bombCooldown === 0) { var nearestEnemy = self.findNearestEnemy(); if (nearestEnemy) { var bomb = new Bomb(self, nearestEnemy); battlefield.addChild(bomb); battlefield.projectiles.push(bomb); self.bombCooldown = 240; // 4 seconds cooldown } } // Skeleton summoning (every 6 seconds) if (self.skeletonCooldown === 0) { var skeletonCount = units.filter(function (unit) { return unit.type === 'skeleton' && unit.active && unit.team === self.team; }).length; if (skeletonCount < 3) { var skeleton = new Unit(self.team, 'skeleton', self.x + (Math.random() - 0.5) * 100, self.y + (Math.random() - 0.5) * 100); units.push(skeleton); game.addChild(skeleton); LK.getSound('Gnehehe').play(); self.skeletonCooldown = 360; // 6 seconds cooldown } } } // Monkey smuggler special ability if (self.type === 'monkeySmuggler' && battlefield.initialized) { if (self.monkeyCooldown > 0) { self.monkeyCooldown--; } // Throw monkey every 3 seconds if (self.monkeyCooldown === 0) { var nearestEnemy = self.findNearestEnemy(); if (nearestEnemy && getDistance(self.x, self.y, nearestEnemy.x, nearestEnemy.y) <= self.attackRange) { // Create monkey projectile var monkey = new Container(); var monkeyGraphics = monkey.attachAsset('monkey', { anchorX: 0.5, anchorY: 0.5 }); monkey.x = self.x; monkey.y = self.y; monkey.target = nearestEnemy; monkey.speed = 8; monkey.source = self; // Monkey update function monkey.update = function () { if (!this.target || !this.target.active) { this.destroy(); return; } var dx = this.target.x - this.x; var dy = this.target.y - this.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > this.speed) { this.x += dx / dist * this.speed; this.y += dy / dist * this.speed; } else { // Hit target - remove invincibility and add target marker if (this.target.invincible) { this.target.invincible = false; this.target.invincibilityCooldown = 300; // Reset cooldown } // Add target marker if (!this.target.targetMarker) { this.target.targetMarker = this.target.attachAsset('targetSign', { anchorX: 0.5, anchorY: 0.5, y: -50 }); this.target.targetPriority = true; // Remove marker after 5 seconds LK.setTimeout(function () { if (this.target && this.target.targetMarker) { this.target.targetMarker.destroy(); this.target.targetMarker = null; this.target.targetPriority = false; } }.bind(this), 5000); } this.destroy(); } }; battlefield.addChild(monkey); battlefield.projectiles.push(monkey); self.monkeyCooldown = 180; // 3 seconds cooldown } } } // Barracks spawning logic if (self.type === 'barracks' && battlefield.initialized) { if (self.spawnCooldown > 0) { self.spawnCooldown--; } if (self.spawnCooldown === 0) { // Spawn rifleman near the barracks var spawnX = self.x + (Math.random() - 0.5) * 100; var spawnY = self.y + (Math.random() - 0.5) * 100; var rifleman = new Unit(self.team, 'rifleman', spawnX, spawnY); units.push(rifleman); game.addChild(rifleman); self.spawnCooldown = 300; // Reset to 5 seconds } } // Robot Factory spawning logic if (self.type === 'robotFactory' && battlefield.initialized) { if (self.spawnCooldown > 0) { self.spawnCooldown--; } if (self.spawnCooldown === 0) { // Spawn robot blaster near the factory var spawnX = self.x + (Math.random() - 0.5) * 100; var spawnY = self.y + (Math.random() - 0.5) * 100; var robotBlaster = new Unit(self.team, 'robotBlaster', spawnX, spawnY); units.push(robotBlaster); game.addChild(robotBlaster); self.spawnCooldown = 420; // Reset to 7 seconds } } // Chicken Coop spawning logic if (self.type === 'chickenCoop' && battlefield.initialized) { if (self.spawnCooldown > 0) { self.spawnCooldown--; } if (self.spawnCooldown === 0) { // Spawn chicken near the coop var spawnX = self.x + (Math.random() - 0.5) * 100; var spawnY = self.y + (Math.random() - 0.5) * 100; var chicken = new Unit(self.team, 'chicken', spawnX, spawnY); units.push(chicken); game.addChild(chicken); LK.getSound('Bawk').play(); self.spawnCooldown = 270; // Reset to 4.5 seconds } } // Tombstone spawning logic if (self.type === 'tombstone' && battlefield.initialized) { if (self.spawnCooldown > 0) { self.spawnCooldown--; } if (self.spawnCooldown === 0) { // Spawn 2 skeletons near the tombstone for (var i = 0; i < 2; i++) { var spawnX = self.x + (Math.random() - 0.5) * 100; var spawnY = self.y + (Math.random() - 0.5) * 100; var skeleton = new Unit(self.team, 'skeleton', spawnX, spawnY); units.push(skeleton); game.addChild(skeleton); } LK.getSound('Gnehehe').play(); self.spawnCooldown = 360; // Reset to 6 seconds } } // Mini Mecha special abilities (shorter invincibility than full mecha) if (self.type === 'miniMecha') { // Invincibility like Roman Legionnaire but shorter duration if (!self.invincibilityCooldown) { self.invincibilityCooldown = 300; // 5 seconds at 60 FPS } if (self.invincibilityCooldown > 0) { self.invincibilityCooldown--; } if (self.invincibilityCooldown === 0) { self.invincible = true; LK.getSound('Defend').play(); LK.setTimeout(function () { self.invincible = false; self.invincibilityCooldown = 300; // Reset cooldown }, 500); // Shorter invincibility duration (0.5 seconds vs 1 second) } } // Update health bar position to follow unit self.updateHealthBar(); // Decrease attack cooldown if (self.attackCooldown > 0) { self.attackCooldown--; } // Cavalry uncontrollable movement if (self.type === 'cavalry' && battlefield.initialized) { // Update turn cooldown if (self.turnCooldown > 0) { self.turnCooldown--; } // Randomly change direction sometimes if (self.turnCooldown === 0 && Math.random() < 0.1) { self.moveAngle += (Math.random() - 0.5) * Math.PI * 0.5; // Turn up to 45 degrees self.turnCooldown = 30; // Don't turn again for 0.5 seconds } // Move in current direction at high speed self.x += Math.cos(self.moveAngle) * self.moveSpeed; self.y += Math.sin(self.moveAngle) * self.moveSpeed; // Bounce off edges if (self.x < 50 || self.x > battlefield.width - 50) { self.moveAngle = Math.PI - self.moveAngle; self.x = Math.max(50, Math.min(battlefield.width - 50, self.x)); } if (self.y < 50 || self.y > battlefield.height - 50) { self.moveAngle = -self.moveAngle; self.y = Math.max(50, Math.min(battlefield.height - 50, self.y)); } // Attack nearby enemies while moving if (self.attackCooldown === 0) { for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active && unit.team !== self.team) { var dist = getDistance(self.x, self.y, unit.x, unit.y); if (dist <= self.attackRange) { unit.takeDamage(self.attackDamage, self); self.attackCooldown = 45; LK.getSound('attack').play(); break; } } } } } // Find and move towards target if (self.type === 'plane' || self.type === 'vfFighter') { // Plane specific logic: Find enemy, fly towards, drop bomb, fly away/retarget if (!self.target || !self.target.active) { self.target = self.findNearestEnemy(); } if (self.target) { var dx = self.target.x - self.x; // Aim slightly above the target's head maybe? var targetFlyY = self.target.y - 100; // Fly 100px above target var dy = targetFlyY - self.y; var dist = Math.sqrt(dx * dx + dy * dy); // Different attack logic for plane vs VF fighter if (self.type === 'plane') { // If close enough and cooldown ready, attack (drop pineapple) // Let's define "close enough" as within 50 pixels horizontally or vertically if (Math.abs(dx) < 50 && Math.abs(dy) < 50 && self.attackCooldown <= 0 && battlefield.initialized) { self.attack(); // Drop pineapple self.target = null; // Find a new target after dropping } else { // Move towards target // Normalize direction and move } } else if (self.type === 'vfFighter') { // VF fighter attacks from range if (dist <= self.attackRange && self.attackCooldown <= 0 && battlefield.initialized) { self.attack(); // Shoot missile } // Move to maintain optimal distance var moveX = dx / dist * self.moveSpeed; var moveY = dy / dist * self.moveSpeed; // Ensure plane stays roughly in the upper half of the battlefield if (self.y + moveY < battlefield.height * 0.7 || moveY < 0) { self.x += moveX; self.y += moveY; } else { // If moving down would take it too low, just move horizontally self.x += moveX; // Optionally, try to find a new target if stuck low if (self.y > battlefield.height * 0.65) { self.target = null; } } // Optional: Rotate plane towards movement direction // self.rotation = Math.atan2(moveY, moveX); } } else { // No target, fly randomly for a bit? Or towards center? // Let's fly towards a random point in the upper half if (!self.randomTargetX || getDistance(self.x, self.y, self.randomTargetX, self.randomTargetY) < self.moveSpeed * 5) { self.randomTargetX = Math.random() * battlefield.width; self.randomTargetY = (Math.random() * 0.6 + 0.1) * battlefield.height; // Upper 60% } var dxRand = self.randomTargetX - self.x; var dyRand = self.randomTargetY - self.y; var distRand = Math.sqrt(dxRand * dxRand + dyRand * dyRand); if (distRand > self.moveSpeed) { self.x += dxRand / distRand * self.moveSpeed; self.y += dyRand / distRand * self.moveSpeed; } } } else { // Original logic for other units self.moveTowardsTarget(); } if (self.attackCooldown === 0 && battlefield.initialized) { // Necromancer summons skeletons every 1.5 seconds if battle has started if (self.type === 'necromancer' && battlefield.initialized && LK.ticks % 150 === 0) { // Count current skeletons on the battlefield var skeletonCount = units.filter(function (unit) { return unit.type === 'skeleton' && unit.active; }).length; if (skeletonCount < 3) { var skeleton = new Unit(self.team, 'skeleton', self.x, self.y); units.push(skeleton); game.addChild(skeleton); LK.getSound('Gnehehe').play(); } } // self.attack(); // Removed: Attack logic is now handled within moveTowardsTarget or plane specific update logic } }; return self; }); var UnitButton = Container.expand(function (team, type, x, y) { var self = Container.call(this); self.team = team; self.type = type; self.x = x; self.y = y; // Create button appearance var buttonShape = self.attachAsset('button' + team.charAt(0).toUpperCase() + team.slice(1), { anchorX: 0.5, anchorY: 0.5, width: 150, height: 80 }); // Add unit icon var unitIcon = self.attachAsset(type, { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8 }); // Add lock icon if unit is locked var lockIcon = null; if (endlessMode.active && !endlessMode.unlockedUnits.includes(self.type)) { lockIcon = new Text2("🔒", { size: 24, fill: 0xFF0000 }); lockIcon.anchor.set(0.5, 0.5); lockIcon.y = -30; self.addChild(lockIcon); } // Remove lock icon if unit is unlocked self.updateLockIcon = function () { if (endlessMode.unlockedUnits.includes(self.type) && lockIcon && lockIcon.parent) { lockIcon.destroy(); lockIcon = null; } }; self.updateLockIcon(); // Add unit name text var nameText = new Text2(type.charAt(0).toUpperCase() + type.slice(1), { size: 24, fill: 0xFFFFFF }); nameText.anchor.set(0.5, 0.5); nameText.y = 30; self.addChild(nameText); self.down = function (x, y, obj) { // When button is pressed, start dragging a new unit if (endlessMode.active && !endlessMode.unlockedUnits.includes(self.type)) { return; // Prevent adding locked units } var unit = new Unit(self.team, self.type, x, y); // Mark as dragging unit draggingUnit = unit; game.addChild(unit); // Highlight button LK.effects.flashObject(self, 0xffffff, 300); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x87CEEB }); /**** * Game Code ****/ // Define factions and their respective units var endlessModeInstructions = new Text2("Endless Mode Instructions:\n- Place units on the battlefield.\n- Survive waves of enemies.\n- Unlock new units as you progress.\n- Win by unlocking all units.", { size: 30, fill: 0xFFFFFF }); endlessModeInstructions.anchor.set(0.5, 0.5); endlessModeInstructions.x = 2048 / 2; endlessModeInstructions.y = 2732 / 2 - 200; endlessModeInstructions.visible = false; game.addChild(endlessModeInstructions); // Unit costs for campaign mode var unitCosts = { // Farm 'wobbler': 50, 'farmer': 100, 'appleThrower': 150, 'scarecrow': 200, 'chicken': 250, 'pistolero': 180, // Medieval 'sword': 150, 'archer': 200, 'crossbowMan': 250, 'king': 800, 'shield': 200, 'cannon': 500, 'queen': 600, 'catapult': 450, 'priest': 300, // Rome 'spearman': 180, 'retarius': 220, 'romanJavelinThrower': 280, 'romanLegionnaire': 350, 'ballista': 400, // Magic 'mirrorShield': 250, 'wizard': 300, 'necromancer': 400, 'skeleton': 50, 'witch': 350, 'apprentice': 150, 'dragon': 1200, 'archmage': 800, 'death': 1200, // Vikings 'berserker': 250, 'hammer': 300, 'axeThrower': 280, 'jarl': 500, 'vikingBearRider': 400, // Modern 'sniper': 350, 'warMachine': 600, 'rifleman': 250, 'mortar': 450, 'gatlingGunner': 500, 'jeep': 400, 'plane': 700, 'vfFighter': 750, 'barracks': 800, // Pirates 'swashbuckler': 200, 'pirateCrew': 180, 'pirateCaptain': 350, 'bombThrower': 300, 'monkeySmuggler': 380, // Dinosaurs 'raptor': 300, 'brachiosaur': 600, 't-rex': 800, 't-rexJr': 400, 'miachelJames': 450, 'triceratops': 500, // Robots 'robotBlaster': 400, 'explodingSpiderBot': 200, 'robotTRex': 600, 'robotFactory': 750, 'miniMecha': 1200, // Bosses 'mecha': 5000, 'brachiosaurGod': 5000, // Other 'chickenCoop': 500, 'elephantRiders': 600, 'tombstone': 450, 'cavalry': 350, 'vikingWarrior': 300 }; // Define factions before campaignMode to ensure Object.keys(factions) is valid var factions = _defineProperty(_defineProperty(_defineProperty3({ farm: ['wobbler', 'farmer', 'appleThrower', 'scarecrow', 'chicken', 'pistolero', 'chickenCoop'], medieval: ['sword', 'archer', 'crossbowMan', 'king', 'shield', 'cannon', 'queen', 'catapult', 'priest', 'cavalry'], rome: ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire', 'ballista', 'elephantRiders'], magic: ['mirrorShield', 'wizard', 'necromancer', 'skeleton', 'witch', 'apprentice', 'dragon', 'archmage', 'death', 'tombstone'], vikings: ['berserker', 'hammer', 'axeThrower', 'jarl', 'vikingBearRider', 'vikingWarrior'], // Added Viking Bear Rider // Removed Valkyrie modern: ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner', 'jeep', 'plane', 'vfFighter', 'barracks'], pirates: ['swashbuckler', 'pirateCrew', 'pirateCaptain', 'bombThrower', 'monkeySmuggler'], dinosaurs: ['raptor', 'brachiosaur', 't-rex', 't-rexJr', 'miachelJames', 'triceratops'], // Added dinosaur units robots: ['robotBlaster', 'explodingSpiderBot', 'robotTRex', 'robotFactory', 'miniMecha'], // Added Robot Blaster and Exploding Spider Bot to robots faction bosses: ['mecha', 'brachiosaurGod'], // New Bosses faction with Mecha and Brachiosaur GOD stoneage: [], // New Stone Age faction with no units other: [] // Barracks moved to modern faction }, "rome", ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire', 'ballista', 'elephantRiders']), "vikings", ['berserker', 'hammer', 'axeThrower', 'jarl', 'vikingBearRider', 'vikingWarrior']), "modern", ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner', 'jeep', 'plane', 'vfFighter', 'barracks'], "farm", ['wobbler', 'farmer', 'appleThrower', 'scarecrow', 'chicken', 'pistolero', 'chickenCoop']); // Removed Valkyrie, Added Viking Bear Rider, Moved Mecha to Bosses faction, Moved VF Fighter to Modern, Moved Chicken Coop to Farm, Moved Elephant Riders to Rome, Moved Barracks to Modern var campaignMode = { active: false, currentLevel: 1, budget: 5000, playerBudget: 5000, levels: [{ name: "Tutorial", enemyBudget: 500, enemyFactions: ['farm'] }, { name: "Farm Defense", enemyBudget: 1000, enemyFactions: ['farm', 'medieval'] }, { name: "Roman Invasion", enemyBudget: 1500, enemyFactions: ['rome'] }, { name: "Magic Mayhem", enemyBudget: 2000, enemyFactions: ['magic'] }, { name: "Viking Raid", enemyBudget: 2500, enemyFactions: ['vikings'] }, { name: "Modern Warfare", enemyBudget: 3000, enemyFactions: ['modern'] }, { name: "Pirate Attack", enemyBudget: 3500, enemyFactions: ['pirates'] }, { name: "Dinosaur Rampage", enemyBudget: 4000, enemyFactions: ['dinosaurs'] }, { name: "Robot Revolution", enemyBudget: 4500, enemyFactions: ['robots'] }, { name: "Sports Team", enemyBudget: 4500, enemyFactions: ['miachelJamesOnly'], specialUnits: ['miachelJames'] // Only spawn Miachel James units }, { name: "Brachio's Revenge", enemyBudget: 5000, enemyFactions: ['brachiosaurGodOnly'], specialUnits: ['brachiosaurGod'], bannedFactions: ['dinosaurs'] // Dinosaurs are banned for player }, { name: "Just Wobblers", enemyBudget: 3000, enemyFactions: ['wobblerOnly'], specialUnits: ['wobbler'] // Army of 300 wobblers }, { name: "Robots' Revenge", enemyBudget: 7500, enemyFactions: ['mechaOnly'], specialUnits: ['mecha'], bannedFactions: ['robots'] // Robots are banned for player }, { name: "The Chickening", enemyBudget: 2000, enemyFactions: ['farm'], specialUnits: ['chicken'] // Only chickens }, { name: "Smiley", enemyBudget: 2500, enemyFactions: ['medieval', 'farm', 'vikings', 'pirates'], specialUnits: ['smileyLayout'] // Custom smiley face layout }, { name: "Double Mechatron", enemyBudget: 10000, enemyFactions: ['mechaOnly'], specialUnits: ['doubleMecha'] }, { name: "Wobblers", enemyBudget: 520, enemyFactions: ['wobblerOnly'], specialUnits: ['wobblers10swords'], bannedFactions: Object.keys(factions).filter(function (f) { return f !== 'farm'; }) // Only allow farm (wobbler) for player }, { name: "Plane Storm", enemyBudget: 3000, enemyFactions: ['planeStorm'], specialUnits: ['planeStorm'] // Random planes and VF fighters }, { name: "Battle of Carthage", enemyBudget: 3500, enemyFactions: ['carthage'], specialUnits: ['carthage'] // Elephant riders and spearmen }, { name: "Trio Showdown", enemyBudget: 2000, enemyFactions: ['trio'], specialUnits: ['trio'] // 3 random units }, { name: "One, two, T-REX!", enemyBudget: 8000, enemyFactions: ['trexArmy'], specialUnits: ['trexArmy'] // 10 T-REXes }, { name: "The Second Chickening", enemyBudget: 3000, enemyFactions: ['chickenRevenge'], specialUnits: ['chickenRevenge'] // Chickens and chicken coops }, { name: "Spawners Only", enemyBudget: 4000, enemyFactions: ['spawnersOnly'], specialUnits: ['spawnersOnly'] // Factories and coops only }, { name: "Final Battle", enemyBudget: 5000, enemyFactions: ['farm', 'medieval', 'rome', 'magic', 'vikings', 'modern', 'pirates', 'dinosaurs', 'robots'] }], start: function start() { this.active = true; this.currentLevel = 1; this.playerBudget = this.budget; this.showLevelInfo(); this.createBudgetDisplay(); }, showLevelInfo: function showLevelInfo() { var levelInfo = this.levels[this.currentLevel - 1]; var infoText = new Text2("Level " + this.currentLevel + ": " + levelInfo.name + "\nBudget: $" + this.playerBudget, { size: 50, fill: 0xFFFFFF }); infoText.anchor.set(0.5, 0.5); infoText.x = 2048 / 2; infoText.y = 2732 / 2; game.addChild(infoText); LK.setTimeout(function () { if (infoText.parent) { infoText.destroy(); } }, 3000); }, createBudgetDisplay: function createBudgetDisplay() { if (this.budgetText && this.budgetText.parent) { this.budgetText.destroy(); } this.budgetText = new Text2("Budget: $" + this.playerBudget, { size: 40, fill: 0xFFFF00 }); this.budgetText.anchor.set(0.5, 0); LK.gui.top.addChild(this.budgetText); }, updateBudget: function updateBudget(cost) { this.playerBudget -= cost; if (this.budgetText) { this.budgetText.setText("Budget: $" + this.playerBudget); } }, canAfford: function canAfford(unitType) { return this.playerBudget >= (unitCosts[unitType] || 100); }, spawnEnemies: function spawnEnemies() { var levelInfo = this.levels[this.currentLevel - 1]; var enemyBudget = levelInfo.enemyBudget; // Clear any existing campaign buttons before spawning enemies var campaignButtons = game.children.filter(function (child) { return child instanceof CampaignUnitButton; }); campaignButtons.forEach(function (button) { button.destroy(); }); // Handle special unit spawning for specific levels if (levelInfo.specialUnits) { // The Chickening: just chickens if (levelInfo.name === "The Chickening") { var numChickens = Math.floor(enemyBudget / (unitCosts['chicken'] || 250)); for (var i = 0; i < numChickens; i++) { var enemy = new Unit('red', 'chicken', Math.random() * 2970, Math.random() * 1981); units.push(enemy); game.addChild(enemy); } } // Smiley: custom smiley face layout else if (levelInfo.name === "Smiley") { // Eyes var eyeY = 500, eyeX1 = 700, eyeX2 = 1348; var eyeUnits = ['archer', 'archer']; for (var i = 0; i < 2; i++) { var enemy = new Unit('red', eyeUnits[i], i === 0 ? eyeX1 : eyeX2, eyeY); units.push(enemy); game.addChild(enemy); } // Smile (arc of units) var smileY = 900, smileRadius = 300, smileUnits = ['wobbler', 'wobbler', 'farmer', 'farmer', 'pirateCrew', 'pirateCrew', 'berserker', 'berserker']; for (var j = 0; j < smileUnits.length; j++) { var angle = Math.PI * (0.25 + 0.5 * (j / (smileUnits.length - 1))); // from 45deg to 135deg var x = 1024 + Math.cos(angle) * smileRadius; var y = smileY + Math.sin(angle) * smileRadius * 0.7; var enemy = new Unit('red', smileUnits[j], x, y); units.push(enemy); game.addChild(enemy); } // Nose var nose = new Unit('red', 'sword', 1024, 700); units.push(nose); game.addChild(nose); } // Double Mechatron: 2 mechas else if (levelInfo.name === "Double Mechatron") { var mecha1 = new Unit('red', 'mecha', 824, 400); var mecha2 = new Unit('red', 'mecha', 1224, 400); units.push(mecha1); units.push(mecha2); game.addChild(mecha1); game.addChild(mecha2); } // Wobblers: 10 swords, only player can use wobblers else if (levelInfo.name === "Wobblers") { for (var i = 0; i < 10; i++) { var x = 400 + i * 120; var y = 600 + Math.random() * 100; var enemy = new Unit('red', 'sword', x, y); units.push(enemy); game.addChild(enemy); } } // For "Just Wobblers" level, spawn 153 wobblers else if (levelInfo.name === "Just Wobblers") { for (var i = 0; i < 153; i++) { var enemy = new Unit('red', 'wobbler', Math.random() * 2970, Math.random() * 1981); units.push(enemy); game.addChild(enemy); } } else if (levelInfo.name === "Sports Team") { // Spawn many Miachel James units var numUnits = Math.floor(enemyBudget / (unitCosts['miachelJames'] || 450)); for (var i = 0; i < numUnits; i++) { var enemy = new Unit('red', 'miachelJames', Math.random() * 2970, Math.random() * 1981); units.push(enemy); game.addChild(enemy); } } else if (levelInfo.name === "Brachio's Revenge") { // Spawn Brachiosaur GOD and allies var enemy = new Unit('red', 'brachiosaurGod', 1024, 300); units.push(enemy); game.addChild(enemy); // Add some supporting units (not dinosaurs) var supportBudget = 2000; var supportFactions = ['magic', 'medieval', 'vikings']; while (supportBudget > 0) { var faction = supportFactions[Math.floor(Math.random() * supportFactions.length)]; var availableUnits = factions[faction].filter(function (unit) { return unitCosts[unit] <= supportBudget; }); if (availableUnits.length === 0) { break; } var unitType = availableUnits[Math.floor(Math.random() * availableUnits.length)]; var cost = unitCosts[unitType] || 100; var ally = new Unit('red', unitType, Math.random() * 2970, Math.random() * 1981); units.push(ally); game.addChild(ally); supportBudget -= cost; } } else if (levelInfo.name === "Robots' Revenge") { // Spawn Mecha and robot army var enemy = new Unit('red', 'mecha', 1024, 300); units.push(enemy); game.addChild(enemy); // Add robot army var robotBudget = 3000; var robotUnits = ['robotBlaster', 'explodingSpiderBot', 'robotTRex']; while (robotBudget > 0) { var unitType = robotUnits[Math.floor(Math.random() * robotUnits.length)]; var cost = unitCosts[unitType] || 100; if (cost > robotBudget) { continue; } var robot = new Unit('red', unitType, Math.random() * 2970, Math.random() * 1981); units.push(robot); game.addChild(robot); robotBudget -= cost; } } // Plane Storm: 15 random planes or VF fighters else if (levelInfo.name === "Plane Storm") { var planeTypes = ['plane', 'vfFighter']; for (var i = 0; i < 15; i++) { var unitType = planeTypes[Math.floor(Math.random() * planeTypes.length)]; var enemy = new Unit('red', unitType, Math.random() * 2970, 200 + Math.random() * 600); units.push(enemy); game.addChild(enemy); } } // Battle of Carthage: elephant riders and spearmen else if (levelInfo.name === "Battle of Carthage") { // Spawn elephant riders var numElephants = 5; for (var i = 0; i < numElephants; i++) { var enemy = new Unit('red', 'elephantRiders', 400 + i * 300, 400 + Math.random() * 200); units.push(enemy); game.addChild(enemy); } // Spawn spearmen var numSpearmen = 10; for (var i = 0; i < numSpearmen; i++) { var enemy = new Unit('red', 'spearman', 300 + Math.random() * 2070, 600 + Math.random() * 400); units.push(enemy); game.addChild(enemy); } } // Trio Showdown: 3 random units else if (levelInfo.name === "Trio Showdown") { var allUnits = Object.keys(unitCosts).filter(function (unit) { return unit !== 'skeleton'; // Exclude skeleton as it's summoned }); for (var i = 0; i < 3; i++) { var unitType = allUnits[Math.floor(Math.random() * allUnits.length)]; var enemy = new Unit('red', unitType, 600 + i * 400, 600); units.push(enemy); game.addChild(enemy); } } // One, two, T-REX!: 10 T-REXes (only living units) else if (levelInfo.name === "One, two, T-REX!") { for (var i = 0; i < 10; i++) { var enemy = new Unit('red', 't-rex', 200 + i % 5 * 350, 400 + Math.floor(i / 5) * 300); units.push(enemy); game.addChild(enemy); } } // The Second Chickening: chickens and chicken coops else if (levelInfo.name === "The Second Chickening") { // Spawn chicken coops var numCoops = 3; for (var i = 0; i < numCoops; i++) { var enemy = new Unit('red', 'chickenCoop', 400 + i * 500, 300); units.push(enemy); game.addChild(enemy); } // Spawn initial chickens var numChickens = 8; for (var i = 0; i < numChickens; i++) { var enemy = new Unit('red', 'chicken', Math.random() * 2970, 600 + Math.random() * 600); units.push(enemy); game.addChild(enemy); } } // Spawners Only: factories and coops only else if (levelInfo.name === "Spawners Only") { // Spawn robot factories var numFactories = 2; for (var i = 0; i < numFactories; i++) { var enemy = new Unit('red', 'robotFactory', 500 + i * 600, 300); units.push(enemy); game.addChild(enemy); } // Spawn chicken coops var numCoops = 3; for (var i = 0; i < numCoops; i++) { var enemy = new Unit('red', 'chickenCoop', 300 + i * 400, 600); units.push(enemy); game.addChild(enemy); } // Spawn barracks var numBarracks = 2; for (var i = 0; i < numBarracks; i++) { var enemy = new Unit('red', 'barracks', 600 + i * 500, 900); units.push(enemy); game.addChild(enemy); } } } else { // Normal spawning logic for regular levels while (enemyBudget > 0) { var faction = levelInfo.enemyFactions[Math.floor(Math.random() * levelInfo.enemyFactions.length)]; var availableUnits = factions[faction].filter(function (unit) { return unitCosts[unit] <= enemyBudget; }); if (availableUnits.length === 0) { break; } var unitType = availableUnits[Math.floor(Math.random() * availableUnits.length)]; var cost = unitCosts[unitType] || 100; var enemy = new Unit('red', unitType, Math.random() * 2970, Math.random() * 500); units.push(enemy); game.addChild(enemy); enemyBudget -= cost; } } // Start the battle immediately after spawning enemies battlefield.initialized = true; campaignStartButton.visible = false; }, nextLevel: function nextLevel() { this.currentLevel++; if (this.currentLevel > this.levels.length) { this.showVictory(); } else { this.playerBudget = this.budget; resetBattle(); // Re-enable campaign mode after reset this.active = true; this.showLevelInfo(); this.createBudgetDisplay(); // Show blue faction buttons for unit placement blueFactionButtons.forEach(function (button) { button.visible = true; }); campaignStartButton.visible = true; } }, showVictory: function showVictory() { var victoryText = new Text2("Campaign Complete!\nYou have defeated all enemies!", { size: 60, fill: 0xFFD700 }); victoryText.anchor.set(0.5, 0.5); victoryText.x = 2048 / 2; victoryText.y = 2732 / 2; game.addChild(victoryText); LK.getSound('victory').play(); LK.setTimeout(function () { resetBattle(); campaignMode.active = false; startButton.visible = true; endlessModeButton.visible = true; campaignModeButton.visible = true; instructions.visible = true; endlessModeInstructionsButton.visible = true; if (victoryText.parent) { victoryText.destroy(); } }, 5000); } }; var endlessMode = { active: false, currentWave: 0, unlockedUnits: ['wobbler'], enemyUnits: ['farmer', 'archer', 'sword', 'shield', 'berserker', 'spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire', 'wizard', 'witch', 'necromancer', 'skeleton', 'mirrorShield', 'crossbowMan', 'sniper', 'warMachine', 'rifleman', 'hammer', 'axeThrower', 'cannon', 'catapult', 'queen', 'raptor', 'brachiosaur', 't-rex', 'mecha', 'robotTRex', 'pistolero', 'monkeySmuggler', 'miachelJames', 'dragon', 'triceratops', 'archmage', 'brachiosaurGod', 'chickenCoop', 'elephantRiders', 'tombstone', 'cavalry', 'vikingWarrior'], // The robots faction will be added here when units are implemented start: function start() { var _this2 = this; this.active = true; this.currentWave = 0; this.unlockedUnits = ['wobbler']; this.lockedUnits = Object.keys(factions).reduce(function (acc, faction) { return acc.concat(factions[faction].filter(function (unit) { return !_this2.unlockedUnits.includes(unit); })); }, []); this.spawnNextWave(); }, spawnNextWave: function spawnNextWave() { this.currentWave++; var numEnemies = this.currentWave * 2; // Increase number of enemies with each wave for (var i = 0; i < numEnemies; i++) { var enemyType = this.enemyUnits[Math.floor(Math.random() * this.enemyUnits.length)]; // Count current units of the same type on the battlefield var currentUnitCount = units.filter(function (unit) { return unit.type === enemyType && unit.team === 'red' && unit.active; }).length; // Limit the number of units based on type var maxUnits = enemyType === 'wobbler' ? 5 : 3; if (currentUnitCount < maxUnits) { var enemy = new Unit('red', enemyType, Math.random() * 2970, Math.random() * 1981); units.push(enemy); game.addChild(enemy); } } if (units.filter(function (unit) { return unit.team === 'red' && unit.active; }).length === 0) { this.unlockUnit(); } else { return; // Exit if there are still active red units } }, unlockUnit: function unlockUnit() { var _this = this; if (this.lockedUnits.length > 0) { var nextUnit = this.lockedUnits[Math.floor(Math.random() * this.lockedUnits.length)]; if (nextUnit) { this.unlockedUnits.push(nextUnit); this.lockedUnits = this.lockedUnits.filter(function (unit) { return unit !== nextUnit; }); // Display popup for unlocked unit var popupText = new Text2("Unlocked: " + nextUnit.charAt(0).toUpperCase() + nextUnit.slice(1), { size: 50, fill: 0xFFFFFF }); popupText.anchor.set(0.5, 0.5); popupText.x = 2048 / 2; popupText.y = 2732 / 2; game.addChild(popupText); // Remove popup after 2 seconds LK.setTimeout(function () { if (popupText.parent) { popupText.destroy(); } }, 2000); } } }, checkWinCondition: function checkWinCondition() { if (this.unlockedUnits.length === Object.keys(factions).reduce(function (acc, faction) { return acc.concat(factions[faction]); }, []).length) { LK.showYouWin(); this.active = false; this.unlockedUnits = Object.keys(factions).reduce(function (acc, faction) { return acc.concat(factions[faction]); }, []); } } }; function _typeof2(o) { "@babel/helpers - typeof"; return _typeof2 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof2(o); } function _defineProperty3(e, r, t) { return (r = _toPropertyKey2(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey2(t) { var i = _toPrimitive2(t, "string"); return "symbol" == _typeof2(i) ? i : i + ""; } function _toPrimitive2(t, r) { if ("object" != _typeof2(t) || !t) { return t; } var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof2(i)) { return i; } throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) { return t; } var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) { return i; } throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } // Remove duplicate factions definition - use the one defined earlier // Game state variables var units = []; var draggingUnit = null; var battlefield = null; var resetButton = null; // Create the battlefield battlefield = new Battlefield(); battlefield.x = 0; battlefield.y = 0; game.addChild(battlefield); // Available maps var availableMaps = ['mapPlains', 'mapArenaOfIce', 'mapCastle', 'mapColosseum', 'mapDinosaurJungle', 'mapFarm', 'mapMagicalForest', 'mapModernBattlefield', 'mapPirateShip', 'mapScienceLab', 'mapVillage']; // Create map selection buttons var mapButtons = []; var mapButtonX = 724; // Left side of center (1024 - 300) var mapButtonY = 150; for (var i = 0; i < availableMaps.length; i++) { var mapButton = new MapButton(availableMaps[i], mapButtonX, mapButtonY); mapButtons.push(mapButton); game.addChild(mapButton); mapButtonY += 60; // Start new column if needed if (i === 5) { mapButtonX = 924; // Second column, still left of center (1024 - 100) mapButtonY = 150; } } // Function to change map function changeMap(mapName) { if (battlefield) { battlefield.changeMap(mapName); } } // Create faction buttons for red team var redFactionButtons = []; var factionNames = Object.keys(factions); var factionYOffset = 80; for (var i = 0; i < factionNames.length; i++) { var factionButton = new FactionButton('red', factionNames[i], 2048 - 300, factionYOffset); factionButton.x = 2048 - 300; // Adjusted position even further to the left factionButton.y = factionYOffset; redFactionButtons.push(factionButton); game.addChild(factionButton); factionYOffset += 100; } // Create faction buttons for blue team var blueFactionButtons = []; factionYOffset = 2732 - 80; for (var i = 0; i < factionNames.length; i++) { var factionButton = new FactionButton('blue', factionNames[i], 60, factionYOffset); blueFactionButtons.push(factionButton); game.addChild(factionButton); factionYOffset -= 100; } // Start button var startButton = new StartButton(); startButton.x = 2048 / 2; startButton.y = 2732 / 2; game.addChild(startButton); // Endless mode button var endlessModeButton = new EndlessModeButton(); endlessModeButton.x = 2048 / 2; endlessModeButton.y = 2732 / 2 + 100; game.addChild(endlessModeButton); // Campaign mode button var campaignModeButton = new CampaignModeButton(); campaignModeButton.x = 2048 / 2; campaignModeButton.y = 2732 / 2 + 300; game.addChild(campaignModeButton); // Endless mode instructions button var endlessModeInstructionsButton = new EndlessModeInstructionsButton(); endlessModeInstructionsButton.x = 2048 / 2; endlessModeInstructionsButton.y = 2732 / 2 + 200; game.addChild(endlessModeInstructionsButton); // Campaign start button (initially hidden) var campaignStartButton = new StartButton(); campaignStartButton.x = 2048 / 2; campaignStartButton.y = 2732 / 2; campaignStartButton.visible = false; campaignStartButton.down = function (x, y, obj) { if (units.filter(function (unit) { return unit.team === 'blue'; }).length > 0) { battlefield.initialized = true; campaignStartButton.visible = false; campaignMode.spawnEnemies(); // Hide campaign unit buttons var campaignButtons = game.children.filter(function (child) { return child instanceof CampaignUnitButton; }); campaignButtons.forEach(function (button) { button.visible = false; }); } }; game.addChild(campaignStartButton); // Win text var winText = new Text2("", { size: 80, fill: 0xFFFF00 }); winText.anchor.set(0.5, 0.5); winText.x = 2048 / 2; winText.y = 2732 / 2 - 100; winText.visible = false; game.addChild(winText); // Instructions text var instructions = new Text2("Place units on the battlefield\nand press Start Battle!", { size: 40, fill: 0xFFFFFF }); instructions.anchor.set(0.5, 0.5); instructions.x = 2048 / 2; instructions.y = 2732 / 2 - 100; game.addChild(instructions); // Helper function to calculate distance between two points function getDistance(x1, y1, x2, y2) { var dx = x2 - x1; var dy = y2 - y1; return Math.sqrt(dx * dx + dy * dy); } // Helper function to calculate squared distance (faster, no sqrt) function getDistanceSquared(x1, y1, x2, y2) { var dx = x2 - x1; var dy = y2 - y1; return dx * dx + dy * dy; } // Reset the battle function resetBattle() { // Remove all units for (var i = units.length - 1; i >= 0; i--) { if (units[i].parent) { units[i].destroy(); } } units = []; // Clear projectiles for (var i = battlefield.projectiles.length - 1; i >= 0; i--) { if (battlefield.projectiles[i].parent) { battlefield.projectiles[i].destroy(); } } battlefield.projectiles = []; // Reset battlefield state battlefield.initialized = false; // Show start button, endless mode button, and instructions again if (startButton) { startButton.visible = true; } if (endlessModeButton) { endlessModeButton.visible = true; } if (instructions) { instructions.visible = true; } // Hide win text and reset button if (winText) { winText.visible = false; } if (resetButton && resetButton.parent) { resetButton.destroy(); resetButton = null; } // Stop music LK.stopMusic(); // Clean up campaign mode if (campaignMode.active) { campaignMode.active = false; if (campaignMode.budgetText && campaignMode.budgetText.parent) { campaignMode.budgetText.destroy(); } // Show main menu buttons startButton.visible = true; endlessModeButton.visible = true; campaignModeButton.visible = true; // Show faction buttons again blueFactionButtons.forEach(function (button) { button.visible = true; }); redFactionButtons.forEach(function (button) { button.visible = true; }); } } // Handle dragging game.move = function (x, y, obj) { if (draggingUnit) { draggingUnit.x = x; draggingUnit.y = y; } }; game.up = function (x, y, obj) { if (draggingUnit) { // Check if unit is placed on the battlefield (not on buttons) var validPlacement = y > 150 && y < 2732 - 150; if (validPlacement) { // Add to units array units.push(draggingUnit); } else { // Not valid placement, destroy the unit draggingUnit.destroy(); } draggingUnit = null; } }; // Game update loop game.update = function () { // Update all units if battle has started if (battlefield.initialized) { // Cache active units count for endless mode var activeRedCount = 0; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (unit.active) { unit.update(); if (endlessMode.active && unit.team === 'red') { activeRedCount++; } } } if (endlessMode.active) { if (activeRedCount === 0) { endlessMode.spawnNextWave(); endlessMode.unlockUnit(); } endlessMode.checkWinCondition(); } } // Update projectiles battlefield.update(); };
===================================================================
--- original.js
+++ change.js
@@ -54,19 +54,29 @@
// Create background - default to Grasslands map
self.currentMap = 'mapPlains';
self.bg = self.attachAsset(self.currentMap, {
anchorX: 0,
- anchorY: 0
+ anchorY: 0,
+ width: self.width,
+ height: self.height
});
+ // Position the background to cover the entire battlefield
+ self.bg.x = 0;
+ self.bg.y = 0;
self.changeMap = function (mapName) {
if (self.bg) {
self.bg.destroy();
}
self.currentMap = mapName;
self.bg = self.attachAsset(mapName, {
anchorX: 0,
- anchorY: 0
+ anchorY: 0,
+ width: self.width,
+ height: self.height
});
+ // Position the background to cover the entire battlefield
+ self.bg.x = 0;
+ self.bg.y = 0;
// Make sure background is at the bottom
self.setChildIndex(self.bg, 0);
};
self.projectiles = [];
victory
Sound effect
battleMusic
Music
death
Sound effect
arrow
Sound effect
spell
Sound effect
attack
Sound effect
Explosion
Sound effect
Destroyed
Sound effect
Kiss
Sound effect
Gnehehe
Sound effect
Defend
Sound effect
Girldeath
Sound effect
Shoot
Sound effect
RAWR
Sound effect
Malfunction
Sound effect
Blaster
Sound effect
Holy
Sound effect
Bawk
Sound effect