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;
// Play enemy destroy sound
LK.getSound('enemyDestroy').play();
} else {
self.targetEnemy.healthBar.width = self.targetEnemy.health / self.targetEnemy.maxHealth * 70;
// Play enemy hit sound
LK.getSound('enemyHit').play();
}
// 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;
}
// Mark junction cells for branching
for (var i = 0; i < pathJunctions.length; i++) {
var junction = pathJunctions[i];
var junctionCell = self.getCell(junction.gridX, junction.gridY);
if (junctionCell) {
junctionCell.isJunction = true;
junctionCell.junction = junction;
}
}
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) {
// Check if enemy is at a junction
if (cell && cell.isJunction && !enemy.chosenBranch) {
var junction = cell.junction;
var chosenBranch = junction.choosePath(enemy);
if (chosenBranch) {
enemy.chosenBranch = chosenBranch;
enemy.currentTarget = grid.getCell(chosenBranch.targetX, chosenBranch.targetY);
// Show visual path indicator
if (routeVisualizer && Math.random() < 0.3) {
// Show for 30% of enemies
routeVisualizer.showPaths(junction, enemy);
}
// Add notification for significant route changes
if (Math.random() < 0.1) {
// 10% chance
var routeType = chosenBranch.isShortPath ? "direct" : "alternate";
var notification = game.addChild(new Notification("Enemy taking " + routeType + " route!"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
}
} else {
enemy.currentTarget = cell.targets[0];
}
} else {
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;
// Play hero ability sound
LK.getSound('heroAbility').play();
// 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: 0xFFFFFF,
weight: 800
});
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 60;
notificationGraphics.height = 80;
notificationGraphics.tint = 0x1A1A1A;
notificationGraphics.alpha = 0.95;
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 PathJunction = Container.expand(function (x, y, branches) {
var self = Container.call(this);
self.x = x;
self.y = y;
self.branches = branches || []; // Array of possible path directions
self.gridX = Math.floor((x - grid.x) / CELL_SIZE);
self.gridY = Math.floor((y - grid.y) / CELL_SIZE);
self.isDestroyed = false;
// Visual junction indicator
var junctionGraphics = self.attachAsset('teleportEffect', {
anchorX: 0.5,
anchorY: 0.5
});
junctionGraphics.width = CELL_SIZE * 0.8;
junctionGraphics.height = CELL_SIZE * 0.8;
junctionGraphics.tint = 0x9c27b0;
junctionGraphics.alpha = 0.6;
// Pulsing animation to indicate junction
tween(junctionGraphics, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.8
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.isDestroyed) {
tween(junctionGraphics, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.6
}, {
duration: 1500,
easing: tween.easeInOut
});
}
}
});
// Choose path for enemy based on various factors
self.choosePath = function (enemy) {
if (self.branches.length === 0) return null;
var pathScores = [];
// Evaluate each branch
for (var i = 0; i < self.branches.length; i++) {
var branch = self.branches[i];
var score = 0;
// Base score - some randomness for variety
score += Math.random() * 100;
// Factor in enemy type preferences
switch (enemy.type) {
case 'fast':
// Fast enemies prefer shorter paths
score += branch.isShortPath ? 150 : 50;
break;
case 'immune':
// Immune enemies prefer heavily defended paths (they can tank it)
score += branch.towerDensity * 80;
break;
case 'flying':
// Flying enemies prefer paths with fewer anti-air towers
score += branch.hasAntiAir ? 20 : 120;
break;
case 'swarm':
// Swarm enemies prefer paths other swarm enemies haven't taken recently
score += branch.recentSwarmTraffic > 3 ? 30 : 100;
break;
default:
// Normal enemies prefer balanced paths
score += branch.difficulty < 50 ? 80 : 60;
break;
}
// Factor in current congestion (avoid clustering)
var congestionPenalty = branch.currentEnemyCount * 25;
score -= congestionPenalty;
// Factor in tower coverage (enemies avoid heavy defenses unless immune)
if (!enemy.isImmune) {
score -= branch.towerDensity * 40;
}
// Factor in path length (generally prefer shorter paths)
score -= branch.pathLength * 2;
pathScores.push({
branch: branch,
score: score
});
}
// Sort by score and add some weighted randomness
pathScores.sort(function (a, b) {
return b.score - a.score;
});
// Weighted selection - higher scores more likely but not guaranteed
var totalWeight = 0;
for (var i = 0; i < pathScores.length; i++) {
var weight = Math.max(1, pathScores[i].score);
pathScores[i].weight = weight;
totalWeight += weight;
}
var randomValue = Math.random() * totalWeight;
var currentWeight = 0;
for (var i = 0; i < pathScores.length; i++) {
currentWeight += pathScores[i].weight;
if (randomValue <= currentWeight) {
return pathScores[i].branch;
}
}
// Fallback to first option
return pathScores[0].branch;
};
// Update junction statistics
self.update = function () {
// Update branch statistics
for (var i = 0; i < self.branches.length; i++) {
var branch = self.branches[i];
// Count enemies currently on this branch
branch.currentEnemyCount = 0;
branch.recentSwarmTraffic = Math.max(0, branch.recentSwarmTraffic - 0.1);
for (var j = 0; j < enemies.length; j++) {
var enemy = enemies[j];
if (enemy.chosenBranch === branch) {
branch.currentEnemyCount++;
if (enemy.type === 'swarm') {
branch.recentSwarmTraffic += 0.1;
}
}
}
// Calculate tower density in branch area
branch.towerDensity = 0;
branch.hasAntiAir = false;
for (var t = 0; t < towers.length; t++) {
var tower = towers[t];
var dx = tower.x - (grid.x + branch.targetX * CELL_SIZE);
var dy = tower.y - (grid.y + branch.targetY * CELL_SIZE);
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= CELL_SIZE * 4) {
branch.towerDensity++;
if (tower.id === 'sniper' || tower.id === 'rapid') {
branch.hasAntiAir = true;
}
}
}
// Update path difficulty
branch.difficulty = branch.towerDensity * 10 + branch.pathLength;
}
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
var junctionIndex = pathJunctions.indexOf(self);
if (junctionIndex !== -1) {
pathJunctions.splice(junctionIndex, 1);
}
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
return self;
});
var RouteVisualizer = Container.expand(function () {
var self = Container.call(this);
self.pathLines = [];
self.isDestroyed = false;
self.showPaths = function (junction, enemy) {
// Clear existing path lines
self.clearPaths();
// Create visual lines for each possible branch
for (var i = 0; i < junction.branches.length; i++) {
var branch = junction.branches[i];
var pathLine = new Container();
// Create line segments to show the path
var segments = [];
var currentX = junction.gridX;
var currentY = junction.gridY;
// Draw path to target
var targetX = branch.targetX;
var targetY = branch.targetY;
var steps = Math.max(Math.abs(targetX - currentX), Math.abs(targetY - currentY));
for (var step = 0; step <= steps; step++) {
var progress = step / steps;
var segmentX = Math.round(currentX + (targetX - currentX) * progress);
var segmentY = Math.round(currentY + (targetY - currentY) * progress);
var segment = pathLine.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
segment.width = 12;
segment.height = 12;
segment.x = grid.x + segmentX * CELL_SIZE;
segment.y = grid.y + segmentY * CELL_SIZE;
// Color code based on path difficulty
var difficulty = branch.difficulty || 0;
if (difficulty < 30) {
segment.tint = 0x00FF00; // Green for easy
} else if (difficulty < 60) {
segment.tint = 0xFFFF00; // Yellow for medium
} else {
segment.tint = 0xFF0000; // Red for hard
}
segment.alpha = 0.7;
}
game.addChild(pathLine);
self.pathLines.push(pathLine);
// Animate the path appearance
tween(pathLine, {
alpha: 1.0
}, {
duration: 500,
easing: tween.easeOut
});
}
// Auto-hide after a few seconds
LK.setTimeout(function () {
self.clearPaths();
}, 3000);
};
self.clearPaths = function () {
for (var i = 0; i < self.pathLines.length; i++) {
if (self.pathLines[i].parent) {
self.pathLines[i].parent.removeChild(self.pathLines[i]);
}
}
self.pathLines = [];
};
self.destroy = function () {
if (self.isDestroyed) return;
self.isDestroyed = true;
self.clearPaths();
tween.stop(self);
if (self.parent) {
self.parent.removeChild(self);
}
Container.prototype.destroy.call(self);
};
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 = 0x1A1A1A;
menuBackground.alpha = 0.98;
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;
}
// Create tower base structure
var baseAssetName = 'tower_base_' + (self.id === 'default' ? 'default' : self.id);
var baseGraphics = self.attachAsset(baseAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Create support structures for higher levels
self.supportStructures = [];
// 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
});
}
});
// Method to update tower visual appearance based on level
self.updateTowerVisuals = function () {
// Ensure gunContainer exists before trying to use it
if (!gunContainer) {
console.warn("gunContainer not found, skipping visual update");
return;
}
// Clear existing gun graphics
if (self.gunGraphics && self.gunGraphics.parent) {
gunContainer.removeChild(self.gunGraphics);
}
// Clear existing support structures
for (var i = 0; i < self.supportStructures.length; i++) {
if (self.supportStructures[i].parent) {
self.removeChild(self.supportStructures[i]);
}
}
self.supportStructures = [];
// Create new gun graphics based on current level
var gunAssetName = 'tower_gun_' + (self.id === 'default' ? 'default' : self.id) + '_' + self.level;
self.gunGraphics = gunContainer.attachAsset(gunAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
// Add support structures for higher levels
if (self.level >= 2) {
// Add support beams
for (var i = 0; i < 2; i++) {
var beam = self.attachAsset('tower_support_beam', {
anchorX: 0.5,
anchorY: 1.0
});
beam.x = i === 0 ? -30 : 30;
beam.y = baseGraphics.height / 2 - 10;
beam.rotation = i === 0 ? -0.3 : 0.3;
self.supportStructures.push(beam);
}
}
if (self.level >= 3) {
// Add radar dish for better targeting
var radar = self.attachAsset('tower_radar_dish', {
anchorX: 0.5,
anchorY: 0.5
});
radar.x = 0;
radar.y = -baseGraphics.height / 2 - 20;
radar.alpha = 0.8;
self.supportStructures.push(radar);
// Rotate radar dish slowly
tween(radar, {
rotation: Math.PI * 2
}, {
duration: 4000,
easing: tween.linear,
onFinish: function onFinish() {
if (!self.isDestroyed && radar.parent) {
radar.rotation = 0;
}
}
});
}
if (self.level >= 4) {
// Add antenna for enhanced range
var antenna = self.attachAsset('tower_antenna', {
anchorX: 0.5,
anchorY: 1.0
});
antenna.x = 0;
antenna.y = -baseGraphics.height / 2 - 60;
self.supportStructures.push(antenna);
// Add armor plates
for (var i = 0; i < 4; i++) {
var armor = self.attachAsset('tower_armor_plate', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = i / 4 * Math.PI * 2;
armor.x = Math.cos(angle) * 40;
armor.y = Math.sin(angle) * 40;
armor.rotation = angle;
armor.alpha = 0.7;
self.supportStructures.push(armor);
}
}
if (self.level >= 5) {
// Add energy conduits
for (var i = 0; i < 3; i++) {
var conduit = self.attachAsset('tower_energy_conduit', {
anchorX: 0.5,
anchorY: 0.5
});
var angle = i / 3 * Math.PI * 2;
conduit.x = Math.cos(angle) * 50;
conduit.y = Math.sin(angle) * 50;
conduit.rotation = angle + Math.PI / 2;
// Pulsing energy effect
tween(conduit, {
alpha: 0.4
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.isDestroyed && conduit.parent) {
tween(conduit, {
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut
});
}
}
});
self.supportStructures.push(conduit);
}
}
if (self.level >= 6) {
// Max level - add ultimate enhancement visual
var enhancement = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
enhancement.width = 80;
enhancement.height = 80;
enhancement.tint = 0xFFD700;
enhancement.alpha = 0.3;
enhancement.y = 0;
// Rotating golden aura for max level
tween(enhancement, {
rotation: Math.PI * 2,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 3000,
easing: tween.linear,
onFinish: function onFinish() {
if (!self.isDestroyed && enhancement.parent) {
enhancement.rotation = 0;
enhancement.scaleX = 1.0;
enhancement.scaleY = 1.0;
}
}
});
self.supportStructures.push(enhancement);
}
// Update energy core size based on level
var coreSize = 40 + (self.level - 1) * 8;
self.energyCore.width = coreSize;
self.energyCore.height = coreSize;
// Add specialization visuals if present
if (self.specialization) {
self.updateSpecializationVisuals();
}
};
// Method to add specialization visual indicators
self.updateSpecializationVisuals = function () {
if (!self.specialization) return;
var specAssetName;
switch (self.specialization.branchType) {
case 'antiarmor':
specAssetName = 'tower_spec_armor_pierce';
break;
case 'longrange':
specAssetName = 'tower_spec_long_range';
break;
case 'nuclear':
specAssetName = 'tower_spec_nuclear';
break;
case 'chainreaction':
specAssetName = 'tower_spec_chain_reaction';
break;
default:
return;
}
var specVisual = self.attachAsset(specAssetName, {
anchorX: 0.5,
anchorY: 0.5
});
specVisual.y = -baseGraphics.height / 2 - 40;
specVisual.alpha = 0.9;
// Pulsing effect for specialization indicator
tween(specVisual, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.6
}, {
duration: 1200,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.isDestroyed && specVisual.parent) {
tween(specVisual, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.9
}, {
duration: 1200,
easing: tween.easeInOut
});
}
}
});
self.supportStructures.push(specVisual);
};
// Initialize tower visuals
self.updateTowerVisuals();
var gunContainer = new Container();
self.addChild(gunContainer);
var gunGraphics = gunContainer.attachAsset('defense', {
anchorX: 0.5,
anchorY: 0.5
});
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);
}
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++;
// Play upgrade sound
LK.getSound('towerUpgrade').play();
// Update tower visuals for new level
self.updateTowerVisuals();
// 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) {
// Tutorial action tracking
if (tutorialSystem && tutorialSystem.isActive) {
tutorialSystem.checkActionCompleted('selectTower');
}
// 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);
// Play shooting sound
LK.getSound('towerShoot').play();
// --- 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);
}
// Clean up all support structures and their tweens
if (self.supportStructures) {
for (var i = 0; i < self.supportStructures.length; i++) {
if (self.supportStructures[i]) {
tween.stop(self.supportStructures[i]);
}
}
self.supportStructures = [];
}
// Clean up gun graphics
if (self.gunGraphics) {
tween.stop(self.gunGraphics);
self.gunGraphics = null;
}
// 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;
}
// Update tower visuals to show specialization
if (self.tower.updateSpecializationVisuals) {
self.tower.updateSpecializationVisuals();
}
};
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.isWaitingForAction = false;
self.tutorialCompleted = storage.tutorialCompleted || false;
self.currentPhase = 'introduction'; // introduction, basics, combat, advanced, endgame
self.highlightOverlay = null;
self.actionArrow = null;
self.tutorialSteps = [
// INTRODUCTION PHASE
{
phase: 'introduction',
title: "Welcome to Space Tower Defense!",
text: "Commander, our space station is under attack!\nYou must defend against waves of alien invaders.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'introduction',
title: "Your Mission",
text: "Build defensive towers to stop enemies from reaching\nthe bottom of the screen and destroying our base.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'introduction',
title: "Resources Overview",
text: "Gold: Used to build and upgrade towers\nLives: Lost when enemies reach the bottom\nScore: Points earned for defeating enemies",
highlight: "ui",
action: null,
waitForAction: false
},
// BASICS PHASE
{
phase: 'basics',
title: "Building Your First Tower",
text: "Let's start by building a basic tower.\nDrag the 'Default' tower from the bottom panel.",
highlight: "sourceTowers",
action: "buildTower",
waitForAction: true
}, {
phase: 'basics',
title: "Tower Placement",
text: "Good! Towers need 2x2 space and cannot block enemy paths.\nThe green preview shows valid placement areas.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'basics',
title: "Tower Information",
text: "Tap on your tower to see its stats and upgrade options.\nTry tapping the tower you just built.",
highlight: "tower",
action: "selectTower",
waitForAction: true
}, {
phase: 'basics',
title: "Upgrading Towers",
text: "Excellent! Upgrading increases damage and fire rate.\nUpgrades get more expensive at higher levels.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'basics',
title: "Starting Combat",
text: "Now let's face some enemies!\nTap the 'Start Game' button to begin wave 1.",
highlight: "startButton",
action: "startGame",
waitForAction: true
},
// COMBAT PHASE
{
phase: 'combat',
title: "Enemy Types - Normal",
text: "The first enemies are normal type - balanced health and speed.\nYour towers will automatically target and fire at them.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'combat',
title: "Earning Gold",
text: "Great! You earn gold for each enemy defeated.\nUse this gold to build more towers and upgrades.",
highlight: "gold",
action: null,
waitForAction: false
}, {
phase: 'combat',
title: "Tower Specialization",
text: "Different tower types are effective against different enemies.\nLet's build a Rapid tower for faster firing.",
highlight: "sourceTowers",
action: "buildRapidTower",
waitForAction: true
}, {
phase: 'combat',
title: "Enemy Types - Fast",
text: "Wave 2 brings fast enemies (blue).\nRapid towers are excellent against quick targets.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'combat',
title: "Tower Range",
text: "Each tower has a different range.\nSniper towers have long range but fire slowly.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'combat',
title: "Build a Sniper Tower",
text: "Try building a Sniper tower (orange) for long-range support.\nPlace it where it can cover a large area.",
highlight: "sourceTowers",
action: "buildSniperTower",
waitForAction: true
},
// ADVANCED PHASE
{
phase: 'advanced',
title: "Special Enemy Types",
text: "As waves progress, you'll face special enemies:\n• Flying (yellow) - immune to some towers\n• Immune (red) - resistant to effects",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'advanced',
title: "Splash Damage",
text: "Splash towers (green) deal area damage.\nThey're effective against groups of enemies.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'advanced',
title: "Support Towers",
text: "Slow towers (purple) reduce enemy speed.\nPoison towers (cyan) deal damage over time.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'advanced',
title: "Hero Units",
text: "Deploy hero units for additional firepower!\nHeroes can move around and have special abilities.",
highlight: "heroPanel",
action: null,
waitForAction: false
}, {
phase: 'advanced',
title: "Deploy a Tank Hero",
text: "Try deploying a Tank hero (green).\nTap the tank button, then tap on the battlefield.",
highlight: "heroPanel",
action: "deployHero",
waitForAction: true
}, {
phase: 'advanced',
title: "Hero Abilities",
text: "Heroes gain experience and can use special abilities.\nTap on your hero to activate its ability when ready.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'advanced',
title: "Station Modules",
text: "Customize your space station with modules!\nTap the 'Customize' button to manage station upgrades.",
highlight: "stationPanel",
action: null,
waitForAction: false
},
// ENDGAME PHASE
{
phase: 'endgame',
title: "Boss Waves",
text: "Every 10th wave is a Boss wave with powerful enemies.\nBoss enemies have much more health but give more gold.",
highlight: "waveIndicator",
action: null,
waitForAction: false
}, {
phase: 'endgame',
title: "Environmental Hazards",
text: "Watch for space hazards like asteroids, nebulas, and solar flares.\nThese can help or hinder your defense strategy.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'endgame',
title: "Advanced Strategies",
text: "• Upgrade key towers to maximum level\n• Use hero abilities strategically\n• Adapt to enemy types each wave",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'endgame',
title: "Tower Specializations",
text: "At level 3, some towers can specialize:\n• Sniper: Anti-Armor or Long-Range\n• Splash: Nuclear or Chain Reaction",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'endgame',
title: "Path Branching",
text: "Enemies may take different routes through your defenses.\nSome paths are shorter but more defended.",
highlight: null,
action: null,
waitForAction: false
}, {
phase: 'endgame',
title: "Tutorial Complete!",
text: "You're ready to defend the station, Commander!\nSurvive all 50 waves to achieve victory.",
highlight: null,
action: null,
waitForAction: false
}];
var tutorialContainer = new Container();
self.addChild(tutorialContainer);
var overlay = tutorialContainer.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
overlay.width = 1900;
overlay.height = 450;
overlay.tint = 0x0A0A0A;
overlay.alpha = 0.95;
// Phase indicator
var phaseText = new Text2("", {
size: 40,
fill: 0xFFD700,
weight: 600
});
phaseText.anchor.set(0.5, 0.5);
phaseText.y = -160;
tutorialContainer.addChild(phaseText);
var titleText = new Text2("", {
size: 65,
fill: 0xFFFFFF,
weight: 800
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -100;
tutorialContainer.addChild(titleText);
var bodyText = new Text2("", {
size: 45,
fill: 0xE0E0E0,
weight: 400
});
bodyText.anchor.set(0.5, 0.5);
bodyText.y = 0;
tutorialContainer.addChild(bodyText);
// Progress indicator
var progressText = new Text2("", {
size: 35,
fill: 0xCCCCCC,
weight: 400
});
progressText.anchor.set(0.5, 0.5);
progressText.y = 80;
tutorialContainer.addChild(progressText);
var buttonContainer = new Container();
buttonContainer.y = 140;
tutorialContainer.addChild(buttonContainer);
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: 55,
fill: 0xFFFFFF,
weight: 800
});
nextText.anchor.set(0.5, 0.5);
nextButton.addChild(nextText);
nextButton.x = -250;
buttonContainer.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: 55,
fill: 0xFFFFFF,
weight: 800
});
skipText.anchor.set(0.5, 0.5);
skipButton.addChild(skipText);
skipButton.x = 0;
buttonContainer.addChild(skipButton);
var prevButton = new Container();
var prevBg = prevButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
prevBg.width = 200;
prevBg.height = 80;
prevBg.tint = 0x0066CC;
var prevText = new Text2("Back", {
size: 55,
fill: 0xFFFFFF,
weight: 800
});
prevText.anchor.set(0.5, 0.5);
prevButton.addChild(prevText);
prevButton.x = 250;
buttonContainer.addChild(prevButton);
self.createHighlight = function (target) {
if (self.highlightOverlay) {
self.highlightOverlay.parent.removeChild(self.highlightOverlay);
}
self.highlightOverlay = new Container();
var highlight = self.highlightOverlay.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.width = 300;
highlight.height = 300;
highlight.tint = 0xFFD700;
highlight.alpha = 0.3;
// Pulsing animation
tween(highlight, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.6
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (!self.highlightOverlay.isDestroyed) {
tween(highlight, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.3
}, {
duration: 1000,
easing: tween.easeInOut
});
}
}
});
game.addChild(self.highlightOverlay);
return self.highlightOverlay;
};
self.removeHighlight = function () {
if (self.highlightOverlay && self.highlightOverlay.parent) {
tween.stop(self.highlightOverlay);
self.highlightOverlay.parent.removeChild(self.highlightOverlay);
self.highlightOverlay = null;
}
};
self.showStep = function (stepIndex) {
if (stepIndex >= self.tutorialSteps.length) {
self.endTutorial();
return;
}
var step = self.tutorialSteps[stepIndex];
var phaseDisplay = step.phase.charAt(0).toUpperCase() + step.phase.slice(1) + " Phase";
phaseText.setText(phaseDisplay);
titleText.setText(step.title);
bodyText.setText(step.text);
progressText.setText("Step " + (stepIndex + 1) + " of " + self.tutorialSteps.length);
// Update button states
nextButton.visible = !step.waitForAction;
skipButton.visible = true;
prevButton.visible = stepIndex > 0;
if (stepIndex === self.tutorialSteps.length - 1) {
nextText.setText("Finish!");
skipText.setText("Start!");
}
// Handle highlights
self.removeHighlight();
if (step.highlight) {
switch (step.highlight) {
case "ui":
// Highlight UI area
if (self.highlightOverlay) {
self.highlightOverlay.x = 2048 / 2;
self.highlightOverlay.y = 100;
}
break;
case "sourceTowers":
// Highlight tower selection area
if (sourceTowers.length > 0) {
var highlight = self.createHighlight();
highlight.x = sourceTowers[0].x;
highlight.y = sourceTowers[0].y;
}
break;
case "gold":
// Highlight gold display
var highlight = self.createHighlight();
highlight.x = 2048 / 2 - 400;
highlight.y = 100;
break;
case "heroPanel":
// Highlight hero panel
var highlight = self.createHighlight();
highlight.x = 2048 / 2;
highlight.y = 200;
break;
case "stationPanel":
// Highlight station panel
var highlight = self.createHighlight();
highlight.x = 2048 - 200;
highlight.y = 250;
break;
case "waveIndicator":
// Highlight wave indicator
var highlight = self.createHighlight();
highlight.x = 2048 / 2;
highlight.y = 2732 - 80;
break;
case "startButton":
// Highlight start game button
if (waveIndicator && waveIndicator.waveMarkers.length > 0) {
var highlight = self.createHighlight();
highlight.x = waveIndicator.waveMarkers[0].x + waveIndicator.x;
highlight.y = waveIndicator.waveMarkers[0].y + waveIndicator.y;
}
break;
}
}
// Set up action waiting
if (step.waitForAction) {
self.isWaitingForAction = true;
self.expectedAction = step.action;
}
};
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 () {
if (self.isWaitingForAction) return;
self.currentStep++;
self.showStep(self.currentStep);
};
self.prevStep = function () {
if (self.currentStep > 0) {
self.currentStep--;
self.showStep(self.currentStep);
}
};
self.endTutorial = function () {
self.isActive = false;
self.visible = false;
self.removeHighlight();
storage.tutorialCompleted = true;
self.tutorialCompleted = true;
// Auto-start the game after tutorial
if (waveIndicator && !waveIndicator.gameStarted) {
waveIndicator.gameStarted = true;
currentWave = 0;
waveTimer = nextWaveTime;
}
var notification = game.addChild(new Notification("Tutorial completed! Good luck, Commander!"));
notification.x = 2048 / 2;
notification.y = grid.height - 100;
};
self.checkActionCompleted = function (action) {
if (!self.isWaitingForAction || self.expectedAction !== action) return;
self.isWaitingForAction = false;
self.expectedAction = null;
// Show next button and auto-advance after delay
nextButton.visible = true;
LK.setTimeout(function () {
if (self.isActive && !self.isWaitingForAction) {
self.nextStep();
}
}, 1500);
};
nextButton.down = function () {
self.nextStep();
};
skipButton.down = function () {
self.endTutorial();
};
prevButton.down = function () {
self.prevStep();
};
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: 0xE0E0E0,
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;
// Tutorial action tracking
if (tutorialSystem && tutorialSystem.isActive) {
tutorialSystem.checkActionCompleted('startGame');
}
var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
// Play wave start sound
LK.getSound('waveStart').play();
}
};
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 = 3;
waveTypeShadow.y = 3;
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
****/
// Add background image
// Base tower structures for each type
// Tower gun/weapon parts - Level 1
// Tower gun/weapon parts - Level 2
// Tower gun/weapon parts - Level 3
// Tower gun/weapon parts - Level 4
// Tower gun/weapon parts - Level 5
// Tower gun/weapon parts - Level 6 (Max Level)
// Support structures and details
// Visual indicators
// Specialization visual elements
var backgroundImage = game.attachAsset('spaceBackground', {
anchorX: 0.5,
anchorY: 0.5
});
backgroundImage.x = 2048 / 2;
backgroundImage.y = 2732 / 2;
backgroundImage.alpha = 0.8;
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
// Branching routes system variables
var pathJunctions = [];
var alternativePaths = [];
var routeChoiceInfluences = [];
var pathDifficulties = {};
// 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: []
};
// Initialize route visualizer
var routeVisualizer = new RouteVisualizer();
game.addChild(routeVisualizer);
// Initialize branching routes system
function initializeBranchingRoutes() {
// Create main junction points in the middle area
var mainJunction = new PathJunction(grid.x + 12 * CELL_SIZE, grid.y + 15 * CELL_SIZE, [{
targetX: 10,
targetY: 22,
// Left branch - shorter but more defended
isShortPath: true,
pathLength: 8,
towerDensity: 0,
currentEnemyCount: 0,
recentSwarmTraffic: 0,
hasAntiAir: false,
difficulty: 20
}, {
targetX: 14,
targetY: 22,
// Right branch - longer but less defended
isShortPath: false,
pathLength: 12,
towerDensity: 0,
currentEnemyCount: 0,
recentSwarmTraffic: 0,
hasAntiAir: false,
difficulty: 15
}]);
// Secondary junction for more complex routing
var secondaryJunction = new PathJunction(grid.x + 8 * CELL_SIZE, grid.y + 12 * CELL_SIZE, [{
targetX: 6,
targetY: 18,
// Far left route
isShortPath: false,
pathLength: 15,
towerDensity: 0,
currentEnemyCount: 0,
recentSwarmTraffic: 0,
hasAntiAir: false,
difficulty: 25
}, {
targetX: 12,
targetY: 15,
// Center route to main junction
isShortPath: true,
pathLength: 6,
towerDensity: 0,
currentEnemyCount: 0,
recentSwarmTraffic: 0,
hasAntiAir: false,
difficulty: 30
}]);
game.addChild(mainJunction);
game.addChild(secondaryJunction);
pathJunctions.push(mainJunction);
pathJunctions.push(secondaryJunction);
var notification = game.addChild(new Notification("🔀 Multi-path routing system activated! 🔀"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
// Initialize the branching system after a short delay
LK.setTimeout(function () {
initializeBranchingRoutes();
}, 2000);
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();
// Play tower placement sound
LK.getSound('towerPlace').play();
// Tutorial action tracking
if (tutorialSystem && tutorialSystem.isActive) {
if (towerType === 'default') {
tutorialSystem.checkActionCompleted('buildTower');
} else if (towerType === 'rapid') {
tutorialSystem.checkActionCompleted('buildRapidTower');
} else if (towerType === 'sniper') {
tutorialSystem.checkActionCompleted('buildSniperTower');
}
}
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;
// Tutorial action tracking
if (tutorialSystem && tutorialSystem.isActive) {
tutorialSystem.checkActionCompleted('deployHero');
}
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 = 2048 - 200; // Position on right side
stationPanel.y = 200; // Below the score display which is at y=50
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);
// Start background music
LK.playMusic('bgMusic');
// Show tutorial automatically for new players
var hasSeenTutorial = storage.tutorialCompleted || false;
if (!hasSeenTutorial) {
tutorialSystem.startTutorial();
}
// Add tutorial restart button for experienced players
var tutorialRestartButton = new Container();
var restartBg = tutorialRestartButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
restartBg.width = 200;
restartBg.height = 60;
restartBg.tint = 0x4A90E2;
restartBg.alpha = 0.9;
var restartText = new Text2("Tutorial", {
size: 35,
fill: 0xFFFFFF,
weight: 600
});
restartText.anchor.set(0.5, 0.5);
tutorialRestartButton.addChild(restartText);
tutorialRestartButton.x = 150;
tutorialRestartButton.y = 2732 - 50;
tutorialRestartButton.down = function () {
if (!tutorialSystem.isActive) {
tutorialSystem.startTutorial();
}
};
game.addChild(tutorialRestartButton);
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;
// Play boss spawn sound
LK.getSound('bossSpawn').play();
}
// 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
}
}
// Bonus gold for enemies who took difficult routes
if (enemy.chosenBranch && enemy.chosenBranch.difficulty > 50) {
var routeBonus = Math.floor(goldEarned * 0.3);
goldEarned += routeBonus;
if (routeBonus > 0) {
var notification = game.addChild(new Notification("Route bonus: +" + routeBonus + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 200;
}
}
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) {
// Play game over sound
LK.getSound('gameOver').play();
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 path junctions and route analysis
for (var i = 0; i < pathJunctions.length; i++) {
if (pathJunctions[i].parent) {
pathJunctions[i].update();
}
}
// 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) {
// Play victory sound
LK.getSound('victory').play();
LK.showYouWin();
}
}; ===================================================================
--- original.js
+++ change.js
@@ -1345,65 +1345,10 @@
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
// Update shadow position if this is a flying enemy
return false;
}
- // Handle normal pathfinding enemies with enhanced path system
+ // Handle normal pathfinding enemies
if (!enemy.currentTarget) {
- // Check current path type for special behavior
- var pathType = pathSystem ? pathSystem.getPathType(enemy.cellX, enemy.cellY) : 'normal';
- var pathDifficulty = pathSystem ? pathSystem.getPathDifficulty(enemy.cellX, enemy.cellY) : 1;
- // Apply path-specific effects
- switch (pathType) {
- case 'danger':
- // Dangerous paths make enemies more vulnerable
- if (!enemy.pathEffectApplied) {
- enemy.originalSpeed = enemy.originalSpeed || enemy.speed;
- enemy.speed *= 1.2; // 20% faster but exposed
- enemy.pathEffectApplied = true;
- // Visual danger effect
- if (enemy.children[0]) {
- tween(enemy.children[0], {
- tint: 0xff4444
- }, {
- duration: 200,
- easing: tween.easeOut
- });
- }
- }
- break;
- case 'safe':
- // Safe paths provide some protection
- if (!enemy.pathEffectApplied) {
- enemy.originalSpeed = enemy.originalSpeed || enemy.speed;
- enemy.speed *= 0.9; // 10% slower but safer
- enemy.pathEffectApplied = true;
- // Visual safety effect
- if (enemy.children[0]) {
- tween(enemy.children[0], {
- tint: 0x44ff44
- }, {
- duration: 200,
- easing: tween.easeOut
- });
- }
- }
- break;
- case 'bridge':
- // Bridge paths are elevated - flying enemies get speed boost
- if (enemy.isFlying && !enemy.pathEffectApplied) {
- enemy.originalSpeed = enemy.originalSpeed || enemy.speed;
- enemy.speed *= 1.3; // 30% faster on bridges
- enemy.pathEffectApplied = true;
- }
- break;
- case 'tunnel':
- // Tunnel paths hide enemies from some tower types
- if (!enemy.pathEffectApplied) {
- enemy.tunnelStealth = true; // Harder for towers to target
- enemy.pathEffectApplied = true;
- }
- break;
- }
// Check if enemy is at a junction
if (cell && cell.isJunction && !enemy.chosenBranch) {
var junction = cell.junction;
var chosenBranch = junction.choosePath(enemy);
@@ -2183,566 +2128,8 @@
Container.prototype.destroy.call(self);
};
return self;
});
-var PathSystem = Container.expand(function (grid) {
- var self = Container.call(this);
- self.grid = grid;
- self.pathSegments = [];
- self.visualElements = [];
- self.junctions = [];
- self.alternativeRoutes = [];
- self.isDestroyed = false;
- // Create main path layout with creative routing
- self.createPathLayout = function () {
- // Clear existing visuals
- self.clearPathVisuals();
- // Define the creative path structure
- var pathLayout = [
- // Entry area - funnel effect
- {
- type: 'straight',
- startX: 9,
- startY: 0,
- endX: 15,
- endY: 4,
- width: 7,
- difficulty: 1
- },
- // First major junction - choice between speed vs safety
- {
- type: 'junction',
- x: 12,
- y: 6,
- branches: [{
- name: 'speed',
- targetX: 8,
- targetY: 12,
- difficulty: 2,
- bonus: 'fast'
- }, {
- name: 'safety',
- targetX: 16,
- targetY: 12,
- difficulty: 1,
- bonus: 'safe'
- }]
- },
- // Speed route - narrow and dangerous but faster
- {
- type: 'narrow',
- startX: 6,
- startY: 8,
- endX: 8,
- endY: 16,
- width: 3,
- difficulty: 4
- },
- // Safety route - wider but longer with defensive positions
- {
- type: 'wide',
- startX: 14,
- startY: 8,
- endX: 18,
- endY: 16,
- width: 5,
- difficulty: 2
- },
- // Bridge section - elevated path over dangerous terrain
- {
- type: 'bridge',
- startX: 6,
- startY: 16,
- endX: 12,
- endY: 18,
- difficulty: 3
- },
- // Tunnel section - underground passage
- {
- type: 'tunnel',
- startX: 14,
- startY: 16,
- endX: 16,
- endY: 20,
- difficulty: 2
- },
- // Second junction - tactical choice
- {
- type: 'junction',
- x: 12,
- y: 20,
- branches: [{
- name: 'direct',
- targetX: 12,
- targetY: 26,
- difficulty: 3,
- bonus: 'direct'
- }, {
- name: 'flank',
- targetX: 8,
- targetY: 24,
- difficulty: 2,
- bonus: 'flank'
- }, {
- name: 'siege',
- targetX: 16,
- targetY: 24,
- difficulty: 4,
- bonus: 'siege'
- }]
- },
- // Final convergence - all paths meet before exit
- {
- type: 'convergence',
- startX: 8,
- startY: 24,
- endX: 16,
- endY: 28,
- width: 9,
- difficulty: 2
- }];
- // Build visual representation of paths
- for (var i = 0; i < pathLayout.length; i++) {
- self.buildPathSection(pathLayout[i]);
- }
- // Add decorative elements
- self.addPathDecorations();
- };
- self.buildPathSection = function (section) {
- switch (section.type) {
- case 'straight':
- self.createStraightPath(section);
- break;
- case 'junction':
- self.createJunctionPath(section);
- break;
- case 'narrow':
- self.createNarrowPath(section);
- break;
- case 'wide':
- self.createWidePath(section);
- break;
- case 'bridge':
- self.createBridgePath(section);
- break;
- case 'tunnel':
- self.createTunnelPath(section);
- break;
- case 'convergence':
- self.createConvergencePath(section);
- break;
- }
- };
- self.createStraightPath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create path segments with width
- for (var w = -Math.floor(section.width / 2); w <= Math.floor(section.width / 2); w++) {
- self.createPathSegment(x + w, y, 'straight', section.difficulty);
- }
- // Add directional arrows every few segments
- if (i % 3 === 0) {
- self.createPathArrow(x, y, deltaX, deltaY);
- }
- }
- };
- self.createJunctionPath = function (section) {
- // Create central junction node
- var junction = self.createJunctionNode(section.x, section.y, section.branches);
- self.junctions.push(junction);
- // Create branch indicators
- for (var i = 0; i < section.branches.length; i++) {
- var branch = section.branches[i];
- self.createBranchIndicator(section.x, section.y, branch);
- }
- };
- self.createNarrowPath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create narrow path (width 3) with danger indicators
- for (var w = -1; w <= 1; w++) {
- var segmentType = Math.abs(w) === 1 ? 'danger' : 'path';
- self.createPathSegment(x + w, y, segmentType, section.difficulty);
- }
- }
- };
- self.createWidePath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create wide path (width 5) with safe zones
- for (var w = -2; w <= 2; w++) {
- var segmentType = Math.abs(w) === 2 ? 'safe' : 'path';
- self.createPathSegment(x + w, y, segmentType, section.difficulty);
- }
- }
- };
- self.createBridgePath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create elevated bridge segments
- var bridge = self.createBridgeSegment(x, y, section.difficulty);
- self.visualElements.push(bridge);
- }
- };
- self.createTunnelPath = function (section) {
- var deltaX = section.endX - section.startX;
- var deltaY = section.endY - section.startY;
- var distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- var steps = Math.floor(distance);
- for (var i = 0; i <= steps; i++) {
- var progress = i / steps;
- var x = Math.round(section.startX + deltaX * progress);
- var y = Math.round(section.startY + deltaY * progress);
- // Create underground tunnel segments
- var tunnel = self.createTunnelSegment(x, y, section.difficulty);
- self.visualElements.push(tunnel);
- }
- };
- self.createConvergencePath = function (section) {
- // Create funnel effect where all paths merge
- var centerX = (section.startX + section.endX) / 2;
- var centerY = (section.startY + section.endY) / 2;
- // Create converging segments from multiple directions
- var convergencePoints = [{
- x: section.startX,
- y: section.startY
- }, {
- x: section.startX + 2,
- y: section.startY
- }, {
- x: section.startX + 4,
- y: section.startY
- }, {
- x: section.endX - 4,
- y: section.startY
- }, {
- x: section.endX - 2,
- y: section.startY
- }, {
- x: section.endX,
- y: section.startY
- }];
- for (var i = 0; i < convergencePoints.length; i++) {
- var point = convergencePoints[i];
- var deltaX = centerX - point.x;
- var deltaY = centerY - point.y;
- var steps = Math.max(Math.abs(deltaX), Math.abs(deltaY));
- for (var j = 0; j <= steps; j++) {
- var progress = j / steps;
- var x = Math.round(point.x + deltaX * progress);
- var y = Math.round(point.y + deltaY * progress);
- self.createPathSegment(x, y, 'convergence', section.difficulty);
- }
- }
- // Create final exit path
- for (var y = Math.round(centerY); y <= section.endY; y++) {
- for (var x = section.startX; x <= section.endX; x++) {
- self.createPathSegment(x, y, 'exit', section.difficulty);
- }
- }
- };
- self.createPathSegment = function (gridX, gridY, segmentType, difficulty) {
- var cell = self.grid.getCell(gridX, gridY);
- if (!cell) return;
- // Set cell as walkable path
- cell.type = 0;
- cell.pathType = segmentType;
- cell.pathDifficulty = difficulty;
- // Create visual representation
- var segment = new Container();
- segment.x = self.grid.x + gridX * CELL_SIZE;
- segment.y = self.grid.y + gridY * CELL_SIZE;
- var assetName = 'pathSegment';
- var tint = 0x2a4d3a;
- switch (segmentType) {
- case 'danger':
- assetName = 'pathDanger';
- tint = 0x8b2635;
- break;
- case 'safe':
- assetName = 'pathSafe';
- tint = 0x4a6741;
- break;
- case 'bridge':
- assetName = 'pathBridge';
- tint = 0x3d5a47;
- break;
- case 'tunnel':
- assetName = 'pathTunnel';
- tint = 0x2d4235;
- break;
- case 'convergence':
- tint = 0x4a7c59;
- break;
- case 'exit':
- tint = 0x5c8a6b;
- break;
- }
- var segmentGraphics = segment.attachAsset(assetName, {
- anchorX: 0.5,
- anchorY: 0.5
- });
- segmentGraphics.tint = tint;
- segmentGraphics.alpha = 0.7;
- // Add difficulty-based visual effects
- if (difficulty >= 3) {
- // High difficulty - add warning pulse
- tween(segmentGraphics, {
- alpha: 0.4
- }, {
- duration: 800,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!self.isDestroyed) {
- tween(segmentGraphics, {
- alpha: 0.7
- }, {
- duration: 800,
- easing: tween.easeInOut
- });
- }
- }
- });
- }
- self.addChild(segment);
- self.visualElements.push(segment);
- };
- self.createJunctionNode = function (gridX, gridY, branches) {
- var junction = new Container();
- junction.x = self.grid.x + gridX * CELL_SIZE;
- junction.y = self.grid.y + gridY * CELL_SIZE;
- var junctionGraphics = junction.attachAsset('pathJunctionNode', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- junctionGraphics.tint = 0x4a7c59;
- junctionGraphics.alpha = 0.8;
- // Pulsing effect for junctions
- tween(junctionGraphics, {
- scaleX: 1.2,
- scaleY: 1.2,
- alpha: 1.0
- }, {
- duration: 1000,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!self.isDestroyed) {
- tween(junctionGraphics, {
- scaleX: 1.0,
- scaleY: 1.0,
- alpha: 0.8
- }, {
- duration: 1000,
- easing: tween.easeInOut
- });
- }
- }
- });
- // Add branch count indicator
- var branchCount = new Text2(branches.length.toString(), {
- size: 30,
- fill: 0xFFFFFF,
- weight: 800
- });
- branchCount.anchor.set(0.5, 0.5);
- junction.addChild(branchCount);
- self.addChild(junction);
- self.visualElements.push(junction);
- return junction;
- };
- self.createBranchIndicator = function (junctionX, junctionY, branch) {
- var indicator = new Container();
- var angle = Math.atan2(branch.targetY - junctionY, branch.targetX - junctionX);
- var distance = 80;
- indicator.x = self.grid.x + junctionX * CELL_SIZE + Math.cos(angle) * distance;
- indicator.y = self.grid.y + junctionY * CELL_SIZE + Math.sin(angle) * distance;
- var indicatorGraphics = indicator.attachAsset('pathArrow', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- indicatorGraphics.rotation = angle;
- // Color code by difficulty and bonus
- switch (branch.bonus) {
- case 'fast':
- indicatorGraphics.tint = 0x00aaff;
- break;
- case 'safe':
- indicatorGraphics.tint = 0x4a6741;
- break;
- case 'direct':
- indicatorGraphics.tint = 0xff6600;
- break;
- case 'flank':
- indicatorGraphics.tint = 0x9900ff;
- break;
- case 'siege':
- indicatorGraphics.tint = 0xff0000;
- break;
- default:
- indicatorGraphics.tint = 0x7da085;
- }
- self.addChild(indicator);
- self.visualElements.push(indicator);
- };
- self.createPathArrow = function (gridX, gridY, deltaX, deltaY) {
- var arrow = new Container();
- arrow.x = self.grid.x + gridX * CELL_SIZE;
- arrow.y = self.grid.y + gridY * CELL_SIZE;
- var arrowGraphics = arrow.attachAsset('pathArrow', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- arrowGraphics.rotation = Math.atan2(deltaY, deltaX);
- arrowGraphics.tint = 0x7da085;
- arrowGraphics.alpha = 0.6;
- arrowGraphics.width = 30;
- arrowGraphics.height = 30;
- self.addChild(arrow);
- self.visualElements.push(arrow);
- };
- self.createBridgeSegment = function (gridX, gridY, difficulty) {
- var bridge = new Container();
- bridge.x = self.grid.x + gridX * CELL_SIZE;
- bridge.y = self.grid.y + gridY * CELL_SIZE;
- var bridgeGraphics = bridge.attachAsset('pathBridge', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- bridgeGraphics.tint = 0x3d5a47;
- bridgeGraphics.alpha = 0.8;
- // Add elevation effect
- bridge.y -= 5; // Slightly elevated
- self.addChild(bridge);
- return bridge;
- };
- self.createTunnelSegment = function (gridX, gridY, difficulty) {
- var tunnel = new Container();
- tunnel.x = self.grid.x + gridX * CELL_SIZE;
- tunnel.y = self.grid.y + gridY * CELL_SIZE;
- var tunnelGraphics = tunnel.attachAsset('pathTunnel', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- tunnelGraphics.tint = 0x2d4235;
- tunnelGraphics.alpha = 0.6;
- // Add underground effect
- tunnel.y += 3; // Slightly below surface
- self.addChild(tunnel);
- return tunnel;
- };
- self.addPathDecorations = function () {
- // Add checkpoints along major path sections
- var checkpointPositions = [{
- x: 12,
- y: 8
- }, {
- x: 9,
- y: 14
- }, {
- x: 15,
- y: 14
- }, {
- x: 12,
- y: 22
- }];
- for (var i = 0; i < checkpointPositions.length; i++) {
- var pos = checkpointPositions[i];
- var checkpoint = new Container();
- checkpoint.x = self.grid.x + pos.x * CELL_SIZE;
- checkpoint.y = self.grid.y + pos.y * CELL_SIZE;
- var checkpointGraphics = checkpoint.attachAsset('pathCheckpoint', {
- anchorX: 0.5,
- anchorY: 0.5
- });
- checkpointGraphics.tint = 0x5c8a6b;
- checkpointGraphics.alpha = 0.7;
- // Checkpoint pulse animation
- tween(checkpointGraphics, {
- scaleX: 1.3,
- scaleY: 1.3,
- alpha: 1.0
- }, {
- duration: 1500,
- easing: tween.easeInOut,
- onFinish: function onFinish() {
- if (!self.isDestroyed) {
- tween(checkpointGraphics, {
- scaleX: 1.0,
- scaleY: 1.0,
- alpha: 0.7
- }, {
- duration: 1500,
- easing: tween.easeInOut
- });
- }
- }
- });
- self.addChild(checkpoint);
- self.visualElements.push(checkpoint);
- }
- };
- self.getPathDifficulty = function (gridX, gridY) {
- var cell = self.grid.getCell(gridX, gridY);
- return cell && cell.pathDifficulty ? cell.pathDifficulty : 1;
- };
- self.getPathType = function (gridX, gridY) {
- var cell = self.grid.getCell(gridX, gridY);
- return cell && cell.pathType ? cell.pathType : 'normal';
- };
- self.clearPathVisuals = function () {
- for (var i = 0; i < self.visualElements.length; i++) {
- if (self.visualElements[i].parent) {
- self.visualElements[i].parent.removeChild(self.visualElements[i]);
- }
- }
- self.visualElements = [];
- };
- self.update = function () {
- // Update junction states and path conditions
- for (var i = 0; i < self.junctions.length; i++) {
- var junction = self.junctions[i];
- // Junction could show current traffic or difficulty
- }
- };
- self.destroy = function () {
- if (self.isDestroyed) return;
- self.isDestroyed = true;
- self.clearPathVisuals();
- self.pathSegments = [];
- self.junctions = [];
- self.alternativeRoutes = [];
- tween.stop(self);
- if (self.parent) {
- self.parent.removeChild(self);
- }
- Container.prototype.destroy.call(self);
- };
- return self;
-});
var RouteVisualizer = Container.expand(function () {
var self = Container.call(this);
self.pathLines = [];
self.isDestroyed = false;
@@ -3749,8 +3136,13 @@
}
});
// Method to update tower visual appearance based on level
self.updateTowerVisuals = function () {
+ // Ensure gunContainer exists before trying to use it
+ if (!gunContainer) {
+ console.warn("gunContainer not found, skipping visual update");
+ return;
+ }
// Clear existing gun graphics
if (self.gunGraphics && self.gunGraphics.parent) {
gunContainer.removeChild(self.gunGraphics);
}
@@ -3946,8 +3338,14 @@
self.supportStructures.push(specVisual);
};
// Initialize tower visuals
self.updateTowerVisuals();
+ var gunContainer = new Container();
+ self.addChild(gunContainer);
+ var gunGraphics = gunContainer.attachAsset('defense', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
var levelIndicators = [];
var maxDots = self.maxLevel;
var dotSpacing = baseGraphics.width / (maxDots + 1);
var dotSize = CELL_SIZE / 6;
@@ -3971,14 +3369,8 @@
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];
@@ -4195,18 +3587,12 @@
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 stealth effects - nebula and tunnel stealth
- var targetingChance = 1.0;
- if (enemy.nebulaStealthed) {
- targetingChance *= 0.3; // Nebula stealth
- }
- if (enemy.tunnelStealth) {
- targetingChance *= 0.6; // Tunnel stealth - less severe than nebula
- }
+ // 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 effects
+ 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
@@ -6956,12 +6342,8 @@
enemyLayer.addChild(enemyLayerTop);
var grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * 4;
-// Initialize creative path system
-var pathSystem = new PathSystem(grid);
-pathSystem.createPathLayout();
-debugLayer.addChild(pathSystem);
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
game.addChild(debugLayer);
@@ -7786,12 +7168,8 @@
break;
}
}
}
- // Update path system
- if (pathSystem && pathSystem.update) {
- pathSystem.update();
- }
// Update path junctions and route analysis
for (var i = 0; i < pathJunctions.length; i++) {
if (pathJunctions[i].parent) {
pathJunctions[i].update();
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