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