User prompt
aun aparece el error
User prompt
Please fix the bug: 'TypeError: LK.effects.stopFlash is not a function' in or related to this line: 'LK.effects.stopFlash(ship);' Line Number: 1556
User prompt
mira sigue apareciendo el barco chico con ese fallo
User prompt
corrije el hecho que despies de matar un barco quede titilando rojo y no se desaparezca
User prompt
no lo haz sacado solo cambia de color no te das cuenta?
User prompt
sacalo del codigo y de los assets
User prompt
sigue estando en assets sacalo
User prompt
borra el asset de fryendlyoctopus
User prompt
elimina el asset fryendy octopus
User prompt
haz que el juego no tenga asset de aliados ni nada por el estilo elimina todo lo relacionado
User prompt
elimiina los asset de los aliados pulpo y todo lo relacionado a aliados
User prompt
mira ese pulpo rojo deberia ser el brazo
User prompt
que la p se trasforme en el brazo del pulpo que pelea de cerca de hecho usa la imagen del amigo pulpo con cañon pero ese no es deveria usar el de el brazo del pulpo
User prompt
que el power up octopus se trasforme en el aset octopus y que si muere sea muerte permanente
User prompt
salieron muchos pulpos no se por que
User prompt
elimina el asset de powerupoctopus
User prompt
el asset que dice octopus arm
User prompt
elimina octopus arm
User prompt
elimina la p para que aparezcan los brazos de pulpos que sea un aliado que aparece en cada ronda y que tenga 3 de vida y muere permanentemente los enemigos igual le disparan es muy rapido y lucha a melee y que ningun poder pueda mover a mis amigos pulpos hacia atras
User prompt
cuando tengo el escudo no me deja disparar corrije eso
User prompt
que los aliados no puedan reaparecer, ahora por alguna razon los poderes los arrastran corrije eso
User prompt
ahora no se porque el escudo arrastra a mis amigos los pulpos y no funciona cuando le disparo para obtenerlo
User prompt
que paso con el escudo? no aparece ahora, y bueno tambien si mis aliados les quitan la vida no aparece mas ese
User prompt
haz que el escudo solo pueda interactuar yo y que me de el escudo a mi solamente si yo le disparo
User prompt
por que cuando interactuo ahora con el escudo se queda pegado el juego
/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); /**** * Classes ****/ var Boss = Container.expand(function () { var self = Container.call(this); self.shipType = 'boss'; self.speed = 0.5; // Very slow base speed self.points = 500; // High points for boss self.graphics = null; self.healthBar = null; self.healthBarBg = null; self.isDying = false; self.init = function (wave) { self.shipType = 'boss'; self.graphics = self.attachAsset('bossShip', { anchorX: 0.5, anchorY: 0.5 }); // Make boss larger and more intimidating self.graphics.scaleX = 1.5; self.graphics.scaleY = 1.5; // Slow speed that slightly increases with wave self.speed = 0.5 + (wave - 4) * 0.1; self.points = 500 + (wave - 4) * 100; // Scaling points self.health = 40; self.maxHealth = 40; // Create larger health bar for boss var healthBarWidth = 300; self.healthBarBg = LK.getAsset('healthBarBg', { width: healthBarWidth, height: 20, color: 0x444444, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBarBg.y = -self.graphics.height / 2 - 40; self.addChild(self.healthBarBg); self.healthBar = LK.getAsset('healthBar', { width: healthBarWidth - 4, height: 16, color: 0xFF0000, // Red health bar for boss shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBar.y = -self.graphics.height / 2 - 40; self.addChild(self.healthBar); // Boss shoots at moderate frequency self.lastShotTime = 0; self.shotDelay = 90; // 1.5 seconds (much slower) }; self.updateHealthBar = function () { if (self.healthBar && self.maxHealth > 1) { var healthPercent = self.health / self.maxHealth; self.healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { self.healthBar.tint = 0xFF4444; // Dark red } else if (healthPercent > 0.3) { self.healthBar.tint = 0xFF0000; // Red } else { self.healthBar.tint = 0x880000; // Dark red } } }; self.update = function () { self.x += self.speed; if (self.graphics) { self.graphics.visible = true; } }; self.canShoot = function () { return LK.ticks - self.lastShotTime >= self.shotDelay; }; self.shootAtCannon = function (cannonX, cannonY) { if (!self.canShoot()) return null; self.lastShotTime = LK.ticks; var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y; var dx = cannonX - self.x; var dy = cannonY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); bullet.velocityX = dx / distance * bullet.speed; bullet.velocityY = dy / distance * bullet.speed; bullet.damage = 2; // Boss bullets do more damage return bullet; }; return self; }); var Cannon = Container.expand(function () { var self = Container.call(this); self.graphics = self.attachAsset('cannon', { anchorX: 0.5, anchorY: 0.5 }); self.targetX = 0; self.targetY = 0; self.lastFireTime = 0; self.fireDelay = 300; self.health = 10; self.maxHealth = 10; self.isShielded = false; self.shieldTimer = 0; self.aimAt = function (x, y) { self.targetX = x; self.targetY = y; var dx = x - self.x; var dy = y - self.y; var angle = Math.atan2(dy, dx); self.graphics.rotation = angle; }; self.canFire = function () { var currentFireDelay = rapidFire ? self.fireDelay / 3 : self.fireDelay; return LK.ticks * 16.67 - self.lastFireTime >= currentFireDelay; }; self.fire = function () { if (!self.canFire()) return null; self.lastFireTime = LK.ticks * 16.67; var cannonballs = []; var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var baseAngle = Math.atan2(dy, dx); if (guidedShot) { // Determine number of guided projectiles to fire based on active powers var projectilesToFire = 1; if (tripleShot) { projectilesToFire = 3; // Triple shot multiplies guided projectiles } // Fire multiple guided projectiles if triple shot is active for (var projIndex = 0; projIndex < projectilesToFire; projIndex++) { var guidedProjectile = new GuidedProjectile(); guidedProjectile.x = self.x - 45 + (projIndex - Math.floor(projectilesToFire / 2)) * 30; // Spread them out horizontally guidedProjectile.y = self.y - 40; guidedProjectile.targetX = self.targetX; guidedProjectile.targetY = self.targetY; guidedProjectile.lastTargetUpdateTime = LK.ticks; guidedProjectile.creationTime = LK.ticks; // Set creation time // Apply rapid fire speed boost if active if (rapidFire) { guidedProjectile.speed = guidedProjectile.speed * 1.8; // 80% faster when rapid fire is active } guidedProjectiles.push(guidedProjectile); game.addChild(guidedProjectile); } } else if (tripleShot) { // Fire three cannonballs with slight angle differences for (var i = 0; i < 3; i++) { var cannonball = new Cannonball(); cannonball.x = self.x - 45; cannonball.y = self.y - 40; var angleOffset = (i - 1) * 0.2; // -0.2, 0, 0.2 radians var adjustedAngle = baseAngle + angleOffset; cannonball.velocityX = Math.cos(adjustedAngle) * cannonball.speed; cannonball.velocityY = Math.sin(adjustedAngle) * cannonball.speed; cannonballs.push(cannonball); } } else { // Fire single cannonball var cannonball = new Cannonball(); cannonball.x = self.x - 45; cannonball.y = self.y - 40; cannonball.velocityX = dx / distance * cannonball.speed; cannonball.velocityY = dy / distance * cannonball.speed; cannonballs.push(cannonball); } LK.getSound('shoot').play(); return cannonballs; }; self.takeDamage = function (damage) { if (self.isShielded) { damage = Math.floor(damage / 2); // Shield reduces damage by half } self.health -= damage; if (self.health < 0) self.health = 0; // Flash red when taking damage tween(self.graphics, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { tween(self.graphics, { tint: 0xffffff }, { duration: 100 }); } }); }; self.heal = function (amount) { self.health += amount; if (self.health > self.maxHealth) self.health = self.maxHealth; // Flash green when healing tween(self.graphics, { tint: 0x00ff00 }, { duration: 200, onFinish: function onFinish() { tween(self.graphics, { tint: 0xffffff }, { duration: 200 }); } }); }; self.activateShield = function () { self.isShielded = true; self.shieldTimer = 900; // 15 seconds at 60fps // Use self.graphics directly if available, otherwise find it in children var cannonGraphics = self.graphics; if (!cannonGraphics && self.children && self.children.length > 0) { // Find the first child that has a tint property (likely the graphics asset) for (var i = 0; i < self.children.length; i++) { var child = self.children[i]; if (child && typeof child.tint !== 'undefined') { cannonGraphics = child; break; } } } // Only set tint if we found valid graphics with tint property if (cannonGraphics && typeof cannonGraphics.tint !== 'undefined') { cannonGraphics.tint = 0x88ccff; // Blue tint for shield } }; return self; }); var Cannonball = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('cannonball', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.speed = 16; self.damage = 1; // Base damage, will be multiplied by cannonDamage when hitting self.bounceCount = 0; // Track number of bounces self.maxBounces = 2; // Maximum number of bounces before disappearing self.canBounce = function () { return self.bounceCount < self.maxBounces; }; self.bounceTowardsTarget = function (targetX, targetY) { if (!self.canBounce()) return false; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.bounceCount++; // Visual effect for bounce tween(graphics, { scaleX: 1.3, scaleY: 1.3, tint: 0xffff00 }, { duration: 100, onFinish: function onFinish() { tween(graphics, { scaleX: 1.0, scaleY: 1.0, tint: 0xffffff }, { duration: 100 }); } }); return true; } return false; }; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; }; return self; }); var EnemyBullet = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('enemyBullet', { anchorX: 0.5, anchorY: 0.5 }); self.velocityX = 0; self.velocityY = 0; self.speed = 8; self.damage = 1; self.bounceCount = 0; // Track number of bounces self.maxBounces = 2; // Maximum number of bounces before disappearing self.canBounce = function () { return self.bounceCount < self.maxBounces; }; self.bounceTowardsTarget = function (targetX, targetY) { if (!self.canBounce()) return false; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.velocityX = dx / distance * self.speed; self.velocityY = dy / distance * self.speed; self.bounceCount++; // Visual effect for bounce tween(graphics, { scaleX: 1.3, scaleY: 1.3, tint: 0xff6600 }, { duration: 100, onFinish: function onFinish() { tween(graphics, { scaleX: 1.0, scaleY: 1.0, tint: 0xffffff }, { duration: 100 }); } }); return true; } return false; }; self.update = function () { self.x += self.velocityX; self.y += self.velocityY; }; return self; }); var FriendlyOctopus = Container.expand(function () { var self = Container.call(this); self.health = 2; self.maxHealth = 2; self.speed = 2; self.lastShotTime = 0; self.shotDelay = 120; // 2 seconds self.moveDirection = Math.random() * Math.PI * 2; // Random initial direction self.changeDirectionTimer = 0; self.targetShip = null; self.isShielded = false; self.shieldTimer = 0; self.targetPriority = 'enemy'; self.movingToPowerUp = false; var graphics = self.attachAsset('friendlyOctopus', { anchorX: 0.5, anchorY: 0.5 }); graphics.tint = 0x00ff88; // Green tint to distinguish from enemy octopus graphics.scaleX = 0.6; graphics.scaleY = 0.6; // Create health bar self.healthBarBg = LK.getAsset('healthBarBg', { width: 120, height: 12, color: 0x444444, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBarBg.y = -graphics.height / 2 - 20; self.addChild(self.healthBarBg); self.healthBar = LK.getAsset('healthBar', { width: 116, height: 8, color: 0x00FF00, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBar.y = -graphics.height / 2 - 20; self.addChild(self.healthBar); self.updateHealthBar = function () { if (self.healthBar && self.maxHealth > 0) { var healthPercent = Math.max(0, self.health / self.maxHealth); self.healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { self.healthBar.tint = 0x00FF00; // Green } else if (healthPercent > 0.3) { self.healthBar.tint = 0xFFFF00; // Yellow } else { self.healthBar.tint = 0xFF0000; // Red } } }; self.findNearestTarget = function () { var nearestTarget = null; var nearestDistance = Infinity; var targetPriority = 'enemy'; // First priority: Find nearest enemy ship for (var i = 0; i < ships.length; i++) { var ship = ships[i]; if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) { var dx = ship.x - self.x; var dy = ship.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestTarget = ship; targetPriority = 'enemy'; } } } // Second priority: Find power-ups if no nearby enemies if (!nearestTarget || nearestDistance > 600) { var powerUpArrays = [powerUps, healthKits, shieldPowerUps, guidedShotPowerUps, octopusPowerUps]; for (var arrayIndex = 0; arrayIndex < powerUpArrays.length; arrayIndex++) { var powerUpArray = powerUpArrays[arrayIndex]; for (var p = 0; p < powerUpArray.length; p++) { var powerUp = powerUpArray[p]; if (powerUp && powerUp.parent) { var dx = powerUp.x - self.x; var dy = powerUp.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestTarget = powerUp; targetPriority = 'powerup'; } } } } } self.targetPriority = targetPriority; return nearestTarget; }; self.findNearestEnemyShip = function () { var nearestShip = null; var nearestDistance = Infinity; for (var i = 0; i < ships.length; i++) { var ship = ships[i]; if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) { var dx = ship.x - self.x; var dy = ship.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestShip = ship; } } } return nearestShip; }; self.canShoot = function () { var currentShotDelay = rapidFire ? self.shotDelay / 3 : self.shotDelay; return LK.ticks - self.lastShotTime >= currentShotDelay; }; self.shootAtTarget = function (targetX, targetY) { if (!self.canShoot()) return null; self.lastShotTime = LK.ticks; var bullets = []; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var baseAngle = Math.atan2(dy, dx); if (guidedShot) { // Fire guided projectiles like cannon when guided shot is active var projectilesToFire = 1; if (tripleShot) { projectilesToFire = 3; // Triple shot multiplies guided projectiles } // Fire multiple guided projectiles if triple shot is active for (var projIndex = 0; projIndex < projectilesToFire; projIndex++) { var guidedProjectile = new GuidedProjectile(); guidedProjectile.x = self.x + (projIndex - Math.floor(projectilesToFire / 2)) * 30; // Spread them out horizontally guidedProjectile.y = self.y; guidedProjectile.targetX = targetX; guidedProjectile.targetY = targetY; guidedProjectile.lastTargetUpdateTime = LK.ticks; guidedProjectile.creationTime = LK.ticks; // Set creation time // Apply rapid fire speed boost if active if (rapidFire) { guidedProjectile.speed = guidedProjectile.speed * 1.8; // 80% faster when rapid fire is active } guidedProjectiles.push(guidedProjectile); game.addChild(guidedProjectile); } return null; // Don't return cannonballs when using guided shot } else if (tripleShot) { // Fire three cannonballs with slight angle differences for (var i = 0; i < 3; i++) { var bullet = new Cannonball(); bullet.x = self.x; bullet.y = self.y; var angleOffset = (i - 1) * 0.2; // -0.2, 0, 0.2 radians var adjustedAngle = baseAngle + angleOffset; bullet.velocityX = Math.cos(adjustedAngle) * bullet.speed; bullet.velocityY = Math.sin(adjustedAngle) * bullet.speed; bullet.damage = 2; // Friendly octopus bullets do more damage bullets.push(bullet); } } else { // Fire single cannonball var bullet = new Cannonball(); bullet.x = self.x; bullet.y = self.y; bullet.velocityX = dx / distance * bullet.speed; bullet.velocityY = dy / distance * bullet.speed; bullet.damage = 2; // Friendly octopus bullets do more damage bullets.push(bullet); } return bullets; }; self.takeDamage = function (damage) { if (self.isShielded) { damage = Math.floor(damage / 2); // Shield reduces damage by half } self.health -= damage; if (self.health < 0) self.health = 0; self.updateHealthBar(); // Flash red when taking damage tween(self, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { var originalTint = self.isShielded ? 0x88ccff : 0x00ff88; tween(self, { tint: originalTint }, { duration: 100 }); } }); }; self.activateShield = function () { self.isShielded = true; self.shieldTimer = 900; // 15 seconds at 60fps self.graphics.tint = 0x88ccff; // Blue tint for shield }; self.update = function () { // Update shield status if (self.isShielded && self.shieldTimer > 0) { self.shieldTimer--; if (self.shieldTimer <= 0) { self.isShielded = false; tween(self.graphics, { tint: 0x00ff88 }, { duration: 500 }); } } // Find nearest target (enemy or power-up) var target = self.findNearestTarget(); // Check distance to cannon and avoid shield if active var distanceToCannon = Math.sqrt(Math.pow(cannon.x - self.x, 2) + Math.pow(cannon.y - self.y, 2)); var shieldAvoidanceDistance = cannon.isShielded ? 300 : 200; // Movement logic based on target priority if (target && self.targetPriority === 'powerup') { // Move towards power-up var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if path to power-up would go through shield area if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) { // Move around the shield instead of towards power-up var angleAroundShield = Math.atan2(self.y - cannon.y, self.x - cannon.x) + Math.PI / 3; self.moveDirection = angleAroundShield; self.movingToPowerUp = false; } else if (distance > 50) { self.moveDirection = Math.atan2(dy, dx); self.movingToPowerUp = true; } else { self.movingToPowerUp = false; } } else if (target && self.targetPriority === 'enemy' && !self.movingToPowerUp) { // Combat positioning var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var optimalDistance = 350; // Avoid shield area during combat if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) { var angleAwayFromShield = Math.atan2(self.y - cannon.y, self.x - cannon.x); self.moveDirection = angleAwayFromShield; } else if (distance < optimalDistance - 50) { self.moveDirection = Math.atan2(self.y - target.y, self.x - target.x); } else if (distance > optimalDistance + 100) { self.moveDirection = Math.atan2(dy, dx); } else { self.changeDirectionTimer++; if (self.changeDirectionTimer >= 120) { var currentAngle = Math.atan2(dy, dx); self.moveDirection = currentAngle + Math.PI / 2 * (Math.random() > 0.5 ? 1 : -1); self.changeDirectionTimer = 0; } } } else { // Normal movement - avoid shield area if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) { var angleAwayFromShield = Math.atan2(self.y - cannon.y, self.x - cannon.x); self.moveDirection = angleAwayFromShield; self.changeDirectionTimer = 0; } else { self.changeDirectionTimer++; if (self.changeDirectionTimer >= 180) { self.moveDirection = Math.random() * Math.PI * 2; self.changeDirectionTimer = 0; } } } // Move in current direction var moveX = Math.cos(self.moveDirection) * self.speed; var moveY = Math.sin(self.moveDirection) * self.speed; var newX = self.x + moveX; var newY = self.y + moveY; if (newX < 100 || newX > 1948) { self.moveDirection = Math.PI - self.moveDirection; } if (newY < 300 || newY > 2632) { self.moveDirection = -self.moveDirection; } // Apply movement self.x = Math.max(100, Math.min(1948, newX)); self.y = Math.max(300, Math.min(2632, newY)); // Find and shoot at nearest enemy self.targetShip = self.findNearestEnemyShip(); if (self.targetShip) { var newBullets = self.shootAtTarget(self.targetShip.x, self.targetShip.y); if (newBullets && newBullets.length > 0) { for (var bulletIndex = 0; bulletIndex < newBullets.length; bulletIndex++) { cannonballs.push(newBullets[bulletIndex]); game.addChild(newBullets[bulletIndex]); } LK.getSound('shoot').play(); } } }; return self; }); var GuidedProjectile = Container.expand(function () { var self = Container.call(this); var graphics = self.attachAsset('guidedProjectile', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 16; self.damage = 3; // Base damage for guided projectiles (will be enhanced by cannonDamage) self.targetX = 0; self.targetY = 0; self.lastTargetUpdateTime = 0; self.targetUpdateDelay = 30; // Update target every 0.5 seconds self.creationTime = 0; // Track when the projectile was created self.lifetime = 120; // 2 seconds at 60fps (2 * 60) self.update = function () { // Update target position periodically if (LK.ticks - self.lastTargetUpdateTime >= self.targetUpdateDelay) { // Find nearest enemy ship var nearestShip = null; var nearestDistance = Infinity; for (var i = 0; i < ships.length; i++) { var ship = ships[i]; if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) { var dx = ship.x - self.x; var dy = ship.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestShip = ship; } } } // If guided shot is active, also look for health kits to target if (guidedShot && !nearestShip) { var nearestHealthKit = null; var nearestHealthKitDistance = Infinity; for (var hk = 0; hk < healthKits.length; hk++) { var healthKit = healthKits[hk]; if (healthKit && healthKit.parent) { var dx = healthKit.x - self.x; var dy = healthKit.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestHealthKitDistance) { nearestHealthKitDistance = distance; nearestHealthKit = healthKit; } } } if (nearestHealthKit) { self.targetX = nearestHealthKit.x; self.targetY = nearestHealthKit.y; } } // If we found a ship, target it (priority over health kits) if (nearestShip) { self.targetX = nearestShip.x; self.targetY = nearestShip.y; } self.lastTargetUpdateTime = LK.ticks; } // Move toward target var dx = self.targetX - self.x; var dy = self.targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Add slight rotation for visual effect graphics.rotation += 0.1; }; return self; }); var GuidedShotPowerUp = Container.expand(function () { var self = Container.call(this); self.speed = 2; var graphics = self.attachAsset('guidedShotPowerUp', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { self.x += self.speed; }; return self; }); var HealthKit = Container.expand(function () { var self = Container.call(this); self.speed = 2; self.healAmount = 2; var graphics = self.attachAsset('healthKit', { anchorX: 0.5, anchorY: 0.5 }); // Add a cross symbol var crossV = LK.getAsset('healthKit', { width: 20, height: 60, color: 0xffffff, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); var crossH = LK.getAsset('healthKit', { width: 60, height: 20, color: 0xffffff, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.addChild(crossV); self.addChild(crossH); self.update = function () { self.x += self.speed; }; return self; }); var Octopus = Container.expand(function () { var self = Container.call(this); self.speed = 4; self.targetShip = null; self.state = 'seeking'; // seeking, grabbing, sinking self.sinkTimer = 0; self.sinkDuration = 120; // 2 seconds at 60fps var graphics = self.attachAsset('smallShip', { anchorX: 0.5, anchorY: 0.5, color: 0x800080, // Purple color for octopus shape: 'ellipse' }); graphics.scaleX = 0.8; graphics.scaleY = 0.8; graphics.tint = 0x800080; self.init = function () { // Find nearest ship to grab var nearestShip = null; var nearestDistance = Infinity; for (var i = 0; i < ships.length; i++) { var ship = ships[i]; if (ship && ship.graphics && ship.graphics.visible && !ship.isDying && ship.shipType !== 'boss') { var dx = ship.x - self.x; var dy = ship.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestShip = ship; } } } self.targetShip = nearestShip; }; self.update = function () { if (self.state === 'seeking' && self.targetShip && self.targetShip.parent) { // Move towards target ship var dx = self.targetShip.x - self.x; var dy = self.targetShip.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Check if reached the ship if (distance < 50) { self.state = 'grabbing'; self.targetShip.isDying = true; // Start sinking animation tween(self.targetShip, { y: self.targetShip.y + 100, scaleX: 0.8, scaleY: 0.8, alpha: 0.5 }, { duration: 2000, easing: tween.easeIn, onFinish: function onFinish() { if (self.targetShip && self.targetShip.parent) { self.targetShip.destroy(); // Remove from ships array for (var i = ships.length - 1; i >= 0; i--) { if (ships[i] === self.targetShip) { ships.splice(i, 1); break; } } } } }); self.state = 'sinking'; } } else if (self.state === 'sinking') { self.sinkTimer++; if (self.sinkTimer >= self.sinkDuration) { // Octopus disappears self.destroy(); } } }; return self; }); var OctopusArm = Container.expand(function () { var self = Container.call(this); self.speed = 28; self.targetShip = null; self.state = 'emerging'; // emerging, seeking, grabbing, sinking, retreating self.stateTimer = 0; self.grabCount = 0; self.maxGrabs = 5; self.lifeTimer = 1200; // 20 seconds at 60fps (20 * 60) var graphics = self.attachAsset('octopusArm', { anchorX: 0.5, anchorY: 0.5 }); graphics.tint = 0x4B0082; // Deep purple color for octopus arm self.init = function () { self.x = 1024; // Start from center bottom self.y = 2600; // Start from bottom of screen self.alpha = 0.8; self.scaleX = 0.5; self.scaleY = 0.5; self.state = 'emerging'; self.stateTimer = 60; // 1 second emergence time self.grabCount = 0; // Emergence animation tween(self, { y: 2200, scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 1000, easing: tween.easeOut }); }; self.findNearestShip = function () { var nearestShip = null; var nearestDistance = Infinity; for (var i = 0; i < ships.length; i++) { var ship = ships[i]; if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) { var dx = ship.x - self.x; var dy = ship.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestShip = ship; } } } return nearestShip; }; self.update = function () { self.stateTimer--; self.lifeTimer--; // Decrease life timer each frame // Check if 20 seconds have passed - force retreat (only if not already retreating) if (self.lifeTimer <= 0 && self.state !== 'retreating') { self.state = 'retreating'; self.stateTimer = 60; } if (self.state === 'emerging') { if (self.stateTimer <= 0) { self.state = 'seeking'; self.targetShip = self.findNearestShip(); } } else if (self.state === 'seeking') { if (self.targetShip && self.targetShip.parent && !self.targetShip.isDying) { // Move towards target ship var dx = self.targetShip.x - self.x; var dy = self.targetShip.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 0) { self.x += dx / distance * self.speed; self.y += dy / distance * self.speed; } // Check if reached the ship if (distance < 80) { self.state = 'grabbing'; self.stateTimer = 30; // Half second grab time // Special handling for bosses - damage instead of instant kill if (self.targetShip.shipType === 'boss') { self.targetShip.health -= 15; self.targetShip.updateHealthBar(); LK.effects.flashObject(self.targetShip, 0xff0000, 500); if (self.targetShip.health <= 0) { self.targetShip.isDying = true; // Count this as an octopus kill since boss died from octopus damage self.grabCount++; // Grab animation - ship gets pulled towards octopus tween(self.targetShip, { x: self.x, y: self.y + 50, scaleX: 0.6, scaleY: 0.6, alpha: 0.7 }, { duration: 500, easing: tween.easeIn }); } else { // Boss survives, octopus retreats after damaging but doesn't count as kill self.state = 'retreating'; self.stateTimer = 60; } } else { self.targetShip.isDying = true; // Count this kill immediately when octopus grabs the ship self.grabCount++; // Grab animation - ship gets pulled towards octopus tween(self.targetShip, { x: self.x, y: self.y + 50, scaleX: 0.6, scaleY: 0.6, alpha: 0.7 }, { duration: 500, easing: tween.easeIn }); } } } else { // Find new target if current one is gone self.targetShip = self.findNearestShip(); if (!self.targetShip) { self.state = 'retreating'; self.stateTimer = 60; } } } else if (self.state === 'grabbing') { if (self.stateTimer <= 0) { self.state = 'sinking'; self.stateTimer = 90; // 1.5 second sink time // Sinking animation - ship disappears into the depths if (self.targetShip && self.targetShip.parent) { tween(self.targetShip, { y: self.targetShip.y + 200, scaleX: 0.2, scaleY: 0.2, alpha: 0.0, rotation: Math.PI }, { duration: 1500, easing: tween.easeIn, onFinish: function onFinish() { if (self.targetShip && self.targetShip.parent) { // Award points and remove ship LK.setScore(LK.getScore() + self.targetShip.points); scoreTxt.setText('Score: ' + LK.getScore()); LK.getSound('shipDestroyed').play(); self.targetShip.destroy(); // Remove from ships array for (var i = ships.length - 1; i >= 0; i--) { if (ships[i] === self.targetShip) { ships.splice(i, 1); break; } } } } }); } } } else if (self.state === 'sinking') { if (self.stateTimer <= 0) { // Continue seeking until lifetime expires (controlled by lifeTimer check above) // Only continue seeking if we still have time left and haven't reached max grabs if (self.lifeTimer > 60 && self.grabCount < self.maxGrabs) { self.state = 'seeking'; self.targetShip = self.findNearestShip(); } else { // Time is almost up or max grabs reached, start retreating self.state = 'retreating'; self.stateTimer = 60; } } } else if (self.state === 'retreating') { // Retreat animation self.y += 4; self.alpha -= 0.02; self.scaleX -= 0.02; self.scaleY -= 0.02; if (self.stateTimer <= 0 || self.alpha <= 0) { self.destroy(); } } }; return self; }); var OctopusPowerUp = Container.expand(function () { var self = Container.call(this); self.speed = 2; var graphics = self.attachAsset('octopusPowerUpSphere', { anchorX: 0.5, anchorY: 0.5 }); // Add "P" text on the sphere var pText = new Text2('P', { size: 120, fill: 0xFFFFFF }); pText.anchor.set(0.5, 0.5); self.addChild(pText); self.update = function () { self.x += self.speed; }; return self; }); var PermanentAlly = Container.expand(function () { var self = Container.call(this); self.health = 2; self.maxHealth = 2; self.speed = 2; self.lastShotTime = 0; self.shotDelay = 120; // 2 seconds self.moveDirection = Math.random() * Math.PI * 2; // Random initial direction self.changeDirectionTimer = 0; self.targetShip = null; self.isShielded = false; self.shieldTimer = 0; self.targetPriority = 'enemy'; self.movingToPowerUp = false; var graphics = self.attachAsset('friendlyOctopus', { anchorX: 0.5, anchorY: 0.5 }); graphics.tint = 0x88ff44; // Different green tint to distinguish from temporary allies graphics.scaleX = 0.6; graphics.scaleY = 0.6; // Create health bar self.healthBarBg = LK.getAsset('healthBarBg', { width: 120, height: 12, color: 0x444444, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBarBg.y = -graphics.height / 2 - 20; self.addChild(self.healthBarBg); self.healthBar = LK.getAsset('healthBar', { width: 116, height: 8, color: 0x88FF44, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBar.y = -graphics.height / 2 - 20; self.addChild(self.healthBar); self.updateHealthBar = function () { if (self.healthBar && self.maxHealth > 0) { var healthPercent = Math.max(0, self.health / self.maxHealth); self.healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { self.healthBar.tint = 0x88FF44; // Bright green } else if (healthPercent > 0.3) { self.healthBar.tint = 0xFFFF00; // Yellow } else { self.healthBar.tint = 0xFF0000; // Red } } }; self.findNearestTarget = function () { var nearestTarget = null; var nearestDistance = Infinity; var targetPriority = 'enemy'; // Default priority // First priority: Find nearest enemy ship for (var i = 0; i < ships.length; i++) { var ship = ships[i]; if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) { var dx = ship.x - self.x; var dy = ship.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestTarget = ship; targetPriority = 'enemy'; } } } // Second priority: Find power-ups if no nearby enemies or enemies are far if (!nearestTarget || nearestDistance > 600) { // Look for power-ups to collect var powerUpArrays = [powerUps, healthKits, shieldPowerUps, guidedShotPowerUps, octopusPowerUps]; for (var arrayIndex = 0; arrayIndex < powerUpArrays.length; arrayIndex++) { var powerUpArray = powerUpArrays[arrayIndex]; for (var p = 0; p < powerUpArray.length; p++) { var powerUp = powerUpArray[p]; if (powerUp && powerUp.parent) { var dx = powerUp.x - self.x; var dy = powerUp.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestTarget = powerUp; targetPriority = 'powerup'; } } } } } self.targetPriority = targetPriority; return nearestTarget; }; self.findNearestEnemyShip = function () { var nearestShip = null; var nearestDistance = Infinity; for (var i = 0; i < ships.length; i++) { var ship = ships[i]; if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) { var dx = ship.x - self.x; var dy = ship.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestShip = ship; } } } return nearestShip; }; self.canShoot = function () { var currentShotDelay = rapidFire ? self.shotDelay / 3 : self.shotDelay; return LK.ticks - self.lastShotTime >= currentShotDelay; }; self.shootAtTarget = function (targetX, targetY) { if (!self.canShoot()) return null; self.lastShotTime = LK.ticks; var bullets = []; var dx = targetX - self.x; var dy = targetY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var baseAngle = Math.atan2(dy, dx); if (guidedShot) { // Fire guided projectiles like cannon when guided shot is active var projectilesToFire = 1; if (tripleShot) { projectilesToFire = 3; // Triple shot multiplies guided projectiles } // Fire multiple guided projectiles if triple shot is active for (var projIndex = 0; projIndex < projectilesToFire; projIndex++) { var guidedProjectile = new GuidedProjectile(); guidedProjectile.x = self.x + (projIndex - Math.floor(projectilesToFire / 2)) * 30; // Spread them out horizontally guidedProjectile.y = self.y; guidedProjectile.targetX = targetX; guidedProjectile.targetY = targetY; guidedProjectile.lastTargetUpdateTime = LK.ticks; guidedProjectile.creationTime = LK.ticks; // Set creation time // Apply rapid fire speed boost if active if (rapidFire) { guidedProjectile.speed = guidedProjectile.speed * 1.8; // 80% faster when rapid fire is active } guidedProjectiles.push(guidedProjectile); game.addChild(guidedProjectile); } return null; // Don't return cannonballs when using guided shot } else if (tripleShot) { // Fire three cannonballs with slight angle differences for (var i = 0; i < 3; i++) { var bullet = new Cannonball(); bullet.x = self.x; bullet.y = self.y; var angleOffset = (i - 1) * 0.2; // -0.2, 0, 0.2 radians var adjustedAngle = baseAngle + angleOffset; bullet.velocityX = Math.cos(adjustedAngle) * bullet.speed; bullet.velocityY = Math.sin(adjustedAngle) * bullet.speed; bullet.damage = 2; // Permanent ally bullets do more damage bullets.push(bullet); } } else { // Fire single cannonball var bullet = new Cannonball(); bullet.x = self.x; bullet.y = self.y; bullet.velocityX = dx / distance * bullet.speed; bullet.velocityY = dy / distance * bullet.speed; bullet.damage = 2; // Permanent ally bullets do more damage bullets.push(bullet); } return bullets; }; self.takeDamage = function (damage) { if (self.isShielded) { damage = Math.floor(damage / 2); // Shield reduces damage by half } self.health -= damage; if (self.health < 0) self.health = 0; self.updateHealthBar(); // Flash red when taking damage tween(self, { tint: 0xff0000 }, { duration: 100, onFinish: function onFinish() { var originalTint = self.isShielded ? 0x88ccff : 0x88ff44; tween(self, { tint: originalTint }, { duration: 100 }); } }); }; self.activateShield = function () { self.isShielded = true; self.shieldTimer = 900; // 15 seconds at 60fps self.graphics.tint = 0x88ccff; // Blue tint for shield }; self.update = function () { // Update shield status if (self.isShielded && self.shieldTimer > 0) { self.shieldTimer--; if (self.shieldTimer <= 0) { self.isShielded = false; tween(self.graphics, { tint: 0x88ff44 }, { duration: 500 }); } } // Find nearest target (enemy or power-up) var target = self.findNearestTarget(); var screenCenterX = 1024; // Center of screen var screenCenterY = 1366; // Middle of screen height var minDistanceFromCannon = 200; // Minimum distance to stay away from cannon var distanceToCannon = Math.sqrt(Math.pow(cannon.x - self.x, 2) + Math.pow(cannon.y - self.y, 2)); // Check if cannon shield is active and avoid it var shieldAvoidanceDistance = cannon.isShielded ? 300 : minDistanceFromCannon; // Movement logic based on target priority if (target && self.targetPriority === 'powerup') { // Move towards power-up var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); // Check if path to power-up would go through shield area if (cannon.isShielded) { var distanceToCannonOnPath = Math.sqrt(Math.pow(cannon.x - self.x, 2) + Math.pow(cannon.y - self.y, 2)); if (distanceToCannonOnPath < shieldAvoidanceDistance) { // Move around the shield instead of towards power-up var angleAroundShield = Math.atan2(self.y - cannon.y, self.x - cannon.x) + Math.PI / 4; self.moveDirection = angleAroundShield; self.movingToPowerUp = false; } else if (distance > 50) { // Move towards power-up if not close enough and path is clear self.moveDirection = Math.atan2(dy, dx); self.movingToPowerUp = true; } else { // Close enough to power-up, resume normal movement self.movingToPowerUp = false; } } else { if (distance > 50) { // Move towards power-up if not close enough self.moveDirection = Math.atan2(dy, dx); self.movingToPowerUp = true; } else { // Close enough to power-up, resume normal movement self.movingToPowerUp = false; } } } else if (target && self.targetPriority === 'enemy' && !self.movingToPowerUp) { // Combat positioning - stay at medium range from enemies var dx = target.x - self.x; var dy = target.y - self.y; var distance = Math.sqrt(dx * dx + dy * dy); var optimalDistance = 300; // Optimal combat distance // Avoid shield area during combat if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) { var angleAwayFromShield = Math.atan2(self.y - cannon.y, self.x - cannon.x); self.moveDirection = angleAwayFromShield; } else if (distance < optimalDistance - 50) { // Too close, back away self.moveDirection = Math.atan2(self.y - target.y, self.x - target.x); } else if (distance > optimalDistance + 100) { // Too far, move closer self.moveDirection = Math.atan2(dy, dx); } else { // Good distance, strafe around target self.changeDirectionTimer++; if (self.changeDirectionTimer >= 120) { var currentAngle = Math.atan2(dy, dx); self.moveDirection = currentAngle + Math.PI / 2 * (Math.random() > 0.5 ? 1 : -1); self.changeDirectionTimer = 0; } } } else { // No target or normal movement if (distanceToCannon < shieldAvoidanceDistance) { var angleAwayFromCannon = Math.atan2(self.y - cannon.y, self.x - cannon.x); self.moveDirection = angleAwayFromCannon; self.changeDirectionTimer = 0; } else { self.changeDirectionTimer++; if (self.changeDirectionTimer >= 180) { self.moveDirection = Math.random() * Math.PI * 2; self.changeDirectionTimer = 0; } } } // Move in current direction var moveX = Math.cos(self.moveDirection) * self.speed; var moveY = Math.sin(self.moveDirection) * self.speed; var newX = self.x + moveX; var newY = self.y + moveY; // Ensure ally stays within screen center area var minX = Math.max(100, screenCenterX - 500); var maxX = Math.min(1948, screenCenterX + 500); var minY = Math.max(800, screenCenterY - 300); var maxY = Math.min(2000, screenCenterY + 300); // Additional check to maintain distance from cannon var newCannonDistance = Math.sqrt(Math.pow(cannon.x - newX, 2) + Math.pow(cannon.y - newY, 2)); if (newCannonDistance < minDistanceFromCannon) { var pushAngle = Math.atan2(newY - cannon.y, newX - cannon.x); newX = cannon.x + Math.cos(pushAngle) * minDistanceFromCannon; newY = cannon.y + Math.sin(pushAngle) * minDistanceFromCannon; } if (newX < minX || newX > maxX) { self.moveDirection = Math.PI - self.moveDirection; } if (newY < minY || newY > maxY) { self.moveDirection = -self.moveDirection; } // Apply movement self.x = Math.max(minX, Math.min(maxX, newX)); self.y = Math.max(minY, Math.min(maxY, newY)); // Combat behavior - shoot at enemies self.targetShip = self.findNearestEnemyShip(); if (self.targetShip) { var newBullets = self.shootAtTarget(self.targetShip.x, self.targetShip.y); if (newBullets && newBullets.length > 0) { for (var bulletIndex = 0; bulletIndex < newBullets.length; bulletIndex++) { cannonballs.push(newBullets[bulletIndex]); game.addChild(newBullets[bulletIndex]); } LK.getSound('shoot').play(); } } }; return self; }); var PowerUp = Container.expand(function () { var self = Container.call(this); self.powerType = 'tripleShot'; self.speed = 2; self.graphics = null; self.init = function (type) { self.powerType = type; if (type === 'tripleShot') { self.graphics = self.attachAsset('tripleShotPowerUp', { anchorX: 0.5, anchorY: 0.5 }); } else if (type === 'rapidFire') { self.graphics = self.attachAsset('rapidFirePowerUp', { anchorX: 0.5, anchorY: 0.5 }); } }; self.update = function () { self.x += self.speed; }; return self; }); var ShieldPowerUp = Container.expand(function () { var self = Container.call(this); self.speed = 2; var graphics = self.attachAsset('shieldPowerUp', { anchorX: 0.5, anchorY: 0.5 }); self.update = function () { self.x += self.speed; }; return self; }); var Ship = Container.expand(function () { var self = Container.call(this); self.shipType = 'small'; self.speed = 2; self.points = 10; self.graphics = null; self.healthBar = null; self.healthBarBg = null; self.isDying = false; // Flag to prevent damage during death animation self.init = function (type) { self.shipType = type; if (type === 'small') { self.graphics = self.attachAsset('smallShip', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 3; // Small ships: 100 points in waves 1-2, then scale with wave if (currentWave <= 2) { self.points = 100; } else { self.points = 10 + (currentWave - 2) * 5; // Gradual increase after wave 2 } self.health = 1; self.maxHealth = 1; } else if (type === 'medium') { self.graphics = self.attachAsset('mediumShip', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 2; // Medium ships: 100 points when first appearing (waves 3-4), then scale if (currentWave <= 4) { self.points = 100; } else { self.points = 25 + (currentWave - 4) * 10; // Gradual increase after wave 4 } self.health = 3; self.maxHealth = 3; } else if (type === 'large') { self.graphics = self.attachAsset('largeShip', { anchorX: 0.5, anchorY: 0.5 }); self.speed = 1.5; // Large ships: 100 points when first appearing (waves 5-6), then scale if (currentWave <= 6) { self.points = 100; } else { self.points = 50 + (currentWave - 6) * 15; // Gradual increase after wave 6 } self.health = 5; self.maxHealth = 5; } // Create health bar background - wider for better visibility var healthBarWidth = Math.max(120, self.graphics.width * 1.2); self.healthBarBg = LK.getAsset('healthBarBg', { width: healthBarWidth, height: 12, color: 0x444444, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBarBg.y = -self.graphics.height / 2 - 20; self.addChild(self.healthBarBg); // Create health bar foreground - wider for better visibility self.healthBar = LK.getAsset('healthBar', { width: healthBarWidth - 4, height: 8, color: 0x00FF00, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); self.healthBar.y = -self.graphics.height / 2 - 20; self.addChild(self.healthBar); // Show health bar for all ships now - no hiding for small ships // Set shooting properties based on ship type - slower attack speeds self.lastShotTime = 0; if (type === 'small') { self.shotDelay = 180; // 3 seconds (much slower) } else if (type === 'medium') { self.shotDelay = 150; // 2.5 seconds (much slower) } else if (type === 'large') { self.shotDelay = 120; // 2 seconds (much slower) } }; self.updateHealthBar = function () { if (self.healthBar && self.maxHealth >= 1) { var healthPercent = Math.max(0, self.health / self.maxHealth); self.healthBar.scaleX = healthPercent; if (healthPercent > 0.6) { self.healthBar.tint = 0x00FF00; // Green } else if (healthPercent > 0.3) { self.healthBar.tint = 0xFFFF00; // Yellow } else { self.healthBar.tint = 0xFF0000; // Red } // Ensure health bar visibility for all ship types self.healthBar.visible = true; self.healthBarBg.visible = true; } }; self.update = function () { self.x += self.speed; // Ensure collision detection remains active if (self.graphics) { self.graphics.visible = true; } }; self.canShoot = function () { return LK.ticks - self.lastShotTime >= self.shotDelay; }; self.shootAtCannon = function (cannonX, cannonY) { if (!self.canShoot()) return null; self.lastShotTime = LK.ticks; var bullet = new EnemyBullet(); bullet.x = self.x; bullet.y = self.y; var dx = cannonX - self.x; var dy = cannonY - self.y; var distance = Math.sqrt(dx * dx + dy * dy); bullet.velocityX = dx / distance * bullet.speed; bullet.velocityY = dy / distance * bullet.speed; // Enhanced damage based on ship type if (self.shipType === 'small') { bullet.damage = 1; } else if (self.shipType === 'medium') { bullet.damage = 2; } else if (self.shipType === 'large') { bullet.damage = 3; } return bullet; }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x001122 }); /**** * Game Code ****/ var oceanBackground = LK.getAsset('oceanBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); oceanBackground.visible = false; // Start hidden since we show sketches first game.addChild(oceanBackground); // Game background - will replace ocean background when game starts var gameBackground = LK.getAsset('gameBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); gameBackground.visible = true; // Start visible for sketches game.addChild(gameBackground); // Start menu variables - reset to beginning var gameStarted = false; var sketchShown = true; // Start with sketches visible var menuShown = false; // Track if menu has been shown var currentSketchIndex = 0; // Reset to show first sketch var totalSketches = 3; var startMenu = new Container(); game.addChild(startMenu); startMenu.visible = false; // Start with menu hidden // Sketch menu container var sketchMenu = new Container(); game.addChild(sketchMenu); sketchMenu.visible = true; // Start with sketches visible // Sketch background - only visible during sketch var sketchBackgroundImage = sketchMenu.attachAsset('sketchBackground', { anchorX: 0, anchorY: 0, x: 0, y: 0 }); // Sketch images var sketchImage = sketchMenu.attachAsset('sketch', { anchorX: 0.5, anchorY: 0.5 }); sketchImage.x = 1024; sketchImage.y = 1366; var sketchImage2 = sketchMenu.attachAsset('sketch2', { anchorX: 0.5, anchorY: 0.5 }); sketchImage2.x = 1024; sketchImage2.y = 1366; sketchImage2.visible = false; var sketchImage3 = sketchMenu.attachAsset('sketch3', { anchorX: 0.5, anchorY: 0.5 }); sketchImage3.x = 1024; sketchImage3.y = 1366; sketchImage3.visible = false; // Add pulsating animation to sketch images function pulseSketchImage(sketchImg) { if (sketchImg.visible && sketchImg.parent) { tween(sketchImg, { scaleX: 1.05, scaleY: 1.05, alpha: 0.9 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { if (sketchImg.visible && sketchImg.parent) { tween(sketchImg, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { pulseSketchImage(sketchImg); } }); } } }); } } // Sketch images no longer have pulsating animations // Continue button text var continueText = new Text2('Tap to continue', { size: 60, fill: 0xFFFFFF }); continueText.anchor.set(0.5, 0.5); continueText.x = 1024; continueText.y = 2400; sketchMenu.addChild(continueText); // Add pulsating animation to continue text function pulseContinueText() { tween(continueText, { alpha: 0.5, scaleX: 0.9, scaleY: 0.9 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(continueText, { alpha: 1.0, scaleX: 1.0, scaleY: 1.0 }, { duration: 1000, easing: tween.easeInOut, onFinish: pulseContinueText }); } }); } // Start continue text animation immediately since sketches are shown first pulseContinueText(); // Ocean title image var oceanTitleImage = startMenu.attachAsset('oceanTitle', { anchorX: 0.5, anchorY: 0.5 }); oceanTitleImage.x = 1024; oceanTitleImage.y = 750; // Defender title image var defenderTitleImage = startMenu.attachAsset('defenderTitle', { anchorX: 0.5, anchorY: 0.5 }); defenderTitleImage.x = 1024; defenderTitleImage.y = 1600; // Play button image var playButtonImage = startMenu.attachAsset('playButtonImage', { anchorX: 0.5, anchorY: 0.5 }); playButtonImage.x = 1024; playButtonImage.y = 2300; // Add pulsating animation to ocean title function pulseOceanTitle() { tween(oceanTitleImage, { scaleX: 1.1, scaleY: 1.1 }, { duration: 1000, easing: tween.easeInOut, onFinish: function onFinish() { tween(oceanTitleImage, { scaleX: 1.0, scaleY: 1.0 }, { duration: 1000, easing: tween.easeInOut, onFinish: pulseOceanTitle }); } }); } pulseOceanTitle(); // Add pulsating animation to defender title function pulseDefenderTitle() { tween(defenderTitleImage, { scaleX: 1.1, scaleY: 1.1 }, { duration: 1200, easing: tween.easeInOut, onFinish: function onFinish() { tween(defenderTitleImage, { scaleX: 1.0, scaleY: 1.0 }, { duration: 1200, easing: tween.easeInOut, onFinish: pulseDefenderTitle }); } }); } pulseDefenderTitle(); // Add pulsating animation to play button function pulsePlayButton() { tween(playButtonImage, { scaleX: 1.15, scaleY: 1.15 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { tween(playButtonImage, { scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: pulsePlayButton }); } }); } pulsePlayButton(); // Creator name image var creatorNameImage = startMenu.attachAsset('creatorNameImage', { anchorX: 0.5, anchorY: 0.5 }); creatorNameImage.x = 1024; creatorNameImage.y = 2500; // Add pulsating animation to creator image function pulseCreatorName() { if (creatorNameImage.visible && creatorNameImage.parent) { tween(creatorNameImage, { scaleX: 1.05, scaleY: 1.05, alpha: 0.9 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { if (creatorNameImage.visible && creatorNameImage.parent) { tween(creatorNameImage, { scaleX: 1.0, scaleY: 1.0, alpha: 1.0 }, { duration: 1500, easing: tween.easeInOut, onFinish: function onFinish() { pulseCreatorName(); } }); } } }); } } pulseCreatorName(); // Start playing the sketch music immediately when game starts LK.playMusic('sketchMusic', { loop: true, fade: { start: 0, end: 0.5, duration: 500 } }); // Initialize permanent allies array var permanentAllies = []; function checkWaveCompletion() { // Simple wave completion based on enemies killed var requiredKills = enemiesRequiredPerWave + (currentWave - 1) * 5; if (enemiesKilledThisWave >= requiredKills && !waveCompleted) { waveCompleted = true; enemiesKilledThisWave = 0; waveCompleted = false; } } var cannon = game.addChild(new Cannon()); cannon.x = 1024; cannon.y = 2600; cannon.visible = false; cannon.baseFireDelay = 300; // Store original fire delay // Add breathing animation to cannon function breatheCannon() { if (cannon.graphics && cannon.graphics.parent) { tween(cannon.graphics, { scaleX: 1.05, scaleY: 1.05 }, { duration: 2000, easing: tween.easeInOut, onFinish: function onFinish() { if (cannon.graphics && cannon.graphics.parent) { tween(cannon.graphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 2000, easing: tween.easeInOut, onFinish: breatheCannon }); } } }); } } // Start breathing animation immediately breatheCannon(); // Shield effect visual var shieldEffect = LK.getAsset('shieldEffect', { anchorX: 0.5, anchorY: 0.5 }); shieldEffect.x = cannon.x; shieldEffect.y = cannon.y; shieldEffect.visible = false; shieldEffect.alpha = 0.6; shieldEffect.scaleX = 0.3; shieldEffect.scaleY = 0.3; game.addChild(shieldEffect); var ships = []; var cannonballs = []; var shipSpawnTimer = 0; var shipSpawnDelay = 120; var difficultyTimer = 0; var shipsEscaped = 0; var maxEscapedShips = 10; // Escaped ships counter UI var escapedShipsTxt = new Text2('Escaped: 0/10', { size: 50, fill: 0xFFFFFF }); escapedShipsTxt.anchor.set(0.5, 0); escapedShipsTxt.y = 60; escapedShipsTxt.visible = false; LK.gui.top.addChild(escapedShipsTxt); // Wave number display UI var waveNumberTxt = new Text2('Wave: 1', { size: 50, fill: 0xFFFFFF }); waveNumberTxt.anchor.set(0.5, 0); waveNumberTxt.y = 120; waveNumberTxt.visible = false; LK.gui.top.addChild(waveNumberTxt); var scoreTxt = new Text2('Score: 0', { size: 60, fill: 0xFFFFFF }); scoreTxt.anchor.set(0.5, 0); scoreTxt.visible = false; LK.gui.top.addChild(scoreTxt); var powerUps = []; var tripleShot = false; var tripleShotTimer = 0; var rapidFire = false; var rapidFireTimer = 0; var powerUpDuration = 300; // 5 seconds at 60fps // Power-up timer UI var powerUpTimerTxt = new Text2('', { size: 70, fill: 0xFFFFFF }); powerUpTimerTxt.anchor.set(0.5, 0.5); powerUpTimerTxt.x = 0; powerUpTimerTxt.y = 100; LK.gui.center.addChild(powerUpTimerTxt); var activePowerUpType = ''; var activePowerUpTimer = 0; var isShooting = false; var shootingTimer = 0; // Wave system var currentWave = 1; var lastWaveScore = 0; var waveMessage = null; var waveMessageTimer = 0; var waveCompleted = false; // Wave completion tracking var enemiesKilledThisWave = 0; var enemiesRequiredPerWave = 15; // Base number of enemies to kill per wave // Cannon health system var enemyBullets = []; var healthKits = []; var shieldPowerUps = []; var guidedShotPowerUps = []; var guidedProjectiles = []; var octopusPowerUps = []; var octopuses = []; var friendlyOctopuses = []; var guidedShot = false; var guidedShotTimer = 0; var guidedShotDuration = 420; // 7 seconds at 60fps // Cannon health UI - positioned next to cannon var cannonHealthBarBg = LK.getAsset('cannonHealthBarBg', { width: 400, height: 20, color: 0x444444, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); cannonHealthBarBg.x = cannon.x; cannonHealthBarBg.y = cannon.y - 150; cannonHealthBarBg.visible = false; game.addChild(cannonHealthBarBg); var cannonHealthBar = LK.getAsset('cannonHealthBar', { width: 396, height: 16, color: 0x00ff00, shape: 'box', anchorX: 0.5, anchorY: 0.5 }); cannonHealthBar.x = cannon.x; cannonHealthBar.y = cannon.y - 150; cannonHealthBar.visible = false; game.addChild(cannonHealthBar); var healthText = new Text2('Health: 10/10', { size: 40, fill: 0xFFFFFF }); healthText.anchor.set(0.5, 0.5); healthText.x = cannon.x; healthText.y = cannon.y - 100; healthText.visible = false; game.addChild(healthText); // Wave message text var waveMessageTxt = new Text2('', { size: 100, fill: 0xFFFF00 }); waveMessageTxt.anchor.set(0.5, 0.5); LK.gui.center.addChild(waveMessageTxt); waveMessageTxt.visible = false; // Track if boss has been spawned this wave var bossSpawnedThisWave = false; function spawnShip() { // Check if it's a boss wave (every 4 waves) and boss hasn't been spawned yet if (currentWave % 4 === 0 && currentWave >= 4 && !bossSpawnedThisWave) { // Check if there's already a boss in the ships array var bossExists = false; for (var i = 0; i < ships.length; i++) { if (ships[i].shipType === 'boss') { bossExists = true; break; } } // Only spawn boss if none exists if (!bossExists) { var boss = new Boss(); boss.init(currentWave); boss.x = -300; // Start further left due to larger size boss.y = 1366; // Center vertically boss.zIndex = 10; // Highest priority for rendering ships.push(boss); game.addChild(boss); game.children.sort(function (a, b) { return (a.zIndex || 0) - (b.zIndex || 0); }); bossSpawnedThisWave = true; } } // Regular ship spawning for all waves (including boss waves) var ship = new Ship(); var shipType; // Wave-based enemy spawning if (currentWave <= 2) { // Waves 1-2: Only small ships shipType = 'small'; } else if (currentWave <= 4) { // Waves 3-4: Small and medium ships var types = ['small', 'small', 'medium']; shipType = types[Math.floor(Math.random() * types.length)]; } else if (currentWave <= 6) { // Waves 5-6: Medium and some large ships var types = ['small', 'medium', 'medium', 'large']; shipType = types[Math.floor(Math.random() * types.length)]; } else { // Wave 7+: All ship types with more large ships var types = ['small', 'medium', 'large', 'large']; shipType = types[Math.floor(Math.random() * types.length)]; } ship.init(shipType); ship.x = -150; ship.y = 1000 + Math.random() * 1200; // Apply wave-based speed multiplier var speedMultiplier = 1 + (currentWave - 1) * 0.3; // If it's a boss wave, apply slower speed to all enemies if (currentWave % 4 === 0 && currentWave >= 4) { speedMultiplier = speedMultiplier * 0.5; // Make all enemies slower on boss waves } ship.speed = ship.speed * speedMultiplier; // Set zIndex based on ship type for proper layering if (ship.shipType === 'large') { ship.zIndex = 3; } else if (ship.shipType === 'medium') { ship.zIndex = 2; } else { ship.zIndex = 1; } game.children.sort(function (a, b) { return (a.zIndex || 0) - (b.zIndex || 0); }); ships.push(ship); game.addChild(ship); } function updateDifficulty() { difficultyTimer++; if (difficultyTimer % 1800 === 0) { shipSpawnDelay = Math.max(60, shipSpawnDelay - 10); } } function checkWaveTransition() { var currentScore = LK.getScore(); var newWave = Math.floor(currentScore / 1000) + 1; if (newWave > currentWave) { currentWave = newWave; lastWaveScore = Math.floor(currentScore / 1000) * 1000; // Reset boss spawning flag for new wave bossSpawnedThisWave = false; // Reset escaped ships counter for new wave shipsEscaped = 0; escapedShipsTxt.setText('Escaped: 0/10'); // Update wave number display waveNumberTxt.setText('Wave: ' + currentWave); // Increase max health by 2 each wave cannon.maxHealth += 2; cannon.health = cannon.maxHealth; // Restore to full health // Keep base fire rate constant across waves cannon.fireDelay = cannon.baseFireDelay; // Spawn new permanent ally each wave var newAlly = new PermanentAlly(); // Position allies in a circle around the cannon, close to it var allyIndex = permanentAllies.length; var angle = allyIndex * Math.PI * 2 / 8; // Distribute up to 8 allies in circle var radius = 250; // Close radius around cannon newAlly.x = cannon.x + Math.cos(angle) * radius; newAlly.y = cannon.y + Math.sin(angle) * radius; // Keep allies within screen bounds newAlly.x = Math.max(150, Math.min(1898, newAlly.x)); newAlly.y = Math.max(400, Math.min(2500, newAlly.y)); permanentAllies.push(newAlly); game.addChild(newAlly); // Spawn friendly octopus every 2 waves starting from wave 3 if (currentWave >= 3 && currentWave % 2 === 1) { var friendlyOctopus = new FriendlyOctopus(); friendlyOctopus.x = 1024; // Center position friendlyOctopus.y = 1500; // Middle of screen friendlyOctopuses.push(friendlyOctopus); game.addChild(friendlyOctopus); // Show friendly octopus spawn message var octopusMessage = new Text2('¡Pulpo Amigo se une a la batalla!', { size: 70, fill: 0x00ff88 }); octopusMessage.anchor.set(0.5, 0.5); LK.gui.center.addChild(octopusMessage); // Animate and remove message tween(octopusMessage, { y: -80, alpha: 0 }, { duration: 2500, easing: tween.easeOut, onFinish: function onFinish() { if (octopusMessage.parent) { octopusMessage.destroy(); } } }); } // Show ally spawn message var allyMessage = new Text2('¡Nuevo Aliado Permanente! (' + permanentAllies.length + ' activos)', { size: 70, fill: 0x88ff44 }); allyMessage.anchor.set(0.5, 0.5); LK.gui.center.addChild(allyMessage); // Animate and remove message tween(allyMessage, { y: -80, alpha: 0 }, { duration: 2500, easing: tween.easeOut, onFinish: function onFinish() { if (allyMessage.parent) { allyMessage.destroy(); } } }); // Show wave message if (currentWave === 2) { waveMessageTxt.setText('Segunda Oleada'); } else if (currentWave === 3) { waveMessageTxt.setText('Tercera Oleada'); } else if (currentWave === 4) { waveMessageTxt.setText('¡JEFE! - Oleada 4'); } else if (currentWave === 5) { waveMessageTxt.setText('Quinta Oleada'); } else if (currentWave % 4 === 0) { waveMessageTxt.setText('¡JEFE! - Oleada ' + currentWave); } else { waveMessageTxt.setText('Oleada ' + currentWave); } waveMessageTxt.visible = true; waveMessageTimer = 180; // Show for 3 seconds // Increase difficulty shipSpawnDelay = Math.max(30, 120 - (currentWave - 1) * 15); // Increase ship speeds for (var i = 0; i < ships.length; i++) { var ship = ships[i]; var speedMultiplier = 1 + (currentWave - 1) * 0.3; ship.speed = ship.speed * speedMultiplier; } } } // Touch/click handlers game.down = function (x, y, obj) { if (!gameStarted) return; cannon.aimAt(x, y); isShooting = true; shootingTimer = 0; // Fire immediately on first click var newCannonballs = cannon.fire(); if (newCannonballs) { for (var c = 0; c < newCannonballs.length; c++) { cannonballs.push(newCannonballs[c]); game.addChild(newCannonballs[c]); } } }; game.up = function (x, y, obj) { if (!gameStarted) return; isShooting = false; }; // Play button event handler playButtonImage.down = function (x, y, obj) { if (!gameStarted && menuShown) { // Start the game directly gameStarted = true; startMenu.visible = false; // Change background to game background oceanBackground.visible = false; gameBackground.visible = true; // Stop button animations tween.stop(oceanTitleImage); tween.stop(defenderTitleImage); tween.stop(playButtonImage); tween.stop(creatorNameImage); // Stop menu music completely before starting game music LK.stopMusic(); // Start game music immediately after stopping menu music LK.playMusic('calmSeaMelody', { loop: true }); // Show game UI cannon.visible = true; cannonHealthBarBg.visible = true; cannonHealthBar.visible = true; healthText.visible = true; scoreTxt.visible = true; escapedShipsTxt.visible = true; waveNumberTxt.visible = true; // Spawn first permanent ally close to cannon when game starts var firstAlly = new PermanentAlly(); firstAlly.x = cannon.x + 150; // Position close to cannon firstAlly.y = cannon.y - 200; // Position above cannon permanentAllies.push(firstAlly); game.addChild(firstAlly); } }; // Sketch menu click handler to cycle through sketches and show menu sketchMenu.down = function (x, y, obj) { if (sketchShown && !menuShown) { currentSketchIndex++; if (currentSketchIndex < totalSketches) { // Hide current sketch and show next one if (currentSketchIndex === 1) { sketchImage.visible = false; sketchImage2.visible = true; } else if (currentSketchIndex === 2) { sketchImage2.visible = false; sketchImage3.visible = true; } } else { // All sketches shown, show the menu menuShown = true; sketchMenu.visible = false; startMenu.visible = true; // Change background to ocean background gameBackground.visible = false; oceanBackground.visible = true; // Stop continue text animation tween.stop(continueText); // Stop sketch music completely before starting menu music LK.stopMusic(); // Start menu music immediately after stopping sketch music LK.playMusic('menuMusic', { loop: true }); // Start menu animations pulseOceanTitle(); pulseDefenderTitle(); pulsePlayButton(); pulseCreatorName(); } } }; game.update = function () { if (!gameStarted) return; // Handle continuous shooting if (isShooting) { shootingTimer++; var currentFireDelay = rapidFire ? cannon.fireDelay / 3 : cannon.fireDelay; var fireDelayInTicks = currentFireDelay / 16.67; // Convert ms to ticks if (shootingTimer >= fireDelayInTicks) { var newCannonballs = cannon.fire(); if (newCannonballs) { for (var c = 0; c < newCannonballs.length; c++) { cannonballs.push(newCannonballs[c]); game.addChild(newCannonballs[c]); } } shootingTimer = 0; } } updateDifficulty(); checkWaveTransition(); // Update wave message timer if (waveMessageTimer > 0) { waveMessageTimer--; if (waveMessageTimer <= 0) { waveMessageTxt.visible = false; } } shipSpawnTimer++; if (shipSpawnTimer >= shipSpawnDelay) { spawnShip(); shipSpawnTimer = 0; } for (var i = ships.length - 1; i >= 0; i--) { var ship = ships[i]; if (ship.lastX === undefined) ship.lastX = ship.x; if (ship.lastX <= 2048 && ship.x > 2048) { shipsEscaped++; escapedShipsTxt.setText('Escaped: ' + shipsEscaped + '/10'); LK.getSound('shipEscape').play(); ship.destroy(); ships.splice(i, 1); // Check if too many ships escaped if (shipsEscaped >= maxEscapedShips) { // Show final score and wave reached var finalScore = LK.getScore(); var finalWave = currentWave; LK.showGameOver('Puntaje: ' + finalScore + '\nOleada alcanzada: ' + finalWave); return; } continue; } // Ships shoot at cannon and allies if (ship.x > 200 && ship.x < 1800) { // Only shoot when in range var targetX = cannon.x; var targetY = cannon.y; // Check if there are permanent allies to target (50% chance to target ally instead of cannon) if (permanentAllies.length > 0 && Math.random() < 0.5) { // Find nearest permanent ally to shoot at var nearestAlly = null; var nearestDistance = Infinity; for (var allyIndex = 0; allyIndex < permanentAllies.length; allyIndex++) { var ally = permanentAllies[allyIndex]; if (ally && ally.parent && ally.health > 0) { var dx = ally.x - ship.x; var dy = ally.y - ship.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance) { nearestDistance = distance; nearestAlly = ally; } } } // Target the nearest ally if found if (nearestAlly) { targetX = nearestAlly.x; targetY = nearestAlly.y; } } var newBullet = ship.shootAtCannon(targetX, targetY); if (newBullet) { enemyBullets.push(newBullet); game.addChild(newBullet); } } ship.lastX = ship.x; } for (var j = cannonballs.length - 1; j >= 0; j--) { var cannonball = cannonballs[j]; if (cannonball.lastX === undefined) cannonball.lastX = cannonball.x; if (cannonball.lastY === undefined) cannonball.lastY = cannonball.y; if (cannonball.lastX >= 0 && cannonball.x < 0 || cannonball.lastX <= 2048 && cannonball.x > 2048 || cannonball.lastY >= 0 && cannonball.y < 0 || cannonball.lastY <= 2732 && cannonball.y > 2732) { cannonball.destroy(); cannonballs.splice(j, 1); continue; } // Auto-selection is now used - no upgrade button collision detection needed for (var k = ships.length - 1; k >= 0; k--) { var ship = ships[k]; // Ensure ship is valid and has proper collision detection, and not dying if (ship && ship.graphics && ship.graphics.visible && !ship.isDying && cannonball.intersects(ship)) { ship.health -= cannonball.damage; ship.updateHealthBar(); LK.effects.flashObject(ship, 0xff0000, 200); LK.getSound('hit').play(); // Check if cannonball can bounce to another enemy var bounced = false; if (cannonball.canBounce()) { var nearestEnemy = null; var nearestDistance = Infinity; // Find nearest enemy ship for bouncing (excluding current target) for (var targetIndex = 0; targetIndex < ships.length; targetIndex++) { var targetShip = ships[targetIndex]; if (targetShip && targetShip !== ship && targetShip.graphics && targetShip.graphics.visible && !targetShip.isDying) { var dx = targetShip.x - cannonball.x; var dy = targetShip.y - cannonball.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance && distance < 400) { // Max bounce range nearestDistance = distance; nearestEnemy = targetShip; } } } // Bounce to nearest enemy if found if (nearestEnemy) { bounced = cannonball.bounceTowardsTarget(nearestEnemy.x, nearestEnemy.y); } } if (!bounced) { cannonball.destroy(); cannonballs.splice(j, 1); } if (ship.health <= 0) { enemiesKilledThisWave++; // Track kills for wave completion ship.isDying = true; // Mark as dying to prevent further damage LK.setScore(LK.getScore() + ship.points); scoreTxt.setText('Score: ' + LK.getScore()); LK.getSound('shipDestroyed').play(); // Add small jump death animation var originalY = ship.y; var deadShip = ship; // Store reference to avoid closure issues tween(ship, { y: originalY - 30, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(deadShip, { y: originalY + 10, scaleX: 0.8, scaleY: 0.8 }, { duration: 150, easing: tween.bounceOut, onFinish: function onFinish() { // Random chance to spawn different power-ups (45% total chance) var spawnChance = Math.random(); if (spawnChance < 0.15) { // 15% chance for weapon power-ups var powerUp = new PowerUp(); var powerTypes = ['tripleShot', 'rapidFire']; var randomPowerType = powerTypes[Math.floor(Math.random() * powerTypes.length)]; powerUp.init(randomPowerType); powerUp.x = deadShip.x; powerUp.y = deadShip.y; powerUps.push(powerUp); game.addChild(powerUp); // Add brilliant glowing animation to power-up powerUp.graphics.tint = 0xffffff; tween(powerUp.graphics, { tint: 0xffff00, scaleX: 1.3, scaleY: 1.3 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(powerUp.graphics, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { // Continue pulsing glow function continuePulse() { if (powerUp.parent) { tween(powerUp.graphics, { tint: 0xffff00, scaleX: 1.2, scaleY: 1.2 }, { duration: 600, easing: tween.easeInOut, onFinish: function onFinish() { if (powerUp.parent) { tween(powerUp.graphics, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 600, easing: tween.easeInOut, onFinish: continuePulse }); } } }); } } continuePulse(); } }); } }); } else if (spawnChance < 0.25) { // 10% chance for health kit var healthKit = new HealthKit(); healthKit.x = deadShip.x; healthKit.y = deadShip.y; healthKits.push(healthKit); game.addChild(healthKit); // Add brilliant glowing animation to health kit healthKit.tint = 0xffffff; tween(healthKit, { tint: 0x00ff00, scaleX: 1.3, scaleY: 1.3 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(healthKit, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { // Continue pulsing glow function continuePulse() { if (healthKit.parent) { tween(healthKit, { tint: 0x00ff00, scaleX: 1.15, scaleY: 1.15 }, { duration: 800, easing: tween.easeInOut, onFinish: function onFinish() { if (healthKit.parent) { tween(healthKit, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 800, easing: tween.easeInOut, onFinish: continuePulse }); } } }); } } continuePulse(); } }); } }); } else if (spawnChance < 0.35) { // 15% chance for guided shot var guidedShotPowerUp = new GuidedShotPowerUp(); guidedShotPowerUp.x = deadShip.x; guidedShotPowerUp.y = deadShip.y; guidedShotPowerUps.push(guidedShotPowerUp); game.addChild(guidedShotPowerUp); // Add brilliant glowing animation to guided shot power-up guidedShotPowerUp.tint = 0xffffff; tween(guidedShotPowerUp, { tint: 0xff6600, scaleX: 1.3, scaleY: 1.3 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(guidedShotPowerUp, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { // Continue pulsing glow function continuePulse() { if (guidedShotPowerUp.parent) { tween(guidedShotPowerUp, { tint: 0xff6600, scaleX: 1.2, scaleY: 1.2 }, { duration: 700, easing: tween.easeInOut, onFinish: function onFinish() { if (guidedShotPowerUp.parent) { tween(guidedShotPowerUp, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 700, easing: tween.easeInOut, onFinish: continuePulse }); } } }); } } continuePulse(); } }); } }); } else if (spawnChance < 0.40) { // 5% chance for octopus power - same as other special powers var octopusPowerUp = new OctopusPowerUp(); octopusPowerUp.x = deadShip.x; octopusPowerUp.y = deadShip.y; octopusPowerUps.push(octopusPowerUp); game.addChild(octopusPowerUp); // Add brilliant glowing animation octopusPowerUp.tint = 0xffffff; tween(octopusPowerUp, { tint: 0x800080, scaleX: 1.3, scaleY: 1.3 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(octopusPowerUp, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { function continuePulse() { if (octopusPowerUp.parent) { tween(octopusPowerUp, { tint: 0x800080, scaleX: 1.2, scaleY: 1.2 }, { duration: 700, easing: tween.easeInOut, onFinish: function onFinish() { if (octopusPowerUp.parent) { tween(octopusPowerUp, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 700, easing: tween.easeInOut, onFinish: continuePulse }); } } }); } } continuePulse(); } }); } }); } else if (spawnChance < 0.45) { // 5% chance for shield var shieldPowerUp = new ShieldPowerUp(); shieldPowerUp.x = deadShip.x; shieldPowerUp.y = deadShip.y; shieldPowerUps.push(shieldPowerUp); game.addChild(shieldPowerUp); // Add brilliant glowing animation to shield power-up shieldPowerUp.tint = 0xffffff; tween(shieldPowerUp, { tint: 0x0099ff, scaleX: 1.3, scaleY: 1.3 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { tween(shieldPowerUp, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 400, easing: tween.easeInOut, onFinish: function onFinish() { // Continue pulsing glow function continuePulse() { if (shieldPowerUp.parent) { tween(shieldPowerUp, { tint: 0x0099ff, scaleX: 1.2, scaleY: 1.2 }, { duration: 700, easing: tween.easeInOut, onFinish: function onFinish() { if (shieldPowerUp.parent) { tween(shieldPowerUp, { tint: 0xffffff, scaleX: 1.0, scaleY: 1.0 }, { duration: 700, easing: tween.easeInOut, onFinish: continuePulse }); } } }); } } continuePulse(); } }); } }); } if (deadShip.parent) { deadShip.destroy(); } // Remove from ships array by finding the ship for (var removeIndex = ships.length - 1; removeIndex >= 0; removeIndex--) { if (ships[removeIndex] === deadShip) { ships.splice(removeIndex, 1); break; } } } }); } }); } break; } } if (cannonballs[j]) { cannonball.lastX = cannonball.x; cannonball.lastY = cannonball.y; } } // Update power-ups for (var p = powerUps.length - 1; p >= 0; p--) { var powerUp = powerUps[p]; if (powerUp.lastX === undefined) powerUp.lastX = powerUp.x; // Remove power-up if it goes off screen if (powerUp.lastX <= 2048 && powerUp.x > 2048) { powerUp.destroy(); powerUps.splice(p, 1); continue; } // Check collision with cannonballs for collection for (var cb = cannonballs.length - 1; cb >= 0; cb--) { var cannonball = cannonballs[cb]; if (cannonball.intersects(powerUp)) { // Collect power-up LK.getSound('powerUpCollect').play(); LK.effects.flashScreen(0x00ff00, 300); // Clear any existing power-up to prevent accumulation tripleShot = false; rapidFire = false; activePowerUpTimer = 0; // Reset timer to prevent accumulation // Activate new power-up if (powerUp.powerType === 'tripleShot') { tripleShot = true; activePowerUpType = 'Triple Shot'; activePowerUpTimer = powerUpDuration; } else if (powerUp.powerType === 'rapidFire') { rapidFire = true; activePowerUpType = 'Rapid Fire'; activePowerUpTimer = powerUpDuration; } powerUp.destroy(); powerUps.splice(p, 1); break; } } if (powerUps[p]) { powerUp.lastX = powerUp.x; } } // Update enemy bullets for (var eb = enemyBullets.length - 1; eb >= 0; eb--) { var enemyBullet = enemyBullets[eb]; if (enemyBullet.lastY === undefined) enemyBullet.lastY = enemyBullet.y; // Remove if off screen if (enemyBullet.lastY <= 2732 && enemyBullet.y > 2732) { enemyBullet.destroy(); enemyBullets.splice(eb, 1); continue; } // Check collision with cannon first if (enemyBullet.intersects(cannon)) { cannon.takeDamage(enemyBullet.damage); // Check if enemy bullet can bounce to allies var bounced = false; if (enemyBullet.canBounce()) { var nearestAlly = null; var nearestDistance = Infinity; // Find nearest ally for bouncing for (var allyIndex = 0; allyIndex < permanentAllies.length; allyIndex++) { var ally = permanentAllies[allyIndex]; if (ally && ally.parent && ally.health > 0) { var dx = ally.x - enemyBullet.x; var dy = ally.y - enemyBullet.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance && distance < 400) { // Max bounce range nearestDistance = distance; nearestAlly = ally; } } } // Also check friendly octopuses for (var foIndex = 0; foIndex < friendlyOctopuses.length; foIndex++) { var friendlyOct = friendlyOctopuses[foIndex]; if (friendlyOct && friendlyOct.parent && friendlyOct.health > 0) { var dx = friendlyOct.x - enemyBullet.x; var dy = friendlyOct.y - enemyBullet.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance && distance < 400) { // Max bounce range nearestDistance = distance; nearestAlly = friendlyOct; } } } // Bounce to nearest ally if found if (nearestAlly) { bounced = enemyBullet.bounceTowardsTarget(nearestAlly.x, nearestAlly.y); } } if (!bounced) { enemyBullet.destroy(); enemyBullets.splice(eb, 1); } // Update health UI var healthPercent = cannon.health / cannon.maxHealth; cannonHealthBar.scaleX = healthPercent; if (healthPercent > 0.6) { cannonHealthBar.tint = 0x00FF00; } else if (healthPercent > 0.3) { cannonHealthBar.tint = 0xFFFF00; } else { cannonHealthBar.tint = 0xFF0000; } healthText.setText('Health: ' + cannon.health + '/' + cannon.maxHealth); // Check game over if (cannon.health <= 0) { // Show final score and wave reached var finalScore = LK.getScore(); var finalWave = currentWave; LK.showGameOver('Puntaje: ' + finalScore + '\nOleada alcanzada: ' + finalWave); return; } continue; } // Check collision with permanent allies var hitAlly = false; for (var pa = 0; pa < permanentAllies.length; pa++) { var permanentAlly = permanentAllies[pa]; if (permanentAlly && permanentAlly.parent && permanentAlly.health > 0 && enemyBullet.intersects(permanentAlly)) { permanentAlly.takeDamage(enemyBullet.damage); LK.getSound('hit').play(); // Check if enemy bullet can bounce to enemy ships var bounced = false; if (enemyBullet.canBounce()) { var nearestEnemy = null; var nearestDistance = Infinity; // Find nearest enemy ship for bouncing for (var shipIndex = 0; shipIndex < ships.length; shipIndex++) { var targetShip = ships[shipIndex]; if (targetShip && targetShip.graphics && targetShip.graphics.visible && !targetShip.isDying) { var dx = targetShip.x - enemyBullet.x; var dy = targetShip.y - enemyBullet.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance && distance < 400) { // Max bounce range nearestDistance = distance; nearestEnemy = targetShip; } } } // Bounce to nearest enemy if found if (nearestEnemy) { bounced = enemyBullet.bounceTowardsTarget(nearestEnemy.x, nearestEnemy.y); } } if (!bounced) { enemyBullet.destroy(); enemyBullets.splice(eb, 1); } hitAlly = true; break; } } if (hitAlly) continue; if (enemyBullets[eb]) { enemyBullet.lastY = enemyBullet.y; } } // Update health kits for (var hk = healthKits.length - 1; hk >= 0; hk--) { var healthKit = healthKits[hk]; if (healthKit.lastX === undefined) healthKit.lastX = healthKit.x; // Remove if off screen if (healthKit.lastX <= 2048 && healthKit.x > 2048) { healthKit.destroy(); healthKits.splice(hk, 1); continue; } // Check collection by cannonball for (var cb = cannonballs.length - 1; cb >= 0; cb--) { var cannonball = cannonballs[cb]; if (cannonball.intersects(healthKit)) { cannon.heal(healthKit.healAmount); LK.getSound('powerUpCollect').play(); LK.effects.flashScreen(0x00ff00, 200); // Update health UI var healthPercent = cannon.health / cannon.maxHealth; cannonHealthBar.scaleX = healthPercent; if (healthPercent > 0.6) { cannonHealthBar.tint = 0x00FF00; } else if (healthPercent > 0.3) { cannonHealthBar.tint = 0xFFFF00; } else { cannonHealthBar.tint = 0xFF0000; } healthText.setText('Health: ' + cannon.health + '/' + cannon.maxHealth); healthKit.destroy(); healthKits.splice(hk, 1); break; } } // Check collection by guided projectiles for (var gp = guidedProjectiles.length - 1; gp >= 0; gp--) { var guidedProjectile = guidedProjectiles[gp]; if (guidedProjectile.intersects(healthKit)) { cannon.heal(healthKit.healAmount); LK.getSound('powerUpCollect').play(); LK.effects.flashScreen(0x00ff00, 200); // Update health UI var healthPercent = cannon.health / cannon.maxHealth; cannonHealthBar.scaleX = healthPercent; if (healthPercent > 0.6) { cannonHealthBar.tint = 0x00FF00; } else if (healthPercent > 0.3) { cannonHealthBar.tint = 0xFFFF00; } else { cannonHealthBar.tint = 0xFF0000; } healthText.setText('Health: ' + cannon.health + '/' + cannon.maxHealth); healthKit.destroy(); healthKits.splice(hk, 1); break; } } if (healthKits[hk]) { healthKit.lastX = healthKit.x; } } // Update shield power-ups for (var sp = shieldPowerUps.length - 1; sp >= 0; sp--) { var shieldPowerUp = shieldPowerUps[sp]; if (shieldPowerUp.lastX === undefined) shieldPowerUp.lastX = shieldPowerUp.x; // Remove if off screen if (shieldPowerUp.lastX <= 2048 && shieldPowerUp.x > 2048) { shieldPowerUp.destroy(); shieldPowerUps.splice(sp, 1); continue; } // Check collection by cannonball for (var cb = cannonballs.length - 1; cb >= 0; cb--) { var cannonball = cannonballs[cb]; if (cannonball.intersects(shieldPowerUp)) { cannon.activateShield(); // Share shield power with all allies for (var allyIndex = 0; allyIndex < permanentAllies.length; allyIndex++) { var ally = permanentAllies[allyIndex]; if (ally && ally.parent && ally.health > 0) { ally.activateShield(); } } for (var foIndex = 0; foIndex < friendlyOctopuses.length; foIndex++) { var friendlyOct = friendlyOctopuses[foIndex]; if (friendlyOct && friendlyOct.parent && friendlyOct.health > 0) { friendlyOct.activateShield(); } } LK.getSound('powerUpCollect').play(); LK.effects.flashScreen(0x0099ff, 300); // Add brilliant glowing animation to cannon when shield is collected tween(cannon.graphics, { tint: 0x88ccff, scaleX: 1.2, scaleY: 1.2 }, { duration: 300, easing: tween.easeInOut, onFinish: function onFinish() { tween(cannon.graphics, { scaleX: 1.0, scaleY: 1.0 }, { duration: 300, easing: tween.easeInOut }); } }); shieldPowerUp.destroy(); shieldPowerUps.splice(sp, 1); break; } } if (shieldPowerUps[sp]) { shieldPowerUp.lastX = shieldPowerUp.x; } } // Update guided shot power-ups for (var gsp = guidedShotPowerUps.length - 1; gsp >= 0; gsp--) { var guidedShotPowerUp = guidedShotPowerUps[gsp]; if (guidedShotPowerUp.lastX === undefined) guidedShotPowerUp.lastX = guidedShotPowerUp.x; // Remove if off screen if (guidedShotPowerUp.lastX <= 2048 && guidedShotPowerUp.x > 2048) { guidedShotPowerUp.destroy(); guidedShotPowerUps.splice(gsp, 1); continue; } // Check collection by cannonball for (var cb = cannonballs.length - 1; cb >= 0; cb--) { var cannonball = cannonballs[cb]; if (cannonball.intersects(guidedShotPowerUp)) { // Clear existing power-ups tripleShot = false; rapidFire = false; guidedShot = true; activePowerUpType = 'Guided Shot'; activePowerUpTimer = guidedShotDuration; LK.getSound('powerUpCollect').play(); LK.effects.flashScreen(0xff6600, 300); guidedShotPowerUp.destroy(); guidedShotPowerUps.splice(gsp, 1); break; } } if (guidedShotPowerUps[gsp]) { guidedShotPowerUp.lastX = guidedShotPowerUp.x; } } // Update octopus power-ups for (var op = octopusPowerUps.length - 1; op >= 0; op--) { var octopusPowerUp = octopusPowerUps[op]; if (octopusPowerUp.lastX === undefined) octopusPowerUp.lastX = octopusPowerUp.x; // Remove if off screen if (octopusPowerUp.lastX <= 2048 && octopusPowerUp.x > 2048) { octopusPowerUp.destroy(); octopusPowerUps.splice(op, 1); continue; } // Check collection by cannonball for (var cb = cannonballs.length - 1; cb >= 0; cb--) { var cannonball = cannonballs[cb]; if (cannonball.intersects(octopusPowerUp)) { // Determine number of octopus arms to spawn based on active powers var armsToSpawn = 1; if (tripleShot) { armsToSpawn = 3; // Triple shot multiplies octopus arms } // Spawn multiple octopus arms if triple shot is active for (var armIndex = 0; armIndex < armsToSpawn; armIndex++) { var octopusArm = new OctopusArm(); octopusArm.init(); // Apply rapid fire speed boost if active if (rapidFire) { octopusArm.speed = octopusArm.speed * 1.5; // 50% faster movement } octopuses.push(octopusArm); game.addChild(octopusArm); } // Destroy the cannonball that collected the power-up cannonball.destroy(); cannonballs.splice(cb, 1); LK.getSound('powerUpCollect').play(); LK.effects.flashScreen(0x800080, 300); // Show power activation message with count var activeOctopusCount = octopuses.length; var armText = armsToSpawn > 1 ? '¡' + armsToSpawn + ' Brazos de Pulpo Invocados!' : '¡Brazo de Pulpo Invocado!'; var powerMessage = new Text2(armText + ' (' + activeOctopusCount + ' activos)', { size: 80, fill: 0x800080 }); powerMessage.anchor.set(0.5, 0.5); LK.gui.center.addChild(powerMessage); // Animate and remove message tween(powerMessage, { y: -100, alpha: 0 }, { duration: 2000, easing: tween.easeOut, onFinish: function onFinish() { if (powerMessage.parent) { powerMessage.destroy(); } } }); octopusPowerUp.destroy(); octopusPowerUps.splice(op, 1); break; } } if (octopusPowerUps[op]) { octopusPowerUp.lastX = octopusPowerUp.x; } } // Update friendly octopuses for (var fo = friendlyOctopuses.length - 1; fo >= 0; fo--) { var friendlyOctopus = friendlyOctopuses[fo]; // Remove if friendly octopus is destroyed or dead if (!friendlyOctopus.parent || friendlyOctopus.health <= 0) { if (friendlyOctopus.parent) { // Death animation tween(friendlyOctopus, { alpha: 0, scaleX: 0.5, scaleY: 0.5, y: friendlyOctopus.y + 50 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { if (friendlyOctopus.parent) { friendlyOctopus.destroy(); } } }); } friendlyOctopuses.splice(fo, 1); } } // Update permanent allies for (var pa = permanentAllies.length - 1; pa >= 0; pa--) { var permanentAlly = permanentAllies[pa]; // Remove if permanent ally is destroyed or dead if (!permanentAlly.parent || permanentAlly.health <= 0) { if (permanentAlly.parent) { // Death animation tween(permanentAlly, { alpha: 0, scaleX: 0.5, scaleY: 0.5, y: permanentAlly.y + 50 }, { duration: 1000, easing: tween.easeIn, onFinish: function onFinish() { if (permanentAlly.parent) { permanentAlly.destroy(); } } }); } permanentAllies.splice(pa, 1); } } // Check enemy bullet collision with friendly octopuses and permanent allies for (var eb = enemyBullets.length - 1; eb >= 0; eb--) { var enemyBullet = enemyBullets[eb]; var bulletHit = false; // Check collision with friendly octopuses for (var fo = 0; fo < friendlyOctopuses.length; fo++) { var friendlyOctopus = friendlyOctopuses[fo]; if (friendlyOctopus && friendlyOctopus.parent && enemyBullet.intersects(friendlyOctopus)) { friendlyOctopus.takeDamage(enemyBullet.damage); LK.getSound('hit').play(); // Check if enemy bullet can bounce to enemy ships var bounced = false; if (enemyBullet.canBounce()) { var nearestEnemy = null; var nearestDistance = Infinity; // Find nearest enemy ship for bouncing for (var shipIndex = 0; shipIndex < ships.length; shipIndex++) { var targetShip = ships[shipIndex]; if (targetShip && targetShip.graphics && targetShip.graphics.visible && !targetShip.isDying) { var dx = targetShip.x - enemyBullet.x; var dy = targetShip.y - enemyBullet.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < nearestDistance && distance < 400) { // Max bounce range nearestDistance = distance; nearestEnemy = targetShip; } } } // Bounce to nearest enemy if found if (nearestEnemy) { bounced = enemyBullet.bounceTowardsTarget(nearestEnemy.x, nearestEnemy.y); } } if (!bounced) { enemyBullet.destroy(); enemyBullets.splice(eb, 1); } bulletHit = true; break; } } // Check collision with permanent allies if bullet hasn't hit yet if (!bulletHit) { for (var pa = 0; pa < permanentAllies.length; pa++) { var permanentAlly = permanentAllies[pa]; if (permanentAlly && permanentAlly.parent && enemyBullet.intersects(permanentAlly)) { permanentAlly.takeDamage(enemyBullet.damage); enemyBullet.destroy(); enemyBullets.splice(eb, 1); LK.getSound('hit').play(); break; } } } } // Update octopuses for (var oc = octopuses.length - 1; oc >= 0; oc--) { var octopus = octopuses[oc]; // Remove if octopus is destroyed or off screen if (!octopus.parent || octopus.x > 2300 || octopus.y > 2900) { if (octopus.parent) { octopus.destroy(); } octopuses.splice(oc, 1); } } // Update guided projectiles for (var gp = guidedProjectiles.length - 1; gp >= 0; gp--) { var guidedProjectile = guidedProjectiles[gp]; // Check if projectile has exceeded its lifetime (4 seconds) if (LK.ticks - guidedProjectile.creationTime >= guidedProjectile.lifetime) { guidedProjectile.destroy(); guidedProjectiles.splice(gp, 1); continue; } // Check collision with enemy ships var hitShip = false; for (var k = ships.length - 1; k >= 0; k--) { var ship = ships[k]; // Ensure ship is valid and has proper collision detection, and not dying if (ship && ship.graphics && ship.graphics.visible && !ship.isDying && guidedProjectile.intersects(ship)) { ship.health -= guidedProjectile.damage; ship.updateHealthBar(); LK.effects.flashObject(ship, 0xff0000, 200); LK.getSound('hit').play(); guidedProjectile.destroy(); guidedProjectiles.splice(gp, 1); hitShip = true; if (ship.health <= 0) { enemiesKilledThisWave++; // Track kills for wave completion ship.isDying = true; // Mark as dying to prevent further damage LK.setScore(LK.getScore() + ship.points); scoreTxt.setText('Score: ' + LK.getScore()); LK.getSound('shipDestroyed').play(); // Add small jump death animation var originalY = ship.y; var deadShip = ship; // Store reference to avoid closure issues tween(ship, { y: originalY - 30, scaleX: 1.2, scaleY: 1.2 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { tween(deadShip, { y: originalY + 10, scaleX: 0.8, scaleY: 0.8 }, { duration: 150, easing: tween.bounceOut, onFinish: function onFinish() { if (deadShip.parent) { deadShip.destroy(); } // Remove from ships array by finding the ship for (var removeIndex = ships.length - 1; removeIndex >= 0; removeIndex--) { if (ships[removeIndex] === deadShip) { ships.splice(removeIndex, 1); break; } } } }); } }); } break; } } if (hitShip) continue; // Remove if too far off screen if (guidedProjectile.x < -200 || guidedProjectile.x > 2248 || guidedProjectile.y < -200 || guidedProjectile.y > 2932) { guidedProjectile.destroy(); guidedProjectiles.splice(gp, 1); } } // Check collision between cannonballs and shield effect if (cannon.isShielded && shieldEffect.visible) { for (var cb = cannonballs.length - 1; cb >= 0; cb--) { var cannonball = cannonballs[cb]; if (cannonball.intersects(shieldEffect)) { // Simply destroy the cannonball when it hits the shield - no infinite shield activation cannonball.destroy(); cannonballs.splice(cb, 1); // Visual feedback only LK.effects.flashObject(shieldEffect, 0x88ccff, 200); break; } } } // Update cannon shield if (cannon.isShielded && cannon.shieldTimer > 0) { cannon.shieldTimer--; // Show shield effect when active if (!shieldEffect.visible) { shieldEffect.visible = true; shieldEffect.x = cannon.x; shieldEffect.y = cannon.y; } if (cannon.shieldTimer <= 0) { cannon.isShielded = false; shieldEffect.visible = false; tween(cannon.graphics, { tint: 0xffffff }, { duration: 500 }); } } else { shieldEffect.visible = false; } // Update power-up timer if (activePowerUpTimer > 0) { activePowerUpTimer--; // Update timer display var timeLeft = Math.ceil(activePowerUpTimer / 60); var timerText = activePowerUpType + ': ' + timeLeft + 's'; if (cannon.isShielded) { var shieldTimeLeft = Math.ceil(cannon.shieldTimer / 60); timerText += ' | Shield: ' + shieldTimeLeft + 's'; } powerUpTimerTxt.setText(timerText); // Check if power-up expired if (activePowerUpTimer <= 0) { tripleShot = false; rapidFire = false; guidedShot = false; activePowerUpType = ''; if (cannon.isShielded) { var shieldTimeLeft = Math.ceil(cannon.shieldTimer / 60); powerUpTimerTxt.setText('Shield: ' + shieldTimeLeft + 's'); } else { powerUpTimerTxt.setText(''); } } } else { // No active weapon power-up, but maybe shield if (cannon.isShielded) { var shieldTimeLeft = Math.ceil(cannon.shieldTimer / 60); powerUpTimerTxt.setText('Shield: ' + shieldTimeLeft + 's'); } else { powerUpTimerTxt.setText(''); } } // Check for wave completion at end of update loop checkWaveCompletion(); }; // Duplicate activateShield methods removed - method already exists in PermanentAlly class
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Boss = Container.expand(function () {
var self = Container.call(this);
self.shipType = 'boss';
self.speed = 0.5; // Very slow base speed
self.points = 500; // High points for boss
self.graphics = null;
self.healthBar = null;
self.healthBarBg = null;
self.isDying = false;
self.init = function (wave) {
self.shipType = 'boss';
self.graphics = self.attachAsset('bossShip', {
anchorX: 0.5,
anchorY: 0.5
});
// Make boss larger and more intimidating
self.graphics.scaleX = 1.5;
self.graphics.scaleY = 1.5;
// Slow speed that slightly increases with wave
self.speed = 0.5 + (wave - 4) * 0.1;
self.points = 500 + (wave - 4) * 100; // Scaling points
self.health = 40;
self.maxHealth = 40;
// Create larger health bar for boss
var healthBarWidth = 300;
self.healthBarBg = LK.getAsset('healthBarBg', {
width: healthBarWidth,
height: 20,
color: 0x444444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -self.graphics.height / 2 - 40;
self.addChild(self.healthBarBg);
self.healthBar = LK.getAsset('healthBar', {
width: healthBarWidth - 4,
height: 16,
color: 0xFF0000,
// Red health bar for boss
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBar.y = -self.graphics.height / 2 - 40;
self.addChild(self.healthBar);
// Boss shoots at moderate frequency
self.lastShotTime = 0;
self.shotDelay = 90; // 1.5 seconds (much slower)
};
self.updateHealthBar = function () {
if (self.healthBar && self.maxHealth > 1) {
var healthPercent = self.health / self.maxHealth;
self.healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0xFF4444; // Dark red
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFF0000; // Red
} else {
self.healthBar.tint = 0x880000; // Dark red
}
}
};
self.update = function () {
self.x += self.speed;
if (self.graphics) {
self.graphics.visible = true;
}
};
self.canShoot = function () {
return LK.ticks - self.lastShotTime >= self.shotDelay;
};
self.shootAtCannon = function (cannonX, cannonY) {
if (!self.canShoot()) return null;
self.lastShotTime = LK.ticks;
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = cannonX - self.x;
var dy = cannonY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
bullet.velocityX = dx / distance * bullet.speed;
bullet.velocityY = dy / distance * bullet.speed;
bullet.damage = 2; // Boss bullets do more damage
return bullet;
};
return self;
});
var Cannon = Container.expand(function () {
var self = Container.call(this);
self.graphics = self.attachAsset('cannon', {
anchorX: 0.5,
anchorY: 0.5
});
self.targetX = 0;
self.targetY = 0;
self.lastFireTime = 0;
self.fireDelay = 300;
self.health = 10;
self.maxHealth = 10;
self.isShielded = false;
self.shieldTimer = 0;
self.aimAt = function (x, y) {
self.targetX = x;
self.targetY = y;
var dx = x - self.x;
var dy = y - self.y;
var angle = Math.atan2(dy, dx);
self.graphics.rotation = angle;
};
self.canFire = function () {
var currentFireDelay = rapidFire ? self.fireDelay / 3 : self.fireDelay;
return LK.ticks * 16.67 - self.lastFireTime >= currentFireDelay;
};
self.fire = function () {
if (!self.canFire()) return null;
self.lastFireTime = LK.ticks * 16.67;
var cannonballs = [];
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var baseAngle = Math.atan2(dy, dx);
if (guidedShot) {
// Determine number of guided projectiles to fire based on active powers
var projectilesToFire = 1;
if (tripleShot) {
projectilesToFire = 3; // Triple shot multiplies guided projectiles
}
// Fire multiple guided projectiles if triple shot is active
for (var projIndex = 0; projIndex < projectilesToFire; projIndex++) {
var guidedProjectile = new GuidedProjectile();
guidedProjectile.x = self.x - 45 + (projIndex - Math.floor(projectilesToFire / 2)) * 30; // Spread them out horizontally
guidedProjectile.y = self.y - 40;
guidedProjectile.targetX = self.targetX;
guidedProjectile.targetY = self.targetY;
guidedProjectile.lastTargetUpdateTime = LK.ticks;
guidedProjectile.creationTime = LK.ticks; // Set creation time
// Apply rapid fire speed boost if active
if (rapidFire) {
guidedProjectile.speed = guidedProjectile.speed * 1.8; // 80% faster when rapid fire is active
}
guidedProjectiles.push(guidedProjectile);
game.addChild(guidedProjectile);
}
} else if (tripleShot) {
// Fire three cannonballs with slight angle differences
for (var i = 0; i < 3; i++) {
var cannonball = new Cannonball();
cannonball.x = self.x - 45;
cannonball.y = self.y - 40;
var angleOffset = (i - 1) * 0.2; // -0.2, 0, 0.2 radians
var adjustedAngle = baseAngle + angleOffset;
cannonball.velocityX = Math.cos(adjustedAngle) * cannonball.speed;
cannonball.velocityY = Math.sin(adjustedAngle) * cannonball.speed;
cannonballs.push(cannonball);
}
} else {
// Fire single cannonball
var cannonball = new Cannonball();
cannonball.x = self.x - 45;
cannonball.y = self.y - 40;
cannonball.velocityX = dx / distance * cannonball.speed;
cannonball.velocityY = dy / distance * cannonball.speed;
cannonballs.push(cannonball);
}
LK.getSound('shoot').play();
return cannonballs;
};
self.takeDamage = function (damage) {
if (self.isShielded) {
damage = Math.floor(damage / 2); // Shield reduces damage by half
}
self.health -= damage;
if (self.health < 0) self.health = 0;
// Flash red when taking damage
tween(self.graphics, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
tween(self.graphics, {
tint: 0xffffff
}, {
duration: 100
});
}
});
};
self.heal = function (amount) {
self.health += amount;
if (self.health > self.maxHealth) self.health = self.maxHealth;
// Flash green when healing
tween(self.graphics, {
tint: 0x00ff00
}, {
duration: 200,
onFinish: function onFinish() {
tween(self.graphics, {
tint: 0xffffff
}, {
duration: 200
});
}
});
};
self.activateShield = function () {
self.isShielded = true;
self.shieldTimer = 900; // 15 seconds at 60fps
// Use self.graphics directly if available, otherwise find it in children
var cannonGraphics = self.graphics;
if (!cannonGraphics && self.children && self.children.length > 0) {
// Find the first child that has a tint property (likely the graphics asset)
for (var i = 0; i < self.children.length; i++) {
var child = self.children[i];
if (child && typeof child.tint !== 'undefined') {
cannonGraphics = child;
break;
}
}
}
// Only set tint if we found valid graphics with tint property
if (cannonGraphics && typeof cannonGraphics.tint !== 'undefined') {
cannonGraphics.tint = 0x88ccff; // Blue tint for shield
}
};
return self;
});
var Cannonball = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('cannonball', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 16;
self.damage = 1; // Base damage, will be multiplied by cannonDamage when hitting
self.bounceCount = 0; // Track number of bounces
self.maxBounces = 2; // Maximum number of bounces before disappearing
self.canBounce = function () {
return self.bounceCount < self.maxBounces;
};
self.bounceTowardsTarget = function (targetX, targetY) {
if (!self.canBounce()) return false;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
self.bounceCount++;
// Visual effect for bounce
tween(graphics, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xffff00
}, {
duration: 100,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xffffff
}, {
duration: 100
});
}
});
return true;
}
return false;
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var EnemyBullet = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('enemyBullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.velocityX = 0;
self.velocityY = 0;
self.speed = 8;
self.damage = 1;
self.bounceCount = 0; // Track number of bounces
self.maxBounces = 2; // Maximum number of bounces before disappearing
self.canBounce = function () {
return self.bounceCount < self.maxBounces;
};
self.bounceTowardsTarget = function (targetX, targetY) {
if (!self.canBounce()) return false;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.velocityX = dx / distance * self.speed;
self.velocityY = dy / distance * self.speed;
self.bounceCount++;
// Visual effect for bounce
tween(graphics, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xff6600
}, {
duration: 100,
onFinish: function onFinish() {
tween(graphics, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xffffff
}, {
duration: 100
});
}
});
return true;
}
return false;
};
self.update = function () {
self.x += self.velocityX;
self.y += self.velocityY;
};
return self;
});
var FriendlyOctopus = Container.expand(function () {
var self = Container.call(this);
self.health = 2;
self.maxHealth = 2;
self.speed = 2;
self.lastShotTime = 0;
self.shotDelay = 120; // 2 seconds
self.moveDirection = Math.random() * Math.PI * 2; // Random initial direction
self.changeDirectionTimer = 0;
self.targetShip = null;
self.isShielded = false;
self.shieldTimer = 0;
self.targetPriority = 'enemy';
self.movingToPowerUp = false;
var graphics = self.attachAsset('friendlyOctopus', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x00ff88; // Green tint to distinguish from enemy octopus
graphics.scaleX = 0.6;
graphics.scaleY = 0.6;
// Create health bar
self.healthBarBg = LK.getAsset('healthBarBg', {
width: 120,
height: 12,
color: 0x444444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -graphics.height / 2 - 20;
self.addChild(self.healthBarBg);
self.healthBar = LK.getAsset('healthBar', {
width: 116,
height: 8,
color: 0x00FF00,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBar.y = -graphics.height / 2 - 20;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
if (self.healthBar && self.maxHealth > 0) {
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
self.findNearestTarget = function () {
var nearestTarget = null;
var nearestDistance = Infinity;
var targetPriority = 'enemy';
// First priority: Find nearest enemy ship
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) {
var dx = ship.x - self.x;
var dy = ship.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = ship;
targetPriority = 'enemy';
}
}
}
// Second priority: Find power-ups if no nearby enemies
if (!nearestTarget || nearestDistance > 600) {
var powerUpArrays = [powerUps, healthKits, shieldPowerUps, guidedShotPowerUps, octopusPowerUps];
for (var arrayIndex = 0; arrayIndex < powerUpArrays.length; arrayIndex++) {
var powerUpArray = powerUpArrays[arrayIndex];
for (var p = 0; p < powerUpArray.length; p++) {
var powerUp = powerUpArray[p];
if (powerUp && powerUp.parent) {
var dx = powerUp.x - self.x;
var dy = powerUp.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = powerUp;
targetPriority = 'powerup';
}
}
}
}
}
self.targetPriority = targetPriority;
return nearestTarget;
};
self.findNearestEnemyShip = function () {
var nearestShip = null;
var nearestDistance = Infinity;
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) {
var dx = ship.x - self.x;
var dy = ship.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestShip = ship;
}
}
}
return nearestShip;
};
self.canShoot = function () {
var currentShotDelay = rapidFire ? self.shotDelay / 3 : self.shotDelay;
return LK.ticks - self.lastShotTime >= currentShotDelay;
};
self.shootAtTarget = function (targetX, targetY) {
if (!self.canShoot()) return null;
self.lastShotTime = LK.ticks;
var bullets = [];
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var baseAngle = Math.atan2(dy, dx);
if (guidedShot) {
// Fire guided projectiles like cannon when guided shot is active
var projectilesToFire = 1;
if (tripleShot) {
projectilesToFire = 3; // Triple shot multiplies guided projectiles
}
// Fire multiple guided projectiles if triple shot is active
for (var projIndex = 0; projIndex < projectilesToFire; projIndex++) {
var guidedProjectile = new GuidedProjectile();
guidedProjectile.x = self.x + (projIndex - Math.floor(projectilesToFire / 2)) * 30; // Spread them out horizontally
guidedProjectile.y = self.y;
guidedProjectile.targetX = targetX;
guidedProjectile.targetY = targetY;
guidedProjectile.lastTargetUpdateTime = LK.ticks;
guidedProjectile.creationTime = LK.ticks; // Set creation time
// Apply rapid fire speed boost if active
if (rapidFire) {
guidedProjectile.speed = guidedProjectile.speed * 1.8; // 80% faster when rapid fire is active
}
guidedProjectiles.push(guidedProjectile);
game.addChild(guidedProjectile);
}
return null; // Don't return cannonballs when using guided shot
} else if (tripleShot) {
// Fire three cannonballs with slight angle differences
for (var i = 0; i < 3; i++) {
var bullet = new Cannonball();
bullet.x = self.x;
bullet.y = self.y;
var angleOffset = (i - 1) * 0.2; // -0.2, 0, 0.2 radians
var adjustedAngle = baseAngle + angleOffset;
bullet.velocityX = Math.cos(adjustedAngle) * bullet.speed;
bullet.velocityY = Math.sin(adjustedAngle) * bullet.speed;
bullet.damage = 2; // Friendly octopus bullets do more damage
bullets.push(bullet);
}
} else {
// Fire single cannonball
var bullet = new Cannonball();
bullet.x = self.x;
bullet.y = self.y;
bullet.velocityX = dx / distance * bullet.speed;
bullet.velocityY = dy / distance * bullet.speed;
bullet.damage = 2; // Friendly octopus bullets do more damage
bullets.push(bullet);
}
return bullets;
};
self.takeDamage = function (damage) {
if (self.isShielded) {
damage = Math.floor(damage / 2); // Shield reduces damage by half
}
self.health -= damage;
if (self.health < 0) self.health = 0;
self.updateHealthBar();
// Flash red when taking damage
tween(self, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
var originalTint = self.isShielded ? 0x88ccff : 0x00ff88;
tween(self, {
tint: originalTint
}, {
duration: 100
});
}
});
};
self.activateShield = function () {
self.isShielded = true;
self.shieldTimer = 900; // 15 seconds at 60fps
self.graphics.tint = 0x88ccff; // Blue tint for shield
};
self.update = function () {
// Update shield status
if (self.isShielded && self.shieldTimer > 0) {
self.shieldTimer--;
if (self.shieldTimer <= 0) {
self.isShielded = false;
tween(self.graphics, {
tint: 0x00ff88
}, {
duration: 500
});
}
}
// Find nearest target (enemy or power-up)
var target = self.findNearestTarget();
// Check distance to cannon and avoid shield if active
var distanceToCannon = Math.sqrt(Math.pow(cannon.x - self.x, 2) + Math.pow(cannon.y - self.y, 2));
var shieldAvoidanceDistance = cannon.isShielded ? 300 : 200;
// Movement logic based on target priority
if (target && self.targetPriority === 'powerup') {
// Move towards power-up
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if path to power-up would go through shield area
if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) {
// Move around the shield instead of towards power-up
var angleAroundShield = Math.atan2(self.y - cannon.y, self.x - cannon.x) + Math.PI / 3;
self.moveDirection = angleAroundShield;
self.movingToPowerUp = false;
} else if (distance > 50) {
self.moveDirection = Math.atan2(dy, dx);
self.movingToPowerUp = true;
} else {
self.movingToPowerUp = false;
}
} else if (target && self.targetPriority === 'enemy' && !self.movingToPowerUp) {
// Combat positioning
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var optimalDistance = 350;
// Avoid shield area during combat
if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) {
var angleAwayFromShield = Math.atan2(self.y - cannon.y, self.x - cannon.x);
self.moveDirection = angleAwayFromShield;
} else if (distance < optimalDistance - 50) {
self.moveDirection = Math.atan2(self.y - target.y, self.x - target.x);
} else if (distance > optimalDistance + 100) {
self.moveDirection = Math.atan2(dy, dx);
} else {
self.changeDirectionTimer++;
if (self.changeDirectionTimer >= 120) {
var currentAngle = Math.atan2(dy, dx);
self.moveDirection = currentAngle + Math.PI / 2 * (Math.random() > 0.5 ? 1 : -1);
self.changeDirectionTimer = 0;
}
}
} else {
// Normal movement - avoid shield area
if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) {
var angleAwayFromShield = Math.atan2(self.y - cannon.y, self.x - cannon.x);
self.moveDirection = angleAwayFromShield;
self.changeDirectionTimer = 0;
} else {
self.changeDirectionTimer++;
if (self.changeDirectionTimer >= 180) {
self.moveDirection = Math.random() * Math.PI * 2;
self.changeDirectionTimer = 0;
}
}
}
// Move in current direction
var moveX = Math.cos(self.moveDirection) * self.speed;
var moveY = Math.sin(self.moveDirection) * self.speed;
var newX = self.x + moveX;
var newY = self.y + moveY;
if (newX < 100 || newX > 1948) {
self.moveDirection = Math.PI - self.moveDirection;
}
if (newY < 300 || newY > 2632) {
self.moveDirection = -self.moveDirection;
}
// Apply movement
self.x = Math.max(100, Math.min(1948, newX));
self.y = Math.max(300, Math.min(2632, newY));
// Find and shoot at nearest enemy
self.targetShip = self.findNearestEnemyShip();
if (self.targetShip) {
var newBullets = self.shootAtTarget(self.targetShip.x, self.targetShip.y);
if (newBullets && newBullets.length > 0) {
for (var bulletIndex = 0; bulletIndex < newBullets.length; bulletIndex++) {
cannonballs.push(newBullets[bulletIndex]);
game.addChild(newBullets[bulletIndex]);
}
LK.getSound('shoot').play();
}
}
};
return self;
});
var GuidedProjectile = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('guidedProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 16;
self.damage = 3; // Base damage for guided projectiles (will be enhanced by cannonDamage)
self.targetX = 0;
self.targetY = 0;
self.lastTargetUpdateTime = 0;
self.targetUpdateDelay = 30; // Update target every 0.5 seconds
self.creationTime = 0; // Track when the projectile was created
self.lifetime = 120; // 2 seconds at 60fps (2 * 60)
self.update = function () {
// Update target position periodically
if (LK.ticks - self.lastTargetUpdateTime >= self.targetUpdateDelay) {
// Find nearest enemy ship
var nearestShip = null;
var nearestDistance = Infinity;
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) {
var dx = ship.x - self.x;
var dy = ship.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestShip = ship;
}
}
}
// If guided shot is active, also look for health kits to target
if (guidedShot && !nearestShip) {
var nearestHealthKit = null;
var nearestHealthKitDistance = Infinity;
for (var hk = 0; hk < healthKits.length; hk++) {
var healthKit = healthKits[hk];
if (healthKit && healthKit.parent) {
var dx = healthKit.x - self.x;
var dy = healthKit.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestHealthKitDistance) {
nearestHealthKitDistance = distance;
nearestHealthKit = healthKit;
}
}
}
if (nearestHealthKit) {
self.targetX = nearestHealthKit.x;
self.targetY = nearestHealthKit.y;
}
}
// If we found a ship, target it (priority over health kits)
if (nearestShip) {
self.targetX = nearestShip.x;
self.targetY = nearestShip.y;
}
self.lastTargetUpdateTime = LK.ticks;
}
// Move toward target
var dx = self.targetX - self.x;
var dy = self.targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Add slight rotation for visual effect
graphics.rotation += 0.1;
};
return self;
});
var GuidedShotPowerUp = Container.expand(function () {
var self = Container.call(this);
self.speed = 2;
var graphics = self.attachAsset('guidedShotPowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.x += self.speed;
};
return self;
});
var HealthKit = Container.expand(function () {
var self = Container.call(this);
self.speed = 2;
self.healAmount = 2;
var graphics = self.attachAsset('healthKit', {
anchorX: 0.5,
anchorY: 0.5
});
// Add a cross symbol
var crossV = LK.getAsset('healthKit', {
width: 20,
height: 60,
color: 0xffffff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
var crossH = LK.getAsset('healthKit', {
width: 60,
height: 20,
color: 0xffffff,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.addChild(crossV);
self.addChild(crossH);
self.update = function () {
self.x += self.speed;
};
return self;
});
var Octopus = Container.expand(function () {
var self = Container.call(this);
self.speed = 4;
self.targetShip = null;
self.state = 'seeking'; // seeking, grabbing, sinking
self.sinkTimer = 0;
self.sinkDuration = 120; // 2 seconds at 60fps
var graphics = self.attachAsset('smallShip', {
anchorX: 0.5,
anchorY: 0.5,
color: 0x800080,
// Purple color for octopus
shape: 'ellipse'
});
graphics.scaleX = 0.8;
graphics.scaleY = 0.8;
graphics.tint = 0x800080;
self.init = function () {
// Find nearest ship to grab
var nearestShip = null;
var nearestDistance = Infinity;
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying && ship.shipType !== 'boss') {
var dx = ship.x - self.x;
var dy = ship.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestShip = ship;
}
}
}
self.targetShip = nearestShip;
};
self.update = function () {
if (self.state === 'seeking' && self.targetShip && self.targetShip.parent) {
// Move towards target ship
var dx = self.targetShip.x - self.x;
var dy = self.targetShip.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Check if reached the ship
if (distance < 50) {
self.state = 'grabbing';
self.targetShip.isDying = true;
// Start sinking animation
tween(self.targetShip, {
y: self.targetShip.y + 100,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.5
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.targetShip && self.targetShip.parent) {
self.targetShip.destroy();
// Remove from ships array
for (var i = ships.length - 1; i >= 0; i--) {
if (ships[i] === self.targetShip) {
ships.splice(i, 1);
break;
}
}
}
}
});
self.state = 'sinking';
}
} else if (self.state === 'sinking') {
self.sinkTimer++;
if (self.sinkTimer >= self.sinkDuration) {
// Octopus disappears
self.destroy();
}
}
};
return self;
});
var OctopusArm = Container.expand(function () {
var self = Container.call(this);
self.speed = 28;
self.targetShip = null;
self.state = 'emerging'; // emerging, seeking, grabbing, sinking, retreating
self.stateTimer = 0;
self.grabCount = 0;
self.maxGrabs = 5;
self.lifeTimer = 1200; // 20 seconds at 60fps (20 * 60)
var graphics = self.attachAsset('octopusArm', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x4B0082; // Deep purple color for octopus arm
self.init = function () {
self.x = 1024; // Start from center bottom
self.y = 2600; // Start from bottom of screen
self.alpha = 0.8;
self.scaleX = 0.5;
self.scaleY = 0.5;
self.state = 'emerging';
self.stateTimer = 60; // 1 second emergence time
self.grabCount = 0;
// Emergence animation
tween(self, {
y: 2200,
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 1000,
easing: tween.easeOut
});
};
self.findNearestShip = function () {
var nearestShip = null;
var nearestDistance = Infinity;
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) {
var dx = ship.x - self.x;
var dy = ship.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestShip = ship;
}
}
}
return nearestShip;
};
self.update = function () {
self.stateTimer--;
self.lifeTimer--; // Decrease life timer each frame
// Check if 20 seconds have passed - force retreat (only if not already retreating)
if (self.lifeTimer <= 0 && self.state !== 'retreating') {
self.state = 'retreating';
self.stateTimer = 60;
}
if (self.state === 'emerging') {
if (self.stateTimer <= 0) {
self.state = 'seeking';
self.targetShip = self.findNearestShip();
}
} else if (self.state === 'seeking') {
if (self.targetShip && self.targetShip.parent && !self.targetShip.isDying) {
// Move towards target ship
var dx = self.targetShip.x - self.x;
var dy = self.targetShip.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 0) {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
// Check if reached the ship
if (distance < 80) {
self.state = 'grabbing';
self.stateTimer = 30; // Half second grab time
// Special handling for bosses - damage instead of instant kill
if (self.targetShip.shipType === 'boss') {
self.targetShip.health -= 15;
self.targetShip.updateHealthBar();
LK.effects.flashObject(self.targetShip, 0xff0000, 500);
if (self.targetShip.health <= 0) {
self.targetShip.isDying = true;
// Count this as an octopus kill since boss died from octopus damage
self.grabCount++;
// Grab animation - ship gets pulled towards octopus
tween(self.targetShip, {
x: self.x,
y: self.y + 50,
scaleX: 0.6,
scaleY: 0.6,
alpha: 0.7
}, {
duration: 500,
easing: tween.easeIn
});
} else {
// Boss survives, octopus retreats after damaging but doesn't count as kill
self.state = 'retreating';
self.stateTimer = 60;
}
} else {
self.targetShip.isDying = true;
// Count this kill immediately when octopus grabs the ship
self.grabCount++;
// Grab animation - ship gets pulled towards octopus
tween(self.targetShip, {
x: self.x,
y: self.y + 50,
scaleX: 0.6,
scaleY: 0.6,
alpha: 0.7
}, {
duration: 500,
easing: tween.easeIn
});
}
}
} else {
// Find new target if current one is gone
self.targetShip = self.findNearestShip();
if (!self.targetShip) {
self.state = 'retreating';
self.stateTimer = 60;
}
}
} else if (self.state === 'grabbing') {
if (self.stateTimer <= 0) {
self.state = 'sinking';
self.stateTimer = 90; // 1.5 second sink time
// Sinking animation - ship disappears into the depths
if (self.targetShip && self.targetShip.parent) {
tween(self.targetShip, {
y: self.targetShip.y + 200,
scaleX: 0.2,
scaleY: 0.2,
alpha: 0.0,
rotation: Math.PI
}, {
duration: 1500,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.targetShip && self.targetShip.parent) {
// Award points and remove ship
LK.setScore(LK.getScore() + self.targetShip.points);
scoreTxt.setText('Score: ' + LK.getScore());
LK.getSound('shipDestroyed').play();
self.targetShip.destroy();
// Remove from ships array
for (var i = ships.length - 1; i >= 0; i--) {
if (ships[i] === self.targetShip) {
ships.splice(i, 1);
break;
}
}
}
}
});
}
}
} else if (self.state === 'sinking') {
if (self.stateTimer <= 0) {
// Continue seeking until lifetime expires (controlled by lifeTimer check above)
// Only continue seeking if we still have time left and haven't reached max grabs
if (self.lifeTimer > 60 && self.grabCount < self.maxGrabs) {
self.state = 'seeking';
self.targetShip = self.findNearestShip();
} else {
// Time is almost up or max grabs reached, start retreating
self.state = 'retreating';
self.stateTimer = 60;
}
}
} else if (self.state === 'retreating') {
// Retreat animation
self.y += 4;
self.alpha -= 0.02;
self.scaleX -= 0.02;
self.scaleY -= 0.02;
if (self.stateTimer <= 0 || self.alpha <= 0) {
self.destroy();
}
}
};
return self;
});
var OctopusPowerUp = Container.expand(function () {
var self = Container.call(this);
self.speed = 2;
var graphics = self.attachAsset('octopusPowerUpSphere', {
anchorX: 0.5,
anchorY: 0.5
});
// Add "P" text on the sphere
var pText = new Text2('P', {
size: 120,
fill: 0xFFFFFF
});
pText.anchor.set(0.5, 0.5);
self.addChild(pText);
self.update = function () {
self.x += self.speed;
};
return self;
});
var PermanentAlly = Container.expand(function () {
var self = Container.call(this);
self.health = 2;
self.maxHealth = 2;
self.speed = 2;
self.lastShotTime = 0;
self.shotDelay = 120; // 2 seconds
self.moveDirection = Math.random() * Math.PI * 2; // Random initial direction
self.changeDirectionTimer = 0;
self.targetShip = null;
self.isShielded = false;
self.shieldTimer = 0;
self.targetPriority = 'enemy';
self.movingToPowerUp = false;
var graphics = self.attachAsset('friendlyOctopus', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.tint = 0x88ff44; // Different green tint to distinguish from temporary allies
graphics.scaleX = 0.6;
graphics.scaleY = 0.6;
// Create health bar
self.healthBarBg = LK.getAsset('healthBarBg', {
width: 120,
height: 12,
color: 0x444444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -graphics.height / 2 - 20;
self.addChild(self.healthBarBg);
self.healthBar = LK.getAsset('healthBar', {
width: 116,
height: 8,
color: 0x88FF44,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBar.y = -graphics.height / 2 - 20;
self.addChild(self.healthBar);
self.updateHealthBar = function () {
if (self.healthBar && self.maxHealth > 0) {
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x88FF44; // Bright green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
}
};
self.findNearestTarget = function () {
var nearestTarget = null;
var nearestDistance = Infinity;
var targetPriority = 'enemy'; // Default priority
// First priority: Find nearest enemy ship
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) {
var dx = ship.x - self.x;
var dy = ship.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = ship;
targetPriority = 'enemy';
}
}
}
// Second priority: Find power-ups if no nearby enemies or enemies are far
if (!nearestTarget || nearestDistance > 600) {
// Look for power-ups to collect
var powerUpArrays = [powerUps, healthKits, shieldPowerUps, guidedShotPowerUps, octopusPowerUps];
for (var arrayIndex = 0; arrayIndex < powerUpArrays.length; arrayIndex++) {
var powerUpArray = powerUpArrays[arrayIndex];
for (var p = 0; p < powerUpArray.length; p++) {
var powerUp = powerUpArray[p];
if (powerUp && powerUp.parent) {
var dx = powerUp.x - self.x;
var dy = powerUp.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestTarget = powerUp;
targetPriority = 'powerup';
}
}
}
}
}
self.targetPriority = targetPriority;
return nearestTarget;
};
self.findNearestEnemyShip = function () {
var nearestShip = null;
var nearestDistance = Infinity;
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying) {
var dx = ship.x - self.x;
var dy = ship.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestShip = ship;
}
}
}
return nearestShip;
};
self.canShoot = function () {
var currentShotDelay = rapidFire ? self.shotDelay / 3 : self.shotDelay;
return LK.ticks - self.lastShotTime >= currentShotDelay;
};
self.shootAtTarget = function (targetX, targetY) {
if (!self.canShoot()) return null;
self.lastShotTime = LK.ticks;
var bullets = [];
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var baseAngle = Math.atan2(dy, dx);
if (guidedShot) {
// Fire guided projectiles like cannon when guided shot is active
var projectilesToFire = 1;
if (tripleShot) {
projectilesToFire = 3; // Triple shot multiplies guided projectiles
}
// Fire multiple guided projectiles if triple shot is active
for (var projIndex = 0; projIndex < projectilesToFire; projIndex++) {
var guidedProjectile = new GuidedProjectile();
guidedProjectile.x = self.x + (projIndex - Math.floor(projectilesToFire / 2)) * 30; // Spread them out horizontally
guidedProjectile.y = self.y;
guidedProjectile.targetX = targetX;
guidedProjectile.targetY = targetY;
guidedProjectile.lastTargetUpdateTime = LK.ticks;
guidedProjectile.creationTime = LK.ticks; // Set creation time
// Apply rapid fire speed boost if active
if (rapidFire) {
guidedProjectile.speed = guidedProjectile.speed * 1.8; // 80% faster when rapid fire is active
}
guidedProjectiles.push(guidedProjectile);
game.addChild(guidedProjectile);
}
return null; // Don't return cannonballs when using guided shot
} else if (tripleShot) {
// Fire three cannonballs with slight angle differences
for (var i = 0; i < 3; i++) {
var bullet = new Cannonball();
bullet.x = self.x;
bullet.y = self.y;
var angleOffset = (i - 1) * 0.2; // -0.2, 0, 0.2 radians
var adjustedAngle = baseAngle + angleOffset;
bullet.velocityX = Math.cos(adjustedAngle) * bullet.speed;
bullet.velocityY = Math.sin(adjustedAngle) * bullet.speed;
bullet.damage = 2; // Permanent ally bullets do more damage
bullets.push(bullet);
}
} else {
// Fire single cannonball
var bullet = new Cannonball();
bullet.x = self.x;
bullet.y = self.y;
bullet.velocityX = dx / distance * bullet.speed;
bullet.velocityY = dy / distance * bullet.speed;
bullet.damage = 2; // Permanent ally bullets do more damage
bullets.push(bullet);
}
return bullets;
};
self.takeDamage = function (damage) {
if (self.isShielded) {
damage = Math.floor(damage / 2); // Shield reduces damage by half
}
self.health -= damage;
if (self.health < 0) self.health = 0;
self.updateHealthBar();
// Flash red when taking damage
tween(self, {
tint: 0xff0000
}, {
duration: 100,
onFinish: function onFinish() {
var originalTint = self.isShielded ? 0x88ccff : 0x88ff44;
tween(self, {
tint: originalTint
}, {
duration: 100
});
}
});
};
self.activateShield = function () {
self.isShielded = true;
self.shieldTimer = 900; // 15 seconds at 60fps
self.graphics.tint = 0x88ccff; // Blue tint for shield
};
self.update = function () {
// Update shield status
if (self.isShielded && self.shieldTimer > 0) {
self.shieldTimer--;
if (self.shieldTimer <= 0) {
self.isShielded = false;
tween(self.graphics, {
tint: 0x88ff44
}, {
duration: 500
});
}
}
// Find nearest target (enemy or power-up)
var target = self.findNearestTarget();
var screenCenterX = 1024; // Center of screen
var screenCenterY = 1366; // Middle of screen height
var minDistanceFromCannon = 200; // Minimum distance to stay away from cannon
var distanceToCannon = Math.sqrt(Math.pow(cannon.x - self.x, 2) + Math.pow(cannon.y - self.y, 2));
// Check if cannon shield is active and avoid it
var shieldAvoidanceDistance = cannon.isShielded ? 300 : minDistanceFromCannon;
// Movement logic based on target priority
if (target && self.targetPriority === 'powerup') {
// Move towards power-up
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if path to power-up would go through shield area
if (cannon.isShielded) {
var distanceToCannonOnPath = Math.sqrt(Math.pow(cannon.x - self.x, 2) + Math.pow(cannon.y - self.y, 2));
if (distanceToCannonOnPath < shieldAvoidanceDistance) {
// Move around the shield instead of towards power-up
var angleAroundShield = Math.atan2(self.y - cannon.y, self.x - cannon.x) + Math.PI / 4;
self.moveDirection = angleAroundShield;
self.movingToPowerUp = false;
} else if (distance > 50) {
// Move towards power-up if not close enough and path is clear
self.moveDirection = Math.atan2(dy, dx);
self.movingToPowerUp = true;
} else {
// Close enough to power-up, resume normal movement
self.movingToPowerUp = false;
}
} else {
if (distance > 50) {
// Move towards power-up if not close enough
self.moveDirection = Math.atan2(dy, dx);
self.movingToPowerUp = true;
} else {
// Close enough to power-up, resume normal movement
self.movingToPowerUp = false;
}
}
} else if (target && self.targetPriority === 'enemy' && !self.movingToPowerUp) {
// Combat positioning - stay at medium range from enemies
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var optimalDistance = 300; // Optimal combat distance
// Avoid shield area during combat
if (cannon.isShielded && distanceToCannon < shieldAvoidanceDistance) {
var angleAwayFromShield = Math.atan2(self.y - cannon.y, self.x - cannon.x);
self.moveDirection = angleAwayFromShield;
} else if (distance < optimalDistance - 50) {
// Too close, back away
self.moveDirection = Math.atan2(self.y - target.y, self.x - target.x);
} else if (distance > optimalDistance + 100) {
// Too far, move closer
self.moveDirection = Math.atan2(dy, dx);
} else {
// Good distance, strafe around target
self.changeDirectionTimer++;
if (self.changeDirectionTimer >= 120) {
var currentAngle = Math.atan2(dy, dx);
self.moveDirection = currentAngle + Math.PI / 2 * (Math.random() > 0.5 ? 1 : -1);
self.changeDirectionTimer = 0;
}
}
} else {
// No target or normal movement
if (distanceToCannon < shieldAvoidanceDistance) {
var angleAwayFromCannon = Math.atan2(self.y - cannon.y, self.x - cannon.x);
self.moveDirection = angleAwayFromCannon;
self.changeDirectionTimer = 0;
} else {
self.changeDirectionTimer++;
if (self.changeDirectionTimer >= 180) {
self.moveDirection = Math.random() * Math.PI * 2;
self.changeDirectionTimer = 0;
}
}
}
// Move in current direction
var moveX = Math.cos(self.moveDirection) * self.speed;
var moveY = Math.sin(self.moveDirection) * self.speed;
var newX = self.x + moveX;
var newY = self.y + moveY;
// Ensure ally stays within screen center area
var minX = Math.max(100, screenCenterX - 500);
var maxX = Math.min(1948, screenCenterX + 500);
var minY = Math.max(800, screenCenterY - 300);
var maxY = Math.min(2000, screenCenterY + 300);
// Additional check to maintain distance from cannon
var newCannonDistance = Math.sqrt(Math.pow(cannon.x - newX, 2) + Math.pow(cannon.y - newY, 2));
if (newCannonDistance < minDistanceFromCannon) {
var pushAngle = Math.atan2(newY - cannon.y, newX - cannon.x);
newX = cannon.x + Math.cos(pushAngle) * minDistanceFromCannon;
newY = cannon.y + Math.sin(pushAngle) * minDistanceFromCannon;
}
if (newX < minX || newX > maxX) {
self.moveDirection = Math.PI - self.moveDirection;
}
if (newY < minY || newY > maxY) {
self.moveDirection = -self.moveDirection;
}
// Apply movement
self.x = Math.max(minX, Math.min(maxX, newX));
self.y = Math.max(minY, Math.min(maxY, newY));
// Combat behavior - shoot at enemies
self.targetShip = self.findNearestEnemyShip();
if (self.targetShip) {
var newBullets = self.shootAtTarget(self.targetShip.x, self.targetShip.y);
if (newBullets && newBullets.length > 0) {
for (var bulletIndex = 0; bulletIndex < newBullets.length; bulletIndex++) {
cannonballs.push(newBullets[bulletIndex]);
game.addChild(newBullets[bulletIndex]);
}
LK.getSound('shoot').play();
}
}
};
return self;
});
var PowerUp = Container.expand(function () {
var self = Container.call(this);
self.powerType = 'tripleShot';
self.speed = 2;
self.graphics = null;
self.init = function (type) {
self.powerType = type;
if (type === 'tripleShot') {
self.graphics = self.attachAsset('tripleShotPowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (type === 'rapidFire') {
self.graphics = self.attachAsset('rapidFirePowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
self.update = function () {
self.x += self.speed;
};
return self;
});
var ShieldPowerUp = Container.expand(function () {
var self = Container.call(this);
self.speed = 2;
var graphics = self.attachAsset('shieldPowerUp', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
self.x += self.speed;
};
return self;
});
var Ship = Container.expand(function () {
var self = Container.call(this);
self.shipType = 'small';
self.speed = 2;
self.points = 10;
self.graphics = null;
self.healthBar = null;
self.healthBarBg = null;
self.isDying = false; // Flag to prevent damage during death animation
self.init = function (type) {
self.shipType = type;
if (type === 'small') {
self.graphics = self.attachAsset('smallShip', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 3;
// Small ships: 100 points in waves 1-2, then scale with wave
if (currentWave <= 2) {
self.points = 100;
} else {
self.points = 10 + (currentWave - 2) * 5; // Gradual increase after wave 2
}
self.health = 1;
self.maxHealth = 1;
} else if (type === 'medium') {
self.graphics = self.attachAsset('mediumShip', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 2;
// Medium ships: 100 points when first appearing (waves 3-4), then scale
if (currentWave <= 4) {
self.points = 100;
} else {
self.points = 25 + (currentWave - 4) * 10; // Gradual increase after wave 4
}
self.health = 3;
self.maxHealth = 3;
} else if (type === 'large') {
self.graphics = self.attachAsset('largeShip', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 1.5;
// Large ships: 100 points when first appearing (waves 5-6), then scale
if (currentWave <= 6) {
self.points = 100;
} else {
self.points = 50 + (currentWave - 6) * 15; // Gradual increase after wave 6
}
self.health = 5;
self.maxHealth = 5;
}
// Create health bar background - wider for better visibility
var healthBarWidth = Math.max(120, self.graphics.width * 1.2);
self.healthBarBg = LK.getAsset('healthBarBg', {
width: healthBarWidth,
height: 12,
color: 0x444444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBarBg.y = -self.graphics.height / 2 - 20;
self.addChild(self.healthBarBg);
// Create health bar foreground - wider for better visibility
self.healthBar = LK.getAsset('healthBar', {
width: healthBarWidth - 4,
height: 8,
color: 0x00FF00,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
self.healthBar.y = -self.graphics.height / 2 - 20;
self.addChild(self.healthBar);
// Show health bar for all ships now - no hiding for small ships
// Set shooting properties based on ship type - slower attack speeds
self.lastShotTime = 0;
if (type === 'small') {
self.shotDelay = 180; // 3 seconds (much slower)
} else if (type === 'medium') {
self.shotDelay = 150; // 2.5 seconds (much slower)
} else if (type === 'large') {
self.shotDelay = 120; // 2 seconds (much slower)
}
};
self.updateHealthBar = function () {
if (self.healthBar && self.maxHealth >= 1) {
var healthPercent = Math.max(0, self.health / self.maxHealth);
self.healthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
self.healthBar.tint = 0x00FF00; // Green
} else if (healthPercent > 0.3) {
self.healthBar.tint = 0xFFFF00; // Yellow
} else {
self.healthBar.tint = 0xFF0000; // Red
}
// Ensure health bar visibility for all ship types
self.healthBar.visible = true;
self.healthBarBg.visible = true;
}
};
self.update = function () {
self.x += self.speed;
// Ensure collision detection remains active
if (self.graphics) {
self.graphics.visible = true;
}
};
self.canShoot = function () {
return LK.ticks - self.lastShotTime >= self.shotDelay;
};
self.shootAtCannon = function (cannonX, cannonY) {
if (!self.canShoot()) return null;
self.lastShotTime = LK.ticks;
var bullet = new EnemyBullet();
bullet.x = self.x;
bullet.y = self.y;
var dx = cannonX - self.x;
var dy = cannonY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
bullet.velocityX = dx / distance * bullet.speed;
bullet.velocityY = dy / distance * bullet.speed;
// Enhanced damage based on ship type
if (self.shipType === 'small') {
bullet.damage = 1;
} else if (self.shipType === 'medium') {
bullet.damage = 2;
} else if (self.shipType === 'large') {
bullet.damage = 3;
}
return bullet;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x001122
});
/****
* Game Code
****/
var oceanBackground = LK.getAsset('oceanBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
oceanBackground.visible = false; // Start hidden since we show sketches first
game.addChild(oceanBackground);
// Game background - will replace ocean background when game starts
var gameBackground = LK.getAsset('gameBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
gameBackground.visible = true; // Start visible for sketches
game.addChild(gameBackground);
// Start menu variables - reset to beginning
var gameStarted = false;
var sketchShown = true; // Start with sketches visible
var menuShown = false; // Track if menu has been shown
var currentSketchIndex = 0; // Reset to show first sketch
var totalSketches = 3;
var startMenu = new Container();
game.addChild(startMenu);
startMenu.visible = false; // Start with menu hidden
// Sketch menu container
var sketchMenu = new Container();
game.addChild(sketchMenu);
sketchMenu.visible = true; // Start with sketches visible
// Sketch background - only visible during sketch
var sketchBackgroundImage = sketchMenu.attachAsset('sketchBackground', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0
});
// Sketch images
var sketchImage = sketchMenu.attachAsset('sketch', {
anchorX: 0.5,
anchorY: 0.5
});
sketchImage.x = 1024;
sketchImage.y = 1366;
var sketchImage2 = sketchMenu.attachAsset('sketch2', {
anchorX: 0.5,
anchorY: 0.5
});
sketchImage2.x = 1024;
sketchImage2.y = 1366;
sketchImage2.visible = false;
var sketchImage3 = sketchMenu.attachAsset('sketch3', {
anchorX: 0.5,
anchorY: 0.5
});
sketchImage3.x = 1024;
sketchImage3.y = 1366;
sketchImage3.visible = false;
// Add pulsating animation to sketch images
function pulseSketchImage(sketchImg) {
if (sketchImg.visible && sketchImg.parent) {
tween(sketchImg, {
scaleX: 1.05,
scaleY: 1.05,
alpha: 0.9
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (sketchImg.visible && sketchImg.parent) {
tween(sketchImg, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
pulseSketchImage(sketchImg);
}
});
}
}
});
}
}
// Sketch images no longer have pulsating animations
// Continue button text
var continueText = new Text2('Tap to continue', {
size: 60,
fill: 0xFFFFFF
});
continueText.anchor.set(0.5, 0.5);
continueText.x = 1024;
continueText.y = 2400;
sketchMenu.addChild(continueText);
// Add pulsating animation to continue text
function pulseContinueText() {
tween(continueText, {
alpha: 0.5,
scaleX: 0.9,
scaleY: 0.9
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(continueText, {
alpha: 1.0,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: pulseContinueText
});
}
});
}
// Start continue text animation immediately since sketches are shown first
pulseContinueText();
// Ocean title image
var oceanTitleImage = startMenu.attachAsset('oceanTitle', {
anchorX: 0.5,
anchorY: 0.5
});
oceanTitleImage.x = 1024;
oceanTitleImage.y = 750;
// Defender title image
var defenderTitleImage = startMenu.attachAsset('defenderTitle', {
anchorX: 0.5,
anchorY: 0.5
});
defenderTitleImage.x = 1024;
defenderTitleImage.y = 1600;
// Play button image
var playButtonImage = startMenu.attachAsset('playButtonImage', {
anchorX: 0.5,
anchorY: 0.5
});
playButtonImage.x = 1024;
playButtonImage.y = 2300;
// Add pulsating animation to ocean title
function pulseOceanTitle() {
tween(oceanTitleImage, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(oceanTitleImage, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: pulseOceanTitle
});
}
});
}
pulseOceanTitle();
// Add pulsating animation to defender title
function pulseDefenderTitle() {
tween(defenderTitleImage, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(defenderTitleImage, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: pulseDefenderTitle
});
}
});
}
pulseDefenderTitle();
// Add pulsating animation to play button
function pulsePlayButton() {
tween(playButtonImage, {
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(playButtonImage, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: pulsePlayButton
});
}
});
}
pulsePlayButton();
// Creator name image
var creatorNameImage = startMenu.attachAsset('creatorNameImage', {
anchorX: 0.5,
anchorY: 0.5
});
creatorNameImage.x = 1024;
creatorNameImage.y = 2500;
// Add pulsating animation to creator image
function pulseCreatorName() {
if (creatorNameImage.visible && creatorNameImage.parent) {
tween(creatorNameImage, {
scaleX: 1.05,
scaleY: 1.05,
alpha: 0.9
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (creatorNameImage.visible && creatorNameImage.parent) {
tween(creatorNameImage, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
pulseCreatorName();
}
});
}
}
});
}
}
pulseCreatorName();
// Start playing the sketch music immediately when game starts
LK.playMusic('sketchMusic', {
loop: true,
fade: {
start: 0,
end: 0.5,
duration: 500
}
});
// Initialize permanent allies array
var permanentAllies = [];
function checkWaveCompletion() {
// Simple wave completion based on enemies killed
var requiredKills = enemiesRequiredPerWave + (currentWave - 1) * 5;
if (enemiesKilledThisWave >= requiredKills && !waveCompleted) {
waveCompleted = true;
enemiesKilledThisWave = 0;
waveCompleted = false;
}
}
var cannon = game.addChild(new Cannon());
cannon.x = 1024;
cannon.y = 2600;
cannon.visible = false;
cannon.baseFireDelay = 300; // Store original fire delay
// Add breathing animation to cannon
function breatheCannon() {
if (cannon.graphics && cannon.graphics.parent) {
tween(cannon.graphics, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (cannon.graphics && cannon.graphics.parent) {
tween(cannon.graphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: breatheCannon
});
}
}
});
}
}
// Start breathing animation immediately
breatheCannon();
// Shield effect visual
var shieldEffect = LK.getAsset('shieldEffect', {
anchorX: 0.5,
anchorY: 0.5
});
shieldEffect.x = cannon.x;
shieldEffect.y = cannon.y;
shieldEffect.visible = false;
shieldEffect.alpha = 0.6;
shieldEffect.scaleX = 0.3;
shieldEffect.scaleY = 0.3;
game.addChild(shieldEffect);
var ships = [];
var cannonballs = [];
var shipSpawnTimer = 0;
var shipSpawnDelay = 120;
var difficultyTimer = 0;
var shipsEscaped = 0;
var maxEscapedShips = 10;
// Escaped ships counter UI
var escapedShipsTxt = new Text2('Escaped: 0/10', {
size: 50,
fill: 0xFFFFFF
});
escapedShipsTxt.anchor.set(0.5, 0);
escapedShipsTxt.y = 60;
escapedShipsTxt.visible = false;
LK.gui.top.addChild(escapedShipsTxt);
// Wave number display UI
var waveNumberTxt = new Text2('Wave: 1', {
size: 50,
fill: 0xFFFFFF
});
waveNumberTxt.anchor.set(0.5, 0);
waveNumberTxt.y = 120;
waveNumberTxt.visible = false;
LK.gui.top.addChild(waveNumberTxt);
var scoreTxt = new Text2('Score: 0', {
size: 60,
fill: 0xFFFFFF
});
scoreTxt.anchor.set(0.5, 0);
scoreTxt.visible = false;
LK.gui.top.addChild(scoreTxt);
var powerUps = [];
var tripleShot = false;
var tripleShotTimer = 0;
var rapidFire = false;
var rapidFireTimer = 0;
var powerUpDuration = 300; // 5 seconds at 60fps
// Power-up timer UI
var powerUpTimerTxt = new Text2('', {
size: 70,
fill: 0xFFFFFF
});
powerUpTimerTxt.anchor.set(0.5, 0.5);
powerUpTimerTxt.x = 0;
powerUpTimerTxt.y = 100;
LK.gui.center.addChild(powerUpTimerTxt);
var activePowerUpType = '';
var activePowerUpTimer = 0;
var isShooting = false;
var shootingTimer = 0;
// Wave system
var currentWave = 1;
var lastWaveScore = 0;
var waveMessage = null;
var waveMessageTimer = 0;
var waveCompleted = false;
// Wave completion tracking
var enemiesKilledThisWave = 0;
var enemiesRequiredPerWave = 15; // Base number of enemies to kill per wave
// Cannon health system
var enemyBullets = [];
var healthKits = [];
var shieldPowerUps = [];
var guidedShotPowerUps = [];
var guidedProjectiles = [];
var octopusPowerUps = [];
var octopuses = [];
var friendlyOctopuses = [];
var guidedShot = false;
var guidedShotTimer = 0;
var guidedShotDuration = 420; // 7 seconds at 60fps
// Cannon health UI - positioned next to cannon
var cannonHealthBarBg = LK.getAsset('cannonHealthBarBg', {
width: 400,
height: 20,
color: 0x444444,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
cannonHealthBarBg.x = cannon.x;
cannonHealthBarBg.y = cannon.y - 150;
cannonHealthBarBg.visible = false;
game.addChild(cannonHealthBarBg);
var cannonHealthBar = LK.getAsset('cannonHealthBar', {
width: 396,
height: 16,
color: 0x00ff00,
shape: 'box',
anchorX: 0.5,
anchorY: 0.5
});
cannonHealthBar.x = cannon.x;
cannonHealthBar.y = cannon.y - 150;
cannonHealthBar.visible = false;
game.addChild(cannonHealthBar);
var healthText = new Text2('Health: 10/10', {
size: 40,
fill: 0xFFFFFF
});
healthText.anchor.set(0.5, 0.5);
healthText.x = cannon.x;
healthText.y = cannon.y - 100;
healthText.visible = false;
game.addChild(healthText);
// Wave message text
var waveMessageTxt = new Text2('', {
size: 100,
fill: 0xFFFF00
});
waveMessageTxt.anchor.set(0.5, 0.5);
LK.gui.center.addChild(waveMessageTxt);
waveMessageTxt.visible = false;
// Track if boss has been spawned this wave
var bossSpawnedThisWave = false;
function spawnShip() {
// Check if it's a boss wave (every 4 waves) and boss hasn't been spawned yet
if (currentWave % 4 === 0 && currentWave >= 4 && !bossSpawnedThisWave) {
// Check if there's already a boss in the ships array
var bossExists = false;
for (var i = 0; i < ships.length; i++) {
if (ships[i].shipType === 'boss') {
bossExists = true;
break;
}
}
// Only spawn boss if none exists
if (!bossExists) {
var boss = new Boss();
boss.init(currentWave);
boss.x = -300; // Start further left due to larger size
boss.y = 1366; // Center vertically
boss.zIndex = 10; // Highest priority for rendering
ships.push(boss);
game.addChild(boss);
game.children.sort(function (a, b) {
return (a.zIndex || 0) - (b.zIndex || 0);
});
bossSpawnedThisWave = true;
}
}
// Regular ship spawning for all waves (including boss waves)
var ship = new Ship();
var shipType;
// Wave-based enemy spawning
if (currentWave <= 2) {
// Waves 1-2: Only small ships
shipType = 'small';
} else if (currentWave <= 4) {
// Waves 3-4: Small and medium ships
var types = ['small', 'small', 'medium'];
shipType = types[Math.floor(Math.random() * types.length)];
} else if (currentWave <= 6) {
// Waves 5-6: Medium and some large ships
var types = ['small', 'medium', 'medium', 'large'];
shipType = types[Math.floor(Math.random() * types.length)];
} else {
// Wave 7+: All ship types with more large ships
var types = ['small', 'medium', 'large', 'large'];
shipType = types[Math.floor(Math.random() * types.length)];
}
ship.init(shipType);
ship.x = -150;
ship.y = 1000 + Math.random() * 1200;
// Apply wave-based speed multiplier
var speedMultiplier = 1 + (currentWave - 1) * 0.3;
// If it's a boss wave, apply slower speed to all enemies
if (currentWave % 4 === 0 && currentWave >= 4) {
speedMultiplier = speedMultiplier * 0.5; // Make all enemies slower on boss waves
}
ship.speed = ship.speed * speedMultiplier;
// Set zIndex based on ship type for proper layering
if (ship.shipType === 'large') {
ship.zIndex = 3;
} else if (ship.shipType === 'medium') {
ship.zIndex = 2;
} else {
ship.zIndex = 1;
}
game.children.sort(function (a, b) {
return (a.zIndex || 0) - (b.zIndex || 0);
});
ships.push(ship);
game.addChild(ship);
}
function updateDifficulty() {
difficultyTimer++;
if (difficultyTimer % 1800 === 0) {
shipSpawnDelay = Math.max(60, shipSpawnDelay - 10);
}
}
function checkWaveTransition() {
var currentScore = LK.getScore();
var newWave = Math.floor(currentScore / 1000) + 1;
if (newWave > currentWave) {
currentWave = newWave;
lastWaveScore = Math.floor(currentScore / 1000) * 1000;
// Reset boss spawning flag for new wave
bossSpawnedThisWave = false;
// Reset escaped ships counter for new wave
shipsEscaped = 0;
escapedShipsTxt.setText('Escaped: 0/10');
// Update wave number display
waveNumberTxt.setText('Wave: ' + currentWave);
// Increase max health by 2 each wave
cannon.maxHealth += 2;
cannon.health = cannon.maxHealth; // Restore to full health
// Keep base fire rate constant across waves
cannon.fireDelay = cannon.baseFireDelay;
// Spawn new permanent ally each wave
var newAlly = new PermanentAlly();
// Position allies in a circle around the cannon, close to it
var allyIndex = permanentAllies.length;
var angle = allyIndex * Math.PI * 2 / 8; // Distribute up to 8 allies in circle
var radius = 250; // Close radius around cannon
newAlly.x = cannon.x + Math.cos(angle) * radius;
newAlly.y = cannon.y + Math.sin(angle) * radius;
// Keep allies within screen bounds
newAlly.x = Math.max(150, Math.min(1898, newAlly.x));
newAlly.y = Math.max(400, Math.min(2500, newAlly.y));
permanentAllies.push(newAlly);
game.addChild(newAlly);
// Spawn friendly octopus every 2 waves starting from wave 3
if (currentWave >= 3 && currentWave % 2 === 1) {
var friendlyOctopus = new FriendlyOctopus();
friendlyOctopus.x = 1024; // Center position
friendlyOctopus.y = 1500; // Middle of screen
friendlyOctopuses.push(friendlyOctopus);
game.addChild(friendlyOctopus);
// Show friendly octopus spawn message
var octopusMessage = new Text2('¡Pulpo Amigo se une a la batalla!', {
size: 70,
fill: 0x00ff88
});
octopusMessage.anchor.set(0.5, 0.5);
LK.gui.center.addChild(octopusMessage);
// Animate and remove message
tween(octopusMessage, {
y: -80,
alpha: 0
}, {
duration: 2500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (octopusMessage.parent) {
octopusMessage.destroy();
}
}
});
}
// Show ally spawn message
var allyMessage = new Text2('¡Nuevo Aliado Permanente! (' + permanentAllies.length + ' activos)', {
size: 70,
fill: 0x88ff44
});
allyMessage.anchor.set(0.5, 0.5);
LK.gui.center.addChild(allyMessage);
// Animate and remove message
tween(allyMessage, {
y: -80,
alpha: 0
}, {
duration: 2500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (allyMessage.parent) {
allyMessage.destroy();
}
}
});
// Show wave message
if (currentWave === 2) {
waveMessageTxt.setText('Segunda Oleada');
} else if (currentWave === 3) {
waveMessageTxt.setText('Tercera Oleada');
} else if (currentWave === 4) {
waveMessageTxt.setText('¡JEFE! - Oleada 4');
} else if (currentWave === 5) {
waveMessageTxt.setText('Quinta Oleada');
} else if (currentWave % 4 === 0) {
waveMessageTxt.setText('¡JEFE! - Oleada ' + currentWave);
} else {
waveMessageTxt.setText('Oleada ' + currentWave);
}
waveMessageTxt.visible = true;
waveMessageTimer = 180; // Show for 3 seconds
// Increase difficulty
shipSpawnDelay = Math.max(30, 120 - (currentWave - 1) * 15);
// Increase ship speeds
for (var i = 0; i < ships.length; i++) {
var ship = ships[i];
var speedMultiplier = 1 + (currentWave - 1) * 0.3;
ship.speed = ship.speed * speedMultiplier;
}
}
}
// Touch/click handlers
game.down = function (x, y, obj) {
if (!gameStarted) return;
cannon.aimAt(x, y);
isShooting = true;
shootingTimer = 0;
// Fire immediately on first click
var newCannonballs = cannon.fire();
if (newCannonballs) {
for (var c = 0; c < newCannonballs.length; c++) {
cannonballs.push(newCannonballs[c]);
game.addChild(newCannonballs[c]);
}
}
};
game.up = function (x, y, obj) {
if (!gameStarted) return;
isShooting = false;
};
// Play button event handler
playButtonImage.down = function (x, y, obj) {
if (!gameStarted && menuShown) {
// Start the game directly
gameStarted = true;
startMenu.visible = false;
// Change background to game background
oceanBackground.visible = false;
gameBackground.visible = true;
// Stop button animations
tween.stop(oceanTitleImage);
tween.stop(defenderTitleImage);
tween.stop(playButtonImage);
tween.stop(creatorNameImage);
// Stop menu music completely before starting game music
LK.stopMusic();
// Start game music immediately after stopping menu music
LK.playMusic('calmSeaMelody', {
loop: true
});
// Show game UI
cannon.visible = true;
cannonHealthBarBg.visible = true;
cannonHealthBar.visible = true;
healthText.visible = true;
scoreTxt.visible = true;
escapedShipsTxt.visible = true;
waveNumberTxt.visible = true;
// Spawn first permanent ally close to cannon when game starts
var firstAlly = new PermanentAlly();
firstAlly.x = cannon.x + 150; // Position close to cannon
firstAlly.y = cannon.y - 200; // Position above cannon
permanentAllies.push(firstAlly);
game.addChild(firstAlly);
}
};
// Sketch menu click handler to cycle through sketches and show menu
sketchMenu.down = function (x, y, obj) {
if (sketchShown && !menuShown) {
currentSketchIndex++;
if (currentSketchIndex < totalSketches) {
// Hide current sketch and show next one
if (currentSketchIndex === 1) {
sketchImage.visible = false;
sketchImage2.visible = true;
} else if (currentSketchIndex === 2) {
sketchImage2.visible = false;
sketchImage3.visible = true;
}
} else {
// All sketches shown, show the menu
menuShown = true;
sketchMenu.visible = false;
startMenu.visible = true;
// Change background to ocean background
gameBackground.visible = false;
oceanBackground.visible = true;
// Stop continue text animation
tween.stop(continueText);
// Stop sketch music completely before starting menu music
LK.stopMusic();
// Start menu music immediately after stopping sketch music
LK.playMusic('menuMusic', {
loop: true
});
// Start menu animations
pulseOceanTitle();
pulseDefenderTitle();
pulsePlayButton();
pulseCreatorName();
}
}
};
game.update = function () {
if (!gameStarted) return;
// Handle continuous shooting
if (isShooting) {
shootingTimer++;
var currentFireDelay = rapidFire ? cannon.fireDelay / 3 : cannon.fireDelay;
var fireDelayInTicks = currentFireDelay / 16.67; // Convert ms to ticks
if (shootingTimer >= fireDelayInTicks) {
var newCannonballs = cannon.fire();
if (newCannonballs) {
for (var c = 0; c < newCannonballs.length; c++) {
cannonballs.push(newCannonballs[c]);
game.addChild(newCannonballs[c]);
}
}
shootingTimer = 0;
}
}
updateDifficulty();
checkWaveTransition();
// Update wave message timer
if (waveMessageTimer > 0) {
waveMessageTimer--;
if (waveMessageTimer <= 0) {
waveMessageTxt.visible = false;
}
}
shipSpawnTimer++;
if (shipSpawnTimer >= shipSpawnDelay) {
spawnShip();
shipSpawnTimer = 0;
}
for (var i = ships.length - 1; i >= 0; i--) {
var ship = ships[i];
if (ship.lastX === undefined) ship.lastX = ship.x;
if (ship.lastX <= 2048 && ship.x > 2048) {
shipsEscaped++;
escapedShipsTxt.setText('Escaped: ' + shipsEscaped + '/10');
LK.getSound('shipEscape').play();
ship.destroy();
ships.splice(i, 1);
// Check if too many ships escaped
if (shipsEscaped >= maxEscapedShips) {
// Show final score and wave reached
var finalScore = LK.getScore();
var finalWave = currentWave;
LK.showGameOver('Puntaje: ' + finalScore + '\nOleada alcanzada: ' + finalWave);
return;
}
continue;
}
// Ships shoot at cannon and allies
if (ship.x > 200 && ship.x < 1800) {
// Only shoot when in range
var targetX = cannon.x;
var targetY = cannon.y;
// Check if there are permanent allies to target (50% chance to target ally instead of cannon)
if (permanentAllies.length > 0 && Math.random() < 0.5) {
// Find nearest permanent ally to shoot at
var nearestAlly = null;
var nearestDistance = Infinity;
for (var allyIndex = 0; allyIndex < permanentAllies.length; allyIndex++) {
var ally = permanentAllies[allyIndex];
if (ally && ally.parent && ally.health > 0) {
var dx = ally.x - ship.x;
var dy = ally.y - ship.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestAlly = ally;
}
}
}
// Target the nearest ally if found
if (nearestAlly) {
targetX = nearestAlly.x;
targetY = nearestAlly.y;
}
}
var newBullet = ship.shootAtCannon(targetX, targetY);
if (newBullet) {
enemyBullets.push(newBullet);
game.addChild(newBullet);
}
}
ship.lastX = ship.x;
}
for (var j = cannonballs.length - 1; j >= 0; j--) {
var cannonball = cannonballs[j];
if (cannonball.lastX === undefined) cannonball.lastX = cannonball.x;
if (cannonball.lastY === undefined) cannonball.lastY = cannonball.y;
if (cannonball.lastX >= 0 && cannonball.x < 0 || cannonball.lastX <= 2048 && cannonball.x > 2048 || cannonball.lastY >= 0 && cannonball.y < 0 || cannonball.lastY <= 2732 && cannonball.y > 2732) {
cannonball.destroy();
cannonballs.splice(j, 1);
continue;
}
// Auto-selection is now used - no upgrade button collision detection needed
for (var k = ships.length - 1; k >= 0; k--) {
var ship = ships[k];
// Ensure ship is valid and has proper collision detection, and not dying
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying && cannonball.intersects(ship)) {
ship.health -= cannonball.damage;
ship.updateHealthBar();
LK.effects.flashObject(ship, 0xff0000, 200);
LK.getSound('hit').play();
// Check if cannonball can bounce to another enemy
var bounced = false;
if (cannonball.canBounce()) {
var nearestEnemy = null;
var nearestDistance = Infinity;
// Find nearest enemy ship for bouncing (excluding current target)
for (var targetIndex = 0; targetIndex < ships.length; targetIndex++) {
var targetShip = ships[targetIndex];
if (targetShip && targetShip !== ship && targetShip.graphics && targetShip.graphics.visible && !targetShip.isDying) {
var dx = targetShip.x - cannonball.x;
var dy = targetShip.y - cannonball.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance && distance < 400) {
// Max bounce range
nearestDistance = distance;
nearestEnemy = targetShip;
}
}
}
// Bounce to nearest enemy if found
if (nearestEnemy) {
bounced = cannonball.bounceTowardsTarget(nearestEnemy.x, nearestEnemy.y);
}
}
if (!bounced) {
cannonball.destroy();
cannonballs.splice(j, 1);
}
if (ship.health <= 0) {
enemiesKilledThisWave++; // Track kills for wave completion
ship.isDying = true; // Mark as dying to prevent further damage
LK.setScore(LK.getScore() + ship.points);
scoreTxt.setText('Score: ' + LK.getScore());
LK.getSound('shipDestroyed').play();
// Add small jump death animation
var originalY = ship.y;
var deadShip = ship; // Store reference to avoid closure issues
tween(ship, {
y: originalY - 30,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(deadShip, {
y: originalY + 10,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 150,
easing: tween.bounceOut,
onFinish: function onFinish() {
// Random chance to spawn different power-ups (45% total chance)
var spawnChance = Math.random();
if (spawnChance < 0.15) {
// 15% chance for weapon power-ups
var powerUp = new PowerUp();
var powerTypes = ['tripleShot', 'rapidFire'];
var randomPowerType = powerTypes[Math.floor(Math.random() * powerTypes.length)];
powerUp.init(randomPowerType);
powerUp.x = deadShip.x;
powerUp.y = deadShip.y;
powerUps.push(powerUp);
game.addChild(powerUp);
// Add brilliant glowing animation to power-up
powerUp.graphics.tint = 0xffffff;
tween(powerUp.graphics, {
tint: 0xffff00,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(powerUp.graphics, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue pulsing glow
function continuePulse() {
if (powerUp.parent) {
tween(powerUp.graphics, {
tint: 0xffff00,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (powerUp.parent) {
tween(powerUp.graphics, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 600,
easing: tween.easeInOut,
onFinish: continuePulse
});
}
}
});
}
}
continuePulse();
}
});
}
});
} else if (spawnChance < 0.25) {
// 10% chance for health kit
var healthKit = new HealthKit();
healthKit.x = deadShip.x;
healthKit.y = deadShip.y;
healthKits.push(healthKit);
game.addChild(healthKit);
// Add brilliant glowing animation to health kit
healthKit.tint = 0xffffff;
tween(healthKit, {
tint: 0x00ff00,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(healthKit, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue pulsing glow
function continuePulse() {
if (healthKit.parent) {
tween(healthKit, {
tint: 0x00ff00,
scaleX: 1.15,
scaleY: 1.15
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (healthKit.parent) {
tween(healthKit, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: continuePulse
});
}
}
});
}
}
continuePulse();
}
});
}
});
} else if (spawnChance < 0.35) {
// 15% chance for guided shot
var guidedShotPowerUp = new GuidedShotPowerUp();
guidedShotPowerUp.x = deadShip.x;
guidedShotPowerUp.y = deadShip.y;
guidedShotPowerUps.push(guidedShotPowerUp);
game.addChild(guidedShotPowerUp);
// Add brilliant glowing animation to guided shot power-up
guidedShotPowerUp.tint = 0xffffff;
tween(guidedShotPowerUp, {
tint: 0xff6600,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(guidedShotPowerUp, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue pulsing glow
function continuePulse() {
if (guidedShotPowerUp.parent) {
tween(guidedShotPowerUp, {
tint: 0xff6600,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (guidedShotPowerUp.parent) {
tween(guidedShotPowerUp, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: continuePulse
});
}
}
});
}
}
continuePulse();
}
});
}
});
} else if (spawnChance < 0.40) {
// 5% chance for octopus power - same as other special powers
var octopusPowerUp = new OctopusPowerUp();
octopusPowerUp.x = deadShip.x;
octopusPowerUp.y = deadShip.y;
octopusPowerUps.push(octopusPowerUp);
game.addChild(octopusPowerUp);
// Add brilliant glowing animation
octopusPowerUp.tint = 0xffffff;
tween(octopusPowerUp, {
tint: 0x800080,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(octopusPowerUp, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
function continuePulse() {
if (octopusPowerUp.parent) {
tween(octopusPowerUp, {
tint: 0x800080,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (octopusPowerUp.parent) {
tween(octopusPowerUp, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: continuePulse
});
}
}
});
}
}
continuePulse();
}
});
}
});
} else if (spawnChance < 0.45) {
// 5% chance for shield
var shieldPowerUp = new ShieldPowerUp();
shieldPowerUp.x = deadShip.x;
shieldPowerUp.y = deadShip.y;
shieldPowerUps.push(shieldPowerUp);
game.addChild(shieldPowerUp);
// Add brilliant glowing animation to shield power-up
shieldPowerUp.tint = 0xffffff;
tween(shieldPowerUp, {
tint: 0x0099ff,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(shieldPowerUp, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Continue pulsing glow
function continuePulse() {
if (shieldPowerUp.parent) {
tween(shieldPowerUp, {
tint: 0x0099ff,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (shieldPowerUp.parent) {
tween(shieldPowerUp, {
tint: 0xffffff,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 700,
easing: tween.easeInOut,
onFinish: continuePulse
});
}
}
});
}
}
continuePulse();
}
});
}
});
}
if (deadShip.parent) {
deadShip.destroy();
}
// Remove from ships array by finding the ship
for (var removeIndex = ships.length - 1; removeIndex >= 0; removeIndex--) {
if (ships[removeIndex] === deadShip) {
ships.splice(removeIndex, 1);
break;
}
}
}
});
}
});
}
break;
}
}
if (cannonballs[j]) {
cannonball.lastX = cannonball.x;
cannonball.lastY = cannonball.y;
}
}
// Update power-ups
for (var p = powerUps.length - 1; p >= 0; p--) {
var powerUp = powerUps[p];
if (powerUp.lastX === undefined) powerUp.lastX = powerUp.x;
// Remove power-up if it goes off screen
if (powerUp.lastX <= 2048 && powerUp.x > 2048) {
powerUp.destroy();
powerUps.splice(p, 1);
continue;
}
// Check collision with cannonballs for collection
for (var cb = cannonballs.length - 1; cb >= 0; cb--) {
var cannonball = cannonballs[cb];
if (cannonball.intersects(powerUp)) {
// Collect power-up
LK.getSound('powerUpCollect').play();
LK.effects.flashScreen(0x00ff00, 300);
// Clear any existing power-up to prevent accumulation
tripleShot = false;
rapidFire = false;
activePowerUpTimer = 0; // Reset timer to prevent accumulation
// Activate new power-up
if (powerUp.powerType === 'tripleShot') {
tripleShot = true;
activePowerUpType = 'Triple Shot';
activePowerUpTimer = powerUpDuration;
} else if (powerUp.powerType === 'rapidFire') {
rapidFire = true;
activePowerUpType = 'Rapid Fire';
activePowerUpTimer = powerUpDuration;
}
powerUp.destroy();
powerUps.splice(p, 1);
break;
}
}
if (powerUps[p]) {
powerUp.lastX = powerUp.x;
}
}
// Update enemy bullets
for (var eb = enemyBullets.length - 1; eb >= 0; eb--) {
var enemyBullet = enemyBullets[eb];
if (enemyBullet.lastY === undefined) enemyBullet.lastY = enemyBullet.y;
// Remove if off screen
if (enemyBullet.lastY <= 2732 && enemyBullet.y > 2732) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
continue;
}
// Check collision with cannon first
if (enemyBullet.intersects(cannon)) {
cannon.takeDamage(enemyBullet.damage);
// Check if enemy bullet can bounce to allies
var bounced = false;
if (enemyBullet.canBounce()) {
var nearestAlly = null;
var nearestDistance = Infinity;
// Find nearest ally for bouncing
for (var allyIndex = 0; allyIndex < permanentAllies.length; allyIndex++) {
var ally = permanentAllies[allyIndex];
if (ally && ally.parent && ally.health > 0) {
var dx = ally.x - enemyBullet.x;
var dy = ally.y - enemyBullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance && distance < 400) {
// Max bounce range
nearestDistance = distance;
nearestAlly = ally;
}
}
}
// Also check friendly octopuses
for (var foIndex = 0; foIndex < friendlyOctopuses.length; foIndex++) {
var friendlyOct = friendlyOctopuses[foIndex];
if (friendlyOct && friendlyOct.parent && friendlyOct.health > 0) {
var dx = friendlyOct.x - enemyBullet.x;
var dy = friendlyOct.y - enemyBullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance && distance < 400) {
// Max bounce range
nearestDistance = distance;
nearestAlly = friendlyOct;
}
}
}
// Bounce to nearest ally if found
if (nearestAlly) {
bounced = enemyBullet.bounceTowardsTarget(nearestAlly.x, nearestAlly.y);
}
}
if (!bounced) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
}
// Update health UI
var healthPercent = cannon.health / cannon.maxHealth;
cannonHealthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
cannonHealthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
cannonHealthBar.tint = 0xFFFF00;
} else {
cannonHealthBar.tint = 0xFF0000;
}
healthText.setText('Health: ' + cannon.health + '/' + cannon.maxHealth);
// Check game over
if (cannon.health <= 0) {
// Show final score and wave reached
var finalScore = LK.getScore();
var finalWave = currentWave;
LK.showGameOver('Puntaje: ' + finalScore + '\nOleada alcanzada: ' + finalWave);
return;
}
continue;
}
// Check collision with permanent allies
var hitAlly = false;
for (var pa = 0; pa < permanentAllies.length; pa++) {
var permanentAlly = permanentAllies[pa];
if (permanentAlly && permanentAlly.parent && permanentAlly.health > 0 && enemyBullet.intersects(permanentAlly)) {
permanentAlly.takeDamage(enemyBullet.damage);
LK.getSound('hit').play();
// Check if enemy bullet can bounce to enemy ships
var bounced = false;
if (enemyBullet.canBounce()) {
var nearestEnemy = null;
var nearestDistance = Infinity;
// Find nearest enemy ship for bouncing
for (var shipIndex = 0; shipIndex < ships.length; shipIndex++) {
var targetShip = ships[shipIndex];
if (targetShip && targetShip.graphics && targetShip.graphics.visible && !targetShip.isDying) {
var dx = targetShip.x - enemyBullet.x;
var dy = targetShip.y - enemyBullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance && distance < 400) {
// Max bounce range
nearestDistance = distance;
nearestEnemy = targetShip;
}
}
}
// Bounce to nearest enemy if found
if (nearestEnemy) {
bounced = enemyBullet.bounceTowardsTarget(nearestEnemy.x, nearestEnemy.y);
}
}
if (!bounced) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
}
hitAlly = true;
break;
}
}
if (hitAlly) continue;
if (enemyBullets[eb]) {
enemyBullet.lastY = enemyBullet.y;
}
}
// Update health kits
for (var hk = healthKits.length - 1; hk >= 0; hk--) {
var healthKit = healthKits[hk];
if (healthKit.lastX === undefined) healthKit.lastX = healthKit.x;
// Remove if off screen
if (healthKit.lastX <= 2048 && healthKit.x > 2048) {
healthKit.destroy();
healthKits.splice(hk, 1);
continue;
}
// Check collection by cannonball
for (var cb = cannonballs.length - 1; cb >= 0; cb--) {
var cannonball = cannonballs[cb];
if (cannonball.intersects(healthKit)) {
cannon.heal(healthKit.healAmount);
LK.getSound('powerUpCollect').play();
LK.effects.flashScreen(0x00ff00, 200);
// Update health UI
var healthPercent = cannon.health / cannon.maxHealth;
cannonHealthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
cannonHealthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
cannonHealthBar.tint = 0xFFFF00;
} else {
cannonHealthBar.tint = 0xFF0000;
}
healthText.setText('Health: ' + cannon.health + '/' + cannon.maxHealth);
healthKit.destroy();
healthKits.splice(hk, 1);
break;
}
}
// Check collection by guided projectiles
for (var gp = guidedProjectiles.length - 1; gp >= 0; gp--) {
var guidedProjectile = guidedProjectiles[gp];
if (guidedProjectile.intersects(healthKit)) {
cannon.heal(healthKit.healAmount);
LK.getSound('powerUpCollect').play();
LK.effects.flashScreen(0x00ff00, 200);
// Update health UI
var healthPercent = cannon.health / cannon.maxHealth;
cannonHealthBar.scaleX = healthPercent;
if (healthPercent > 0.6) {
cannonHealthBar.tint = 0x00FF00;
} else if (healthPercent > 0.3) {
cannonHealthBar.tint = 0xFFFF00;
} else {
cannonHealthBar.tint = 0xFF0000;
}
healthText.setText('Health: ' + cannon.health + '/' + cannon.maxHealth);
healthKit.destroy();
healthKits.splice(hk, 1);
break;
}
}
if (healthKits[hk]) {
healthKit.lastX = healthKit.x;
}
}
// Update shield power-ups
for (var sp = shieldPowerUps.length - 1; sp >= 0; sp--) {
var shieldPowerUp = shieldPowerUps[sp];
if (shieldPowerUp.lastX === undefined) shieldPowerUp.lastX = shieldPowerUp.x;
// Remove if off screen
if (shieldPowerUp.lastX <= 2048 && shieldPowerUp.x > 2048) {
shieldPowerUp.destroy();
shieldPowerUps.splice(sp, 1);
continue;
}
// Check collection by cannonball
for (var cb = cannonballs.length - 1; cb >= 0; cb--) {
var cannonball = cannonballs[cb];
if (cannonball.intersects(shieldPowerUp)) {
cannon.activateShield();
// Share shield power with all allies
for (var allyIndex = 0; allyIndex < permanentAllies.length; allyIndex++) {
var ally = permanentAllies[allyIndex];
if (ally && ally.parent && ally.health > 0) {
ally.activateShield();
}
}
for (var foIndex = 0; foIndex < friendlyOctopuses.length; foIndex++) {
var friendlyOct = friendlyOctopuses[foIndex];
if (friendlyOct && friendlyOct.parent && friendlyOct.health > 0) {
friendlyOct.activateShield();
}
}
LK.getSound('powerUpCollect').play();
LK.effects.flashScreen(0x0099ff, 300);
// Add brilliant glowing animation to cannon when shield is collected
tween(cannon.graphics, {
tint: 0x88ccff,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(cannon.graphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
shieldPowerUp.destroy();
shieldPowerUps.splice(sp, 1);
break;
}
}
if (shieldPowerUps[sp]) {
shieldPowerUp.lastX = shieldPowerUp.x;
}
}
// Update guided shot power-ups
for (var gsp = guidedShotPowerUps.length - 1; gsp >= 0; gsp--) {
var guidedShotPowerUp = guidedShotPowerUps[gsp];
if (guidedShotPowerUp.lastX === undefined) guidedShotPowerUp.lastX = guidedShotPowerUp.x;
// Remove if off screen
if (guidedShotPowerUp.lastX <= 2048 && guidedShotPowerUp.x > 2048) {
guidedShotPowerUp.destroy();
guidedShotPowerUps.splice(gsp, 1);
continue;
}
// Check collection by cannonball
for (var cb = cannonballs.length - 1; cb >= 0; cb--) {
var cannonball = cannonballs[cb];
if (cannonball.intersects(guidedShotPowerUp)) {
// Clear existing power-ups
tripleShot = false;
rapidFire = false;
guidedShot = true;
activePowerUpType = 'Guided Shot';
activePowerUpTimer = guidedShotDuration;
LK.getSound('powerUpCollect').play();
LK.effects.flashScreen(0xff6600, 300);
guidedShotPowerUp.destroy();
guidedShotPowerUps.splice(gsp, 1);
break;
}
}
if (guidedShotPowerUps[gsp]) {
guidedShotPowerUp.lastX = guidedShotPowerUp.x;
}
}
// Update octopus power-ups
for (var op = octopusPowerUps.length - 1; op >= 0; op--) {
var octopusPowerUp = octopusPowerUps[op];
if (octopusPowerUp.lastX === undefined) octopusPowerUp.lastX = octopusPowerUp.x;
// Remove if off screen
if (octopusPowerUp.lastX <= 2048 && octopusPowerUp.x > 2048) {
octopusPowerUp.destroy();
octopusPowerUps.splice(op, 1);
continue;
}
// Check collection by cannonball
for (var cb = cannonballs.length - 1; cb >= 0; cb--) {
var cannonball = cannonballs[cb];
if (cannonball.intersects(octopusPowerUp)) {
// Determine number of octopus arms to spawn based on active powers
var armsToSpawn = 1;
if (tripleShot) {
armsToSpawn = 3; // Triple shot multiplies octopus arms
}
// Spawn multiple octopus arms if triple shot is active
for (var armIndex = 0; armIndex < armsToSpawn; armIndex++) {
var octopusArm = new OctopusArm();
octopusArm.init();
// Apply rapid fire speed boost if active
if (rapidFire) {
octopusArm.speed = octopusArm.speed * 1.5; // 50% faster movement
}
octopuses.push(octopusArm);
game.addChild(octopusArm);
}
// Destroy the cannonball that collected the power-up
cannonball.destroy();
cannonballs.splice(cb, 1);
LK.getSound('powerUpCollect').play();
LK.effects.flashScreen(0x800080, 300);
// Show power activation message with count
var activeOctopusCount = octopuses.length;
var armText = armsToSpawn > 1 ? '¡' + armsToSpawn + ' Brazos de Pulpo Invocados!' : '¡Brazo de Pulpo Invocado!';
var powerMessage = new Text2(armText + ' (' + activeOctopusCount + ' activos)', {
size: 80,
fill: 0x800080
});
powerMessage.anchor.set(0.5, 0.5);
LK.gui.center.addChild(powerMessage);
// Animate and remove message
tween(powerMessage, {
y: -100,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (powerMessage.parent) {
powerMessage.destroy();
}
}
});
octopusPowerUp.destroy();
octopusPowerUps.splice(op, 1);
break;
}
}
if (octopusPowerUps[op]) {
octopusPowerUp.lastX = octopusPowerUp.x;
}
}
// Update friendly octopuses
for (var fo = friendlyOctopuses.length - 1; fo >= 0; fo--) {
var friendlyOctopus = friendlyOctopuses[fo];
// Remove if friendly octopus is destroyed or dead
if (!friendlyOctopus.parent || friendlyOctopus.health <= 0) {
if (friendlyOctopus.parent) {
// Death animation
tween(friendlyOctopus, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
y: friendlyOctopus.y + 50
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (friendlyOctopus.parent) {
friendlyOctopus.destroy();
}
}
});
}
friendlyOctopuses.splice(fo, 1);
}
}
// Update permanent allies
for (var pa = permanentAllies.length - 1; pa >= 0; pa--) {
var permanentAlly = permanentAllies[pa];
// Remove if permanent ally is destroyed or dead
if (!permanentAlly.parent || permanentAlly.health <= 0) {
if (permanentAlly.parent) {
// Death animation
tween(permanentAlly, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5,
y: permanentAlly.y + 50
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (permanentAlly.parent) {
permanentAlly.destroy();
}
}
});
}
permanentAllies.splice(pa, 1);
}
}
// Check enemy bullet collision with friendly octopuses and permanent allies
for (var eb = enemyBullets.length - 1; eb >= 0; eb--) {
var enemyBullet = enemyBullets[eb];
var bulletHit = false;
// Check collision with friendly octopuses
for (var fo = 0; fo < friendlyOctopuses.length; fo++) {
var friendlyOctopus = friendlyOctopuses[fo];
if (friendlyOctopus && friendlyOctopus.parent && enemyBullet.intersects(friendlyOctopus)) {
friendlyOctopus.takeDamage(enemyBullet.damage);
LK.getSound('hit').play();
// Check if enemy bullet can bounce to enemy ships
var bounced = false;
if (enemyBullet.canBounce()) {
var nearestEnemy = null;
var nearestDistance = Infinity;
// Find nearest enemy ship for bouncing
for (var shipIndex = 0; shipIndex < ships.length; shipIndex++) {
var targetShip = ships[shipIndex];
if (targetShip && targetShip.graphics && targetShip.graphics.visible && !targetShip.isDying) {
var dx = targetShip.x - enemyBullet.x;
var dy = targetShip.y - enemyBullet.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance && distance < 400) {
// Max bounce range
nearestDistance = distance;
nearestEnemy = targetShip;
}
}
}
// Bounce to nearest enemy if found
if (nearestEnemy) {
bounced = enemyBullet.bounceTowardsTarget(nearestEnemy.x, nearestEnemy.y);
}
}
if (!bounced) {
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
}
bulletHit = true;
break;
}
}
// Check collision with permanent allies if bullet hasn't hit yet
if (!bulletHit) {
for (var pa = 0; pa < permanentAllies.length; pa++) {
var permanentAlly = permanentAllies[pa];
if (permanentAlly && permanentAlly.parent && enemyBullet.intersects(permanentAlly)) {
permanentAlly.takeDamage(enemyBullet.damage);
enemyBullet.destroy();
enemyBullets.splice(eb, 1);
LK.getSound('hit').play();
break;
}
}
}
}
// Update octopuses
for (var oc = octopuses.length - 1; oc >= 0; oc--) {
var octopus = octopuses[oc];
// Remove if octopus is destroyed or off screen
if (!octopus.parent || octopus.x > 2300 || octopus.y > 2900) {
if (octopus.parent) {
octopus.destroy();
}
octopuses.splice(oc, 1);
}
}
// Update guided projectiles
for (var gp = guidedProjectiles.length - 1; gp >= 0; gp--) {
var guidedProjectile = guidedProjectiles[gp];
// Check if projectile has exceeded its lifetime (4 seconds)
if (LK.ticks - guidedProjectile.creationTime >= guidedProjectile.lifetime) {
guidedProjectile.destroy();
guidedProjectiles.splice(gp, 1);
continue;
}
// Check collision with enemy ships
var hitShip = false;
for (var k = ships.length - 1; k >= 0; k--) {
var ship = ships[k];
// Ensure ship is valid and has proper collision detection, and not dying
if (ship && ship.graphics && ship.graphics.visible && !ship.isDying && guidedProjectile.intersects(ship)) {
ship.health -= guidedProjectile.damage;
ship.updateHealthBar();
LK.effects.flashObject(ship, 0xff0000, 200);
LK.getSound('hit').play();
guidedProjectile.destroy();
guidedProjectiles.splice(gp, 1);
hitShip = true;
if (ship.health <= 0) {
enemiesKilledThisWave++; // Track kills for wave completion
ship.isDying = true; // Mark as dying to prevent further damage
LK.setScore(LK.getScore() + ship.points);
scoreTxt.setText('Score: ' + LK.getScore());
LK.getSound('shipDestroyed').play();
// Add small jump death animation
var originalY = ship.y;
var deadShip = ship; // Store reference to avoid closure issues
tween(ship, {
y: originalY - 30,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(deadShip, {
y: originalY + 10,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 150,
easing: tween.bounceOut,
onFinish: function onFinish() {
if (deadShip.parent) {
deadShip.destroy();
}
// Remove from ships array by finding the ship
for (var removeIndex = ships.length - 1; removeIndex >= 0; removeIndex--) {
if (ships[removeIndex] === deadShip) {
ships.splice(removeIndex, 1);
break;
}
}
}
});
}
});
}
break;
}
}
if (hitShip) continue;
// Remove if too far off screen
if (guidedProjectile.x < -200 || guidedProjectile.x > 2248 || guidedProjectile.y < -200 || guidedProjectile.y > 2932) {
guidedProjectile.destroy();
guidedProjectiles.splice(gp, 1);
}
}
// Check collision between cannonballs and shield effect
if (cannon.isShielded && shieldEffect.visible) {
for (var cb = cannonballs.length - 1; cb >= 0; cb--) {
var cannonball = cannonballs[cb];
if (cannonball.intersects(shieldEffect)) {
// Simply destroy the cannonball when it hits the shield - no infinite shield activation
cannonball.destroy();
cannonballs.splice(cb, 1);
// Visual feedback only
LK.effects.flashObject(shieldEffect, 0x88ccff, 200);
break;
}
}
}
// Update cannon shield
if (cannon.isShielded && cannon.shieldTimer > 0) {
cannon.shieldTimer--;
// Show shield effect when active
if (!shieldEffect.visible) {
shieldEffect.visible = true;
shieldEffect.x = cannon.x;
shieldEffect.y = cannon.y;
}
if (cannon.shieldTimer <= 0) {
cannon.isShielded = false;
shieldEffect.visible = false;
tween(cannon.graphics, {
tint: 0xffffff
}, {
duration: 500
});
}
} else {
shieldEffect.visible = false;
}
// Update power-up timer
if (activePowerUpTimer > 0) {
activePowerUpTimer--;
// Update timer display
var timeLeft = Math.ceil(activePowerUpTimer / 60);
var timerText = activePowerUpType + ': ' + timeLeft + 's';
if (cannon.isShielded) {
var shieldTimeLeft = Math.ceil(cannon.shieldTimer / 60);
timerText += ' | Shield: ' + shieldTimeLeft + 's';
}
powerUpTimerTxt.setText(timerText);
// Check if power-up expired
if (activePowerUpTimer <= 0) {
tripleShot = false;
rapidFire = false;
guidedShot = false;
activePowerUpType = '';
if (cannon.isShielded) {
var shieldTimeLeft = Math.ceil(cannon.shieldTimer / 60);
powerUpTimerTxt.setText('Shield: ' + shieldTimeLeft + 's');
} else {
powerUpTimerTxt.setText('');
}
}
} else {
// No active weapon power-up, but maybe shield
if (cannon.isShielded) {
var shieldTimeLeft = Math.ceil(cannon.shieldTimer / 60);
powerUpTimerTxt.setText('Shield: ' + shieldTimeLeft + 's');
} else {
powerUpTimerTxt.setText('');
}
}
// Check for wave completion at end of update loop
checkWaveCompletion();
};
// Duplicate activateShield methods removed - method already exists in PermanentAlly class
The small warship is fast and agile, designed for quick attacks and evading enemy fire. It has light armor and carries a few small cannons. Its compact size makes it harder to hit but less durable.. In-Game asset. 2d. High contrast. No shadows
The medium warship balances speed and firepower. It is equipped with multiple cannons and moderate armor. It moves steadily and can withstand more hits than smaller ships, making it a tougher target.. In-Game asset. 2d. High contrast. No shadows
boton de play pero que sea con tematica oceanica y que tenga una cocha al lado. In-Game asset. 2d. High contrast. No shadows
un barco gigante de guerra. In-Game asset. 2d. High contrast. No shadows
escudo celestial. In-Game asset. 2d. High contrast. No shadows
mas velocidad pero escrito en ingles. In-Game asset. 2d. High contrast. No shadows
mas vida pero escrito en ingles. In-Game asset. 2d. High contrast. No shadows
mas daño pero escrito en ingles. In-Game asset. 2d. High contrast. No shadows
un mar con un fondo a lo lejos. In-Game asset. 2d. High contrast. No shadows
unas letras que digan : creado por ZURI HARDAWAY. In-Game asset. 2d. High contrast. No shadows
que un pulpo este sentado en un cañon. In-Game asset. 2d. High contrast. No shadows
crea un titulo que diga oceanic defence con un pulplo detras de las letras que esta sentado en un cañon negro. In-Game asset. 2d. High contrast. No shadows
crea una imagen de un barco estallando. In-Game asset. 2d. High contrast. No shadows
un pulpo naranja viendo que barcos de guerra vienen a atacar su hogar marino. In-Game asset. 2d. High contrast. No shadows
una sola viñeta en blanco de comic de manera rectangular horizontal en el centro de la pantalla, que el back ground este adornado con un fondo marino y brazos de pulpos. In-Game asset. 2d. High contrast. No shadows, que los tentaculos no toquen la viñeta q
un pulpo naranja abriendo un cofre del tesoro donde encuentra en el fondo marino dentro de ese cofre un cañon nergro donde. In-Game asset. 2d. High contrast. No shadows
un pulpo naranja frenetico disparandole a buques de guerras encima de un cañon negro. que se vea el fondo del cielo y el mar pero que no este enojado sino calmado
bala de cañon. In-Game asset. 2d. High contrast. No shadows
bala de cañon. In-Game asset. 2d. High contrast. No shadows
ojo de pulpo. In-Game asset. 2d. High contrast. No shadows
botiquin con un pulpo rodeandolo. In-Game asset. 2d. High contrast. No shadows
una esfera con una letra r dentro y un pulpo rodeando la esfera. In-Game asset. 2d. High contrast. No shadows
una letra t dentro de una esfera y un pulpo que la rodea. In-Game asset. 2d. High contrast. No shadows
una p dentro de una esfera con un brazo de un pulpo rojo rodeandola. In-Game asset. 2d. High contrast. No shadows
guerra de pulpos con cañones, que sean de diferentes colores los pulpos de diferentes, tipo dibujo contra buques de guerra