/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1"); /**** * Classes ****/ var Battlefield = Container.expand(function () { var self = Container.call(this); self.width = 2048; self.height = 1366; self.initialized = false; // Create background var bg = self.attachAsset('background', { anchorX: 0, anchorY: 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 (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 for (var i = self.projectiles.length - 1; i >= 0; i--) { self.projectiles[i].update(); // Remove destroyed projectiles if (!self.projectiles[i].parent) { self.projectiles.splice(i, 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 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) { // When button is pressed, create unit buttons for the faction var xOffset = 120; for (var i = 0; i < factions[faction].length; i++) { 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 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 // Default properties based on unit type switch (type) { 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 'valkyrie': self.attackRange = 100; self.attackDamage = 20; self.moveSpeed = 1.5; self.health = 100; self.maxHealth = 100; self.attackCooldown = 60; self.special = "immune to melee attacks"; 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; 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"; break; case 'skeleton': self.attackRange = 70; self.attackDamage = 25; self.moveSpeed = 1.2; 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 = "ranged"; break; case 'hammer': self.attackRange = 50; self.attackDamage = 30; self.moveSpeed = 1.0; self.attackCooldown = 70; self.special = "aoe"; break; case 'retarius': self.attackRange = 100; self.attackDamage = 18; self.moveSpeed = 1.2; self.attackCooldown = 60; self.special = "stun 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"; 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"; 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 = 100; // High damage on explosion self.moveSpeed = 1.5; // Fast movement self.health = 1; // Very fragile self.maxHealth = 1; self.attackCooldown = 0; // No regular attacks self.special = "self-destruct"; // Charges and explodes self.isLiving = false; // Robots are not living 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) { if (!self.active || self.type === 'romanLegionnaire' && self.invincible || self.type === 'valkyrie' && attacker && attacker.attackType === 'melee') { return; } // Shield units take less damage from archers if (self.type === 'shield' && attacker && attacker.type === 'archer') { amount = Math.floor(amount * 0.5); } // 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; if (self.type === 'warMachine' || self.type === 'cannon' || self.type === 'catapult' || self.type === 'jeep') { LK.getSound('Destroyed').play(); } else if (self.type === 'bombThrower') { // Play both die and explosion sounds simultaneously for bombThrower LK.getSound('death').play(); LK.getSound('Explosion').play(); } else if (self.type === 'queen' || self.type === 'valkyrie' || self.type === 'witch') { LK.getSound('Girldeath').play(); } else if (self.type === 'raptor' || self.type === 't-rex' || self.type === 't-rexJr' || self.type === 'brachiosaur') { // Dinosaur death sounds - use RAWR sound for all dinosaurs LK.getSound('RAWR').play(); } else if (self.type === 'robotBlaster' || self.type === 'explodingSpiderBot') { 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 closestDist = Infinity; var closestEnemy = null; 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 < closestDist) { closestDist = dist; closestEnemy = unit; } } } return closestEnemy; }; self.moveTowardsTarget = function () { if (!self.target || !self.target.active) { 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(); 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 } } 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 = getDistance(self.x, self.y, self.target.x, self.target.y); if (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.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.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 > 2048 || this.y < 0 || this.y > 2732) { 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 > 2048 || this.y < 0 || this.y > 2732) { 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; // 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") { // Exploding Spider Bot attack logic if (getDistance(self.x, self.y, self.target.x, self.target.y) <= self.attackRange) { self.target.takeDamage(self.attackDamage, self); self.die(); // Self-destruct after attacking } } 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); } } } } } } }; 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 } } // Update health bar position to follow unit self.updateHealthBar(); // Decrease attack cooldown if (self.attackCooldown > 0) { self.attackCooldown--; } // Find and move towards target self.moveTowardsTarget(); // Attack if cooldown is ready 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(); } }; 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); 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'], // 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() * 2048, Math.random() * 1366); 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); } var factions = _defineProperty(_defineProperty(_defineProperty3({ farm: ['wobbler', 'farmer', 'appleThrower', 'scarecrow'], medieval: ['sword', 'archer', 'crossbowMan', 'king', 'shield', 'cannon', 'queen', 'catapult', 'priest'], rome: ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire'], magic: ['mirrorShield', 'wizard', 'necromancer', 'skeleton', 'witch'], vikings: ['berserker', 'hammer', 'axeThrower', 'valkyrie', 'jarl'], modern: ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner', 'jeep'], pirates: ['swashbuckler', 'pirateCrew', 'pirateCaptain', 'bombThrower'], dinosaurs: ['raptor', 'brachiosaur', 't-rex', 't-rexJr'], // Added dinosaur units robots: ['robotBlaster', 'explodingSpiderBot'] // Added Robot Blaster and Exploding Spider Bot to robots faction }, "rome", ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire']), "vikings", ['berserker', 'hammer', 'axeThrower', 'valkyrie', 'jarl']), "modern", ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner', 'jeep'], "farm", ['wobbler', 'farmer', 'appleThrower', 'scarecrow']); // 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); // 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); // Endless mode instructions button var endlessModeInstructionsButton = new EndlessModeInstructionsButton(); endlessModeInstructionsButton.x = 2048 / 2; endlessModeInstructionsButton.y = 2732 / 2 + 200; game.addChild(endlessModeInstructionsButton); // 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); } // 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(); } // 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) { for (var i = 0; i < units.length; i++) { if (units[i].active) { units[i].update(); } } if (endlessMode.active) { var redTeamUnits = units.filter(function (unit) { return unit.team === 'red' && unit.active; }).length; if (redTeamUnits === 0) { endlessMode.spawnNextWave(); endlessMode.unlockUnit(); } endlessMode.checkWinCondition(); } } // Update projectiles battlefield.update(); };
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Battlefield = Container.expand(function () {
var self = Container.call(this);
self.width = 2048;
self.height = 1366;
self.initialized = false;
// Create background
var bg = self.attachAsset('background', {
anchorX: 0,
anchorY: 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 (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
for (var i = self.projectiles.length - 1; i >= 0; i--) {
self.projectiles[i].update();
// Remove destroyed projectiles
if (!self.projectiles[i].parent) {
self.projectiles.splice(i, 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 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) {
// When button is pressed, create unit buttons for the faction
var xOffset = 120;
for (var i = 0; i < factions[faction].length; i++) {
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 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
// Default properties based on unit type
switch (type) {
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 'valkyrie':
self.attackRange = 100;
self.attackDamage = 20;
self.moveSpeed = 1.5;
self.health = 100;
self.maxHealth = 100;
self.attackCooldown = 60;
self.special = "immune to melee attacks";
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;
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";
break;
case 'skeleton':
self.attackRange = 70;
self.attackDamage = 25;
self.moveSpeed = 1.2;
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 = "ranged";
break;
case 'hammer':
self.attackRange = 50;
self.attackDamage = 30;
self.moveSpeed = 1.0;
self.attackCooldown = 70;
self.special = "aoe";
break;
case 'retarius':
self.attackRange = 100;
self.attackDamage = 18;
self.moveSpeed = 1.2;
self.attackCooldown = 60;
self.special = "stun 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";
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";
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 = 100; // High damage on explosion
self.moveSpeed = 1.5; // Fast movement
self.health = 1; // Very fragile
self.maxHealth = 1;
self.attackCooldown = 0; // No regular attacks
self.special = "self-destruct"; // Charges and explodes
self.isLiving = false; // Robots are not living
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) {
if (!self.active || self.type === 'romanLegionnaire' && self.invincible || self.type === 'valkyrie' && attacker && attacker.attackType === 'melee') {
return;
}
// Shield units take less damage from archers
if (self.type === 'shield' && attacker && attacker.type === 'archer') {
amount = Math.floor(amount * 0.5);
}
// 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;
if (self.type === 'warMachine' || self.type === 'cannon' || self.type === 'catapult' || self.type === 'jeep') {
LK.getSound('Destroyed').play();
} else if (self.type === 'bombThrower') {
// Play both die and explosion sounds simultaneously for bombThrower
LK.getSound('death').play();
LK.getSound('Explosion').play();
} else if (self.type === 'queen' || self.type === 'valkyrie' || self.type === 'witch') {
LK.getSound('Girldeath').play();
} else if (self.type === 'raptor' || self.type === 't-rex' || self.type === 't-rexJr' || self.type === 'brachiosaur') {
// Dinosaur death sounds - use RAWR sound for all dinosaurs
LK.getSound('RAWR').play();
} else if (self.type === 'robotBlaster' || self.type === 'explodingSpiderBot') {
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 closestDist = Infinity;
var closestEnemy = null;
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 < closestDist) {
closestDist = dist;
closestEnemy = unit;
}
}
}
return closestEnemy;
};
self.moveTowardsTarget = function () {
if (!self.target || !self.target.active) {
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();
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
}
}
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 = getDistance(self.x, self.y, self.target.x, self.target.y);
if (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.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.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 > 2048 || this.y < 0 || this.y > 2732) {
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 > 2048 || this.y < 0 || this.y > 2732) {
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;
// 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") {
// Exploding Spider Bot attack logic
if (getDistance(self.x, self.y, self.target.x, self.target.y) <= self.attackRange) {
self.target.takeDamage(self.attackDamage, self);
self.die(); // Self-destruct after attacking
}
} 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);
}
}
}
}
}
}
};
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
}
}
// Update health bar position to follow unit
self.updateHealthBar();
// Decrease attack cooldown
if (self.attackCooldown > 0) {
self.attackCooldown--;
}
// Find and move towards target
self.moveTowardsTarget();
// Attack if cooldown is ready
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();
}
};
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);
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'],
// 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() * 2048, Math.random() * 1366);
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);
}
var factions = _defineProperty(_defineProperty(_defineProperty3({
farm: ['wobbler', 'farmer', 'appleThrower', 'scarecrow'],
medieval: ['sword', 'archer', 'crossbowMan', 'king', 'shield', 'cannon', 'queen', 'catapult', 'priest'],
rome: ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire'],
magic: ['mirrorShield', 'wizard', 'necromancer', 'skeleton', 'witch'],
vikings: ['berserker', 'hammer', 'axeThrower', 'valkyrie', 'jarl'],
modern: ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner', 'jeep'],
pirates: ['swashbuckler', 'pirateCrew', 'pirateCaptain', 'bombThrower'],
dinosaurs: ['raptor', 'brachiosaur', 't-rex', 't-rexJr'],
// Added dinosaur units
robots: ['robotBlaster', 'explodingSpiderBot'] // Added Robot Blaster and Exploding Spider Bot to robots faction
}, "rome", ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire']), "vikings", ['berserker', 'hammer', 'axeThrower', 'valkyrie', 'jarl']), "modern", ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner', 'jeep'], "farm", ['wobbler', 'farmer', 'appleThrower', 'scarecrow']);
// 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);
// 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);
// Endless mode instructions button
var endlessModeInstructionsButton = new EndlessModeInstructionsButton();
endlessModeInstructionsButton.x = 2048 / 2;
endlessModeInstructionsButton.y = 2732 / 2 + 200;
game.addChild(endlessModeInstructionsButton);
// 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);
}
// 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();
}
// 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) {
for (var i = 0; i < units.length; i++) {
if (units[i].active) {
units[i].update();
}
}
if (endlessMode.active) {
var redTeamUnits = units.filter(function (unit) {
return unit.team === 'red' && unit.active;
}).length;
if (redTeamUnits === 0) {
endlessMode.spawnNextWave();
endlessMode.unlockUnit();
}
endlessMode.checkWinCondition();
}
}
// Update projectiles
battlefield.update();
};
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