User prompt
Add new units: ballista: has a great range (not infinite) and throws big arrows that cant be reflected (type: ranged, faction: rome); Yarl: AN unit with massive damage that Has a 20% chance that, when attacking, stuns the enemy for 0.4 seconds (type: melee, faction: vikings); Santa: dosent attack and moves on random locations; when moving, every 5 seconds Santa places a gift that can spawn any unit in the game! (Type: melee, faction: farm); use Container.expand
User prompt
Please fix the bug: 'Unit.extend is not a function' in or related to this line: 'var Santa = Unit.extend({' Line Number: 1097
User prompt
Add new units: ballista: has a great range (not infinite) and throws big arrows that cant be reflected (type: ranged, faction: rome); Yarl: AN unit with massive damage that Has a 20% chance that, when attacking, stuns the enemy for 0.4 seconds (type: melee, faction: vikings); Santa: dosent attack and moves on random locations; when moving, every 5 seconds Santa places a gift that can spawn any unit in the game! (Type: melee, faction: farm)
User prompt
Please fix the bug: 'Unit.extend is not a function' in or related to this line: 'var Santa = Unit.extend(function (team, x, y) {' Line Number: 1058
User prompt
Add new units: ballista: has a great range (not infinite) and throws big arrows that cant be reflected (type: ranged, faction: rome); Yarl: AN unit with massive damage that Has a 20% chance that, when attacking, stuns the enemy for 0.4 seconds (type: melee, faction: vikings); Santa: dosent attack and moves on random locations; when moving, every 5 seconds Santa places a gift that can spawn any unit in the game! (Type: melee, faction: farm)
User prompt
Now you can aim the mortar and gatling gunner using your finger or cursor! Show a target sign showing where youre aiming
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'attachAsset')' in or related to this line: 'battlefield.targetSign = battlefield.attachAsset('targetSign', {' Line Number: 1063
User prompt
Now you can aim the mortar and gatling gunner using your finger or cursor! Show a target sign showing where youre aiming
User prompt
Gatling now stands still and has infinite range and shoots 25 projectiles instead
User prompt
Please fix the bug: 'Uncaught TypeError: storage.get is not a function' in or related to this line: 'if ((self.type === 'gatlingGunner' || self.type === 'mortar') && !storage.get(self.type + 'Placed')) {' Line Number: 1044 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Gatling gunner and mortar get reworked so now they follow your mouse/finger! Show a popup that appears how to use them when you first place them ever
User prompt
Valkyrie now has a different ability: it cannot be targeted by melee units
User prompt
Spearman now throws javelins instead pf spears
User prompt
Spearman now has a chance of throwing javelins instead of spears
User prompt
Spearman should throw a new projectile called "spear" that deals the same damage as AN arrow
User prompt
Spearman's spear deals no damage; make it deal the same damage as AN arrow
User prompt
Now the mortar uses the "Destroyed" sound when killed
User prompt
Please fix the bug: 'TypeError: Cannot read properties of null (reading 'x')' in or related to this line: 'var dist = getDistance(self.x, self.y, self.target.x, self.target.y);' Line Number: 774
User prompt
Witch also uses girldeath sound
User prompt
Now queen and valkyrie use the "Girldeath" sound when dying
User prompt
Make infinite range units not attack until the battle starts
User prompt
Make the gatling gunner deal some more damage
User prompt
Make Gatling gunner aim at the enemy, but make the projectiles have a chance to miss
User prompt
Make gatling gunner shoot 10 bullets every second but shooting them at random directory
User prompt
Mortar has too high damage; make it deal less damage
/**** * 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); } } // Update target sign position if (self.targetSign) { self.targetSign.x = self.aimX; self.targetSign.y = self.aimY; } }; 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 = ""; // Default properties based on unit type switch (type) { 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"; 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 '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"; break; case 'axeThrower': self.attackRange = 500; self.attackDamage = 20; self.moveSpeed = 1.0; self.attackCooldown = 80; self.special = "boomerang attack"; 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"; 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"; 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; } // 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') { LK.getSound('Destroyed').play(); } else if (self.type === 'queen' || self.type === 'valkyrie' || self.type === 'witch') { LK.getSound('Girldeath').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; } } 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 : 100; self.attack(); } var dist = getDistance(self.x, self.y, self.target.x, self.target.y); if (dist <= self.attackRange) { // Ensure all units deal damage to enemies if (self.target && self.target.team !== self.team) { self.target.takeDamage(self.attackDamage, 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 : 100; // Handle special attacks if (self.special === "ranged") { // Create arrow projectile battlefield.createProjectile(self, self.target, 'arrow'); LK.getSound('arrow').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 === "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 === "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); } } }; 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" + "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 ****/ // Initialize target sign battlefield.targetSign = battlefield.attachAsset('targetSign', { anchorX: 0.5, anchorY: 0.5 }); battlefield.aimX = 0; battlefield.aimY = 0; // Handle aiming for mortar and gatling gunner game.move = function (x, y, obj) { battlefield.aimX = x; battlefield.aimY = y; if (draggingUnit) { draggingUnit.x = x; draggingUnit.y = y; } }; 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'], 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'], rome: ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire'], magic: ['mirrorShield', 'wizard', 'necromancer', 'skeleton', 'witch'], vikings: ['berserker', 'valkyrie'], modern: ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner'] }, "rome", ['spearman', 'retarius', 'romanJavelinThrower', 'romanLegionnaire']), "vikings", ['berserker', 'hammer', 'axeThrower', 'valkyrie']), "modern", ['sniper', 'warMachine', 'rifleman', 'mortar', 'gatlingGunner'], "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(); };
===================================================================
--- original.js
+++ change.js
@@ -69,8 +69,13 @@
if (!self.projectiles[i].parent) {
self.projectiles.splice(i, 1);
}
}
+ // Update target sign position
+ if (self.targetSign) {
+ self.targetSign.x = self.aimX;
+ self.targetSign.y = self.aimY;
+ }
};
return self;
});
var EndlessModeButton = Container.expand(function () {
@@ -992,9 +997,24 @@
/****
* Game Code
****/
-// Define factions and their respective units
+// Initialize target sign
+battlefield.targetSign = battlefield.attachAsset('targetSign', {
+ anchorX: 0.5,
+ anchorY: 0.5
+});
+battlefield.aimX = 0;
+battlefield.aimY = 0;
+// Handle aiming for mortar and gatling gunner
+game.move = function (x, y, obj) {
+ battlefield.aimX = x;
+ battlefield.aimY = y;
+ if (draggingUnit) {
+ draggingUnit.x = x;
+ draggingUnit.y = y;
+ }
+};
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
});
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