User prompt
Make the power icon a little futlrther down on the upgrade cards
User prompt
Optimize affects to make the game less laggy
User prompt
Do the promp that was not finished
User prompt
Make a sound for when you die
User prompt
Make orbital objects move slower
User prompt
Add a sound for when orbital objects hit monsters
User prompt
Make orbital objects more futuristic ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add a sound when lazer is fired
User prompt
Change the word arrows on the upgrade cards to lazers
User prompt
Make the arrows lazers
User prompt
Move the title of the upgrade cards to over the colored icon
User prompt
MKe it so the upgrade cards description is not colliding with the name of the upgrade
User prompt
MKe it so if you have multiple orbital objects They do not colide
User prompt
Change the game naMe to space archers
User prompt
Make the orbiting shields energy barriers
User prompt
Make the stars vary in color
User prompt
Make the purple in the background darker
User prompt
MAke the background have just the slightest tint of purple ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Add stars on the backround
User prompt
Change monster color to blue and purple
User prompt
Make monster bigger and change to yellow
User prompt
Make the player bigger
User prompt
Make the player bigger
User prompt
Make the spaceship more realistic ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/**** * 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: 1.2, scaleY: 0.7, tint: 0x33CCFF // Blue laser color }); self.arrowHead = self.attachAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, x: 50, scaleX: 0.8, scaleY: 0.8, tint: 0x33CCFF // Matching laser color }); // Add laser glow effect self.glow = LK.getAsset('arrow', { anchorX: 0, anchorY: 0.5, scaleX: 1.3, scaleY: 1.5, tint: 0x33CCFF, alpha: 0.3 }); self.addChildAt(self.glow, 0); self.speed = 15; 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 if (LK.ticks % 3 === 0) { var trail = new Container(); var trailParticle = LK.getAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5, tint: 0xFF6600, alpha: 0.5 }); trail.x = self.x; trail.y = self.y; trail.addChild(trailParticle); game.addChild(trail); // Fade out trail tween(trailParticle, { alpha: 0, scaleX: 0.1, scaleY: 0.1 }, { duration: 300, 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++; // 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 }); 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.03; 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") { self.body = self.attachAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.2, scaleY: 0.2 }); self.body.tint = 0x3399FF; // Blue shields } else if (type === "swords") { self.body = self.attachAsset('arrow', { anchorX: 0.5, anchorY: 0.5, scaleX: 2, scaleY: 0.4 }); self.body.tint = 0xCCCCCC; // Silver swords } else if (type === "orbs") { self.body = self.attachAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.5, scaleY: 0.5 }); self.body.tint = 0xFFCC00; // Yellow orbs } else if (type === "flame_barrier") { 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 } else if (type === "ice_shards") { self.body = self.attachAsset('upgradeCard', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.15, scaleY: 0.15 }); self.body.tint = 0x99CCFF; // Light blue for ice shards } else if (type === "spirit_guardians") { self.body = self.attachAsset('arrowHead', { anchorX: 0.5, anchorY: 0.5, scaleX: 3, scaleY: 3 }); self.body.tint = 0xDDDDFF; // Pale blue-white for spirits } self.update = function () { if (!player) return; // 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; } // 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 shield impact effect var shieldEffect = new Container(); var impactWave = LK.getAsset('powerIcon', { anchorX: 0.5, anchorY: 0.5, scaleX: 0.6, scaleY: 0.6, tint: 0x3399FF, alpha: 0.6 }); shieldEffect.x = self.x; shieldEffect.y = self.y; game.addChild(shieldEffect); shieldEffect.addChild(impactWave); // Animate shield effect tween(impactWave, { scaleX: 1.8, scaleY: 1.8, alpha: 0 }, { duration: 400, onFinish: function onFinish() { if (shieldEffect.parent) { shieldEffect.parent.removeChild(shieldEffect); } } }); } LK.effects.flashObject(self, 0x3399FF, 200); 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); } 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); } } 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); } 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); } 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); // 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 }); // 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 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: 500, easing: tween.easeOut, onFinish: function onFinish() { if (thrusterFlare.parent) { thrusterFlare.parent.removeChild(thrusterFlare); } } }); // Create multiple thruster particles for (var i = 0; i < 5; i++) { 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 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: 300 + Math.random() * 200, easing: tween.easeOut, onFinish: function onFinish() { if (thrusterParticle.parent) { thrusterParticle.parent.removeChild(thrusterParticle); } } }); }, particleDelay); } // Enhanced weapon firing sound LK.getSound('shoot').play(); return arrow; }; self.takeDamage = function (amount) { self.health -= amount; if (self.health <= 0) { self.health = 0; 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 for (var i = 0; i < 3; i++) { var angle = Math.PI * 2 / 3 * i; var orbitalObj = new OrbitalObject(ability.specialType, angle); orbitalObjects.push(orbitalObj); game.addChild(orbitalObj); } } } }; // Add update method for continuous effects self.update = function () { // Generate idle thruster particles var now = Date.now(); if (now - self.lastParticleTime > 150) { // Emit particles every 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 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: 400, easing: tween.easeOut, onFinish: function onFinish() { if (idleParticle.parent) { idleParticle.parent.removeChild(idleParticle); } } }); } // Blink wing tip lights if (LK.ticks % 60 === 0) { // Every second (60 frames) 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 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: -150 }); // Title self.title = new Text2(ability.name, { size: 36, fill: 0x000000 }); self.title.anchor.set(0.5, 0.5); self.title.y = -50; 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 = 50; 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: 0x222222 }); /**** * Game Code ****/ // Game variables var player; var monsters = []; var arrows = []; var upgradeCards = []; var orbitalObjects = []; 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 Arrows", description: "Arrows poison enemies, dealing damage over time", type: "special", specialType: "poison" }, { name: "Explosive Arrows", description: "Arrows explode on impact, damaging nearby enemies", type: "special", specialType: "explosive" }, { name: "Freezing Arrows", description: "Arrows slow down enemies for a short time", type: "special", specialType: "freeze" }, { name: "Fire Arrows", description: "Arrows burn enemies with intense flames", type: "special", specialType: "fire" }, { name: "Lightning Arrows", description: "Arrows chain lightning between nearby enemies", type: "special", specialType: "lightning" }, { name: "Vampiric Arrows", description: "Arrows steal health from enemies and heal you", type: "special", specialType: "vampiric" }, { name: "Multi-Shot Arrows", description: "Fire three arrows in a spread pattern with each shot (additional upgrades add more arrows)", type: "special", specialType: "multishot" }, { name: "Homing Arrows", description: "Arrows seek out nearby enemies after being fired", type: "special", specialType: "homing" }, { name: "Orbiting Shields", description: "Three shields circle around you, blocking projectiles", 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 = []; // 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 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: 1.2,
scaleY: 0.7,
tint: 0x33CCFF // Blue laser color
});
self.arrowHead = self.attachAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
x: 50,
scaleX: 0.8,
scaleY: 0.8,
tint: 0x33CCFF // Matching laser color
});
// Add laser glow effect
self.glow = LK.getAsset('arrow', {
anchorX: 0,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.5,
tint: 0x33CCFF,
alpha: 0.3
});
self.addChildAt(self.glow, 0);
self.speed = 15;
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
if (LK.ticks % 3 === 0) {
var trail = new Container();
var trailParticle = LK.getAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5,
tint: 0xFF6600,
alpha: 0.5
});
trail.x = self.x;
trail.y = self.y;
trail.addChild(trailParticle);
game.addChild(trail);
// Fade out trail
tween(trailParticle, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
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++;
// 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
});
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.03;
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") {
self.body = self.attachAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.2,
scaleY: 0.2
});
self.body.tint = 0x3399FF; // Blue shields
} else if (type === "swords") {
self.body = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 2,
scaleY: 0.4
});
self.body.tint = 0xCCCCCC; // Silver swords
} else if (type === "orbs") {
self.body = self.attachAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.5,
scaleY: 0.5
});
self.body.tint = 0xFFCC00; // Yellow orbs
} else if (type === "flame_barrier") {
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
} else if (type === "ice_shards") {
self.body = self.attachAsset('upgradeCard', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.15,
scaleY: 0.15
});
self.body.tint = 0x99CCFF; // Light blue for ice shards
} else if (type === "spirit_guardians") {
self.body = self.attachAsset('arrowHead', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 3,
scaleY: 3
});
self.body.tint = 0xDDDDFF; // Pale blue-white for spirits
}
self.update = function () {
if (!player) return;
// 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;
}
// 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 shield impact effect
var shieldEffect = new Container();
var impactWave = LK.getAsset('powerIcon', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 0.6,
scaleY: 0.6,
tint: 0x3399FF,
alpha: 0.6
});
shieldEffect.x = self.x;
shieldEffect.y = self.y;
game.addChild(shieldEffect);
shieldEffect.addChild(impactWave);
// Animate shield effect
tween(impactWave, {
scaleX: 1.8,
scaleY: 1.8,
alpha: 0
}, {
duration: 400,
onFinish: function onFinish() {
if (shieldEffect.parent) {
shieldEffect.parent.removeChild(shieldEffect);
}
}
});
}
LK.effects.flashObject(self, 0x3399FF, 200);
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);
} 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);
}
} 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);
} 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);
} 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);
// 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
});
// 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 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: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (thrusterFlare.parent) {
thrusterFlare.parent.removeChild(thrusterFlare);
}
}
});
// Create multiple thruster particles
for (var i = 0; i < 5; i++) {
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
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: 300 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (thrusterParticle.parent) {
thrusterParticle.parent.removeChild(thrusterParticle);
}
}
});
}, particleDelay);
}
// Enhanced weapon firing sound
LK.getSound('shoot').play();
return arrow;
};
self.takeDamage = function (amount) {
self.health -= amount;
if (self.health <= 0) {
self.health = 0;
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
for (var i = 0; i < 3; i++) {
var angle = Math.PI * 2 / 3 * i;
var orbitalObj = new OrbitalObject(ability.specialType, angle);
orbitalObjects.push(orbitalObj);
game.addChild(orbitalObj);
}
}
}
};
// Add update method for continuous effects
self.update = function () {
// Generate idle thruster particles
var now = Date.now();
if (now - self.lastParticleTime > 150) {
// Emit particles every 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
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: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
if (idleParticle.parent) {
idleParticle.parent.removeChild(idleParticle);
}
}
});
}
// Blink wing tip lights
if (LK.ticks % 60 === 0) {
// Every second (60 frames)
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 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: -150
});
// Title
self.title = new Text2(ability.name, {
size: 36,
fill: 0x000000
});
self.title.anchor.set(0.5, 0.5);
self.title.y = -50;
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 = 50;
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: 0x222222
});
/****
* Game Code
****/
// Game variables
var player;
var monsters = [];
var arrows = [];
var upgradeCards = [];
var orbitalObjects = [];
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 Arrows",
description: "Arrows poison enemies, dealing damage over time",
type: "special",
specialType: "poison"
}, {
name: "Explosive Arrows",
description: "Arrows explode on impact, damaging nearby enemies",
type: "special",
specialType: "explosive"
}, {
name: "Freezing Arrows",
description: "Arrows slow down enemies for a short time",
type: "special",
specialType: "freeze"
}, {
name: "Fire Arrows",
description: "Arrows burn enemies with intense flames",
type: "special",
specialType: "fire"
}, {
name: "Lightning Arrows",
description: "Arrows chain lightning between nearby enemies",
type: "special",
specialType: "lightning"
}, {
name: "Vampiric Arrows",
description: "Arrows steal health from enemies and heal you",
type: "special",
specialType: "vampiric"
}, {
name: "Multi-Shot Arrows",
description: "Fire three arrows in a spread pattern with each shot (additional upgrades add more arrows)",
type: "special",
specialType: "multishot"
}, {
name: "Homing Arrows",
description: "Arrows seek out nearby enemies after being fired",
type: "special",
specialType: "homing"
}, {
name: "Orbiting Shields",
description: "Three shields circle around you, blocking projectiles",
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 = [];
// 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 health bar
updateHealthBar();
};
// Initialize background music with intense battle theme
// Start the game
initGame();
;