User prompt
Compilation error[L3291]: Uncaught TypeError: Cannot read properties of undefined (reading 'attachAsset')
User prompt
design path and path visual for enemy to walk properly and follow the path design tricky path which looks creative and fun for enemy ensure game balance
User prompt
add all towers visual assets till all levels
User prompt
analyze the code and apply background image and music and other sounds
User prompt
add full tutorial in game ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
all the system warning not readable due to color correction is required for clean understanding and hint
User prompt
shift station position to right side below scores
User prompt
- **Branching Routes**: Enemies can choose between multiple paths add this ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **5. Ultimate Abilities** - **Tower Ultimates**: Devastating special attacks with long cooldowns - **Orbital Strike**: Sniper tower calls down satellite laser - **Nanite Swarm**: Poison tower releases self-replicating nanobots - **Time Dilation**: Slow tower creates temporal distortion field ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **4. Tower Specialization Paths** - **Branch Upgrades**: At level 3, towers can choose specialization paths - **Sniper Variants**: Anti-armor (pierce shields) vs Long-range (extended range) - **Splash Variants**: Nuclear (huge damage) vs Chain reaction (spreads) - **Support Integration**: Towers can link together for combined effects ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **3. Environmental Hazards & Power-ups** - **Asteroid Fields**: Destructible obstacles that block paths - **Nebula Clouds**: Areas that slow all units but provide stealth - **Solar Flares**: Periodic events that boost energy-based towers - **Wormholes**: Teleportation portals that redirect enemies ↪💡 Consider importing and using the following plugins: @upit/tween.v1, @upit/storage.v1
User prompt
apply all ##### **2. Space Station Customization** - **Modular Base**: Players can customize their space station layout - **Research Lab**: Unlock new technologies and tower upgrades - **Shield Generator**: Temporary invulnerability for critical moments - **Teleporter Network**: Instantly move heroes or redirect enemy paths ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
apply all ## Futuristic Update Patch Suggestions ### **🚀 "Galactic Expansion" Major Update** #### **New Core Features** ##### **1. Hero Units System** - **Deployable Heroes**: Special units that can be placed anywhere and move around - **Unique Abilities**: Each hero has special powers with cooldowns - **Leveling System**: Heroes gain experience and unlock new abilities - **Hero Types**: Tank (absorbs damage), Support (buffs towers), DPS (high damage) ↪💡 Consider importing and using the following plugins: @upit/storage.v1, @upit/tween.v1
User prompt
apply fixes #### **User Experience Issues** 1. **Tutorial Absence**: No guidance for new players 2. **Visual Clarity**: Sometimes hard to distinguish enemy types 3. **Touch Responsiveness**: Mobile dragging could be more responsive
User prompt
apply fixes #### **Gameplay Balance Issues** 1. **Tower Balance**: Some towers significantly outperform others 2. **Wave Difficulty**: Exponential health scaling may become too steep in later waves 3. **Economy Balance**: Gold earning vs tower costs may need adjustment
User prompt
**Solution Approach**: - Create a comprehensive `destroy()` method for each class that: - Removes the object from all parent containers - Clears all array references to the object - Stops all active tweens on the object - Removes all event listeners - Nullifies all object references - Implement proper cleanup in the main game loop when removing objects from arrays - Use weak references where possible to avoid circular reference chains - Add memory monitoring to detect when cleanup isn't working properly - Ensure that when bullets are destroyed, they remove themselves from their target enemy's tracking array ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
**Solution Approach**: - Implement object pooling - reuse bullet and enemy objects instead of creating/destroying them constantly - Optimize collision detection using spatial partitioning (only check nearby objects) - Limit the maximum number of visual effects active at once - Cache pathfinding results and only recalculate when the map actually changes - Use level-of-detail rendering - simpler graphics for distant or numerous objects - Implement frame rate adaptive logic - skip some update cycles when performance drop
User prompt
**Solution Approach**: - Before destroying any enemy, stop all active tweens on that enemy using `tween.stop(enemy)` - Add cleanup code in enemy destruction that cancels any ongoing animations - Implement a "isDestroyed" flag on enemies to prevent new tweens from starting on dying enemies - Use defensive programming - check if objects still exist before applying tween effects ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Please fix the bug: 'TypeError: Cannot use 'in' operator to search for 'tint' in null' in or related to this line: 'tween(self.targetEnemy, {' Line Number: 101 ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
will change the current game as theme of galaxy adventure TD defense and sci-fi tower type and animation and attack ↪💡 Consider importing and using the following plugins: @upit/tween.v1
Remix started
Copy Tower Defense Template
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var Asteroid = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.health = 150;
self.maxHealth = 150;
self.isDestroyed = false;
self.gridX = Math.floor((x - grid.x) / CELL_SIZE);
self.gridY = Math.floor((y - grid.y) / CELL_SIZE);
var asteroidGraphics = self.attachAsset('asteroid', {
anchorX: 0.5,
anchorY: 0.5
});
// Add rotation animation
tween(asteroidGraphics, {
rotation: Math.PI * 2
}, {
duration: 8000,
easing: tween.linear,
onFinish: function onFinish() {
if (!self.isDestroyed) {
asteroidGraphics.rotation = 0;
}
}
});
// Health bar
var healthBarOutline = self.attachAsset('healthBarOutline', {
anchorX: 0,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
healthBarOutline.y = healthBar.y = -asteroidGraphics.height / 2 - 15;
healthBarOutline.x = -healthBarOutline.width / 2;
healthBar.x = -healthBar.width / 2;
healthBar.tint = 0x8B4513;
self.healthBar = healthBar;
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.health = 0;
self.destroy();
} else {
self.healthBar.width = self.health / self.maxHealth * 70;
}
};
self.down = function () {
// Allow manual destruction by tapping
self.takeDamage(50);
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
// Create debris effect
for (var i = 0; i < 5; i++) {
var debris = new Container();
debris.x = self.x + (Math.random() - 0.5) * 100;
debris.y = self.y + (Math.random() - 0.5) * 100;
var debrisGraphics = debris.attachAsset('asteroidDebris', {
anchorX: 0.5,
anchorY: 0.5
});
game.addChild(debris);
tween(debris, {
x: debris.x + (Math.random() - 0.5) * 200,
y: debris.y + (Math.random() - 0.5) * 200,
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (debris.parent) {
debris.parent.removeChild(debris);
}
}
});
}
// Clear grid cell
var cell = grid.getCell(self.gridX, self.gridY);
if (cell) {
cell.type = 0;
}
// Remove from asteroids array
var asteroidIndex = asteroids.indexOf(self);
if (asteroidIndex !== -1) {
asteroids.splice(asteroidIndex, 1);
}
// Recalculate paths
grid.pathFind();
grid.renderDebug();
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
// Block the grid cell
var cell = grid.getCell(self.gridX, self.gridY);
if (cell) {
cell.type = 1;
}
return self;
});
var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) {
var self = Container.call(this);
self.targetEnemy = targetEnemy;
self.damage = damage || 10;
self.speed = speed || 5;
self.x = startX;
self.y = startY;
// Create sci-fi projectile based on bullet type
var assetName = 'bullet'; // default
var bulletGraphics;
if (self.type === 'rapid') {
bulletGraphics = self.attachAsset('energyBeam', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0x00aaff;
} else if (self.type === 'sniper') {
bulletGraphics = self.attachAsset('laserBurst', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0xff0044;
} else if (self.type === 'splash') {
bulletGraphics = self.attachAsset('plasmaBolt', {
anchorX: 0.5,
anchorY: 0.5
});
bulletGraphics.tint = 0x44ff00;
} else {
bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Add pulsing glow effect to projectiles
tween(bulletGraphics, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bulletGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
self.update = function () {
if (!self.targetEnemy || !self.targetEnemy.parent) {
returnBulletToPool(self);
return;
}
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < self.speed) {
// Create sci-fi impact flash
if (self.targetEnemy && self.targetEnemy.parent && !self.targetEnemy.isDestroyed) {
tween(self.targetEnemy, {
tint: 0xffffff
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
if (self.targetEnemy && self.targetEnemy.parent && !self.targetEnemy.isDestroyed) {
tween(self.targetEnemy, {
tint: 0xffffff
}, {
duration: 200,
easing: tween.easeIn
});
}
}
});
}
// Apply damage to target enemy
self.targetEnemy.health -= self.damage;
if (self.targetEnemy.health <= 0) {
self.targetEnemy.health = 0;
} else {
self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70;
}
// Apply special effects based on bullet type
if (self.type === 'splash') {
// Create visual splash effect with limiting
if (canCreateEffect()) {
var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash');
game.addChild(splashEffect);
registerEffect(splashEffect);
}
// Determine splash radius and damage based on specialization
var splashRadius = CELL_SIZE * 1.5;
var splashDamageMultiplier = 0.5;
if (self.specialization === 'nuclear') {
splashRadius = CELL_SIZE * 3;
splashDamageMultiplier = 0.8;
// Create nuclear explosion effect
if (canCreateEffect()) {
var nuclearEffect = new Container();
nuclearEffect.x = self.targetEnemy.x;
nuclearEffect.y = self.targetEnemy.y;
var nuclearGraphics = nuclearEffect.attachAsset('nuclearBlast', {
anchorX: 0.5,
anchorY: 0.5
});
nuclearGraphics.tint = 0xFF0000;
nuclearGraphics.alpha = 0.8;
game.addChild(nuclearEffect);
tween(nuclearEffect, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (nuclearEffect.parent) {
nuclearEffect.parent.removeChild(nuclearEffect);
}
}
});
}
}
// Splash damage to nearby enemies
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self.targetEnemy) {
var splashDx = otherEnemy.x - self.targetEnemy.x;
var splashDy = otherEnemy.y - self.targetEnemy.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance <= splashRadius) {
var splashDamage = self.damage * splashDamageMultiplier;
otherEnemy.health -= splashDamage;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
} else {
otherEnemy.healthBar.width = otherEnemy.health / otherEnemy.maxHealth * 70;
}
// Chain reaction effect
if (self.specialization === 'chainreaction' && Math.random() < 0.3) {
// 30% chance to chain to another enemy
for (var j = 0; j < enemies.length; j++) {
var chainEnemy = enemies[j];
if (chainEnemy !== otherEnemy && chainEnemy !== self.targetEnemy) {
var chainDx = chainEnemy.x - otherEnemy.x;
var chainDy = chainEnemy.y - otherEnemy.y;
var chainDistance = Math.sqrt(chainDx * chainDx + chainDy * chainDy);
if (chainDistance <= CELL_SIZE * 2) {
chainEnemy.health -= splashDamage * 0.7;
if (chainEnemy.health <= 0) {
chainEnemy.health = 0;
} else {
chainEnemy.healthBar.width = chainEnemy.health / chainEnemy.maxHealth * 70;
}
break;
}
}
}
}
}
}
}
} else if (self.type === 'slow') {
// Prevent slow effect on immune enemies
if (!self.targetEnemy.isImmune) {
// Create visual slow effect with limiting
if (canCreateEffect()) {
var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow');
game.addChild(slowEffect);
registerEffect(slowEffect);
}
// Apply slow effect
// Make slow percentage scale with tower level (default 50%, up to 80% at max level)
var slowPct = 0.5;
if (self.sourceTowerLevel !== undefined) {
// Scale: 50% at level 1, 60% at 2, 65% at 3, 70% at 4, 75% at 5, 80% at 6
var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8];
var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1));
slowPct = slowLevels[idx];
}
if (!self.targetEnemy.slowed) {
self.targetEnemy.originalSpeed = self.targetEnemy.speed;
self.targetEnemy.speed *= 1 - slowPct; // Slow by X%
self.targetEnemy.slowed = true;
self.targetEnemy.slowDuration = 180; // 3 seconds at 60 FPS
} else {
self.targetEnemy.slowDuration = 180; // Reset duration
}
}
} else if (self.type === 'poison') {
// Prevent poison effect on immune enemies
if (!self.targetEnemy.isImmune) {
// Create visual poison effect with limiting
if (canCreateEffect()) {
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
registerEffect(poisonEffect);
}
// Apply poison effect
self.targetEnemy.poisoned = true;
self.targetEnemy.poisonDamage = self.damage * 0.2; // 20% of original damage per tick
self.targetEnemy.poisonDuration = 300; // 5 seconds at 60 FPS
}
} else if (self.type === 'sniper') {
// Create visual critical hit effect for sniper with limiting
if (canCreateEffect()) {
var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper');
game.addChild(sniperEffect);
registerEffect(sniperEffect);
}
}
returnBulletToPool(self);
} else {
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
self.destroy = function () {
if (self.isDestroyed) {
return;
}
self.isDestroyed = true;
// Stop all active tweens on this bullet and its graphics
tween.stop(self);
if (self.children && self.children[0]) {
tween.stop(self.children[0]);
}
// Remove from target enemy's bullets array
if (self.targetEnemy && self.targetEnemy.bulletsTargetingThis) {
var bulletIndex = self.targetEnemy.bulletsTargetingThis.indexOf(self);
if (bulletIndex !== -1) {
self.targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
// Remove from bullets array
var bulletArrayIndex = bullets.indexOf(self);
if (bulletArrayIndex !== -1) {
bullets.splice(bulletArrayIndex, 1);
}
// Remove from parent container
if (self.parent) {
self.parent.removeChild(self);
}
// Nullify all object references
self.targetEnemy = null;
// Call parent destroy
Container.prototype.destroy.call(self);
};
return self;
});
var DebugCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5
});
cellGraphics.tint = Math.random() * 0xffffff;
var debugArrows = [];
var numberLabel = new Text2('0', {
size: 30,
fill: 0xFFFFFF,
weight: 800
});
numberLabel.anchor.set(.5, .5);
self.addChild(numberLabel);
self.update = function () {};
self.down = function () {
return;
if (self.cell.type == 0 || self.cell.type == 1) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
if (grid.pathFind()) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
grid.pathFind();
var notification = game.addChild(new Notification("Path is blocked!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
grid.renderDebug();
}
};
self.removeArrows = function () {
while (debugArrows.length) {
self.removeChild(debugArrows.pop());
}
};
self.render = function (data) {
switch (data.type) {
case 0:
case 2:
{
if (data.pathId != pathId) {
self.removeArrows();
numberLabel.setText("-");
cellGraphics.tint = 0x220044; // Deep space color
return;
}
numberLabel.visible = true;
var tint = Math.floor(data.score / maxScore * 0x88);
var towerInRangeHighlight = false;
if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) {
towerInRangeHighlight = true;
cellGraphics.tint = 0x0088ff; // Energy field blue
} else {
// Create starfield effect with bluish tint
var starfieldTint = 0x001122 + (tint << 8) + (tint >> 1);
cellGraphics.tint = starfieldTint;
}
while (debugArrows.length > data.targets.length) {
self.removeChild(debugArrows.pop());
}
for (var a = 0; a < data.targets.length; a++) {
var destination = data.targets[a];
var ox = destination.x - data.x;
var oy = destination.y - data.y;
var angle = Math.atan2(oy, ox);
if (!debugArrows[a]) {
debugArrows[a] = LK.getAsset('arrow', {
anchorX: -.5,
anchorY: 0.5
});
debugArrows[a].alpha = .5;
self.addChildAt(debugArrows[a], 1);
}
debugArrows[a].rotation = angle;
}
break;
}
case 1:
{
self.removeArrows();
cellGraphics.tint = 0xaaaaaa;
numberLabel.visible = false;
break;
}
case 3:
{
self.removeArrows();
cellGraphics.tint = 0x008800;
numberLabel.visible = false;
break;
}
}
numberLabel.setText(Math.floor(data.score / 1000) / 10);
};
});
// This update method was incorrectly placed here and should be removed
var EffectIndicator = Container.expand(function (x, y, type) {
var self = Container.call(this);
self.x = x;
self.y = y;
var effectGraphics = self.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
effectGraphics.blendMode = 1;
// Create energy particles around main effect
var particles = [];
for (var i = 0; i < 6; i++) {
var particle = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
particle.width = 8;
particle.height = 8;
var angle = i / 6 * Math.PI * 2;
particle.x = Math.cos(angle) * 30;
particle.y = Math.sin(angle) * 30;
particles.push(particle);
}
switch (type) {
case 'splash':
effectGraphics.tint = 0x33CC00;
effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5;
for (var i = 0; i < particles.length; i++) {
particles[i].tint = 0x88FF44;
}
break;
case 'slow':
effectGraphics.tint = 0x9900FF;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
for (var i = 0; i < particles.length; i++) {
particles[i].tint = 0xCC44FF;
}
break;
case 'poison':
effectGraphics.tint = 0x00FFAA;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
for (var i = 0; i < particles.length; i++) {
particles[i].tint = 0x44FFCC;
}
break;
case 'sniper':
effectGraphics.tint = 0xFF5500;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
for (var i = 0; i < particles.length; i++) {
particles[i].tint = 0xFF8844;
}
break;
}
effectGraphics.alpha = 0.7;
// Animate particles spiraling outward
for (var i = 0; i < particles.length; i++) {
var particle = particles[i];
var delay = i * 50;
tween(particle, {
x: particle.x * 2,
y: particle.y * 2,
scaleX: 0.5,
scaleY: 0.5,
alpha: 0
}, {
duration: 400,
delay: delay,
easing: tween.easeOut
});
}
self.alpha = 0;
// Animate the effect
tween(self, {
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
self.destroy = function () {
if (self.isDestroyed) {
return;
}
self.isDestroyed = true;
// Stop all active tweens on this effect and its children
tween.stop(self);
for (var i = 0; i < self.children.length; i++) {
if (self.children[i]) {
tween.stop(self.children[i]);
}
}
// Remove from activeEffects array
var effectIndex = activeEffects.indexOf(self);
if (effectIndex !== -1) {
activeEffects.splice(effectIndex, 1);
}
// Remove from parent container
if (self.parent) {
self.parent.removeChild(self);
}
// Call parent destroy
Container.prototype.destroy.call(self);
};
return self;
});
// Base enemy class for common functionality
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = .01;
self.cellX = 0;
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
self.maxHealth = 100;
self.health = self.maxHealth;
self.bulletsTargetingThis = [];
self.waveNumber = currentWave;
self.isFlying = false;
self.isImmune = false;
self.isBoss = false;
self.isDestroyed = false;
// Check if this is a boss wave
// Check if this is a boss wave
// Apply different stats based on enemy type
switch (self.type) {
case 'fast':
self.speed *= 2; // Twice as fast
self.maxHealth = 100;
break;
case 'immune':
self.isImmune = true;
self.maxHealth = 80;
break;
case 'flying':
self.isFlying = true;
self.maxHealth = 80;
break;
case 'swarm':
self.maxHealth = 50; // Weaker enemies
break;
case 'normal':
default:
// Normal enemy uses default values
break;
}
if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') {
self.isBoss = true;
// Boss enemies have 20x health and are larger
self.maxHealth *= 20;
// Slower speed for bosses
self.speed = self.speed * 0.7;
}
self.health = self.maxHealth;
// Get appropriate asset for this enemy type
var assetId = 'enemy';
if (self.type !== 'normal') {
assetId = 'enemy_' + self.type;
}
var enemyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
// Scale up boss enemies
if (self.isBoss) {
enemyGraphics.scaleX = 1.8;
enemyGraphics.scaleY = 1.8;
}
// Fall back to regular enemy asset if specific type asset not found
// Apply tint to differentiate enemy types
/*switch (self.type) {
case 'fast':
enemyGraphics.tint = 0x00AAFF; // Blue for fast enemies
break;
case 'immune':
enemyGraphics.tint = 0xAA0000; // Red for immune enemies
break;
case 'flying':
enemyGraphics.tint = 0xFFFF00; // Yellow for flying enemies
break;
case 'swarm':
enemyGraphics.tint = 0xFF00FF; // Pink for swarm enemies
break;
}*/
// Create shadow for flying enemies
if (self.isFlying) {
// Create a shadow container that will be added to the shadow layer
self.shadow = new Container();
// Clone the enemy graphics for the shadow
var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', {
anchorX: 0.5,
anchorY: 0.5
});
// Apply shadow effect
shadowGraphics.tint = 0x000000; // Black shadow
shadowGraphics.alpha = 0.4; // Semi-transparent
// If this is a boss, scale up the shadow to match
if (self.isBoss) {
shadowGraphics.scaleX = 1.8;
shadowGraphics.scaleY = 1.8;
}
// Position shadow slightly offset
self.shadow.x = 20; // Offset right
self.shadow.y = 20; // Offset down
// Ensure shadow has the same rotation as the enemy
shadowGraphics.rotation = enemyGraphics.rotation;
}
var healthBarOutline = self.attachAsset('healthBarOutline', {
anchorX: 0,
anchorY: 0.5
});
var healthBarBG = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
var healthBar = self.attachAsset('healthBar', {
anchorX: 0,
anchorY: 0.5
});
healthBarBG.y = healthBarOutline.y = healthBar.y = -enemyGraphics.height / 2 - 10;
healthBarOutline.x = -healthBarOutline.width / 2;
healthBarBG.x = healthBar.x = -healthBar.width / 2 - .5;
healthBar.tint = 0x00ff00;
healthBarBG.tint = 0xff0000;
self.healthBar = healthBar;
// Add visual type indicator for better enemy identification
if (self.type !== 'normal') {
var typeIndicator = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
typeIndicator.width = 16;
typeIndicator.height = 16;
typeIndicator.y = -enemyGraphics.height / 2 - 35; // Above health bar
// Color code the indicator based on enemy type
switch (self.type) {
case 'fast':
typeIndicator.tint = 0x00AAFF;
break;
case 'immune':
typeIndicator.tint = 0xAA0000;
break;
case 'flying':
typeIndicator.tint = 0xFFFF00;
break;
case 'swarm':
typeIndicator.tint = 0xFF00FF;
break;
}
// Make boss indicators larger and more prominent
if (self.isBoss) {
typeIndicator.width = 24;
typeIndicator.height = 24;
typeIndicator.tint = 0xFFD700; // Gold for bosses
// Add pulsing effect for boss indicator
tween(typeIndicator, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(typeIndicator, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
});
}
}
self.update = function () {
if (self.health <= 0) {
self.health = 0;
self.healthBar.width = 0;
}
// Handle slow effect
if (self.isImmune) {
// Immune enemies cannot be slowed or poisoned, clear any such effects
self.slowed = false;
self.slowEffect = false;
self.poisoned = false;
self.poisonEffect = false;
// Reset speed to original if needed
if (self.originalSpeed !== undefined) {
self.speed = self.originalSpeed;
}
} else {
// Handle slow effect
if (self.slowed) {
// Visual indication of slowed status
if (!self.slowEffect) {
self.slowEffect = true;
}
self.slowDuration--;
if (self.slowDuration <= 0) {
self.speed = self.originalSpeed;
self.slowed = false;
self.slowEffect = false;
// Only reset tint if not poisoned
if (!self.poisoned) {
enemyGraphics.tint = 0xFFFFFF; // Reset tint
}
}
}
// Handle poison effect
if (self.poisoned) {
// Visual indication of poisoned status
if (!self.poisonEffect) {
self.poisonEffect = true;
}
// Apply poison damage every 30 frames (twice per second)
if (LK.ticks % 30 === 0) {
self.health -= self.poisonDamage;
if (self.health <= 0) {
self.health = 0;
}
self.healthBar.width = self.health / self.maxHealth * 70;
}
self.poisonDuration--;
if (self.poisonDuration <= 0) {
self.poisoned = false;
self.poisonEffect = false;
// Only reset tint if not slowed
if (!self.slowed) {
enemyGraphics.tint = 0xFFFFFF; // Reset tint
}
}
}
}
// Improved visual clarity for enemy types with better differentiation
var baseTint = 0xFFFFFF;
// First set base tint based on enemy type for better visual clarity
switch (self.type) {
case 'fast':
baseTint = 0x4488FF; // Bright blue for fast enemies
break;
case 'immune':
baseTint = 0xFF4444; // Bright red for immune enemies
break;
case 'flying':
baseTint = 0xFFDD44; // Bright yellow for flying enemies
break;
case 'swarm':
baseTint = 0xFF44DD; // Bright magenta for swarm enemies
break;
case 'normal':
default:
baseTint = 0xCCCCCC; // Light gray for normal enemies
break;
}
// Apply boss scaling to tint if this is a boss
if (self.isBoss) {
// Make boss enemies more vibrant and add a red outline effect
var r = baseTint >> 16 & 0xFF;
var g = baseTint >> 8 & 0xFF;
var b = baseTint & 0xFF;
// Boost color intensity for bosses
r = Math.min(255, Math.floor(r * 1.3));
g = Math.min(255, Math.floor(g * 1.3));
b = Math.min(255, Math.floor(b * 1.3));
baseTint = r << 16 | g << 8 | b;
}
// Then apply status effect overlays
if (self.isImmune) {
// Keep immune tint but add pulsing effect for better visibility
enemyGraphics.tint = baseTint;
// Add shield shimmer effect for immune enemies
if (!self.shieldEffect && !self.isDestroyed) {
self.shieldEffect = true;
tween(enemyGraphics, {
alpha: 0.8
}, {
duration: 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.isDestroyed) {
tween(enemyGraphics, {
alpha: 1.0
}, {
duration: 500,
easing: tween.easeInOut
});
}
}
});
}
} else if (self.poisoned && self.slowed) {
// Blend poison green with slow purple over the base tint
enemyGraphics.tint = 0x4C7FD4; // Keep existing blend color
} else if (self.poisoned) {
// Poison overlay - green tint
var r = Math.floor((baseTint >> 16 & 0xFF) * 0.5);
var g = Math.min(255, Math.floor((baseTint >> 8 & 0xFF) * 0.5 + 170));
var b = Math.floor((baseTint & 0xFF) * 0.5 + 85);
enemyGraphics.tint = r << 16 | g << 8 | b;
// Add crackling energy effect for poisoned
if (!self.poisonFlicker && !self.isDestroyed) {
self.poisonFlicker = true;
tween(enemyGraphics, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
if (!self.isDestroyed) {
tween(enemyGraphics, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
self.poisonFlicker = false;
}
});
}
}
});
}
} else if (self.slowed) {
// Slow overlay - purple tint
var r = Math.floor((baseTint >> 16 & 0xFF) * 0.5 + 76);
var g = Math.floor((baseTint >> 8 & 0xFF) * 0.5);
var b = Math.min(255, Math.floor((baseTint & 0xFF) * 0.5 + 127));
enemyGraphics.tint = r << 16 | g << 8 | b;
} else {
enemyGraphics.tint = baseTint;
self.shieldEffect = false;
}
if (self.currentTarget) {
var ox = self.currentTarget.x - self.currentCellX;
var oy = self.currentTarget.y - self.currentCellY;
if (ox !== 0 || oy !== 0) {
var angle = Math.atan2(oy, ox);
if (enemyGraphics.targetRotation === undefined) {
enemyGraphics.targetRotation = angle;
enemyGraphics.rotation = angle;
} else {
if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05 && !self.isDestroyed) {
tween.stop(enemyGraphics, {
rotation: true
});
// Calculate the shortest angle to rotate
var currentRotation = enemyGraphics.rotation;
var angleDiff = angle - currentRotation;
// Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemyGraphics.targetRotation = angle;
tween(enemyGraphics, {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
}
}
healthBarOutline.y = healthBarBG.y = healthBar.y = -enemyGraphics.height / 2 - 10;
};
self.destroy = function () {
if (self.isDestroyed) {
return;
}
self.isDestroyed = true;
// Stop all active tweens on this enemy and its graphics
tween.stop(self);
if (self.children && self.children[0]) {
tween.stop(self.children[0]);
}
// Clear all bullets targeting this enemy
for (var i = 0; i < self.bulletsTargetingThis.length; i++) {
var bullet = self.bulletsTargetingThis[i];
if (bullet) {
bullet.targetEnemy = null;
}
}
self.bulletsTargetingThis = [];
// Remove from enemies array
var enemyIndex = enemies.indexOf(self);
if (enemyIndex !== -1) {
enemies.splice(enemyIndex, 1);
}
// Remove from parent container
if (self.parent) {
self.parent.removeChild(self);
}
// Clean up shadow if it's a flying enemy
if (self.isFlying && self.shadow) {
if (self.shadow.parent) {
self.shadow.parent.removeChild(self.shadow);
}
self.shadow = null;
}
// Nullify all object references
self.targetEnemy = null;
self.currentTarget = null;
self.flyingTarget = null;
self.healthBar = null;
self.energyCore = null;
// Call parent destroy
Container.prototype.destroy.call(self);
};
return self;
});
var GoldIndicator = Container.expand(function (value, x, y) {
var self = Container.call(this);
var shadowText = new Text2("+" + value, {
size: 45,
fill: 0x000000,
weight: 800
});
shadowText.anchor.set(0.5, 0.5);
shadowText.x = 2;
shadowText.y = 2;
self.addChild(shadowText);
var goldText = new Text2("+" + value, {
size: 45,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
self.addChild(goldText);
self.x = x;
self.y = y;
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
y: y - 40
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
y: y - 80
}, {
duration: 600,
easing: tween.easeIn,
delay: 800,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
var Grid = Container.expand(function (gridWidth, gridHeight) {
var self = Container.call(this);
self.cells = [];
self.spawns = [];
self.goals = [];
for (var i = 0; i < gridWidth; i++) {
self.cells[i] = [];
for (var j = 0; j < gridHeight; j++) {
self.cells[i][j] = {
score: 0,
pathId: 0,
towersInRange: []
};
}
}
/*
Cell Types
0: Transparent floor
1: Wall
2: Spawn
3: Goal
*/
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var cell = self.cells[i][j];
var cellType = i === 0 || i === gridWidth - 1 || j <= 4 || j >= gridHeight - 4 ? 1 : 0;
if (i > 11 - 3 && i <= 11 + 3) {
if (j === 0) {
cellType = 2;
self.spawns.push(cell);
} else if (j <= 4) {
cellType = 0;
} else if (j === gridHeight - 1) {
cellType = 3;
self.goals.push(cell);
} else if (j >= gridHeight - 4) {
cellType = 0;
}
}
cell.type = cellType;
cell.x = i;
cell.y = j;
cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1];
cell.up = self.cells[i - 1] && self.cells[i - 1][j];
cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1];
cell.left = self.cells[i][j - 1];
cell.right = self.cells[i][j + 1];
cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1];
cell.down = self.cells[i + 1] && self.cells[i + 1][j];
cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1];
cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left];
cell.targets = [];
if (j > 3 && j <= gridHeight - 4) {
var debugCell = new DebugCell();
self.addChild(debugCell);
debugCell.cell = cell;
debugCell.x = i * CELL_SIZE;
debugCell.y = j * CELL_SIZE;
cell.debugCell = debugCell;
}
}
}
self.getCell = function (x, y) {
return self.cells[x] && self.cells[x][y];
};
self.pathFind = function () {
var before = new Date().getTime();
var toProcess = self.goals.concat([]);
maxScore = 0;
pathId += 1;
for (var a = 0; a < toProcess.length; a++) {
toProcess[a].pathId = pathId;
}
function processNode(node, targetValue, targetNode) {
if (node && node.type != 1) {
if (node.pathId < pathId || targetValue < node.score) {
node.targets = [targetNode];
} else if (node.pathId == pathId && targetValue == node.score) {
node.targets.push(targetNode);
}
if (node.pathId < pathId || targetValue < node.score) {
node.score = targetValue;
if (node.pathId != pathId) {
toProcess.push(node);
}
node.pathId = pathId;
if (targetValue > maxScore) {
maxScore = targetValue;
}
}
}
}
while (toProcess.length) {
var nodes = toProcess;
toProcess = [];
for (var a = 0; a < nodes.length; a++) {
var node = nodes[a];
var targetScore = node.score + 14142;
if (node.up && node.left && node.up.type != 1 && node.left.type != 1) {
processNode(node.upLeft, targetScore, node);
}
if (node.up && node.right && node.up.type != 1 && node.right.type != 1) {
processNode(node.upRight, targetScore, node);
}
if (node.down && node.right && node.down.type != 1 && node.right.type != 1) {
processNode(node.downRight, targetScore, node);
}
if (node.down && node.left && node.down.type != 1 && node.left.type != 1) {
processNode(node.downLeft, targetScore, node);
}
targetScore = node.score + 10000;
processNode(node.up, targetScore, node);
processNode(node.right, targetScore, node);
processNode(node.down, targetScore, node);
processNode(node.left, targetScore, node);
}
}
for (var a = 0; a < self.spawns.length; a++) {
if (self.spawns[a].pathId != pathId) {
console.warn("Spawn blocked");
return true;
}
}
for (var a = 0; a < enemies.length; a++) {
var enemy = enemies[a];
// Skip enemies that haven't entered the viewable area yet
if (enemy.currentCellY < 4) {
continue;
}
// Skip flying enemies from path check as they can fly over obstacles
if (enemy.isFlying) {
continue;
}
var target = self.getCell(enemy.cellX, enemy.cellY);
if (enemy.currentTarget) {
if (enemy.currentTarget.pathId != pathId) {
if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 1 ");
return true;
}
}
} else if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 2");
return true;
}
}
console.log("Speed", new Date().getTime() - before);
};
self.renderDebug = function () {
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var debugCell = self.cells[i][j].debugCell;
if (debugCell) {
debugCell.render(self.cells[i][j]);
}
}
}
};
self.updateEnemy = function (enemy) {
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell.type == 3) {
return true;
}
if (enemy.isFlying && enemy.shadow) {
enemy.shadow.x = enemy.x + 20; // Match enemy x-position + offset
enemy.shadow.y = enemy.y + 20; // Match enemy y-position + offset
// Match shadow rotation with enemy rotation
if (enemy.children[0] && enemy.shadow.children[0]) {
enemy.shadow.children[0].rotation = enemy.children[0].rotation;
}
}
// Check if the enemy has reached the entry area (y position is at least 5)
var hasReachedEntryArea = enemy.currentCellY >= 4;
// If enemy hasn't reached the entry area yet, just move down vertically
if (!hasReachedEntryArea) {
// Move directly downward
enemy.currentCellY += enemy.speed;
// Rotate enemy graphic to face downward (PI/2 radians = 90 degrees)
var angle = Math.PI / 2;
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05 && !enemy.isDestroyed) {
tween.stop(enemy.children[0], {
rotation: true
});
// Calculate the shortest angle to rotate
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
// Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
// Set target rotation and animate to it
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
// Update enemy's position
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
// If enemy has now reached the entry area, update cell coordinates
if (enemy.currentCellY >= 4) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
}
return false;
}
// After reaching entry area, handle flying enemies differently
if (enemy.isFlying) {
// Flying enemies head straight to the closest goal
if (!enemy.flyingTarget) {
// Set flying target to the closest goal
enemy.flyingTarget = self.goals[0];
// Find closest goal if there are multiple
if (self.goals.length > 1) {
var closestDist = Infinity;
for (var i = 0; i < self.goals.length; i++) {
var goal = self.goals[i];
var dx = goal.x - enemy.cellX;
var dy = goal.y - enemy.cellY;
var dist = dx * dx + dy * dy;
if (dist < closestDist) {
closestDist = dist;
enemy.flyingTarget = goal;
}
}
}
}
// Move directly toward the goal
var ox = enemy.flyingTarget.x - enemy.currentCellX;
var oy = enemy.flyingTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
// Reached the goal
return true;
}
var angle = Math.atan2(oy, ox);
// Rotate enemy graphic to match movement direction
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05 && !enemy.isDestroyed) {
tween.stop(enemy.children[0], {
rotation: true
});
// Calculate the shortest angle to rotate
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
// Normalize angle difference to -PI to PI range for shortest path
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
// Set target rotation and animate to it
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
// Update the cell position to track where the flying enemy is
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
// Update shadow position if this is a flying enemy
return false;
}
// Handle normal pathfinding enemies
if (!enemy.currentTarget) {
enemy.currentTarget = cell.targets[0];
}
if (enemy.currentTarget) {
if (cell.score < enemy.currentTarget.score) {
enemy.currentTarget = cell;
}
var ox = enemy.currentTarget.x - enemy.currentCellX;
var oy = enemy.currentTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentTarget = undefined;
return;
}
var angle = Math.atan2(oy, ox);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
}
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
};
});
var Hero = Container.expand(function (heroType) {
var self = Container.call(this);
self.heroType = heroType || 'tank';
self.level = storage['hero_' + heroType + '_level'] || 1;
self.experience = storage['hero_' + heroType + '_xp'] || 0;
self.maxHealth = 200;
self.health = self.maxHealth;
self.isDestroyed = false;
self.movementSpeed = 1.5;
self.abilities = [];
self.currentTarget = null;
self.lastAbilityUse = 0;
self.abilityCooldown = 300; // 5 seconds at 60fps
// Set hero-specific stats
switch (self.heroType) {
case 'tank':
self.maxHealth = 300 + (self.level - 1) * 50;
self.movementSpeed = 1.0;
self.abilityCooldown = 600; // 10 seconds
break;
case 'support':
self.maxHealth = 150 + (self.level - 1) * 30;
self.movementSpeed = 2.0;
self.abilityCooldown = 480; // 8 seconds
break;
case 'dps':
self.maxHealth = 180 + (self.level - 1) * 35;
self.movementSpeed = 1.8;
self.abilityCooldown = 360; // 6 seconds
break;
}
self.health = self.maxHealth;
// Get appropriate asset
var assetName = 'hero_' + self.heroType;
var heroGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Create health bar
var healthBarOutline = self.attachAsset('heroHealthBarOutline', {
anchorX: 0,
anchorY: 0.5
});
var healthBar = self.attachAsset('heroHealthBar', {
anchorX: 0,
anchorY: 0.5
});
healthBarOutline.y = healthBar.y = -heroGraphics.height / 2 - 20;
healthBarOutline.x = -healthBarOutline.width / 2;
healthBar.x = -healthBar.width / 2;
self.healthBar = healthBar;
// Create XP bar
var xpBarOutline = self.attachAsset('heroXpBarOutline', {
anchorX: 0,
anchorY: 0.5
});
var xpBar = self.attachAsset('heroXpBar', {
anchorX: 0,
anchorY: 0.5
});
xpBarOutline.y = xpBar.y = -heroGraphics.height / 2 - 35;
xpBarOutline.x = -xpBarOutline.width / 2;
xpBar.x = -xpBar.width / 2;
self.xpBar = xpBar;
// Create ability icon
var abilityIcon = self.attachAsset('heroAbilityIcon', {
anchorX: 0.5,
anchorY: 0.5
});
abilityIcon.y = heroGraphics.height / 2 + 20;
self.abilityIcon = abilityIcon;
// Level indicator
var levelText = new Text2(self.level.toString(), {
size: 40,
fill: 0xFFFFFF,
weight: 800
});
levelText.anchor.set(0.5, 0.5);
levelText.y = heroGraphics.height / 2 + 20;
levelText.x = 40;
self.addChild(levelText);
self.levelText = levelText;
self.getExperienceForNextLevel = function () {
return self.level * 100; // 100 XP per level
};
self.gainExperience = function (amount) {
self.experience += amount;
var xpNeeded = self.getExperienceForNextLevel();
if (self.experience >= xpNeeded) {
self.experience -= xpNeeded;
self.level++;
self.levelText.setText(self.level.toString());
// Save progression
storage['hero_' + self.heroType + '_level'] = self.level;
// Level up effects
self.onLevelUp();
// Visual level up effect
tween(heroGraphics, {
scaleX: 1.3,
scaleY: 1.3,
tint: 0xFFD700
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(heroGraphics, {
scaleX: 1.0,
scaleY: 1.0,
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeIn
});
}
});
}
// Save XP
storage['hero_' + self.heroType + '_xp'] = self.experience;
self.updateXpBar();
};
self.onLevelUp = function () {
// Increase health
var healthIncrease = self.heroType === 'tank' ? 50 : self.heroType === 'support' ? 30 : 35;
self.maxHealth += healthIncrease;
self.health = self.maxHealth; // Full heal on level up
self.updateHealthBar();
};
self.updateHealthBar = function () {
self.healthBar.width = self.health / self.maxHealth * 80;
};
self.updateXpBar = function () {
var xpProgress = self.experience / self.getExperienceForNextLevel();
self.xpBar.width = xpProgress * 80;
};
self.useAbility = function () {
if (LK.ticks - self.lastAbilityUse < self.abilityCooldown) {
return false; // Ability on cooldown
}
self.lastAbilityUse = LK.ticks;
// Ability cooldown visual effect
tween(self.abilityIcon, {
tint: 0x888888,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: self.abilityCooldown,
easing: tween.linear,
onFinish: function onFinish() {
tween(self.abilityIcon, {
tint: 0xFFD700,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeOut
});
}
});
switch (self.heroType) {
case 'tank':
return self.tankAbility();
case 'support':
return self.supportAbility();
case 'dps':
return self.dpsAbility();
}
return false;
};
self.tankAbility = function () {
// Taunt - Draw all enemies within range to attack hero
var tauntRadius = CELL_SIZE * 4;
var taunted = 0;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= tauntRadius) {
enemy.heroTarget = self;
enemy.heroTargetDuration = 300; // 5 seconds
taunted++;
}
}
// Visual effect
var effectRadius = tauntRadius;
var tauntEffect = new Container();
tauntEffect.x = self.x;
tauntEffect.y = self.y;
var effectGraphics = tauntEffect.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
effectGraphics.width = effectGraphics.height = effectRadius * 2;
effectGraphics.tint = 0x4CAF50;
effectGraphics.alpha = 0.6;
game.addChild(tauntEffect);
tween(tauntEffect, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
if (tauntEffect.parent) {
tauntEffect.parent.removeChild(tauntEffect);
}
}
});
return taunted > 0;
};
self.supportAbility = function () {
// Buff nearby towers
var buffRadius = CELL_SIZE * 3;
var buffed = 0;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = tower.x - self.x;
var dy = tower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= buffRadius) {
tower.heroBuff = {
damage: 1.5,
fireRate: 0.7,
duration: 600 // 10 seconds
};
buffed++;
// Visual effect on tower
tween(tower.children[0], {
tint: 0x2196F3
}, {
duration: 600,
easing: tween.linear,
onFinish: function onFinish() {
tower.children[0].tint = 0x445566;
}
});
}
}
return buffed > 0;
};
self.dpsAbility = function () {
// Area damage attack
var damageRadius = CELL_SIZE * 2.5;
var damaged = 0;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= damageRadius) {
var damage = 100 + (self.level - 1) * 20;
enemy.health -= damage;
if (enemy.health <= 0) {
enemy.health = 0;
} else {
enemy.healthBar.width = enemy.health / enemy.maxHealth * 70;
}
damaged++;
// Visual damage effect
tween(enemy, {
tint: 0xFF5722
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
// Visual explosion effect
var explosionEffect = new Container();
explosionEffect.x = self.x;
explosionEffect.y = self.y;
var effectGraphics = explosionEffect.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
effectGraphics.width = effectGraphics.height = damageRadius * 2;
effectGraphics.tint = 0xFF5722;
effectGraphics.alpha = 0.8;
game.addChild(explosionEffect);
tween(explosionEffect, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (explosionEffect.parent) {
explosionEffect.parent.removeChild(explosionEffect);
}
}
});
return damaged > 0;
};
self.update = function () {
if (self.health <= 0) {
return;
}
// Update bars
self.updateHealthBar();
self.updateXpBar();
// Move towards enemies or patrol
if (!self.currentTarget) {
self.findTarget();
}
if (self.currentTarget && self.currentTarget.parent && self.currentTarget.health > 0) {
var dx = self.currentTarget.x - self.x;
var dy = self.currentTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > CELL_SIZE * 0.8) {
// Move towards target
var angle = Math.atan2(dy, dx);
self.x += Math.cos(angle) * self.movementSpeed;
self.y += Math.sin(angle) * self.movementSpeed;
} else {
// Attack target
if (self.heroType === 'tank') {
self.currentTarget.health -= 15;
} else if (self.heroType === 'dps') {
self.currentTarget.health -= 25;
} else {
self.currentTarget.health -= 10;
}
if (self.currentTarget.health <= 0) {
self.currentTarget.health = 0;
self.gainExperience(20);
self.currentTarget = null;
} else {
self.currentTarget.healthBar.width = self.currentTarget.health / self.currentTarget.maxHealth * 70;
}
}
} else {
self.currentTarget = null;
}
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.health <= 0) continue;
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance && distance < CELL_SIZE * 5) {
closestDistance = distance;
closestEnemy = enemy;
}
}
self.currentTarget = closestEnemy;
};
self.down = function (x, y, obj) {
// Use ability when tapped
if (self.useAbility()) {
var notification = game.addChild(new Notification("Hero ability activated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
}
};
self.destroy = function () {
if (self.isDestroyed) {
return;
}
self.isDestroyed = true;
tween.stop(self);
for (var i = 0; i < self.children.length; i++) {
if (self.children[i]) {
tween.stop(self.children[i]);
}
}
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
var NebulaCloud = Container.expand(function (x, y) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.radius = 100;
self.isDestroyed = false;
var nebulaGraphics = self.attachAsset('nebulaCloud', {
anchorX: 0.5,
anchorY: 0.5
});
nebulaGraphics.alpha = 0.6;
// Pulsing animation
tween(nebulaGraphics, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.isDestroyed) {
tween(nebulaGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.6
}, {
duration: 2000,
easing: tween.easeInOut
});
}
}
});
self.update = function () {
// Apply effects to units within the nebula
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.radius) {
// Slow effect
if (!enemy.nebulaSlowed) {
enemy.originalSpeed = enemy.originalSpeed || enemy.speed;
enemy.speed *= 0.7; // 30% speed reduction
enemy.nebulaSlowed = true;
}
// Stealth effect - harder for towers to target
enemy.nebulaStealthed = true;
} else {
// Remove effects when outside nebula
if (enemy.nebulaSlowed) {
enemy.speed = enemy.originalSpeed || enemy.speed;
enemy.nebulaSlowed = false;
}
enemy.nebulaStealthed = false;
}
}
// Apply slow effect to heroes
for (var i = 0; i < heroes.length; i++) {
var hero = heroes[i];
var dx = hero.x - self.x;
var dy = hero.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.radius) {
if (!hero.nebulaSlowed) {
hero.originalMovementSpeed = hero.originalMovementSpeed || hero.movementSpeed;
hero.movementSpeed *= 0.7;
hero.nebulaSlowed = true;
}
} else {
if (hero.nebulaSlowed) {
hero.movementSpeed = hero.originalMovementSpeed || hero.movementSpeed;
hero.nebulaSlowed = false;
}
}
}
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
// Remove effects from all units
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.nebulaSlowed) {
enemy.speed = enemy.originalSpeed || enemy.speed;
enemy.nebulaSlowed = false;
}
enemy.nebulaStealthed = false;
}
for (var i = 0; i < heroes.length; i++) {
var hero = heroes[i];
if (hero.nebulaSlowed) {
hero.movementSpeed = hero.originalMovementSpeed || hero.movementSpeed;
hero.nebulaSlowed = false;
}
}
var nebulaIndex = nebulaClouds.indexOf(self);
if (nebulaIndex !== -1) {
nebulaClouds.splice(nebulaIndex, 1);
}
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
var NextWaveButton = Container.expand(function () {
var self = Container.call(this);
var buttonBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 300;
buttonBackground.height = 100;
buttonBackground.tint = 0x0088FF;
var buttonText = new Text2("Next Wave", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.enabled = false;
self.visible = false;
self.update = function () {
if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) {
self.enabled = true;
self.visible = true;
buttonBackground.tint = 0x0088FF;
self.alpha = 1;
} else {
self.enabled = false;
self.visible = false;
buttonBackground.tint = 0x888888;
self.alpha = 0.7;
}
};
self.down = function () {
if (!self.enabled) {
return;
}
if (waveIndicator.gameStarted && currentWave < totalWaves) {
currentWave++; // Increment to the next wave directly
waveTimer = 0; // Reset wave timer
waveInProgress = true;
waveSpawned = false;
// Get the type of the current wave (which is now the next wave)
var waveType = waveIndicator.getWaveTypeName(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
};
return self;
});
var Notification = Container.expand(function (message) {
var self = Container.call(this);
var notificationGraphics = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
var notificationText = new Text2(message, {
size: 50,
fill: 0x000000,
weight: 800
});
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 30;
self.addChild(notificationText);
self.alpha = 1;
var fadeOutTime = 120;
self.update = function () {
if (fadeOutTime > 0) {
fadeOutTime--;
self.alpha = Math.min(fadeOutTime / 120 * 2, 1);
} else {
self.destroy();
}
};
return self;
});
var SolarFlare = Container.expand(function () {
var self = Container.call(this);
self.duration = 600; // 10 seconds at 60fps
self.remainingTime = self.duration;
self.isActive = true;
self.isDestroyed = false;
// Position in center of battlefield
self.x = grid.x + 12 * CELL_SIZE;
self.y = grid.y + 15 * CELL_SIZE;
var flareGraphics = self.attachAsset('solarFlare', {
anchorX: 0.5,
anchorY: 0.5
});
flareGraphics.alpha = 0.8;
flareGraphics.blendMode = 1; // Additive blending
// Intense pulsing animation
tween(flareGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.isDestroyed) {
tween(flareGraphics, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 300,
easing: tween.easeInOut
});
}
}
});
// Show notification
var notification = game.addChild(new Notification("⚡ Solar Flare! Energy towers boosted! ⚡"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
self.update = function () {
self.remainingTime--;
if (self.remainingTime <= 0) {
self.destroy();
return;
}
// Boost energy-based towers (rapid, sniper, default)
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.id === 'rapid' || tower.id === 'sniper' || tower.id === 'default') {
if (!tower.solarFlareBoost) {
tower.originalFireRate = tower.fireRate;
tower.originalDamage = tower.damage;
tower.fireRate = Math.floor(tower.fireRate * 0.6); // 40% faster
tower.damage = Math.floor(tower.damage * 1.3); // 30% more damage
tower.solarFlareBoost = true;
// Visual effect on boosted towers
tween(tower.energyCore, {
tint: 0xFFD700,
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut
});
}
}
}
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
// Remove boosts from all towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.solarFlareBoost) {
tower.fireRate = tower.originalFireRate;
tower.damage = tower.originalDamage;
tower.solarFlareBoost = false;
// Reset visual effects
var originalTint = 0x88AACC;
switch (tower.id) {
case 'rapid':
originalTint = 0x00AAFF;
break;
case 'sniper':
originalTint = 0xFF5500;
break;
case 'default':
originalTint = 0x88AACC;
break;
}
tween(tower.energyCore, {
tint: originalTint,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
}
}
var flareIndex = solarFlares.indexOf(self);
if (flareIndex !== -1) {
solarFlares.splice(flareIndex, 1);
}
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
var SourceTower = Container.expand(function (towerType) {
var self = Container.call(this);
self.towerType = towerType || 'default';
// Increase size of base for easier touch
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
switch (self.towerType) {
case 'rapid':
baseGraphics.tint = 0x00AAFF;
break;
case 'sniper':
baseGraphics.tint = 0xFF5500;
break;
case 'splash':
baseGraphics.tint = 0x33CC00;
break;
case 'slow':
baseGraphics.tint = 0x9900FF;
break;
case 'poison':
baseGraphics.tint = 0x00FFAA;
break;
default:
baseGraphics.tint = 0xAAAAAA;
}
var towerCost = getTowerCost(self.towerType);
// Add shadow for tower type label
var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0x000000,
weight: 800
});
typeLabelShadow.anchor.set(0.5, 0.5);
typeLabelShadow.x = 4;
typeLabelShadow.y = -20 + 4;
self.addChild(typeLabelShadow);
// Add tower type label
var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
typeLabel.anchor.set(0.5, 0.5);
typeLabel.y = -20; // Position above center of tower
self.addChild(typeLabel);
// Add cost shadow
var costLabelShadow = new Text2(towerCost, {
size: 50,
fill: 0x000000,
weight: 800
});
costLabelShadow.anchor.set(0.5, 0.5);
costLabelShadow.x = 4;
costLabelShadow.y = 24 + 12;
self.addChild(costLabelShadow);
// Add cost label
var costLabel = new Text2(towerCost, {
size: 50,
fill: 0xFFD700,
weight: 800
});
costLabel.anchor.set(0.5, 0.5);
costLabel.y = 20 + 12;
self.addChild(costLabel);
self.update = function () {
// Check if player can afford this tower
var canAfford = gold >= getTowerCost(self.towerType);
// Set opacity based on affordability
self.alpha = canAfford ? 1 : 0.5;
};
return self;
});
var SpecializationMenu = Container.expand(function (tower) {
var self = Container.call(this);
self.tower = tower;
self.y = 2732 + 300;
var menuBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
menuBackground.width = 1800;
menuBackground.height = 600;
menuBackground.tint = 0x2A2A2A;
menuBackground.alpha = 0.95;
var titleText = new Text2('Choose Specialization Path', {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -200;
self.addChild(titleText);
var subtitleText = new Text2('Your tower has reached level 3 and can specialize!', {
size: 50,
fill: 0xCCCCCC,
weight: 400
});
subtitleText.anchor.set(0.5, 0.5);
subtitleText.y = -140;
self.addChild(subtitleText);
// Create specialization options based on tower type
var options = [];
if (self.tower.id === 'sniper') {
options = [{
type: 'antiarmor',
name: 'Anti-Armor',
desc: 'Pierce shields\n+50% damage vs immune',
color: 0xFF6600
}, {
type: 'longrange',
name: 'Long-Range',
desc: 'Double firing range\nBetter enemy targeting',
color: 0x00AAFF
}];
} else if (self.tower.id === 'splash') {
options = [{
type: 'nuclear',
name: 'Nuclear',
desc: 'Huge damage\nLarger splash radius',
color: 0xFF0000
}, {
type: 'chainreaction',
name: 'Chain Reaction',
desc: 'Damage spreads\nbetween enemies',
color: 0xFFFF00
}];
}
// Create option buttons
for (var i = 0; i < options.length; i++) {
var option = options[i];
var button = new Container();
var buttonBg = button.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 700;
buttonBg.height = 200;
buttonBg.tint = option.color;
buttonBg.alpha = 0.8;
var nameText = new Text2(option.name, {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
nameText.anchor.set(0.5, 0.5);
nameText.y = -40;
button.addChild(nameText);
var descText = new Text2(option.desc, {
size: 45,
fill: 0xFFFFFF,
weight: 400
});
descText.anchor.set(0.5, 0.5);
descText.y = 40;
button.addChild(descText);
button.x = (i - 0.5) * 800;
button.y = 50;
button.optionType = option.type;
button.down = function () {
self.selectSpecialization(this.optionType);
};
self.addChild(button);
}
// Close button
var closeButton = new Container();
var closeBg = closeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
closeBg.width = 100;
closeBg.height = 100;
closeBg.tint = 0xAA0000;
var closeText = new Text2('X', {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
closeButton.x = 850;
closeButton.y = -250;
closeButton.down = function () {
self.destroy();
};
self.addChild(closeButton);
self.selectSpecialization = function (branchType) {
// Apply specialization to tower
var specialization = new TowerSpecialization(self.tower, branchType);
self.tower.specialization = specialization;
self.tower.addChild(specialization);
specialization.applySpecialization();
var notification = game.addChild(new Notification("Tower specialized: " + branchType));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
self.destroy();
};
return self;
});
var StationCustomizer = Container.expand(function () {
var self = Container.call(this);
self.modules = [];
self.moduleSlots = [];
self.selectedSlot = null;
self.customizationMode = false;
// Create module slots around the station
var slotPositions = [{
x: -2,
y: -2
}, {
x: 0,
y: -2
}, {
x: 2,
y: -2
}, {
x: -2,
y: 0
}, {
x: 2,
y: 0
}, {
x: -2,
y: 2
}, {
x: 0,
y: 2
}, {
x: 2,
y: 2
}];
for (var i = 0; i < slotPositions.length; i++) {
var slot = new Container();
var slotGraphics = slot.attachAsset('moduleSocket', {
anchorX: 0.5,
anchorY: 0.5
});
slotGraphics.alpha = 0.5;
slotGraphics.tint = 0x556677;
slot.gridX = slotPositions[i].x;
slot.gridY = slotPositions[i].y;
slot.x = grid.x + (12 + slot.gridX) * CELL_SIZE;
slot.y = grid.y + (15 + slot.gridY) * CELL_SIZE;
slot.module = null;
slot.slotIndex = i;
slot.down = function () {
if (self.customizationMode) {
self.selectedSlot = this;
self.showModuleMenu();
}
};
self.addChild(slot);
self.moduleSlots.push(slot);
}
self.showModuleMenu = function () {
if (!self.selectedSlot) return;
var menu = new Container();
var menuBg = menu.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
menuBg.width = 600;
menuBg.height = 300;
menuBg.tint = 0x333333;
var moduleTypes = ['research', 'shield', 'teleporter'];
for (var i = 0; i < moduleTypes.length; i++) {
var button = new Container();
var buttonBg = button.attachAsset('techTreeNode', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBg.width = 120;
buttonBg.height = 80;
var cost = getModuleCost(moduleTypes[i], 1);
var buttonText = new Text2(moduleTypes[i] + '\n' + cost + 'g', {
size: 30,
fill: 0xFFFFFF,
weight: 600
});
buttonText.anchor.set(0.5, 0.5);
button.addChild(buttonText);
button.moduleType = moduleTypes[i];
button.x = -180 + i * 180;
button.y = 50;
button.down = function () {
self.buildModule(this.moduleType);
menu.destroy();
};
menu.addChild(button);
}
menu.x = 2048 / 2;
menu.y = 1400;
game.addChild(menu);
LK.setTimeout(function () {
if (menu.parent) {
menu.parent.removeChild(menu);
}
}, 5000);
};
self.buildModule = function (moduleType) {
if (!self.selectedSlot || self.selectedSlot.module) return;
var cost = getModuleCost(moduleType, 1);
if (gold < cost) {
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return;
}
var module;
switch (moduleType) {
case 'research':
module = new ResearchLab();
break;
case 'shield':
module = new ShieldGenerator();
break;
case 'teleporter':
module = new TeleporterNode();
break;
default:
module = new StationModule(moduleType);
}
module.gridX = self.selectedSlot.gridX;
module.gridY = self.selectedSlot.gridY;
module.x = self.selectedSlot.x;
module.y = self.selectedSlot.y;
self.selectedSlot.module = module;
self.selectedSlot.children[0].alpha = 0; // Hide slot graphic
self.addChild(module);
self.modules.push(module);
setGold(gold - cost);
var notification = game.addChild(new Notification(moduleType + " module built!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
self.selectedSlot = null;
};
self.toggleCustomizationMode = function () {
self.customizationMode = !self.customizationMode;
for (var i = 0; i < self.moduleSlots.length; i++) {
var slot = self.moduleSlots[i];
if (!slot.module) {
slot.children[0].alpha = self.customizationMode ? 0.8 : 0.3;
}
}
var modeText = self.customizationMode ? "ON" : "OFF";
var notification = game.addChild(new Notification("Customization mode: " + modeText));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
};
self.update = function () {
for (var i = 0; i < self.modules.length; i++) {
if (self.modules[i].update) {
self.modules[i].update();
}
}
};
return self;
});
var StationModule = Container.expand(function (moduleType) {
var self = Container.call(this);
self.moduleType = moduleType || 'basic';
self.level = 1;
self.maxLevel = 3;
self.isActive = true;
self.gridX = 0;
self.gridY = 0;
self.isDestroyed = false;
// Get appropriate asset
var assetName = 'stationModule';
switch (moduleType) {
case 'research':
assetName = 'researchLab';
break;
case 'shield':
assetName = 'shieldGenerator';
break;
case 'teleporter':
assetName = 'teleporter';
break;
}
var moduleGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Add energy core
var energyCore = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
energyCore.width = 30;
energyCore.height = 30;
energyCore.tint = 0x00aaff;
// Level indicators
var levelIndicators = [];
for (var i = 0; i < self.maxLevel; i++) {
var indicator = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.width = 12;
indicator.height = 12;
indicator.x = -30 + i * 30;
indicator.y = 60;
indicator.tint = i < self.level ? 0xffffff : 0x444444;
levelIndicators.push(indicator);
}
self.updateLevelIndicators = function () {
for (var i = 0; i < levelIndicators.length; i++) {
levelIndicators[i].tint = i < self.level ? 0xffffff : 0x444444;
}
};
self.upgrade = function () {
if (self.level < self.maxLevel) {
var upgradeCost = getModuleCost(self.moduleType, self.level + 1);
if (gold >= upgradeCost) {
setGold(gold - upgradeCost);
self.level++;
self.updateLevelIndicators();
self.onUpgrade();
return true;
}
}
return false;
};
self.onUpgrade = function () {
// Override in specific module types
};
self.update = function () {
if (!self.isActive) return;
// Pulsing energy core
if (LK.ticks % 120 === 0) {
tween(energyCore, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(energyCore, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeInOut
});
}
});
}
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
var TeleporterNode = StationModule.expand(function () {
var self = StationModule.call(this, 'teleporter');
self.teleportEnergy = 100;
self.maxTeleportEnergy = 100;
self.linkedNodes = [];
self.onUpgrade = function () {
self.maxTeleportEnergy = 100 + (self.level - 1) * 25;
self.teleportEnergy = self.maxTeleportEnergy;
};
self.teleportHero = function (hero, targetNode) {
if (self.teleportEnergy >= 30 && targetNode && targetNode.teleportEnergy >= 30) {
// Create teleport effect at source
var sourceEffect = new Container();
sourceEffect.x = self.x;
sourceEffect.y = self.y;
var sourceGraphics = sourceEffect.attachAsset('teleportEffect', {
anchorX: 0.5,
anchorY: 0.5
});
sourceGraphics.tint = 0x9c27b0;
sourceGraphics.alpha = 0.8;
game.addChild(sourceEffect);
// Create teleport effect at destination
var destEffect = new Container();
destEffect.x = targetNode.x;
destEffect.y = targetNode.y;
var destGraphics = destEffect.attachAsset('teleportEffect', {
anchorX: 0.5,
anchorY: 0.5
});
destGraphics.tint = 0x9c27b0;
destGraphics.alpha = 0.8;
game.addChild(destEffect);
// Animate effects
tween(sourceEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (sourceEffect.parent) {
sourceEffect.parent.removeChild(sourceEffect);
}
}
});
tween(destEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (destEffect.parent) {
destEffect.parent.removeChild(destEffect);
}
}
});
// Teleport hero
hero.x = targetNode.x;
hero.y = targetNode.y;
// Consume energy
self.teleportEnergy = Math.max(0, self.teleportEnergy - 30);
targetNode.teleportEnergy = Math.max(0, targetNode.teleportEnergy - 30);
return true;
}
return false;
};
self.redirectEnemyPath = function () {
if (self.teleportEnergy >= 50 && self.linkedNodes.length > 0) {
var targetNode = self.linkedNodes[Math.floor(Math.random() * self.linkedNodes.length)];
// Find nearby enemies and redirect one
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (!enemy.isFlying) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= CELL_SIZE * 2) {
// Teleport enemy
enemy.x = targetNode.x;
enemy.y = targetNode.y;
enemy.currentCellX = targetNode.gridX;
enemy.currentCellY = targetNode.gridY;
enemy.currentTarget = null;
self.teleportEnergy = Math.max(0, self.teleportEnergy - 50);
var notification = game.addChild(new Notification("Enemy redirected through teleporter!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
break;
}
}
}
}
};
self.update = function () {
StationModule.prototype.update.call(self);
// Regenerate teleport energy
if (LK.ticks % 120 === 0 && self.teleportEnergy < self.maxTeleportEnergy) {
// Every 2 seconds
self.teleportEnergy = Math.min(self.maxTeleportEnergy, self.teleportEnergy + self.level * 3);
}
};
self.down = function () {
self.redirectEnemyPath();
};
return self;
});
var ShieldGenerator = StationModule.expand(function () {
var self = StationModule.call(this, 'shield');
self.shieldEnergy = 100;
self.maxShieldEnergy = 100;
self.shieldActive = false;
self.shieldEffect = null;
self.onUpgrade = function () {
self.maxShieldEnergy = 100 + (self.level - 1) * 50;
self.shieldEnergy = self.maxShieldEnergy;
};
self.activateShield = function () {
if (self.shieldEnergy >= 50 && !self.shieldActive) {
self.shieldActive = true;
self.shieldEnergy = Math.max(0, self.shieldEnergy - 50);
// Create visual shield effect
self.shieldEffect = new Container();
self.shieldEffect.x = self.x;
self.shieldEffect.y = self.y;
var shieldGraphics = self.shieldEffect.attachAsset('shieldEffect', {
anchorX: 0.5,
anchorY: 0.5
});
shieldGraphics.alpha = 0.3;
shieldGraphics.tint = 0x00ffff;
game.addChild(self.shieldEffect);
// Shield lasts for 10 seconds
LK.setTimeout(function () {
self.deactivateShield();
}, 10000);
var notification = game.addChild(new Notification("Station shields activated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
};
self.deactivateShield = function () {
self.shieldActive = false;
if (self.shieldEffect && self.shieldEffect.parent) {
self.shieldEffect.parent.removeChild(self.shieldEffect);
self.shieldEffect = null;
}
};
self.update = function () {
StationModule.prototype.update.call(self);
// Regenerate shield energy
if (LK.ticks % 180 === 0 && self.shieldEnergy < self.maxShieldEnergy) {
// Every 3 seconds
self.shieldEnergy = Math.min(self.maxShieldEnergy, self.shieldEnergy + self.level * 5);
}
// Update shield effect position
if (self.shieldEffect) {
self.shieldEffect.x = self.x;
self.shieldEffect.y = self.y;
}
};
self.down = function () {
self.activateShield();
};
return self;
});
var ResearchLab = StationModule.expand(function () {
var self = StationModule.call(this, 'research');
self.researchPoints = 0;
self.activeResearch = null;
self.completedTechs = [];
self.onUpgrade = function () {
// Higher level labs generate research points faster
self.researchPointsPerTick = self.level;
};
self.update = function () {
StationModule.prototype.update.call(self);
// Generate research points
if (LK.ticks % 60 === 0) {
// Every second
self.researchPoints += self.level;
storage.researchPoints = self.researchPoints;
}
// Complete active research
if (self.activeResearch && self.researchPoints >= self.activeResearch.cost) {
self.completeResearch();
}
};
self.startResearch = function (techId) {
var tech = getTechnology(techId);
if (tech && !self.activeResearch) {
self.activeResearch = tech;
var notification = game.addChild(new Notification("Research started: " + tech.name));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
};
self.completeResearch = function () {
if (self.activeResearch) {
self.researchPoints -= self.activeResearch.cost;
self.completedTechs.push(self.activeResearch.id);
storage.completedTechs = self.completedTechs;
// Apply research benefits
self.activeResearch.onComplete();
var notification = game.addChild(new Notification("Research complete: " + self.activeResearch.name + "!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
self.activeResearch = null;
}
};
return self;
});
var Tower = Container.expand(function (id) {
var self = Container.call(this);
self.id = id || 'default';
self.level = 1;
self.maxLevel = 6;
self.gridX = 0;
self.gridY = 0;
self.range = 3 * CELL_SIZE;
// Standardized method to get the current range of the tower
self.getRange = function () {
// Always calculate range based on tower type and level
var baseRange;
switch (self.id) {
case 'sniper':
// Sniper: base 5, +0.8 per level, but final upgrade gets a huge boost
if (self.level === self.maxLevel) {
baseRange = 12 * CELL_SIZE; // Significantly increased range for max level
} else {
baseRange = (5 + (self.level - 1) * 0.8) * CELL_SIZE;
}
break;
case 'splash':
// Splash: base 2, +0.2 per level (max ~4 blocks at max level)
return (2 + (self.level - 1) * 0.2) * CELL_SIZE;
case 'rapid':
// Rapid: base 2.5, +0.5 per level
return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'slow':
// Slow: base 3.5, +0.5 per level
return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'poison':
// Poison: base 3.2, +0.5 per level
return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
default:
// Default: base 3, +0.5 per level
baseRange = (3 + (self.level - 1) * 0.5) * CELL_SIZE;
}
// Apply specialization range multiplier
if (self.specialization && self.specialization.branchType === 'longrange') {
baseRange *= 2.0;
}
return baseRange;
};
self.cellsInRange = [];
self.fireRate = 60;
self.bulletSpeed = 5;
self.damage = 10;
self.lastFired = 0;
self.targetEnemy = null;
switch (self.id) {
case 'rapid':
self.fireRate = 35; // Slightly slower to reduce overwhelming power
self.damage = 8; // Increased from 5 for better effectiveness
self.range = 2.8 * CELL_SIZE; // Slightly better range
self.bulletSpeed = 7;
break;
case 'sniper':
self.fireRate = 100; // Slower but more impactful
self.damage = 40; // Increased significantly for true sniper feel
self.range = 5 * CELL_SIZE;
self.bulletSpeed = 25;
break;
case 'splash':
self.fireRate = 80; // Slightly slower
self.damage = 18; // Increased base damage
self.range = 2.2 * CELL_SIZE; // Slightly better range
self.bulletSpeed = 4;
break;
case 'slow':
self.fireRate = 45; // Faster to apply slow effects more consistently
self.damage = 12; // Increased from 8 for better utility
self.range = 3.8 * CELL_SIZE; // Better range for support role
self.bulletSpeed = 5;
break;
case 'poison':
self.fireRate = 60; // Faster to apply poison more effectively
self.damage = 15; // Increased from 12
self.range = 3.5 * CELL_SIZE; // Better range
self.bulletSpeed = 5;
break;
}
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
// Add sci-fi base styling with metallic look
baseGraphics.tint = 0x445566;
// Create energy core based on tower type
var energyCore = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
energyCore.width = 40;
energyCore.height = 40;
switch (self.id) {
case 'rapid':
energyCore.tint = 0x00AAFF;
break;
case 'sniper':
energyCore.tint = 0xFF5500;
break;
case 'splash':
energyCore.tint = 0x33CC00;
break;
case 'slow':
energyCore.tint = 0x9900FF;
break;
case 'poison':
energyCore.tint = 0x00FFAA;
break;
default:
energyCore.tint = 0x88AACC;
}
// Add pulsing energy core animation
self.energyCore = energyCore;
tween(energyCore, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(energyCore, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
var levelIndicators = [];
var maxDots = self.maxLevel;
var dotSpacing = baseGraphics.width / (maxDots + 1);
var dotSize = CELL_SIZE / 6;
for (var i = 0; i < maxDots; i++) {
var dot = new Container();
var outlineCircle = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
outlineCircle.width = dotSize + 4;
outlineCircle.height = dotSize + 4;
outlineCircle.tint = 0x000000;
var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
towerLevelIndicator.width = dotSize;
towerLevelIndicator.height = dotSize;
towerLevelIndicator.tint = 0xCCCCCC;
dot.x = -CELL_SIZE + dotSpacing * (i + 1);
dot.y = CELL_SIZE * 0.7;
self.addChild(dot);
levelIndicators.push(dot);
}
var gunContainer = new Container();
self.addChild(gunContainer);
var gunGraphics = gunContainer.attachAsset('defense', {
anchorX: 0.5,
anchorY: 0.5
});
self.updateLevelIndicators = function () {
for (var i = 0; i < maxDots; i++) {
var dot = levelIndicators[i];
var towerLevelIndicator = dot.children[1];
if (i < self.level) {
towerLevelIndicator.tint = 0xFFFFFF;
} else {
switch (self.id) {
case 'rapid':
towerLevelIndicator.tint = 0x00AAFF;
break;
case 'sniper':
towerLevelIndicator.tint = 0xFF5500;
break;
case 'splash':
towerLevelIndicator.tint = 0x33CC00;
break;
case 'slow':
towerLevelIndicator.tint = 0x9900FF;
break;
case 'poison':
towerLevelIndicator.tint = 0x00FFAA;
break;
default:
towerLevelIndicator.tint = 0xAAAAAA;
}
}
}
};
self.updateLevelIndicators();
self.refreshCellsInRange = function () {
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
var towerIndex = cell.towersInRange.indexOf(self);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
self.cellsInRange = [];
var rangeRadius = self.getRange() / CELL_SIZE;
var centerX = self.gridX + 1;
var centerY = self.gridY + 1;
var minI = Math.floor(centerX - rangeRadius - 0.5);
var maxI = Math.ceil(centerX + rangeRadius + 0.5);
var minJ = Math.floor(centerY - rangeRadius - 0.5);
var maxJ = Math.ceil(centerY + rangeRadius + 0.5);
for (var i = minI; i <= maxI; i++) {
for (var j = minJ; j <= maxJ; j++) {
var closestX = Math.max(i, Math.min(centerX, i + 1));
var closestY = Math.max(j, Math.min(centerY, j + 1));
var deltaX = closestX - centerX;
var deltaY = closestY - centerY;
var distanceSquared = deltaX * deltaX + deltaY * deltaY;
if (distanceSquared <= rangeRadius * rangeRadius) {
var cell = grid.getCell(i, j);
if (cell) {
self.cellsInRange.push(cell);
cell.towersInRange.push(self);
}
}
}
}
grid.renderDebug();
};
self.getTotalValue = function () {
var baseTowerCost = getTowerCost(self.id);
var totalInvestment = baseTowerCost;
var baseUpgradeCost = baseTowerCost; // Upgrade cost now scales with base tower cost
for (var i = 1; i < self.level; i++) {
totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1));
}
return totalInvestment;
};
self.upgrade = function () {
if (self.level < self.maxLevel) {
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.id);
var upgradeCost;
// More balanced upgrade cost scaling
if (self.level === self.maxLevel - 1) {
// Final upgrade is expensive but not prohibitive
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(1.8, self.level - 1) * 2.5);
} else {
// Use 1.8x multiplier instead of 2x for more manageable costs
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(1.8, self.level - 1));
}
if (gold >= upgradeCost) {
setGold(gold - upgradeCost);
self.level++;
// Check for specialization unlock at level 3
if (self.level === 3 && !self.specialization && (self.id === 'sniper' || self.id === 'splash')) {
var specMenu = new SpecializationMenu(self);
game.addChild(specMenu);
specMenu.x = 2048 / 2;
tween(specMenu, {
y: 2732 - 300
}, {
duration: 300,
easing: tween.backOut
});
specializationMenus.push(specMenu);
return true;
}
// No need to update self.range here; getRange() is now the source of truth
// Apply tower-specific upgrades based on type with balanced scaling
switch (self.id) {
case 'rapid':
if (self.level === self.maxLevel) {
// Max level gets significant boost
self.fireRate = Math.max(8, 35 - self.level * 4.2);
self.damage = 8 + self.level * 6; // More modest scaling
self.bulletSpeed = 7 + self.level * 1.5;
} else {
self.fireRate = Math.max(18, 35 - self.level * 2.8);
self.damage = 8 + self.level * 3.5;
self.bulletSpeed = 7 + self.level * 0.8;
}
break;
case 'sniper':
if (self.level === self.maxLevel) {
self.fireRate = Math.max(15, 100 - self.level * 12);
self.damage = 40 + self.level * 18; // High damage scaling
self.bulletSpeed = 25 + self.level * 2;
} else {
self.fireRate = Math.max(30, 100 - self.level * 8);
self.damage = 40 + self.level * 12;
self.bulletSpeed = 25 + self.level * 1;
}
break;
case 'splash':
if (self.level === self.maxLevel) {
self.fireRate = Math.max(12, 80 - self.level * 10);
self.damage = 18 + self.level * 12; // Strong splash scaling
self.bulletSpeed = 4 + self.level * 1.5;
} else {
self.fireRate = Math.max(25, 80 - self.level * 6);
self.damage = 18 + self.level * 8;
self.bulletSpeed = 4 + self.level * 0.8;
}
break;
case 'slow':
if (self.level === self.maxLevel) {
self.fireRate = Math.max(8, 45 - self.level * 5.5);
self.damage = 12 + self.level * 8; // Better damage for utility tower
self.bulletSpeed = 5 + self.level * 1.2;
} else {
self.fireRate = Math.max(20, 45 - self.level * 3.5);
self.damage = 12 + self.level * 5;
self.bulletSpeed = 5 + self.level * 0.7;
}
break;
case 'poison':
if (self.level === self.maxLevel) {
self.fireRate = Math.max(10, 60 - self.level * 7.5);
self.damage = 15 + self.level * 10; // Strong poison scaling
self.bulletSpeed = 5 + self.level * 1.3;
} else {
self.fireRate = Math.max(22, 60 - self.level * 5);
self.damage = 15 + self.level * 6;
self.bulletSpeed = 5 + self.level * 0.8;
}
break;
default:
if (self.level === self.maxLevel) {
self.fireRate = Math.max(10, 60 - self.level * 8);
self.damage = 10 + self.level * 12;
self.bulletSpeed = 5 + self.level * 1.5;
} else {
self.fireRate = Math.max(25, 60 - self.level * 5);
self.damage = 10 + self.level * 7;
self.bulletSpeed = 5 + self.level * 0.8;
}
}
if (self.level > 1) {
var levelDot = levelIndicators[self.level - 1].children[1];
tween(levelDot, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(levelDot, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold to upgrade!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
return false;
};
self.findTarget = function () {
var closestEnemy = null;
var closestScore = Infinity;
// Use spatial partitioning for better performance
var nearbyEnemies = spatialGrid.getNearbyEnemies(self.x, self.y, self.getRange());
for (var i = 0; i < nearbyEnemies.length; i++) {
var enemy = nearbyEnemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if enemy is in range
if (distance <= self.getRange()) {
// Apply nebula stealth effect - reduce targeting chance
var targetingChance = enemy.nebulaStealthed ? 0.3 : 1.0;
if (Math.random() > targetingChance) {
continue; // Skip this enemy due to stealth
}
// Handle flying enemies differently - they can be targeted regardless of path
if (enemy.isFlying) {
// For flying enemies, prioritize by distance to the goal
if (enemy.flyingTarget) {
var goalX = enemy.flyingTarget.x;
var goalY = enemy.flyingTarget.y;
var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY));
// Use distance to goal as score
if (distToGoal < closestScore) {
closestScore = distToGoal;
closestEnemy = enemy;
}
} else {
// If no flying target yet (shouldn't happen), prioritize by distance to tower
if (distance < closestScore) {
closestScore = distance;
closestEnemy = enemy;
}
}
} else {
// For ground enemies, use the original path-based targeting
// Get the cell for this enemy
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell && cell.pathId === pathId) {
// Use the cell's score (distance to exit) for prioritization
// Lower score means closer to exit
if (cell.score < closestScore) {
closestScore = cell.score;
closestEnemy = enemy;
}
}
}
}
}
if (!closestEnemy) {
self.targetEnemy = null;
}
return closestEnemy;
};
self.update = function () {
// Update ultimate ability
if (self.ultimate) {
self.ultimate.update();
}
self.targetEnemy = self.findTarget();
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var angle = Math.atan2(dy, dx);
gunContainer.rotation = angle;
var currentFireRate = self.fireRate;
if (self.heroBuff && self.heroBuff.duration > 0) {
currentFireRate = Math.floor(self.fireRate * self.heroBuff.fireRate);
}
if (LK.ticks - self.lastFired >= currentFireRate) {
self.fire();
self.lastFired = LK.ticks;
}
}
};
self.down = function (x, y, obj) {
// Check for ultimate ability activation (double tap or long press simulation)
if (self.ultimate && self.ultimate.canActivate()) {
// Simple activation - tap when ultimate is ready
if (self.ultimate.activate()) {
return; // Ultimate was activated, don't show menu
}
}
var existingMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
var hasOwnMenu = false;
var rangeCircle = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self) {
rangeCircle = game.children[i];
break;
}
}
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hasOwnMenu = true;
break;
}
}
if (hasOwnMenu) {
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hideUpgradeMenu(existingMenus[i]);
}
}
if (rangeCircle) {
game.removeChild(rangeCircle);
}
selectedTower = null;
grid.renderDebug();
return;
}
for (var i = 0; i < existingMenus.length; i++) {
existingMenus[i].destroy();
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = self;
var rangeIndicator = new Container();
rangeIndicator.isTowerRange = true;
rangeIndicator.tower = self;
game.addChild(rangeIndicator);
rangeIndicator.x = self.x;
rangeIndicator.y = self.y;
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.width = rangeGraphics.height = self.getRange() * 2;
rangeGraphics.alpha = 0.3;
var upgradeMenu = new UpgradeMenu(self);
game.addChild(upgradeMenu);
upgradeMenu.x = 2048 / 2;
tween(upgradeMenu, {
y: 2732 - 225
}, {
duration: 200,
easing: tween.backOut
});
grid.renderDebug();
};
self.isInRange = function (enemy) {
if (!enemy) {
return false;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance <= self.getRange();
};
self.fire = function () {
if (self.targetEnemy) {
var potentialDamage = 0;
for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) {
potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage;
}
if (self.targetEnemy.health > potentialDamage) {
var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
// Apply hero buff if present
var finalDamage = self.damage;
var finalFireRate = self.fireRate;
if (self.heroBuff && self.heroBuff.duration > 0) {
finalDamage = Math.floor(self.damage * self.heroBuff.damage);
finalFireRate = Math.floor(self.fireRate * self.heroBuff.fireRate);
self.heroBuff.duration--;
if (self.heroBuff.duration <= 0) {
self.heroBuff = null;
}
}
// Apply support bonus from linked towers
if (self.supportBonus) {
finalDamage = Math.floor(finalDamage * self.supportBonus);
finalFireRate = Math.floor(finalFireRate / self.supportBonus);
}
// Apply specialization effects
if (self.specialization) {
if (self.specialization.branchType === 'antiarmor' && self.targetEnemy.isImmune) {
finalDamage = Math.floor(finalDamage * 1.5);
}
}
var bullet = getBulletFromPool(bulletX, bulletY, self.targetEnemy, finalDamage, self.bulletSpeed);
// Set bullet type based on tower type
bullet.type = self.id;
// For slow tower, pass level for scaling slow effect
if (self.id === 'slow') {
bullet.sourceTowerLevel = self.level;
}
// Customize bullet appearance based on tower type
switch (self.id) {
case 'rapid':
bullet.children[0].tint = 0x00AAFF;
bullet.children[0].width = 20;
bullet.children[0].height = 20;
break;
case 'sniper':
bullet.children[0].tint = 0xFF5500;
bullet.children[0].width = 15;
bullet.children[0].height = 15;
break;
case 'splash':
bullet.children[0].tint = 0x33CC00;
bullet.children[0].width = 40;
bullet.children[0].height = 40;
break;
case 'slow':
bullet.children[0].tint = 0x9900FF;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'poison':
bullet.children[0].tint = 0x00FFAA;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
}
game.addChild(bullet);
bullets.push(bullet);
self.targetEnemy.bulletsTargetingThis.push(bullet);
// --- Sci-fi charge and firing effect ---
// Stop any ongoing recoil tweens before starting a new one
tween.stop(gunContainer, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
// Add energy charge effect to core
if (self.energyCore) {
tween.stop(self.energyCore, {
scaleX: true,
scaleY: true,
alpha: true
});
tween(self.energyCore, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1.5
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.energyCore, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
}
});
}
// Always use the original resting position for recoil, never accumulate offset
if (gunContainer._restX === undefined) {
gunContainer._restX = 0;
}
if (gunContainer._restY === undefined) {
gunContainer._restY = 0;
}
if (gunContainer._restScaleX === undefined) {
gunContainer._restScaleX = 1;
}
if (gunContainer._restScaleY === undefined) {
gunContainer._restScaleY = 1;
}
// Reset to resting position before animating (in case of interrupted tweens)
gunContainer.x = gunContainer._restX;
gunContainer.y = gunContainer._restY;
gunContainer.scaleX = gunContainer._restScaleX;
gunContainer.scaleY = gunContainer._restScaleY;
// Calculate recoil offset (recoil back along the gun's rotation)
var recoilDistance = 8;
var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance;
var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance;
// Animate recoil back from the resting position
tween(gunContainer, {
x: gunContainer._restX + recoilX,
y: gunContainer._restY + recoilY
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
// Animate return to original position/scale
tween(gunContainer, {
x: gunContainer._restX,
y: gunContainer._restY
}, {
duration: 90,
easing: tween.cubicIn
});
}
});
}
}
};
self.placeOnGrid = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cell.type = 1;
}
}
}
self.refreshCellsInRange();
};
self.destroy = function () {
if (self.isDestroyed) {
return;
}
self.isDestroyed = true;
// Stop all active tweens on this tower and its components
tween.stop(self);
tween.stop(gunContainer);
if (self.energyCore) {
tween.stop(self.energyCore);
}
// Clear cells in range references
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
var towerIndex = cell.towersInRange.indexOf(self);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
self.cellsInRange = [];
// Clear grid cells occupied by this tower
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(self.gridX + i, self.gridY + j);
if (cell) {
cell.type = 0;
}
}
}
// Remove from towers array
var towerIndex = towers.indexOf(self);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
// Remove from parent container
if (self.parent) {
self.parent.removeChild(self);
}
// Remove any associated range indicators
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange && game.children[i].tower === self) {
game.removeChild(game.children[i]);
}
}
// Close any upgrade menus for this tower
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu && child.tower === self;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
// Clear selected tower if it's this tower
if (selectedTower === self) {
selectedTower = null;
}
// Clean up ultimate ability
if (self.ultimate) {
self.ultimate.destroy();
self.ultimate = null;
}
// Nullify all object references
self.targetEnemy = null;
self.energyCore = null;
// Call parent destroy
Container.prototype.destroy.call(self);
};
return self;
});
var TowerPreview = Container.expand(function () {
var self = Container.call(this);
var towerRange = 3;
var rangeInPixels = towerRange * CELL_SIZE;
self.towerType = 'default';
self.hasEnoughGold = true;
var rangeIndicator = new Container();
self.addChild(rangeIndicator);
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.alpha = 0.3;
var previewGraphics = self.attachAsset('towerpreview', {
anchorX: 0.5,
anchorY: 0.5
});
previewGraphics.width = CELL_SIZE * 2;
previewGraphics.height = CELL_SIZE * 2;
self.canPlace = false;
self.gridX = 0;
self.gridY = 0;
self.blockedByEnemy = false;
self.update = function () {
var previousHasEnoughGold = self.hasEnoughGold;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
// Only update appearance if the affordability status has changed
if (previousHasEnoughGold !== self.hasEnoughGold) {
self.updateAppearance();
}
};
self.updateAppearance = function () {
// Use Tower class to get the source of truth for range
var tempTower = new Tower(self.towerType);
var previewRange = tempTower.getRange();
// Clean up tempTower to avoid memory leaks
if (tempTower && tempTower.destroy) {
tempTower.destroy();
}
// Set range indicator using unified range logic
rangeGraphics.width = rangeGraphics.height = previewRange * 2;
switch (self.towerType) {
case 'rapid':
previewGraphics.tint = 0x00AAFF;
break;
case 'sniper':
previewGraphics.tint = 0xFF5500;
break;
case 'splash':
previewGraphics.tint = 0x33CC00;
break;
case 'slow':
previewGraphics.tint = 0x9900FF;
break;
case 'poison':
previewGraphics.tint = 0x00FFAA;
break;
default:
previewGraphics.tint = 0xAAAAAA;
}
if (!self.canPlace || !self.hasEnoughGold) {
previewGraphics.tint = 0xFF0000;
}
};
self.updatePlacementStatus = function () {
var validGridPlacement = true;
if (self.gridY <= 4 || self.gridY + 1 >= grid.cells[0].length - 4) {
validGridPlacement = false;
} else {
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(self.gridX + i, self.gridY + j);
if (!cell || cell.type !== 0) {
validGridPlacement = false;
break;
}
}
if (!validGridPlacement) {
break;
}
}
}
self.blockedByEnemy = false;
if (validGridPlacement) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.currentCellY < 4) {
continue;
}
// Only check non-flying enemies, flying enemies can pass over towers
if (!enemy.isFlying) {
if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
if (enemy.currentTarget) {
var targetX = enemy.currentTarget.x;
var targetY = enemy.currentTarget.y;
if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
}
}
}
}
self.canPlace = validGridPlacement && !self.blockedByEnemy;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
self.updateAppearance();
};
self.checkPlacement = function () {
self.updatePlacementStatus();
};
self.snapToGrid = function (x, y) {
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
self.gridX = Math.floor(gridPosX / CELL_SIZE);
self.gridY = Math.floor(gridPosY / CELL_SIZE);
self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2;
self.checkPlacement();
};
return self;
});
var TowerSpecialization = Container.expand(function (tower, branchType) {
var self = Container.call(this);
self.tower = tower;
self.branchType = branchType || 'none';
self.linkedTowers = [];
self.isDestroyed = false;
// Add specialization visual indicator
var specIcon = self.attachAsset('specializationIcon', {
anchorX: 0.5,
anchorY: 0.5
});
specIcon.width = 30;
specIcon.height = 30;
specIcon.y = -self.tower.children[0].height / 2 - 20;
// Set specialization color based on branch
switch (self.branchType) {
case 'antiarmor':
specIcon.tint = 0xFF6600;
break;
case 'longrange':
specIcon.tint = 0x00AAFF;
break;
case 'nuclear':
specIcon.tint = 0xFF0000;
break;
case 'chainreaction':
specIcon.tint = 0xFFFF00;
break;
default:
specIcon.tint = 0xFFD700;
}
self.applySpecialization = function () {
switch (self.branchType) {
case 'antiarmor':
// Anti-armor: pierce shields, extra damage to immune enemies
self.tower.armorPiercing = true;
self.tower.damage = Math.floor(self.tower.damage * 1.5);
break;
case 'longrange':
// Long-range: significantly extended range
self.tower.rangeMultiplier = 2.0;
self.tower.refreshCellsInRange();
break;
case 'nuclear':
// Nuclear: huge splash damage, slower fire rate
self.tower.damage = Math.floor(self.tower.damage * 2.5);
self.tower.fireRate = Math.floor(self.tower.fireRate * 1.8);
self.tower.splashRadius = CELL_SIZE * 3;
break;
case 'chainreaction':
// Chain reaction: damage spreads between enemies
self.tower.chainReaction = true;
self.tower.chainRange = CELL_SIZE * 2;
self.tower.chainDamage = 0.7;
break;
}
};
self.findNearbyTowers = function () {
var nearby = [];
var searchRadius = CELL_SIZE * 4;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower !== self.tower && tower.specialization) {
var dx = tower.x - self.tower.x;
var dy = tower.y - self.tower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= searchRadius) {
nearby.push(tower);
}
}
}
return nearby;
};
self.createSupportLinks = function () {
var nearbyTowers = self.findNearbyTowers();
for (var i = 0; i < nearbyTowers.length; i++) {
var tower = nearbyTowers[i];
if (self.linkedTowers.indexOf(tower) === -1) {
self.linkedTowers.push(tower);
// Create visual link beam
var linkBeam = new Container();
linkBeam.x = self.tower.x;
linkBeam.y = self.tower.y;
var beamGraphics = linkBeam.attachAsset('linkBeam', {
anchorX: 0,
anchorY: 0.5
});
var dx = tower.x - self.tower.x;
var dy = tower.y - self.tower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
beamGraphics.width = distance;
beamGraphics.rotation = angle;
beamGraphics.alpha = 0.5;
beamGraphics.tint = 0x00FF88;
game.addChild(linkBeam);
self.tower.linkBeam = linkBeam;
}
}
};
self.applySupportBonus = function () {
// Linked towers get damage and fire rate bonus
var bonusMultiplier = 1 + self.linkedTowers.length * 0.15;
self.tower.supportBonus = bonusMultiplier;
};
self.update = function () {
if (self.branchType !== 'none') {
self.createSupportLinks();
self.applySupportBonus();
}
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
// Remove link beams
if (self.tower.linkBeam && self.tower.linkBeam.parent) {
self.tower.linkBeam.parent.removeChild(self.tower.linkBeam);
}
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
var TowerUltimate = Container.expand(function (tower, ultimateType) {
var self = Container.call(this);
self.tower = tower;
self.ultimateType = ultimateType || 'none';
self.cooldownTime = 1800; // 30 seconds at 60fps
self.currentCooldown = 0;
self.isDestroyed = false;
// Add ultimate indicator
var ultimateIcon = self.attachAsset('ultimateIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
ultimateIcon.y = -self.tower.children[0].height / 2 - 35;
ultimateIcon.alpha = 0.3; // Dim when on cooldown
// Set ultimate-specific properties
switch (self.ultimateType) {
case 'orbital':
self.cooldownTime = 2400; // 40 seconds
ultimateIcon.tint = 0xFF0000;
break;
case 'nanite':
self.cooldownTime = 2100; // 35 seconds
ultimateIcon.tint = 0x00FF88;
break;
case 'temporal':
self.cooldownTime = 1800; // 30 seconds
ultimateIcon.tint = 0x9966FF;
break;
default:
ultimateIcon.tint = 0xFFD700;
}
self.canActivate = function () {
return self.currentCooldown <= 0 && !self.isDestroyed;
};
self.activate = function () {
if (!self.canActivate()) return false;
self.currentCooldown = self.cooldownTime;
// Visual cooldown effect
ultimateIcon.alpha = 0.3;
tween(ultimateIcon, {
alpha: 1.0
}, {
duration: self.cooldownTime,
easing: tween.linear
});
switch (self.ultimateType) {
case 'orbital':
return self.orbitalStrike();
case 'nanite':
return self.naniteSwarm();
case 'temporal':
return self.timeDilation();
}
return false;
};
self.orbitalStrike = function () {
// Find target area with most enemies
var bestTarget = null;
var maxEnemies = 0;
var strikeRadius = CELL_SIZE * 3;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var nearbyCount = 0;
for (var j = 0; j < enemies.length; j++) {
var otherEnemy = enemies[j];
var dx = otherEnemy.x - enemy.x;
var dy = otherEnemy.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= strikeRadius) {
nearbyCount++;
}
}
if (nearbyCount > maxEnemies) {
maxEnemies = nearbyCount;
bestTarget = enemy;
}
}
if (bestTarget) {
// Create orbital strike effect
var strikeEffect = new Container();
strikeEffect.x = bestTarget.x;
strikeEffect.y = bestTarget.y;
var strikeGraphics = strikeEffect.attachAsset('orbitalStrike', {
anchorX: 0.5,
anchorY: 0.5
});
strikeGraphics.alpha = 0.0;
strikeGraphics.scaleX = 0.1;
strikeGraphics.scaleY = 0.1;
strikeGraphics.blendMode = 1; // Additive
game.addChild(strikeEffect);
// Warning phase
tween(strikeGraphics, {
alpha: 0.7,
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
// Strike phase - massive damage
var strikeDamage = self.tower.damage * 15;
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = enemy.x - bestTarget.x;
var dy = enemy.y - bestTarget.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= strikeRadius) {
enemy.health -= strikeDamage;
if (enemy.health <= 0) {
enemy.health = 0;
} else {
enemy.healthBar.width = enemy.health / enemy.maxHealth * 70;
}
// Visual damage effect
tween(enemy, {
tint: 0xFFFFFF,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemy, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 200,
easing: tween.easeIn
});
}
});
}
}
// Final explosion effect
tween(strikeGraphics, {
scaleX: 2.5,
scaleY: 2.5,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
if (strikeEffect.parent) {
strikeEffect.parent.removeChild(strikeEffect);
}
}
});
}
});
var notification = game.addChild(new Notification("🛰️ ORBITAL STRIKE INCOMING! 🛰️"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return true;
}
return false;
};
self.naniteSwarm = function () {
// Create nanite swarm that spreads poison and self-replicates
var swarmCenter = self.tower;
var swarmRadius = CELL_SIZE * 4;
var swarmDamage = self.tower.damage * 3;
// Create visual swarm effect
var swarmEffect = new Container();
swarmEffect.x = swarmCenter.x;
swarmEffect.y = swarmCenter.y;
var swarmGraphics = swarmEffect.attachAsset('naniteSwarm', {
anchorX: 0.5,
anchorY: 0.5
});
swarmGraphics.alpha = 0.8;
swarmGraphics.tint = 0x00FF88;
game.addChild(swarmEffect);
// Create multiple nanite particles
var particles = [];
for (var i = 0; i < 12; i++) {
var particle = swarmEffect.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
particle.width = 15;
particle.height = 15;
particle.tint = 0x44FFAA;
var angle = i / 12 * Math.PI * 2;
particle.x = Math.cos(angle) * 50;
particle.y = Math.sin(angle) * 50;
particles.push(particle);
}
// Animate swarm expansion
tween(swarmEffect, {
scaleX: 3.0,
scaleY: 3.0
}, {
duration: 2000,
easing: tween.easeOut
});
// Animate particles spiraling outward
for (var i = 0; i < particles.length; i++) {
var particle = particles[i];
tween(particle, {
x: particle.x * 3,
y: particle.y * 3,
rotation: Math.PI * 4
}, {
duration: 2000,
delay: i * 100,
easing: tween.easeOut
});
}
// Apply nanite effects over time
var swarmDuration = 180; // 3 seconds
var _swarmTick = function swarmTick() {
swarmDuration--;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - swarmCenter.x;
var dy = enemy.y - swarmCenter.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= swarmRadius) {
// Apply nanite damage
enemy.health -= swarmDamage;
if (enemy.health <= 0) {
enemy.health = 0;
} else {
enemy.healthBar.width = enemy.health / enemy.maxHealth * 70;
}
// Apply enhanced poison effect
enemy.poisoned = true;
enemy.poisonDamage = swarmDamage * 0.5;
enemy.poisonDuration = 300; // Extended duration
// Self-replication chance
if (Math.random() < 0.3 && swarmDuration > 60) {
// 30% chance to spread to nearby enemies
for (var j = 0; j < enemies.length; j++) {
var nearbyEnemy = enemies[j];
if (nearbyEnemy !== enemy) {
var nearbyDx = nearbyEnemy.x - enemy.x;
var nearbyDy = nearbyEnemy.y - enemy.y;
var nearbyDistance = Math.sqrt(nearbyDx * nearbyDx + nearbyDy * nearbyDy);
if (nearbyDistance <= CELL_SIZE * 2) {
nearbyEnemy.poisoned = true;
nearbyEnemy.poisonDamage = swarmDamage * 0.3;
nearbyEnemy.poisonDuration = 240;
break;
}
}
}
}
}
}
if (swarmDuration > 0) {
LK.setTimeout(_swarmTick, 100); // Continue every 100ms
} else {
// Clean up swarm effect
tween(swarmEffect, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
if (swarmEffect.parent) {
swarmEffect.parent.removeChild(swarmEffect);
}
}
});
}
};
LK.setTimeout(_swarmTick, 100); // Start swarm effects
var notification = game.addChild(new Notification("🤖 NANITE SWARM DEPLOYED! 🤖"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return true;
};
self.timeDilation = function () {
// Create temporal distortion field that slows time for enemies
var dilationCenter = self.tower;
var dilationRadius = CELL_SIZE * 5;
var dilationDuration = 600; // 10 seconds
// Create visual time dilation effect
var dilationEffect = new Container();
dilationEffect.x = dilationCenter.x;
dilationEffect.y = dilationCenter.y;
var dilationGraphics = dilationEffect.attachAsset('timeDilation', {
anchorX: 0.5,
anchorY: 0.5
});
dilationGraphics.alpha = 0.4;
dilationGraphics.tint = 0x9966FF;
dilationGraphics.blendMode = 1; // Additive
game.addChild(dilationEffect);
// Pulsing time distortion effect
tween(dilationGraphics, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(dilationGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.4
}, {
duration: 1000,
easing: tween.easeInOut
});
}
});
// Apply time dilation effects
var affectedEnemies = [];
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - dilationCenter.x;
var dy = enemy.y - dilationCenter.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= dilationRadius) {
// Store original speed
if (!enemy.originalDilationSpeed) {
enemy.originalDilationSpeed = enemy.speed;
}
// Extreme slow effect (90% speed reduction)
enemy.speed = enemy.originalDilationSpeed * 0.1;
enemy.temporallyDilated = true;
enemy.dilationDuration = dilationDuration;
affectedEnemies.push(enemy);
// Visual distortion effect on enemy
tween(enemy, {
tint: 0x9966FF,
alpha: 0.8
}, {
duration: 300,
easing: tween.easeOut
});
}
}
// During dilation, towers in the field get boosted fire rate
var boostedTowers = [];
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = tower.x - dilationCenter.x;
var dy = tower.y - dilationCenter.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= dilationRadius && tower !== self.tower) {
tower.originalDilationFireRate = tower.fireRate;
tower.fireRate = Math.floor(tower.fireRate * 0.3); // 70% faster firing
tower.temporallyBoosted = true;
boostedTowers.push(tower);
}
}
// Clean up after duration
LK.setTimeout(function () {
// Restore enemy speeds
for (var i = 0; i < affectedEnemies.length; i++) {
var enemy = affectedEnemies[i];
if (enemy.parent && !enemy.isDestroyed) {
enemy.speed = enemy.originalDilationSpeed;
enemy.temporallyDilated = false;
// Restore enemy appearance
tween(enemy, {
alpha: 1.0
}, {
duration: 300,
easing: tween.easeIn
});
}
}
// Restore tower fire rates
for (var i = 0; i < boostedTowers.length; i++) {
var tower = boostedTowers[i];
if (tower.parent && !tower.isDestroyed) {
tower.fireRate = tower.originalDilationFireRate;
tower.temporallyBoosted = false;
}
}
// Remove visual effect
tween(dilationEffect, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 1000,
easing: tween.easeIn,
onFinish: function onFinish() {
if (dilationEffect.parent) {
dilationEffect.parent.removeChild(dilationEffect);
}
}
});
}, dilationDuration * 16.67); // Convert frames to milliseconds
var notification = game.addChild(new Notification("⏰ TIME DILATION ACTIVATED! ⏰"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return true;
};
self.update = function () {
if (self.currentCooldown > 0) {
self.currentCooldown--;
}
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
var TutorialSystem = Container.expand(function () {
var self = Container.call(this);
self.currentStep = 0;
self.isActive = false;
self.tutorialSteps = [{
title: "Welcome to Tower Defense!",
text: "Build towers to stop enemies from reaching the bottom. Tap 'Next' to continue.",
highlight: null
}, {
title: "Gold & Economy",
text: "You earn gold by defeating enemies. Use gold to build and upgrade towers.",
highlight: "gold"
}, {
title: "Tower Types",
text: "Each tower has unique abilities. Basic towers are cheap, specialized towers cost more.",
highlight: "towers"
}, {
title: "Building Towers",
text: "Drag a tower from the bottom panel to place it. Towers need 2x2 space.",
highlight: "sourceTowers"
}, {
title: "Enemy Types",
text: "Different enemies have special abilities. Flying enemies can't be hit by some towers.",
highlight: "enemies"
}, {
title: "Waves & Progression",
text: "Enemies come in waves. Survive all waves to win! Tap 'Start Game' when ready.",
highlight: "waveIndicator"
}];
var tutorialContainer = new Container();
self.addChild(tutorialContainer);
var overlay = tutorialContainer.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 1800;
overlay.height = 400;
overlay.tint = 0x000066;
overlay.alpha = 0.95;
var titleText = new Text2("", {
size: 70,
fill: 0xFFFFFF,
weight: 800
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -80;
tutorialContainer.addChild(titleText);
var bodyText = new Text2("", {
size: 50,
fill: 0xCCCCCC,
weight: 400
});
bodyText.anchor.set(0.5, 0.5);
bodyText.y = 20;
tutorialContainer.addChild(bodyText);
var nextButton = new Container();
var nextBg = nextButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
nextBg.width = 200;
nextBg.height = 80;
nextBg.tint = 0x00AA00;
var nextText = new Text2("Next", {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
nextText.anchor.set(0.5, 0.5);
nextButton.addChild(nextText);
nextButton.y = 120;
nextButton.x = -300;
tutorialContainer.addChild(nextButton);
var skipButton = new Container();
var skipBg = skipButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
skipBg.width = 200;
skipBg.height = 80;
skipBg.tint = 0xAA0000;
var skipText = new Text2("Skip", {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
skipText.anchor.set(0.5, 0.5);
skipButton.addChild(skipText);
skipButton.y = 120;
skipButton.x = 300;
tutorialContainer.addChild(skipButton);
self.showStep = function (stepIndex) {
if (stepIndex >= self.tutorialSteps.length) {
self.endTutorial();
return;
}
var step = self.tutorialSteps[stepIndex];
titleText.setText(step.title);
bodyText.setText(step.text);
if (stepIndex === self.tutorialSteps.length - 1) {
nextText.setText("Start!");
}
};
self.startTutorial = function () {
self.isActive = true;
self.visible = true;
self.currentStep = 0;
self.showStep(0);
// Position tutorial in center of screen
tutorialContainer.x = 2048 / 2;
tutorialContainer.y = 1400;
};
self.nextStep = function () {
self.currentStep++;
self.showStep(self.currentStep);
};
self.endTutorial = function () {
self.isActive = false;
self.visible = false;
// Auto-start the game after tutorial
if (waveIndicator && !waveIndicator.gameStarted) {
waveIndicator.gameStarted = true;
currentWave = 0;
waveTimer = nextWaveTime;
}
};
nextButton.down = function () {
self.nextStep();
};
skipButton.down = function () {
self.endTutorial();
};
self.visible = false;
return self;
});
var UpgradeMenu = Container.expand(function (tower) {
var self = Container.call(this);
self.tower = tower;
self.y = 2732 + 225;
var menuBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
menuBackground.width = 2048;
menuBackground.height = 500;
menuBackground.tint = 0x444444;
menuBackground.alpha = 0.9;
var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
towerTypeText.anchor.set(0, 0);
towerTypeText.x = -840;
towerTypeText.y = -160;
self.addChild(towerTypeText);
var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', {
size: 70,
fill: 0xFFFFFF,
weight: 400
});
statsText.anchor.set(0, 0.5);
statsText.x = -840;
statsText.y = 50;
self.addChild(statsText);
var buttonsContainer = new Container();
buttonsContainer.x = 500;
self.addChild(buttonsContainer);
var upgradeButton = new Container();
buttonsContainer.addChild(upgradeButton);
var buttonBackground = upgradeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 500;
buttonBackground.height = 150;
var isMaxLevel = self.tower.level >= self.tower.maxLevel;
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
var upgradeCost;
if (isMaxLevel) {
upgradeCost = 0;
} else if (self.tower.level === self.tower.maxLevel - 1) {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
} else {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
}
buttonBackground.tint = isMaxLevel ? 0x888888 : gold >= upgradeCost ? 0x00AA00 : 0x888888;
var buttonText = new Text2(isMaxLevel ? 'Max Level' : 'Upgrade: ' + upgradeCost + ' gold', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(buttonText);
var sellButton = new Container();
buttonsContainer.addChild(sellButton);
var sellButtonBackground = sellButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
sellButtonBackground.width = 500;
sellButtonBackground.height = 150;
sellButtonBackground.tint = 0xCC0000;
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
var sellValue = getTowerSellValue(totalInvestment);
var sellButtonText = new Text2('Sell: +' + sellValue + ' gold', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
upgradeButton.y = -85;
sellButton.y = 85;
var closeButton = new Container();
self.addChild(closeButton);
var closeBackground = closeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
closeBackground.width = 90;
closeBackground.height = 90;
closeBackground.tint = 0xAA0000;
var closeText = new Text2('X', {
size: 68,
fill: 0xFFFFFF,
weight: 800
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
closeButton.x = menuBackground.width / 2 - 57;
closeButton.y = -menuBackground.height / 2 + 57;
upgradeButton.down = function (x, y, obj) {
if (self.tower.level >= self.tower.maxLevel) {
var notification = game.addChild(new Notification("Tower is already at max level!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return;
}
if (self.tower.upgrade()) {
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
if (self.tower.level >= self.tower.maxLevel) {
upgradeCost = 0;
} else if (self.tower.level === self.tower.maxLevel - 1) {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
} else {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
}
statsText.setText('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s');
buttonText.setText('Upgrade: ' + upgradeCost + ' gold');
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
var sellValue = Math.floor(totalInvestment * 0.6);
sellButtonText.setText('Sell: +' + sellValue + ' gold');
if (self.tower.level >= self.tower.maxLevel) {
buttonBackground.tint = 0x888888;
buttonText.setText('Max Level');
}
var rangeCircle = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
rangeCircle = game.children[i];
break;
}
}
if (rangeCircle) {
var rangeGraphics = rangeCircle.children[0];
rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2;
} else {
var newRangeIndicator = new Container();
newRangeIndicator.isTowerRange = true;
newRangeIndicator.tower = self.tower;
game.addChildAt(newRangeIndicator, 0);
newRangeIndicator.x = self.tower.x;
newRangeIndicator.y = self.tower.y;
var rangeGraphics = newRangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.width = rangeGraphics.height = self.tower.getRange() * 2;
rangeGraphics.alpha = 0.3;
}
tween(self, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeIn
});
}
});
}
};
sellButton.down = function (x, y, obj) {
var totalInvestment = self.tower.getTotalValue ? self.tower.getTotalValue() : 0;
var sellValue = getTowerSellValue(totalInvestment);
setGold(gold + sellValue);
var notification = game.addChild(new Notification("Tower sold for " + sellValue + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
var gridX = self.tower.gridX;
var gridY = self.tower.gridY;
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cell.type = 0;
var towerIndex = cell.towersInRange.indexOf(self.tower);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
}
}
if (selectedTower === self.tower) {
selectedTower = null;
}
var towerIndex = towers.indexOf(self.tower);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
towerLayer.removeChild(self.tower);
grid.pathFind();
grid.renderDebug();
self.destroy();
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
game.removeChild(game.children[i]);
break;
}
}
};
closeButton.down = function (x, y, obj) {
hideUpgradeMenu(self);
selectedTower = null;
grid.renderDebug();
};
self.update = function () {
if (self.tower.level >= self.tower.maxLevel) {
if (buttonText.text !== 'Max Level') {
buttonText.setText('Max Level');
buttonBackground.tint = 0x888888;
}
return;
}
// Exponential upgrade cost: base cost * (2 ^ (level-1)), scaled by tower base cost
var baseUpgradeCost = getTowerCost(self.tower.id);
var currentUpgradeCost;
if (self.tower.level >= self.tower.maxLevel) {
currentUpgradeCost = 0;
} else if (self.tower.level === self.tower.maxLevel - 1) {
currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1) * 3.5 / 2);
} else {
currentUpgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.tower.level - 1));
}
var canAfford = gold >= currentUpgradeCost;
buttonBackground.tint = canAfford ? 0x00AA00 : 0x888888;
var newText = 'Upgrade: ' + currentUpgradeCost + ' gold';
if (buttonText.text !== newText) {
buttonText.setText(newText);
}
};
return self;
});
var WaveIndicator = Container.expand(function () {
var self = Container.call(this);
self.gameStarted = false;
self.waveMarkers = [];
self.waveTypes = [];
self.enemyCounts = [];
self.indicatorWidth = 0;
self.lastBossType = null; // Track the last boss type to avoid repeating
var blockWidth = 400;
var totalBlocksWidth = blockWidth * totalWaves;
var startMarker = new Container();
var startBlock = startMarker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
startBlock.width = blockWidth - 10;
startBlock.height = 70 * 2;
startBlock.tint = 0x00AA00;
// Add shadow for start text
var startTextShadow = new Text2("Start Game", {
size: 50,
fill: 0x000000,
weight: 800
});
startTextShadow.anchor.set(0.5, 0.5);
startTextShadow.x = 4;
startTextShadow.y = 4;
startMarker.addChild(startTextShadow);
var startText = new Text2("Start Game", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
startText.anchor.set(0.5, 0.5);
startMarker.addChild(startText);
startMarker.x = -self.indicatorWidth;
self.addChild(startMarker);
self.waveMarkers.push(startMarker);
startMarker.down = function () {
if (!self.gameStarted) {
self.gameStarted = true;
currentWave = 0;
waveTimer = nextWaveTime;
startBlock.tint = 0x00FF00;
startText.setText("Started!");
startTextShadow.setText("Started!");
// Make sure shadow position remains correct after text change
startTextShadow.x = 4;
startTextShadow.y = 4;
var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
};
for (var i = 0; i < totalWaves; i++) {
var marker = new Container();
var block = marker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
block.width = blockWidth - 10;
block.height = 70 * 2;
// --- Begin new unified wave logic ---
var waveType = "normal";
var enemyType = "normal";
var enemyCount = 10;
var isBossWave = (i + 1) % 10 === 0;
// Ensure all types appear in early waves
if (i === 0) {
block.tint = 0xAAAAAA;
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
} else if (i === 1) {
block.tint = 0x00AAFF;
waveType = "Fast";
enemyType = "fast";
enemyCount = 10;
} else if (i === 2) {
block.tint = 0xAA0000;
waveType = "Immune";
enemyType = "immune";
enemyCount = 10;
} else if (i === 3) {
block.tint = 0xFFFF00;
waveType = "Flying";
enemyType = "flying";
enemyCount = 10;
} else if (i === 4) {
block.tint = 0xFF00FF;
waveType = "Swarm";
enemyType = "swarm";
enemyCount = 30;
} else if (isBossWave) {
// Boss waves: cycle through all boss types, last boss is always flying
var bossTypes = ['normal', 'fast', 'immune', 'flying'];
var bossTypeIndex = Math.floor((i + 1) / 10) - 1;
if (i === totalWaves - 1) {
// Last boss is always flying
enemyType = 'flying';
waveType = "Boss Flying";
block.tint = 0xFFFF00;
} else {
enemyType = bossTypes[bossTypeIndex % bossTypes.length];
switch (enemyType) {
case 'normal':
block.tint = 0xAAAAAA;
waveType = "Boss Normal";
break;
case 'fast':
block.tint = 0x00AAFF;
waveType = "Boss Fast";
break;
case 'immune':
block.tint = 0xAA0000;
waveType = "Boss Immune";
break;
case 'flying':
block.tint = 0xFFFF00;
waveType = "Boss Flying";
break;
}
}
enemyCount = 1;
// Make the wave indicator for boss waves stand out
// Set boss wave color to the color of the wave type
switch (enemyType) {
case 'normal':
block.tint = 0xAAAAAA;
break;
case 'fast':
block.tint = 0x00AAFF;
break;
case 'immune':
block.tint = 0xAA0000;
break;
case 'flying':
block.tint = 0xFFFF00;
break;
default:
block.tint = 0xFF0000;
break;
}
} else if ((i + 1) % 5 === 0) {
// Every 5th non-boss wave is fast
block.tint = 0x00AAFF;
waveType = "Fast";
enemyType = "fast";
enemyCount = 10;
} else if ((i + 1) % 4 === 0) {
// Every 4th non-boss wave is immune
block.tint = 0xAA0000;
waveType = "Immune";
enemyType = "immune";
enemyCount = 10;
} else if ((i + 1) % 7 === 0) {
// Every 7th non-boss wave is flying
block.tint = 0xFFFF00;
waveType = "Flying";
enemyType = "flying";
enemyCount = 10;
} else if ((i + 1) % 3 === 0) {
// Every 3rd non-boss wave is swarm
block.tint = 0xFF00FF;
waveType = "Swarm";
enemyType = "swarm";
enemyCount = 30;
} else {
block.tint = 0xAAAAAA;
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
}
// --- End new unified wave logic ---
// Mark boss waves with a special visual indicator
if (isBossWave && enemyType !== 'swarm') {
// Add a crown or some indicator to the wave marker for boss waves
var bossIndicator = marker.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
bossIndicator.width = 30;
bossIndicator.height = 30;
bossIndicator.tint = 0xFFD700; // Gold color
bossIndicator.y = -block.height / 2 - 15;
// Change the wave type text to indicate boss
waveType = "BOSS";
}
// Store the wave type and enemy count
self.waveTypes[i] = enemyType;
self.enemyCounts[i] = enemyCount;
// Add shadow for wave type - 30% smaller than before
var waveTypeShadow = new Text2(waveType, {
size: 56,
fill: 0x000000,
weight: 800
});
waveTypeShadow.anchor.set(0.5, 0.5);
waveTypeShadow.x = 4;
waveTypeShadow.y = 4;
marker.addChild(waveTypeShadow);
// Add wave type text - 30% smaller than before
var waveTypeText = new Text2(waveType, {
size: 56,
fill: 0xFFFFFF,
weight: 800
});
waveTypeText.anchor.set(0.5, 0.5);
waveTypeText.y = 0;
marker.addChild(waveTypeText);
// Add shadow for wave number - 20% larger than before
var waveNumShadow = new Text2((i + 1).toString(), {
size: 48,
fill: 0x000000,
weight: 800
});
waveNumShadow.anchor.set(1.0, 1.0);
waveNumShadow.x = blockWidth / 2 - 16 + 5;
waveNumShadow.y = block.height / 2 - 12 + 5;
marker.addChild(waveNumShadow);
// Main wave number text - 20% larger than before
var waveNum = new Text2((i + 1).toString(), {
size: 48,
fill: 0xFFFFFF,
weight: 800
});
waveNum.anchor.set(1.0, 1.0);
waveNum.x = blockWidth / 2 - 16;
waveNum.y = block.height / 2 - 12;
marker.addChild(waveNum);
marker.x = -self.indicatorWidth + (i + 1) * blockWidth;
self.addChild(marker);
self.waveMarkers.push(marker);
}
// Get wave type for a specific wave number
self.getWaveType = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return "normal";
}
// If this is a boss wave (waveNumber % 10 === 0), and the type is the same as lastBossType
// then we should return a different boss type
var waveType = self.waveTypes[waveNumber - 1];
return waveType;
};
// Get enemy count for a specific wave number
self.getEnemyCount = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return 10;
}
return self.enemyCounts[waveNumber - 1];
};
// Get display name for a wave type
self.getWaveTypeName = function (waveNumber) {
var type = self.getWaveType(waveNumber);
var typeName = type.charAt(0).toUpperCase() + type.slice(1);
// Add boss prefix for boss waves (every 10th wave)
if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') {
typeName = "BOSS";
}
return typeName;
};
self.positionIndicator = new Container();
var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.width = blockWidth - 10;
indicator.height = 16;
indicator.tint = 0xffad0e;
indicator.y = -65;
var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator2.width = blockWidth - 10;
indicator2.height = 16;
indicator2.tint = 0xffad0e;
indicator2.y = 65;
var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
leftWall.width = 16;
leftWall.height = 146;
leftWall.tint = 0xffad0e;
leftWall.x = -(blockWidth - 16) / 2;
var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
rightWall.width = 16;
rightWall.height = 146;
rightWall.tint = 0xffad0e;
rightWall.x = (blockWidth - 16) / 2;
self.addChild(self.positionIndicator);
self.update = function () {
var progress = waveTimer / nextWaveTime;
var moveAmount = (progress + currentWave) * blockWidth;
for (var i = 0; i < self.waveMarkers.length; i++) {
var marker = self.waveMarkers[i];
marker.x = -moveAmount + i * blockWidth;
}
self.positionIndicator.x = 0;
for (var i = 0; i < totalWaves + 1; i++) {
var marker = self.waveMarkers[i];
if (i === 0) {
continue;
}
var block = marker.children[0];
if (i - 1 < currentWave) {
block.alpha = .5;
}
}
self.handleWaveProgression = function () {
if (!self.gameStarted) {
return;
}
if (currentWave < totalWaves) {
waveTimer++;
if (waveTimer >= nextWaveTime) {
waveTimer = 0;
currentWave++;
waveInProgress = true;
waveSpawned = false;
if (currentWave != 1) {
var waveType = self.getWaveTypeName(currentWave);
var enemyCount = self.getEnemyCount(currentWave);
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
}
}
};
self.handleWaveProgression();
};
return self;
});
var Wormhole = Container.expand(function (x, y, targetX, targetY) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.targetX = targetX;
self.targetY = targetY;
self.radius = 90;
self.isDestroyed = false;
self.cooldown = 0;
var wormholeGraphics = self.attachAsset('wormhole', {
anchorX: 0.5,
anchorY: 0.5
});
wormholeGraphics.alpha = 0.7;
wormholeGraphics.blendMode = 1; // Additive blending
// Spinning animation
tween(wormholeGraphics, {
rotation: Math.PI * 2
}, {
duration: 3000,
easing: tween.linear,
onFinish: function onFinish() {
if (!self.isDestroyed) {
wormholeGraphics.rotation = 0;
}
}
});
// Create target wormhole visual
self.targetWormhole = new Container();
self.targetWormhole.x = targetX;
self.targetWormhole.y = targetY;
var targetGraphics = self.targetWormhole.attachAsset('wormhole', {
anchorX: 0.5,
anchorY: 0.5
});
targetGraphics.alpha = 0.5;
targetGraphics.tint = 0xFF1493; // Different color for exit
targetGraphics.blendMode = 1;
game.addChild(self.targetWormhole);
// Sync rotation with main wormhole
tween(targetGraphics, {
rotation: -Math.PI * 2
}, {
duration: 3000,
easing: tween.linear,
onFinish: function onFinish() {
if (!self.isDestroyed) {
targetGraphics.rotation = 0;
}
}
});
self.update = function () {
if (self.cooldown > 0) {
self.cooldown--;
return;
}
// Check for enemies within range
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.isFlying) continue; // Flying enemies ignore wormholes
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.radius) {
// Teleport enemy
self.teleportEnemy(enemy);
self.cooldown = 180; // 3 second cooldown
break;
}
}
};
self.teleportEnemy = function (enemy) {
// Create teleport effect at source
var sourceEffect = new Container();
sourceEffect.x = self.x;
sourceEffect.y = self.y;
var sourceGraphics = sourceEffect.attachAsset('teleportEffect', {
anchorX: 0.5,
anchorY: 0.5
});
sourceGraphics.tint = 0x9400D3;
sourceGraphics.alpha = 0.8;
game.addChild(sourceEffect);
// Create teleport effect at destination
var destEffect = new Container();
destEffect.x = self.targetX;
destEffect.y = self.targetY;
var destGraphics = destEffect.attachAsset('teleportEffect', {
anchorX: 0.5,
anchorY: 0.5
});
destGraphics.tint = 0xFF1493;
destGraphics.alpha = 0.8;
game.addChild(destEffect);
// Animate effects
tween(sourceEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (sourceEffect.parent) {
sourceEffect.parent.removeChild(sourceEffect);
}
}
});
tween(destEffect, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
if (destEffect.parent) {
destEffect.parent.removeChild(destEffect);
}
}
});
// Teleport enemy
enemy.x = self.targetX;
enemy.y = self.targetY;
enemy.currentCellX = Math.floor((self.targetX - grid.x) / CELL_SIZE);
enemy.currentCellY = Math.floor((self.targetY - grid.y) / CELL_SIZE);
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentTarget = null; // Reset pathfinding
// Flash the wormhole
tween(wormholeGraphics, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(wormholeGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.7
}, {
duration: 300,
easing: tween.easeIn
});
}
});
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
// Remove target wormhole
if (self.targetWormhole && self.targetWormhole.parent) {
self.targetWormhole.parent.removeChild(self.targetWormhole);
}
var wormholeIndex = wormholes.indexOf(self);
if (wormholeIndex !== -1) {
wormholes.splice(wormholeIndex, 1);
}
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000011
});
/****
* Game Code
****/
var isHidingUpgradeMenu = false;
function hideUpgradeMenu(menu) {
if (isHidingUpgradeMenu) {
return;
}
isHidingUpgradeMenu = true;
tween(menu, {
y: 2732 + 225
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
menu.destroy();
isHidingUpgradeMenu = false;
}
});
}
var CELL_SIZE = 76;
var pathId = 1;
var maxScore = 0;
var enemies = [];
var towers = [];
var bullets = [];
// Object pooling for bullets
var bulletPool = [];
var maxBulletPoolSize = 100;
function getBulletFromPool(startX, startY, targetEnemy, damage, speed) {
var bullet;
if (bulletPool.length > 0) {
bullet = bulletPool.pop();
// Reset bullet properties
bullet.targetEnemy = targetEnemy;
bullet.damage = damage || 10;
bullet.speed = speed || 5;
bullet.x = startX;
bullet.y = startY;
bullet.isDestroyed = false;
bullet.visible = true;
bullet.alpha = 1;
if (bullet.children[0]) {
bullet.children[0].alpha = 1;
bullet.children[0].scaleX = 1;
bullet.children[0].scaleY = 1;
}
} else {
bullet = new Bullet(startX, startY, targetEnemy, damage, speed);
}
return bullet;
}
function returnBulletToPool(bullet) {
if (bulletPool.length < maxBulletPoolSize && bullet.parent) {
// Clean up target enemy reference properly
if (bullet.targetEnemy && bullet.targetEnemy.bulletsTargetingThis) {
var bulletIndex = bullet.targetEnemy.bulletsTargetingThis.indexOf(bullet);
if (bulletIndex !== -1) {
bullet.targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
// Stop all tweens before pooling
tween.stop(bullet);
if (bullet.children && bullet.children[0]) {
tween.stop(bullet.children[0]);
}
bullet.parent.removeChild(bullet);
bullet.targetEnemy = null;
bullet.visible = false;
bulletPool.push(bullet);
} else {
bullet.destroy();
}
}
var defenses = [];
var selectedTower = null;
var gold = 60; // Reduced starting gold to make early choices more meaningful
var lives = 20;
var score = 0;
var currentWave = 0;
var totalWaves = 50;
var waveTimer = 0;
var waveInProgress = false;
var waveSpawned = false;
var nextWaveTime = 12000 / 2;
var sourceTower = null;
// Hero system variables
var heroes = [];
var maxHeroes = 3;
var heroDeploymentMode = false;
var heroPreview = null;
var selectedHeroType = 'tank';
// Station customization variables
var stationCustomizer = null;
var researchLabs = [];
var shieldGenerators = [];
var teleporterNodes = [];
// Environmental hazards variables
var asteroids = [];
var nebulaClouds = [];
var solarFlares = [];
var wormholes = [];
var hazardTimer = 0;
var nextHazardTime = 1800; // 30 seconds at 60fps
// Tower specialization variables
var specializationMenus = [];
var towerLinks = [];
// Ultimate ability variables
var activeUltimates = [];
var ultimateEffects = [];
// Load station data from storage
var stationData = storage.stationData || {
modules: [],
researchPoints: 0,
completedTechs: []
};
function getModuleCost(moduleType, level) {
var baseCosts = {
research: 50,
shield: 75,
teleporter: 100
};
var baseCost = baseCosts[moduleType] || 50;
return Math.floor(baseCost * Math.pow(1.5, level - 1));
}
function getTechnology(techId) {
var technologies = {
'tower_damage': {
id: 'tower_damage',
name: 'Enhanced Weapons',
cost: 100,
onComplete: function onComplete() {
// Increase all tower damage by 20%
for (var i = 0; i < towers.length; i++) {
towers[i].damage = Math.floor(towers[i].damage * 1.2);
}
}
},
'tower_range': {
id: 'tower_range',
name: 'Extended Range',
cost: 150,
onComplete: function onComplete() {
// Increase all tower ranges
for (var i = 0; i < towers.length; i++) {
towers[i].refreshCellsInRange();
}
}
},
'shield_efficiency': {
id: 'shield_efficiency',
name: 'Shield Efficiency',
cost: 200,
onComplete: function onComplete() {
// Reduce shield energy costs
for (var i = 0; i < shieldGenerators.length; i++) {
shieldGenerators[i].maxShieldEnergy *= 1.5;
shieldGenerators[i].shieldEnergy = shieldGenerators[i].maxShieldEnergy;
}
}
}
};
return technologies[techId];
}
// Spatial partitioning for collision optimization
var spatialGrid = {
cellSize: CELL_SIZE * 2,
cells: {},
getKey: function getKey(x, y) {
var gridX = Math.floor(x / this.cellSize);
var gridY = Math.floor(y / this.cellSize);
return gridX + ',' + gridY;
},
addEnemy: function addEnemy(enemy) {
var key = this.getKey(enemy.x, enemy.y);
if (!this.cells[key]) this.cells[key] = [];
this.cells[key].push(enemy);
},
removeEnemy: function removeEnemy(enemy) {
var key = this.getKey(enemy.x, enemy.y);
if (this.cells[key]) {
var index = this.cells[key].indexOf(enemy);
if (index !== -1) {
this.cells[key].splice(index, 1);
}
}
},
getNearbyEnemies: function getNearbyEnemies(x, y, range) {
var nearby = [];
var checkRadius = Math.ceil(range / this.cellSize);
var centerX = Math.floor(x / this.cellSize);
var centerY = Math.floor(y / this.cellSize);
for (var dx = -checkRadius; dx <= checkRadius; dx++) {
for (var dy = -checkRadius; dy <= checkRadius; dy++) {
var key = centerX + dx + ',' + (centerY + dy);
if (this.cells[key]) {
nearby = nearby.concat(this.cells[key]);
}
}
}
return nearby;
},
clear: function clear() {
this.cells = {};
}
};
var enemiesToSpawn = 10; // Default number of enemies per wave
// Visual effects limiting
var activeEffects = [];
var maxActiveEffects = 20;
var effectSkipCounter = 0;
function canCreateEffect() {
// Count active effects and remove destroyed ones
activeEffects = activeEffects.filter(function (effect) {
return effect.parent && !effect.isDestroyed;
});
// Limit based on performance
if (activeEffects.length >= maxActiveEffects) {
effectSkipCounter++;
// Skip every other effect when at limit
return effectSkipCounter % 2 === 0;
}
return true;
}
function registerEffect(effect) {
if (activeEffects.length < maxActiveEffects) {
activeEffects.push(effect);
}
}
var goldText = new Text2('Gold: ' + gold, {
size: 60,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
var livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: 0x00FF00,
weight: 800
});
livesText.anchor.set(0.5, 0.5);
var scoreText = new Text2('Score: ' + score, {
size: 60,
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
var topMargin = 50;
var centerX = 2048 / 2;
var spacing = 400;
LK.gui.top.addChild(goldText);
LK.gui.top.addChild(livesText);
LK.gui.top.addChild(scoreText);
livesText.x = 0;
livesText.y = topMargin;
goldText.x = -spacing;
goldText.y = topMargin;
scoreText.x = spacing;
scoreText.y = topMargin;
function updateUI() {
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
}
function setGold(value) {
gold = value;
updateUI();
}
// Performance monitoring and adaptive logic
var performanceMonitor = {
frameTime: 16.67,
// Target 60 FPS
lastTime: 0,
avgFrameTime: 16.67,
frameCount: 0,
performanceLevel: 1.0,
// 1.0 = full performance, 0.5 = half performance
update: function update() {
var currentTime = Date.now();
if (this.lastTime > 0) {
var deltaTime = currentTime - this.lastTime;
this.avgFrameTime = this.avgFrameTime * 0.9 + deltaTime * 0.1;
// Adjust performance level based on frame time
if (this.avgFrameTime > 25) {
// Below 40 FPS
this.performanceLevel = Math.max(0.3, this.performanceLevel - 0.1);
} else if (this.avgFrameTime < 18) {
// Above 55 FPS
this.performanceLevel = Math.min(1.0, this.performanceLevel + 0.05);
}
}
this.lastTime = currentTime;
this.frameCount++;
},
shouldSkipUpdate: function shouldSkipUpdate() {
return Math.random() > this.performanceLevel;
}
};
// Memory monitoring system
var memoryMonitor = {
lastEnemyCount: 0,
lastBulletCount: 0,
lastTowerCount: 0,
lastEffectCount: 0,
update: function update() {
// Track object counts for memory leak detection
this.lastEnemyCount = enemies.length;
this.lastBulletCount = bullets.length;
this.lastTowerCount = towers.length;
this.lastEffectCount = activeEffects.length;
// Log warnings if counts are unexpectedly high
if (this.lastEnemyCount > 100) {
console.warn("High enemy count detected:", this.lastEnemyCount);
}
if (this.lastBulletCount > 200) {
console.warn("High bullet count detected:", this.lastBulletCount);
}
if (this.lastEffectCount > 50) {
console.warn("High effect count detected:", this.lastEffectCount);
}
},
forceCleanup: function forceCleanup() {
// Emergency cleanup when memory usage is too high
console.log("Performing emergency cleanup...");
// Clean up destroyed effects
activeEffects = activeEffects.filter(function (effect) {
return effect.parent && !effect.isDestroyed;
});
// Clean up orphaned bullets
for (var i = bullets.length - 1; i >= 0; i--) {
if (!bullets[i].parent || bullets[i].isDestroyed) {
bullets.splice(i, 1);
}
}
// Clean up bullet pool
bulletPool = bulletPool.filter(function (bullet) {
return !bullet.isDestroyed;
});
console.log("Cleanup complete. Objects remaining:", {
enemies: enemies.length,
bullets: bullets.length,
towers: towers.length,
effects: activeEffects.length,
pooledBullets: bulletPool.length
});
}
};
var debugLayer = new Container();
var towerLayer = new Container();
// Create three separate layers for enemy hierarchy
var enemyLayerBottom = new Container(); // For normal enemies
var enemyLayerMiddle = new Container(); // For shadows
var enemyLayerTop = new Container(); // For flying enemies
var enemyLayer = new Container(); // Main container to hold all enemy layers
// Add layers in correct order (bottom first, then middle for shadows, then top)
enemyLayer.addChild(enemyLayerBottom);
enemyLayer.addChild(enemyLayerMiddle);
enemyLayer.addChild(enemyLayerTop);
var grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
game.addChild(debugLayer);
game.addChild(towerLayer);
game.addChild(enemyLayer);
var offset = 0;
var towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
var isDragging = false;
function wouldBlockPath(gridX, gridY) {
var cells = [];
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cells.push({
cell: cell,
originalType: cell.type
});
cell.type = 1;
}
}
}
var blocked = grid.pathFind();
for (var i = 0; i < cells.length; i++) {
cells[i].cell.type = cells[i].originalType;
}
grid.pathFind();
grid.renderDebug();
return blocked;
}
function getTowerCost(towerType) {
var cost = 8; // Increased base cost from 5
switch (towerType) {
case 'rapid':
cost = 12; // Reduced from 15 - cheaper early game tower
break;
case 'sniper':
cost = 20; // Reduced from 25 for better accessibility
break;
case 'splash':
cost = 28; // Reduced from 35
break;
case 'slow':
cost = 32; // Reduced from 45 - utility tower should be accessible
break;
case 'poison':
cost = 38; // Reduced from 55 - was too expensive
break;
}
return cost;
}
function getTowerSellValue(totalValue) {
return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue;
}
function placeTower(gridX, gridY, towerType) {
var towerCost = getTowerCost(towerType);
if (gold >= towerCost) {
var tower = new Tower(towerType || 'default');
tower.placeOnGrid(gridX, gridY);
towerLayer.addChild(tower);
towers.push(tower);
setGold(gold - towerCost);
grid.pathFind();
grid.renderDebug();
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
game.down = function (x, y, obj) {
// Handle hero deployment
if (heroDeploymentMode) {
// Check if tap is in valid deployment area (not on towers or paths)
var deploymentValid = true;
var gridX = Math.floor((x - grid.x) / CELL_SIZE);
var gridY = Math.floor((y - grid.y) / CELL_SIZE);
// Check if position is within grid bounds and not on blocked areas
if (gridX < 2 || gridX >= 22 || gridY < 6 || gridY >= 25) {
deploymentValid = false;
} else {
var cell = grid.getCell(gridX, gridY);
if (!cell || cell.type !== 0) {
deploymentValid = false;
}
}
if (deploymentValid && heroes.length < maxHeroes) {
var hero = new Hero(selectedHeroType);
hero.x = x;
hero.y = y;
enemyLayerTop.addChild(hero); // Add heroes to top layer
heroes.push(hero);
updateHeroPanel();
heroDeploymentMode = false;
var notification = game.addChild(new Notification(selectedHeroType.charAt(0).toUpperCase() + selectedHeroType.slice(1) + " hero deployed!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return;
} else {
var notification = game.addChild(new Notification("Cannot deploy hero here!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
heroDeploymentMode = false;
return;
}
}
var upgradeMenuVisible = game.children.some(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenuVisible) {
return;
}
// Improved touch detection with larger hit areas for mobile
var touchPadding = 40; // Extra padding for easier touch
for (var i = 0; i < sourceTowers.length; i++) {
var tower = sourceTowers[i];
var hitArea = {
left: tower.x - tower.width / 2 - touchPadding,
right: tower.x + tower.width / 2 + touchPadding,
top: tower.y - tower.height / 2 - touchPadding,
bottom: tower.y + tower.height / 2 + touchPadding
};
if (x >= hitArea.left && x <= hitArea.right && y >= hitArea.top && y <= hitArea.bottom) {
// Check if player can afford this tower
if (gold >= getTowerCost(tower.towerType)) {
towerPreview.visible = true;
isDragging = true;
towerPreview.towerType = tower.towerType;
towerPreview.updateAppearance();
// Improved drag offset for better mobile experience
var dragOffsetY = CELL_SIZE * 2; // Larger offset to keep preview visible above finger
towerPreview.snapToGrid(x, y - dragOffsetY);
// Add immediate visual feedback with scaling animation
tween(tower, {
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(tower, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
}
});
} else {
// Visual feedback for unaffordable towers
tween(tower, {
scaleX: 0.95,
scaleY: 0.95,
alpha: 0.7
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(tower, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 150,
easing: tween.easeIn
});
}
});
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
break;
}
}
};
game.move = function (x, y, obj) {
if (isDragging) {
// Shift the y position upward by 1.5 tiles to show preview above finger
towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
}
};
game.up = function (x, y, obj) {
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var towerLeft = tower.x - tower.width / 2;
var towerRight = tower.x + tower.width / 2;
var towerTop = tower.y - tower.height / 2;
var towerBottom = tower.y + tower.height / 2;
if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
clickedOnTower = true;
break;
}
}
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) {
var clickedOnMenu = false;
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
var menuWidth = 2048;
var menuHeight = 450;
var menuLeft = menu.x - menuWidth / 2;
var menuRight = menu.x + menuWidth / 2;
var menuTop = menu.y - menuHeight / 2;
var menuBottom = menu.y + menuHeight / 2;
if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) {
clickedOnMenu = true;
break;
}
}
if (!clickedOnMenu) {
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
hideUpgradeMenu(menu);
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = null;
grid.renderDebug();
}
}
if (isDragging) {
isDragging = false;
if (towerPreview.canPlace) {
if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) {
placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType);
} else {
var notification = game.addChild(new Notification("Tower would block the path!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
} else if (towerPreview.blockedByEnemy) {
var notification = game.addChild(new Notification("Cannot build: Enemy in the way!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
} else if (towerPreview.visible) {
var notification = game.addChild(new Notification("Cannot build here!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
towerPreview.visible = false;
if (isDragging) {
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
}
}
};
var waveIndicator = new WaveIndicator();
waveIndicator.x = 2048 / 2;
waveIndicator.y = 2732 - 80;
game.addChild(waveIndicator);
var nextWaveButtonContainer = new Container();
var nextWaveButton = new NextWaveButton();
nextWaveButton.x = 2048 - 200;
nextWaveButton.y = 2732 - 100 + 20;
nextWaveButtonContainer.addChild(nextWaveButton);
game.addChild(nextWaveButtonContainer);
var towerTypes = ['default', 'rapid', 'sniper', 'splash', 'slow', 'poison'];
var sourceTowers = [];
var towerSpacing = 300; // Increase spacing for larger towers
var startX = 2048 / 2 - towerTypes.length * towerSpacing / 2 + towerSpacing / 2;
var towerY = 2732 - CELL_SIZE * 3 - 90;
for (var i = 0; i < towerTypes.length; i++) {
var tower = new SourceTower(towerTypes[i]);
tower.x = startX + i * towerSpacing;
tower.y = towerY;
towerLayer.addChild(tower);
sourceTowers.push(tower);
}
sourceTower = null;
// Initialize station customizer
stationCustomizer = new StationCustomizer();
stationCustomizer.x = 0;
stationCustomizer.y = 0;
game.addChild(stationCustomizer);
// Create station customization panel
var stationPanel = new Container();
var stationPanelBg = stationPanel.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
stationPanelBg.width = 400;
stationPanelBg.height = 100;
stationPanelBg.tint = 0x2E7D32;
stationPanelBg.alpha = 0.9;
var stationTitleText = new Text2("Station", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
stationTitleText.anchor.set(0.5, 0.5);
stationTitleText.y = -15;
stationPanel.addChild(stationTitleText);
var customizeButton = new Container();
var customizeBg = customizeButton.attachAsset('heroAbilityIcon', {
anchorX: 0.5,
anchorY: 0.5
});
customizeBg.width = 80;
customizeBg.height = 40;
customizeBg.tint = 0x4CAF50;
customizeButton.y = 20;
var customizeText = new Text2("Customize", {
size: 30,
fill: 0xFFFFFF,
weight: 600
});
customizeText.anchor.set(0.5, 0.5);
customizeButton.addChild(customizeText);
customizeButton.down = function () {
stationCustomizer.toggleCustomizationMode();
};
stationPanel.addChild(customizeButton);
stationPanel.x = 200;
stationPanel.y = 120;
game.addChild(stationPanel);
// Create hero deployment panel
var heroPanel = new Container();
var heroPanelBg = heroPanel.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
heroPanelBg.width = 800;
heroPanelBg.height = 120;
heroPanelBg.tint = 0x1A237E;
heroPanelBg.alpha = 0.9;
var heroTitleText = new Text2("Heroes (" + heroes.length + "/" + maxHeroes + ")", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
heroTitleText.anchor.set(0.5, 0.5);
heroTitleText.y = -30;
heroPanel.addChild(heroTitleText);
// Hero type buttons
var heroTypes = ['tank', 'support', 'dps'];
var heroTypeButtons = [];
for (var i = 0; i < heroTypes.length; i++) {
var heroButton = new Container();
var buttonBg = heroButton.attachAsset('heroAbilityIcon', {
anchorX: 0.5,
anchorY: 0.5
});
switch (heroTypes[i]) {
case 'tank':
buttonBg.tint = 0x4CAF50;
break;
case 'support':
buttonBg.tint = 0x2196F3;
break;
case 'dps':
buttonBg.tint = 0xFF5722;
break;
}
buttonBg.width = 80;
buttonBg.height = 80;
heroButton.heroType = heroTypes[i];
heroButton.x = -240 + i * 120;
heroButton.y = 10;
heroButton.down = function () {
if (heroes.length >= maxHeroes) {
var notification = game.addChild(new Notification("Maximum heroes deployed!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return;
}
selectedHeroType = this.heroType;
heroDeploymentMode = true;
var notification = game.addChild(new Notification("Tap anywhere to deploy " + this.heroType + " hero"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
};
heroPanel.addChild(heroButton);
heroTypeButtons.push(heroButton);
}
heroPanel.x = 2048 / 2;
heroPanel.y = 120;
game.addChild(heroPanel);
function updateHeroPanel() {
heroTitleText.setText("Heroes (" + heroes.length + "/" + maxHeroes + ")");
}
enemiesToSpawn = 10;
// Initialize tutorial system for new players
var tutorialSystem = new TutorialSystem();
game.addChild(tutorialSystem);
// Show tutorial automatically for new players
// In a real game, this would check if player has seen tutorial before
var hasSeenTutorial = false; // Could use storage.get('hasSeenTutorial') for persistence
if (!hasSeenTutorial) {
tutorialSystem.startTutorial();
}
game.update = function () {
performanceMonitor.update();
// Clear and rebuild spatial grid each frame for enemies
spatialGrid.clear();
for (var i = 0; i < enemies.length; i++) {
spatialGrid.addEnemy(enemies[i]);
}
if (waveInProgress) {
if (!waveSpawned) {
waveSpawned = true;
// Get wave type and enemy count from the wave indicator
var waveType = waveIndicator.getWaveType(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
// Check if this is a boss wave
var isBossWave = currentWave % 10 === 0 && currentWave > 0;
if (isBossWave && waveType !== 'swarm') {
// Boss waves have just 1 enemy regardless of what the wave indicator says
enemyCount = 1;
// Show boss announcement
var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️"));
notification.x = 2048 / 2;
notification.y = grid.height - 200;
}
// Spawn the appropriate number of enemies
for (var i = 0; i < enemyCount; i++) {
var enemy = new Enemy(waveType);
// Add enemy to the appropriate layer based on type
if (enemy.isFlying) {
// Add flying enemy to the top layer
enemyLayerTop.addChild(enemy);
// If it's a flying enemy, add its shadow to the middle layer
if (enemy.shadow) {
enemyLayerMiddle.addChild(enemy.shadow);
}
} else {
// Add normal/ground enemies to the bottom layer
enemyLayerBottom.addChild(enemy);
}
// Scale difficulty with wave number but don't apply to boss
// as bosses already have their health multiplier
// Use more balanced scaling that starts linear and becomes logarithmic
var healthMultiplier;
if (currentWave <= 10) {
// Linear scaling for early waves
healthMultiplier = 1 + (currentWave - 1) * 0.15; // 15% per wave for first 10 waves
} else if (currentWave <= 25) {
// Moderate scaling for mid waves
healthMultiplier = 2.35 + (currentWave - 10) * 0.08; // Start at 2.35x, add 8% per wave
} else {
// Logarithmic scaling for late waves to prevent exponential growth
healthMultiplier = 3.55 + Math.log(currentWave - 24) * 0.5; // Logarithmic growth
}
enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier);
enemy.health = enemy.maxHealth;
// Increment speed slightly with wave number
//enemy.speed = enemy.speed + currentWave * 0.002;
// All enemy types now spawn in the middle 6 tiles at the top spacing
var gridWidth = 24;
var midPoint = Math.floor(gridWidth / 2); // 12
// Find a column that isn't occupied by another enemy that's not yet in view
var availableColumns = [];
for (var col = midPoint - 3; col < midPoint + 3; col++) {
var columnOccupied = false;
// Check if any enemy is already in this column but not yet in view
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === col && enemies[e].currentCellY < 4) {
columnOccupied = true;
break;
}
}
if (!columnOccupied) {
availableColumns.push(col);
}
}
// If all columns are occupied, use original random method
var spawnX;
if (availableColumns.length > 0) {
// Choose a random unoccupied column
spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)];
} else {
// Fallback to random if all columns are occupied
spawnX = midPoint - 3 + Math.floor(Math.random() * 6); // x from 9 to 14
}
var spawnY = -1 - Math.random() * 5; // Random distance above the grid for spreading
enemy.cellX = spawnX;
enemy.cellY = 5; // Position after entry
enemy.currentCellX = spawnX;
enemy.currentCellY = spawnY;
enemy.waveNumber = currentWave;
enemies.push(enemy);
}
}
var currentWaveEnemiesRemaining = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].waveNumber === currentWave) {
currentWaveEnemiesRemaining = true;
break;
}
}
if (waveSpawned && !currentWaveEnemiesRemaining) {
waveInProgress = false;
waveSpawned = false;
}
}
// Update heroes
for (var i = heroes.length - 1; i >= 0; i--) {
var hero = heroes[i];
if (hero.health <= 0) {
// Hero defeated - remove from play but don't reset progression
var notification = game.addChild(new Notification(hero.heroType.charAt(0).toUpperCase() + hero.heroType.slice(1) + " hero defeated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
hero.destroy();
heroes.splice(i, 1);
updateHeroPanel();
continue;
}
hero.update();
}
for (var a = enemies.length - 1; a >= 0; a--) {
var enemy = enemies[a];
// Skip some enemy updates when performance is poor
if (performanceMonitor.shouldSkipUpdate() && enemy.health > 0) {
continue;
}
// Handle hero targeting for enemies
if (enemy.heroTarget && enemy.heroTarget.parent && enemy.heroTarget.health > 0) {
if (enemy.heroTargetDuration > 0) {
enemy.heroTargetDuration--;
// Move towards hero instead of following path
var dx = enemy.heroTarget.x - enemy.x;
var dy = enemy.heroTarget.y - enemy.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > CELL_SIZE * 0.5) {
var angle = Math.atan2(dy, dx);
enemy.x += Math.cos(angle) * enemy.speed * 30;
enemy.y += Math.sin(angle) * enemy.speed * 30;
} else {
// Attack hero
enemy.heroTarget.health -= 10;
if (enemy.heroTarget.health <= 0) {
enemy.heroTarget.health = 0;
}
}
continue; // Skip normal pathfinding
} else {
enemy.heroTarget = null;
enemy.heroTargetDuration = 0;
}
} else {
enemy.heroTarget = null;
enemy.heroTargetDuration = 0;
}
if (enemy.health <= 0) {
// Improved gold rewards that scale better with difficulty
var goldEarned;
if (enemy.isBoss) {
// Boss gold scales more reasonably: 25 base + 3 per wave
goldEarned = Math.floor(25 + (enemy.waveNumber - 1) * 3);
} else {
// Regular enemy gold: better scaling for later waves
if (enemy.waveNumber <= 10) {
goldEarned = Math.floor(2 + (enemy.waveNumber - 1) * 0.3); // 2-4.7 gold for waves 1-10
} else if (enemy.waveNumber <= 25) {
goldEarned = Math.floor(4.7 + (enemy.waveNumber - 10) * 0.4); // 4.7-10.7 gold for waves 11-25
} else {
goldEarned = Math.floor(10.7 + (enemy.waveNumber - 25) * 0.2); // Slower growth for late waves
}
}
var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y);
game.addChild(goldIndicator);
setGold(gold + goldEarned);
// Give more score for defeating a boss
var scoreValue = enemy.isBoss ? 100 : 5;
score += scoreValue;
// Add a notification for boss defeat
if (enemy.isBoss) {
var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
updateUI();
// Use proper destroy method for comprehensive cleanup
enemy.destroy();
continue;
}
if (grid.updateEnemy(enemy)) {
// Check if shields are active
var shieldsActive = false;
for (var s = 0; s < stationCustomizer.modules.length; s++) {
var module = stationCustomizer.modules[s];
if (module.moduleType === 'shield' && module.shieldActive) {
shieldsActive = true;
break;
}
}
if (shieldsActive) {
// Shields absorb damage
var notification = game.addChild(new Notification("Enemy blocked by shields!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
} else {
lives = Math.max(0, lives - 1);
updateUI();
if (lives <= 0) {
LK.showGameOver();
}
}
// Use proper destroy method for comprehensive cleanup
enemy.destroy();
}
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (!bullets[i].parent) {
if (bullets[i].targetEnemy) {
var targetEnemy = bullets[i].targetEnemy;
var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]);
if (bulletIndex !== -1) {
targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
bullets.splice(i, 1);
}
}
// Limit bullet count when performance drops
if (performanceMonitor.performanceLevel < 0.7 && bullets.length > 50) {
// Remove oldest bullets when performance is poor
var bulletsToRemove = Math.min(10, bullets.length - 30);
for (var i = 0; i < bulletsToRemove; i++) {
var oldBullet = bullets[i];
if (oldBullet.targetEnemy) {
var bulletIndex = oldBullet.targetEnemy.bulletsTargetingThis.indexOf(oldBullet);
if (bulletIndex !== -1) {
oldBullet.targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
returnBulletToPool(oldBullet);
bullets.splice(i, 1);
i--; // Adjust index after removal
bulletsToRemove--;
}
}
if (towerPreview.visible) {
towerPreview.checkPlacement();
}
// Update station customizer
if (stationCustomizer) {
stationCustomizer.update();
// Update module arrays for quick access
researchLabs = stationCustomizer.modules.filter(function (m) {
return m.moduleType === 'research';
});
shieldGenerators = stationCustomizer.modules.filter(function (m) {
return m.moduleType === 'shield';
});
teleporterNodes = stationCustomizer.modules.filter(function (m) {
return m.moduleType === 'teleporter';
});
// Link teleporter nodes
for (var i = 0; i < teleporterNodes.length; i++) {
teleporterNodes[i].linkedNodes = teleporterNodes.filter(function (node, index) {
return index !== i;
});
}
}
// Update tower specializations and support links
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.specialization && tower.specialization.update) {
tower.specialization.update();
}
}
// Clean up destroyed specialization menus
specializationMenus = specializationMenus.filter(function (menu) {
return menu.parent && !menu.isDestroyed;
});
// Environmental hazards management
if (waveIndicator && waveIndicator.gameStarted) {
hazardTimer++;
if (hazardTimer >= nextHazardTime) {
hazardTimer = 0;
nextHazardTime = 1200 + Math.random() * 1200; // 20-40 seconds
// Spawn random environmental hazard
var hazardType = Math.floor(Math.random() * 4);
switch (hazardType) {
case 0:
// Asteroid field
if (asteroids.length < 3) {
var validPositions = [];
for (var x = 2; x < 22; x++) {
for (var y = 6; y < 25; y++) {
var cell = grid.getCell(x, y);
if (cell && cell.type === 0) {
validPositions.push({
x: x,
y: y
});
}
}
}
if (validPositions.length > 0) {
var pos = validPositions[Math.floor(Math.random() * validPositions.length)];
var asteroid = new Asteroid(grid.x + pos.x * CELL_SIZE + CELL_SIZE / 2, grid.y + pos.y * CELL_SIZE + CELL_SIZE / 2);
game.addChild(asteroid);
asteroids.push(asteroid);
var notification = game.addChild(new Notification("⚠️ Asteroid field detected! ⚠️"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
}
break;
case 1:
// Nebula cloud
if (nebulaClouds.length < 2) {
var cloudX = grid.x + (5 + Math.random() * 14) * CELL_SIZE;
var cloudY = grid.y + (8 + Math.random() * 12) * CELL_SIZE;
var nebula = new NebulaCloud(cloudX, cloudY);
game.addChild(nebula);
nebulaClouds.push(nebula);
var notification = game.addChild(new Notification("🌌 Nebula cloud formation! 🌌"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
// Remove after 15 seconds
LK.setTimeout(function () {
if (nebula.parent) {
nebula.destroy();
}
}, 15000);
}
break;
case 2:
// Solar flare
if (solarFlares.length === 0) {
var flare = new SolarFlare();
game.addChild(flare);
solarFlares.push(flare);
}
break;
case 3:
// Wormhole
if (wormholes.length < 2) {
// Create entry and exit points
var entryX = grid.x + (3 + Math.random() * 6) * CELL_SIZE;
var entryY = grid.y + (8 + Math.random() * 8) * CELL_SIZE;
var exitX = grid.x + (15 + Math.random() * 6) * CELL_SIZE;
var exitY = grid.y + (16 + Math.random() * 6) * CELL_SIZE;
var wormhole = new Wormhole(entryX, entryY, exitX, exitY);
game.addChild(wormhole);
wormholes.push(wormhole);
var notification = game.addChild(new Notification("🌀 Wormhole opened! 🌀"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
// Remove after 20 seconds
LK.setTimeout(function () {
if (wormhole.parent) {
wormhole.destroy();
}
}, 20000);
}
break;
}
}
}
// Update environmental hazards
for (var i = asteroids.length - 1; i >= 0; i--) {
if (asteroids[i].parent) {
// Asteroids can be damaged by bullets
for (var j = 0; j < bullets.length; j++) {
var bullet = bullets[j];
var dx = bullet.x - asteroids[i].x;
var dy = bullet.y - asteroids[i].y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 60) {
// Hit asteroid
asteroids[i].takeDamage(bullet.damage);
returnBulletToPool(bullet);
break;
}
}
}
}
for (var i = 0; i < nebulaClouds.length; i++) {
if (nebulaClouds[i].parent) {
nebulaClouds[i].update();
}
}
for (var i = 0; i < solarFlares.length; i++) {
if (solarFlares[i].parent) {
solarFlares[i].update();
}
}
for (var i = 0; i < wormholes.length; i++) {
if (wormholes[i].parent) {
wormholes[i].update();
}
}
if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) {
LK.showYouWin();
}
}; ===================================================================
--- original.js
+++ change.js
@@ -3058,10 +3058,8 @@
self.damage = 10 + self.level * 7;
self.bulletSpeed = 5 + self.level * 0.8;
}
}
- self.refreshCellsInRange();
- self.updateLevelIndicators();
if (self.level > 1) {
var levelDot = levelIndicators[self.level - 1].children[1];
tween(levelDot, {
scaleX: 1.5,
@@ -3146,8 +3144,12 @@
}
return closestEnemy;
};
self.update = function () {
+ // Update ultimate ability
+ if (self.ultimate) {
+ self.ultimate.update();
+ }
self.targetEnemy = self.findTarget();
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
@@ -3163,8 +3165,15 @@
}
}
};
self.down = function (x, y, obj) {
+ // Check for ultimate ability activation (double tap or long press simulation)
+ if (self.ultimate && self.ultimate.canActivate()) {
+ // Simple activation - tap when ultimate is ready
+ if (self.ultimate.activate()) {
+ return; // Ultimate was activated, don't show menu
+ }
+ }
var existingMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
var hasOwnMenu = false;
@@ -3450,8 +3459,13 @@
// Clear selected tower if it's this tower
if (selectedTower === self) {
selectedTower = null;
}
+ // Clean up ultimate ability
+ if (self.ultimate) {
+ self.ultimate.destroy();
+ self.ultimate = null;
+ }
// Nullify all object references
self.targetEnemy = null;
self.energyCore = null;
// Call parent destroy
@@ -3708,8 +3722,421 @@
Container.prototype.destroy.call(self);
};
return self;
});
+var TowerUltimate = Container.expand(function (tower, ultimateType) {
+ var self = Container.call(this);
+ self.tower = tower;
+ self.ultimateType = ultimateType || 'none';
+ self.cooldownTime = 1800; // 30 seconds at 60fps
+ self.currentCooldown = 0;
+ self.isDestroyed = false;
+ // Add ultimate indicator
+ var ultimateIcon = self.attachAsset('ultimateIndicator', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ ultimateIcon.y = -self.tower.children[0].height / 2 - 35;
+ ultimateIcon.alpha = 0.3; // Dim when on cooldown
+ // Set ultimate-specific properties
+ switch (self.ultimateType) {
+ case 'orbital':
+ self.cooldownTime = 2400; // 40 seconds
+ ultimateIcon.tint = 0xFF0000;
+ break;
+ case 'nanite':
+ self.cooldownTime = 2100; // 35 seconds
+ ultimateIcon.tint = 0x00FF88;
+ break;
+ case 'temporal':
+ self.cooldownTime = 1800; // 30 seconds
+ ultimateIcon.tint = 0x9966FF;
+ break;
+ default:
+ ultimateIcon.tint = 0xFFD700;
+ }
+ self.canActivate = function () {
+ return self.currentCooldown <= 0 && !self.isDestroyed;
+ };
+ self.activate = function () {
+ if (!self.canActivate()) return false;
+ self.currentCooldown = self.cooldownTime;
+ // Visual cooldown effect
+ ultimateIcon.alpha = 0.3;
+ tween(ultimateIcon, {
+ alpha: 1.0
+ }, {
+ duration: self.cooldownTime,
+ easing: tween.linear
+ });
+ switch (self.ultimateType) {
+ case 'orbital':
+ return self.orbitalStrike();
+ case 'nanite':
+ return self.naniteSwarm();
+ case 'temporal':
+ return self.timeDilation();
+ }
+ return false;
+ };
+ self.orbitalStrike = function () {
+ // Find target area with most enemies
+ var bestTarget = null;
+ var maxEnemies = 0;
+ var strikeRadius = CELL_SIZE * 3;
+ for (var i = 0; i < enemies.length; i++) {
+ var enemy = enemies[i];
+ var nearbyCount = 0;
+ for (var j = 0; j < enemies.length; j++) {
+ var otherEnemy = enemies[j];
+ var dx = otherEnemy.x - enemy.x;
+ var dy = otherEnemy.y - enemy.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= strikeRadius) {
+ nearbyCount++;
+ }
+ }
+ if (nearbyCount > maxEnemies) {
+ maxEnemies = nearbyCount;
+ bestTarget = enemy;
+ }
+ }
+ if (bestTarget) {
+ // Create orbital strike effect
+ var strikeEffect = new Container();
+ strikeEffect.x = bestTarget.x;
+ strikeEffect.y = bestTarget.y;
+ var strikeGraphics = strikeEffect.attachAsset('orbitalStrike', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ strikeGraphics.alpha = 0.0;
+ strikeGraphics.scaleX = 0.1;
+ strikeGraphics.scaleY = 0.1;
+ strikeGraphics.blendMode = 1; // Additive
+ game.addChild(strikeEffect);
+ // Warning phase
+ tween(strikeGraphics, {
+ alpha: 0.7,
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 1000,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ // Strike phase - massive damage
+ var strikeDamage = self.tower.damage * 15;
+ for (var i = enemies.length - 1; i >= 0; i--) {
+ var enemy = enemies[i];
+ var dx = enemy.x - bestTarget.x;
+ var dy = enemy.y - bestTarget.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= strikeRadius) {
+ enemy.health -= strikeDamage;
+ if (enemy.health <= 0) {
+ enemy.health = 0;
+ } else {
+ enemy.healthBar.width = enemy.health / enemy.maxHealth * 70;
+ }
+ // Visual damage effect
+ tween(enemy, {
+ tint: 0xFFFFFF,
+ scaleX: 1.2,
+ scaleY: 1.2
+ }, {
+ duration: 200,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ tween(enemy, {
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 200,
+ easing: tween.easeIn
+ });
+ }
+ });
+ }
+ }
+ // Final explosion effect
+ tween(strikeGraphics, {
+ scaleX: 2.5,
+ scaleY: 2.5,
+ alpha: 0
+ }, {
+ duration: 800,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ if (strikeEffect.parent) {
+ strikeEffect.parent.removeChild(strikeEffect);
+ }
+ }
+ });
+ }
+ });
+ var notification = game.addChild(new Notification("🛰️ ORBITAL STRIKE INCOMING! 🛰️"));
+ notification.x = 2048 / 2;
+ notification.y = grid.height - 50;
+ return true;
+ }
+ return false;
+ };
+ self.naniteSwarm = function () {
+ // Create nanite swarm that spreads poison and self-replicates
+ var swarmCenter = self.tower;
+ var swarmRadius = CELL_SIZE * 4;
+ var swarmDamage = self.tower.damage * 3;
+ // Create visual swarm effect
+ var swarmEffect = new Container();
+ swarmEffect.x = swarmCenter.x;
+ swarmEffect.y = swarmCenter.y;
+ var swarmGraphics = swarmEffect.attachAsset('naniteSwarm', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ swarmGraphics.alpha = 0.8;
+ swarmGraphics.tint = 0x00FF88;
+ game.addChild(swarmEffect);
+ // Create multiple nanite particles
+ var particles = [];
+ for (var i = 0; i < 12; i++) {
+ var particle = swarmEffect.attachAsset('towerLevelIndicator', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ particle.width = 15;
+ particle.height = 15;
+ particle.tint = 0x44FFAA;
+ var angle = i / 12 * Math.PI * 2;
+ particle.x = Math.cos(angle) * 50;
+ particle.y = Math.sin(angle) * 50;
+ particles.push(particle);
+ }
+ // Animate swarm expansion
+ tween(swarmEffect, {
+ scaleX: 3.0,
+ scaleY: 3.0
+ }, {
+ duration: 2000,
+ easing: tween.easeOut
+ });
+ // Animate particles spiraling outward
+ for (var i = 0; i < particles.length; i++) {
+ var particle = particles[i];
+ tween(particle, {
+ x: particle.x * 3,
+ y: particle.y * 3,
+ rotation: Math.PI * 4
+ }, {
+ duration: 2000,
+ delay: i * 100,
+ easing: tween.easeOut
+ });
+ }
+ // Apply nanite effects over time
+ var swarmDuration = 180; // 3 seconds
+ var _swarmTick = function swarmTick() {
+ swarmDuration--;
+ for (var i = 0; i < enemies.length; i++) {
+ var enemy = enemies[i];
+ var dx = enemy.x - swarmCenter.x;
+ var dy = enemy.y - swarmCenter.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= swarmRadius) {
+ // Apply nanite damage
+ enemy.health -= swarmDamage;
+ if (enemy.health <= 0) {
+ enemy.health = 0;
+ } else {
+ enemy.healthBar.width = enemy.health / enemy.maxHealth * 70;
+ }
+ // Apply enhanced poison effect
+ enemy.poisoned = true;
+ enemy.poisonDamage = swarmDamage * 0.5;
+ enemy.poisonDuration = 300; // Extended duration
+ // Self-replication chance
+ if (Math.random() < 0.3 && swarmDuration > 60) {
+ // 30% chance to spread to nearby enemies
+ for (var j = 0; j < enemies.length; j++) {
+ var nearbyEnemy = enemies[j];
+ if (nearbyEnemy !== enemy) {
+ var nearbyDx = nearbyEnemy.x - enemy.x;
+ var nearbyDy = nearbyEnemy.y - enemy.y;
+ var nearbyDistance = Math.sqrt(nearbyDx * nearbyDx + nearbyDy * nearbyDy);
+ if (nearbyDistance <= CELL_SIZE * 2) {
+ nearbyEnemy.poisoned = true;
+ nearbyEnemy.poisonDamage = swarmDamage * 0.3;
+ nearbyEnemy.poisonDuration = 240;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ if (swarmDuration > 0) {
+ LK.setTimeout(_swarmTick, 100); // Continue every 100ms
+ } else {
+ // Clean up swarm effect
+ tween(swarmEffect, {
+ alpha: 0,
+ scaleX: 0.1,
+ scaleY: 0.1
+ }, {
+ duration: 500,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ if (swarmEffect.parent) {
+ swarmEffect.parent.removeChild(swarmEffect);
+ }
+ }
+ });
+ }
+ };
+ LK.setTimeout(_swarmTick, 100); // Start swarm effects
+ var notification = game.addChild(new Notification("🤖 NANITE SWARM DEPLOYED! 🤖"));
+ notification.x = 2048 / 2;
+ notification.y = grid.height - 50;
+ return true;
+ };
+ self.timeDilation = function () {
+ // Create temporal distortion field that slows time for enemies
+ var dilationCenter = self.tower;
+ var dilationRadius = CELL_SIZE * 5;
+ var dilationDuration = 600; // 10 seconds
+ // Create visual time dilation effect
+ var dilationEffect = new Container();
+ dilationEffect.x = dilationCenter.x;
+ dilationEffect.y = dilationCenter.y;
+ var dilationGraphics = dilationEffect.attachAsset('timeDilation', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ dilationGraphics.alpha = 0.4;
+ dilationGraphics.tint = 0x9966FF;
+ dilationGraphics.blendMode = 1; // Additive
+ game.addChild(dilationEffect);
+ // Pulsing time distortion effect
+ tween(dilationGraphics, {
+ scaleX: 1.3,
+ scaleY: 1.3,
+ alpha: 0.7
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ tween(dilationGraphics, {
+ scaleX: 1.0,
+ scaleY: 1.0,
+ alpha: 0.4
+ }, {
+ duration: 1000,
+ easing: tween.easeInOut
+ });
+ }
+ });
+ // Apply time dilation effects
+ var affectedEnemies = [];
+ for (var i = 0; i < enemies.length; i++) {
+ var enemy = enemies[i];
+ var dx = enemy.x - dilationCenter.x;
+ var dy = enemy.y - dilationCenter.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= dilationRadius) {
+ // Store original speed
+ if (!enemy.originalDilationSpeed) {
+ enemy.originalDilationSpeed = enemy.speed;
+ }
+ // Extreme slow effect (90% speed reduction)
+ enemy.speed = enemy.originalDilationSpeed * 0.1;
+ enemy.temporallyDilated = true;
+ enemy.dilationDuration = dilationDuration;
+ affectedEnemies.push(enemy);
+ // Visual distortion effect on enemy
+ tween(enemy, {
+ tint: 0x9966FF,
+ alpha: 0.8
+ }, {
+ duration: 300,
+ easing: tween.easeOut
+ });
+ }
+ }
+ // During dilation, towers in the field get boosted fire rate
+ var boostedTowers = [];
+ for (var i = 0; i < towers.length; i++) {
+ var tower = towers[i];
+ var dx = tower.x - dilationCenter.x;
+ var dy = tower.y - dilationCenter.y;
+ var distance = Math.sqrt(dx * dx + dy * dy);
+ if (distance <= dilationRadius && tower !== self.tower) {
+ tower.originalDilationFireRate = tower.fireRate;
+ tower.fireRate = Math.floor(tower.fireRate * 0.3); // 70% faster firing
+ tower.temporallyBoosted = true;
+ boostedTowers.push(tower);
+ }
+ }
+ // Clean up after duration
+ LK.setTimeout(function () {
+ // Restore enemy speeds
+ for (var i = 0; i < affectedEnemies.length; i++) {
+ var enemy = affectedEnemies[i];
+ if (enemy.parent && !enemy.isDestroyed) {
+ enemy.speed = enemy.originalDilationSpeed;
+ enemy.temporallyDilated = false;
+ // Restore enemy appearance
+ tween(enemy, {
+ alpha: 1.0
+ }, {
+ duration: 300,
+ easing: tween.easeIn
+ });
+ }
+ }
+ // Restore tower fire rates
+ for (var i = 0; i < boostedTowers.length; i++) {
+ var tower = boostedTowers[i];
+ if (tower.parent && !tower.isDestroyed) {
+ tower.fireRate = tower.originalDilationFireRate;
+ tower.temporallyBoosted = false;
+ }
+ }
+ // Remove visual effect
+ tween(dilationEffect, {
+ alpha: 0,
+ scaleX: 0.1,
+ scaleY: 0.1
+ }, {
+ duration: 1000,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ if (dilationEffect.parent) {
+ dilationEffect.parent.removeChild(dilationEffect);
+ }
+ }
+ });
+ }, dilationDuration * 16.67); // Convert frames to milliseconds
+ var notification = game.addChild(new Notification("⏰ TIME DILATION ACTIVATED! ⏰"));
+ notification.x = 2048 / 2;
+ notification.y = grid.height - 50;
+ return true;
+ };
+ self.update = function () {
+ if (self.currentCooldown > 0) {
+ self.currentCooldown--;
+ }
+ };
+ self.destroy = function () {
+ if (self.isDestroyed) return;
+ self.isDestroyed = true;
+ tween.stop(self);
+ if (self.parent) {
+ self.parent.removeChild(self);
+ }
+ Container.prototype.destroy.call(self);
+ };
+ return self;
+});
var TutorialSystem = Container.expand(function () {
var self = Container.call(this);
self.currentStep = 0;
self.isActive = false;
@@ -4701,8 +5128,11 @@
var nextHazardTime = 1800; // 30 seconds at 60fps
// Tower specialization variables
var specializationMenus = [];
var towerLinks = [];
+// Ultimate ability variables
+var activeUltimates = [];
+var ultimateEffects = [];
// Load station data from storage
var stationData = storage.stationData || {
modules: [],
researchPoints: 0,
White circle with two eyes, seen from above.. In-Game asset. 2d. High contrast. No shadows
White simple circular enemy seen from above, black outline. Black eyes, with a single shield in-font of it. Black and white only. Blue background.
White circle with black outline. Blue background.. In-Game asset. 2d. High contrast. No shadows
wormwhole for TD game towdown. In-Game asset. 2d. High contrast. No shadows
tank hero character space galaxy theme tower defense game. In-Game asset. 2d. High contrast. No shadows
support hero character space galaxy theme tower defense game. In-Game asset. 2d. High contrast. No shadows
poison tower space galaxy theme tower defense game, advanced base level tower. In-Game asset. 2d. High contrast. No shadows