User prompt
Revert all assets back how they were
User prompt
Extend the enemy system for level-specific behaviors
User prompt
Please fix the bug: 'waterTowerButton is not defined' in or related to this line: 'var towerMapping = {' Line Number: 958
User prompt
Please fix the bug: 'waterTowerButton is not defined' in or related to this line: 'var towerMapping = {' Line Number: 958
User prompt
Create level transition system ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Extend the asset management system for level-specific content
User prompt
Create global LevelManager instance and integrate with existing systems
User prompt
Create a LevelManager class or consolidated level management functions
User prompt
Add level state management to the existing game state system"**
User prompt
Add level state management to the existing game state system"** - Extend GAME_STATE to include current level tracking, level progression flags, and level completion status - Add level-specific variables like unlocked towers, level score, and completion time - Ensure the level state integrates seamlessly with the existing wave and player state management - Create level state reset functionality for when players restart levels ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Create level metadata like names, unlock requirements, and completion rewards - Ensure the configuration follows the existing pattern used for towers, enemies, and waves
User prompt
Define level-specific configurations including backgrounds, enemy pools, tower availability, wave counts, and special mechanics
User prompt
Create a level configuration system"** - Extend the existing GAME_CONFIG object to include a `levels` section
User prompt
What steps should I take now to remake level1
User prompt
Fix this please
User prompt
Ensure all global variables are properly organized and eliminate any remaining code duplication
User prompt
Ensure all global variables are properly organized and eliminate any remaining code duplication
User prompt
Review and optimize the game update loop to reduce redundant calculations and improve performance
User prompt
Remove redundant collision tracking variables and simplify the collision detection logic
User prompt
Consolidate asset initialization by grouping similar assets and using loops where appropriate
User prompt
Remove all debug console.log statements and unused variables throughout the codebase
User prompt
Extract common tower upgrade and interaction logic into reusable methods
User prompt
Merge similar click handlers and event listeners that follow the same pattern into consolidated functions
User prompt
Consolidate enemy asset management by extracting common cleanup and recreation logic from setAssetType methods
User prompt
Create a unified showWaveTransition() function instead of repeating the wave asset display logic
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
// Cutscene class
var Cutscene = Container.expand(function () {
var self = Container.call(this);
// Properties
self.scenes = [];
self.currentSceneIndex = 0;
self.isPlaying = false;
// Add a scene to the cutscene
self.addScene = function (sceneData) {
self.scenes.push(sceneData);
};
// Start playing the cutscene
self.play = function () {
if (self.scenes.length === 0) return;
self.isPlaying = true;
self.currentSceneIndex = 0;
self.playCurrentScene();
};
// Play the current scene
self.playCurrentScene = function () {
if (self.currentSceneIndex >= self.scenes.length) {
self.finish();
return;
}
var scene = self.scenes[self.currentSceneIndex];
if (scene.onStart) {
scene.onStart();
}
// Auto-advance to next scene after duration
if (scene.duration) {
LK.setTimeout(function () {
self.nextScene();
}, scene.duration);
}
};
// Move to next scene
self.nextScene = function () {
var scene = self.scenes[self.currentSceneIndex];
if (scene.onEnd) {
scene.onEnd();
}
self.currentSceneIndex++;
self.playCurrentScene();
};
// Skip to specific scene
self.skipToScene = function (index) {
if (index >= 0 && index < self.scenes.length) {
self.currentSceneIndex = index;
self.playCurrentScene();
}
};
// Finish cutscene
self.finish = function () {
self.isPlaying = false;
if (self.onFinish) {
self.onFinish();
}
};
// Stop cutscene
self.stop = function () {
self.isPlaying = false;
// Stop any running tweens or timeouts
// Stop tweens on cutscene assets if they exist
if (self.intro2Asset) {
tween.stop(self.intro2Asset);
}
if (self.cellAsset) {
tween.stop(self.cellAsset);
}
if (self.skipIntroButton) {
tween.stop(self.skipIntroButton);
}
if (self.story1Asset) {
tween.stop(self.story1Asset);
}
if (self.story2Asset) {
tween.stop(self.story2Asset);
}
if (self.story3Asset) {
tween.stop(self.story3Asset);
}
if (self.story4Asset) {
tween.stop(self.story4Asset);
}
};
return self;
});
// Defense class
var Defense = Container.expand(function () {
var self = Container.call(this);
var defenseGraphics = self.attachAsset('defense', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
// Defense logic goes here
};
});
// Enemy class for tower defense
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Default to Probedroid, but can be changed
self.assetType = 'Probedroid';
var enemyGraphics = self.attachAsset(self.assetType, {
anchorX: 0.5,
anchorY: 0.5
});
// Enemy properties
self.health = 100;
self.maxHealth = 100;
self.speed = 1.5;
self.targetX = -100; // Move to left side of screen
self.lastX = self.x;
self.lastCollisionState = false; // Unified collision tracking
// Health bar graphics - positioned above enemy regardless of asset type
self.healthBarBackground = self.attachAsset('healthBarBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -80
});
self.healthBarForeground = self.attachAsset('healthBarForeground', {
anchorX: 0,
anchorY: 0.5,
x: -30,
y: -80
});
self.update = function () {
// Cache frequently used values
var currentTick = LK.ticks;
var speedMultiplier = typeof gameSpeedMultiplier !== 'undefined' ? gameSpeedMultiplier : 1;
var towersLength = towers.length;
// Store last position and check current collision state
self.lastX = self.x;
var currentCollisionState = false;
// Optimize collision detection - only check every other frame for non-critical enemies
var shouldCheckCollision = self.isUFOBoss || currentTick % 2 === 0;
if (shouldCheckCollision) {
// Check collision with towers on same guideline that are directly to the left
for (var i = 0; i < towersLength; i++) {
var tower = towers[i];
// Only check towers on the same guideline that are positioned to the left of the enemy
if (tower.guidelineIndex === self.guidelineIndex && tower.x < self.x && self.intersects(tower)) {
currentCollisionState = true;
break;
}
}
}
// Move towards target (right to left) only if not colliding with tower
if (self.x > self.targetX && !currentCollisionState) {
self.x -= self.speed * speedMultiplier;
}
// Move towards the center of the assigned guideline
if (self.baseY !== undefined) {
var targetY = self.baseY;
var yDifference = targetY - self.y;
// Move towards guideline center with a fraction of movement speed
if (Math.abs(yDifference) > 2) {
self.y += yDifference * 0.1; // Adjust 0.1 to control how quickly enemies center on guidelines
}
}
// Update health bar only when health changes or every 10th frame
var healthPercentage = self.health / self.maxHealth;
if (self.lastHealthPercentage === undefined || self.lastHealthPercentage !== healthPercentage || currentTick % 10 === 0) {
self.healthBarForeground.scaleX = healthPercentage;
// Change health bar color based on health percentage
if (healthPercentage > 0.6) {
self.healthBarForeground.tint = 0x00ff00; // Green
} else if (healthPercentage > 0.3) {
self.healthBarForeground.tint = 0xffff00; // Yellow
} else {
self.healthBarForeground.tint = 0xff0000; // Red
}
self.lastHealthPercentage = healthPercentage;
}
// Consolidated enemy shooting logic using parameters
self.performEnemyShooting = function (config) {
// Initialize shooting properties if not set
if (self.lastShotFrame === undefined) {
self.lastShotFrame = 0;
self.shootingRange = config.range;
self.fireRate = config.fireRate;
}
// Handle special animations (like wheel rotation for robots)
if (config.specialAnimation && !self.collidingWithTower) {
config.specialAnimation();
}
// Find nearest tower to shoot at
var nearestTower = null;
var nearestDistance = Infinity;
for (var t = 0; t < towers.length; t++) {
var tower = towers[t];
// Only target towers on the same guideline
if (tower.guidelineIndex === self.guidelineIndex) {
var dx = tower.x - self.x;
var dy = tower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if tower is within shooting range and closer than current target
if (distance <= self.shootingRange && distance < nearestDistance) {
nearestDistance = distance;
nearestTower = tower;
}
}
}
// Shoot at nearest tower if in range and fire rate allows
var adjustedFireRate = typeof gameSpeedMultiplier !== 'undefined' ? self.fireRate / gameSpeedMultiplier : self.fireRate;
if (nearestTower && LK.ticks - self.lastShotFrame >= adjustedFireRate) {
// Create projectile
var projectile = game.addChild(LK.getAsset(config.projectileAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
tint: config.projectileTint || 0xFFFFFF
}));
// Calculate direction to target tower
var dx = nearestTower.x - self.x;
var dy = nearestTower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Animate projectile to target tower
tween(projectile, {
x: nearestTower.x,
y: nearestTower.y
}, {
duration: distance / config.projectileSpeed,
easing: tween.linear,
onFinish: function onFinish() {
projectile.destroy();
// Deal damage to target tower
if (nearestTower && nearestTower.parent) {
nearestTower.health -= config.damage;
// Flash tower to show damage
tween(nearestTower, {
tint: config.damageFlashColor
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(nearestTower, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Check if tower is destroyed
if (nearestTower.health <= 0) {
// Create explosion effect at tower position
var explosionEffect = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: nearestTower.x,
y: nearestTower.y,
alpha: 1,
scaleX: 0.5,
scaleY: 0.5
}));
// Play explosion sound
LK.getSound('Explosion').play();
// Animate explosion with scale and fade out
tween(explosionEffect, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
// Remove tower from towers array
for (var k = towers.length - 1; k >= 0; k--) {
if (towers[k] === nearestTower) {
towers.splice(k, 1);
break;
}
}
// Destroy tower
nearestTower.destroy();
}
}
}
});
self.lastShotFrame = LK.ticks;
}
};
// SpaceDrone shooting logic
if (self.assetType === 'SpaceDrone') {
self.performEnemyShooting({
range: 400,
fireRate: 45,
projectileAsset: 'bullet',
projectileSpeed: 3,
projectileTint: 0xFF0000,
damage: 25,
damageFlashColor: 0xFF0000
});
}
// Robot shooting logic
if (self.assetType === 'Robot') {
self.performEnemyShooting({
range: 450,
fireRate: 60,
projectileAsset: 'Laserfire',
projectileSpeed: 4,
damage: 35,
damageFlashColor: 0x00FFFF,
specialAnimation: function specialAnimation() {
if (self.leftWheel && self.rightWheel) {
self.leftWheel.rotation -= 0.15;
self.rightWheel.rotation -= 0.15;
}
}
});
}
// UFO boss special behavior - simplified sequential abduction
if (self.isUFOBoss) {
// Initialize UFO properties if not set
if (!self.movementInitialized) {
self.movementInitialized = true;
self.currentTargetTower = null;
self.movingToTower = false;
self.abductionPhase = 'seeking'; // seeking, hovering, abducting, moving_to_next
}
// Phase 1: Hover down from top if not already done
if (!self.hasHoveredDown) {
if (self.y < 2732 / 2) {
self.y += self.speed * (typeof gameSpeedMultiplier !== 'undefined' ? gameSpeedMultiplier : 1);
} else {
self.hasHoveredDown = true;
}
return;
}
// Phase 2: Sequential tower abduction
if (towers.length > 0) {
// Find next tower to abduct if we don't have a target
if (!self.currentTargetTower && self.abductionPhase === 'seeking') {
// Find closest tower
var closestTower = null;
var closestDistance = Infinity;
for (var t = 0; t < towers.length; t++) {
var tower = towers[t];
var dx = tower.x - self.x;
var dy = tower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestTower = tower;
}
}
if (closestTower) {
self.currentTargetTower = closestTower;
self.abductionPhase = 'moving_to_tower';
// Move to hover higher above the tower
tween(self, {
x: closestTower.x,
y: closestTower.y - 350 // Hover much higher above tower
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.abductionPhase = 'hovering';
// Start abduction after brief hover
LK.setTimeout(function () {
if (self.currentTargetTower && self.currentTargetTower.parent) {
self.abductionPhase = 'abducting';
// Create tractor beam that starts small and expands to engulf tower
self.tractorBeam = game.addChild(LK.getAsset('Blowing', {
anchorX: 0.5,
anchorY: 0.0,
x: self.currentTargetTower.x,
y: self.y + 50,
alpha: 0,
scaleX: 0.1,
scaleY: 0.5,
tint: 0x00FF00 // Green tractor beam
}));
// Animate tractor beam expanding to engulf tower
tween(self.tractorBeam, {
alpha: 0.7,
scaleX: 1.5,
scaleY: 1.8
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
// Add pulsing effect to tractor beam
function pulseTractorBeam() {
if (self.tractorBeam && self.tractorBeam.parent) {
tween(self.tractorBeam, {
scaleX: 1.7,
scaleY: 2.0,
alpha: 0.9
}, {
duration: 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.tractorBeam && self.tractorBeam.parent) {
tween(self.tractorBeam, {
scaleX: 1.5,
scaleY: 1.8,
alpha: 0.7
}, {
duration: 400,
easing: tween.easeInOut
});
}
}
});
}
}
self.tractorBeamPulse = LK.setInterval(pulseTractorBeam, 800);
}
});
}
}, 1000);
}
});
}
}
// Keep UFO positioned over tractor beam and tower during abduction
if (self.abductionPhase === 'abducting' && self.currentTargetTower && self.currentTargetTower.parent) {
// Keep UFO directly above the tower being abducted
self.x = self.currentTargetTower.x;
// Update tractor beam position to stay connected between UFO and tower
if (self.tractorBeam && self.tractorBeam.parent) {
self.tractorBeam.x = self.currentTargetTower.x;
self.tractorBeam.y = self.y + 50; // Position at bottom of UFO
// Dynamically scale tractor beam height to reach tower
var beamHeight = Math.abs(self.currentTargetTower.y - (self.y + 50));
self.tractorBeam.scaleY = Math.max(0.5, beamHeight / 200);
}
}
// Phase 3: Abduction process with enhanced sucking effect
if (self.abductionPhase === 'abducting' && self.currentTargetTower && self.currentTargetTower.parent) {
var tower = self.currentTargetTower;
var targetY = self.y + 100; // Target position inside UFO
var dy = targetY - tower.y;
var distance = Math.abs(dy);
if (distance > 20) {
// Create sucking motion with acceleration
var liftSpeed = Math.min(8, 1.5 + (1 - distance / 400) * 6); // Accelerate as tower gets closer
tower.y += dy / distance * liftSpeed * (typeof gameSpeedMultiplier !== 'undefined' ? gameSpeedMultiplier : 1);
// No spinning during lift - removed per request
// Gradually shrink tower with more dramatic effect
var scaleEffect = Math.max(0.1, distance / 300);
tower.scaleX = scaleEffect;
tower.scaleY = scaleEffect;
// Make tower slightly transparent as it gets sucked up
tower.alpha = Math.max(0.3, distance / 400);
// Keep tower centered in tractor beam
tower.x = self.currentTargetTower.x + Math.sin(LK.ticks * 0.1) * 10; // Add slight wobble
} else {
// Tower fully abducted - clean up and move to next
if (self.tractorBeamPulse) {
LK.clearInterval(self.tractorBeamPulse);
self.tractorBeamPulse = null;
}
if (self.tractorBeam && self.tractorBeam.parent) {
// Fade out tractor beam
tween(self.tractorBeam, {
alpha: 0,
scaleX: 0.1,
scaleY: 0.1
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
if (self.tractorBeam && self.tractorBeam.parent) {
self.tractorBeam.destroy();
self.tractorBeam = null;
}
}
});
}
// Remove tower from game
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] === tower) {
towers.splice(i, 1);
break;
}
}
tower.destroy();
self.currentTargetTower = null;
self.abductionPhase = 'seeking';
}
}
}
// Phase 4: Victory condition - head to goal when all towers abducted
if (towers.length === 0 && !self.headingToGoal) {
// Clean up any remaining tractor beam and intervals
if (self.tractorBeamPulse) {
LK.clearInterval(self.tractorBeamPulse);
self.tractorBeamPulse = null;
}
if (self.tractorBeam && self.tractorBeam.parent) {
self.tractorBeam.destroy();
self.tractorBeam = null;
}
self.headingToGoal = true;
tween(self, {
x: -200,
y: 2732 / 2
}, {
duration: 3000,
easing: tween.easeInOut
});
}
// Check if UFO reached goal
if (self.headingToGoal && self.lastX > 0 && self.x <= 0) {
gameState.player.lives = 0;
// Call comprehensive reset before showing game over
resetGameCompletely();
LK.showGameOver();
return;
}
return; // Skip normal enemy logic for UFO boss
}
// Check if enemy reached the end
if (self.lastX > 0 && self.x <= 0) {
// Enemy reached the end - remove from game and reduce lives
if (self.parent) {
// Reduce player lives
gameState.player.lives--;
// Update lives display if it exists
if (gameDisplays.lives) {
gameDisplays.lives.update(gameState.player.lives);
}
// Check for game over
if (gameState.player.lives <= 0) {
// Call comprehensive reset before showing game over
resetGameCompletely();
LK.showGameOver();
}
self.destroy();
// Remove from enemies array
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
}
};
// Method to defeat this enemy
self.defeat = function () {
if (self.parent) {
// Award player 50 points and $10 cash
gameState.player.score += 50;
gameState.player.cash += 10;
// Update displays if they exist
if (gameDisplays.score) {
gameDisplays.score.update(gameState.player.score);
}
if (gameDisplays.cash) {
gameDisplays.cash.update(gameState.player.cash);
}
// Create explosion effect at enemy position
var explosionEffect = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
alpha: 1,
scaleX: 0.5,
scaleY: 0.5
}));
// Play explosion sound
LK.getSound('Explosion').play();
// Animate explosion with scale and fade out
tween(explosionEffect, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
enemiesDefeated++;
// Special handling for UFO boss defeat
if (self.isUFOBoss) {
// Clean up tractor beam if it exists
if (self.tractorBeam && self.tractorBeam.parent) {
self.tractorBeam.destroy();
self.tractorBeam = null;
}
// UFO boss defeated - transition to story5 cutscene and then level2
var story5Asset = game.addChild(LK.getAsset('Story5', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Fade in story5 asset
tween(story5Asset, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Add click handler to story5 to transition to level2
story5Asset.down = function (x, y, obj) {
story5Asset.assetType = 'Story5';
handleGameClick(x, y, story5Asset, 'storyTransition');
};
}
});
}
self.destroy();
// Remove from enemies array
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
self.setAssetType = function (assetType) {
// Use consolidated cleanup function
cleanupEnemyAssets(self);
// Store reference for cleanup function
self.enemyGraphics = enemyGraphics;
// Use consolidated recreation function
recreateEnemyAssets(self, assetType);
// Update main graphics reference
enemyGraphics = self.enemyGraphics;
// Ensure health bars are brought to front after asset change
if (self.healthBarBackground && self.healthBarBackground.parent) {
self.removeChild(self.healthBarBackground);
self.addChild(self.healthBarBackground);
}
if (self.healthBarForeground && self.healthBarForeground.parent) {
self.removeChild(self.healthBarForeground);
self.addChild(self.healthBarForeground);
}
};
return self;
});
// Tower class for tower defense
var Tower = Container.expand(function () {
var self = Container.call(this);
// Tower properties
self.towerType = 'basic';
self.level = 1;
self.damage = 25;
self.range = 300;
self.fireRate = 60; // Frames between shots (60 = 1 second at 60fps)
self.cost = 100;
self.upgradeCost = 150;
self.lastShotFrame = 0;
self.target = null;
// Tower1 specific properties
self.isTower1 = false; // Flag to identify Tower1 instances
// Tower health properties
self.health = 500;
self.maxHealth = 500;
// Guideline tracking for same-line targeting
self.guidelineIndex = -1; // Which guideline this tower is on
// Dog tower graphics (for gas towers)
self.dogGraphics = null;
self.dog1Graphics = null;
// Create tower graphics based on tower type
var towerGraphics;
if (self.towerType === 'fire') {
towerGraphics = self.attachAsset('Fireworks', {
anchorX: 0.5,
anchorY: 0.5
});
} else if (self.towerType === 'air') {
towerGraphics = self.attachAsset('Fan', {
anchorX: 0.5,
anchorY: 0.5
});
} else {
towerGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Create level indicator
var levelIndicator = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: 30,
y: -30
});
// Tower health bar graphics
self.towerHealthBarBackground = self.attachAsset('towerHealthBarBackground', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -30
});
self.towerHealthBarForeground = self.attachAsset('towerHealthBarForeground', {
anchorX: 0,
anchorY: 0.5,
x: -40,
y: -30
});
// Method to find nearest enemy in range
self.findTarget = function () {
var nearestEnemy = null;
var nearestDistance = Infinity;
var enemiesLength = enemies.length;
var towerRange = self.range;
var towerX = self.x;
var towerY = self.y;
var guidelineIndex = self.guidelineIndex;
for (var i = 0; i < enemiesLength; i++) {
var enemy = enemies[i];
// Only target enemies on the same guideline, except UFO boss (guidelineIndex -1) which can be targeted by all towers
if (enemy.guidelineIndex !== guidelineIndex && enemy.guidelineIndex !== -1) {
continue; // Skip enemies not on same line
}
var dx = enemy.x - towerX;
var dy = enemy.y - towerY;
// Quick distance check using Manhattan distance first (cheaper than sqrt)
var manhattanDistance = Math.abs(dx) + Math.abs(dy);
if (manhattanDistance > towerRange * 1.5) {
// Early exit for obviously out-of-range enemies
continue;
}
var distance = Math.sqrt(dx * dx + dy * dy);
var inRange = false;
var targetDistance = distance;
if (self.isTower1) {
inRange = distance <= 650;
} else if (self.towerType === 'fire') {
var absDx = Math.abs(dx);
var absDy = Math.abs(dy);
inRange = absDx <= 800 && absDy <= 50;
targetDistance = absDx;
} else if (self.towerType === 'dog') {
inRange = dx >= 0 && dx <= 600 && Math.abs(dy) <= 50;
targetDistance = dx;
} else if (self.towerType === 'electric') {
inRange = dx >= 0 && dx <= 500 && Math.abs(dy) <= 50;
targetDistance = dx;
} else {
inRange = distance <= towerRange;
}
if (inRange && targetDistance < nearestDistance) {
nearestDistance = targetDistance;
nearestEnemy = enemy;
}
}
return nearestEnemy;
};
// Consolidated shooting method that handles all tower types through parameters
self.shootAtTarget = function (target, shootingConfig) {
if (!target || !target.parent) return;
// Handle pre-shooting animations and sounds
if (shootingConfig.preShootAnimation) {
shootingConfig.preShootAnimation();
}
if (shootingConfig.soundEffect) {
LK.getSound(shootingConfig.soundEffect).play();
}
// Handle special tower behaviors
if (shootingConfig.specialBehavior) {
shootingConfig.specialBehavior(target);
}
// Handle water tower special spray attack
if (shootingConfig.isWaterTower) {
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= 650) {
var streamWidth = 25;
var streamLength = Math.min(distance, 650);
var stream = game.addChild(LK.getAsset('guideLine', {
anchorX: 0,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: streamLength / 2200,
scaleY: streamWidth / 4,
tint: 0x66CCFF,
alpha: 0.8
}));
var angle = Math.atan2(dy, dx);
stream.rotation = angle;
var sprayDuration = 1500;
var damageInterval = 100;
var damageDealt = 0;
var maxDamage = self.damage;
// Pulsing spray effect
var pulseSpray = function pulseSpray() {
if (stream && stream.parent) {
tween(stream, {
scaleY: (streamWidth + 10) / 4,
alpha: 1.0
}, {
duration: 200,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (stream && stream.parent) {
tween(stream, {
scaleY: streamWidth / 4,
alpha: 0.8
}, {
duration: 200,
easing: tween.easeInOut
});
}
}
});
}
};
pulseSpray();
var pulseInterval = LK.setInterval(pulseSpray, 400);
// Deal damage over time
var damageTimer = LK.setInterval(function () {
if (target && target.parent && damageDealt < maxDamage) {
var damagePerTick = Math.ceil(maxDamage / (sprayDuration / damageInterval));
target.health -= damagePerTick;
damageDealt += damagePerTick;
tween(target, {
tint: 0x66CCFF
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(target, {
tint: 0xFFFFFF
}, {
duration: 150,
easing: tween.easeOut
});
}
});
if (target.health <= 0) {
target.defeat();
LK.clearInterval(damageTimer);
LK.clearInterval(pulseInterval);
}
}
}, damageInterval);
// Clean up spray effect
LK.setTimeout(function () {
LK.clearInterval(damageTimer);
LK.clearInterval(pulseInterval);
if (stream && stream.parent) {
tween(stream, {
alpha: 0
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
stream.destroy();
}
});
}
}, sprayDuration);
}
return;
}
// Handle projectile-based towers
if (!shootingConfig.skipProjectile) {
var bullet = game.addChild(LK.getAsset(shootingConfig.projectileAsset || 'bullet', {
anchorX: 0.5,
anchorY: 0.5,
x: shootingConfig.projectileStartX || self.x,
y: shootingConfig.projectileStartY || self.y,
tint: shootingConfig.projectileTint || 0xFFFFFF
}));
// Add special effects
if (shootingConfig.additionalEffect) {
shootingConfig.additionalEffect();
}
// Calculate trajectory
var dx = target.x - self.x;
var dy = target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Animate projectile
tween(bullet, {
x: target.x,
y: target.y
}, {
duration: distance / (shootingConfig.projectileSpeed || 2),
easing: tween.linear,
onFinish: function onFinish() {
bullet.destroy();
if (target && target.parent) {
target.health -= self.damage;
if (target.health <= 0) {
target.defeat();
}
}
}
});
}
};
// Method to shoot at target (legacy wrapper)
self.shoot = function (target) {
if (!target || !target.parent) return;
var shootingConfig = {};
// Configure based on tower type
if (self.towerType === 'slingshot') {
shootingConfig.preShootAnimation = function () {
tween(self, {
scaleX: 0.8,
scaleY: 1.1,
y: self.y + 20
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0,
y: self.y - 20
}, {
duration: 100,
easing: tween.easeIn
});
}
});
};
shootingConfig.skipProjectile = true;
return; // Slingshot handled elsewhere
} else if (self.towerType === 'dog') {
shootingConfig.specialBehavior = function () {
if (self.dogGraphics && self.dog1Graphics) {
self.dogGraphics.visible = false;
self.dog1Graphics.visible = true;
tween(self, {}, {
duration: 200,
onFinish: function onFinish() {
if (self.dogGraphics && self.dog1Graphics) {
self.dogGraphics.visible = true;
self.dog1Graphics.visible = false;
}
}
});
}
};
shootingConfig.soundEffect = 'Fart';
shootingConfig.projectileAsset = 'Gas';
shootingConfig.projectileStartX = self.x + 20;
shootingConfig.projectileStartY = self.y + 25;
} else if (self.isTower1) {
shootingConfig.isWaterTower = true;
shootingConfig.soundEffect = 'Water';
} else if (self.towerType === 'fire') {
shootingConfig.soundEffect = 'Fireball';
shootingConfig.projectileAsset = 'Fireball';
} else if (self.towerType === 'electric') {
shootingConfig.soundEffect = 'Electric';
shootingConfig.projectileTint = 0x00FFFF;
shootingConfig.additionalEffect = function () {
var electricEffect = game.addChild(LK.getAsset('Electriceffect', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
alpha: 1,
scaleX: 0.8,
scaleY: 0.8
}));
tween(electricEffect, {
alpha: 0
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
electricEffect.destroy();
}
});
};
}
self.shootAtTarget(target, shootingConfig);
};
// Consolidated tower upgrade method that handles all upgrade logic uniformly
self.performUpgrade = function (upgradeConfig) {
upgradeConfig = upgradeConfig || {};
var cost = upgradeConfig.cost || self.upgradeCost;
var maxLevel = upgradeConfig.maxLevel || 3;
var damageIncrease = upgradeConfig.damageIncrease || 15;
var rangeIncrease = upgradeConfig.rangeIncrease || 50;
var fireRateReduction = upgradeConfig.fireRateReduction || 10;
var costMultiplier = upgradeConfig.costMultiplier || 1.5;
var soundEffect = upgradeConfig.soundEffect || 'Towerselect';
if (playerCash >= cost && self.level < maxLevel) {
playerCash -= cost;
self.level++;
self.damage += damageIncrease;
self.range += rangeIncrease;
self.fireRate = Math.max(30, self.fireRate - fireRateReduction);
self.upgradeCost = Math.floor(self.upgradeCost * costMultiplier);
// Update level indicator
levelIndicator.destroy();
levelIndicator = self.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5,
x: 30,
y: -30,
scaleX: self.level,
scaleY: self.level
});
// Play upgrade sound
LK.getSound(soundEffect).play();
// Execute custom upgrade callback if provided
if (upgradeConfig.onUpgrade) {
upgradeConfig.onUpgrade(self);
}
return true;
}
return false;
};
// Method to upgrade tower (legacy wrapper)
self.upgrade = function () {
return self.performUpgrade();
};
// Consolidated tower interaction handler for click events and UI responses
self.handleInteraction = function (x, y, obj, interactionType) {
interactionType = interactionType || 'down';
// Handle slingshot aiming interaction
if (self.towerType === 'slingshot' && interactionType === 'down') {
self.isAiming = true;
var gamePos = game.toLocal(self.toGlobal({
x: x,
y: y
}));
self.aimStartX = gamePos.x;
self.aimStartY = gamePos.y;
self.aimEndX = gamePos.x;
self.aimEndY = gamePos.y;
return;
}
// Handle upgrade interaction for other tower types
if (interactionType === 'upgrade') {
return self.performUpgrade();
}
// Handle selection interaction
if (interactionType === 'select') {
// Flash tower to show selection
tween(self, {
tint: 0xFFFF00
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
});
return;
}
// Handle range display interaction
if (interactionType === 'showRange') {
self.showRangeIndicator();
return;
}
};
// Click handler for tower interaction (legacy wrapper)
self.down = function (x, y, obj) {
self.handleInteraction(x, y, obj, 'down');
};
// Consolidated method to show tower range indicator
self.showRangeIndicator = function () {
// Remove existing range indicator if present
if (self.rangeIndicator && self.rangeIndicator.parent) {
self.rangeIndicator.destroy();
}
// Create range circle based on tower type
var rangeSize = self.range * 2;
if (self.towerType === 'fire' || self.towerType === 'dog' || self.towerType === 'electric') {
// Rectangular range for directional towers
self.rangeIndicator = game.addChild(LK.getAsset('guideLine', {
anchorX: 0,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: self.range / 2200,
scaleY: 50 / 4,
alpha: 0.3,
tint: 0x00FF00
}));
} else {
// Circular range for other towers
self.rangeIndicator = game.addChild(LK.getAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: rangeSize / 1000,
scaleY: rangeSize / 1000,
alpha: 0.3,
tint: 0x00FF00
}));
}
// Auto-hide range indicator after 3 seconds
LK.setTimeout(function () {
if (self.rangeIndicator && self.rangeIndicator.parent) {
tween(self.rangeIndicator, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
self.rangeIndicator.destroy();
self.rangeIndicator = null;
}
});
}
}, 3000);
};
// Consolidated tower status management method for health and damage effects
self.manageTowerStatus = function (enemies, towerGraphics) {
// Check for enemy collisions and reduce tower health
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Only check collision with enemies on the same guideline
if (enemy.guidelineIndex === self.guidelineIndex && self.intersects(enemy)) {
// Initialize collision tracking if not set
if (enemy.lastCollisionState === undefined) {
enemy.lastCollisionState = false;
}
// Check if this enemy just started colliding (state transition)
if (!enemy.lastCollisionState) {
// Apply collision damage
self.applyCollisionDamage(5, towerGraphics);
// Check if tower should be destroyed
if (self.health <= 0) {
self.handleTowerDestruction();
return;
}
}
// Update collision state
enemy.lastCollisionState = true;
} else {
// Enemy is not colliding - reset collision state
enemy.lastCollisionState = false;
}
}
};
// Consolidated method to apply collision damage with visual feedback
self.applyCollisionDamage = function (damage, graphics) {
self.health -= damage;
// Flash tower red to show damage
tween(graphics, {
tint: 0xFF0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xFFFFFF
}, {
duration: 300,
easing: tween.easeOut
});
}
});
};
// Consolidated method to handle tower destruction
self.handleTowerDestruction = function () {
// Create explosion effect at tower position
var explosionEffect = game.addChild(LK.getAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
alpha: 1,
scaleX: 0.5,
scaleY: 0.5
}));
// Play explosion sound
LK.getSound('Explosion').play();
// Animate explosion with scale and fade out
tween(explosionEffect, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
explosionEffect.destroy();
}
});
// Remove tower from towers array
for (var k = towers.length - 1; k >= 0; k--) {
if (towers[k] === self) {
towers.splice(k, 1);
break;
}
}
// Destroy tower
self.destroy();
};
self.update = function () {
// Only operate when wave is active
if (!waveActive) return;
// Cache frequently used values
var currentTick = LK.ticks;
var enemiesLength = enemies.length;
var speedMultiplier = typeof gameSpeedMultiplier !== 'undefined' ? gameSpeedMultiplier : 1;
// Rotate blades for air towers
if (self.towerType === 'air' && self.bladeGraphics) {
self.bladeGraphics.rotation += 0.2; // Continuous blade rotation
// Check for enemies within 300 pixel range straight ahead (to the right)
for (var j = 0; j < enemiesLength; j++) {
var enemy = enemies[j];
// Only target enemies on the same guideline
if (enemy.guidelineIndex === self.guidelineIndex) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
// Check if enemy is within 300 range straight ahead (to the right)
if (dx >= 0 && dx <= 300 && Math.abs(dy) <= 50) {
// Initialize collision state if not set
if (enemy.lastCollisionState === undefined) {
enemy.lastCollisionState = false;
}
// Only blow back if enemy wasn't affected last frame (prevent continuous blowing)
if (!enemy.lastCollisionState) {
// Blow enemy back 400 pixels
var newX = enemy.x + 400;
// Use tween to animate the blow-back effect
tween(enemy, {
x: newX
}, {
duration: 300,
easing: tween.easeOut
});
// Inflict small amount of damage (15 damage)
enemy.health -= 15;
// Reduce air tower health by 5 when blowing back enemy
self.health -= 5;
if (enemy.health <= 0) {
enemy.defeat();
}
// Visual effect - flash enemy to show they were hit
tween(enemy, {
tint: 0x00FFFF
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
// Update collision state
enemy.lastCollisionState = true;
} else {
// Enemy is not in range, reset collision state
enemy.lastCollisionState = false;
}
}
}
}
// Consolidated tower status management using extracted method
self.manageTowerStatus(enemies, towerGraphics);
// Update tower health bar only when health changes or every 10th frame
var healthPercentage = self.health / self.maxHealth;
if (self.lastHealthPercentage === undefined || self.lastHealthPercentage !== healthPercentage || currentTick % 10 === 0) {
self.towerHealthBarForeground.scaleX = healthPercentage;
// Change health bar color based on health percentage
if (healthPercentage > 0.6) {
self.towerHealthBarForeground.tint = 0x00ff00; // Green
} else if (healthPercentage > 0.3) {
self.towerHealthBarForeground.tint = 0xffff00; // Yellow
} else {
self.towerHealthBarForeground.tint = 0xff0000; // Red
}
self.lastHealthPercentage = healthPercentage;
}
// Find target if we don't have one or current target is invalid
if (!self.target || !self.target.parent) {
self.target = self.findTarget();
}
// Plasma tower pulsation and damage logic
if (self.towerType === 'plasma') {
// Initialize pulsation tracking if not set
if (self.lastPulseFrame === undefined) {
self.lastPulseFrame = 0;
self.pulseRate = 90; // Pulsate every 1.5 seconds
}
// Pulsate plasma tower
var adjustedPulseRate = typeof gameSpeedMultiplier !== 'undefined' ? self.pulseRate / gameSpeedMultiplier : self.pulseRate;
if (LK.ticks - self.lastPulseFrame >= adjustedPulseRate) {
// Create visual pulsation effect
tween(self, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 300,
easing: tween.easeOut
});
}
});
// Find all enemies within 400 pixels radius on same line and lines above/below
for (var e = 0; e < enemies.length; e++) {
var enemy = enemies[e];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if enemy is on same line or adjacent lines (above/below)
var guidelineIndexDiff = Math.abs(enemy.guidelineIndex - self.guidelineIndex);
if (distance <= 400 && guidelineIndexDiff <= 1) {
// Deal damage to enemy
enemy.health -= self.damage;
// Play electric sound for plasma damage
LK.getSound('Electric').play();
// Flash enemy purple to show plasma damage
tween(enemy, {
tint: 0xFF00FF
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
});
if (enemy.health <= 0) {
enemy.defeat();
}
}
}
self.lastPulseFrame = LK.ticks;
}
}
// WiFi tower shooting logic
if (self.towerType === 'wifi') {
// Initialize shooting properties if not set
if (self.lastShotFrame === undefined) {
self.lastShotFrame = 0;
self.fireRate = 30; // Fast shooting rate
self.range = 600; // 600 pixel range
}
// Find all enemies within 600 range and shoot at them
for (var e = 0; e < enemies.length; e++) {
var enemy = enemies[e];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if enemy is within 600 range
if (distance <= 600) {
// Check if enough time has passed since last shot
var adjustedFireRate = typeof gameSpeedMultiplier !== 'undefined' ? self.fireRate / gameSpeedMultiplier : self.fireRate;
if (LK.ticks - self.lastShotFrame >= adjustedFireRate) {
// Play wifi sound when attacking
LK.getSound('Wifi').play();
// Create WiFi projectile using WiFi asset
var bullet = game.addChild(LK.getAsset('WiFi', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y,
scaleX: 0.5,
// Scale down WiFi asset
scaleY: 0.5
// Scale down WiFi asset
}));
// Animate bullet to target enemy
tween(bullet, {
x: enemy.x,
y: enemy.y
}, {
duration: distance / 4,
// Fast bullet speed
easing: tween.linear,
onFinish: function onFinish() {
bullet.destroy();
// Deal high damage to target enemy
if (enemy && enemy.parent) {
enemy.health -= 100; // High damage
// Flash enemy yellow to show hit
tween(enemy, {
tint: 0xFFFF00
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(enemy, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
});
if (enemy.health <= 0) {
enemy.defeat();
}
}
}
});
self.lastShotFrame = LK.ticks;
break; // Only shoot at one enemy per frame
}
}
}
return; // Skip regular shooting logic for WiFi towers
}
// Slingshot tower aiming mechanics - manual player aiming required
if (self.towerType === 'slingshot') {
// Initialize aiming properties if not set
if (self.isAiming === undefined) {
self.isAiming = false;
self.aimStartX = 0;
self.aimStartY = 0;
self.aimEndX = 0;
self.aimEndY = 0;
}
// Handle aiming visualization during drag
if (self.isAiming) {
// Calculate aim direction for visual feedback
var aimDx = self.aimStartX - self.aimEndX;
var aimDy = self.aimStartY - self.aimEndY;
var aimDistance = Math.sqrt(aimDx * aimDx + aimDy * aimDy);
// Visual feedback - tilt slingshot based on aim direction
if (aimDistance > 10) {
var aimAngle = Math.atan2(aimDy, aimDx);
// Limit rotation to reasonable range
var maxRotation = Math.PI / 6; // 30 degrees
var rotation = Math.max(-maxRotation, Math.min(maxRotation, aimAngle * 0.3));
self.rotation = rotation;
}
} else {
// Reset rotation when not aiming
self.rotation = 0;
}
return; // Skip auto-shooting logic for slingshot towers
}
// Shoot at target if in range and fire rate allows (but not for air towers, plasma towers, and slingshot towers)
if (self.target && self.target.parent && self.towerType !== 'air' && self.towerType !== 'plasma' && self.towerType !== 'slingshot') {
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var targetInRange = false;
// Check if target is still in range based on tower type
if (self.isTower1) {
// Tower1 (water tower) has 650 pixel circular range (extended by 150 pixels)
targetInRange = distance <= 650;
} else if (self.towerType === 'fire') {
// Fire tower has 800 range both left and right
targetInRange = Math.abs(dx) <= 800 && Math.abs(dy) <= 50;
} else if (self.towerType === 'dog') {
// Gas tower has 600 range straight ahead to the right
targetInRange = dx >= 0 && dx <= 600 && Math.abs(dy) <= 50;
} else if (self.towerType === 'electric') {
// Electric tower has 500 range straight ahead to the right
targetInRange = dx >= 0 && dx <= 500 && Math.abs(dy) <= 50;
} else {
// Regular circular range for other towers
targetInRange = distance <= self.range;
}
if (targetInRange) {
// Check if enough time has passed since last shot
var adjustedFireRate = typeof gameSpeedMultiplier !== 'undefined' ? self.fireRate / gameSpeedMultiplier : self.fireRate;
if (LK.ticks - self.lastShotFrame >= adjustedFireRate) {
self.shoot(self.target);
self.lastShotFrame = LK.ticks;
}
} else {
// Target moved out of range
self.target = null;
}
}
};
self.setAssetType = function (assetType) {
if (towerGraphics && towerGraphics.parent) {
towerGraphics.destroy();
}
// Clean up existing dog graphics if they exist
if (self.dogGraphics && self.dogGraphics.parent) {
self.dogGraphics.destroy();
self.dogGraphics = null;
}
if (self.dog1Graphics && self.dog1Graphics.parent) {
self.dog1Graphics.destroy();
self.dog1Graphics = null;
}
// Clean up existing air tower graphics if they exist
if (self.bladeGraphics && self.bladeGraphics.parent) {
self.bladeGraphics.destroy();
self.bladeGraphics = null;
}
self.towerType = assetType.toLowerCase();
// For dog towers, create both Dog and Dog1 assets
if (assetType === 'Dog') {
self.dogGraphics = self.attachAsset('Dog', {
anchorX: 0.5,
anchorY: 0.5
});
self.dog1Graphics = self.attachAsset('Dog1', {
anchorX: 0.5,
anchorY: 0.5
});
// Initially show Dog, hide Dog1
self.dogGraphics.visible = true;
self.dog1Graphics.visible = false;
towerGraphics = self.dogGraphics; // Set main reference to dogGraphics
} else if (assetType === 'Fan') {
// For air towers, use Fan asset as actual tower
towerGraphics = self.attachAsset('Fan', {
anchorX: 0.5,
anchorY: 0.5
});
self.bladeGraphics = self.attachAsset('Blades', {
anchorX: 0.5,
anchorY: 0.5,
y: -70
});
self.towerType = 'air';
} else {
towerGraphics = self.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5
});
}
// Ensure health bars are brought to front after asset change
if (self.towerHealthBarBackground && self.towerHealthBarBackground.parent) {
self.removeChild(self.towerHealthBarBackground);
self.addChild(self.towerHealthBarBackground);
}
if (self.towerHealthBarForeground && self.towerHealthBarForeground.parent) {
self.removeChild(self.towerHealthBarForeground);
self.addChild(self.towerHealthBarForeground);
}
};
// Move handler for slingshot aiming updates
self.move = function (x, y, obj) {
if (self.towerType === 'slingshot' && self.isAiming) {
// Update end position during drag
var gamePos = game.toLocal(self.toGlobal({
x: x,
y: y
}));
self.aimEndX = gamePos.x;
self.aimEndY = gamePos.y;
}
};
// Up handler for slingshot aiming
self.up = function (x, y, obj) {
if (self.towerType === 'slingshot' && self.isAiming) {
// Convert to game coordinates for final aim position
var gamePos = game.toLocal(self.toGlobal({
x: x,
y: y
}));
self.aimEndX = gamePos.x;
self.aimEndY = gamePos.y;
// Calculate aim direction (reverse drag direction for slingshot physics)
var aimDx = self.aimStartX - self.aimEndX;
var aimDy = self.aimStartY - self.aimEndY;
var aimDistance = Math.sqrt(aimDx * aimDx + aimDy * aimDy);
// Only shoot if player dragged far enough (minimum 50 pixels)
if (aimDistance > 50) {
// Find target enemy in aim direction
var targetEnemy = null;
var bestScore = -1;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = enemy.x - self.x;
var enemyDy = enemy.y - self.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
// Check if enemy is within range
if (enemyDistance <= self.range) {
// Calculate angle between aim direction and enemy direction
var aimAngle = Math.atan2(aimDy, aimDx);
var enemyAngle = Math.atan2(enemyDy, enemyDx);
var angleDiff = Math.abs(aimAngle - enemyAngle);
// Normalize angle difference to 0-π range
if (angleDiff > Math.PI) {
angleDiff = 2 * Math.PI - angleDiff;
}
// Score based on angle accuracy and distance (closer and more accurate = higher score)
var angleScore = Math.PI - angleDiff; // Higher score for smaller angle diff
var distanceScore = self.range - enemyDistance; // Higher score for closer enemies
var totalScore = angleScore * 2 + distanceScore * 0.5;
// Accept targets within 45 degrees of aim direction
if (angleDiff < Math.PI / 4 && totalScore > bestScore) {
bestScore = totalScore;
targetEnemy = enemy;
}
}
}
// Shoot at target enemy if found
if (targetEnemy) {
// Add pullback effect before firing
tween(self, {
scaleX: 0.8,
scaleY: 1.1,
y: self.y + 20
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
// Snap forward and create bullet
tween(self, {
scaleX: 1.0,
scaleY: 1.0,
y: self.y - 20
}, {
duration: 100,
easing: tween.easeIn
});
// Create bullet projectile with larger size and distinctive appearance
var bullet = game.addChild(LK.getAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
x: self.x,
y: self.y - 100,
scaleX: 2.0,
// Make projectile larger
scaleY: 2.0,
// Make projectile larger
tint: 0xFF6B00 // Bright orange color for better visibility
}));
// Add pulsing effect to make projectile more visible
tween(bullet, {
scaleX: 2.5,
scaleY: 2.5
}, {
duration: 150,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bullet, {
scaleX: 2.0,
scaleY: 2.0
}, {
duration: 150,
easing: tween.easeInOut
});
}
});
// Calculate trajectory to target
var dx = targetEnemy.x - self.x;
var dy = targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Play slingshot sound when firing
LK.getSound('Slingshot').play();
// Animate bullet to target with arc trajectory
tween(bullet, {
x: targetEnemy.x,
y: targetEnemy.y
}, {
duration: distance / 3,
// Bullet speed
easing: tween.easeOut,
// Arc-like trajectory
onFinish: function onFinish() {
bullet.destroy();
// Deal heavy damage to target
if (targetEnemy && targetEnemy.parent) {
targetEnemy.health -= self.damage;
// Flash target to show hit
tween(targetEnemy, {
tint: 0xFFFF00 // Yellow flash
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(targetEnemy, {
tint: 0xFFFFFF
}, {
duration: 200,
easing: tween.easeOut
});
}
});
if (targetEnemy.health <= 0) {
targetEnemy.defeat();
}
}
}
});
}
});
}
}
// No aiming guide to clean up
// Reset aiming state
self.isAiming = false;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x000000 //Init game with black background
});
/****
* Game Code
****/
// Consolidated Configuration System
function _typeof(o) {
"@babel/helpers - typeof";
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
return typeof o;
} : function (o) {
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
}, _typeof(o);
}
function _defineProperty(e, r, t) {
return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
value: t,
enumerable: !0,
configurable: !0,
writable: !0
}) : e[r] = t, e;
}
function _toPropertyKey(t) {
var i = _toPrimitive(t, "string");
return "symbol" == _typeof(i) ? i : i + "";
}
function _toPrimitive(t, r) {
if ("object" != _typeof(t) || !t) return t;
var e = t[Symbol.toPrimitive];
if (void 0 !== e) {
var i = e.call(t, r || "default");
if ("object" != _typeof(i)) return i;
throw new TypeError("@@toPrimitive must return a primitive value.");
}
return ("string" === r ? String : Number)(t);
}
var GAME_CONFIG = {
// Level-specific configurations
levels: {
1: _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({
name: 'Urban Defense',
background: {
asset: 'backdrop',
scale: {
x: 0.5,
y: 0.5
},
position: {
x: 2048 / 2,
y: 2732 / 2
}
},
enemyPools: ['Probedroid', 'Drone', 'SpaceDrone', 'Robot', 'UFO'],
availableTowers: ['water', 'gas', 'air', 'electric', 'fire', 'plasma', 'wifi', 'slingshot'],
towerCostModifier: 1.0,
waves: {
1: {
maxEnemies: 10,
spawnInterval: 420,
enemyType: 'Probedroid',
cashReward: 50
},
2: {
maxEnemies: 20,
spawnInterval: 300,
enemyType: 'Drone',
cashReward: 50
},
3: {
maxEnemies: 30,
spawnInterval: 300,
enemyType: 'SpaceDrone',
cashReward: 50
},
4: {
maxEnemies: 40,
spawnInterval: 180,
enemyType: 'Robot',
cashReward: 50
},
5: {
maxEnemies: 1,
spawnInterval: 60,
enemyType: 'UFO',
cashReward: 50
}
},
totalWaves: 5,
unlockRequirement: null,
completionReward: {
score: 500,
cash: 100
},
specialMechanics: {
guidelineLayout: 'standard',
enemyBehavior: 'normal',
environmentalHazards: false,
tutorialEnabled: true,
introMusic: 'Intro'
}
}, "name", 'Urban Defense'), "displayName", 'Level 1: Urban Defense'), "description", 'Defend the city from the first wave of alien invaders'), "unlockRequirement", null), "unlockConditions", {
previousLevelComplete: false,
minimumScore: 0,
minimumStars: 0
}), "completionReward", {
score: 500,
cash: 100,
experience: 100,
unlockedTowers: ['mirror'],
unlockedUpgrades: ['range_boost_1']
}), "starRequirements", {
oneStar: {
condition: 'complete',
value: true
},
twoStar: {
condition: 'livesRemaining',
value: 5
},
threeStar: {
condition: 'perfectDefense',
value: true
}
}), "difficulty", 'beginner'), "estimatedTime", '10-15 minutes'),
2: _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({
name: 'Industrial Complex',
displayName: 'Level 2: Industrial Siege',
description: 'Navigate through factory obstacles as stronger enemies attack',
background: {
asset: 'Backdrop2',
scale: {
x: 1.0,
y: 1.0
},
position: {
x: 2048 / 2,
y: 2732 / 2
}
},
enemyPools: ['Drone', 'SpaceDrone', 'Robot', 'Aliencar', 'Flyingalien', 'Giantrobot'],
availableTowers: ['water', 'gas', 'air', 'electric', 'fire', 'plasma', 'wifi', 'slingshot'],
towerCostModifier: 1.2,
waves: {
1: {
maxEnemies: 15,
spawnInterval: 360,
enemyType: 'Drone',
cashReward: 60
},
2: {
maxEnemies: 25,
spawnInterval: 240,
enemyType: 'SpaceDrone',
cashReward: 60
},
3: {
maxEnemies: 35,
spawnInterval: 180,
enemyType: 'Robot',
cashReward: 60
},
4: {
maxEnemies: 20,
spawnInterval: 300,
enemyType: 'Aliencar',
cashReward: 80
},
5: {
maxEnemies: 3,
spawnInterval: 480,
enemyType: 'Flyingalien',
cashReward: 100
},
6: {
maxEnemies: 1,
spawnInterval: 60,
enemyType: 'Giantrobot',
cashReward: 200
}
},
totalWaves: 6
}, "displayName", 'Level 2: Industrial Siege'), "description", 'Heavy machinery and armored enemies challenge your defenses'), "unlockRequirement", 'level1Complete'), "unlockConditions", {
previousLevelComplete: true,
minimumScore: 1000,
minimumStars: 1
}), "completionReward", {
score: 1000,
cash: 200,
experience: 200,
unlockedTowers: ['laser', 'freeze'],
unlockedUpgrades: ['damage_boost_1', 'fire_rate_1']
}), "starRequirements", {
oneStar: {
condition: 'complete',
value: true
},
twoStar: {
condition: 'livesRemaining',
value: 7
},
threeStar: {
condition: 'timeLimit',
value: 900
} // 15 minutes
}), "difficulty", 'intermediate'), "estimatedTime", '15-20 minutes'), "specialMechanics", {
guidelineLayout: 'industrial',
enemyBehavior: 'aggressive',
environmentalHazards: true,
factoryObstacles: true,
increasedEnemySpeed: 1.3,
enhancedEnemyAI: true
}),
3: _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({
name: 'Alien Mothership',
displayName: 'Level 3: Final Assault',
description: 'Board the alien mothership for the ultimate confrontation',
background: {
asset: 'Mothership',
scale: {
x: 1.2,
y: 1.2
},
position: {
x: 2048 / 2,
y: 2732 / 2
}
},
enemyPools: ['Flyingalien', 'Alien', 'UFO', 'Giantrobot'],
availableTowers: ['air', 'electric', 'plasma', 'wifi'],
towerCostModifier: 0.8,
waves: {
1: {
maxEnemies: 20,
spawnInterval: 300,
enemyType: 'Flyingalien',
cashReward: 80
},
2: {
maxEnemies: 30,
spawnInterval: 240,
enemyType: 'Alien',
cashReward: 80
},
3: {
maxEnemies: 15,
spawnInterval: 360,
enemyType: 'UFO',
cashReward: 120
},
4: {
maxEnemies: 5,
spawnInterval: 600,
enemyType: 'Giantrobot',
cashReward: 200
},
5: {
maxEnemies: 1,
spawnInterval: 60,
enemyType: 'MothershipCore',
cashReward: 500
}
},
totalWaves: 5
}, "displayName", 'Level 3: Final Assault'), "description", 'Face the mothership core in zero gravity combat'), "unlockRequirement", 'level2Complete'), "unlockConditions", {
previousLevelComplete: true,
minimumScore: 2500,
minimumStars: 2
}), "completionReward", {
score: 2000,
cash: 500,
experience: 500,
unlockedTowers: ['quantum', 'antimatter'],
unlockedUpgrades: ['master_upgrade'],
gameComplete: true
}), "starRequirements", {
oneStar: {
condition: 'complete',
value: true
},
twoStar: {
condition: 'livesRemaining',
value: 8
},
threeStar: {
condition: 'noTowerLoss',
value: true
}
}), "difficulty", 'expert'), "estimatedTime", '20-25 minutes'), "specialMechanics", {
guidelineLayout: 'zero_gravity',
enemyBehavior: 'aerial',
environmentalHazards: true,
gravityWells: true,
energyShields: true,
finalBossSequence: true,
unlimitedTowerEnergy: true
})
},
// Level metadata for progression system
metadata: {
currentLevel: 1,
highestUnlocked: 1,
levelProgress: {
1: {
completed: false,
bestScore: 0,
fastestTime: 0,
starsEarned: 0
},
2: {
completed: false,
bestScore: 0,
fastestTime: 0,
starsEarned: 0
},
3: {
completed: false,
bestScore: 0,
fastestTime: 0,
starsEarned: 0
}
},
unlockedTowers: ['water', 'gas', 'air'],
globalUpgrades: {
towerDamageBonus: 0,
towerRangeBonus: 0,
enemySlowEffect: 0,
cashMultiplier: 1.0
}
},
// Asset configurations
assets: {
shapes: [{
name: 'Placemat',
width: 200,
height: 200,
color: 0x33cb0b,
shape: 'box'
}, {
name: 'defense',
width: 100,
height: 100,
color: 0x0abcaa,
shape: 'box'
}, {
name: 'guideLine',
width: 2200,
height: 4,
color: 0x666666,
shape: 'box'
}, {
name: 'healthBarBackground',
width: 60,
height: 8,
color: 0x444444,
shape: 'box'
}, {
name: 'healthBarForeground',
width: 60,
height: 8,
color: 0x00ff00,
shape: 'box'
}, {
name: 'rangeCircle',
width: 1000,
height: 1000,
color: 0xffffff,
shape: 'ellipse'
}, {
name: 'squareLight',
width: 30,
height: 30,
color: 0x00ff00,
shape: 'box'
}, {
name: 'tower',
width: 2,
height: 2,
color: 0x2df5c0,
shape: 'box'
}, {
name: 'towerHealthBarBackground',
width: 80,
height: 10,
color: 0x444444,
shape: 'box'
}, {
name: 'towerHealthBarForeground',
width: 80,
height: 10,
color: 0x00ff00,
shape: 'box'
}, {
name: 'towerLevelIndicator',
width: 10,
height: 10,
color: 0xffffff,
shape: 'box'
}, {
name: 'towerpreview',
width: 100,
height: 100,
color: 0xffffff,
shape: 'box'
}, {
name: 'verticalGuideLine',
width: 4,
height: 1500,
color: 0x666666,
shape: 'box'
}],
numbered: [{
name: '1',
width: 187.3,
id: '687e8cb1b06057c5f9deadd2'
}, {
name: '2',
width: 218.4,
id: '687e8cdbb06057c5f9deadd5'
}, {
name: '3',
width: 108,
id: '687e8cf9b06057c5f9deadd8'
}, {
name: '4',
width: 170.73,
id: '687e8d18b06057c5f9deaddb'
}],
digits: [{
name: 'Zero',
height: 108.94,
id: '6883b90671447ae2e425a89a'
}, {
name: 'One',
height: 125,
id: '6883b92071447ae2e425a89d'
}, {
name: 'Two',
height: 116.89,
id: '6883b93571447ae2e425a8a1'
}, {
name: 'Three',
height: 117.97,
id: '6883b94a71447ae2e425a8a5'
}, {
name: 'Four',
height: 121.9,
id: '6883b97671447ae2e425a8a9'
}, {
name: 'Five',
height: 124.27,
id: '6883b98f71447ae2e425a8b0'
}, {
name: 'Six',
height: 128,
id: '6883b9ab71447ae2e425a8b4'
}, {
name: 'Seven',
height: 127.36,
id: '6883b9ca71447ae2e425a8bc'
}, {
name: 'Eight',
height: 116.36,
id: '6883b9e571447ae2e425a8c0'
}, {
name: 'Nine',
height: 113.78,
id: '6883ba0b71447ae2e425a8cf'
}],
story: [{
name: 'Story1',
id: '6881191e41db13ea527b2565'
}, {
name: 'Story2',
id: '6881195441db13ea527b256e'
}, {
name: 'Story3',
id: '6881198441db13ea527b2572'
}, {
name: 'Story4',
id: '6881726749b756ddb352615c'
}, {
name: 'Story5',
id: '68827a863bc9457bd8fe4ee4'
}],
waves: [{
name: 'Wave1',
height: 416.02,
id: '6876a5c7282810e95e7b2e71'
}, {
name: 'Wave2',
height: 400.39,
id: '6877ec933e8c5761702af89f'
}, {
name: 'Wave3',
height: 400,
id: '6877eccf3e8c5761702af8a9'
}, {
name: 'Wave4',
height: 400,
id: '6877ed053e8c5761702af8b3'
}, {
name: 'Wave5',
height: 400,
id: '68788ee96cd3a5bb82a92af7'
}],
sounds: [{
name: 'Alien1',
id: '686f7806e85c05a4e97b90fe'
}, {
name: 'Alien2',
id: '686f791ae85c05a4e97b9107'
}, {
name: 'Alien3',
id: '686f7d58e85c05a4e97b911f'
}, {
name: 'Canvasser',
id: '686d14ebf11e6c3a82bb7d19'
}, {
name: 'Probedroid',
id: '687f2a4cc4e580a313211215'
}, {
name: 'Probedroid2',
id: '687f2b03c4e580a31321121a'
}, {
name: 'Wifi',
id: '688588ad48e567a9f8c09f89'
}]
},
// Tower configurations
towers: {
water: {
cost: 10,
assetType: 'Tower1',
damage: 35,
range: 650,
fireRate: 60,
health: 500,
maxHealth: 500,
sound: 'Water'
},
gas: {
cost: 20,
assetType: 'Dog',
damage: 30,
range: 600,
fireRate: 45,
health: 500,
maxHealth: 500,
sound: 'Fart'
},
air: {
cost: 30,
assetType: 'Fan',
damage: 35,
range: 350,
fireRate: 30,
health: 500,
maxHealth: 500,
blowbackDamage: 15,
blowbackDistance: 400,
blowbackRange: 300
},
electric: {
cost: 50,
assetType: 'Bugzapper',
damage: 60,
range: 350,
fireRate: 40,
health: 500,
maxHealth: 500,
sound: 'Electric'
},
fire: {
cost: 40,
assetType: 'Fireworks',
damage: 40,
range: 400,
fireRate: 50,
health: 500,
maxHealth: 500,
projectileAsset: 'Fireball',
sound: 'Fireball'
},
plasma: {
cost: 60,
assetType: 'Plasmaball',
damage: 80,
range: 400,
fireRate: 60,
pulseRate: 90,
pulseRange: 400,
health: 500,
maxHealth: 500,
sound: 'Electric'
},
wifi: {
cost: 80,
assetType: 'Wifitower',
damage: 100,
range: 600,
fireRate: 30,
health: 500,
maxHealth: 500,
projectileAsset: 'WiFi',
sound: 'Wifi'
},
slingshot: {
cost: 35,
assetType: 'Slingshot',
damage: 80,
range: 1300,
fireRate: 120,
health: 500,
maxHealth: 500,
sound: 'Slingshot'
}
},
// Enemy configurations with level-specific variations
enemies: {
Probedroid: {
health: 150,
maxHealth: 150,
speed: 1.5,
reward: {
score: 50,
cash: 10
},
levelVariations: {
1: {
healthMultiplier: 1.0,
speedMultiplier: 1.0
},
2: {
healthMultiplier: 1.3,
speedMultiplier: 1.2
},
3: {
healthMultiplier: 1.5,
speedMultiplier: 1.4
}
}
},
Drone: {
health: 200,
maxHealth: 200,
speed: 2,
reward: {
score: 50,
cash: 10
},
levelVariations: {
1: {
healthMultiplier: 1.0,
speedMultiplier: 1.0
},
2: {
healthMultiplier: 1.4,
speedMultiplier: 1.3
},
3: {
healthMultiplier: 1.6,
speedMultiplier: 1.5
}
}
},
SpaceDrone: {
health: 250,
maxHealth: 250,
speed: 2.5,
reward: {
score: 50,
cash: 10
},
shootingRange: 400,
fireRate: 45,
damage: 25,
levelVariations: {
1: {
healthMultiplier: 1.0,
speedMultiplier: 1.0,
damageMultiplier: 1.0
},
2: {
healthMultiplier: 1.5,
speedMultiplier: 1.2,
damageMultiplier: 1.3
},
3: {
healthMultiplier: 1.8,
speedMultiplier: 1.4,
damageMultiplier: 1.5
}
}
},
Robot: {
health: 350,
maxHealth: 350,
speed: 4,
reward: {
score: 50,
cash: 10
},
shootingRange: 450,
fireRate: 60,
damage: 35,
levelVariations: {
1: {
healthMultiplier: 1.0,
speedMultiplier: 1.0,
damageMultiplier: 1.0
},
2: {
healthMultiplier: 1.6,
speedMultiplier: 1.1,
damageMultiplier: 1.4
},
3: {
healthMultiplier: 2.0,
speedMultiplier: 1.3,
damageMultiplier: 1.6
}
}
},
UFO: {
health: 5000,
maxHealth: 5000,
speed: 1,
reward: {
score: 50,
cash: 10
},
guidelineIndex: -1,
levelVariations: {
1: {
healthMultiplier: 1.0,
speedMultiplier: 1.0
},
2: {
healthMultiplier: 1.5,
speedMultiplier: 1.2
},
3: {
healthMultiplier: 2.0,
speedMultiplier: 1.4
}
}
},
Aliencar: {
health: 400,
maxHealth: 400,
speed: 3,
reward: {
score: 75,
cash: 15
},
special: 'armored',
levelVariations: {
2: {
healthMultiplier: 1.0,
speedMultiplier: 1.0,
armorRating: 0.2
},
3: {
healthMultiplier: 1.3,
speedMultiplier: 1.2,
armorRating: 0.3
}
}
},
Flyingalien: {
health: 300,
maxHealth: 300,
speed: 2.5,
reward: {
score: 80,
cash: 20
},
special: 'aerial',
levelVariations: {
2: {
healthMultiplier: 1.0,
speedMultiplier: 1.0,
evasionChance: 0.1
},
3: {
healthMultiplier: 1.4,
speedMultiplier: 1.3,
evasionChance: 0.2
}
}
},
Giantrobot: {
health: 8000,
maxHealth: 8000,
speed: 0.5,
reward: {
score: 500,
cash: 100
},
special: 'boss',
levelVariations: {
2: {
healthMultiplier: 1.0,
speedMultiplier: 1.0,
shieldCapacity: 2000
},
3: {
healthMultiplier: 1.5,
speedMultiplier: 1.2,
shieldCapacity: 3000
}
}
},
MothershipCore: {
health: 12000,
maxHealth: 12000,
speed: 0.3,
reward: {
score: 1000,
cash: 500
},
special: 'final_boss',
levelVariations: {
3: {
healthMultiplier: 1.0,
speedMultiplier: 1.0,
phases: ['shield', 'regeneration', 'berserk'],
specialAttacks: ['gravity_well', 'energy_blast', 'minion_spawn']
}
}
}
},
// Wave configurations (legacy compatibility)
waves: {
1: {
maxEnemies: 10,
spawnInterval: 420,
enemyType: 'Probedroid',
cashReward: 50
},
2: {
maxEnemies: 20,
spawnInterval: 300,
enemyType: 'Drone',
cashReward: 50
},
3: {
maxEnemies: 30,
spawnInterval: 300,
enemyType: 'SpaceDrone',
cashReward: 50
},
4: {
maxEnemies: 40,
spawnInterval: 180,
enemyType: 'Robot',
cashReward: 50
},
5: {
maxEnemies: 1,
spawnInterval: 60,
enemyType: 'UFO',
cashReward: 50
}
},
// Game settings
game: {
startingLives: 10,
startingCash: 50,
startingScore: 0,
gameSpeedMultiplier: 1,
guidelineCount: 6,
verticalGuidelineCount: 8,
towerCollisionDamage: 5,
enemyEndReward: {
livesLost: 1
}
}
};
// Consolidated Game State Management
var GAME_STATE = {
player: {
score: 0,
lives: 10,
cash: 50
},
wave: {
active: false,
current: 1,
enemiesSpawned: 0,
maxEnemies: 10,
spawnTimer: 0,
spawnInterval: 420,
firstEnemySpawned: false,
enemiesDefeated: 0
},
game: {
speedMultiplier: 1,
introPlayed: false,
started: false
},
placement: {
active: false,
pendingCost: 10,
preview: null,
confirmButton: null,
cancelButton: null
}
};
// Consolidated Game Objects
var GAME_OBJECTS = {
enemies: [],
towers: [],
verticalGuidelines: [],
snapPositions: [],
guidelineYPositions: []
};
// Consolidated Game Displays
var GAME_DISPLAYS = {
score: null,
lives: null,
cash: null
};
// Consolidated configuration aliases
var towerConfig = GAME_CONFIG.towers;
var enemyConfig = GAME_CONFIG.enemies;
var waveConfig = GAME_CONFIG.waves;
var gameConfig = GAME_CONFIG.game;
var gameState = GAME_STATE;
var gameObjects = GAME_OBJECTS;
var gameDisplays = GAME_DISPLAYS;
// UI Management
var uiContainer = null;
var storyTimeouts = [];
// Legacy variable aliases - all point to gameState for unified management
var placingTower = false;
var towerPreview = null;
var confirmButton = null;
var cancelButton = null;
var pendingTowerCost = 10;
var waveActive = false;
var currentWave = 1;
var enemySpawnTimer = 0;
var enemiesSpawned = 0;
var maxEnemiesInWave = 10;
var enemySpawnInterval = 420;
var firstEnemySpawned = false;
var enemiesDefeated = 0;
var playerCash = 50;
var gameSpeedMultiplier = 1;
var enemies = [];
var towers = [];
var guidelineYPositions = [];
var scoreDisplay = null;
var cashDisplay = null;
var livesDisplay = null;
var introCutscene = new Cutscene();
// Add intro2 scene
introCutscene.addScene({
onStart: function onStart() {
// Create intro2 asset
var intro2Asset = game.addChild(LK.getAsset('Intro2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Create cell asset in middle of intro2
var cellAsset = game.addChild(LK.getAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Store references for cleanup
introCutscene.intro2Asset = intro2Asset;
introCutscene.cellAsset = cellAsset;
// Fade in intro2
tween(intro2Asset, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut
});
// Play intro music when cell appears
LK.playMusic('Intro', {
loop: false
});
// Fade in cell asset
tween(cellAsset, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// After cell asset fades in, wait 5 seconds then transition to story1
var cellToStory1Timeout = LK.setTimeout(function () {
// Fade out intro2 and cell assets
tween(intro2Asset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut
});
tween(cellAsset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Create and fade in story1 scene
var story1Asset = game.addChild(LK.getAsset('Story1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
tween(story1Asset, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Hide skip intro button during story scenes
if (introCutscene.skipIntroButton) {
introCutscene.skipIntroButton.alpha = 0;
}
// Store story1 reference for cleanup
introCutscene.story1Asset = story1Asset;
// Auto-transition to story2 after 3 seconds
var story1ToStory2Timeout = LK.setTimeout(function () {
tween(story1Asset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
story1Asset.destroy();
// Create and fade in story2 scene
var story2Asset = game.addChild(LK.getAsset('Story2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
tween(story2Asset, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
introCutscene.story2Asset = story2Asset;
// Auto-transition to story3 after 3 seconds
var story2ToStory3Timeout = LK.setTimeout(function () {
tween(story2Asset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
story2Asset.destroy();
// Create and fade in story3 scene
var story3Asset = game.addChild(LK.getAsset('Story3', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
tween(story3Asset, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
introCutscene.story3Asset = story3Asset;
// Auto-transition to story4 after 3 seconds
var story3ToStory4Timeout = LK.setTimeout(function () {
tween(story3Asset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
story3Asset.destroy();
// Create and fade in story4 scene
var story4Asset = game.addChild(LK.getAsset('Story4', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
tween(story4Asset, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
introCutscene.story4Asset = story4Asset;
// After story4, wait 3 seconds then finish cutscene
var story4CompletionTimeout = LK.setTimeout(function () {
tween(story4Asset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
story4Asset.destroy();
// Cutscene complete - continue to title screen
showTitleScreen();
}
});
}, 3000);
storyTimeouts.push(story4CompletionTimeout);
}
});
}
});
}, 3000);
storyTimeouts.push(story3ToStory4Timeout);
}
});
}
});
}, 3000);
storyTimeouts.push(story2ToStory3Timeout);
}
});
}
});
}, 3000);
storyTimeouts.push(story1ToStory2Timeout);
}
});
}
});
}, 5000);
// Store timeout reference for cancellation
storyTimeouts.push(cellToStory1Timeout);
}
});
// Add skip intro button
var skipIntroButton = new Text2('SKIP INTRO', {
size: 60,
fill: 0xFFFFFF
});
skipIntroButton.anchor.set(0.5, 0);
skipIntroButton.x = 2048 / 2;
skipIntroButton.y = 200;
skipIntroButton.alpha = 0;
game.addChild(skipIntroButton);
// Store reference for cleanup
introCutscene.skipIntroButton = skipIntroButton;
// Fade in skip button
tween(skipIntroButton, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut
});
// Consolidated skip intro handler function
function handleSkipIntro(skipButtonRef, intro2Ref, cellRef) {
// Don't allow skip if game has already started
if (gameState.game.started) return;
// Cancel all pending story timeouts to prevent them from executing
for (var i = 0; i < storyTimeouts.length; i++) {
LK.clearTimeout(storyTimeouts[i]);
}
storyTimeouts = []; // Clear the array
// Clean up intro assets if provided
if (intro2Ref) {
tween.stop(intro2Ref);
intro2Ref.destroy();
}
if (cellRef) {
tween.stop(cellRef);
cellRef.destroy();
}
if (skipButtonRef) {
tween.stop(skipButtonRef);
skipButtonRef.destroy();
}
// Destroy story scenes 1-4 if they exist to prevent them from appearing later
if (introCutscene.story1Asset && introCutscene.story1Asset.parent) {
introCutscene.story1Asset.destroy();
introCutscene.story1Asset = null;
}
if (introCutscene.story2Asset && introCutscene.story2Asset.parent) {
introCutscene.story2Asset.destroy();
introCutscene.story2Asset = null;
}
if (introCutscene.story3Asset && introCutscene.story3Asset.parent) {
introCutscene.story3Asset.destroy();
introCutscene.story3Asset = null;
}
if (introCutscene.story4Asset && introCutscene.story4Asset.parent) {
introCutscene.story4Asset.destroy();
introCutscene.story4Asset = null;
}
// Skip directly to backdrop scene
skipToBackdrop();
}
// Add click event handler to skip button
skipIntroButton.down = function (x, y, obj) {
handleSkipIntro(skipIntroButton, intro2Asset, cellAsset);
};
// Cell asset now has no click handler to prevent story replay
}
});
// Set what happens when cutscene finishes
introCutscene.onFinish = function () {
// Cutscene finished - no automatic transition
};
// Function to skip directly to backdrop scene
function skipToBackdrop() {
// Reset intro played flag so next game start goes to backdrop
gameState.game.introPlayed = true;
// Add backdrop asset to game
var backdropAsset = game.addChild(LK.getAsset('backdrop', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 1,
scaleX: 0.5,
scaleY: 0.5
}));
// Add flyby asset to backdrop
var backdropFlyby = game.addChild(LK.getAsset('Flyby', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 800,
alpha: 1,
scaleX: 0.2,
scaleY: 0.2
}));
// Zoom flyby to final size
tween(backdropFlyby, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 1000,
easing: tween.easeInOut
});
// Store the original Y position for backdrop flyby
var backdropFlybyOriginalY = backdropFlyby.y;
// Make flyby move off the right side of backdrop scene
tween(backdropFlyby, {
x: 2048 + 375
}, {
duration: 6000,
easing: tween.linear,
onFinish: function onFinish() {
// After flyby has left the backdrop scene, fade in wave1 asset
var wave1Asset = game.addChild(LK.getAsset('Wave1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Ensure wave1 appears in front of all game objects
wave1Asset.zIndex = 1000;
// Fade in wave1 asset
tween(wave1Asset, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Keep wave1 visible for 2 seconds then fade out
LK.setTimeout(function () {
tween(wave1Asset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
wave1Asset.destroy();
// Start first tower defense wave
startWave1();
}
});
}, 2000);
}
});
}
});
// Add 6 horizontal guide lines immediately
var lineSpacing = 2732 / 7;
for (var i = 1; i <= 6; i++) {
var yPosition = lineSpacing * i;
if (i === 1) {
yPosition += 525; // Move guideline 1 down by 525 pixels (was 505)
} else if (i === 2) {
yPosition += 395; // Move guideline 2 down by 395 pixels (was 420)
} else if (i === 3) {
yPosition += 225; // Move guideline 3 down by 225 pixels (was 250)
} else if (i === 4) {
yPosition += 95; // Move guideline 4 down by 95 pixels (was 120)
} else if (i === 5) {
yPosition -= 95; // Move guideline 5 up by 95 pixels (was -75)
} else if (i === 6) {
yPosition -= 270; // Move guideline 6 up by 270 pixels (was -250)
}
var guideLine = game.addChild(LK.getAsset('guideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: yPosition,
alpha: 0.5
}));
}
}
// Function to show title screen
function showTitleScreen() {
// Don't show title screen if game has already started
if (gameState.game.started) return;
// Display the title
// Import tween plugin
var titleImage = game.addChild(LK.getAsset('Title', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 + 1400
}));
// Add skip intro button to top of title screen
var skipButton = new Text2('SKIP INTRO', {
size: 60,
fill: 0xFFFFFF
});
skipButton.anchor.set(0.5, 0);
skipButton.x = 2048 / 2;
skipButton.y = 150;
game.addChild(skipButton);
// Show skip intro button again if it was hidden during story scenes
if (introCutscene.skipIntroButton) {
introCutscene.skipIntroButton.alpha = 1;
}
// Add touch event to skip button
skipButton.down = function (x, y, obj) {
handleSkipIntro(null, null, null); // No specific assets to clean up in title screen
// Cancel any running title screen animations
tween.stopAll();
};
// Add flyby asset to title - start at right side of screen
var flybyImage = game.addChild(LK.getAsset('Flyby', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 + 375,
y: 2732 / 2 + 1400 - 500
}));
// Store the original Y position for flyby
var flybyOriginalY = flybyImage.y;
// Create hover animation function
function startFlybyHover() {
// Tween up 50 pixels over 2 seconds
tween(flybyImage, {
y: flybyOriginalY - 50
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Tween down 50 pixels over 2 seconds
tween(flybyImage, {
y: flybyOriginalY + 50
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Start the cycle again
startFlybyHover();
}
});
}
});
}
// Start the hover animation
startFlybyHover();
// Start flyby movement from right to left
tween(flybyImage, {
x: -375
}, {
duration: 8000,
easing: tween.linear,
onFinish: function onFinish() {
// After flyby has left the screen, slowly scroll title up
tween(titleImage, {
y: titleImage.y - titleImage.height
}, {
duration: 20000,
easing: tween.easeOut
});
// Stop the title scrolling after 5 seconds
LK.setTimeout(function () {
tween.stop(titleImage, {
y: true
});
// Fade in intro asset in the middle of the screen
var introAsset = game.addChild(LK.getAsset('intro', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Tween alpha from 0 to 1 for fade-in effect
tween(introAsset, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// After intro has faded in, make flyby move from left to right
flybyImage.x = -375; // Reset flyby to left side
flybyImage.y = 2732 / 2; // Center vertically
tween(flybyImage, {
x: 2048 + 375
}, {
duration: 6000,
easing: tween.linear,
onFinish: function onFinish() {
// After flyby has left the screen, fade out intro and fade in backdrop
tween(introAsset, {
alpha: 0
}, {
duration: 2000,
easing: tween.easeInOut
});
// Add backdrop asset to game
var backdropAsset = game.addChild(LK.getAsset('backdrop', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}));
// Add flyby asset to backdrop
var backdropFlyby = game.addChild(LK.getAsset('Flyby', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2 - 800,
alpha: 0,
scaleX: 0.2,
scaleY: 0.2
}));
// Store the original Y position for backdrop flyby
var backdropFlybyOriginalY = backdropFlyby.y;
// Make flyby move off the right side of backdrop scene
function startBackdropFlybyHover() {
tween(backdropFlyby, {
x: 2048 + 375
}, {
duration: 6000,
easing: tween.linear,
onFinish: function onFinish() {
// After flyby has left the backdrop scene, fade in wave1 asset
var wave1Asset = game.addChild(LK.getAsset('Wave1', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Ensure wave1 appears in front of all game objects
wave1Asset.zIndex = 1000;
// Fade in wave1 asset
tween(wave1Asset, {
alpha: 1
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Keep wave1 visible for 2 seconds then fade out
LK.setTimeout(function () {
tween(wave1Asset, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
wave1Asset.destroy();
// Start first tower defense wave
startWave1();
}
});
}, 2000);
}
});
}
});
}
// Fade in backdrop
tween(backdropAsset, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Fade in backdrop flyby and zoom to position
tween(backdropFlyby, {
alpha: 1,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Start flyby movement to right side after fade in
startBackdropFlybyHover();
}
});
// Add 6 horizontal guide lines after backdrop fades in
var lineSpacing = 2732 / 7; // Divide screen height by 7 to get 6 lines with margins
for (var i = 1; i <= 6; i++) {
var yPosition = lineSpacing * i;
if (i === 1) {
yPosition += 525; // Move guideline 1 down by 525 pixels (was 505)
} else if (i === 2) {
yPosition += 395; // Move guideline 2 down by 395 pixels (was 420)
} else if (i === 3) {
yPosition += 225; // Move guideline 3 down by 225 pixels (was 250)
} else if (i === 4) {
yPosition += 95; // Move guideline 4 down by 95 pixels (was 120)
} else if (i === 5) {
yPosition -= 95; // Move guideline 5 up by 95 pixels (was -75)
} else if (i === 6) {
yPosition -= 270; // Move guideline 6 up by 270 pixels (was -250)
}
var guideLine = game.addChild(LK.getAsset('guideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: yPosition,
alpha: 0.5
}));
}
}
});
}
});
}
});
}, 5000);
}
});
}
// Integrate guideline configuration into main config
GAME_CONFIG.guidelines = {
screenHeight: 2732,
screenWidth: 2048,
horizontalCount: 6,
verticalCount: 8,
offsets: {
1: 525,
2: 395,
3: 225,
4: 95,
5: -95,
6: -270
},
alpha: 0.5,
verticalAlpha: 0.3
};
// Legacy alias
var guidelineConfig = GAME_CONFIG.guidelines;
// Utility functions for common calculations
var utilityFunctions = {
// Calculate guideline Y positions for enemy spawning
calculateGuidelinePositions: function calculateGuidelinePositions() {
var lineSpacing = guidelineConfig.screenHeight / 7;
gameObjects.guidelineYPositions = [];
for (var i = 1; i <= guidelineConfig.horizontalCount; i++) {
var yPosition = lineSpacing * i;
if (guidelineConfig.offsets[i]) {
yPosition += guidelineConfig.offsets[i];
}
gameObjects.guidelineYPositions.push(yPosition);
}
},
// Calculate snap positions from guideline intersections
calculateSnapPositions: function calculateSnapPositions() {
gameObjects.snapPositions = [];
// Create vertical guideline positions (8 columns across screen width)
var verticalSpacing = guidelineConfig.screenWidth / 9;
var verticalXPositions = [];
for (var i = 1; i <= guidelineConfig.verticalCount; i++) {
var xPosition = verticalSpacing * i;
verticalXPositions.push(xPosition);
}
// Create snap positions at intersections of horizontal and vertical guidelines
for (var h = 0; h < gameObjects.guidelineYPositions.length; h++) {
for (var v = 0; v < verticalXPositions.length; v++) {
gameObjects.snapPositions.push({
x: verticalXPositions[v],
y: gameObjects.guidelineYPositions[h]
});
}
}
},
// Find nearest snap position to given coordinates
findNearestSnapPosition: function findNearestSnapPosition(x, y) {
var nearestPosition = {
x: x,
y: y
};
var nearestDistance = Infinity;
for (var i = 0; i < gameObjects.snapPositions.length; i++) {
var snap = gameObjects.snapPositions[i];
var dx = snap.x - x;
var dy = snap.y - y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestPosition = snap;
}
}
return nearestPosition;
},
// Show vertical guidelines during tower placement
showVerticalGuidelines: function showVerticalGuidelines() {
utilityFunctions.hideVerticalGuidelines(); // Clear any existing guidelines
var verticalSpacing = guidelineConfig.screenWidth / 9;
for (var i = 1; i <= guidelineConfig.verticalCount; i++) {
var xPosition = verticalSpacing * i;
var yPosition = guidelineConfig.screenHeight / 2;
if (i === 1) {
yPosition += 50; // Move vertical guideline 1 down by 50 pixels
}
var guideLine = game.addChild(LK.getAsset('verticalGuideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: xPosition,
y: yPosition,
alpha: guidelineConfig.verticalAlpha
}));
gameObjects.verticalGuidelines.push(guideLine);
}
},
// Hide vertical guidelines
hideVerticalGuidelines: function hideVerticalGuidelines() {
for (var i = 0; i < gameObjects.verticalGuidelines.length; i++) {
gameObjects.verticalGuidelines[i].destroy();
}
gameObjects.verticalGuidelines = [];
},
// Clean up placement assets (tower preview, buttons, guidelines)
cleanupPlacementAssets: function cleanupPlacementAssets() {
if (gameState.placement.preview) {
if (gameState.placement.preview.bladePreview) {
gameState.placement.preview.bladePreview.destroy();
}
gameState.placement.preview.destroy();
gameState.placement.preview = null;
}
if (gameState.placement.confirmButton) {
gameState.placement.confirmButton.destroy();
gameState.placement.confirmButton = null;
}
if (gameState.placement.cancelButton) {
gameState.placement.cancelButton.destroy();
gameState.placement.cancelButton = null;
}
utilityFunctions.hideVerticalGuidelines();
gameState.placement.active = false;
},
// Find nearest guideline index for a given Y position
findNearestGuidelineIndex: function findNearestGuidelineIndex(yPosition) {
var nearestGuidelineIndex = 0;
var nearestDistance = Infinity;
for (var g = 0; g < gameObjects.guidelineYPositions.length; g++) {
var distance = Math.abs(yPosition - gameObjects.guidelineYPositions[g]);
if (distance < nearestDistance) {
nearestDistance = distance;
nearestGuidelineIndex = g;
}
}
return nearestGuidelineIndex;
}
};
// Legacy function wrappers for backward compatibility
function calculateGuidelinePositions() {
utilityFunctions.calculateGuidelinePositions();
}
// Legacy function wrapper for backward compatibility
function calculateSnapPositions() {
utilityFunctions.calculateSnapPositions();
}
// Legacy function wrapper for backward compatibility
function findNearestSnapPosition(x, y) {
return utilityFunctions.findNearestSnapPosition(x, y);
}
// Legacy function wrapper for backward compatibility
function showVerticalGuidelines() {
utilityFunctions.showVerticalGuidelines();
}
// Legacy function wrapper for backward compatibility
function hideVerticalGuidelines() {
utilityFunctions.hideVerticalGuidelines();
}
// Global UI container management
// Function to create UI container
function createUIContainer() {
if (uiContainer && uiContainer.parent) {
cleanupUIContainer();
}
uiContainer = new Container();
LK.gui.addChild(uiContainer);
}
// Function to clean up UI container
function cleanupUIContainer() {
if (uiContainer && uiContainer.parent) {
// Destroy all children first
while (uiContainer.children && uiContainer.children.length > 0) {
var child = uiContainer.children[0];
if (child && child.destroy) {
child.destroy();
}
}
// Remove from parent and destroy container
uiContainer.destroy();
}
// Always reset to null regardless of parent state
uiContainer = null;
}
// Comprehensive game reset function that is called just before game over
function resetGameCompletely() {
// Stop tweens on specific objects that might be tweening
if (game && game.children) {
for (var j = 0; j < game.children.length; j++) {
var child = game.children[j];
if (child) {
tween.stop(child);
}
}
}
// Stop tweens on enemies
for (var k = 0; k < enemies.length; k++) {
if (enemies[k]) {
tween.stop(enemies[k]);
}
}
// Stop tweens on towers
for (var l = 0; l < towers.length; l++) {
if (towers[l]) {
tween.stop(towers[l]);
}
}
// Clear all intervals and timeouts
for (var i = 0; i < storyTimeouts.length; i++) {
LK.clearTimeout(storyTimeouts[i]);
}
storyTimeouts = [];
// Clear all enemies
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] && enemies[i].destroy) {
enemies[i].destroy();
}
}
enemies = [];
// Clear all towers
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] && towers[i].destroy) {
towers[i].destroy();
}
}
towers = [];
// Clean up placement mode
placingTower = false;
if (towerPreview) {
if (towerPreview.bladePreview) {
towerPreview.bladePreview.destroy();
}
towerPreview.destroy();
towerPreview = null;
}
if (confirmButton) {
confirmButton.destroy();
confirmButton = null;
}
if (cancelButton) {
cancelButton.destroy();
cancelButton = null;
}
// Hide guidelines
hideVerticalGuidelines();
// Complete LK.gui cleanup - remove all children
if (LK.gui && LK.gui.children) {
while (LK.gui.children.length > 0) {
var child = LK.gui.children[0];
try {
if (child && child.destroy) {
child.destroy();
} else if (child && child.parent) {
LK.gui.removeChild(child);
}
} catch (e) {
// Force remove if destroy fails
try {
if (child && child.parent) {
LK.gui.removeChild(child);
}
} catch (e2) {
// Skip if both fail - continue with loop
}
}
}
}
// Clean up UI container
cleanupUIContainer();
// Reset all game state variables to initial values
gameState.wave.active = false;
gameState.wave.spawnTimer = 0;
gameState.wave.spawnInterval = 420;
gameState.wave.enemiesSpawned = 0;
gameState.wave.maxEnemies = 10;
gameState.wave.firstEnemySpawned = false;
gameState.wave.current = 1;
gameState.wave.enemiesDefeated = 0;
gameState.player.score = 0;
gameState.player.lives = 10;
gameState.player.cash = 50;
gameState.game.speedMultiplier = 1;
gameState.game.introPlayed = false;
gameState.game.started = false;
gameState.placement.pendingCost = 10;
// Clear display references
gameDisplays.score = null;
gameDisplays.lives = null;
gameDisplays.cash = null;
// Clear guideline positions
gameObjects.guidelineYPositions = [];
gameObjects.snapPositions = [];
gameObjects.verticalGuidelines = [];
}
// Clean up completely when game resets
LK.on('gameReset', function () {
// Call comprehensive reset function to ensure everything is cleaned up
resetGameCompletely();
// Recreate UI container for fresh start
createUIContainer();
});
// Initialize UI container at game start
createUIContainer();
// Consolidated wave starting function that takes wave parameters
function startWave(waveNumber, config) {
calculateGuidelinePositions();
waveActive = true;
if (waveNumber === 1) {
gameState.game.started = true; // Mark that the game has officially started
}
enemySpawnTimer = 0;
enemiesSpawned = 0;
currentWave = waveNumber;
enemiesDefeated = 0;
maxEnemiesInWave = config.maxEnemies;
enemySpawnInterval = config.spawnInterval;
}
// Start first wave of tower defense
function startWave1() {
startWave(1, waveConfig[1]);
// Ensure UI container exists
if (!uiContainer) {
createUIContainer();
}
// Helper function to map digit character to asset name
function getDigitAssetName(digitChar) {
var digitAssetMap = {
'0': 'Zero',
'1': 'One',
'2': 'Two',
'3': 'Three',
'4': 'Four',
'5': 'Five',
'6': 'Six',
'7': 'Seven',
'8': 'Eight',
'9': 'Nine'
};
return digitAssetMap[digitChar] || 'Zero';
}
// Function to create digit display using number assets
function createDigitDisplay(number, x, y, scale) {
var digits = [];
var numStr = number.toString();
var digitSpacing = 60;
var startX = x - (numStr.length - 1) * digitSpacing / 2;
for (var i = 0; i < numStr.length; i++) {
var digitChar = numStr[i];
var assetName = getDigitAssetName(digitChar);
var digit = LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
x: startX + i * digitSpacing,
y: y,
scaleX: scale || 1,
scaleY: scale || 1
});
uiContainer.addChild(digit);
digits.push(digit);
}
return {
digits: digits,
update: function update(newNumber) {
// Destroy old digits
for (var j = 0; j < digits.length; j++) {
if (digits[j] && digits[j].parent) {
digits[j].destroy();
}
}
digits = [];
// Create new digits
var newNumStr = newNumber.toString();
var newStartX = x - (newNumStr.length - 1) * digitSpacing / 2;
for (var k = 0; k < newNumStr.length; k++) {
var digitChar = newNumStr[k];
var assetName = getDigitAssetName(digitChar);
var digit = LK.getAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5,
x: newStartX + k * digitSpacing,
y: y,
scaleX: scale || 1,
scaleY: scale || 1
});
uiContainer.addChild(digit);
digits.push(digit);
}
},
destroy: function destroy() {
for (var j = 0; j < digits.length; j++) {
if (digits[j] && digits[j].parent) {
digits[j].destroy();
}
}
digits = [];
}
};
}
// Add score display at top left using Score asset as label
var scoreLabel = LK.getAsset('Score', {
anchorX: 0,
anchorY: 0,
x: 650,
y: 50
});
uiContainer.addChild(scoreLabel);
// Create digit display for score
scoreDisplay = createDigitDisplay(gameState.player.score, 750, 175, 0.8);
// Add cash display using Cash asset as label
var cashLabel = LK.getAsset('Cash', {
anchorX: 0.5,
anchorY: 0.5,
x: 1175,
y: 170
});
uiContainer.addChild(cashLabel);
// Create digit display for cash
cashDisplay = createDigitDisplay(gameState.player.cash, 1324, 170, 0.6);
// Add lives asset as label for lives digit display
var livesLabel = LK.getAsset('Lives', {
anchorX: 0.5,
anchorY: 0.5,
x: 1224,
y: 50
});
uiContainer.addChild(livesLabel);
// Create digit display for lives
livesDisplay = createDigitDisplay(gameState.player.lives, 1324, 50, 0.7);
// Add speed up button to top of screen left of score asset using X1speed asset
var speedUpButton = LK.getAsset('X1speed', {
anchorX: 0.5,
anchorY: 0.5,
x: 400,
y: 75
});
uiContainer.addChild(speedUpButton);
// Function to update speed button appearance and add click handler
function updateSpeedButton() {
speedUpButton.down = function (x, y, obj) {
if (gameSpeedMultiplier === 1) {
// Switch to double speed
gameSpeedMultiplier = 2;
// Replace with X2speed asset
speedUpButton.destroy();
speedUpButton = LK.getAsset('X2speed', {
anchorX: 0.5,
anchorY: 0.5,
x: 400,
y: 75
});
uiContainer.addChild(speedUpButton);
updateSpeedButton(); // Re-add click handler to new button
} else {
// Switch to normal speed
gameSpeedMultiplier = 1;
// Replace with X1speed asset
speedUpButton.destroy();
speedUpButton = LK.getAsset('X1speed', {
anchorX: 0.5,
anchorY: 0.5,
x: 400,
y: 75
});
uiContainer.addChild(speedUpButton);
updateSpeedButton(); // Re-add click handler to new button
}
};
}
// Initialize the speed button with click handler
updateSpeedButton();
// Add skip wave button under score display
var skipWaveButton = new Text2('SKIP WAVE', {
size: 40,
fill: 0xFFFF00
});
skipWaveButton.anchor.set(0, 0);
skipWaveButton.x = 400;
skipWaveButton.y = 420;
uiContainer.addChild(skipWaveButton);
// Consolidated click handler function to handle different click scenarios
function handleGameClick(x, y, obj, clickType) {
clickType = clickType || 'down';
// Handle tower placement cancellation on empty space
if (clickType === 'down' && placingTower && obj === game) {
cleanupTowerPlacement();
}
// Handle story asset click transitions
if (clickType === 'storyTransition' && obj && obj.destroy) {
// Fade out story asset
tween(obj, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeInOut,
onFinish: function onFinish() {
obj.destroy();
// Continue to next level if this was story5
if (obj.assetType === 'Story5') {
startLevel2();
}
}
});
}
}
// Add global click handler to cancel tower placement
game.down = function (x, y, obj) {
handleGameClick(x, y, obj, 'down');
};
// Integrate UI configuration into main config
GAME_CONFIG.ui = {
towerButtons: {
y: 2400,
spacing: 50,
startX: 430,
scale: 0.8,
secondRowOffset: 150
},
displays: {
score: {
labelX: 650,
labelY: 50,
valueX: 750,
valueY: 175,
scale: 0.8
},
cash: {
labelX: 1175,
labelY: 170,
valueX: 1324,
valueY: 170,
scale: 0.6
},
lives: {
labelX: 1224,
labelY: 50,
valueX: 1324,
valueY: 50,
scale: 0.7
},
speedButton: {
x: 400,
y: 75
},
skipWave: {
x: 400,
y: 420,
size: 40,
color: 0xFFFF00
}
},
digitSpacing: 60,
confirmButton: {
offsetX: 60,
offsetY: -60
},
cancelButton: {
offsetX: 60,
offsetY: 80 + 25 + 30
}
};
// Legacy aliases for backward compatibility
var uiConfig = GAME_CONFIG.ui;
var towerButtonConfig = GAME_CONFIG.ui.towerButtons;
// Level Management System
var LEVEL_MANAGER = {
// Current level state
currentLevel: 1,
levelInProgress: false,
levelStartTime: 0,
// Level initialization
initializeLevel: function initializeLevel(levelNumber) {
if (!GAME_CONFIG.levels[levelNumber]) {
console.error('Level ' + levelNumber + ' not found in configuration');
return false;
}
var levelConfig = GAME_CONFIG.levels[levelNumber];
this.currentLevel = levelNumber;
this.levelInProgress = true;
this.levelStartTime = Date.now();
// Reset game state for new level
this.resetLevelState();
// Load level-specific configurations
this.loadLevelAssets(levelConfig);
this.setupLevelMechanics(levelConfig);
return true;
},
// Reset game state for level start
resetLevelState: function resetLevelState() {
// Clear existing enemies and towers
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] && enemies[i].destroy) {
enemies[i].destroy();
}
}
enemies = [];
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] && towers[i].destroy) {
towers[i].destroy();
}
}
towers = [];
// Reset wave state
gameState.wave.active = false;
gameState.wave.current = 1;
gameState.wave.enemiesSpawned = 0;
gameState.wave.enemiesDefeated = 0;
gameState.wave.spawnTimer = 0;
gameState.wave.firstEnemySpawned = false;
// Apply level-specific starting resources
var levelConfig = GAME_CONFIG.levels[this.currentLevel];
if (levelConfig.startingResources) {
gameState.player.cash = levelConfig.startingResources.cash || 50;
gameState.player.lives = levelConfig.startingResources.lives || 10;
} else {
gameState.player.cash = 50;
gameState.player.lives = 10;
}
},
// Load level-specific assets and background
loadLevelAssets: function loadLevelAssets(levelConfig) {
// Set background
if (levelConfig.background) {
var background = game.addChild(LK.getAsset(levelConfig.background.asset, {
anchorX: 0.5,
anchorY: 0.5,
x: levelConfig.background.position.x,
y: levelConfig.background.position.y,
scaleX: levelConfig.background.scale.x,
scaleY: levelConfig.background.scale.y,
alpha: 0
}));
// Fade in background
tween(background, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut
});
}
// Setup guidelines
this.setupGuidelines(levelConfig);
},
// Setup level-specific mechanics
setupLevelMechanics: function setupLevelMechanics(levelConfig) {
// Apply tower cost modifiers
if (levelConfig.towerCostModifier && levelConfig.towerCostModifier !== 1.0) {
for (var towerType in GAME_CONFIG.towers) {
var tower = GAME_CONFIG.towers[towerType];
tower.modifiedCost = Math.floor(tower.cost * levelConfig.towerCostModifier);
}
}
// Enable/disable specific towers based on level
this.updateAvailableTowers(levelConfig.availableTowers);
// Setup special mechanics
if (levelConfig.specialMechanics) {
this.applySpecialMechanics(levelConfig.specialMechanics);
}
},
// Setup level-specific guidelines
setupGuidelines: function setupGuidelines(levelConfig) {
// Clear existing guidelines
if (gameObjects.guidelines) {
for (var i = 0; i < gameObjects.guidelines.length; i++) {
if (gameObjects.guidelines[i] && gameObjects.guidelines[i].destroy) {
gameObjects.guidelines[i].destroy();
}
}
}
gameObjects.guidelines = [];
// Create new guidelines based on level layout
var guidelineLayout = levelConfig.specialMechanics.guidelineLayout || 'standard';
this.createGuidelineLayout(guidelineLayout);
},
// Create different guideline layouts
createGuidelineLayout: function createGuidelineLayout(layoutType) {
var lineSpacing = GAME_CONFIG.guidelines.screenHeight / 7;
var guidelines = [];
for (var i = 1; i <= GAME_CONFIG.guidelines.horizontalCount; i++) {
var yPosition = lineSpacing * i;
// Apply layout-specific modifications
if (layoutType === 'industrial') {
// Tighter spacing for industrial level
yPosition = yPosition * 0.9;
} else if (layoutType === 'zero_gravity') {
// Curved guidelines for space level
yPosition += Math.sin(i * 0.5) * 100;
}
// Apply standard offsets
if (GAME_CONFIG.guidelines.offsets[i]) {
yPosition += GAME_CONFIG.guidelines.offsets[i];
}
var guideLine = game.addChild(LK.getAsset('guideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: GAME_CONFIG.guidelines.screenWidth / 2,
y: yPosition,
alpha: GAME_CONFIG.guidelines.alpha
}));
guidelines.push(guideLine);
gameObjects.guidelineYPositions.push(yPosition);
}
gameObjects.guidelines = guidelines;
},
// Update available towers for level
updateAvailableTowers: function updateAvailableTowers(availableTowers) {
// Hide/show tower buttons based on availability
// This would integrate with the existing tower button system
var allTowerButtons = [waterTowerButton, gasTowerButton, basicTowerButton, electricTowerButton, advancedTowerButton, wifiTowerButton, fireButton, slingshotTowerButton];
var towerMapping = {
'water': waterTowerButton,
'gas': gasTowerButton,
'air': basicTowerButton,
'electric': electricTowerButton,
'plasma': advancedTowerButton,
'wifi': wifiTowerButton,
'fire': fireButton,
'slingshot': slingshotTowerButton
};
// Hide all towers first
for (var i = 0; i < allTowerButtons.length; i++) {
if (allTowerButtons[i]) {
allTowerButtons[i].alpha = 0.3; // Dim unavailable towers
}
}
// Show available towers
for (var j = 0; j < availableTowers.length; j++) {
var towerType = availableTowers[j];
if (towerMapping[towerType]) {
towerMapping[towerType].alpha = 1.0; // Full opacity for available towers
}
}
},
// Apply special level mechanics
applySpecialMechanics: function applySpecialMechanics(mechanics) {
if (mechanics.environmentalHazards) {
this.enableEnvironmentalHazards();
}
if (mechanics.enhancedEnemyAI) {
this.enableEnhancedEnemyAI();
}
if (mechanics.gravityWells) {
this.enableGravityWells();
}
if (mechanics.energyShields) {
this.enableEnergyShields();
}
},
// Environmental hazards system
enableEnvironmentalHazards: function enableEnvironmentalHazards() {
// Placeholder for environmental hazard implementation
// Could include obstacles, moving platforms, etc.
},
// Enhanced enemy AI system
enableEnhancedEnemyAI: function enableEnhancedEnemyAI() {
// Placeholder for enhanced AI behaviors
// Could include path adaptation, target prioritization, etc.
},
// Gravity wells system for space level
enableGravityWells: function enableGravityWells() {
// Placeholder for gravity well implementation
// Could affect enemy movement patterns
},
// Energy shields system
enableEnergyShields: function enableEnergyShields() {
// Placeholder for energy shield implementation
// Could provide temporary tower protection
},
// Level completion check
checkLevelCompletion: function checkLevelCompletion() {
var levelConfig = GAME_CONFIG.levels[this.currentLevel];
// Check if all waves are completed
if (gameState.wave.current > levelConfig.totalWaves && enemies.length === 0) {
this.completeLevelSuccessfully();
return true;
}
return false;
},
// Handle successful level completion
completeLevelSuccessfully: function completeLevelSuccessfully() {
this.levelInProgress = false;
var completionTime = Date.now() - this.levelStartTime;
var levelConfig = GAME_CONFIG.levels[this.currentLevel];
// Award completion rewards
if (levelConfig.completionReward) {
gameState.player.score += levelConfig.completionReward.score;
gameState.player.cash += levelConfig.completionReward.cash;
}
// Update level progress
GAME_CONFIG.metadata.levelProgress[this.currentLevel].completed = true;
GAME_CONFIG.metadata.levelProgress[this.currentLevel].bestScore = Math.max(GAME_CONFIG.metadata.levelProgress[this.currentLevel].bestScore, gameState.player.score);
// Unlock next level
if (this.currentLevel < 3) {
GAME_CONFIG.metadata.highestUnlocked = Math.max(GAME_CONFIG.metadata.highestUnlocked, this.currentLevel + 1);
}
// Show level completion UI or transition to next level
this.showLevelCompletionScreen();
},
// Show level completion screen
showLevelCompletionScreen: function showLevelCompletionScreen() {
// Create level completion display
var completionScreen = new Container();
LK.gui.addChild(completionScreen);
var completionText = new Text2('LEVEL ' + this.currentLevel + ' COMPLETE!', {
size: 80,
fill: 0x00FF00
});
completionText.anchor.set(0.5, 0.5);
completionText.x = 1024;
completionText.y = 400;
completionScreen.addChild(completionText);
// Add continue button for next level
if (this.currentLevel < 3) {
var continueButton = new Text2('CONTINUE TO LEVEL ' + (this.currentLevel + 1), {
size: 50,
fill: 0xFFFFFF
});
continueButton.anchor.set(0.5, 0.5);
continueButton.x = 1024;
continueButton.y = 600;
continueButton.down = function () {
completionScreen.destroy();
LEVEL_MANAGER.initializeLevel(LEVEL_MANAGER.currentLevel + 1);
};
completionScreen.addChild(continueButton);
}
// Auto-remove screen after delay
LK.setTimeout(function () {
if (completionScreen && completionScreen.parent) {
completionScreen.destroy();
}
}, 5000);
},
// Handle level failure
handleLevelFailure: function handleLevelFailure() {
this.levelInProgress = false;
// Reset to checkpoint or restart level
this.showLevelFailureScreen();
},
// Show level failure screen
showLevelFailureScreen: function showLevelFailureScreen() {
// Create level failure display
var failureScreen = new Container();
LK.gui.addChild(failureScreen);
var failureText = new Text2('LEVEL ' + this.currentLevel + ' FAILED', {
size: 80,
fill: 0xFF0000
});
failureText.anchor.set(0.5, 0.5);
failureText.x = 1024;
failureText.y = 400;
failureScreen.addChild(failureText);
// Add retry button
var retryButton = new Text2('RETRY LEVEL', {
size: 50,
fill: 0xFFFFFF
});
retryButton.anchor.set(0.5, 0.5);
retryButton.x = 1024;
retryButton.y = 600;
retryButton.down = function () {
failureScreen.destroy();
LEVEL_MANAGER.initializeLevel(LEVEL_MANAGER.currentLevel);
};
failureScreen.addChild(retryButton);
// Auto-remove screen after delay
LK.setTimeout(function () {
if (failureScreen && failureScreen.parent) {
failureScreen.destroy();
}
}, 5000);
}
};
// Extend utility functions with level-aware methods
utilityFunctions.getCurrentLevelConfig = function () {
return GAME_CONFIG.levels[LEVEL_MANAGER.currentLevel];
};
utilityFunctions.isLevelUnlocked = function (levelNumber) {
return levelNumber <= GAME_CONFIG.metadata.highestUnlocked;
};
utilityFunctions.applyLevelModifiers = function (enemyType, baseStats) {
var levelConfig = utilityFunctions.getCurrentLevelConfig();
var enemyConfig = GAME_CONFIG.enemies[enemyType];
if (enemyConfig.levelVariations && enemyConfig.levelVariations[LEVEL_MANAGER.currentLevel]) {
var variation = enemyConfig.levelVariations[LEVEL_MANAGER.currentLevel];
var modifiedStats = {
health: Math.floor(baseStats.health * (variation.healthMultiplier || 1.0)),
maxHealth: Math.floor(baseStats.maxHealth * (variation.healthMultiplier || 1.0)),
speed: baseStats.speed * (variation.speedMultiplier || 1.0),
damage: baseStats.damage ? Math.floor(baseStats.damage * (variation.damageMultiplier || 1.0)) : baseStats.damage
};
return modifiedStats;
}
return baseStats;
};
// Reusable tower button creation function
function createTowerButton(buttonAssetName, x, y, config) {
var button = game.addChild(LK.getAsset(buttonAssetName, {
anchorX: 0.5,
anchorY: 0.5,
x: x,
y: y,
scaleX: towerButtonConfig.scale,
scaleY: towerButtonConfig.scale
}));
button.down = function (bx, by, obj) {
if (gameState.player.cash >= config.cost && !placingTower) {
LK.getSound('Towerselect').play();
// Start tower placement mode - update both systems
placingTower = true;
gameState.placement.active = true;
pendingTowerCost = config.cost;
gameState.placement.pendingCost = config.cost;
// Calculate snap positions and show guidelines
utilityFunctions.calculateSnapPositions();
utilityFunctions.showVerticalGuidelines();
// Find initial snap position
var snapPos = utilityFunctions.findNearestSnapPosition(bx, by);
// Create tower preview
towerPreview = game.addChild(LK.getAsset(config.towerAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: snapPos.x,
y: snapPos.y,
alpha: 0.7
}));
gameState.placement.preview = towerPreview;
// Create blade preview for air towers
if (config.towerType === 'air') {
towerPreview.bladePreview = game.addChild(LK.getAsset('Blades', {
anchorX: 0.5,
anchorY: 0.5,
x: snapPos.x,
y: snapPos.y - 70,
alpha: 0.7
}));
}
// Create placement buttons using consolidated function
var buttons = createPlacementButtons(snapPos, config);
confirmButton = buttons.confirm;
cancelButton = buttons.cancel;
gameState.placement.confirmButton = confirmButton;
gameState.placement.cancelButton = cancelButton;
}
};
return button;
}
// Helper function to start tower placement
function startTowerPlacement(cost, towerType, towerAsset, properties) {
gameState.placement.active = true;
gameState.placement.pendingCost = cost;
utilityFunctions.calculateSnapPositions();
utilityFunctions.showVerticalGuidelines();
createTowerPreview(towerAsset, towerType, properties);
}
// Helper function to create tower preview
function createTowerPreview(towerAsset, towerType, properties) {
var snapPos = utilityFunctions.findNearestSnapPosition(1024, 1366);
gameState.placement.preview = game.addChild(LK.getAsset(towerAsset, {
anchorX: 0.5,
anchorY: 0.5,
x: snapPos.x,
y: snapPos.y,
alpha: 0.7
}));
if (towerType === 'air') {
gameState.placement.preview.bladePreview = game.addChild(LK.getAsset('Blades', {
anchorX: 0.5,
anchorY: 0.5,
x: snapPos.x,
y: snapPos.y - 70,
alpha: 0.7
}));
}
createConfirmCancelButtons(snapPos);
}
// Consolidated function to handle placement button creation and event handling
function createPlacementButtons(snapPos, towerConfig) {
// Create confirmation button
var confirmBtn = game.addChild(LK.getAsset('ConfirmButton', {
anchorX: 0.5,
anchorY: 0.5,
x: snapPos.x + uiConfig.confirmButton.offsetX,
y: snapPos.y + uiConfig.confirmButton.offsetY,
zIndex: 2000
}));
// Create cancellation button
var cancelBtn = game.addChild(LK.getAsset('Cancelbutton', {
anchorX: 0.5,
anchorY: 0.5,
x: snapPos.x + uiConfig.cancelButton.offsetX,
y: snapPos.y + uiConfig.cancelButton.offsetY,
zIndex: 2000
}));
// Consolidated cancel handler
cancelBtn.down = function () {
handlePlacementCancel();
};
// Consolidated confirm handler
confirmBtn.down = function () {
handlePlacementConfirm(towerConfig);
};
return {
confirm: confirmBtn,
cancel: cancelBtn
};
}
// Consolidated placement cancellation handler
function handlePlacementCancel() {
cleanupTowerPlacement();
}
// Consolidated placement confirmation handler
function handlePlacementConfirm(towerConfig) {
if (!placingTower || !towerPreview) return;
// Deduct cost from both systems
gameState.player.cash -= pendingTowerCost;
playerCash = gameState.player.cash;
if (cashDisplay) {
cashDisplay.update(gameState.player.cash);
}
// Create tower from preview
var newTower = createTowerFromPlacement(towerConfig);
game.addChild(newTower);
towers.push(newTower);
sortGameObjectsByY();
// Clean up and play sound
cleanupTowerPlacement();
LK.getSound('Confirm').play();
}
// Helper function to create confirm/cancel buttons (legacy wrapper)
function createConfirmCancelButtons(snapPos) {
var buttons = createPlacementButtons(snapPos, {});
confirmButton = buttons.confirm;
cancelButton = buttons.cancel;
}
// Helper function to cleanup tower placement
function cleanupTowerPlacement() {
if (towerPreview) {
if (towerPreview.bladePreview) {
towerPreview.bladePreview.destroy();
}
towerPreview.destroy();
towerPreview = null;
}
if (confirmButton) {
confirmButton.destroy();
confirmButton = null;
}
if (cancelButton) {
cancelButton.destroy();
cancelButton = null;
}
hideVerticalGuidelines();
placingTower = false;
}
// Helper function to confirm tower placement
function confirmTowerPlacement() {
if (!placingTower || !towerPreview) return;
playerCash -= pendingTowerCost;
if (cashDisplay) {
cashDisplay.update(playerCash);
}
var newTower = createTowerFromPreview();
game.addChild(newTower);
towers.push(newTower);
sortGameObjectsByY();
cleanupTowerPlacement();
pendingTowerCost = 10;
LK.getSound('Confirm').play();
}
// Helper function to create tower from preview (legacy)
function createTowerFromPreview() {
return createTowerFromPlacement({});
}
// Consolidated enemy asset cleanup function
function cleanupEnemyAssets(enemy) {
// Clean up main graphics
if (enemy.enemyGraphics && enemy.enemyGraphics.parent) {
enemy.enemyGraphics.destroy();
enemy.enemyGraphics = null;
}
// Clean up wheel graphics
if (enemy.leftWheel && enemy.leftWheel.parent) {
enemy.leftWheel.destroy();
enemy.leftWheel = null;
}
if (enemy.rightWheel && enemy.rightWheel.parent) {
enemy.rightWheel.destroy();
enemy.rightWheel = null;
}
// Clean up roboarm graphics
if (enemy.roboarm && enemy.roboarm.parent) {
enemy.roboarm.destroy();
enemy.roboarm = null;
}
}
// Consolidated enemy asset recreation function
function recreateEnemyAssets(enemy, assetType) {
enemy.assetType = assetType;
enemy.enemyGraphics = enemy.attachAsset(assetType, {
anchorX: 0.5,
anchorY: 0.5
});
// Add special assets for Robot enemies
if (assetType === 'Robot') {
// Add left wheel
enemy.leftWheel = enemy.attachAsset('Wheel', {
anchorX: 0.5,
anchorY: 0.5,
x: -80,
y: 95,
scaleX: 0.6,
scaleY: 0.6
});
// Move wheel1 (leftWheel) left 25 pixels from its current position
tween(enemy.leftWheel, {
x: -5 // Move from -80 to -5 (75 pixels to the right total, 25 pixels left from original 20)
}, {
duration: 1000,
easing: tween.easeInOut
});
// Add right wheel
enemy.rightWheel = enemy.attachAsset('Wheel', {
anchorX: 0.5,
anchorY: 0.5,
x: 55,
// Move from 80 to 55 (25 pixels left)
y: 95,
scaleX: 0.6,
scaleY: 0.6
});
// Add roboarm attachment
enemy.roboarm = enemy.attachAsset('Roboarm', {
anchorX: 0.5,
anchorY: 0.5,
x: 0,
y: -5,
scaleX: 0.5,
scaleY: 0.5
});
}
// UFO boss special properties
if (assetType === 'UFO') {
enemy.isUFOBoss = true;
enemy.abductionTarget = null;
enemy.isAbducting = false;
enemy.hasHoveredDown = false;
enemy.tractorBeam = null;
enemy.speed = 1; // Slower movement for boss
enemy.targetX = 0; // Move to goal at x=0
}
}
// Helper function to create tower from placement data
function createTowerFromPlacement(config) {
var newTower = new Tower();
var preview = gameState.placement.preview || towerPreview;
newTower.x = preview.x;
newTower.y = preview.y;
newTower.cost = gameState.placement.pendingCost || pendingTowerCost;
newTower.towerType = config.towerType || 'basic';
// Set asset and properties from config
if (config.towerAsset) {
newTower.setAssetType(config.towerAsset);
}
if (config.damage) newTower.damage = config.damage;
if (config.range) newTower.range = config.range;
if (config.fireRate) newTower.fireRate = config.fireRate;
if (config.isTower1) newTower.isTower1 = true;
// Special handling for blade preview (air towers)
if (preview.bladePreview) {
newTower.setAssetType('Fan');
newTower.towerType = 'air';
newTower.damage = 35;
newTower.range = 350;
newTower.fireRate = 30;
}
// Special handling for water towers
if (config.towerType === 'water') {
newTower.attachAsset('Tower1', {
anchorX: 0.5,
anchorY: 0.5
});
}
// Assign to nearest guideline
newTower.guidelineIndex = utilityFunctions.findNearestGuidelineIndex(newTower.y);
return newTower;
}
// Use consolidated configuration for tower button positioning
var startX = GAME_CONFIG.ui.towerButtons.startX;
var towerButtonY = GAME_CONFIG.ui.towerButtons.y;
var buttonSpacing = GAME_CONFIG.ui.towerButtons.spacing;
// Create all tower buttons using consolidated function
var waterTowerButton = createTowerButton('Waterbutton', startX, towerButtonY, {
cost: 10,
towerType: 'water',
towerAsset: 'Tower1',
damage: 35,
range: 650,
fireRate: 60,
isTower1: true
});
var gasTowerButton = createTowerButton('Gasbutton', startX + 341 + buttonSpacing, towerButtonY, {
cost: 20,
towerType: 'dog',
towerAsset: 'Dog',
damage: 30,
range: 600,
fireRate: 45
});
var basicTowerButton = createTowerButton('Airbutton', startX + 341 + buttonSpacing + 341 + buttonSpacing, towerButtonY, {
cost: 30,
towerType: 'air',
towerAsset: 'Fan',
damage: 35,
range: 350,
fireRate: 30
});
var electricTowerButton = createTowerButton('Electricbutton', startX, towerButtonY + 150 + buttonSpacing, {
cost: 50,
towerType: 'electric',
towerAsset: 'Bugzapper',
damage: 60,
range: 350,
fireRate: 40
});
var advancedTowerButton = createTowerButton('Plasmabutton', startX + 341 + buttonSpacing, towerButtonY + 150 + buttonSpacing, {
cost: 60,
towerType: 'plasma',
towerAsset: 'Plasmaball',
damage: 80,
range: 400,
fireRate: 60
});
var wifiTowerButton = createTowerButton('Wifibutton', startX + 341 + buttonSpacing + 360 + buttonSpacing, towerButtonY + 150 + buttonSpacing, {
cost: 80,
towerType: 'wifi',
towerAsset: 'Wifitower',
damage: 100,
range: 600,
fireRate: 30
});
var fireButton = createTowerButton('Firebutton', startX + 341 + buttonSpacing + 341 + buttonSpacing + 400, towerButtonY, {
cost: 40,
towerType: 'fire',
towerAsset: 'Fireworks',
damage: 40,
range: 400,
fireRate: 50
});
var slingshotTowerButton = createTowerButton('Slingshotbutton', startX + 341 + buttonSpacing + 360 + buttonSpacing + 340 + buttonSpacing, towerButtonY + 150 + buttonSpacing, {
cost: 35,
towerType: 'slingshot',
towerAsset: 'Slingshot',
damage: 80,
range: 1300,
fireRate: 120
});
// Tower button click handlers are now consolidated into the createTowerButton function
// Add click handler to skip wave button
skipWaveButton.down = function (x, y, obj) {
// Force wave completion by clearing all enemies and setting appropriate flags
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
enemiesSpawned = maxEnemiesInWave;
enemiesDefeated = maxEnemiesInWave;
// Check which wave we're skipping and trigger appropriate transition
if (currentWave === 1) {
showWaveTransition(2, startWave2);
} else if (currentWave === 2) {
showWaveTransition(3, startWave3);
} else if (currentWave === 3) {
showWaveTransition(4, startWave4);
} else if (currentWave === 4) {
showWaveTransition(5, startWave5);
} else if (currentWave === 5) {
// UFO boss defeated - transition to story5 cutscene
var story5Asset = game.addChild(LK.getAsset('Story5', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Fade in story5 asset
tween(story5Asset, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Add click handler to story5 to transition to level2
story5Asset.down = function (x, y, obj) {
story5Asset.assetType = 'Story5';
handleGameClick(x, y, story5Asset, 'storyTransition');
};
}
});
} else {
waveActive = false;
}
};
}
// Start second wave of tower defense
function startWave2() {
startWave(2, waveConfig[2]);
}
// Start third wave of tower defense
function startWave3() {
startWave(3, waveConfig[3]);
}
// Start fourth wave of tower defense
function startWave4() {
startWave(4, waveConfig[4]);
}
// Start fifth wave of tower defense (boss stage)
function startWave5() {
startWave(5, waveConfig[5]);
}
// Spawn enemy on random guideline
function spawnEnemy() {
if (guidelineYPositions.length === 0) return;
// Pick random guideline with better distribution
var randomIndex = Math.floor(Math.random() * guidelineYPositions.length);
var spawnY = guidelineYPositions[randomIndex];
// Add some vertical variation to make movement less predictable
var verticalVariation = (Math.random() - 0.5) * 60; // +/- 30 pixels variation
spawnY += verticalVariation;
// Create new enemy
var enemy = new Enemy();
// Set asset type and health based on current wave
if (currentWave === 1) {
// Probedroids in wave 1 - make them slightly stronger
enemy.health = 150;
enemy.maxHealth = 150;
} else if (currentWave === 2) {
enemy.setAssetType('Drone');
enemy.health = 200;
enemy.maxHealth = 200;
enemy.speed = 2; // Reduced drone speed
} else if (currentWave === 3) {
enemy.setAssetType('SpaceDrone');
enemy.health = 250;
enemy.maxHealth = 250;
enemy.speed = 2.5; // Reduced SpaceDrone speed
} else if (currentWave === 4) {
enemy.setAssetType('Robot');
enemy.health = 350;
enemy.maxHealth = 350;
enemy.speed = 4; // Make robots slightly faster
} else if (currentWave === 5) {
enemy.setAssetType('UFO');
enemy.health = 5000;
enemy.maxHealth = 5000;
// UFO boss starts at top center of screen
enemy.x = 2048 / 2;
enemy.y = -200; // Start above screen
enemy.baseY = 2732 / 2; // Target center of screen
// Assign UFO boss to special guideline index -1 to indicate it can be targeted by all towers
enemy.guidelineIndex = -1;
// Add UFO to game before early return
enemies.push(enemy);
game.addChild(enemy);
return; // Skip normal positioning for UFO boss
}
enemy.x = 2048 + 100; // Start off right side of screen
enemy.y = spawnY;
// Store which guideline this enemy is following for reference
enemy.guidelineIndex = randomIndex;
enemy.baseY = guidelineYPositions[randomIndex];
enemies.push(enemy);
game.addChild(enemy);
// Play probedroid sound when first enemy appears
if (!firstEnemySpawned) {
LK.getSound('Probedroid').play();
firstEnemySpawned = true;
}
// Play probedroid2 sound when first robot appears in wave4
if (currentWave === 4 && enemiesSpawned === 0) {
LK.getSound('Probedroid2').play();
}
}
// Add tower placement move handler
game.move = function (x, y, obj) {
if (placingTower && towerPreview) {
// Find nearest snap position
var snapPos = utilityFunctions.findNearestSnapPosition(x, y);
towerPreview.x = snapPos.x;
towerPreview.y = snapPos.y;
// Update blade preview position if it exists
if (towerPreview.bladePreview) {
towerPreview.bladePreview.x = snapPos.x;
towerPreview.bladePreview.y = snapPos.y - 70;
}
if (confirmButton) {
confirmButton.x = snapPos.x + 60;
confirmButton.y = snapPos.y - 60;
}
// Update cancel button position if it exists
if (cancelButton) {
cancelButton.x = snapPos.x + 60;
cancelButton.y = snapPos.y - 60 + 80 + 25 + 30;
}
}
// Slingshot aiming is now handled in Tower class move handler
};
// Function to sort game objects by Y position (objects with lower Y appear in front)
function sortGameObjectsByY() {
// Collect all towers and enemies with their Y positions
var allObjects = [];
// Add towers to sorting array
for (var i = 0; i < towers.length; i++) {
if (towers[i].parent) {
allObjects.push({
object: towers[i],
y: towers[i].y,
type: 'tower'
});
}
}
// Add enemies to sorting array
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].parent) {
allObjects.push({
object: enemies[i],
y: enemies[i].y,
type: 'enemy'
});
}
}
// Sort by Y position (higher Y values first, lower Y values last)
// Objects with higher Y values (lower on screen) will be rendered behind (lower zIndex)
// Objects with lower Y values (higher on screen) will be rendered in front (higher zIndex)
allObjects.sort(function (a, b) {
var yDiff = a.y - b.y; // Normal sort: lower Y first, higher Y last
if (yDiff !== 0) {
return yDiff;
}
// If Y positions are equal, maintain consistent ordering by type
return a.type === 'tower' ? -1 : 1;
});
// Set zIndex based on sorted order to ensure proper depth
// Start with a lower base zIndex to avoid conflicts with UI elements like confirm/cancel buttons
var baseZIndex = 50;
for (var i = 0; i < allObjects.length; i++) {
var obj = allObjects[i].object;
if (obj.parent) {
// Set zIndex based on position in sorted array
// Objects earlier in the array (lower Y positions) get higher zIndex (rendered in front)
// Objects later in the array (higher Y positions) get lower zIndex (rendered behind)
var newZIndex = baseZIndex + (allObjects.length - i);
obj.zIndex = newZIndex;
// Force immediate visual update by temporarily removing and re-adding to parent
var parent = obj.parent;
parent.removeChild(obj);
parent.addChild(obj);
}
}
}
// Unified function to show wave transition with fade in/out effects
function showWaveTransition(waveNumber, callback) {
var waveAsset = game.addChild(LK.getAsset('Wave' + waveNumber, {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0,
zIndex: 1000
}));
var duration = waveNumber === 3 ? 750 : 1500;
var holdTime = waveNumber === 3 ? 1500 : 0;
tween(waveAsset, {
alpha: 1
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
var fadeOut = function fadeOut() {
tween(waveAsset, {
alpha: 0
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
waveAsset.destroy();
callback();
}
});
};
if (holdTime > 0) {
LK.setTimeout(fadeOut, holdTime);
} else {
fadeOut();
}
}
});
}
// Update tower defense game
game.update = function () {
// Only operate when wave is active
if (!gameState.wave.active) return;
// Cache frequently accessed values to avoid repeated property lookups
var enemiesLength = enemies.length;
var towersLength = towers.length;
var currentTick = LK.ticks;
// Only sort game objects every 5th frame instead of every frame to reduce CPU load
if (currentTick % 5 === 0) {
sortGameObjectsByY();
}
// Spawn enemies - cache speed multiplier calculation
var adjustedSpawnTimer = gameSpeedMultiplier;
if (enemiesSpawned < maxEnemiesInWave) {
enemySpawnTimer += adjustedSpawnTimer;
if (enemySpawnTimer >= enemySpawnInterval) {
spawnEnemy();
enemiesSpawned++;
enemySpawnTimer = 0;
}
}
// Check if wave is complete - use cached length value
if (enemiesSpawned >= maxEnemiesInWave && enemiesLength === 0) {
// Wave complete - transition to next wave
waveActive = false;
// Award player $50 for completing wave
playerCash += 50;
if (cashDisplay) {
cashDisplay.update(playerCash);
}
// Use cached currentWave value and switch statement for better performance
switch (currentWave) {
case 1:
showWaveTransition(2, startWave2);
break;
case 2:
showWaveTransition(3, startWave3);
break;
case 3:
showWaveTransition(4, startWave4);
break;
case 4:
showWaveTransition(5, startWave5);
break;
case 5:
// UFO boss defeated - transition to story5 cutscene
var story5Asset = game.addChild(LK.getAsset('Story5', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0
}));
// Fade in story5 asset
tween(story5Asset, {
alpha: 1
}, {
duration: 1500,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Add click handler to story5 to transition to level2
story5Asset.down = function (x, y, obj) {
story5Asset.assetType = 'Story5';
handleGameClick(x, y, story5Asset, 'storyTransition');
};
}
});
break;
}
}
};
// Start the intro cutscene only if it hasn't been played yet and wave hasn't started
if (!gameState.game.introPlayed && !gameState.wave.active && gameState.wave.current === 1 && gameState.wave.enemiesSpawned === 0 && !gameState.game.started) {
gameState.game.introPlayed = true;
introCutscene.play();
} else if (!gameState.wave.active && gameState.wave.current === 1 && gameState.wave.enemiesSpawned === 0 && (gameState.game.introPlayed || gameState.game.started)) {
// If intro has been played or game has been reset, skip directly to backdrop scene
skipToBackdrop();
}
;
;
// Start level2 with backdrop2
function startLevel2() {
// Clear any existing game elements
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Reset game variables for level2
waveActive = false;
enemySpawnTimer = 0;
enemiesSpawned = 0;
maxEnemiesInWave = 10;
enemySpawnInterval = 420;
firstEnemySpawned = false;
currentWave = 1;
enemiesDefeated = 0;
// Add backdrop2 asset as the new background
var backdrop2Asset = game.addChild(LK.getAsset('Backdrop2', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: 2732 / 2,
alpha: 0,
scaleX: 1.0,
scaleY: 1.0
}));
// Fade in backdrop2
tween(backdrop2Asset, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Level2 is now ready - could start first wave or wait for player input
// For now, level1 is officially over and level2 has begun with backdrop2
}
});
// Recalculate guideline positions for level2
calculateGuidelinePositions();
// Add 6 horizontal guide lines for level2
var lineSpacing = 2732 / 7;
for (var i = 1; i <= 6; i++) {
var yPosition = lineSpacing * i;
if (i === 1) {
yPosition += 525; // Move guideline 1 down by 525 pixels (was 505)
} else if (i === 2) {
yPosition += 395; // Move guideline 2 down by 395 pixels (was 420)
} else if (i === 3) {
yPosition += 225; // Move guideline 3 down by 225 pixels (was 250)
} else if (i === 4) {
yPosition += 95; // Move guideline 4 down by 95 pixels (was 120)
} else if (i === 5) {
yPosition -= 95; // Move guideline 5 up by 95 pixels (was -75)
} else if (i === 6) {
yPosition -= 270; // Move guideline 6 up by 270 pixels (was -250)
}
var guideLine = game.addChild(LK.getAsset('guideLine', {
anchorX: 0.5,
anchorY: 0.5,
x: 2048 / 2,
y: yPosition,
alpha: 0.5
}));
}
} ===================================================================
--- original.js
+++ change.js
@@ -1728,12 +1728,42 @@
/****
* Game Code
****/
// Consolidated Configuration System
+function _typeof(o) {
+ "@babel/helpers - typeof";
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
+ return typeof o;
+ } : function (o) {
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
+ }, _typeof(o);
+}
+function _defineProperty(e, r, t) {
+ return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
+ value: t,
+ enumerable: !0,
+ configurable: !0,
+ writable: !0
+ }) : e[r] = t, e;
+}
+function _toPropertyKey(t) {
+ var i = _toPrimitive(t, "string");
+ return "symbol" == _typeof(i) ? i : i + "";
+}
+function _toPrimitive(t, r) {
+ if ("object" != _typeof(t) || !t) return t;
+ var e = t[Symbol.toPrimitive];
+ if (void 0 !== e) {
+ var i = e.call(t, r || "default");
+ if ("object" != _typeof(i)) return i;
+ throw new TypeError("@@toPrimitive must return a primitive value.");
+ }
+ return ("string" === r ? String : Number)(t);
+}
var GAME_CONFIG = {
// Level-specific configurations
levels: {
- 1: {
+ 1: _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({
name: 'Urban Defense',
background: {
asset: 'backdrop',
scale: {
@@ -1792,11 +1822,36 @@
environmentalHazards: false,
tutorialEnabled: true,
introMusic: 'Intro'
}
- },
- 2: {
+ }, "name", 'Urban Defense'), "displayName", 'Level 1: Urban Defense'), "description", 'Defend the city from the first wave of alien invaders'), "unlockRequirement", null), "unlockConditions", {
+ previousLevelComplete: false,
+ minimumScore: 0,
+ minimumStars: 0
+ }), "completionReward", {
+ score: 500,
+ cash: 100,
+ experience: 100,
+ unlockedTowers: ['mirror'],
+ unlockedUpgrades: ['range_boost_1']
+ }), "starRequirements", {
+ oneStar: {
+ condition: 'complete',
+ value: true
+ },
+ twoStar: {
+ condition: 'livesRemaining',
+ value: 5
+ },
+ threeStar: {
+ condition: 'perfectDefense',
+ value: true
+ }
+ }), "difficulty", 'beginner'), "estimatedTime", '10-15 minutes'),
+ 2: _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({
name: 'Industrial Complex',
+ displayName: 'Level 2: Industrial Siege',
+ description: 'Navigate through factory obstacles as stronger enemies attack',
background: {
asset: 'Backdrop2',
scale: {
x: 1.0,
@@ -1847,25 +1902,44 @@
enemyType: 'Giantrobot',
cashReward: 200
}
},
- totalWaves: 6,
- unlockRequirement: 'level1Complete',
- completionReward: {
- score: 1000,
- cash: 200
+ totalWaves: 6
+ }, "displayName", 'Level 2: Industrial Siege'), "description", 'Heavy machinery and armored enemies challenge your defenses'), "unlockRequirement", 'level1Complete'), "unlockConditions", {
+ previousLevelComplete: true,
+ minimumScore: 1000,
+ minimumStars: 1
+ }), "completionReward", {
+ score: 1000,
+ cash: 200,
+ experience: 200,
+ unlockedTowers: ['laser', 'freeze'],
+ unlockedUpgrades: ['damage_boost_1', 'fire_rate_1']
+ }), "starRequirements", {
+ oneStar: {
+ condition: 'complete',
+ value: true
},
- specialMechanics: {
- guidelineLayout: 'industrial',
- enemyBehavior: 'aggressive',
- environmentalHazards: true,
- factoryObstacles: true,
- increasedEnemySpeed: 1.3,
- enhancedEnemyAI: true
- }
- },
- 3: {
+ twoStar: {
+ condition: 'livesRemaining',
+ value: 7
+ },
+ threeStar: {
+ condition: 'timeLimit',
+ value: 900
+ } // 15 minutes
+ }), "difficulty", 'intermediate'), "estimatedTime", '15-20 minutes'), "specialMechanics", {
+ guidelineLayout: 'industrial',
+ enemyBehavior: 'aggressive',
+ environmentalHazards: true,
+ factoryObstacles: true,
+ increasedEnemySpeed: 1.3,
+ enhancedEnemyAI: true
+ }),
+ 3: _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({
name: 'Alien Mothership',
+ displayName: 'Level 3: Final Assault',
+ description: 'Board the alien mothership for the ultimate confrontation',
background: {
asset: 'Mothership',
scale: {
x: 1.2,
@@ -1910,24 +1984,42 @@
enemyType: 'MothershipCore',
cashReward: 500
}
},
- totalWaves: 5,
- unlockRequirement: 'level2Complete',
- completionReward: {
- score: 2000,
- cash: 500
+ totalWaves: 5
+ }, "displayName", 'Level 3: Final Assault'), "description", 'Face the mothership core in zero gravity combat'), "unlockRequirement", 'level2Complete'), "unlockConditions", {
+ previousLevelComplete: true,
+ minimumScore: 2500,
+ minimumStars: 2
+ }), "completionReward", {
+ score: 2000,
+ cash: 500,
+ experience: 500,
+ unlockedTowers: ['quantum', 'antimatter'],
+ unlockedUpgrades: ['master_upgrade'],
+ gameComplete: true
+ }), "starRequirements", {
+ oneStar: {
+ condition: 'complete',
+ value: true
},
- specialMechanics: {
- guidelineLayout: 'zero_gravity',
- enemyBehavior: 'aerial',
- environmentalHazards: true,
- gravityWells: true,
- energyShields: true,
- finalBossSequence: true,
- unlimitedTowerEnergy: true
+ twoStar: {
+ condition: 'livesRemaining',
+ value: 8
+ },
+ threeStar: {
+ condition: 'noTowerLoss',
+ value: true
}
- }
+ }), "difficulty", 'expert'), "estimatedTime", '20-25 minutes'), "specialMechanics", {
+ guidelineLayout: 'zero_gravity',
+ enemyBehavior: 'aerial',
+ environmentalHazards: true,
+ gravityWells: true,
+ energyShields: true,
+ finalBossSequence: true,
+ unlimitedTowerEnergy: true
+ })
},
// Level metadata for progression system
metadata: {
currentLevel: 1,
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
Fire hydrant. In-Game asset. 2d. High contrast. No shadows
Water spraying forward In-Game asset. 2d. High contrast. No shadows
Fan blades symmetrical. In-Game asset. 2d. High contrast. No shadows
Plasma ball. In-Game asset. 2d. High contrast. No shadows
Make picture transparent
Bug zapper on a pole. In-Game asset. 2d. High contrast. No shadows
Probe droid. In-Game asset. 2d. High contrast. No shadows
Space drone. In-Game asset. 2d. High contrast. No shadows
Remove propellers and make them symmetrical
Add more rows to gris
Make this picture with more night sky above the city skyline
Change text to say wave 1
Make button grey and say ??????
Make it say Wave 2
Make it say wave 3
Make it say wave 4
WiFi symbol. In-Game asset. 2d. High contrast. No shadows
explosion effect In-Game asset. 2d. High contrast. No shadows
Make it say wave 5
Remove laser beam
Make button hot pink and say 'Reflect $20'
Make button blue and change text to say 'Water $10' in a retro style font
Make button green and change test to say 'Gas $20'
Make button orange and change test to say 'Fire $40'
Make button very light blue and change test to say 'Air $30'
Make button gold and change text to say 'Electric $50'
Make button purple and change test to say 'Plasma $60'
Make button Teal and change test to say 'Slingshot $100'
Make button silver and change test to say 'WiFi $150'
Remove little kick so it's just a smooth oval shape
Make grid 6x8
Hand should be holding the gun by the Handle
Place laser cannon in both hands holding it like a shotgun
Make it stand still
Remove the words 5g
Make sure spelling in speech bubble is correct "We have found the earthlings weakness"
Fix the spelling of the word Planet
Slingshot. In-Game asset. 2d. High contrast. No shadows
Red button with a 'X' on it. In-Game asset. 2d. High contrast. No shadows
Green button with a tick on it
Fix the spelling of word saw
Display icon that says score sci fi comic style font. In-Game asset. 2d. High contrast. No shadows
Display icon that says cash sci fi comic style font. In-Game asset. 2d. High contrast. No shadows
Display icon that says X2 speed sci fi comic style font. In-Game asset. 2d. High contrast. No shadows
Make it say x1 speed and make the x1 blue
Canvasser
Sound effect
Alien1
Sound effect
Alien2
Sound effect
Alien3
Sound effect
Intro
Music
Probedroid
Sound effect
Probedroid2
Sound effect
Towerselect
Sound effect
Water
Sound effect
Explosion
Sound effect
Confirm
Sound effect
Fart
Sound effect
Electric
Sound effect
Fireball
Sound effect