/**** * Plugins ****/ var tween = LK.import("@upit/tween.v1"); var storage = LK.import("@upit/storage.v1", { highscore: 0, totalGamesPlayed: 0 }); /**** * Classes ****/ var Arrow = Container.expand(function () { var self = Container.call(this); // Create laser beam instead of arrow self.arrowBody = self.attachAsset('arrow', { anchorX: 0, anchorY: 0.5, scaleX: 2.0, scaleY: 0.5, tint: 0x33CCFF // Blue laser color }); self.arrowHead = self.attachAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, x: 80, scaleX: 0.6, scaleY: 0.6, tint: 0xFFFFFF // Bright white laser impact point }); // Add laser glow effect self.glow = LK.getAsset('arrow', { anchorX: 0, anchorY: 0.5, scaleX: 2.2, scaleY: 1.8, tint: 0x33CCFF, alpha: 0.5 }); self.addChildAt(self.glow, 0); self.speed = 20; // Faster for lasers self.damage = 10; self.pierce = 0; self.pierceCount = 0; self.hitEnemies = []; self.specialType = null; // To store special ability type (poison, explosive, etc.) self.specialData = {}; // For any special ability-specific data self.update = function () { // Handle homing behavior if enabled if (self.homingEnabled && monsters.length > 0) { // Initialize the homing start time if it's not set if (!self.homingStartTime) { self.homingStartTime = Date.now(); } // Check if the homing arrow has existed for more than 5 seconds if (Date.now() - self.homingStartTime > 5000) { self.needsRemoval = true; return; } // Find nearest monster that hasn't been hit yet var closestDist = Infinity; var closestMonster = null; for (var i = 0; i < monsters.length; i++) { if (self.hitEnemies.indexOf(monsters[i].id) === -1) { var dx = monsters[i].x - self.x; var dy = monsters[i].y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < closestDist) { closestDist = dist; closestMonster = monsters[i]; } } } // If we found a target, gradually adjust arrow direction if (closestMonster) { var targetAngle = Math.atan2(closestMonster.y - self.y, closestMonster.x - self.x); // Normalize angles while (targetAngle < -Math.PI) targetAngle += Math.PI * 2; while (targetAngle > Math.PI) targetAngle -= Math.PI * 2; while (self.rotation < -Math.PI) self.rotation += Math.PI * 2; while (self.rotation > Math.PI) self.rotation -= Math.PI * 2; // Find shortest direction to rotate var diff = targetAngle - self.rotation; if (diff > Math.PI) diff -= Math.PI * 2; if (diff < -Math.PI) diff += Math.PI * 2; // Adjust rotation gradually self.rotation += diff * self.homingSpeed; // Create trail effect for homing arrows - reduced frequency if (LK.ticks % 6 === 0) { var trail = new Container(); var trailParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, tint: 0x33CCFF, alpha: 0.7 }); trail.x = self.x; trail.y = self.y; trail.addChild(trailParticle); game.addChild(trail); // Remove additional particles to improve performance // Fade out trail tween(trailParticle, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 200, onFinish: function onFinish() { if (trail.parent) { trail.parent.removeChild(trail); } } }); } } } // Move arrow in current direction self.x += Math.cos(self.rotation) * self.speed; self.y += Math.sin(self.rotation) * self.speed; // Standard arrow removal when off screen if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) { self.needsRemoval = true; } }; self.canHit = function (monster) { if (self.hitEnemies.indexOf(monster.id) !== -1) { return false; } return true; }; self.hit = function (monster) { self.hitEnemies.push(monster.id); self.pierceCount++; // Create laser impact effect var impact = new Container(); var impactFlash = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2, tint: 0xFFFFFF, alpha: 0.9 }); impact.x = monster.x; impact.y = monster.y; impact.addChild(impactFlash); game.addChild(impact); // Reduce particle count for better performance for (var i = 0; i < 3; i++) { var angle = Math.PI * 2 * Math.random(); var energyParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0x99FFFF, alpha: 0.8 }); energyParticle.x = Math.cos(angle) * 5; energyParticle.y = Math.sin(angle) * 5; impact.addChild(energyParticle); // Animate particle spread tween(energyParticle, { x: energyParticle.x * 8, y: energyParticle.y * 8, alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 200 }); } // Animate impact flash tween(impactFlash, { scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 200, onFinish: function onFinish() { if (impact.parent) { impact.parent.removeChild(impact); } } }); // Apply special effects if (self.specialType === "poison") { // Apply poison effect to monster if (!monster.poisoned) { monster.poisoned = true; monster.poisonDamage = self.damage * 0.2; // 20% of arrow damage per tick monster.poisonTicks = 5; // Duration in ticks monster.body.tint = 0x00FF00; // Green tint for poisoned monsters // Create poison cloud visual effect var poisonCloud = new Container(); var cloudCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5, tint: 0x00FF00, alpha: 0.4 }); poisonCloud.x = monster.x; poisonCloud.y = monster.y; game.addChild(poisonCloud); poisonCloud.addChild(cloudCircle); // Animate the poison cloud tween(cloudCircle, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 1000, onFinish: function onFinish() { poisonCloud.parent.removeChild(poisonCloud); } }); } } else if (self.specialType === "explosive") { // Create explosion effect and damage nearby monsters LK.effects.flashObject(self, 0xFF9900, 200); // Create explosion visual effect var explosion = new Container(); var explosionCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3, tint: 0xFF9900, alpha: 0.7 }); explosionCircle.x = self.x; explosionCircle.y = self.y; game.addChild(explosion); explosion.addChild(explosionCircle); // Animate the explosion tween(explosionCircle, { scaleX: 5, scaleY: 5, alpha: 0 }, { duration: 500, onFinish: function onFinish() { explosion.parent.removeChild(explosion); } }); // Find and damage nearby monsters var now = Date.now(); for (var i = 0; i < monsters.length; i++) { var otherMonster = monsters[i]; if (otherMonster && otherMonster.id !== monster.id) { var dx = otherMonster.x - self.x; var dy = otherMonster.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 150) { // Explosion radius otherMonster.takeDamage(self.damage * 0.5); // 50% damage to nearby monsters } } } } else if (self.specialType === "freeze") { // Slow down monster monster.frozen = true; monster.originalSpeed = monster.speed; monster.speed = monster.speed * 0.5; // 50% slower monster.body.tint = 0x00FFFF; // Cyan tint for frozen monsters // Create ice/frost visual effect var iceEffect = new Container(); var frostCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.3, scaleY: 1.3, tint: 0x00FFFF, alpha: 0.5 }); iceEffect.x = monster.x; iceEffect.y = monster.y; game.addChild(iceEffect); iceEffect.addChild(frostCircle); // Add ice crystal particles for (var i = 0; i < 6; i++) { var iceParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: 0xCCFFFF }); iceParticle.x = Math.random() * 40 - 20; iceParticle.y = Math.random() * 40 - 20; iceEffect.addChild(iceParticle); } // Animate the ice effect tween(frostCircle, { alpha: 0.1 }, { duration: 2800, onFinish: function onFinish() { if (iceEffect.parent) { iceEffect.parent.removeChild(iceEffect); } } }); // Reset speed after 3 seconds LK.setTimeout(function () { if (monster && monster.frozen) { monster.frozen = false; monster.speed = monster.originalSpeed; monster.body.tint = 0xFFFFFF; // Reset tint } }, 3000); } else if (self.specialType === "fire") { // Apply burning effect to monster if (!monster.burning) { monster.burning = true; monster.burnDamage = self.damage * 0.3; // 30% of arrow damage per tick monster.burnTicks = 3; // Duration in ticks monster.body.tint = 0xFF3300; // Orange-red tint for burning monsters // Create fire visual effect var fireEffect = new Container(); var flameCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2, tint: 0xFF3300, alpha: 0.7 }); fireEffect.x = monster.x; fireEffect.y = monster.y; game.addChild(fireEffect); fireEffect.addChild(flameCircle); // Animate the flames with flickering effect var fadeTime = 800; var startTime = Date.now(); var flameAnimation = LK.setInterval(function () { var elapsed = Date.now() - startTime; if (elapsed >= fadeTime) { LK.clearInterval(flameAnimation); if (fireEffect.parent) { fireEffect.parent.removeChild(fireEffect); } return; } // Make flames flicker by varying the alpha flameCircle.alpha = 0.7 * (1 - elapsed / fadeTime) * (0.7 + Math.random() * 0.3); // Move flame with monster if (monster && !monster.destroyed) { fireEffect.x = monster.x; fireEffect.y = monster.y; } }, 60); } } else if (self.specialType === "lightning") { // Chain lightning effect to nearby monsters LK.effects.flashObject(self, 0xFFFF00, 200); var chainedMonsters = []; chainedMonsters.push(monster.id); // Chain to up to 3 nearby monsters for (var i = 0; i < monsters.length && chainedMonsters.length < 4; i++) { var otherMonster = monsters[i]; if (otherMonster && chainedMonsters.indexOf(otherMonster.id) === -1) { var dx = otherMonster.x - monster.x; var dy = otherMonster.y - monster.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist < 200) { otherMonster.takeDamage(self.damage * 0.4); otherMonster.body.tint = 0xFFFF00; // Yellow tint for lightning chainedMonsters.push(otherMonster.id); // Create lightning effect between monsters var lightningStart = { x: monster.x, y: monster.y }; var lightningEnd = { x: otherMonster.x, y: otherMonster.y }; // Create lightning bolt visual effect var bolt = new Container(); var segment = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: dist / 50, scaleY: 0.6, tint: 0xFFFF00 }); // Position and rotate the segment to connect the two points segment.x = (lightningStart.x + lightningEnd.x) / 2; segment.y = (lightningStart.y + lightningEnd.y) / 2; segment.rotation = Math.atan2(lightningEnd.y - lightningStart.y, lightningEnd.x - lightningStart.x); bolt.addChild(segment); game.addChild(bolt); // Animate the lightning bolt tween(segment, { alpha: 0.2 }, { duration: 300, onFinish: function onFinish() { if (bolt.parent) { bolt.parent.removeChild(bolt); } } }); LK.setTimeout(function () { if (otherMonster && otherMonster.body) { otherMonster.body.tint = 0xFFFFFF; } }, 300); } } } } else if (self.specialType === "vampiric") { // Heal player based on damage done if (player) { var healAmount = self.damage * 0.3; // Heal for 30% of damage dealt player.health = Math.min(player.health + healAmount, player.maxHealth); LK.effects.flashObject(player, 0xFF00FF, 200); // Purple flash for healing // Create vampiric drain visual effect var vampiricEffect = new Container(); var startPoint = { x: monster.x, y: monster.y }; var endPoint = { x: player.x, y: player.y }; var distance = Math.sqrt(Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2)); var angleRad = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x); // Create the drain beam var drainBeam = LK.getAsset('arrow', { anchorX: 0, anchorY: 0.5, scaleX: distance / 50, scaleY: 0.4, tint: 0xFF00FF, alpha: 0.7 }); drainBeam.x = startPoint.x; drainBeam.y = startPoint.y; drainBeam.rotation = angleRad; vampiricEffect.addChild(drainBeam); game.addChild(vampiricEffect); // Add life particles traveling along the beam for (var i = 0; i < 3; i++) { var lifeParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7, tint: 0xFF00FF, alpha: 0.8 }); lifeParticle.x = startPoint.x; lifeParticle.y = startPoint.y; vampiricEffect.addChild(lifeParticle); // Animate particles moving from monster to player tween(lifeParticle, { x: endPoint.x, y: endPoint.y, alpha: 0.3 }, { duration: 500 + i * 100, easing: tween.easeOut }); } // Fade out the beam tween(drainBeam, { alpha: 0 }, { duration: 700, onFinish: function onFinish() { if (vampiricEffect.parent) { vampiricEffect.parent.removeChild(vampiricEffect); } } }); } // No rebounding arrows functionality if (self.pierceCount > self.pierce) { self.needsRemoval = true; } } else if (self.specialType === "homing") { // Create homing visual effect var homingEffect = new Container(); var targetMarker = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2, tint: 0xFF6600, alpha: 0.7 }); homingEffect.x = monster.x; homingEffect.y = monster.y; game.addChild(homingEffect); homingEffect.addChild(targetMarker); // Animate the homing effect tween(targetMarker, { scaleX: 2, scaleY: 2, alpha: 0 }, { duration: 400, onFinish: function onFinish() { if (homingEffect.parent) { homingEffect.parent.removeChild(homingEffect); } } }); // Set arrow to homing mode if (!self.homingEnabled && monsters.length > 0) { self.homingEnabled = true; self.homingTarget = null; // Will be set in update method self.homingSpeed = 0.1; // Turning rate LK.effects.flashObject(self, 0xFF6600, 200); } } if (self.pierceCount > self.pierce && !self.hasRebounded && !self.homingEnabled) { self.needsRemoval = true; } }; return self; }); var Boss = Container.expand(function () { var self = Container.call(this); self.id = Date.now() + Math.floor(Math.random() * 10000); // Create larger body for boss monsters self.body = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3 }); // Health bar for boss self.healthBarBg = self.attachAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5, y: -100, scaleX: 1.5, scaleY: 1 }); self.healthBar = self.attachAsset('healthBar', { anchorX: 0, anchorY: 0.5, y: -100, x: -150, scaleX: 1.5 }); // Stats will be set when spawned self.maxHealth = 200; self.health = self.maxHealth; self.speed = 1; self.damage = 20; self.lastAttackTime = 0; self.attackCooldown = 1500; self.bossLevel = 1; // Will be set based on wave // Special abilities flags self.canSpawnMinions = false; self.lastMinionSpawnTime = 0; self.minionSpawnCooldown = 5000; self.shieldActive = false; self.update = function () { if (player && !paused) { var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 10) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } var now = Date.now(); // Attack player if (dist < 150) { // Larger attack range if (now - self.lastAttackTime > self.attackCooldown) { self.lastAttackTime = now; // Create attack effect var attackEffect = new Container(); var attackCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 2, tint: 0xFF0000, alpha: 0.5 }); attackEffect.x = self.x; attackEffect.y = self.y; game.addChild(attackEffect); attackEffect.addChild(attackCircle); // Animate attack tween(attackCircle, { scaleX: 4, scaleY: 4, alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (attackEffect.parent) { attackEffect.parent.removeChild(attackEffect); } } }); if (player.takeDamage(self.damage)) { LK.getSound('death').play(); gameOver(); } } } // Spawn minions ability if (self.canSpawnMinions && now - self.lastMinionSpawnTime > self.minionSpawnCooldown) { self.lastMinionSpawnTime = now; // Create spawn effect var spawnEffect = new Container(); var spawnCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5, tint: 0x9900FF, alpha: 0.7 }); spawnEffect.x = self.x; spawnEffect.y = self.y; game.addChild(spawnEffect); spawnEffect.addChild(spawnCircle); // Animate spawn effect tween(spawnCircle, { scaleX: 3, scaleY: 3, alpha: 0 }, { duration: 700, onFinish: function onFinish() { if (spawnEffect.parent) { spawnEffect.parent.removeChild(spawnEffect); } } }); // Spawn 2-3 minions around boss var minionCount = 2 + Math.floor(Math.random()); for (var i = 0; i < minionCount; i++) { var angle = Math.PI * 2 * (i / minionCount); var monster = new Monster(); monster.maxHealth = 15 + self.bossLevel * 2; monster.health = monster.maxHealth; monster.speed = 1.5 + self.bossLevel * 0.1; monster.body.tint = 0x9900FF; // Mark as boss minions monster.x = self.x + Math.cos(angle) * 150; monster.y = self.y + Math.sin(angle) * 150; monsters.push(monster); game.addChild(monster); } } // Update health bar var healthPercent = self.health / self.maxHealth; self.healthBar.width = 300 * healthPercent; // Handle poison damage over time if (self.poisoned && self.poisonTicks > 0) { if (!self.lastPoisonTime || now - self.lastPoisonTime > 500) { // Every 0.5 seconds self.lastPoisonTime = now; self.poisonTicks--; self.takeDamage(self.poisonDamage); // Create poison effect visuals var poisonEffect = new Container(); var poisonParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: 0x00FF00, alpha: 0.7 }); poisonEffect.x = self.x + (Math.random() * 80 - 40); poisonEffect.y = self.y + (Math.random() * 80 - 40); poisonEffect.addChild(poisonParticle); game.addChild(poisonEffect); // Animate poison particles tween(poisonParticle, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 500, onFinish: function onFinish() { if (poisonEffect.parent) { poisonEffect.parent.removeChild(poisonEffect); } } }); // Remove poison effect when done if (self.poisonTicks <= 0) { self.poisoned = false; self.body.tint = 0xFFFFFF; // Reset tint } } } // Handle burning damage over time if (self.burning && self.burnTicks > 0) { if (!self.lastBurnTime || now - self.lastBurnTime > 400) { // Every 0.4 seconds self.lastBurnTime = now; self.burnTicks--; self.takeDamage(self.burnDamage); // Create fire effect visuals var fireEffect = new Container(); var fireParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: 0xFF3300, alpha: 0.7 }); fireEffect.x = self.x + (Math.random() * 80 - 40); fireEffect.y = self.y + (Math.random() * 80 - 40); fireEffect.addChild(fireParticle); game.addChild(fireEffect); // Animate fire particles rising up tween(fireParticle, { y: fireParticle.y - 40, alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 400, onFinish: function onFinish() { if (fireEffect.parent) { fireEffect.parent.removeChild(fireEffect); } } }); // Remove burn effect when done if (self.burnTicks <= 0) { self.burning = false; self.body.tint = 0xFFFFFF; // Reset tint } } } // Handle shield mechanic - activate shield at 50% health if (!self.shieldActive && self.health <= self.maxHealth * 0.5) { self.shieldActive = true; // Create shield visual self.shield = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.5, scaleY: 3.5, alpha: 0.4, tint: 0x3399FF }); self.addChild(self.shield); // Animate shield activation tween(self.shield, { alpha: 0.6 }, { duration: 1000, onFinish: function onFinish() { // Pulse animation function pulseShield() { if (self.shield && self.shield.parent) { tween(self.shield, { alpha: 0.3 }, { duration: 1000, onFinish: function onFinish() { tween(self.shield, { alpha: 0.6 }, { duration: 1000, onFinish: pulseShield }); } }); } } pulseShield(); } }); } } }; self.takeDamage = function (amount) { // Reduce damage if shield is active if (self.shieldActive) { amount = amount * 0.5; } self.health -= amount; // Flash red when hit LK.effects.flashObject(self, 0xFF0000, 200); LK.getSound('hit').play(); if (self.health <= 0) { return true; } return false; }; return self; }); var Monster = Container.expand(function () { var self = Container.call(this); self.id = Date.now() + Math.floor(Math.random() * 10000); self.body = self.attachAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 1.5, tint: 0x9900FF // Purple and blue color }); self.maxHealth = 20; self.health = self.maxHealth; self.speed = 2; self.damage = 10; self.lastAttackTime = 0; self.attackCooldown = 1000; self.update = function () { if (player && !paused) { var dx = player.x - self.x; var dy = player.y - self.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 10) { self.x += dx / dist * self.speed; self.y += dy / dist * self.speed; } var now = Date.now(); // Attack player if (dist < 100) { if (now - self.lastAttackTime > self.attackCooldown) { self.lastAttackTime = now; if (player.takeDamage(self.damage)) { LK.getSound('death').play(); gameOver(); } } } // Handle poison damage over time if (self.poisoned && self.poisonTicks > 0) { if (!self.lastPoisonTime || now - self.lastPoisonTime > 500) { // Every 0.5 seconds self.lastPoisonTime = now; self.poisonTicks--; self.takeDamage(self.poisonDamage); // Create poison effect visuals var poisonEffect = new Container(); var poisonParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, tint: 0x00FF00, alpha: 0.7 }); poisonEffect.x = self.x + (Math.random() * 40 - 20); poisonEffect.y = self.y + (Math.random() * 40 - 20); poisonEffect.addChild(poisonParticle); game.addChild(poisonEffect); // Animate poison particles tween(poisonParticle, { alpha: 0, scaleX: 1.5, scaleY: 1.5 }, { duration: 500, onFinish: function onFinish() { if (poisonEffect.parent) { poisonEffect.parent.removeChild(poisonEffect); } } }); // Remove poison effect when done if (self.poisonTicks <= 0) { self.poisoned = false; self.body.tint = 0xFFFFFF; // Reset tint } } } // Handle burning damage over time if (self.burning && self.burnTicks > 0) { if (!self.lastBurnTime || now - self.lastBurnTime > 400) { // Every 0.4 seconds self.lastBurnTime = now; self.burnTicks--; self.takeDamage(self.burnDamage); // Create fire effect visuals var fireEffect = new Container(); var fireParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, tint: 0xFF3300, alpha: 0.7 }); fireEffect.x = self.x + (Math.random() * 40 - 20); fireEffect.y = self.y + (Math.random() * 40 - 20); fireEffect.addChild(fireParticle); game.addChild(fireEffect); // Animate fire particles rising up tween(fireParticle, { y: fireParticle.y - 30, alpha: 0, scaleX: 1.2, scaleY: 1.2 }, { duration: 400, onFinish: function onFinish() { if (fireEffect.parent) { fireEffect.parent.removeChild(fireEffect); } } }); // Remove burn effect when done if (self.burnTicks <= 0) { self.burning = false; self.body.tint = 0xFFFFFF; // Reset tint } } } } }; self.takeDamage = function (amount) { self.health -= amount; // Flash red when hit LK.effects.flashObject(self, 0xFF0000, 200); LK.getSound('hit').play(); if (self.health <= 0) { return true; } return false; }; return self; }); var OrbitalObject = Container.expand(function (type, angle, distance) { var self = Container.call(this); self.type = type; self.angle = angle || 0; self.distance = distance || 300; // Increased distance from 200 to 300 to allow monsters to pass more easily self.speed = 0.01; // Reduced speed for orbital objects self.target = null; self.lastAttackTime = 0; // Set different properties based on type if (type === "shields") { self.damage = 5; self.attackCooldown = 800; // Faster attack rate } else if (type === "swords") { self.damage = 12; // Higher damage self.attackCooldown = 1200; } else if (type === "orbs") { self.damage = 8; self.attackCooldown = 1000; } else if (type === "flame_barrier") { self.damage = 7; self.attackCooldown = 600; // Very fast attack rate for flame barrier } else if (type === "ice_shards") { self.damage = 6; self.attackCooldown = 900; } else if (type === "spirit_guardians") { self.damage = 10; self.attackCooldown = 1100; } else { self.damage = 7; self.attackCooldown = 1000; } // Create the visual based on type if (type === "shields") { // Create holographic energy barrier visual self.body = self.attachAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7 }); // Create energy barrier multilayer glow effect self.glow = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0, alpha: 0.4 }); // Add inner energy core self.core = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, alpha: 0.9, tint: 0xFFFFFF }); self.addChild(self.core); self.addChildAt(self.glow, 0); self.body.tint = 0x33CCFF; // Bright blue energy barrier self.glow.tint = 0x33CCFF; // Matching glow color // Add futuristic pulse animation to the shield tween(self.glow, { scaleX: 1.1, scaleY: 1.1, alpha: 0.6 }, { duration: 1000, easing: tween.easeInOut, onFinish: function pulseShield() { tween(self.glow, { scaleX: 0.9, scaleY: 0.9, alpha: 0.3 }, { duration: 1000, easing: tween.easeInOut, onFinish: pulseShield }); } }); // Add rotating core animation tween(self.core, { rotation: Math.PI * 2 }, { duration: 3000, onFinish: function rotateCoreLoop() { self.core.rotation = 0; tween(self.core, { rotation: Math.PI * 2 }, { duration: 3000, onFinish: rotateCoreLoop }); } }); } else if (type === "swords") { // Create energy blade instead of physical sword self.body = self.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 0.4 }); self.body.tint = 0xE6E6FF; // Glowing energy blade // Add energy core and hilt self.hilt = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, x: -30, tint: 0x666666 // Dark metal hilt }); self.addChild(self.hilt); // Add energy blade glow self.bladeGlow = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.2, scaleY: 0.6, alpha: 0.4, tint: 0xE6E6FF }); self.addChildAt(self.bladeGlow, 0); // Add blade flicker animation tween(self.bladeGlow, { alpha: 0.6, scaleY: 0.7 }, { duration: 500 + Math.random() * 500, onFinish: function flickerBlade() { tween(self.bladeGlow, { alpha: 0.3, scaleY: 0.5 }, { duration: 500 + Math.random() * 500, onFinish: flickerBlade }); } }); } else if (type === "orbs") { // Create plasma orb with energy core self.body = self.attachAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); self.body.tint = 0xFFCC00; // Yellow plasma orb // Add energy core self.core = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, tint: 0xFFFFFF, alpha: 0.9 }); self.addChild(self.core); // Add outer energy field self.field = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7, alpha: 0.3, tint: 0xFFCC00 }); self.addChildAt(self.field, 0); // Add plasma fluctuation animation tween(self.field, { scaleX: 0.9, scaleY: 0.9, alpha: 0.4 }, { duration: 800, easing: tween.easeInOut, onFinish: function fluctuatePlasma() { tween(self.field, { scaleX: 0.7, scaleY: 0.7, alpha: 0.2 }, { duration: 800, easing: tween.easeInOut, onFinish: fluctuatePlasma }); } }); // Add energy arcs occasionally self.lastArcTime = 0; } else if (type === "flame_barrier") { // Create plasma flame visual self.body = self.attachAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6 }); self.body.tint = 0xFF3300; // Orange-red for flame barrier // Add flame core self.flameCore = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0xFFFF00, // Yellow hot core alpha: 0.9 }); self.addChild(self.flameCore); // Add outer flame aura self.flameAura = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, alpha: 0.3, tint: 0xFF3300 }); self.addChildAt(self.flameAura, 0); // Add flame flicker animation tween(self.flameAura, { scaleX: 1.0, scaleY: 1.0, alpha: 0.5 }, { duration: 300 + Math.random() * 200, easing: tween.easeOut, onFinish: function flickerFlame() { tween(self.flameAura, { scaleX: 0.8, scaleY: 0.8, alpha: 0.2 }, { duration: 300 + Math.random() * 200, easing: tween.easeIn, onFinish: flickerFlame }); } }); } else if (type === "ice_shards") { // Create crystalline ice structure self.body = self.attachAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: 0xDDEEFF // Light blue-white for ice crystals }); // Add crystal facets for (var i = 0; i < 3; i++) { var angle = Math.PI * 2 * (i / 3); var shard = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.15, rotation: angle, tint: 0x99CCFF, alpha: 0.9 }); self.addChild(shard); } // Add frost aura self.frostAura = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.9, scaleY: 0.9, alpha: 0.2, tint: 0xBBDDFF }); self.addChildAt(self.frostAura, 0); // Add ice crystal shimmer animation tween(self.body, { tint: 0xAACCFF }, { duration: 1500, onFinish: function shimmerIce() { tween(self.body, { tint: 0xDDEEFF }, { duration: 1500, onFinish: shimmerIce }); } }); } else if (type === "spirit_guardians") { // Create ethereal spirit form self.body = self.attachAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3 }); self.body.tint = 0xDDDDFF; // Pale blue-white for spirits // Add spirit core self.spiritCore = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0, tint: 0xFFFFFF, alpha: 0.7 }); self.addChild(self.spiritCore); // Add ethereal aura self.spiritAura = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 3.5, scaleY: 3.5, alpha: 0.15, tint: 0xEEEEFF }); self.addChildAt(self.spiritAura, 0); // Add ethereal pulsing animation tween(self.spiritAura, { alpha: 0.25, scaleX: 3.7, scaleY: 3.7 }, { duration: 2000, easing: tween.easeInOut, onFinish: function pulseSpirit() { tween(self.spiritAura, { alpha: 0.15, scaleX: 3.5, scaleY: 3.5 }, { duration: 2000, easing: tween.easeInOut, onFinish: pulseSpirit }); } }); } self.update = function () { if (!player) return; // Track previous position for trail effect if (self.prevX === undefined) { self.prevX = self.x; self.prevY = self.y; self.lastTrailTime = Date.now(); } // Update position around player self.angle += self.speed; self.x = player.x + Math.cos(self.angle) * self.distance; self.y = player.y + Math.sin(self.angle) * self.distance; // Rotate object to face direction of movement if (self.type === "swords" || self.type === "ice_shards") { self.rotation = self.angle + Math.PI / 2; } // Create energy trail effect - reduced frequency for better performance var now = Date.now(); if (now - self.lastTrailTime > 250) { // Create trail less frequently (250ms instead of 100ms) self.lastTrailTime = now; // Only create trail if moved enough var dx = self.x - self.prevX; var dy = self.y - self.prevY; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 20) { //{dn} // Increased threshold for trail creation var trail = new Container(); var trailParticle; // Different trail effects for different orbital types if (self.type === "shields") { trailParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0x33CCFF, alpha: 0.5 }); } else if (self.type === "swords") { trailParticle = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.2, rotation: self.rotation, tint: 0xE6E6FF, alpha: 0.4 }); } else if (self.type === "orbs") { trailParticle = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, tint: 0xFFCC00, alpha: 0.4 }); } else if (self.type === "flame_barrier") { trailParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0xFF3300, alpha: 0.5 }); } else if (self.type === "ice_shards") { trailParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, tint: 0x99CCFF, alpha: 0.4 }); } else if (self.type === "spirit_guardians") { trailParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, tint: 0xDDDDFF, alpha: 0.3 }); } trail.x = self.x; trail.y = self.y; trail.addChild(trailParticle); game.addChild(trail); // Animate trail fadeout - shorter duration for better performance tween(trailParticle, { alpha: 0, scaleX: trailParticle.scaleX * 0.5, scaleY: trailParticle.scaleY * 0.5 }, { duration: 200, easing: tween.easeOut, onFinish: function onFinish() { if (trail.parent) { trail.parent.removeChild(trail); } } }); // Update previous position self.prevX = self.x; self.prevY = self.y; } } // Check for collisions with monsters for (var i = monsters.length - 1; i >= 0; i--) { var monster = monsters[i]; if (self.intersects(monster)) { var now = Date.now(); if (now - self.lastAttackTime > self.attackCooldown) { self.lastAttackTime = now; // Different effects based on type if (self.type === "shields") { // Shields push monsters back var dx = monster.x - player.x; var dy = monster.y - player.y; var dist = Math.sqrt(dx * dx + dy * dy); if (dist > 0) { var pushX = dx / dist * 50; var pushY = dy / dist * 50; monster.x += pushX; monster.y += pushY; // Create energy barrier impact effect var shieldEffect = new Container(); var impactWave = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.8, tint: 0x33CCFF, alpha: 0.8 }); shieldEffect.x = self.x; shieldEffect.y = self.y; game.addChild(shieldEffect); shieldEffect.addChild(impactWave); // Add energy discharge particles for (var p = 0; p < 8; p++) { var angle = Math.PI * 2 * (p / 8); var particle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0x99EEFF, alpha: 0.9 }); particle.x = Math.cos(angle) * 20; particle.y = Math.sin(angle) * 20; impactWave.addChild(particle); // Animate particles outward tween(particle, { x: particle.x * 4, y: particle.y * 4, alpha: 0 }, { duration: 300, easing: tween.easeOut }); } // Animate shield effect with ripple tween(impactWave, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 500, onFinish: function onFinish() { if (shieldEffect.parent) { shieldEffect.parent.removeChild(shieldEffect); } } }); } // Energy discharge effect LK.effects.flashObject(self, 0x33CCFF, 200); // Play orbital hit sound LK.getSound('orbital_hit').play(); // Create energy ripple effect var energyRipple = new Container(); var rippleWave = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0, tint: 0x33CCFF, alpha: 0.4 }); energyRipple.x = monster.x; energyRipple.y = monster.y; game.addChild(energyRipple); energyRipple.addChild(rippleWave); // Animate ripple tween(rippleWave, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 400, onFinish: function onFinish() { if (energyRipple.parent) { energyRipple.parent.removeChild(energyRipple); } } }); monster.takeDamage(5); // Shields do a little damage } else if (self.type === "swords" || self.type === "orbs") { // Swords and orbs deal damage if (monster.takeDamage(self.damage)) { game.removeChild(monster); monsters.splice(i, 1); // Play pop sound when monster is killed LK.getSound('pop').play(); monstersKilled++; scoreText.setText("Score: " + monstersKilled); } if (self.type === "swords") { // Create sword slash effect var slashEffect = new Container(); var slash = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 0.8, tint: 0xFFFFFF, alpha: 0.8 }); slashEffect.x = monster.x; slashEffect.y = monster.y; slashEffect.rotation = Math.random() * Math.PI; slashEffect.addChild(slash); game.addChild(slashEffect); // Add impact particles for (var p = 0; p < 5; p++) { var sparkParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0xFFCCCC, alpha: 0.9 }); sparkParticle.x = Math.random() * 30 - 15; sparkParticle.y = Math.random() * 30 - 15; slashEffect.addChild(sparkParticle); // Animate particles tween(sparkParticle, { x: sparkParticle.x * 3, y: sparkParticle.y * 3, alpha: 0 }, { duration: 300, easing: tween.easeOut }); } // Animate the slash tween(slash, { scaleX: 4, alpha: 0 }, { duration: 250, onFinish: function onFinish() { if (slashEffect.parent) { slashEffect.parent.removeChild(slashEffect); } } }); LK.effects.flashObject(self, 0xFF0000, 200); // Play orbital hit sound LK.getSound('orbital_hit').play(); } else { // Orbs have electricity effect // Create electricity effect var zapEffect = new Container(); var energyCore = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4, scaleY: 0.4, tint: 0xFFFF00, alpha: 0.8 }); zapEffect.x = monster.x; zapEffect.y = monster.y; zapEffect.addChild(energyCore); game.addChild(zapEffect); // Add lightning bolts for (var b = 0; b < 4; b++) { var angle = Math.PI * 2 * (b / 4); var boltLength = 40 + Math.random() * 20; var bolt = LK.getAsset('arrow', { anchorX: 0, anchorY: 0.5, scaleX: boltLength / 50, scaleY: 0.3, tint: 0xFFFF99, alpha: 0.9 }); bolt.rotation = angle; zapEffect.addChild(bolt); // Animate each bolt tween(bolt, { scaleY: 0.1, alpha: 0 }, { duration: 200 + Math.random() * 200 }); } // Animate the energy core tween(energyCore, { scaleX: 1.5, scaleY: 1.5, alpha: 0 }, { duration: 400, onFinish: function onFinish() { if (zapEffect.parent) { zapEffect.parent.removeChild(zapEffect); } } }); LK.effects.flashObject(self, 0xFFFF00, 200); // Play orbital hit sound LK.getSound('orbital_hit').play(); } } else if (self.type === "flame_barrier") { // Flame barrier burns enemies if (!monster.burning) { monster.burning = true; monster.burnDamage = self.damage * 0.2; monster.burnTicks = 3; monster.body.tint = 0xFF3300; // Create flame burst effect var flameBurst = new Container(); var burstCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0, tint: 0xFF3300, alpha: 0.7 }); flameBurst.x = monster.x; flameBurst.y = monster.y; game.addChild(flameBurst); flameBurst.addChild(burstCircle); // Add flame particles for (var f = 0; f < 6; f++) { var flameParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.7, scaleY: 0.7, tint: 0xFF5500, alpha: 0.8 }); flameParticle.x = Math.random() * 40 - 20; flameParticle.y = Math.random() * 40 - 20; flameBurst.addChild(flameParticle); // Animate flame particle tween(flameParticle, { x: flameParticle.x * 2, y: flameParticle.y - 30, alpha: 0, scaleX: 0.3, scaleY: 0.3 }, { duration: 600, easing: tween.easeOut }); } // Animate the flame burst tween(burstCircle, { scaleX: 2.0, scaleY: 2.0, alpha: 0 }, { duration: 700, onFinish: function onFinish() { if (flameBurst.parent) { flameBurst.parent.removeChild(flameBurst); } } }); } if (monster.takeDamage(self.damage)) { game.removeChild(monster); monsters.splice(i, 1); // Play pop sound when monster is killed LK.getSound('pop').play(); monstersKilled++; scoreText.setText("Score: " + monstersKilled); } LK.effects.flashObject(self, 0xFF3300, 200); // Play orbital hit sound LK.getSound('orbital_hit').play(); } else if (self.type === "ice_shards") { // Ice shards slow enemies monster.frozen = true; monster.originalSpeed = monster.speed; monster.speed = monster.speed * 0.7; monster.body.tint = 0x99CCFF; // Create frost burst effect var frostBurst = new Container(); var frostCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.2, scaleY: 1.2, tint: 0x99CCFF, alpha: 0.6 }); frostBurst.x = monster.x; frostBurst.y = monster.y; game.addChild(frostBurst); frostBurst.addChild(frostCircle); // Add crystal shards for (var s = 0; s < 8; s++) { var iceFragment = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, tint: 0xCCEEFF, alpha: 0.9 }); var angle = Math.PI * 2 * (s / 8); var distance = 30; iceFragment.x = Math.cos(angle) * distance; iceFragment.y = Math.sin(angle) * distance; frostBurst.addChild(iceFragment); // Animate ice fragments expanding outward tween(iceFragment, { x: iceFragment.x * 2.5, y: iceFragment.y * 2.5, alpha: 0, rotation: Math.random() * Math.PI }, { duration: 800, easing: tween.easeOut }); } // Animate the frost circle tween(frostCircle, { scaleX: 2.5, scaleY: 2.5, alpha: 0 }, { duration: 700, onFinish: function onFinish() { if (frostBurst.parent) { frostBurst.parent.removeChild(frostBurst); } } }); // Reset after 2 seconds LK.setTimeout(function () { if (monster && monster.frozen) { monster.frozen = false; monster.speed = monster.originalSpeed; monster.body.tint = 0xFFFFFF; } }, 2000); if (monster.takeDamage(self.damage)) { game.removeChild(monster); monsters.splice(i, 1); // Play pop sound when monster is killed LK.getSound('pop').play(); monstersKilled++; scoreText.setText("Score: " + monstersKilled); } LK.effects.flashObject(self, 0x99CCFF, 200); // Play orbital hit sound LK.getSound('orbital_hit').play(); } else if (self.type === "spirit_guardians") { // Spirit guardians pass through enemies if (monster.takeDamage(self.damage)) { game.removeChild(monster); monsters.splice(i, 1); // Play pop sound when monster is killed LK.getSound('pop').play(); monstersKilled++; scoreText.setText("Score: " + monstersKilled); } LK.effects.flashObject(self, 0xDDDDFF, 200); // Play orbital hit sound LK.getSound('orbital_hit').play(); // Spirits heal player slightly if (player) { player.health = Math.min(player.health + 1, player.maxHealth); // Create spirit healing effect var spiritEffect = new Container(); var spiritGlow = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 2.5, tint: 0xDDDDFF, alpha: 0.7 }); spiritEffect.x = monster.x; spiritEffect.y = monster.y; spiritEffect.addChild(spiritGlow); game.addChild(spiritEffect); // Create healing link to player var healLink = LK.getAsset('arrow', { anchorX: 0, anchorY: 0.5, tint: 0xEEEEFF, alpha: 0.5 }); // Calculate distance and angle to player var dx = player.x - monster.x; var dy = player.y - monster.y; var distance = Math.sqrt(dx * dx + dy * dy); var angle = Math.atan2(dy, dx); healLink.rotation = angle; healLink.scaleX = distance / 50; spiritEffect.addChild(healLink); // Add ethereal particles floating to player for (var h = 0; h < 3; h++) { var healParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, tint: 0xFFFFFF, alpha: 0.8 }); healParticle.x = 0; healParticle.y = 0; spiritEffect.addChild(healParticle); // Animate particles along path to player tween(healParticle, { x: dx, y: dy, alpha: 0 }, { duration: 600 + h * 100, easing: tween.easeInOut }); } // Animate the spirit effect tween(spiritGlow, { scaleX: 3.5, scaleY: 3.5, alpha: 0 }, { duration: 800, onFinish: function onFinish() { if (spiritEffect.parent) { spiritEffect.parent.removeChild(spiritEffect); } } }); } } } } } }; return self; }); var Player = Container.expand(function () { var self = Container.call(this); self.body = self.attachAsset('player', { anchorX: 0.5, anchorY: 0.5, scaleX: 2.5, scaleY: 2.5 }); // Modify ship body appearance self.body.tint = 0x3366CC; // Deeper blue for ship body // Create cockpit self.cockpit = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6, x: 20, tint: 0x99CCFF // Light blue cockpit }); self.addChild(self.cockpit); // Add thruster effect for spaceship with animation self.thruster = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 1.2, x: -40, tint: 0xFF6600 }); self.addChild(self.thruster); // Animate thruster pulsing effect tween(self.thruster, { scaleY: 0.8, alpha: 0.7 }, { duration: 500, onFinish: function pulseThruster() { tween(self.thruster, { scaleY: 1.2, alpha: 1 }, { duration: 500, onFinish: pulseThruster }); } }); // Add wing details with metallic appearance self.leftWing = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.3, x: -20, y: 25, rotation: Math.PI / 4, tint: 0x99AACC }); self.addChild(self.leftWing); self.rightWing = LK.getAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.8, scaleY: 0.3, x: -20, y: -25, rotation: -Math.PI / 4, tint: 0x99AACC }); self.addChild(self.rightWing); // Add wing tip lights self.leftWingLight = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, x: self.leftWing.x + 30, y: self.leftWing.y + 15, tint: 0xFF3333 // Red navigation light }); self.addChild(self.leftWingLight); self.rightWingLight = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, x: self.rightWing.x + 30, y: self.rightWing.y - 15, tint: 0x33FF33 // Green navigation light }); self.addChild(self.rightWingLight); // Create thruster particle emitter function self.lastParticleTime = 0; self.maxHealth = 100; self.health = self.maxHealth; self.attackSpeed = 2; self.lastAttackTime = 0; self.movementSpeed = 0; self.abilities = []; self.shoot = function (targetX, targetY) { var now = Date.now(); if (now - self.lastAttackTime < 1000 / self.attackSpeed) { return null; } self.lastAttackTime = now; // Rotate spaceship to face the target smoothly var angle = Math.atan2(targetY - self.y, targetX - self.x); // Animate ship rotation for smoother movement tween(self, { rotation: angle }, { duration: 150, easing: tween.easeOut }); // Create recoil effect - ship moves slightly backward var recoilDistance = 10; var recoilX = self.x - Math.cos(angle) * recoilDistance; var recoilY = self.y - Math.sin(angle) * recoilDistance; tween(self, { x: recoilX, y: recoilY }, { duration: 100, easing: tween.easeOut, onFinish: function onFinish() { // Return to original position tween(self, { x: self.x + Math.cos(angle) * recoilDistance, y: self.y + Math.sin(angle) * recoilDistance }, { duration: 300, easing: tween.easeOutElastic }); } }); // Create laser beam (arrow) var arrow = new Arrow(); arrow.x = self.x + Math.cos(angle) * 50; // Start from front of ship arrow.y = self.y + Math.sin(angle) * 50; arrow.rotation = angle; // Create muzzle flash effect for laser var laserFlash = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.0, scaleY: 1.0, tint: 0x99FFFF, alpha: 0.9 }); laserFlash.x = self.x + Math.cos(angle) * 60; laserFlash.y = self.y + Math.sin(angle) * 60; game.addChild(laserFlash); // Animate laser flash tween(laserFlash, { scaleX: 0.2, scaleY: 0.2, alpha: 0 }, { duration: 150, onFinish: function onFinish() { if (laserFlash.parent) { laserFlash.parent.removeChild(laserFlash); } } }); // Create enhanced thruster animation effect var thrusterFlare = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 1.5, scaleY: 2.0, tint: 0xFF9900, alpha: 0.9 }); thrusterFlare.x = self.x - Math.cos(angle) * 40; thrusterFlare.y = self.y - Math.sin(angle) * 40; thrusterFlare.rotation = angle + Math.PI; game.addChild(thrusterFlare); // Animate thruster flare with more dynamic effect tween(thrusterFlare, { scaleX: 0.2, scaleY: 0.2, alpha: 0 }, { duration: 300, //{kk} // Reduced duration easing: tween.easeOut, onFinish: function onFinish() { if (thrusterFlare.parent) { thrusterFlare.parent.removeChild(thrusterFlare); } } }); // Create fewer thruster particles for (var i = 0; i < 2; i++) { //{kq} // Reduced from 5 to 2 var particleDelay = i * 50; // Stagger particle creation LK.setTimeout(function () { var thrusterParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.4 + Math.random() * 0.3, scaleY: 0.4 + Math.random() * 0.3, tint: Math.random() > 0.5 ? 0xFF3300 : 0xFFAA00, alpha: 0.7 }); // Random offset from center of thruster var particleOffset = Math.random() * 15; var offsetAngle = angle + Math.PI + (Math.random() * 0.5 - 0.25); thrusterParticle.x = self.x - Math.cos(angle) * 40 + Math.cos(offsetAngle) * particleOffset; thrusterParticle.y = self.y - Math.sin(angle) * 40 + Math.sin(offsetAngle) * particleOffset; game.addChild(thrusterParticle); // Animate particle with varying speed - shorter duration tween(thrusterParticle, { x: thrusterParticle.x - Math.cos(angle) * (50 + Math.random() * 30), y: thrusterParticle.y - Math.sin(angle) * (50 + Math.random() * 30), scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 250, //{kA} // Fixed shorter duration easing: tween.easeOut, onFinish: function onFinish() { if (thrusterParticle.parent) { thrusterParticle.parent.removeChild(thrusterParticle); } } }); }, particleDelay); } // Play laser sound effect LK.getSound('shoot').play(); return arrow; }; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { self.health = 0; // Play death sound when player dies LK.getSound('death').play(); return true; } return false; }; self.addAbility = function (ability) { self.abilities.push(ability); // Apply ability effects if (ability.type === "attackSpeed") { self.attackSpeed += ability.value; } else if (ability.type === "health") { self.maxHealth += ability.value; self.health = Math.min(self.health + ability.value, self.maxHealth); } else if (ability.type === "arrowDamage") { arrowDamage += ability.value; } else if (ability.type === "arrowPierce") { arrowPierce += ability.value; } else if (ability.type === "arrowSpeed") { arrowSpeed += ability.value; } else if (ability.type === "special") { console.log("Added special ability: " + ability.specialType); // Track special abilities if (["poison", "explosive", "freeze", "fire", "lightning", "vampiric", "multishot", "rebound", "homing"].indexOf(ability.specialType) !== -1) { // Track arrow special abilities acquiredSpecialTypes.push(ability.specialType); } // Create orbital objects for shield, sword, orb, etc. abilities if (["shields", "swords", "orbs", "flame_barrier", "ice_shards", "spirit_guardians"].indexOf(ability.specialType) !== -1) { // Track orbital special abilities acquiredOrbitalTypes.push(ability.specialType); // Create 3 objects for each type, evenly spaced around the player // Calculate an offset angle to prevent collision with existing orbital objects var baseOffset = 0; if (orbitalObjects.length > 0) { // If we already have orbital objects, offset the new ones to avoid overlap baseOffset = Math.PI / (3 + orbitalObjects.length / 3); // Stagger based on number of existing orbital groups } for (var i = 0; i < 3; i++) { var angle = Math.PI * 2 / 3 * i + baseOffset; var orbitalObj = new OrbitalObject(ability.specialType, angle); // Slightly vary the orbital distance to prevent collisions orbitalObj.distance = 300 + orbitalObjects.length / 3 * 20; // Increase orbit radius for each new group orbitalObjects.push(orbitalObj); game.addChild(orbitalObj); } } } }; // Add update method for continuous effects self.update = function () { // Generate idle thruster particles less frequently var now = Date.now(); if (now - self.lastParticleTime > 350) { // Emit particles less frequently (350ms instead of 150ms) self.lastParticleTime = now; // Create idle thruster particle var idleParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.3, scaleY: 0.3, tint: Math.random() > 0.3 ? 0xFF6600 : 0xFFAA00, alpha: 0.6 }); // Position behind ship based on current rotation var backAngle = self.rotation + Math.PI; var offset = Math.random() * 10 - 5; var perpendicular = backAngle + Math.PI / 2; idleParticle.x = self.x + Math.cos(backAngle) * 40 + Math.cos(perpendicular) * offset; idleParticle.y = self.y + Math.sin(backAngle) * 40 + Math.sin(perpendicular) * offset; game.addChild(idleParticle); // Animate particle with shorter duration tween(idleParticle, { x: idleParticle.x + Math.cos(backAngle) * 30, y: idleParticle.y + Math.sin(backAngle) * 30, scaleX: 0.1, scaleY: 0.1, alpha: 0 }, { duration: 250, //{lb} // Reduced duration easing: tween.easeOut, onFinish: function onFinish() { if (idleParticle.parent) { idleParticle.parent.removeChild(idleParticle); } } }); } // Blink wing tip lights less frequently if (LK.ticks % 120 === 0) { // Every 2 seconds (120 frames) instead of every second tween(self.leftWingLight, { alpha: 0.4 }, { duration: 300, onFinish: function onFinish() { tween(self.leftWingLight, { alpha: 1 }, { duration: 300 }); } }); // Blink opposite wing with delay LK.setTimeout(function () { tween(self.rightWingLight, { alpha: 0.4 }, { duration: 300, onFinish: function onFinish() { tween(self.rightWingLight, { alpha: 1 }, { duration: 300 }); } }); }, 500); } }; return self; }); var Star = Container.expand(function () { var self = Container.call(this); // Create star visual with random size and color var size = 1 + Math.random() * 3; // Generate random star colors ranging from blue to purple to white var colorChoice = Math.random(); var starTint; if (colorChoice < 0.3) { starTint = 0xCCCCFF; // Light blue/lavender } else if (colorChoice < 0.6) { starTint = 0xDDA0DD; // Light purple } else if (colorChoice < 0.8) { starTint = 0xFFFFFF; // White } else { starTint = 0xFFD700; // Gold/yellow for bright stars } self.body = self.attachAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: size / 5, scaleY: size / 5, tint: starTint, alpha: 0.3 + Math.random() * 0.7 }); // Set random speed for parallax effect self.speed = 0.1 + Math.random() * 0.3; // Twinkle animation self.twinkle = function () { var targetAlpha = 0.3 + Math.random() * 0.7; var duration = 1000 + Math.random() * 2000; tween(self.body, { alpha: targetAlpha }, { duration: duration, onFinish: self.twinkle }); }; // Start twinkling self.twinkle(); // Update method for star movement self.update = function () { // Move star down slowly for parallax effect self.y += self.speed; // If star moves off screen, reset position if (self.y > 2832) { self.y = -20; self.x = Math.random() * 2048; } }; return self; }); var UpgradeCard = Container.expand(function (ability) { var self = Container.call(this); self.ability = ability; self.background = self.attachAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5 }); self.icon = self.attachAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, y: -120 }); // Title self.title = new Text2(ability.name, { size: 36, fill: 0x000000 }); self.title.anchor.set(0.5, 0.5); self.title.y = -180; // Move title position above the colored icon self.addChild(self.title); // Description self.description = new Text2(ability.description, { size: 24, fill: 0x333333, wordWrap: 350 }); self.description.anchor.set(0.5, 0.5); self.description.y = 80; // Increased y position to avoid collision with title self.addChild(self.description); self.down = function (x, y, obj) { tween(self, { scaleX: 0.95, scaleY: 0.95 }, { duration: 100 }); }; self.up = function (x, y, obj) { tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (player) { player.addAbility(self.ability); hideUpgradeScreen(); startNextWave(); LK.getSound('levelup').play(); } } }); }; return self; }); /**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 // Black background for Space Archers }); /**** * Game Code ****/ // Game variables var player; var monsters = []; var arrows = []; var upgradeCards = []; var orbitalObjects = []; var stars = []; // Array to hold background stars var paused = false; var currentWave = 0; var monstersKilled = 0; var lastSpawnTime = 0; var showingUpgrades = false; var bossSpawned = false; var bossDefeated = false; // Track acquired special abilities to prevent duplicates var acquiredSpecialTypes = []; var acquiredOrbitalTypes = []; // Arrow properties var arrowDamage = 20; var arrowPierce = 1; var arrowSpeed = 18; // Abilities pool var abilitiesPool = [{ name: "Quick Shot", description: "Increase attack speed by 15%", type: "attackSpeed", value: 0.15 }, { name: "Heavy Arrow", description: "Increase arrow damage by 5", type: "arrowDamage", value: 5 }, { name: "Fortify", description: "Increase max health by 20", type: "health", value: 20 }, { name: "Piercing Shot", description: "Arrows pierce through one additional enemy", type: "arrowPierce", value: 1 }, { name: "Swift Arrow", description: "Increase arrow speed by 2", type: "arrowSpeed", value: 2 }]; // Special power-up abilities var specialAbilitiesPool = [{ name: "Poison Lazers", description: "Lazers poison enemies, dealing damage over time", type: "special", specialType: "poison" }, { name: "Explosive Lazers", description: "Lazers explode on impact, damaging nearby enemies", type: "special", specialType: "explosive" }, { name: "Freezing Lazers", description: "Lazers slow down enemies for a short time", type: "special", specialType: "freeze" }, { name: "Fire Lazers", description: "Lazers burn enemies with intense flames", type: "special", specialType: "fire" }, { name: "Lightning Lazers", description: "Lazers chain lightning between nearby enemies", type: "special", specialType: "lightning" }, { name: "Vampiric Lazers", description: "Lazers steal health from enemies and heal you", type: "special", specialType: "vampiric" }, { name: "Multi-Shot Lazers", description: "Fire three lazers in a spread pattern with each shot (additional upgrades add more lazers)", type: "special", specialType: "multishot" }, { name: "Homing Lazers", description: "Lazers seek out nearby enemies after being fired", type: "special", specialType: "homing" }, { name: "Energy Barriers", description: "Three energy barriers orbit around you, pushing back and damaging enemies", type: "special", specialType: "shields" }, { name: "Rotating Swords", description: "Spinning swords that damage nearby enemies", type: "special", specialType: "swords" }, { name: "Energy Orbs", description: "Magical orbs circle you, zapping nearby enemies", type: "special", specialType: "orbs" }, { name: "Flame Barrier", description: "A ring of fire surrounds you, burning enemies", type: "special", specialType: "flame_barrier" }, { name: "Ice Shards", description: "Ice crystals orbit you, slowing nearby enemies", type: "special", specialType: "ice_shards" }, { name: "Spirit Guardians", description: "Ethereal spirits orbit and protect you", type: "special", specialType: "spirit_guardians" }]; // UI elements var waveText = new Text2("Wave: 1", { size: 60, fill: 0xFFFFFF }); waveText.anchor.set(0.5, 0); LK.gui.top.addChild(waveText); var scoreText = new Text2("Hits: 0", { size: 60, fill: 0xFFFFFF }); scoreText.anchor.set(0, 0); scoreText.x = 150; scoreText.y = 10; LK.gui.topLeft.addChild(scoreText); var healthBarBg = LK.getAsset('healthBarBg', { anchorX: 0.5, anchorY: 0.5 }); var healthBar = LK.getAsset('healthBar', { anchorX: 0, anchorY: 0.5 }); healthBarBg.x = 2048 / 2; healthBarBg.y = 50; healthBar.x = healthBarBg.x - 100; healthBar.y = healthBarBg.y; LK.gui.addChild(healthBarBg); LK.gui.addChild(healthBar); var upgradeContainer = new Container(); upgradeContainer.visible = false; LK.gui.addChild(upgradeContainer); var upgradeTitle = new Text2("Choose One: Arrow Effect, Orbital, or Stat Upgrade", { size: 60, fill: 0xFFFFFF }); upgradeTitle.anchor.set(0, 0.5); upgradeTitle.x = 50; upgradeTitle.y = 200; upgradeContainer.addChild(upgradeTitle); function initGame() { currentWave = 0; monstersKilled = 0; paused = false; showingUpgrades = false; monsters = []; arrows = []; bossSpawned = false; // Track if boss is spawned for current wave bossDefeated = false; // Track if the current boss has been defeated // Reset acquired special abilities acquiredSpecialTypes = []; acquiredOrbitalTypes = []; // Clean up any existing orbital objects for (var i = 0; i < orbitalObjects.length; i++) { if (orbitalObjects[i].parent) { orbitalObjects[i].parent.removeChild(orbitalObjects[i]); } } orbitalObjects = []; // Clean up existing stars for (var i = 0; i < stars.length; i++) { if (stars[i].parent) { stars[i].parent.removeChild(stars[i]); } } stars = []; // Create starfield background with fewer stars (50 instead of 100) for (var i = 0; i < 50; i++) { var star = new Star(); star.x = Math.random() * 2048; // Random x position star.y = Math.random() * 2732; // Random y position stars.push(star); // Add stars to game at the beginning so they're behind everything else game.addChildAt(star, 0); } // Initialize player player = new Player(); player.x = 2048 / 2; player.y = 2732 / 2; game.addChild(player); // Reset arrow properties to stronger defaults arrowDamage = 20; arrowPierce = 1; arrowSpeed = 18; // Update UI waveText.setText("Wave: 1"); scoreText.setText("Score: 0"); // Start first wave startNextWave(); // Play music with initial intensity LK.playMusic('gameMusic', { fade: { start: 0, end: 1.0, duration: 2000 } }); } function startNextWave() { currentWave++; waveText.setText("Wave: " + currentWave); lastSpawnTime = Date.now(); // Reset boss flags at the start of every wave if (currentWave % 10 === 0) { // Boss wave starts with boss not spawned bossSpawned = false; bossDefeated = false; // Increase music intensity during boss waves LK.playMusic('gameMusic', { volume: 1.0, loop: true }); } else { // Non-boss wave bossSpawned = false; bossDefeated = true; // No boss to defeat on non-boss waves // Return to normal music intensity for regular waves if (currentWave > 10) { LK.playMusic('gameMusic', { volume: 0.8 }); } else { LK.playMusic('gameMusic', { volume: 0.7 }); } } // If boss wave, create special effect if (currentWave % 10 === 0) { // Create warning effect var warningEffect = new Container(); var warningBg = LK.getAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5, scaleX: 5, scaleY: 1, tint: 0xFF0000, alpha: 0.3 }); warningEffect.x = 2048 / 2; warningEffect.y = 2732 / 2; warningEffect.addChild(warningBg); LK.gui.addChild(warningEffect); // Add warning text var warningText = new Text2("BOSS INCOMING", { size: 100, fill: 0xFF0000 }); warningText.anchor.set(0.5, 0.5); warningEffect.addChild(warningText); // Pulse animation tween(warningBg, { alpha: 0.7 }, { duration: 500, onFinish: function onFinish() { tween(warningBg, { alpha: 0.3 }, { duration: 500, onFinish: function onFinish() { tween(warningBg, { alpha: 0.7 }, { duration: 500, onFinish: function onFinish() { tween(warningBg, { alpha: 0 }, { duration: 1000, onFinish: function onFinish() { if (warningEffect.parent) { warningEffect.parent.removeChild(warningEffect); } } }); } }); } }); } }); } } function spawnMonster() { // Check if we should spawn a boss (every 10 waves) if (currentWave % 10 === 0 && !bossSpawned) { var boss = new Boss(); // Scale boss stats based on which "boss wave" this is var bossLevel = currentWave / 10; boss.bossLevel = bossLevel; // Increase boss stats with each boss level boss.maxHealth = 200 + bossLevel * 300; // 500, 800, 1100, etc. boss.health = boss.maxHealth; boss.speed = 1 + bossLevel * 0.2; // Speed increases with level boss.damage = 20 + bossLevel * 10; // Damage increases with level // Add abilities at higher levels if (bossLevel >= 2) { boss.canSpawnMinions = true; boss.minionSpawnCooldown = 6000 - bossLevel * 500; // Spawn faster at higher levels } // Color based on boss level if (bossLevel === 1) { boss.body.tint = 0xFF0000; // Red for first boss } else if (bossLevel === 2) { boss.body.tint = 0x9900FF; // Purple for second boss } else if (bossLevel === 3) { boss.body.tint = 0x00FFFF; // Cyan for third boss } else { boss.body.tint = 0xFFFF00; // Yellow for higher level bosses } // Spawn boss at a random edge var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top boss.x = Math.random() * 2048; boss.y = -150; break; case 1: // Right boss.x = 2198; boss.y = Math.random() * 2732; break; case 2: // Bottom boss.x = Math.random() * 2048; boss.y = 2882; break; case 3: // Left boss.x = -150; boss.y = Math.random() * 2732; break; } // Create boss entrance effect var bossEntranceEffect = new Container(); var entranceCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 5, scaleY: 5, tint: boss.body.tint, alpha: 0.8 }); bossEntranceEffect.x = boss.x; bossEntranceEffect.y = boss.y; game.addChild(bossEntranceEffect); bossEntranceEffect.addChild(entranceCircle); // Animate entrance tween(entranceCircle, { scaleX: 10, scaleY: 10, alpha: 0 }, { duration: 1500, onFinish: function onFinish() { if (bossEntranceEffect.parent) { bossEntranceEffect.parent.removeChild(bossEntranceEffect); } } }); // Add a boss text warning var bossText = new Text2("BOSS LEVEL " + bossLevel, { size: 120, fill: 0xFF0000 }); bossText.anchor.set(0.5, 0.5); bossText.x = 2048 / 2; bossText.y = 2732 / 2; LK.gui.addChild(bossText); // Animate the boss text tween(bossText, { alpha: 0, scaleX: 2, scaleY: 2 }, { duration: 2000, onFinish: function onFinish() { if (bossText.parent) { bossText.parent.removeChild(bossText); } } }); // Add boss to the game monsters.push(boss); game.addChild(boss); bossSpawned = true; return boss; } else { // Regular monster spawning var monster = new Monster(); // Scale difficulty with wave, but with reduced health to make monsters easier to defeat monster.maxHealth = 10 + currentWave * 3; monster.health = monster.maxHealth; monster.speed = 1.5 + currentWave * 0.05; monster.damage = 5 + currentWave; // Change monster color based on wave difficulty if (currentWave <= 5) { monster.body.tint = 0xff3333; // Default red for early waves } else if (currentWave <= 10) { monster.body.tint = 0xff9900; // Orange for medium waves } else if (currentWave <= 15) { monster.body.tint = 0xffcc00; // Yellow-orange for harder waves } else if (currentWave <= 20) { monster.body.tint = 0x9900ff; // Purple for very hard waves } else { monster.body.tint = 0x000000; // Black for super difficult waves // Add a white outline effect with a flash LK.effects.flashObject(monster, 0xffffff, 300); } // Spawn from random edge of screen var side = Math.floor(Math.random() * 4); switch (side) { case 0: // Top monster.x = Math.random() * 2048; monster.y = -100; break; case 1: // Right monster.x = 2148; monster.y = Math.random() * 2732; break; case 2: // Bottom monster.x = Math.random() * 2048; monster.y = 2832; break; case 3: // Left monster.x = -100; monster.y = Math.random() * 2732; break; } monsters.push(monster); game.addChild(monster); return monster; } } function showUpgradeScreen() { paused = true; showingUpgrades = true; upgradeContainer.visible = true; // Clear previous upgrade cards for (var i = 0; i < upgradeCards.length; i++) { if (upgradeCards[i] && upgradeCards[i].parent) { upgradeCards[i].parent.removeChild(upgradeCards[i]); } } upgradeCards = []; // Set boss reward title if we just defeated a boss if (currentWave % 10 === 0 && bossDefeated) { upgradeTitle.setText("BOSS REWARDS: Choose Two Powerful Upgrades"); } else { upgradeTitle.setText("Choose One: Arrow Effect, Orbital, or Stat Upgrade"); } // Prepare for card selection var cardOptions = []; // Filter out already acquired special abilities var arrowSpecials = []; var orbitalSpecials = []; var nonOrbitalSpecials = []; for (var i = 0; i < specialAbilitiesPool.length; i++) { var ability = specialAbilitiesPool[i]; // Check for orbital special types if (["shields", "swords", "orbs", "flame_barrier", "ice_shards", "spirit_guardians"].indexOf(ability.specialType) !== -1) { // Only add if we don't have this orbital type already if (acquiredOrbitalTypes.indexOf(ability.specialType) === -1) { orbitalSpecials.push(ability); } // Check for arrow special types } else if (["poison", "explosive", "freeze", "fire", "lightning", "vampiric", "multishot", "rebound", "homing"].indexOf(ability.specialType) !== -1) { // Only add if we don't have this arrow special already if (acquiredSpecialTypes.indexOf(ability.specialType) === -1) { arrowSpecials.push(ability); } } else { nonOrbitalSpecials.push(ability); } } // Create enhanced abilities for boss rewards var bossRewardPool = []; if (currentWave % 10 === 0 && bossDefeated) { bossRewardPool = [{ name: "Massive Health Boost", description: "Increase max health by 50", type: "health", value: 50 }, { name: "Devastating Arrows", description: "Increase arrow damage by 15", type: "arrowDamage", value: 15 }, { name: "Lightning Reflexes", description: "Increase attack speed by 40%", type: "attackSpeed", value: 0.4 }, { name: "Master Piercer", description: "Arrows pierce through 2 additional enemies", type: "arrowPierce", value: 2 }, { name: "Sniper Training", description: "Increase arrow speed by 5", type: "arrowSpeed", value: 5 }]; } // For boss rewards, offer better options (more choices or better upgrades) if (currentWave % 10 === 0 && bossDefeated) { // Offer two options from boss reward pool for (var i = 0; i < 2 && bossRewardPool.length > 0; i++) { var index = Math.floor(Math.random() * bossRewardPool.length); var ability = bossRewardPool.splice(index, 1)[0]; cardOptions.push(ability); } // Add 1-2 special abilities if available if (arrowSpecials.length > 0) { var arrowSpecialIndex = Math.floor(Math.random() * arrowSpecials.length); var arrowSpecialAbility = arrowSpecials[arrowSpecialIndex]; cardOptions.push(arrowSpecialAbility); } if (orbitalSpecials.length > 0 && cardOptions.length < 4) { var orbitalIndex = Math.floor(Math.random() * orbitalSpecials.length); var orbitalAbility = orbitalSpecials[orbitalIndex]; cardOptions.push(orbitalAbility); } // If we still don't have enough cards, add from regular abilities var availableAbilities = [].concat(abilitiesPool); while (cardOptions.length < 4 && availableAbilities.length > 0) { var index = Math.floor(Math.random() * availableAbilities.length); var ability = availableAbilities.splice(index, 1)[0]; cardOptions.push(ability); } } // For regular upgrades, use standard options else { // Always include one arrow special if available (separate card) if (arrowSpecials.length > 0) { var arrowSpecialIndex = Math.floor(Math.random() * arrowSpecials.length); var arrowSpecialAbility = arrowSpecials[arrowSpecialIndex]; cardOptions.push(arrowSpecialAbility); } // Always include one orbital special if available (separate card) if (orbitalSpecials.length > 0) { var orbitalIndex = Math.floor(Math.random() * orbitalSpecials.length); var orbitalAbility = orbitalSpecials[orbitalIndex]; cardOptions.push(orbitalAbility); } // Include one non-orbital special if available if (nonOrbitalSpecials.length > 0 && cardOptions.length < 2) { var nonOrbitalIndex = Math.floor(Math.random() * nonOrbitalSpecials.length); var nonOrbitalAbility = nonOrbitalSpecials[nonOrbitalIndex]; cardOptions.push(nonOrbitalAbility); } // Pick regular abilities for the remaining slots var availableAbilities = [].concat(abilitiesPool); while (cardOptions.length < 3 && availableAbilities.length > 0) { var index = Math.floor(Math.random() * availableAbilities.length); var ability = availableAbilities.splice(index, 1)[0]; cardOptions.push(ability); } } // Show cards var cardCount = cardOptions.length; var cardWidth = 400; // Width of each card var spacing = 20; // Reduced spacing between cards var cardScale = 0.8; // Smaller card scale to ensure all three fit var totalWidth = cardWidth * cardScale * cardCount + spacing * (cardCount - 1); var startX = (2048 - totalWidth) / 2 + cardWidth * cardScale / 2; // For boss rewards, allow selecting two cards var cardsToSelect = currentWave % 10 === 0 && bossDefeated ? 2 : 1; var selectedCards = 0; for (var i = 0; i < cardCount; i++) { var ability = cardOptions[i]; var card = new UpgradeCard(ability); // Save original up method var originalUpMethod = card.up; // Override up method for boss rewards to allow selecting multiple cards if (currentWave % 10 === 0 && bossDefeated) { card.up = function (x, y, obj) { var self = this; tween(self, { scaleX: 1, scaleY: 1 }, { duration: 100, onFinish: function onFinish() { if (player) { player.addAbility(self.ability); if (self.parent) { self.parent.removeChild(self); } // Increment selected cards count selectedCards++; // If we've selected enough cards, hide the screen and start next wave if (selectedCards >= cardsToSelect) { hideUpgradeScreen(); startNextWave(); LK.getSound('levelup').play(); } } } }); }; } // Set special icon colors for special abilities if (ability.type === "special") { if (ability.specialType === "poison") { card.icon.tint = 0x00FF00; // Green for poison } else if (ability.specialType === "explosive") { card.icon.tint = 0xFF9900; // Orange for explosive } else if (ability.specialType === "freeze") { card.icon.tint = 0x00FFFF; // Cyan for freeze } else if (ability.specialType === "fire") { card.icon.tint = 0xFF3300; // Orange-red for fire } else if (ability.specialType === "lightning") { card.icon.tint = 0xFFFF00; // Yellow for lightning } else if (ability.specialType === "vampiric") { card.icon.tint = 0xFF00FF; // Purple for vampiric } else if (ability.specialType === "multishot") { card.icon.tint = 0x3366FF; // Blue for multishot } else if (ability.specialType === "rebound") { card.icon.tint = 0x66CCFF; // Light blue for rebound } else if (ability.specialType === "homing") { card.icon.tint = 0xFF6600; // Orange for homing } else if (ability.specialType === "shields") { card.icon.tint = 0x3399FF; // Blue for shields } else if (ability.specialType === "swords") { card.icon.tint = 0xCCCCCC; // Silver for swords } else if (ability.specialType === "orbs") { card.icon.tint = 0xFFFF00; // Yellow for orbs } else if (ability.specialType === "flame_barrier") { card.icon.tint = 0xFF3300; // Orange-red for flame barrier } else if (ability.specialType === "ice_shards") { card.icon.tint = 0x99CCFF; // Light blue for ice shards } else if (ability.specialType === "spirit_guardians") { card.icon.tint = 0xDDDDFF; // Pale blue-white for spirits } } // Add boss reward indicator for enhanced abilities if (currentWave % 10 === 0 && bossDefeated && (ability.type !== "special" || ["shields", "swords", "orbs", "flame_barrier", "ice_shards", "spirit_guardians", "poison", "explosive", "freeze", "fire", "lightning", "vampiric", "multishot", "homing"].indexOf(ability.specialType) === -1)) { var bossRewardMarker = new Text2("BOSS REWARD", { size: 24, fill: 0xFF0000 }); bossRewardMarker.anchor.set(0.5, 0.5); bossRewardMarker.y = -190; card.addChild(bossRewardMarker); } // Position cards in a row var xPos = startX + i * (cardWidth * cardScale + spacing) - 200; // For 4 cards, make adjustment to fit screen if (cardCount === 4) { xPos = startX + i * (cardWidth * cardScale + 10) - 300; cardScale = 0.7; // Smaller for 4 cards } card.x = xPos; card.y = 2732 / 2; card.scale.set(cardScale, cardScale); upgradeCards.push(card); upgradeContainer.addChild(card); } } function hideUpgradeScreen() { paused = false; showingUpgrades = false; upgradeContainer.visible = false; } function updateHealthBar() { if (player) { var healthPercent = player.health / player.maxHealth; healthBar.width = 200 * healthPercent; } } function gameOver() { // Update high score in storage if (monstersKilled > storage.highscore) { storage.highscore = monstersKilled; } storage.totalGamesPlayed = (storage.totalGamesPlayed || 0) + 1; // Fade out music before game over LK.playMusic('gameMusic', { fade: { start: 0.7, end: 0, duration: 1000 } }); LK.showGameOver(); } function handleClick(x, y, obj) { if (paused || !player) return; // Check if player has multishot ability and count how many var multishotCount = 0; for (var i = 0; i < player.abilities.length; i++) { if (player.abilities[i].type === "special" && player.abilities[i].specialType === "multishot") { multishotCount++; } } // Get all available arrow special types var arrowSpecials = []; for (var i = 0; i < player.abilities.length; i++) { var ability = player.abilities[i]; if (ability.type === "special") { // Check if it's an arrow-type special ability (not orbital) if (["poison", "explosive", "freeze", "fire", "lightning", "vampiric", "rebound", "homing"].indexOf(ability.specialType) !== -1) { arrowSpecials.push(ability.specialType); } } } // If multishot, create arrows in a spread pattern based on how many upgrades if (multishotCount > 0) { // Center arrow var mainArrow = player.shoot(x, y); if (mainArrow) { applyArrowProperties(mainArrow, arrowSpecials); arrows.push(mainArrow); game.addChild(mainArrow); // Determine angle spread based on upgrade count - widen as we get more arrows var angleSpread = Math.PI / 12; // Base 15 degrees var totalArrows = 2 + multishotCount; // Base 3 arrows (1 + 2) plus 1 per upgrade // Create additional arrows in a spread pattern for (var i = 1; i <= totalArrows - 1; i++) { // Alternate between left and right side var isEven = i % 2 === 0; var arrowIndex = Math.ceil(i / 2); var sideAngle = angleSpread * arrowIndex; if (isEven) { // Right side arrow var rightAngle = mainArrow.rotation + sideAngle; var rightArrow = new Arrow(); rightArrow.x = player.x; rightArrow.y = player.y; rightArrow.rotation = rightAngle; rightArrow.damage = arrowDamage * 0.8; // Side arrows do less damage rightArrow.pierce = arrowPierce; rightArrow.speed = arrowSpeed; applyArrowProperties(rightArrow, arrowSpecials); arrows.push(rightArrow); game.addChild(rightArrow); } else { // Left side arrow var leftAngle = mainArrow.rotation - sideAngle; var leftArrow = new Arrow(); leftArrow.x = player.x; leftArrow.y = player.y; leftArrow.rotation = leftAngle; leftArrow.damage = arrowDamage * 0.8; // Side arrows do less damage leftArrow.pierce = arrowPierce; leftArrow.speed = arrowSpeed; applyArrowProperties(leftArrow, arrowSpecials); arrows.push(leftArrow); game.addChild(leftArrow); } } // Play sound only once LK.getSound('shoot').play(); } } else { // Regular single arrow shot var arrow = player.shoot(x, y); if (arrow) { applyArrowProperties(arrow, arrowSpecials); arrows.push(arrow); game.addChild(arrow); } } // Helper function to apply properties and special effects to arrows function applyArrowProperties(arrow, arrowSpecials) { arrow.damage = arrowDamage; arrow.pierce = arrowPierce; arrow.speed = arrowSpeed; // If we have special arrow types, randomly select one to apply if (arrowSpecials.length > 0) { var selectedSpecial = arrowSpecials[Math.floor(Math.random() * arrowSpecials.length)]; arrow.specialType = selectedSpecial; // Visual indicators for special arrows if (selectedSpecial === "poison") { arrow.arrowBody.tint = 0x00FF00; // Green for poison arrow.arrowHead.tint = 0x00FF00; } else if (selectedSpecial === "explosive") { arrow.arrowBody.tint = 0xFF9900; // Orange for explosive arrow.arrowHead.tint = 0xFF9900; } else if (selectedSpecial === "freeze") { arrow.arrowBody.tint = 0x00FFFF; // Cyan for freeze arrow.arrowHead.tint = 0x00FFFF; } else if (selectedSpecial === "fire") { arrow.arrowBody.tint = 0xFF3300; // Orange-red for fire arrow.arrowHead.tint = 0xFF3300; } else if (selectedSpecial === "lightning") { arrow.arrowBody.tint = 0xFFFF00; // Yellow for lightning arrow.arrowHead.tint = 0xFFFF00; } else if (selectedSpecial === "vampiric") { arrow.arrowBody.tint = 0xFF00FF; // Purple for vampiric arrow.arrowHead.tint = 0xFF00FF; } else if (selectedSpecial === "homing") { arrow.arrowBody.tint = 0xFF6600; // Orange for homing arrow.arrowHead.tint = 0xFF6600; } } } } game.down = handleClick; game.update = function () { if (paused) return; // Update player if exists if (player) { player.update(); } // Spawn monsters var now = Date.now(); var spawnInterval = Math.max(2000 - currentWave * 100, 500); var monstersPerWave = 2 + Math.floor(currentWave * 0.8); // Reduced monster count for shorter waves // For boss waves, only spawn the boss if (currentWave % 10 === 0) { if (!bossSpawned && !bossDefeated) { spawnMonster(); // This will spawn the boss } } // Regular waves spawn multiple monsters else if (now - lastSpawnTime > spawnInterval && monsters.length < monstersPerWave) { spawnMonster(); } // Check for boss defeat var hasBoss = false; for (var i = 0; i < monsters.length; i++) { if (monsters[i] instanceof Boss) { hasBoss = true; break; } } // If this is a boss wave and the boss has been spawned but is no longer present if (currentWave % 10 === 0 && bossSpawned && !hasBoss && !bossDefeated) { bossDefeated = true; // Create boss defeat celebration effect var defeatEffect = new Container(); var celebrationCircle = LK.getAsset('monster', { anchorX: 0.5, anchorY: 0.5, scaleX: 10, scaleY: 10, tint: 0xFFFFFF, alpha: 0.2 }); defeatEffect.x = 2048 / 2; defeatEffect.y = 2732 / 2; game.addChild(defeatEffect); defeatEffect.addChild(celebrationCircle); // Add victory text var victoryText = new Text2("BOSS DEFEATED!", { size: 120, fill: 0x00FF00 }); victoryText.anchor.set(0.5, 0.5); defeatEffect.addChild(victoryText); // Animate the celebration effect tween(celebrationCircle, { scaleX: 20, scaleY: 20, alpha: 0 }, { duration: 2000, onFinish: function onFinish() { if (defeatEffect.parent) { defeatEffect.parent.removeChild(defeatEffect); } // Show special reward for defeating boss showUpgradeScreen(); } }); // No extra bonus points for defeating boss scoreText.setText("Hits: " + monstersKilled); // Boss defeat text animation var bonusText = new Text2("BOSS DEFEATED", { size: 80, fill: 0xFFFF00 }); bonusText.anchor.set(0.5, 0.5); bonusText.x = 2048 / 2; bonusText.y = 2732 / 2 + 150; LK.gui.addChild(bonusText); // Animate bonus text tween(bonusText, { y: bonusText.y - 100, alpha: 0 }, { duration: 2000, onFinish: function onFinish() { if (bonusText.parent) { bonusText.parent.removeChild(bonusText); } } }); } // Check if regular wave is complete - based on monsters killed rather than time if (!showingUpgrades && (currentWave % 10 !== 0 && (monstersKilled >= currentWave * 10 || monsters.length === 0 && now - lastSpawnTime > spawnInterval * 2) || currentWave % 10 === 0 && bossDefeated)) { if (!showingUpgrades) { showUpgradeScreen(); } } // Update arrows for (var i = arrows.length - 1; i >= 0; i--) { if (arrows[i].needsRemoval) { game.removeChild(arrows[i]); arrows.splice(i, 1); continue; } } // Check for collisions for (var i = arrows.length - 1; i >= 0; i--) { var arrow = arrows[i]; for (var j = monsters.length - 1; j >= 0; j--) { var monster = monsters[j]; if (arrow.canHit(monster) && arrow.intersects(monster)) { arrow.hit(monster); if (monster.takeDamage(arrow.damage)) { // Monster is defeated game.removeChild(monster); monsters.splice(j, 1); // Play pop sound when monster is killed LK.getSound('pop').play(); // Add one point regardless of monster type monstersKilled++; scoreText.setText("Score: " + monstersKilled); } if (arrow.needsRemoval) { game.removeChild(arrow); arrows.splice(i, 1); break; } } } } // Update orbital objects for (var i = orbitalObjects.length - 1; i >= 0; i--) { orbitalObjects[i].update(); } // Update stars for parallax effect for (var i = 0; i < stars.length; i++) { stars[i].update(); } // Update health bar updateHealthBar(); }; // Initialize background music with intense battle theme // Start the game initGame(); ;
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
highscore: 0,
totalGamesPlayed: 0
});
/****
* Classes
****/
var Arrow = Container.expand(function () {
var self = Container.call(this);
// Create laser beam instead of arrow
self.arrowBody = self.attachAsset('arrow', {
anchorX: 0,
anchorY: 0.5,
scaleX: 2.0,
scaleY: 0.5,
tint: 0x33CCFF // Blue laser color
});
self.arrowHead = self.attachAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
x: 80,
scaleX: 0.6,
scaleY: 0.6,
tint: 0xFFFFFF // Bright white laser impact point
});
// Add laser glow effect
self.glow = LK.getAsset('arrow', {
anchorX: 0,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 1.8,
tint: 0x33CCFF,
alpha: 0.5
});
self.addChildAt(self.glow, 0);
self.speed = 20; // Faster for lasers
self.damage = 10;
self.pierce = 0;
self.pierceCount = 0;
self.hitEnemies = [];
self.specialType = null; // To store special ability type (poison, explosive, etc.)
self.specialData = {}; // For any special ability-specific data
self.update = function () {
// Handle homing behavior if enabled
if (self.homingEnabled && monsters.length > 0) {
// Initialize the homing start time if it's not set
if (!self.homingStartTime) {
self.homingStartTime = Date.now();
}
// Check if the homing arrow has existed for more than 5 seconds
if (Date.now() - self.homingStartTime > 5000) {
self.needsRemoval = true;
return;
}
// Find nearest monster that hasn't been hit yet
var closestDist = Infinity;
var closestMonster = null;
for (var i = 0; i < monsters.length; i++) {
if (self.hitEnemies.indexOf(monsters[i].id) === -1) {
var dx = monsters[i].x - self.x;
var dy = monsters[i].y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < closestDist) {
closestDist = dist;
closestMonster = monsters[i];
}
}
}
// If we found a target, gradually adjust arrow direction
if (closestMonster) {
var targetAngle = Math.atan2(closestMonster.y - self.y, closestMonster.x - self.x);
// Normalize angles
while (targetAngle < -Math.PI) targetAngle += Math.PI * 2;
while (targetAngle > Math.PI) targetAngle -= Math.PI * 2;
while (self.rotation < -Math.PI) self.rotation += Math.PI * 2;
while (self.rotation > Math.PI) self.rotation -= Math.PI * 2;
// Find shortest direction to rotate
var diff = targetAngle - self.rotation;
if (diff > Math.PI) diff -= Math.PI * 2;
if (diff < -Math.PI) diff += Math.PI * 2;
// Adjust rotation gradually
self.rotation += diff * self.homingSpeed;
// Create trail effect for homing arrows - reduced frequency
if (LK.ticks % 6 === 0) {
var trail = new Container();
var trailParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0x33CCFF,
alpha: 0.7
});
trail.x = self.x;
trail.y = self.y;
trail.addChild(trailParticle);
game.addChild(trail);
// Remove additional particles to improve performance
// Fade out trail
tween(trailParticle, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 200,
onFinish: function onFinish() {
if (trail.parent) {
trail.parent.removeChild(trail);
}
}
});
}
}
}
// Move arrow in current direction
self.x += Math.cos(self.rotation) * self.speed;
self.y += Math.sin(self.rotation) * self.speed;
// Standard arrow removal when off screen
if (self.x < -100 || self.x > 2148 || self.y < -100 || self.y > 2832) {
self.needsRemoval = true;
}
};
self.canHit = function (monster) {
if (self.hitEnemies.indexOf(monster.id) !== -1) {
return false;
}
return true;
};
self.hit = function (monster) {
self.hitEnemies.push(monster.id);
self.pierceCount++;
// Create laser impact effect
var impact = new Container();
var impactFlash = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFFFFFF,
alpha: 0.9
});
impact.x = monster.x;
impact.y = monster.y;
impact.addChild(impactFlash);
game.addChild(impact);
// Reduce particle count for better performance
for (var i = 0; i < 3; i++) {
var angle = Math.PI * 2 * Math.random();
var energyParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0x99FFFF,
alpha: 0.8
});
energyParticle.x = Math.cos(angle) * 5;
energyParticle.y = Math.sin(angle) * 5;
impact.addChild(energyParticle);
// Animate particle spread
tween(energyParticle, {
x: energyParticle.x * 8,
y: energyParticle.y * 8,
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 200
});
}
// Animate impact flash
tween(impactFlash, {
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 200,
onFinish: function onFinish() {
if (impact.parent) {
impact.parent.removeChild(impact);
}
}
});
// Apply special effects
if (self.specialType === "poison") {
// Apply poison effect to monster
if (!monster.poisoned) {
monster.poisoned = true;
monster.poisonDamage = self.damage * 0.2; // 20% of arrow damage per tick
monster.poisonTicks = 5; // Duration in ticks
monster.body.tint = 0x00FF00; // Green tint for poisoned monsters
// Create poison cloud visual effect
var poisonCloud = new Container();
var cloudCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
tint: 0x00FF00,
alpha: 0.4
});
poisonCloud.x = monster.x;
poisonCloud.y = monster.y;
game.addChild(poisonCloud);
poisonCloud.addChild(cloudCircle);
// Animate the poison cloud
tween(cloudCircle, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
poisonCloud.parent.removeChild(poisonCloud);
}
});
}
} else if (self.specialType === "explosive") {
// Create explosion effect and damage nearby monsters
LK.effects.flashObject(self, 0xFF9900, 200);
// Create explosion visual effect
var explosion = new Container();
var explosionCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3,
tint: 0xFF9900,
alpha: 0.7
});
explosionCircle.x = self.x;
explosionCircle.y = self.y;
game.addChild(explosion);
explosion.addChild(explosionCircle);
// Animate the explosion
tween(explosionCircle, {
scaleX: 5,
scaleY: 5,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
explosion.parent.removeChild(explosion);
}
});
// Find and damage nearby monsters
var now = Date.now();
for (var i = 0; i < monsters.length; i++) {
var otherMonster = monsters[i];
if (otherMonster && otherMonster.id !== monster.id) {
var dx = otherMonster.x - self.x;
var dy = otherMonster.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 150) {
// Explosion radius
otherMonster.takeDamage(self.damage * 0.5); // 50% damage to nearby monsters
}
}
}
} else if (self.specialType === "freeze") {
// Slow down monster
monster.frozen = true;
monster.originalSpeed = monster.speed;
monster.speed = monster.speed * 0.5; // 50% slower
monster.body.tint = 0x00FFFF; // Cyan tint for frozen monsters
// Create ice/frost visual effect
var iceEffect = new Container();
var frostCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3,
tint: 0x00FFFF,
alpha: 0.5
});
iceEffect.x = monster.x;
iceEffect.y = monster.y;
game.addChild(iceEffect);
iceEffect.addChild(frostCircle);
// Add ice crystal particles
for (var i = 0; i < 6; i++) {
var iceParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0xCCFFFF
});
iceParticle.x = Math.random() * 40 - 20;
iceParticle.y = Math.random() * 40 - 20;
iceEffect.addChild(iceParticle);
}
// Animate the ice effect
tween(frostCircle, {
alpha: 0.1
}, {
duration: 2800,
onFinish: function onFinish() {
if (iceEffect.parent) {
iceEffect.parent.removeChild(iceEffect);
}
}
});
// Reset speed after 3 seconds
LK.setTimeout(function () {
if (monster && monster.frozen) {
monster.frozen = false;
monster.speed = monster.originalSpeed;
monster.body.tint = 0xFFFFFF; // Reset tint
}
}, 3000);
} else if (self.specialType === "fire") {
// Apply burning effect to monster
if (!monster.burning) {
monster.burning = true;
monster.burnDamage = self.damage * 0.3; // 30% of arrow damage per tick
monster.burnTicks = 3; // Duration in ticks
monster.body.tint = 0xFF3300; // Orange-red tint for burning monsters
// Create fire visual effect
var fireEffect = new Container();
var flameCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF3300,
alpha: 0.7
});
fireEffect.x = monster.x;
fireEffect.y = monster.y;
game.addChild(fireEffect);
fireEffect.addChild(flameCircle);
// Animate the flames with flickering effect
var fadeTime = 800;
var startTime = Date.now();
var flameAnimation = LK.setInterval(function () {
var elapsed = Date.now() - startTime;
if (elapsed >= fadeTime) {
LK.clearInterval(flameAnimation);
if (fireEffect.parent) {
fireEffect.parent.removeChild(fireEffect);
}
return;
}
// Make flames flicker by varying the alpha
flameCircle.alpha = 0.7 * (1 - elapsed / fadeTime) * (0.7 + Math.random() * 0.3);
// Move flame with monster
if (monster && !monster.destroyed) {
fireEffect.x = monster.x;
fireEffect.y = monster.y;
}
}, 60);
}
} else if (self.specialType === "lightning") {
// Chain lightning effect to nearby monsters
LK.effects.flashObject(self, 0xFFFF00, 200);
var chainedMonsters = [];
chainedMonsters.push(monster.id);
// Chain to up to 3 nearby monsters
for (var i = 0; i < monsters.length && chainedMonsters.length < 4; i++) {
var otherMonster = monsters[i];
if (otherMonster && chainedMonsters.indexOf(otherMonster.id) === -1) {
var dx = otherMonster.x - monster.x;
var dy = otherMonster.y - monster.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200) {
otherMonster.takeDamage(self.damage * 0.4);
otherMonster.body.tint = 0xFFFF00; // Yellow tint for lightning
chainedMonsters.push(otherMonster.id);
// Create lightning effect between monsters
var lightningStart = {
x: monster.x,
y: monster.y
};
var lightningEnd = {
x: otherMonster.x,
y: otherMonster.y
};
// Create lightning bolt visual effect
var bolt = new Container();
var segment = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: dist / 50,
scaleY: 0.6,
tint: 0xFFFF00
});
// Position and rotate the segment to connect the two points
segment.x = (lightningStart.x + lightningEnd.x) / 2;
segment.y = (lightningStart.y + lightningEnd.y) / 2;
segment.rotation = Math.atan2(lightningEnd.y - lightningStart.y, lightningEnd.x - lightningStart.x);
bolt.addChild(segment);
game.addChild(bolt);
// Animate the lightning bolt
tween(segment, {
alpha: 0.2
}, {
duration: 300,
onFinish: function onFinish() {
if (bolt.parent) {
bolt.parent.removeChild(bolt);
}
}
});
LK.setTimeout(function () {
if (otherMonster && otherMonster.body) {
otherMonster.body.tint = 0xFFFFFF;
}
}, 300);
}
}
}
} else if (self.specialType === "vampiric") {
// Heal player based on damage done
if (player) {
var healAmount = self.damage * 0.3; // Heal for 30% of damage dealt
player.health = Math.min(player.health + healAmount, player.maxHealth);
LK.effects.flashObject(player, 0xFF00FF, 200); // Purple flash for healing
// Create vampiric drain visual effect
var vampiricEffect = new Container();
var startPoint = {
x: monster.x,
y: monster.y
};
var endPoint = {
x: player.x,
y: player.y
};
var distance = Math.sqrt(Math.pow(endPoint.x - startPoint.x, 2) + Math.pow(endPoint.y - startPoint.y, 2));
var angleRad = Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x);
// Create the drain beam
var drainBeam = LK.getAsset('arrow', {
anchorX: 0,
anchorY: 0.5,
scaleX: distance / 50,
scaleY: 0.4,
tint: 0xFF00FF,
alpha: 0.7
});
drainBeam.x = startPoint.x;
drainBeam.y = startPoint.y;
drainBeam.rotation = angleRad;
vampiricEffect.addChild(drainBeam);
game.addChild(vampiricEffect);
// Add life particles traveling along the beam
for (var i = 0; i < 3; i++) {
var lifeParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7,
tint: 0xFF00FF,
alpha: 0.8
});
lifeParticle.x = startPoint.x;
lifeParticle.y = startPoint.y;
vampiricEffect.addChild(lifeParticle);
// Animate particles moving from monster to player
tween(lifeParticle, {
x: endPoint.x,
y: endPoint.y,
alpha: 0.3
}, {
duration: 500 + i * 100,
easing: tween.easeOut
});
}
// Fade out the beam
tween(drainBeam, {
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
if (vampiricEffect.parent) {
vampiricEffect.parent.removeChild(vampiricEffect);
}
}
});
}
// No rebounding arrows functionality
if (self.pierceCount > self.pierce) {
self.needsRemoval = true;
}
} else if (self.specialType === "homing") {
// Create homing visual effect
var homingEffect = new Container();
var targetMarker = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
tint: 0xFF6600,
alpha: 0.7
});
homingEffect.x = monster.x;
homingEffect.y = monster.y;
game.addChild(homingEffect);
homingEffect.addChild(targetMarker);
// Animate the homing effect
tween(targetMarker, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
if (homingEffect.parent) {
homingEffect.parent.removeChild(homingEffect);
}
}
});
// Set arrow to homing mode
if (!self.homingEnabled && monsters.length > 0) {
self.homingEnabled = true;
self.homingTarget = null; // Will be set in update method
self.homingSpeed = 0.1; // Turning rate
LK.effects.flashObject(self, 0xFF6600, 200);
}
}
if (self.pierceCount > self.pierce && !self.hasRebounded && !self.homingEnabled) {
self.needsRemoval = true;
}
};
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
self.id = Date.now() + Math.floor(Math.random() * 10000);
// Create larger body for boss monsters
self.body = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3
});
// Health bar for boss
self.healthBarBg = self.attachAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5,
y: -100,
scaleX: 1.5,
scaleY: 1
});
self.healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5,
y: -100,
x: -150,
scaleX: 1.5
});
// Stats will be set when spawned
self.maxHealth = 200;
self.health = self.maxHealth;
self.speed = 1;
self.damage = 20;
self.lastAttackTime = 0;
self.attackCooldown = 1500;
self.bossLevel = 1; // Will be set based on wave
// Special abilities flags
self.canSpawnMinions = false;
self.lastMinionSpawnTime = 0;
self.minionSpawnCooldown = 5000;
self.shieldActive = false;
self.update = function () {
if (player && !paused) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 10) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
var now = Date.now();
// Attack player
if (dist < 150) {
// Larger attack range
if (now - self.lastAttackTime > self.attackCooldown) {
self.lastAttackTime = now;
// Create attack effect
var attackEffect = new Container();
var attackCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 2,
tint: 0xFF0000,
alpha: 0.5
});
attackEffect.x = self.x;
attackEffect.y = self.y;
game.addChild(attackEffect);
attackEffect.addChild(attackCircle);
// Animate attack
tween(attackCircle, {
scaleX: 4,
scaleY: 4,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (attackEffect.parent) {
attackEffect.parent.removeChild(attackEffect);
}
}
});
if (player.takeDamage(self.damage)) {
LK.getSound('death').play();
gameOver();
}
}
}
// Spawn minions ability
if (self.canSpawnMinions && now - self.lastMinionSpawnTime > self.minionSpawnCooldown) {
self.lastMinionSpawnTime = now;
// Create spawn effect
var spawnEffect = new Container();
var spawnCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
tint: 0x9900FF,
alpha: 0.7
});
spawnEffect.x = self.x;
spawnEffect.y = self.y;
game.addChild(spawnEffect);
spawnEffect.addChild(spawnCircle);
// Animate spawn effect
tween(spawnCircle, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
if (spawnEffect.parent) {
spawnEffect.parent.removeChild(spawnEffect);
}
}
});
// Spawn 2-3 minions around boss
var minionCount = 2 + Math.floor(Math.random());
for (var i = 0; i < minionCount; i++) {
var angle = Math.PI * 2 * (i / minionCount);
var monster = new Monster();
monster.maxHealth = 15 + self.bossLevel * 2;
monster.health = monster.maxHealth;
monster.speed = 1.5 + self.bossLevel * 0.1;
monster.body.tint = 0x9900FF; // Mark as boss minions
monster.x = self.x + Math.cos(angle) * 150;
monster.y = self.y + Math.sin(angle) * 150;
monsters.push(monster);
game.addChild(monster);
}
}
// Update health bar
var healthPercent = self.health / self.maxHealth;
self.healthBar.width = 300 * healthPercent;
// Handle poison damage over time
if (self.poisoned && self.poisonTicks > 0) {
if (!self.lastPoisonTime || now - self.lastPoisonTime > 500) {
// Every 0.5 seconds
self.lastPoisonTime = now;
self.poisonTicks--;
self.takeDamage(self.poisonDamage);
// Create poison effect visuals
var poisonEffect = new Container();
var poisonParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x00FF00,
alpha: 0.7
});
poisonEffect.x = self.x + (Math.random() * 80 - 40);
poisonEffect.y = self.y + (Math.random() * 80 - 40);
poisonEffect.addChild(poisonParticle);
game.addChild(poisonEffect);
// Animate poison particles
tween(poisonParticle, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
onFinish: function onFinish() {
if (poisonEffect.parent) {
poisonEffect.parent.removeChild(poisonEffect);
}
}
});
// Remove poison effect when done
if (self.poisonTicks <= 0) {
self.poisoned = false;
self.body.tint = 0xFFFFFF; // Reset tint
}
}
}
// Handle burning damage over time
if (self.burning && self.burnTicks > 0) {
if (!self.lastBurnTime || now - self.lastBurnTime > 400) {
// Every 0.4 seconds
self.lastBurnTime = now;
self.burnTicks--;
self.takeDamage(self.burnDamage);
// Create fire effect visuals
var fireEffect = new Container();
var fireParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0xFF3300,
alpha: 0.7
});
fireEffect.x = self.x + (Math.random() * 80 - 40);
fireEffect.y = self.y + (Math.random() * 80 - 40);
fireEffect.addChild(fireParticle);
game.addChild(fireEffect);
// Animate fire particles rising up
tween(fireParticle, {
y: fireParticle.y - 40,
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 400,
onFinish: function onFinish() {
if (fireEffect.parent) {
fireEffect.parent.removeChild(fireEffect);
}
}
});
// Remove burn effect when done
if (self.burnTicks <= 0) {
self.burning = false;
self.body.tint = 0xFFFFFF; // Reset tint
}
}
}
// Handle shield mechanic - activate shield at 50% health
if (!self.shieldActive && self.health <= self.maxHealth * 0.5) {
self.shieldActive = true;
// Create shield visual
self.shield = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0.4,
tint: 0x3399FF
});
self.addChild(self.shield);
// Animate shield activation
tween(self.shield, {
alpha: 0.6
}, {
duration: 1000,
onFinish: function onFinish() {
// Pulse animation
function pulseShield() {
if (self.shield && self.shield.parent) {
tween(self.shield, {
alpha: 0.3
}, {
duration: 1000,
onFinish: function onFinish() {
tween(self.shield, {
alpha: 0.6
}, {
duration: 1000,
onFinish: pulseShield
});
}
});
}
}
pulseShield();
}
});
}
}
};
self.takeDamage = function (amount) {
// Reduce damage if shield is active
if (self.shieldActive) {
amount = amount * 0.5;
}
self.health -= amount;
// Flash red when hit
LK.effects.flashObject(self, 0xFF0000, 200);
LK.getSound('hit').play();
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var Monster = Container.expand(function () {
var self = Container.call(this);
self.id = Date.now() + Math.floor(Math.random() * 10000);
self.body = self.attachAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 1.5,
tint: 0x9900FF // Purple and blue color
});
self.maxHealth = 20;
self.health = self.maxHealth;
self.speed = 2;
self.damage = 10;
self.lastAttackTime = 0;
self.attackCooldown = 1000;
self.update = function () {
if (player && !paused) {
var dx = player.x - self.x;
var dy = player.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 10) {
self.x += dx / dist * self.speed;
self.y += dy / dist * self.speed;
}
var now = Date.now();
// Attack player
if (dist < 100) {
if (now - self.lastAttackTime > self.attackCooldown) {
self.lastAttackTime = now;
if (player.takeDamage(self.damage)) {
LK.getSound('death').play();
gameOver();
}
}
}
// Handle poison damage over time
if (self.poisoned && self.poisonTicks > 0) {
if (!self.lastPoisonTime || now - self.lastPoisonTime > 500) {
// Every 0.5 seconds
self.lastPoisonTime = now;
self.poisonTicks--;
self.takeDamage(self.poisonDamage);
// Create poison effect visuals
var poisonEffect = new Container();
var poisonParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0x00FF00,
alpha: 0.7
});
poisonEffect.x = self.x + (Math.random() * 40 - 20);
poisonEffect.y = self.y + (Math.random() * 40 - 20);
poisonEffect.addChild(poisonParticle);
game.addChild(poisonEffect);
// Animate poison particles
tween(poisonParticle, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
onFinish: function onFinish() {
if (poisonEffect.parent) {
poisonEffect.parent.removeChild(poisonEffect);
}
}
});
// Remove poison effect when done
if (self.poisonTicks <= 0) {
self.poisoned = false;
self.body.tint = 0xFFFFFF; // Reset tint
}
}
}
// Handle burning damage over time
if (self.burning && self.burnTicks > 0) {
if (!self.lastBurnTime || now - self.lastBurnTime > 400) {
// Every 0.4 seconds
self.lastBurnTime = now;
self.burnTicks--;
self.takeDamage(self.burnDamage);
// Create fire effect visuals
var fireEffect = new Container();
var fireParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xFF3300,
alpha: 0.7
});
fireEffect.x = self.x + (Math.random() * 40 - 20);
fireEffect.y = self.y + (Math.random() * 40 - 20);
fireEffect.addChild(fireParticle);
game.addChild(fireEffect);
// Animate fire particles rising up
tween(fireParticle, {
y: fireParticle.y - 30,
alpha: 0,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 400,
onFinish: function onFinish() {
if (fireEffect.parent) {
fireEffect.parent.removeChild(fireEffect);
}
}
});
// Remove burn effect when done
if (self.burnTicks <= 0) {
self.burning = false;
self.body.tint = 0xFFFFFF; // Reset tint
}
}
}
}
};
self.takeDamage = function (amount) {
self.health -= amount;
// Flash red when hit
LK.effects.flashObject(self, 0xFF0000, 200);
LK.getSound('hit').play();
if (self.health <= 0) {
return true;
}
return false;
};
return self;
});
var OrbitalObject = Container.expand(function (type, angle, distance) {
var self = Container.call(this);
self.type = type;
self.angle = angle || 0;
self.distance = distance || 300; // Increased distance from 200 to 300 to allow monsters to pass more easily
self.speed = 0.01; // Reduced speed for orbital objects
self.target = null;
self.lastAttackTime = 0;
// Set different properties based on type
if (type === "shields") {
self.damage = 5;
self.attackCooldown = 800; // Faster attack rate
} else if (type === "swords") {
self.damage = 12; // Higher damage
self.attackCooldown = 1200;
} else if (type === "orbs") {
self.damage = 8;
self.attackCooldown = 1000;
} else if (type === "flame_barrier") {
self.damage = 7;
self.attackCooldown = 600; // Very fast attack rate for flame barrier
} else if (type === "ice_shards") {
self.damage = 6;
self.attackCooldown = 900;
} else if (type === "spirit_guardians") {
self.damage = 10;
self.attackCooldown = 1100;
} else {
self.damage = 7;
self.attackCooldown = 1000;
}
// Create the visual based on type
if (type === "shields") {
// Create holographic energy barrier visual
self.body = self.attachAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7
});
// Create energy barrier multilayer glow effect
self.glow = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.4
});
// Add inner energy core
self.core = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
alpha: 0.9,
tint: 0xFFFFFF
});
self.addChild(self.core);
self.addChildAt(self.glow, 0);
self.body.tint = 0x33CCFF; // Bright blue energy barrier
self.glow.tint = 0x33CCFF; // Matching glow color
// Add futuristic pulse animation to the shield
tween(self.glow, {
scaleX: 1.1,
scaleY: 1.1,
alpha: 0.6
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function pulseShield() {
tween(self.glow, {
scaleX: 0.9,
scaleY: 0.9,
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: pulseShield
});
}
});
// Add rotating core animation
tween(self.core, {
rotation: Math.PI * 2
}, {
duration: 3000,
onFinish: function rotateCoreLoop() {
self.core.rotation = 0;
tween(self.core, {
rotation: Math.PI * 2
}, {
duration: 3000,
onFinish: rotateCoreLoop
});
}
});
} else if (type === "swords") {
// Create energy blade instead of physical sword
self.body = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.4
});
self.body.tint = 0xE6E6FF; // Glowing energy blade
// Add energy core and hilt
self.hilt = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
x: -30,
tint: 0x666666 // Dark metal hilt
});
self.addChild(self.hilt);
// Add energy blade glow
self.bladeGlow = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.2,
scaleY: 0.6,
alpha: 0.4,
tint: 0xE6E6FF
});
self.addChildAt(self.bladeGlow, 0);
// Add blade flicker animation
tween(self.bladeGlow, {
alpha: 0.6,
scaleY: 0.7
}, {
duration: 500 + Math.random() * 500,
onFinish: function flickerBlade() {
tween(self.bladeGlow, {
alpha: 0.3,
scaleY: 0.5
}, {
duration: 500 + Math.random() * 500,
onFinish: flickerBlade
});
}
});
} else if (type === "orbs") {
// Create plasma orb with energy core
self.body = self.attachAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
self.body.tint = 0xFFCC00; // Yellow plasma orb
// Add energy core
self.core = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFFFFFF,
alpha: 0.9
});
self.addChild(self.core);
// Add outer energy field
self.field = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7,
alpha: 0.3,
tint: 0xFFCC00
});
self.addChildAt(self.field, 0);
// Add plasma fluctuation animation
tween(self.field, {
scaleX: 0.9,
scaleY: 0.9,
alpha: 0.4
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function fluctuatePlasma() {
tween(self.field, {
scaleX: 0.7,
scaleY: 0.7,
alpha: 0.2
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: fluctuatePlasma
});
}
});
// Add energy arcs occasionally
self.lastArcTime = 0;
} else if (type === "flame_barrier") {
// Create plasma flame visual
self.body = self.attachAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6
});
self.body.tint = 0xFF3300; // Orange-red for flame barrier
// Add flame core
self.flameCore = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFFFF00,
// Yellow hot core
alpha: 0.9
});
self.addChild(self.flameCore);
// Add outer flame aura
self.flameAura = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.3,
tint: 0xFF3300
});
self.addChildAt(self.flameAura, 0);
// Add flame flicker animation
tween(self.flameAura, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.5
}, {
duration: 300 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function flickerFlame() {
tween(self.flameAura, {
scaleX: 0.8,
scaleY: 0.8,
alpha: 0.2
}, {
duration: 300 + Math.random() * 200,
easing: tween.easeIn,
onFinish: flickerFlame
});
}
});
} else if (type === "ice_shards") {
// Create crystalline ice structure
self.body = self.attachAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0xDDEEFF // Light blue-white for ice crystals
});
// Add crystal facets
for (var i = 0; i < 3; i++) {
var angle = Math.PI * 2 * (i / 3);
var shard = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.15,
rotation: angle,
tint: 0x99CCFF,
alpha: 0.9
});
self.addChild(shard);
}
// Add frost aura
self.frostAura = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.9,
scaleY: 0.9,
alpha: 0.2,
tint: 0xBBDDFF
});
self.addChildAt(self.frostAura, 0);
// Add ice crystal shimmer animation
tween(self.body, {
tint: 0xAACCFF
}, {
duration: 1500,
onFinish: function shimmerIce() {
tween(self.body, {
tint: 0xDDEEFF
}, {
duration: 1500,
onFinish: shimmerIce
});
}
});
} else if (type === "spirit_guardians") {
// Create ethereal spirit form
self.body = self.attachAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3
});
self.body.tint = 0xDDDDFF; // Pale blue-white for spirits
// Add spirit core
self.spiritCore = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF,
alpha: 0.7
});
self.addChild(self.spiritCore);
// Add ethereal aura
self.spiritAura = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3.5,
scaleY: 3.5,
alpha: 0.15,
tint: 0xEEEEFF
});
self.addChildAt(self.spiritAura, 0);
// Add ethereal pulsing animation
tween(self.spiritAura, {
alpha: 0.25,
scaleX: 3.7,
scaleY: 3.7
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function pulseSpirit() {
tween(self.spiritAura, {
alpha: 0.15,
scaleX: 3.5,
scaleY: 3.5
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: pulseSpirit
});
}
});
}
self.update = function () {
if (!player) return;
// Track previous position for trail effect
if (self.prevX === undefined) {
self.prevX = self.x;
self.prevY = self.y;
self.lastTrailTime = Date.now();
}
// Update position around player
self.angle += self.speed;
self.x = player.x + Math.cos(self.angle) * self.distance;
self.y = player.y + Math.sin(self.angle) * self.distance;
// Rotate object to face direction of movement
if (self.type === "swords" || self.type === "ice_shards") {
self.rotation = self.angle + Math.PI / 2;
}
// Create energy trail effect - reduced frequency for better performance
var now = Date.now();
if (now - self.lastTrailTime > 250) {
// Create trail less frequently (250ms instead of 100ms)
self.lastTrailTime = now;
// Only create trail if moved enough
var dx = self.x - self.prevX;
var dy = self.y - self.prevY;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 20) {
//{dn} // Increased threshold for trail creation
var trail = new Container();
var trailParticle;
// Different trail effects for different orbital types
if (self.type === "shields") {
trailParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0x33CCFF,
alpha: 0.5
});
} else if (self.type === "swords") {
trailParticle = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.2,
rotation: self.rotation,
tint: 0xE6E6FF,
alpha: 0.4
});
} else if (self.type === "orbs") {
trailParticle = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0xFFCC00,
alpha: 0.4
});
} else if (self.type === "flame_barrier") {
trailParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFF3300,
alpha: 0.5
});
} else if (self.type === "ice_shards") {
trailParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: 0x99CCFF,
alpha: 0.4
});
} else if (self.type === "spirit_guardians") {
trailParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xDDDDFF,
alpha: 0.3
});
}
trail.x = self.x;
trail.y = self.y;
trail.addChild(trailParticle);
game.addChild(trail);
// Animate trail fadeout - shorter duration for better performance
tween(trailParticle, {
alpha: 0,
scaleX: trailParticle.scaleX * 0.5,
scaleY: trailParticle.scaleY * 0.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (trail.parent) {
trail.parent.removeChild(trail);
}
}
});
// Update previous position
self.prevX = self.x;
self.prevY = self.y;
}
}
// Check for collisions with monsters
for (var i = monsters.length - 1; i >= 0; i--) {
var monster = monsters[i];
if (self.intersects(monster)) {
var now = Date.now();
if (now - self.lastAttackTime > self.attackCooldown) {
self.lastAttackTime = now;
// Different effects based on type
if (self.type === "shields") {
// Shields push monsters back
var dx = monster.x - player.x;
var dy = monster.y - player.y;
var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
var pushX = dx / dist * 50;
var pushY = dy / dist * 50;
monster.x += pushX;
monster.y += pushY;
// Create energy barrier impact effect
var shieldEffect = new Container();
var impactWave = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x33CCFF,
alpha: 0.8
});
shieldEffect.x = self.x;
shieldEffect.y = self.y;
game.addChild(shieldEffect);
shieldEffect.addChild(impactWave);
// Add energy discharge particles
for (var p = 0; p < 8; p++) {
var angle = Math.PI * 2 * (p / 8);
var particle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0x99EEFF,
alpha: 0.9
});
particle.x = Math.cos(angle) * 20;
particle.y = Math.sin(angle) * 20;
impactWave.addChild(particle);
// Animate particles outward
tween(particle, {
x: particle.x * 4,
y: particle.y * 4,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
// Animate shield effect with ripple
tween(impactWave, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 500,
onFinish: function onFinish() {
if (shieldEffect.parent) {
shieldEffect.parent.removeChild(shieldEffect);
}
}
});
}
// Energy discharge effect
LK.effects.flashObject(self, 0x33CCFF, 200);
// Play orbital hit sound
LK.getSound('orbital_hit').play();
// Create energy ripple effect
var energyRipple = new Container();
var rippleWave = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
tint: 0x33CCFF,
alpha: 0.4
});
energyRipple.x = monster.x;
energyRipple.y = monster.y;
game.addChild(energyRipple);
energyRipple.addChild(rippleWave);
// Animate ripple
tween(rippleWave, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
if (energyRipple.parent) {
energyRipple.parent.removeChild(energyRipple);
}
}
});
monster.takeDamage(5); // Shields do a little damage
} else if (self.type === "swords" || self.type === "orbs") {
// Swords and orbs deal damage
if (monster.takeDamage(self.damage)) {
game.removeChild(monster);
monsters.splice(i, 1);
// Play pop sound when monster is killed
LK.getSound('pop').play();
monstersKilled++;
scoreText.setText("Score: " + monstersKilled);
}
if (self.type === "swords") {
// Create sword slash effect
var slashEffect = new Container();
var slash = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 0.8,
tint: 0xFFFFFF,
alpha: 0.8
});
slashEffect.x = monster.x;
slashEffect.y = monster.y;
slashEffect.rotation = Math.random() * Math.PI;
slashEffect.addChild(slash);
game.addChild(slashEffect);
// Add impact particles
for (var p = 0; p < 5; p++) {
var sparkParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFFCCCC,
alpha: 0.9
});
sparkParticle.x = Math.random() * 30 - 15;
sparkParticle.y = Math.random() * 30 - 15;
slashEffect.addChild(sparkParticle);
// Animate particles
tween(sparkParticle, {
x: sparkParticle.x * 3,
y: sparkParticle.y * 3,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
}
// Animate the slash
tween(slash, {
scaleX: 4,
alpha: 0
}, {
duration: 250,
onFinish: function onFinish() {
if (slashEffect.parent) {
slashEffect.parent.removeChild(slashEffect);
}
}
});
LK.effects.flashObject(self, 0xFF0000, 200);
// Play orbital hit sound
LK.getSound('orbital_hit').play();
} else {
// Orbs have electricity effect
// Create electricity effect
var zapEffect = new Container();
var energyCore = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4,
scaleY: 0.4,
tint: 0xFFFF00,
alpha: 0.8
});
zapEffect.x = monster.x;
zapEffect.y = monster.y;
zapEffect.addChild(energyCore);
game.addChild(zapEffect);
// Add lightning bolts
for (var b = 0; b < 4; b++) {
var angle = Math.PI * 2 * (b / 4);
var boltLength = 40 + Math.random() * 20;
var bolt = LK.getAsset('arrow', {
anchorX: 0,
anchorY: 0.5,
scaleX: boltLength / 50,
scaleY: 0.3,
tint: 0xFFFF99,
alpha: 0.9
});
bolt.rotation = angle;
zapEffect.addChild(bolt);
// Animate each bolt
tween(bolt, {
scaleY: 0.1,
alpha: 0
}, {
duration: 200 + Math.random() * 200
});
}
// Animate the energy core
tween(energyCore, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
if (zapEffect.parent) {
zapEffect.parent.removeChild(zapEffect);
}
}
});
LK.effects.flashObject(self, 0xFFFF00, 200);
// Play orbital hit sound
LK.getSound('orbital_hit').play();
}
} else if (self.type === "flame_barrier") {
// Flame barrier burns enemies
if (!monster.burning) {
monster.burning = true;
monster.burnDamage = self.damage * 0.2;
monster.burnTicks = 3;
monster.body.tint = 0xFF3300;
// Create flame burst effect
var flameBurst = new Container();
var burstCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFF3300,
alpha: 0.7
});
flameBurst.x = monster.x;
flameBurst.y = monster.y;
game.addChild(flameBurst);
flameBurst.addChild(burstCircle);
// Add flame particles
for (var f = 0; f < 6; f++) {
var flameParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.7,
scaleY: 0.7,
tint: 0xFF5500,
alpha: 0.8
});
flameParticle.x = Math.random() * 40 - 20;
flameParticle.y = Math.random() * 40 - 20;
flameBurst.addChild(flameParticle);
// Animate flame particle
tween(flameParticle, {
x: flameParticle.x * 2,
y: flameParticle.y - 30,
alpha: 0,
scaleX: 0.3,
scaleY: 0.3
}, {
duration: 600,
easing: tween.easeOut
});
}
// Animate the flame burst
tween(burstCircle, {
scaleX: 2.0,
scaleY: 2.0,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
if (flameBurst.parent) {
flameBurst.parent.removeChild(flameBurst);
}
}
});
}
if (monster.takeDamage(self.damage)) {
game.removeChild(monster);
monsters.splice(i, 1);
// Play pop sound when monster is killed
LK.getSound('pop').play();
monstersKilled++;
scoreText.setText("Score: " + monstersKilled);
}
LK.effects.flashObject(self, 0xFF3300, 200);
// Play orbital hit sound
LK.getSound('orbital_hit').play();
} else if (self.type === "ice_shards") {
// Ice shards slow enemies
monster.frozen = true;
monster.originalSpeed = monster.speed;
monster.speed = monster.speed * 0.7;
monster.body.tint = 0x99CCFF;
// Create frost burst effect
var frostBurst = new Container();
var frostCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.2,
scaleY: 1.2,
tint: 0x99CCFF,
alpha: 0.6
});
frostBurst.x = monster.x;
frostBurst.y = monster.y;
game.addChild(frostBurst);
frostBurst.addChild(frostCircle);
// Add crystal shards
for (var s = 0; s < 8; s++) {
var iceFragment = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xCCEEFF,
alpha: 0.9
});
var angle = Math.PI * 2 * (s / 8);
var distance = 30;
iceFragment.x = Math.cos(angle) * distance;
iceFragment.y = Math.sin(angle) * distance;
frostBurst.addChild(iceFragment);
// Animate ice fragments expanding outward
tween(iceFragment, {
x: iceFragment.x * 2.5,
y: iceFragment.y * 2.5,
alpha: 0,
rotation: Math.random() * Math.PI
}, {
duration: 800,
easing: tween.easeOut
});
}
// Animate the frost circle
tween(frostCircle, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 700,
onFinish: function onFinish() {
if (frostBurst.parent) {
frostBurst.parent.removeChild(frostBurst);
}
}
});
// Reset after 2 seconds
LK.setTimeout(function () {
if (monster && monster.frozen) {
monster.frozen = false;
monster.speed = monster.originalSpeed;
monster.body.tint = 0xFFFFFF;
}
}, 2000);
if (monster.takeDamage(self.damage)) {
game.removeChild(monster);
monsters.splice(i, 1);
// Play pop sound when monster is killed
LK.getSound('pop').play();
monstersKilled++;
scoreText.setText("Score: " + monstersKilled);
}
LK.effects.flashObject(self, 0x99CCFF, 200);
// Play orbital hit sound
LK.getSound('orbital_hit').play();
} else if (self.type === "spirit_guardians") {
// Spirit guardians pass through enemies
if (monster.takeDamage(self.damage)) {
game.removeChild(monster);
monsters.splice(i, 1);
// Play pop sound when monster is killed
LK.getSound('pop').play();
monstersKilled++;
scoreText.setText("Score: " + monstersKilled);
}
LK.effects.flashObject(self, 0xDDDDFF, 200);
// Play orbital hit sound
LK.getSound('orbital_hit').play();
// Spirits heal player slightly
if (player) {
player.health = Math.min(player.health + 1, player.maxHealth);
// Create spirit healing effect
var spiritEffect = new Container();
var spiritGlow = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5,
tint: 0xDDDDFF,
alpha: 0.7
});
spiritEffect.x = monster.x;
spiritEffect.y = monster.y;
spiritEffect.addChild(spiritGlow);
game.addChild(spiritEffect);
// Create healing link to player
var healLink = LK.getAsset('arrow', {
anchorX: 0,
anchorY: 0.5,
tint: 0xEEEEFF,
alpha: 0.5
});
// Calculate distance and angle to player
var dx = player.x - monster.x;
var dy = player.y - monster.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
healLink.rotation = angle;
healLink.scaleX = distance / 50;
spiritEffect.addChild(healLink);
// Add ethereal particles floating to player
for (var h = 0; h < 3; h++) {
var healParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xFFFFFF,
alpha: 0.8
});
healParticle.x = 0;
healParticle.y = 0;
spiritEffect.addChild(healParticle);
// Animate particles along path to player
tween(healParticle, {
x: dx,
y: dy,
alpha: 0
}, {
duration: 600 + h * 100,
easing: tween.easeInOut
});
}
// Animate the spirit effect
tween(spiritGlow, {
scaleX: 3.5,
scaleY: 3.5,
alpha: 0
}, {
duration: 800,
onFinish: function onFinish() {
if (spiritEffect.parent) {
spiritEffect.parent.removeChild(spiritEffect);
}
}
});
}
}
}
}
}
};
return self;
});
var Player = Container.expand(function () {
var self = Container.call(this);
self.body = self.attachAsset('player', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2.5,
scaleY: 2.5
});
// Modify ship body appearance
self.body.tint = 0x3366CC; // Deeper blue for ship body
// Create cockpit
self.cockpit = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
x: 20,
tint: 0x99CCFF // Light blue cockpit
});
self.addChild(self.cockpit);
// Add thruster effect for spaceship with animation
self.thruster = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 1.2,
x: -40,
tint: 0xFF6600
});
self.addChild(self.thruster);
// Animate thruster pulsing effect
tween(self.thruster, {
scaleY: 0.8,
alpha: 0.7
}, {
duration: 500,
onFinish: function pulseThruster() {
tween(self.thruster, {
scaleY: 1.2,
alpha: 1
}, {
duration: 500,
onFinish: pulseThruster
});
}
});
// Add wing details with metallic appearance
self.leftWing = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.3,
x: -20,
y: 25,
rotation: Math.PI / 4,
tint: 0x99AACC
});
self.addChild(self.leftWing);
self.rightWing = LK.getAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.8,
scaleY: 0.3,
x: -20,
y: -25,
rotation: -Math.PI / 4,
tint: 0x99AACC
});
self.addChild(self.rightWing);
// Add wing tip lights
self.leftWingLight = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
x: self.leftWing.x + 30,
y: self.leftWing.y + 15,
tint: 0xFF3333 // Red navigation light
});
self.addChild(self.leftWingLight);
self.rightWingLight = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
x: self.rightWing.x + 30,
y: self.rightWing.y - 15,
tint: 0x33FF33 // Green navigation light
});
self.addChild(self.rightWingLight);
// Create thruster particle emitter function
self.lastParticleTime = 0;
self.maxHealth = 100;
self.health = self.maxHealth;
self.attackSpeed = 2;
self.lastAttackTime = 0;
self.movementSpeed = 0;
self.abilities = [];
self.shoot = function (targetX, targetY) {
var now = Date.now();
if (now - self.lastAttackTime < 1000 / self.attackSpeed) {
return null;
}
self.lastAttackTime = now;
// Rotate spaceship to face the target smoothly
var angle = Math.atan2(targetY - self.y, targetX - self.x);
// Animate ship rotation for smoother movement
tween(self, {
rotation: angle
}, {
duration: 150,
easing: tween.easeOut
});
// Create recoil effect - ship moves slightly backward
var recoilDistance = 10;
var recoilX = self.x - Math.cos(angle) * recoilDistance;
var recoilY = self.y - Math.sin(angle) * recoilDistance;
tween(self, {
x: recoilX,
y: recoilY
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Return to original position
tween(self, {
x: self.x + Math.cos(angle) * recoilDistance,
y: self.y + Math.sin(angle) * recoilDistance
}, {
duration: 300,
easing: tween.easeOutElastic
});
}
});
// Create laser beam (arrow)
var arrow = new Arrow();
arrow.x = self.x + Math.cos(angle) * 50; // Start from front of ship
arrow.y = self.y + Math.sin(angle) * 50;
arrow.rotation = angle;
// Create muzzle flash effect for laser
var laserFlash = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.0,
scaleY: 1.0,
tint: 0x99FFFF,
alpha: 0.9
});
laserFlash.x = self.x + Math.cos(angle) * 60;
laserFlash.y = self.y + Math.sin(angle) * 60;
game.addChild(laserFlash);
// Animate laser flash
tween(laserFlash, {
scaleX: 0.2,
scaleY: 0.2,
alpha: 0
}, {
duration: 150,
onFinish: function onFinish() {
if (laserFlash.parent) {
laserFlash.parent.removeChild(laserFlash);
}
}
});
// Create enhanced thruster animation effect
var thrusterFlare = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.5,
scaleY: 2.0,
tint: 0xFF9900,
alpha: 0.9
});
thrusterFlare.x = self.x - Math.cos(angle) * 40;
thrusterFlare.y = self.y - Math.sin(angle) * 40;
thrusterFlare.rotation = angle + Math.PI;
game.addChild(thrusterFlare);
// Animate thruster flare with more dynamic effect
tween(thrusterFlare, {
scaleX: 0.2,
scaleY: 0.2,
alpha: 0
}, {
duration: 300,
//{kk} // Reduced duration
easing: tween.easeOut,
onFinish: function onFinish() {
if (thrusterFlare.parent) {
thrusterFlare.parent.removeChild(thrusterFlare);
}
}
});
// Create fewer thruster particles
for (var i = 0; i < 2; i++) {
//{kq} // Reduced from 5 to 2
var particleDelay = i * 50; // Stagger particle creation
LK.setTimeout(function () {
var thrusterParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.4 + Math.random() * 0.3,
scaleY: 0.4 + Math.random() * 0.3,
tint: Math.random() > 0.5 ? 0xFF3300 : 0xFFAA00,
alpha: 0.7
});
// Random offset from center of thruster
var particleOffset = Math.random() * 15;
var offsetAngle = angle + Math.PI + (Math.random() * 0.5 - 0.25);
thrusterParticle.x = self.x - Math.cos(angle) * 40 + Math.cos(offsetAngle) * particleOffset;
thrusterParticle.y = self.y - Math.sin(angle) * 40 + Math.sin(offsetAngle) * particleOffset;
game.addChild(thrusterParticle);
// Animate particle with varying speed - shorter duration
tween(thrusterParticle, {
x: thrusterParticle.x - Math.cos(angle) * (50 + Math.random() * 30),
y: thrusterParticle.y - Math.sin(angle) * (50 + Math.random() * 30),
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 250,
//{kA} // Fixed shorter duration
easing: tween.easeOut,
onFinish: function onFinish() {
if (thrusterParticle.parent) {
thrusterParticle.parent.removeChild(thrusterParticle);
}
}
});
}, particleDelay);
}
// Play laser sound effect
LK.getSound('shoot').play();
return arrow;
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
// Play death sound when player dies
LK.getSound('death').play();
return true;
}
return false;
};
self.addAbility = function (ability) {
self.abilities.push(ability);
// Apply ability effects
if (ability.type === "attackSpeed") {
self.attackSpeed += ability.value;
} else if (ability.type === "health") {
self.maxHealth += ability.value;
self.health = Math.min(self.health + ability.value, self.maxHealth);
} else if (ability.type === "arrowDamage") {
arrowDamage += ability.value;
} else if (ability.type === "arrowPierce") {
arrowPierce += ability.value;
} else if (ability.type === "arrowSpeed") {
arrowSpeed += ability.value;
} else if (ability.type === "special") {
console.log("Added special ability: " + ability.specialType);
// Track special abilities
if (["poison", "explosive", "freeze", "fire", "lightning", "vampiric", "multishot", "rebound", "homing"].indexOf(ability.specialType) !== -1) {
// Track arrow special abilities
acquiredSpecialTypes.push(ability.specialType);
}
// Create orbital objects for shield, sword, orb, etc. abilities
if (["shields", "swords", "orbs", "flame_barrier", "ice_shards", "spirit_guardians"].indexOf(ability.specialType) !== -1) {
// Track orbital special abilities
acquiredOrbitalTypes.push(ability.specialType);
// Create 3 objects for each type, evenly spaced around the player
// Calculate an offset angle to prevent collision with existing orbital objects
var baseOffset = 0;
if (orbitalObjects.length > 0) {
// If we already have orbital objects, offset the new ones to avoid overlap
baseOffset = Math.PI / (3 + orbitalObjects.length / 3); // Stagger based on number of existing orbital groups
}
for (var i = 0; i < 3; i++) {
var angle = Math.PI * 2 / 3 * i + baseOffset;
var orbitalObj = new OrbitalObject(ability.specialType, angle);
// Slightly vary the orbital distance to prevent collisions
orbitalObj.distance = 300 + orbitalObjects.length / 3 * 20; // Increase orbit radius for each new group
orbitalObjects.push(orbitalObj);
game.addChild(orbitalObj);
}
}
}
};
// Add update method for continuous effects
self.update = function () {
// Generate idle thruster particles less frequently
var now = Date.now();
if (now - self.lastParticleTime > 350) {
// Emit particles less frequently (350ms instead of 150ms)
self.lastParticleTime = now;
// Create idle thruster particle
var idleParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.3,
scaleY: 0.3,
tint: Math.random() > 0.3 ? 0xFF6600 : 0xFFAA00,
alpha: 0.6
});
// Position behind ship based on current rotation
var backAngle = self.rotation + Math.PI;
var offset = Math.random() * 10 - 5;
var perpendicular = backAngle + Math.PI / 2;
idleParticle.x = self.x + Math.cos(backAngle) * 40 + Math.cos(perpendicular) * offset;
idleParticle.y = self.y + Math.sin(backAngle) * 40 + Math.sin(perpendicular) * offset;
game.addChild(idleParticle);
// Animate particle with shorter duration
tween(idleParticle, {
x: idleParticle.x + Math.cos(backAngle) * 30,
y: idleParticle.y + Math.sin(backAngle) * 30,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0
}, {
duration: 250,
//{lb} // Reduced duration
easing: tween.easeOut,
onFinish: function onFinish() {
if (idleParticle.parent) {
idleParticle.parent.removeChild(idleParticle);
}
}
});
}
// Blink wing tip lights less frequently
if (LK.ticks % 120 === 0) {
// Every 2 seconds (120 frames) instead of every second
tween(self.leftWingLight, {
alpha: 0.4
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.leftWingLight, {
alpha: 1
}, {
duration: 300
});
}
});
// Blink opposite wing with delay
LK.setTimeout(function () {
tween(self.rightWingLight, {
alpha: 0.4
}, {
duration: 300,
onFinish: function onFinish() {
tween(self.rightWingLight, {
alpha: 1
}, {
duration: 300
});
}
});
}, 500);
}
};
return self;
});
var Star = Container.expand(function () {
var self = Container.call(this);
// Create star visual with random size and color
var size = 1 + Math.random() * 3;
// Generate random star colors ranging from blue to purple to white
var colorChoice = Math.random();
var starTint;
if (colorChoice < 0.3) {
starTint = 0xCCCCFF; // Light blue/lavender
} else if (colorChoice < 0.6) {
starTint = 0xDDA0DD; // Light purple
} else if (colorChoice < 0.8) {
starTint = 0xFFFFFF; // White
} else {
starTint = 0xFFD700; // Gold/yellow for bright stars
}
self.body = self.attachAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: size / 5,
scaleY: size / 5,
tint: starTint,
alpha: 0.3 + Math.random() * 0.7
});
// Set random speed for parallax effect
self.speed = 0.1 + Math.random() * 0.3;
// Twinkle animation
self.twinkle = function () {
var targetAlpha = 0.3 + Math.random() * 0.7;
var duration = 1000 + Math.random() * 2000;
tween(self.body, {
alpha: targetAlpha
}, {
duration: duration,
onFinish: self.twinkle
});
};
// Start twinkling
self.twinkle();
// Update method for star movement
self.update = function () {
// Move star down slowly for parallax effect
self.y += self.speed;
// If star moves off screen, reset position
if (self.y > 2832) {
self.y = -20;
self.x = Math.random() * 2048;
}
};
return self;
});
var UpgradeCard = Container.expand(function (ability) {
var self = Container.call(this);
self.ability = ability;
self.background = self.attachAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5
});
self.icon = self.attachAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
y: -120
});
// Title
self.title = new Text2(ability.name, {
size: 36,
fill: 0x000000
});
self.title.anchor.set(0.5, 0.5);
self.title.y = -180; // Move title position above the colored icon
self.addChild(self.title);
// Description
self.description = new Text2(ability.description, {
size: 24,
fill: 0x333333,
wordWrap: 350
});
self.description.anchor.set(0.5, 0.5);
self.description.y = 80; // Increased y position to avoid collision with title
self.addChild(self.description);
self.down = function (x, y, obj) {
tween(self, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100
});
};
self.up = function (x, y, obj) {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (player) {
player.addAbility(self.ability);
hideUpgradeScreen();
startNextWave();
LK.getSound('levelup').play();
}
}
});
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 // Black background for Space Archers
});
/****
* Game Code
****/
// Game variables
var player;
var monsters = [];
var arrows = [];
var upgradeCards = [];
var orbitalObjects = [];
var stars = []; // Array to hold background stars
var paused = false;
var currentWave = 0;
var monstersKilled = 0;
var lastSpawnTime = 0;
var showingUpgrades = false;
var bossSpawned = false;
var bossDefeated = false;
// Track acquired special abilities to prevent duplicates
var acquiredSpecialTypes = [];
var acquiredOrbitalTypes = [];
// Arrow properties
var arrowDamage = 20;
var arrowPierce = 1;
var arrowSpeed = 18;
// Abilities pool
var abilitiesPool = [{
name: "Quick Shot",
description: "Increase attack speed by 15%",
type: "attackSpeed",
value: 0.15
}, {
name: "Heavy Arrow",
description: "Increase arrow damage by 5",
type: "arrowDamage",
value: 5
}, {
name: "Fortify",
description: "Increase max health by 20",
type: "health",
value: 20
}, {
name: "Piercing Shot",
description: "Arrows pierce through one additional enemy",
type: "arrowPierce",
value: 1
}, {
name: "Swift Arrow",
description: "Increase arrow speed by 2",
type: "arrowSpeed",
value: 2
}];
// Special power-up abilities
var specialAbilitiesPool = [{
name: "Poison Lazers",
description: "Lazers poison enemies, dealing damage over time",
type: "special",
specialType: "poison"
}, {
name: "Explosive Lazers",
description: "Lazers explode on impact, damaging nearby enemies",
type: "special",
specialType: "explosive"
}, {
name: "Freezing Lazers",
description: "Lazers slow down enemies for a short time",
type: "special",
specialType: "freeze"
}, {
name: "Fire Lazers",
description: "Lazers burn enemies with intense flames",
type: "special",
specialType: "fire"
}, {
name: "Lightning Lazers",
description: "Lazers chain lightning between nearby enemies",
type: "special",
specialType: "lightning"
}, {
name: "Vampiric Lazers",
description: "Lazers steal health from enemies and heal you",
type: "special",
specialType: "vampiric"
}, {
name: "Multi-Shot Lazers",
description: "Fire three lazers in a spread pattern with each shot (additional upgrades add more lazers)",
type: "special",
specialType: "multishot"
}, {
name: "Homing Lazers",
description: "Lazers seek out nearby enemies after being fired",
type: "special",
specialType: "homing"
}, {
name: "Energy Barriers",
description: "Three energy barriers orbit around you, pushing back and damaging enemies",
type: "special",
specialType: "shields"
}, {
name: "Rotating Swords",
description: "Spinning swords that damage nearby enemies",
type: "special",
specialType: "swords"
}, {
name: "Energy Orbs",
description: "Magical orbs circle you, zapping nearby enemies",
type: "special",
specialType: "orbs"
}, {
name: "Flame Barrier",
description: "A ring of fire surrounds you, burning enemies",
type: "special",
specialType: "flame_barrier"
}, {
name: "Ice Shards",
description: "Ice crystals orbit you, slowing nearby enemies",
type: "special",
specialType: "ice_shards"
}, {
name: "Spirit Guardians",
description: "Ethereal spirits orbit and protect you",
type: "special",
specialType: "spirit_guardians"
}];
// UI elements
var waveText = new Text2("Wave: 1", {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0);
LK.gui.top.addChild(waveText);
var scoreText = new Text2("Hits: 0", {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0, 0);
scoreText.x = 150;
scoreText.y = 10;
LK.gui.topLeft.addChild(scoreText);
var healthBarBg = LK.getAsset('healthBarBg', {
anchorX: 0.5,
anchorY: 0.5
});
var healthBar = LK.getAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
healthBarBg.x = 2048 / 2;
healthBarBg.y = 50;
healthBar.x = healthBarBg.x - 100;
healthBar.y = healthBarBg.y;
LK.gui.addChild(healthBarBg);
LK.gui.addChild(healthBar);
var upgradeContainer = new Container();
upgradeContainer.visible = false;
LK.gui.addChild(upgradeContainer);
var upgradeTitle = new Text2("Choose One: Arrow Effect, Orbital, or Stat Upgrade", {
size: 60,
fill: 0xFFFFFF
});
upgradeTitle.anchor.set(0, 0.5);
upgradeTitle.x = 50;
upgradeTitle.y = 200;
upgradeContainer.addChild(upgradeTitle);
function initGame() {
currentWave = 0;
monstersKilled = 0;
paused = false;
showingUpgrades = false;
monsters = [];
arrows = [];
bossSpawned = false; // Track if boss is spawned for current wave
bossDefeated = false; // Track if the current boss has been defeated
// Reset acquired special abilities
acquiredSpecialTypes = [];
acquiredOrbitalTypes = [];
// Clean up any existing orbital objects
for (var i = 0; i < orbitalObjects.length; i++) {
if (orbitalObjects[i].parent) {
orbitalObjects[i].parent.removeChild(orbitalObjects[i]);
}
}
orbitalObjects = [];
// Clean up existing stars
for (var i = 0; i < stars.length; i++) {
if (stars[i].parent) {
stars[i].parent.removeChild(stars[i]);
}
}
stars = [];
// Create starfield background with fewer stars (50 instead of 100)
for (var i = 0; i < 50; i++) {
var star = new Star();
star.x = Math.random() * 2048; // Random x position
star.y = Math.random() * 2732; // Random y position
stars.push(star);
// Add stars to game at the beginning so they're behind everything else
game.addChildAt(star, 0);
}
// Initialize player
player = new Player();
player.x = 2048 / 2;
player.y = 2732 / 2;
game.addChild(player);
// Reset arrow properties to stronger defaults
arrowDamage = 20;
arrowPierce = 1;
arrowSpeed = 18;
// Update UI
waveText.setText("Wave: 1");
scoreText.setText("Score: 0");
// Start first wave
startNextWave();
// Play music with initial intensity
LK.playMusic('gameMusic', {
fade: {
start: 0,
end: 1.0,
duration: 2000
}
});
}
function startNextWave() {
currentWave++;
waveText.setText("Wave: " + currentWave);
lastSpawnTime = Date.now();
// Reset boss flags at the start of every wave
if (currentWave % 10 === 0) {
// Boss wave starts with boss not spawned
bossSpawned = false;
bossDefeated = false;
// Increase music intensity during boss waves
LK.playMusic('gameMusic', {
volume: 1.0,
loop: true
});
} else {
// Non-boss wave
bossSpawned = false;
bossDefeated = true; // No boss to defeat on non-boss waves
// Return to normal music intensity for regular waves
if (currentWave > 10) {
LK.playMusic('gameMusic', {
volume: 0.8
});
} else {
LK.playMusic('gameMusic', {
volume: 0.7
});
}
}
// If boss wave, create special effect
if (currentWave % 10 === 0) {
// Create warning effect
var warningEffect = new Container();
var warningBg = LK.getAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 1,
tint: 0xFF0000,
alpha: 0.3
});
warningEffect.x = 2048 / 2;
warningEffect.y = 2732 / 2;
warningEffect.addChild(warningBg);
LK.gui.addChild(warningEffect);
// Add warning text
var warningText = new Text2("BOSS INCOMING", {
size: 100,
fill: 0xFF0000
});
warningText.anchor.set(0.5, 0.5);
warningEffect.addChild(warningText);
// Pulse animation
tween(warningBg, {
alpha: 0.7
}, {
duration: 500,
onFinish: function onFinish() {
tween(warningBg, {
alpha: 0.3
}, {
duration: 500,
onFinish: function onFinish() {
tween(warningBg, {
alpha: 0.7
}, {
duration: 500,
onFinish: function onFinish() {
tween(warningBg, {
alpha: 0
}, {
duration: 1000,
onFinish: function onFinish() {
if (warningEffect.parent) {
warningEffect.parent.removeChild(warningEffect);
}
}
});
}
});
}
});
}
});
}
}
function spawnMonster() {
// Check if we should spawn a boss (every 10 waves)
if (currentWave % 10 === 0 && !bossSpawned) {
var boss = new Boss();
// Scale boss stats based on which "boss wave" this is
var bossLevel = currentWave / 10;
boss.bossLevel = bossLevel;
// Increase boss stats with each boss level
boss.maxHealth = 200 + bossLevel * 300; // 500, 800, 1100, etc.
boss.health = boss.maxHealth;
boss.speed = 1 + bossLevel * 0.2; // Speed increases with level
boss.damage = 20 + bossLevel * 10; // Damage increases with level
// Add abilities at higher levels
if (bossLevel >= 2) {
boss.canSpawnMinions = true;
boss.minionSpawnCooldown = 6000 - bossLevel * 500; // Spawn faster at higher levels
}
// Color based on boss level
if (bossLevel === 1) {
boss.body.tint = 0xFF0000; // Red for first boss
} else if (bossLevel === 2) {
boss.body.tint = 0x9900FF; // Purple for second boss
} else if (bossLevel === 3) {
boss.body.tint = 0x00FFFF; // Cyan for third boss
} else {
boss.body.tint = 0xFFFF00; // Yellow for higher level bosses
}
// Spawn boss at a random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
boss.x = Math.random() * 2048;
boss.y = -150;
break;
case 1:
// Right
boss.x = 2198;
boss.y = Math.random() * 2732;
break;
case 2:
// Bottom
boss.x = Math.random() * 2048;
boss.y = 2882;
break;
case 3:
// Left
boss.x = -150;
boss.y = Math.random() * 2732;
break;
}
// Create boss entrance effect
var bossEntranceEffect = new Container();
var entranceCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 5,
scaleY: 5,
tint: boss.body.tint,
alpha: 0.8
});
bossEntranceEffect.x = boss.x;
bossEntranceEffect.y = boss.y;
game.addChild(bossEntranceEffect);
bossEntranceEffect.addChild(entranceCircle);
// Animate entrance
tween(entranceCircle, {
scaleX: 10,
scaleY: 10,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
if (bossEntranceEffect.parent) {
bossEntranceEffect.parent.removeChild(bossEntranceEffect);
}
}
});
// Add a boss text warning
var bossText = new Text2("BOSS LEVEL " + bossLevel, {
size: 120,
fill: 0xFF0000
});
bossText.anchor.set(0.5, 0.5);
bossText.x = 2048 / 2;
bossText.y = 2732 / 2;
LK.gui.addChild(bossText);
// Animate the boss text
tween(bossText, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 2000,
onFinish: function onFinish() {
if (bossText.parent) {
bossText.parent.removeChild(bossText);
}
}
});
// Add boss to the game
monsters.push(boss);
game.addChild(boss);
bossSpawned = true;
return boss;
} else {
// Regular monster spawning
var monster = new Monster();
// Scale difficulty with wave, but with reduced health to make monsters easier to defeat
monster.maxHealth = 10 + currentWave * 3;
monster.health = monster.maxHealth;
monster.speed = 1.5 + currentWave * 0.05;
monster.damage = 5 + currentWave;
// Change monster color based on wave difficulty
if (currentWave <= 5) {
monster.body.tint = 0xff3333; // Default red for early waves
} else if (currentWave <= 10) {
monster.body.tint = 0xff9900; // Orange for medium waves
} else if (currentWave <= 15) {
monster.body.tint = 0xffcc00; // Yellow-orange for harder waves
} else if (currentWave <= 20) {
monster.body.tint = 0x9900ff; // Purple for very hard waves
} else {
monster.body.tint = 0x000000; // Black for super difficult waves
// Add a white outline effect with a flash
LK.effects.flashObject(monster, 0xffffff, 300);
}
// Spawn from random edge of screen
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
monster.x = Math.random() * 2048;
monster.y = -100;
break;
case 1:
// Right
monster.x = 2148;
monster.y = Math.random() * 2732;
break;
case 2:
// Bottom
monster.x = Math.random() * 2048;
monster.y = 2832;
break;
case 3:
// Left
monster.x = -100;
monster.y = Math.random() * 2732;
break;
}
monsters.push(monster);
game.addChild(monster);
return monster;
}
}
function showUpgradeScreen() {
paused = true;
showingUpgrades = true;
upgradeContainer.visible = true;
// Clear previous upgrade cards
for (var i = 0; i < upgradeCards.length; i++) {
if (upgradeCards[i] && upgradeCards[i].parent) {
upgradeCards[i].parent.removeChild(upgradeCards[i]);
}
}
upgradeCards = [];
// Set boss reward title if we just defeated a boss
if (currentWave % 10 === 0 && bossDefeated) {
upgradeTitle.setText("BOSS REWARDS: Choose Two Powerful Upgrades");
} else {
upgradeTitle.setText("Choose One: Arrow Effect, Orbital, or Stat Upgrade");
}
// Prepare for card selection
var cardOptions = [];
// Filter out already acquired special abilities
var arrowSpecials = [];
var orbitalSpecials = [];
var nonOrbitalSpecials = [];
for (var i = 0; i < specialAbilitiesPool.length; i++) {
var ability = specialAbilitiesPool[i];
// Check for orbital special types
if (["shields", "swords", "orbs", "flame_barrier", "ice_shards", "spirit_guardians"].indexOf(ability.specialType) !== -1) {
// Only add if we don't have this orbital type already
if (acquiredOrbitalTypes.indexOf(ability.specialType) === -1) {
orbitalSpecials.push(ability);
}
// Check for arrow special types
} else if (["poison", "explosive", "freeze", "fire", "lightning", "vampiric", "multishot", "rebound", "homing"].indexOf(ability.specialType) !== -1) {
// Only add if we don't have this arrow special already
if (acquiredSpecialTypes.indexOf(ability.specialType) === -1) {
arrowSpecials.push(ability);
}
} else {
nonOrbitalSpecials.push(ability);
}
}
// Create enhanced abilities for boss rewards
var bossRewardPool = [];
if (currentWave % 10 === 0 && bossDefeated) {
bossRewardPool = [{
name: "Massive Health Boost",
description: "Increase max health by 50",
type: "health",
value: 50
}, {
name: "Devastating Arrows",
description: "Increase arrow damage by 15",
type: "arrowDamage",
value: 15
}, {
name: "Lightning Reflexes",
description: "Increase attack speed by 40%",
type: "attackSpeed",
value: 0.4
}, {
name: "Master Piercer",
description: "Arrows pierce through 2 additional enemies",
type: "arrowPierce",
value: 2
}, {
name: "Sniper Training",
description: "Increase arrow speed by 5",
type: "arrowSpeed",
value: 5
}];
}
// For boss rewards, offer better options (more choices or better upgrades)
if (currentWave % 10 === 0 && bossDefeated) {
// Offer two options from boss reward pool
for (var i = 0; i < 2 && bossRewardPool.length > 0; i++) {
var index = Math.floor(Math.random() * bossRewardPool.length);
var ability = bossRewardPool.splice(index, 1)[0];
cardOptions.push(ability);
}
// Add 1-2 special abilities if available
if (arrowSpecials.length > 0) {
var arrowSpecialIndex = Math.floor(Math.random() * arrowSpecials.length);
var arrowSpecialAbility = arrowSpecials[arrowSpecialIndex];
cardOptions.push(arrowSpecialAbility);
}
if (orbitalSpecials.length > 0 && cardOptions.length < 4) {
var orbitalIndex = Math.floor(Math.random() * orbitalSpecials.length);
var orbitalAbility = orbitalSpecials[orbitalIndex];
cardOptions.push(orbitalAbility);
}
// If we still don't have enough cards, add from regular abilities
var availableAbilities = [].concat(abilitiesPool);
while (cardOptions.length < 4 && availableAbilities.length > 0) {
var index = Math.floor(Math.random() * availableAbilities.length);
var ability = availableAbilities.splice(index, 1)[0];
cardOptions.push(ability);
}
}
// For regular upgrades, use standard options
else {
// Always include one arrow special if available (separate card)
if (arrowSpecials.length > 0) {
var arrowSpecialIndex = Math.floor(Math.random() * arrowSpecials.length);
var arrowSpecialAbility = arrowSpecials[arrowSpecialIndex];
cardOptions.push(arrowSpecialAbility);
}
// Always include one orbital special if available (separate card)
if (orbitalSpecials.length > 0) {
var orbitalIndex = Math.floor(Math.random() * orbitalSpecials.length);
var orbitalAbility = orbitalSpecials[orbitalIndex];
cardOptions.push(orbitalAbility);
}
// Include one non-orbital special if available
if (nonOrbitalSpecials.length > 0 && cardOptions.length < 2) {
var nonOrbitalIndex = Math.floor(Math.random() * nonOrbitalSpecials.length);
var nonOrbitalAbility = nonOrbitalSpecials[nonOrbitalIndex];
cardOptions.push(nonOrbitalAbility);
}
// Pick regular abilities for the remaining slots
var availableAbilities = [].concat(abilitiesPool);
while (cardOptions.length < 3 && availableAbilities.length > 0) {
var index = Math.floor(Math.random() * availableAbilities.length);
var ability = availableAbilities.splice(index, 1)[0];
cardOptions.push(ability);
}
}
// Show cards
var cardCount = cardOptions.length;
var cardWidth = 400; // Width of each card
var spacing = 20; // Reduced spacing between cards
var cardScale = 0.8; // Smaller card scale to ensure all three fit
var totalWidth = cardWidth * cardScale * cardCount + spacing * (cardCount - 1);
var startX = (2048 - totalWidth) / 2 + cardWidth * cardScale / 2;
// For boss rewards, allow selecting two cards
var cardsToSelect = currentWave % 10 === 0 && bossDefeated ? 2 : 1;
var selectedCards = 0;
for (var i = 0; i < cardCount; i++) {
var ability = cardOptions[i];
var card = new UpgradeCard(ability);
// Save original up method
var originalUpMethod = card.up;
// Override up method for boss rewards to allow selecting multiple cards
if (currentWave % 10 === 0 && bossDefeated) {
card.up = function (x, y, obj) {
var self = this;
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
onFinish: function onFinish() {
if (player) {
player.addAbility(self.ability);
if (self.parent) {
self.parent.removeChild(self);
}
// Increment selected cards count
selectedCards++;
// If we've selected enough cards, hide the screen and start next wave
if (selectedCards >= cardsToSelect) {
hideUpgradeScreen();
startNextWave();
LK.getSound('levelup').play();
}
}
}
});
};
}
// Set special icon colors for special abilities
if (ability.type === "special") {
if (ability.specialType === "poison") {
card.icon.tint = 0x00FF00; // Green for poison
} else if (ability.specialType === "explosive") {
card.icon.tint = 0xFF9900; // Orange for explosive
} else if (ability.specialType === "freeze") {
card.icon.tint = 0x00FFFF; // Cyan for freeze
} else if (ability.specialType === "fire") {
card.icon.tint = 0xFF3300; // Orange-red for fire
} else if (ability.specialType === "lightning") {
card.icon.tint = 0xFFFF00; // Yellow for lightning
} else if (ability.specialType === "vampiric") {
card.icon.tint = 0xFF00FF; // Purple for vampiric
} else if (ability.specialType === "multishot") {
card.icon.tint = 0x3366FF; // Blue for multishot
} else if (ability.specialType === "rebound") {
card.icon.tint = 0x66CCFF; // Light blue for rebound
} else if (ability.specialType === "homing") {
card.icon.tint = 0xFF6600; // Orange for homing
} else if (ability.specialType === "shields") {
card.icon.tint = 0x3399FF; // Blue for shields
} else if (ability.specialType === "swords") {
card.icon.tint = 0xCCCCCC; // Silver for swords
} else if (ability.specialType === "orbs") {
card.icon.tint = 0xFFFF00; // Yellow for orbs
} else if (ability.specialType === "flame_barrier") {
card.icon.tint = 0xFF3300; // Orange-red for flame barrier
} else if (ability.specialType === "ice_shards") {
card.icon.tint = 0x99CCFF; // Light blue for ice shards
} else if (ability.specialType === "spirit_guardians") {
card.icon.tint = 0xDDDDFF; // Pale blue-white for spirits
}
}
// Add boss reward indicator for enhanced abilities
if (currentWave % 10 === 0 && bossDefeated && (ability.type !== "special" || ["shields", "swords", "orbs", "flame_barrier", "ice_shards", "spirit_guardians", "poison", "explosive", "freeze", "fire", "lightning", "vampiric", "multishot", "homing"].indexOf(ability.specialType) === -1)) {
var bossRewardMarker = new Text2("BOSS REWARD", {
size: 24,
fill: 0xFF0000
});
bossRewardMarker.anchor.set(0.5, 0.5);
bossRewardMarker.y = -190;
card.addChild(bossRewardMarker);
}
// Position cards in a row
var xPos = startX + i * (cardWidth * cardScale + spacing) - 200;
// For 4 cards, make adjustment to fit screen
if (cardCount === 4) {
xPos = startX + i * (cardWidth * cardScale + 10) - 300;
cardScale = 0.7; // Smaller for 4 cards
}
card.x = xPos;
card.y = 2732 / 2;
card.scale.set(cardScale, cardScale);
upgradeCards.push(card);
upgradeContainer.addChild(card);
}
}
function hideUpgradeScreen() {
paused = false;
showingUpgrades = false;
upgradeContainer.visible = false;
}
function updateHealthBar() {
if (player) {
var healthPercent = player.health / player.maxHealth;
healthBar.width = 200 * healthPercent;
}
}
function gameOver() {
// Update high score in storage
if (monstersKilled > storage.highscore) {
storage.highscore = monstersKilled;
}
storage.totalGamesPlayed = (storage.totalGamesPlayed || 0) + 1;
// Fade out music before game over
LK.playMusic('gameMusic', {
fade: {
start: 0.7,
end: 0,
duration: 1000
}
});
LK.showGameOver();
}
function handleClick(x, y, obj) {
if (paused || !player) return;
// Check if player has multishot ability and count how many
var multishotCount = 0;
for (var i = 0; i < player.abilities.length; i++) {
if (player.abilities[i].type === "special" && player.abilities[i].specialType === "multishot") {
multishotCount++;
}
}
// Get all available arrow special types
var arrowSpecials = [];
for (var i = 0; i < player.abilities.length; i++) {
var ability = player.abilities[i];
if (ability.type === "special") {
// Check if it's an arrow-type special ability (not orbital)
if (["poison", "explosive", "freeze", "fire", "lightning", "vampiric", "rebound", "homing"].indexOf(ability.specialType) !== -1) {
arrowSpecials.push(ability.specialType);
}
}
}
// If multishot, create arrows in a spread pattern based on how many upgrades
if (multishotCount > 0) {
// Center arrow
var mainArrow = player.shoot(x, y);
if (mainArrow) {
applyArrowProperties(mainArrow, arrowSpecials);
arrows.push(mainArrow);
game.addChild(mainArrow);
// Determine angle spread based on upgrade count - widen as we get more arrows
var angleSpread = Math.PI / 12; // Base 15 degrees
var totalArrows = 2 + multishotCount; // Base 3 arrows (1 + 2) plus 1 per upgrade
// Create additional arrows in a spread pattern
for (var i = 1; i <= totalArrows - 1; i++) {
// Alternate between left and right side
var isEven = i % 2 === 0;
var arrowIndex = Math.ceil(i / 2);
var sideAngle = angleSpread * arrowIndex;
if (isEven) {
// Right side arrow
var rightAngle = mainArrow.rotation + sideAngle;
var rightArrow = new Arrow();
rightArrow.x = player.x;
rightArrow.y = player.y;
rightArrow.rotation = rightAngle;
rightArrow.damage = arrowDamage * 0.8; // Side arrows do less damage
rightArrow.pierce = arrowPierce;
rightArrow.speed = arrowSpeed;
applyArrowProperties(rightArrow, arrowSpecials);
arrows.push(rightArrow);
game.addChild(rightArrow);
} else {
// Left side arrow
var leftAngle = mainArrow.rotation - sideAngle;
var leftArrow = new Arrow();
leftArrow.x = player.x;
leftArrow.y = player.y;
leftArrow.rotation = leftAngle;
leftArrow.damage = arrowDamage * 0.8; // Side arrows do less damage
leftArrow.pierce = arrowPierce;
leftArrow.speed = arrowSpeed;
applyArrowProperties(leftArrow, arrowSpecials);
arrows.push(leftArrow);
game.addChild(leftArrow);
}
}
// Play sound only once
LK.getSound('shoot').play();
}
} else {
// Regular single arrow shot
var arrow = player.shoot(x, y);
if (arrow) {
applyArrowProperties(arrow, arrowSpecials);
arrows.push(arrow);
game.addChild(arrow);
}
}
// Helper function to apply properties and special effects to arrows
function applyArrowProperties(arrow, arrowSpecials) {
arrow.damage = arrowDamage;
arrow.pierce = arrowPierce;
arrow.speed = arrowSpeed;
// If we have special arrow types, randomly select one to apply
if (arrowSpecials.length > 0) {
var selectedSpecial = arrowSpecials[Math.floor(Math.random() * arrowSpecials.length)];
arrow.specialType = selectedSpecial;
// Visual indicators for special arrows
if (selectedSpecial === "poison") {
arrow.arrowBody.tint = 0x00FF00; // Green for poison
arrow.arrowHead.tint = 0x00FF00;
} else if (selectedSpecial === "explosive") {
arrow.arrowBody.tint = 0xFF9900; // Orange for explosive
arrow.arrowHead.tint = 0xFF9900;
} else if (selectedSpecial === "freeze") {
arrow.arrowBody.tint = 0x00FFFF; // Cyan for freeze
arrow.arrowHead.tint = 0x00FFFF;
} else if (selectedSpecial === "fire") {
arrow.arrowBody.tint = 0xFF3300; // Orange-red for fire
arrow.arrowHead.tint = 0xFF3300;
} else if (selectedSpecial === "lightning") {
arrow.arrowBody.tint = 0xFFFF00; // Yellow for lightning
arrow.arrowHead.tint = 0xFFFF00;
} else if (selectedSpecial === "vampiric") {
arrow.arrowBody.tint = 0xFF00FF; // Purple for vampiric
arrow.arrowHead.tint = 0xFF00FF;
} else if (selectedSpecial === "homing") {
arrow.arrowBody.tint = 0xFF6600; // Orange for homing
arrow.arrowHead.tint = 0xFF6600;
}
}
}
}
game.down = handleClick;
game.update = function () {
if (paused) return;
// Update player if exists
if (player) {
player.update();
}
// Spawn monsters
var now = Date.now();
var spawnInterval = Math.max(2000 - currentWave * 100, 500);
var monstersPerWave = 2 + Math.floor(currentWave * 0.8); // Reduced monster count for shorter waves
// For boss waves, only spawn the boss
if (currentWave % 10 === 0) {
if (!bossSpawned && !bossDefeated) {
spawnMonster(); // This will spawn the boss
}
}
// Regular waves spawn multiple monsters
else if (now - lastSpawnTime > spawnInterval && monsters.length < monstersPerWave) {
spawnMonster();
}
// Check for boss defeat
var hasBoss = false;
for (var i = 0; i < monsters.length; i++) {
if (monsters[i] instanceof Boss) {
hasBoss = true;
break;
}
}
// If this is a boss wave and the boss has been spawned but is no longer present
if (currentWave % 10 === 0 && bossSpawned && !hasBoss && !bossDefeated) {
bossDefeated = true;
// Create boss defeat celebration effect
var defeatEffect = new Container();
var celebrationCircle = LK.getAsset('monster', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 10,
scaleY: 10,
tint: 0xFFFFFF,
alpha: 0.2
});
defeatEffect.x = 2048 / 2;
defeatEffect.y = 2732 / 2;
game.addChild(defeatEffect);
defeatEffect.addChild(celebrationCircle);
// Add victory text
var victoryText = new Text2("BOSS DEFEATED!", {
size: 120,
fill: 0x00FF00
});
victoryText.anchor.set(0.5, 0.5);
defeatEffect.addChild(victoryText);
// Animate the celebration effect
tween(celebrationCircle, {
scaleX: 20,
scaleY: 20,
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
if (defeatEffect.parent) {
defeatEffect.parent.removeChild(defeatEffect);
}
// Show special reward for defeating boss
showUpgradeScreen();
}
});
// No extra bonus points for defeating boss
scoreText.setText("Hits: " + monstersKilled);
// Boss defeat text animation
var bonusText = new Text2("BOSS DEFEATED", {
size: 80,
fill: 0xFFFF00
});
bonusText.anchor.set(0.5, 0.5);
bonusText.x = 2048 / 2;
bonusText.y = 2732 / 2 + 150;
LK.gui.addChild(bonusText);
// Animate bonus text
tween(bonusText, {
y: bonusText.y - 100,
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
if (bonusText.parent) {
bonusText.parent.removeChild(bonusText);
}
}
});
}
// Check if regular wave is complete - based on monsters killed rather than time
if (!showingUpgrades && (currentWave % 10 !== 0 && (monstersKilled >= currentWave * 10 || monsters.length === 0 && now - lastSpawnTime > spawnInterval * 2) || currentWave % 10 === 0 && bossDefeated)) {
if (!showingUpgrades) {
showUpgradeScreen();
}
}
// Update arrows
for (var i = arrows.length - 1; i >= 0; i--) {
if (arrows[i].needsRemoval) {
game.removeChild(arrows[i]);
arrows.splice(i, 1);
continue;
}
}
// Check for collisions
for (var i = arrows.length - 1; i >= 0; i--) {
var arrow = arrows[i];
for (var j = monsters.length - 1; j >= 0; j--) {
var monster = monsters[j];
if (arrow.canHit(monster) && arrow.intersects(monster)) {
arrow.hit(monster);
if (monster.takeDamage(arrow.damage)) {
// Monster is defeated
game.removeChild(monster);
monsters.splice(j, 1);
// Play pop sound when monster is killed
LK.getSound('pop').play();
// Add one point regardless of monster type
monstersKilled++;
scoreText.setText("Score: " + monstersKilled);
}
if (arrow.needsRemoval) {
game.removeChild(arrow);
arrows.splice(i, 1);
break;
}
}
}
}
// Update orbital objects
for (var i = orbitalObjects.length - 1; i >= 0; i--) {
orbitalObjects[i].update();
}
// Update stars for parallax effect
for (var i = 0; i < stars.length; i++) {
stars[i].update();
}
// Update health bar
updateHealthBar();
};
// Initialize background music with intense battle theme
// Start the game
initGame();
;