/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AIPlayer = Container.expand(function (playerIndex) {
var self = Container.call(this);
self.playerIndex = playerIndex;
self.state = 'EXPLORING';
self.hq = null;
self.updateCooldown = 0;
self.attackWaveSize = 15;
self.exploreGroupSize = 10;
self.findHq = function () {
if (self.hq && self.hq.parent) {
return;
}
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === self.playerIndex) {
self.hq = towers[i];
break;
}
}
};
self.getUnits = function (isIdleOnly) {
var myUnits = [];
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex === self.playerIndex) {
if (isIdleOnly && !unit.isIdle) {
continue;
}
myUnits.push(unit);
}
}
return myUnits;
};
self.getTowers = function (ownerIndex) {
var foundTowers = [];
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.isHeadquarters) {
continue;
}
if (ownerIndex === -1 && tower.playerIndex === -1) {
foundTowers.push(tower);
} else if (tower.playerIndex === ownerIndex) {
foundTowers.push(tower);
}
}
return foundTowers;
};
self.isHqUnderAttack = function () {
if (!self.hq) {
return false;
}
if (self.hq.lastHealth === undefined) {
self.hq.lastHealth = self.hq.health;
}
if (self.hq.health < self.hq.lastHealth) {
self.hq.lastHealth = self.hq.health;
return true;
}
self.hq.lastHealth = self.hq.health;
var playerUnits = unitManager.units.filter(function (u) {
return u.ownerIndex === 0;
});
var attackRangeSq = self.hq.getRange() * self.hq.getRange();
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var dx = unit.x - self.hq.x;
var dy = unit.y - self.hq.y;
if (dx * dx + dy * dy < attackRangeSq) {
return true;
}
}
return false;
};
self.findClosestTarget = function (source, targets) {
var closestTarget = null;
var closestDistSq = Infinity;
for (var i = 0; i < targets.length; i++) {
var target = targets[i];
var dx = target.x - source.x;
var dy = target.y - source.y;
var distSq = dx * dx + dy * dy;
if (distSq < closestDistSq) {
closestDistSq = distSq;
closestTarget = target;
}
}
return closestTarget;
};
self.commandUnits = function (units, target) {
if (!target) {
return;
}
var targetGridX = Math.floor((target.x - grid.x) / CELL_SIZE);
var targetGridY = Math.floor((target.y - grid.y) / CELL_SIZE) - mapGridOffset;
for (var i = 0; i < units.length; i++) {
var unit = units[i];
unit.setTarget(targetGridX, targetGridY);
unit.isIdle = false;
}
};
self.update = function () {
if (tutorialActive || !self.hq) {
self.findHq();
return;
}
self.updateCooldown--;
if (self.updateCooldown > 0) {
return;
}
self.updateCooldown = 120;
var idleUnits = self.getUnits(true);
if (self.isHqUnderAttack()) {
self.state = 'DEFENDING';
} else {
var neutralTowers = self.getTowers(-1);
if (neutralTowers.length > 0) {
self.state = 'EXPLORING';
} else {
self.state = 'ATTACKING';
}
}
if (self.state === 'DEFENDING') {
if (self.hq.isProducing) {
self.hq.changeTowerMode();
}
} else if (self.state === 'EXPLORING') {
if (!self.hq.isProducing) {
self.hq.changeTowerMode();
}
if (idleUnits.length >= self.exploreGroupSize) {
var neutralTowers = self.getTowers(-1);
if (neutralTowers.length > 0) {
var targetTower = self.findClosestTarget(self.hq, neutralTowers);
if (targetTower) {
var unitsToCommand = idleUnits.slice(0, self.exploreGroupSize);
self.commandUnits(unitsToCommand, targetTower);
}
}
}
} else if (self.state === 'ATTACKING') {
if (!self.hq.isProducing) {
self.hq.changeTowerMode();
}
if (idleUnits.length >= self.attackWaveSize) {
var playerTowers = self.getTowers(0);
var target;
if (playerTowers.length > 0) {
target = self.findClosestTarget(self.hq, playerTowers);
} else {
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 0) {
target = towers[i];
break;
}
}
}
if (target) {
self.commandUnits(idleUnits, target);
}
}
}
};
return self;
});
var BackgroundManager = Container.expand(function () {
var self = Container.call(this);
self.backgrounds = [];
self.currentBackground = null;
self.animationActive = false;
self.addBackground = function (assetId, config) {
config = config || {};
var background = new Container();
var bgGraphics = background.attachAsset(assetId, {
anchorX: config.anchorX || 0.5,
anchorY: config.anchorY || 0.5,
x: config.x || 0,
y: config.y || 0,
scaleX: config.scaleX || 1,
scaleY: config.scaleY || 1,
alpha: config.alpha !== undefined ? config.alpha : 1,
tint: config.tint || 0xFFFFFF
});
background.config = config;
background.graphics = bgGraphics;
background.assetId = assetId;
self.addChild(background);
self.backgrounds.push(background);
return background;
};
self.removeBackground = function (background) {
var index = self.backgrounds.indexOf(background);
if (index !== -1) {
self.backgrounds.splice(index, 1);
self.removeChild(background);
}
};
self.animateBackground = function (background, properties, config) {
if (!background || !background.graphics) {
return;
}
config = config || {};
var duration = config.duration || 1000;
var easing = config.easing || tween.linear;
var loop = config.loop || false;
var _onFinish = config.onFinish;
var _animateFunc = function animateFunc() {
tween(background.graphics, properties, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
if (loop && self.animationActive) {
if (config.resetOnLoop) {
for (var prop in config.resetOnLoop) {
background.graphics[prop] = config.resetOnLoop[prop];
}
}
_animateFunc();
} else if (_onFinish) {
_onFinish();
}
}
});
};
self.animationActive = true;
_animateFunc();
};
self.stopAnimations = function () {
self.animationActive = false;
for (var i = 0; i < self.backgrounds.length; i++) {
if (self.backgrounds[i].graphics) {
tween.stop(self.backgrounds[i].graphics);
}
}
};
self.fadeToBackground = function (newBackground, duration, onComplete) {
duration = duration || 1000;
if (self.currentBackground && self.currentBackground !== newBackground) {
tween(self.currentBackground.graphics, {
alpha: 0
}, {
duration: duration / 2,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.currentBackground.visible = false;
}
});
}
newBackground.visible = true;
newBackground.graphics.alpha = 0;
tween(newBackground.graphics, {
alpha: 1
}, {
duration: duration / 2,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.currentBackground = newBackground;
if (onComplete) {
onComplete();
}
}
});
};
self.createParallaxEffect = function (backgrounds, baseSpeed) {
baseSpeed = baseSpeed || 10;
for (var i = 0; i < backgrounds.length; i++) {
(function (index) {
var bg1 = backgrounds[index];
var speed = baseSpeed * (index + 1) * 0.5;
var bg2 = self.addBackground(bg1.assetId, {
anchorX: bg1.config.anchorX || 0.5,
anchorY: bg1.config.anchorY || 0.5,
x: bg1.graphics.x + bg1.graphics.width,
y: bg1.config.y || 0,
scaleX: -1,
scaleY: bg1.config.scaleY || 1,
alpha: bg1.config.alpha !== undefined ? bg1.config.alpha : 1,
tint: bg1.config.tint || 0xFFFFFF
});
var moveDistance = bg1.graphics.width;
var duration = 80000 / speed;
var _animate = function animate(bg, initialX) {
self.animateBackground(bg, {
x: bg.graphics.x - moveDistance
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
if (self.animationActive) {
if (bg.graphics.x <= -moveDistance / 2) {
bg.graphics.x = bg.graphics.x + moveDistance * 2;
}
_animate(bg, initialX);
}
}
});
};
if (index > 0) {
var _animateAlpha = function animateAlpha(bg) {
tween(bg.graphics, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bg.graphics, {
alpha: 0.5
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.animationActive) {
_animateAlpha(bg);
}
}
});
}
});
};
_animateAlpha(bg1);
_animateAlpha(bg2);
}
_animate(bg1, bg1.graphics.x);
_animate(bg2, bg2.graphics.x);
})(i);
}
};
return self;
});
var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) {
var self = Container.call(this);
self.targetEnemy = targetEnemy;
self.damage = damage || 10;
self.speed = speed || 5;
self.x = startX;
self.y = startY;
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.targetEnemy || !self.targetEnemy.parent) {
self.destroy();
return;
}
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
bulletGraphics.rotation = angle;
if (distance < self.speed) {
self.targetEnemy.health -= self.damage;
if (self.targetEnemy.health <= 0) {
self.targetEnemy.health = 0;
}
if (self.type === 'splash') {
var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash');
game.addChild(splashEffect);
var splashRadius = CELL_SIZE * 1.5;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self.targetEnemy) {
var splashDx = otherEnemy.x - self.targetEnemy.x;
var splashDy = otherEnemy.y - self.targetEnemy.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance <= splashRadius) {
otherEnemy.health -= self.damage * 0.5;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
}
}
}
}
} else if (self.type === 'slow') {
if (!self.targetEnemy.isImmune) {
var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow');
game.addChild(slowEffect);
var slowPct = 0.5;
if (self.sourceTowerLevel !== undefined) {
var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8];
var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1));
slowPct = slowLevels[idx];
}
if (!self.targetEnemy.slowed) {
self.targetEnemy.originalSpeed = self.targetEnemy.speed;
self.targetEnemy.speed *= 1 - slowPct;
self.targetEnemy.slowed = true;
self.targetEnemy.slowDuration = 180;
} else {
self.targetEnemy.slowDuration = 180;
}
}
} else if (self.type === 'poison') {
if (!self.targetEnemy.isImmune) {
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
self.targetEnemy.poisoned = true;
self.targetEnemy.poisonDamage = self.damage * 0.2;
self.targetEnemy.poisonDuration = 300;
}
} else if (self.type === 'sniper') {
var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper');
game.addChild(sniperEffect);
}
self.destroy();
} else {
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
var DebugCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = null;
if (isDebug) {
cellGraphics = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1
});
cellGraphics.tint = Math.random() * 0xffffff;
}
var debugArrows = [];
var numberLabel = new Text2("-", {
size: 20,
fill: 0xFFFFFF
});
self.addChild(numberLabel);
self.update = function () {};
self.down = function () {
return;
if (self.cell.type == 0 || self.cell.type == 1) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
if (grid.pathFind()) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
grid.pathFind();
var notification = game.addChild(new Notification("Path is blocked!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
grid.renderDebug();
}
};
self.removeArrows = function () {
while (debugArrows.length) {
self.removeChild(debugArrows.pop());
}
};
self.render = function (data) {
if (!isDebug) {
numberLabel.visible = false;
numberLabel.alpha = 0;
numberLabel.setText("");
if (data.type === 1 && !data.isVisuallyEmptyTower) {
if (self.wallInstance) {
self.removeChild(self.wallInstance);
self.wallInstance = null;
}
self.wallInstance = new Wall();
self.addChild(self.wallInstance);
} else {
if (self.wallInstance) {
self.removeChild(self.wallInstance);
self.wallInstance = null;
}
}
return;
}
if (cellGraphics) {
cellGraphics.visible = true;
cellGraphics.alpha = 1;
}
switch (data.type) {
case 0:
case 2:
{
if (data.pathId != pathId) {
self.removeArrows();
numberLabel.setText("-");
if (cellGraphics) {
cellGraphics.tint = 0x880000;
}
return;
}
var tint = Math.floor(data.score / maxScore * 0x88);
var towerInRangeHighlight = false;
if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) {
towerInRangeHighlight = true;
if (cellGraphics) {
cellGraphics.tint = 0x0088ff;
}
} else {
if (cellGraphics) {
cellGraphics.tint = 0x88 - tint << 8 | tint;
}
}
break;
}
case 1:
{
self.removeArrows();
if (cellGraphics) {
cellGraphics.tint = 0xaaaaaa;
}
numberLabel.visible = false;
break;
}
case 3:
{
self.removeArrows();
if (cellGraphics) {
cellGraphics.tint = 0x008800;
}
numberLabel.visible = false;
break;
}
case 4:
{
self.removeArrows();
if (cellGraphics) {
cellGraphics.tint = 0x888888;
}
numberLabel.visible = false;
break;
}
}
};
});
var EffectIndicator = Container.expand(function (x, y, type) {
var self = Container.call(this);
self.x = x;
self.y = y;
var effectGraphics = self.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
effectGraphics.blendMode = 1;
switch (type) {
case 'splash':
effectGraphics.tint = 0x33CC00;
effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5;
break;
case 'slow':
effectGraphics.tint = 0x9900FF;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'poison':
effectGraphics.tint = 0x00FFAA;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'sniper':
effectGraphics.tint = 0xFF5500;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
}
effectGraphics.alpha = 0.7;
self.alpha = 0;
tween(self, {
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = .01;
self.cellX = 0;
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
self.maxHealth = 1;
self.health = self.maxHealth;
self.bulletsTargetingThis = [];
self.waveNumber = currentWave;
self.isFlying = false;
self.isImmune = false;
self.isBoss = false;
if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') {
self.isBoss = true;
self.maxHealth *= 20;
self.speed = self.speed * 0.7;
}
self.health = self.maxHealth;
var assetId = 'enemy';
if (self.type !== 'normal') {
assetId = 'enemy_' + self.type;
}
var enemyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.isBoss) {
enemyGraphics.scaleX = 1.8;
enemyGraphics.scaleY = 1.8;
}
/*switch (self.type) {
case 'fast':
enemyGraphics.tint = 0x00AAFF;
break;
case 'immune':
enemyGraphics.tint = 0xAA0000;
break;
case 'flying':
enemyGraphics.tint = 0xFFFF00;
break;
case 'swarm':
enemyGraphics.tint = 0xFF00FF;
break;
}*/
if (self.isFlying) {
self.shadow = new Container();
var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', {
anchorX: 0.5,
anchorY: 0.5
});
shadowGraphics.tint = 0x000000;
shadowGraphics.alpha = 0.4;
if (self.isBoss) {
shadowGraphics.scaleX = 1.8;
shadowGraphics.scaleY = 1.8;
}
self.shadow.x = 20;
self.shadow.y = 20;
shadowGraphics.rotation = enemyGraphics.rotation;
}
self.update = function () {
if (self.health <= 0) {
self.health = 0;
}
if (self.currentTarget) {
var ox = self.currentTarget.x - self.currentCellX;
var oy = self.currentTarget.y - self.currentCellY;
if (ox !== 0 || oy !== 0) {
var angle = Math.atan2(oy, ox);
if (enemyGraphics.targetRotation === undefined) {
enemyGraphics.targetRotation = angle;
enemyGraphics.rotation = angle;
} else {
if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) {
tween.stop(enemyGraphics, {
rotation: true
});
var currentRotation = enemyGraphics.rotation;
var angleDiff = angle - currentRotation;
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemyGraphics.targetRotation = angle;
tween(enemyGraphics, {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
}
}
};
return self;
});
var GoldIndicator = Container.expand(function (value, x, y) {
var self = Container.call(this);
var shadowText = new Text2("+" + value, {
size: 45,
fill: 0x000000,
weight: 800
});
shadowText.anchor.set(0.5, 0.5);
shadowText.x = 2;
shadowText.y = 2;
self.addChild(shadowText);
var goldText = new Text2("+" + value, {
size: 45,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
self.addChild(goldText);
self.x = x;
self.y = y;
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
y: y - 40
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
y: y - 80
}, {
duration: 600,
easing: tween.easeIn,
delay: 800,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
var Grid = Container.expand(function (gridWidth, gridHeight) {
var self = Container.call(this);
self.cells = [];
self.spawns = [];
self.goals = [];
for (var i = 0; i < gridWidth; i++) {
self.cells[i] = [];
for (var j = 0; j < gridHeight; j++) {
self.cells[i][j] = {
score: 0,
pathId: 0,
towersInRange: []
};
}
}
/*
Cell Types
0: Transparent floor
1: Wall
2: Spawn
3: Goal
4: Empty Tower
9: Wall (from map)
*/
var currentLevel = storage.currentLevel || 1;
// FOR DEBUGGING:
//currentLevel = 1;
//storage.currentLevel = 1;
// FOR DEBUGGING
var currentMap;
if (storage.hasPlayedTutorial) {
currentMap = initMapForLevel(currentLevel);
} else {
currentMap = MAPS[0];
}
var mapHeight = currentMap.length;
var mapWidth = currentMap[0].length;
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var cell = self.cells[i][j];
var cellType = 0;
if (j < mapHeight && i < mapWidth) {
var mapValue = currentMap[j][i];
switch (mapValue) {
case 0:
cellType = 0;
break;
case 11:
cell.orientation = Math.PI;
case 1:
cellType = 3;
self.goals.push(cell);
break;
case 22:
case 33:
cell.orientation = Math.PI;
case 2:
case 3:
cellType = 2;
self.spawns.push(cell);
break;
case 8:
cellType = 4;
break;
case 9:
cellType = 1;
break;
default:
cellType = 0;
}
} else {
cellType = 1;
}
cell.type = cellType;
cell.x = i;
cell.y = j;
cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1];
cell.up = self.cells[i - 1] && self.cells[i - 1][j];
cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1];
cell.left = self.cells[i][j - 1];
cell.right = self.cells[i][j + 1];
cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1];
cell.down = self.cells[i + 1] && self.cells[i + 1][j];
cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1];
cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left];
cell.targets = [];
if (j >= 0 && j <= gridHeight - 5) {
var debugCell = new DebugCell();
self.addChild(debugCell);
debugCell.cell = cell;
debugCell.x = i * CELL_SIZE;
debugCell.y = (j + mapGridOffset) * CELL_SIZE;
cell.debugCell = debugCell;
}
}
}
self.getCell = function (x, y) {
return self.cells[x] && self.cells[x][y];
};
self.pathFind = function () {
var before = new Date().getTime();
var toProcess = self.goals.concat([]);
maxScore = 0;
pathId += 1;
for (var a = 0; a < toProcess.length; a++) {
toProcess[a].pathId = pathId;
}
function processNode(node, targetValue, targetNode) {
if (node && node.type != 1) {
if (node.pathId < pathId || targetValue < node.score) {
node.targets = [targetNode];
} else if (node.pathId == pathId && targetValue == node.score) {
node.targets.push(targetNode);
}
if (node.pathId < pathId || targetValue < node.score) {
node.score = targetValue;
if (node.pathId != pathId) {
toProcess.push(node);
}
node.pathId = pathId;
if (targetValue > maxScore) {
maxScore = targetValue;
}
}
}
}
while (toProcess.length) {
var nodes = toProcess;
toProcess = [];
for (var a = 0; a < nodes.length; a++) {
var node = nodes[a];
var targetScore = node.score + 14142;
if (node.up && node.left && node.up.type != 1 && node.left.type != 1) {
processNode(node.upLeft, targetScore, node);
}
if (node.up && node.right && node.up.type != 1 && node.right.type != 1) {
processNode(node.upRight, targetScore, node);
}
if (node.down && node.right && node.down.type != 1 && node.right.type != 1) {
processNode(node.downRight, targetScore, node);
}
if (node.down && node.left && node.down.type != 1 && node.left.type != 1) {
processNode(node.downLeft, targetScore, node);
}
targetScore = node.score + 10000;
processNode(node.up, targetScore, node);
processNode(node.right, targetScore, node);
processNode(node.down, targetScore, node);
processNode(node.left, targetScore, node);
}
}
for (var a = 0; a < self.spawns.length; a++) {
if (self.spawns[a].pathId != pathId) {
console.warn("Spawn blocked");
return true;
}
}
for (var a = 0; a < enemies.length; a++) {
var enemy = enemies[a];
if (enemy.currentCellY < mapGridOffset) {
continue;
}
if (enemy.isFlying) {
continue;
}
var target = self.getCell(enemy.cellX, enemy.cellY);
if (enemy.currentTarget) {
if (enemy.currentTarget.pathId != pathId) {
if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 1 ");
return true;
}
}
} else if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 2");
return true;
}
}
console.log("Speed", new Date().getTime() - before);
};
self.renderDebug = function () {
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var debugCell = self.cells[i][j].debugCell;
if (debugCell) {
debugCell.render(self.cells[i][j]);
}
}
}
};
self.updateEnemy = function (enemy) {
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell.type == 3) {
return true;
}
if (enemy.isFlying && enemy.shadow) {
enemy.shadow.x = enemy.x + 20;
enemy.shadow.y = enemy.y + 20;
if (enemy.children[0] && enemy.shadow.children[0]) {
enemy.shadow.children[0].rotation = enemy.children[0].rotation;
}
}
var hasReachedEntryArea = enemy.currentCellY >= mapGridOffset;
if (!hasReachedEntryArea) {
enemy.currentCellY += enemy.speed;
var angle = Math.PI / 2;
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
if (enemy.currentCellY >= mapGridOffset) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
}
return false;
}
if (enemy.isFlying) {
if (!enemy.flyingTarget) {
enemy.flyingTarget = self.goals[0];
if (self.goals.length > 1) {
var closestDist = Infinity;
for (var i = 0; i < self.goals.length; i++) {
var goal = self.goals[i];
var dx = goal.x - enemy.cellX;
var dy = goal.y - enemy.cellY;
var dist = dx * dx + dy * dy;
if (dist < closestDist) {
closestDist = dist;
enemy.flyingTarget = goal;
}
}
}
}
var ox = enemy.flyingTarget.x - enemy.currentCellX;
var oy = enemy.flyingTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
return true;
}
var angle = Math.atan2(oy, ox);
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
return false;
}
if (!enemy.currentTarget) {
enemy.currentTarget = cell.targets[0];
}
if (enemy.currentTarget) {
if (cell.score < enemy.currentTarget.score) {
enemy.currentTarget = cell;
}
var ox = enemy.currentTarget.x - enemy.currentCellX;
var oy = enemy.currentTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentTarget = undefined;
return;
}
var angle = Math.atan2(oy, ox);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
}
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
};
});
var NextWaveButton = Container.expand(function () {
var self = Container.call(this);
var buttonBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 300;
buttonBackground.height = 100;
buttonBackground.tint = 0x0088FF;
var buttonText = new Text2("Next Wave", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.enabled = false;
self.visible = false;
self.update = function () {
if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) {
self.enabled = true;
self.visible = true;
buttonBackground.tint = 0x0088FF;
self.alpha = 1;
} else {
self.enabled = false;
self.visible = false;
buttonBackground.tint = 0x888888;
self.alpha = 0.7;
}
};
self.down = function () {
if (!self.enabled) {
return;
}
if (waveIndicator.gameStarted && currentWave < totalWaves) {
currentWave++;
waveTimer = 0;
waveInProgress = true;
waveSpawned = false;
var waveType = waveIndicator.getWaveTypeName(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
};
return self;
});
var Notification = Container.expand(function (message) {
var self = Container.call(this);
var notificationGraphics = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
var notificationText = new Text2(message, {
size: 50,
fill: 0x000000,
weight: 800
});
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 30;
self.addChild(notificationText);
self.alpha = 1;
var fadeOutTime = 120;
self.update = function () {
if (fadeOutTime > 0) {
fadeOutTime--;
self.alpha = Math.min(fadeOutTime / 120 * 2, 1);
} else {
self.destroy();
}
};
return self;
});
var SelectionCircle = Container.expand(function () {
var self = Container.call(this);
self.startX = 0;
self.startY = 0;
self.currentRadius = 0;
var circleGraphics = self.attachAsset('circleSelector', {
anchorX: 0.5,
anchorY: 0.5
});
circleGraphics.alpha = 0.3;
circleGraphics.tint = 0x00FF00;
circleGraphics.width = 1;
circleGraphics.height = 1;
self.updateCircle = function (endX, endY) {
var dx = endX - self.startX;
var dy = endY - self.startY;
self.currentRadius = Math.sqrt(dx * dx + dy * dy);
circleGraphics.width = self.currentRadius * 2;
circleGraphics.height = self.currentRadius * 2;
};
self.setPosition = function (x, y) {
self.startX = x;
self.startY = y;
self.x = x;
self.y = y;
};
return self;
});
var SourceTower = Container.expand(function (towerType) {
var self = Container.call(this);
self.towerType = towerType || 'default';
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
baseGraphics.tint = 0xAAAAAA;
var towerCost = getTowerCost(self.towerType);
var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0x000000,
weight: 800
});
typeLabelShadow.anchor.set(0.5, 0.5);
typeLabelShadow.x = 4;
typeLabelShadow.y = -20 + 4;
self.addChild(typeLabelShadow);
var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
typeLabel.anchor.set(0.5, 0.5);
typeLabel.y = -20;
self.addChild(typeLabel);
var costLabelShadow = new Text2(towerCost, {
size: 50,
fill: 0x000000,
weight: 800
});
costLabelShadow.anchor.set(0.5, 0.5);
costLabelShadow.x = 4;
costLabelShadow.y = 24 + 12;
self.addChild(costLabelShadow);
var costLabel = new Text2(towerCost, {
size: 50,
fill: 0xFFD700,
weight: 800
});
costLabel.anchor.set(0.5, 0.5);
costLabel.y = 20 + 12;
self.addChild(costLabel);
self.update = function () {
var canAfford = gold >= getTowerCost(self.towerType);
self.alpha = canAfford ? 1 : 0.5;
};
return self;
});
var Tower = Container.expand(function (headquarters, orientation, playerIndex) {
var self = Container.call(this);
self.isHeadquarters = headquarters || false;
self.baseOrientation = orientation;
self.playerIndex = playerIndex !== undefined ? playerIndex : 0;
self.isActivated = self.isHeadquarters;
self.activationThreshold = 10;
self.currentShares = 0;
self.capturingPlayer = -1;
self.activationGauge = null;
self.id = self.isHeadquarters ? 'headquarters' : 'default';
self.level = 1;
self.maxLevel = 6;
self.gridX = 0;
self.gridY = 0;
self.isProducing = false;
self.range = 3 * CELL_SIZE;
self.health = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10;
self.maxHealth = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10;
self.getRange = function () {
if (self.isHeadquarters) {
return (3 + (self.level - 1) * 0.5) * CELL_SIZE * 2;
}
switch (self.id) {
case 'sniper':
if (self.level === self.maxLevel) {
return 12 * CELL_SIZE;
}
return (5 + (self.level - 1) * 0.8) * CELL_SIZE;
case 'splash':
return (2 + (self.level - 1) * 0.2) * CELL_SIZE;
case 'rapid':
return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'slow':
return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'poison':
return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
default:
return (3 + (self.level - 1) * 0.5) * CELL_SIZE;
}
};
self.cellsInRange = [];
self.fireRate = self.isHeadquarters ? 20 : 60;
self.bulletSpeed = 5;
self.damage = 1;
self.lastFired = 0;
self.targetEnemy = null;
var baseGraphics;
if (self.isHeadquarters) {
baseGraphics = self.attachAsset('headquarterBase', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150
});
baseGraphics.rotation = orientation || 0;
} else {
baseGraphics = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5
});
}
switch (self.id) {
case 'rapid':
baseGraphics.tint = 0x00AAFF;
break;
case 'sniper':
baseGraphics.tint = 0xFF5500;
break;
case 'splash':
baseGraphics.tint = 0x33CC00;
break;
case 'slow':
baseGraphics.tint = 0x9900FF;
break;
case 'poison':
baseGraphics.tint = 0x00FFAA;
break;
default:
baseGraphics.tint = 0xAAAAAA;
}
var levelIndicators = [];
var maxDots = 0;
var dotSpacing = baseGraphics.width / (maxDots + 1);
var dotSize = CELL_SIZE / 6;
for (var i = 0; i < maxDots; i++) {
var dot = new Container();
var outlineCircle = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
outlineCircle.width = dotSize + 4;
outlineCircle.height = dotSize + 4;
outlineCircle.tint = 0x000000;
var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
towerLevelIndicator.width = dotSize;
towerLevelIndicator.height = dotSize;
towerLevelIndicator.tint = 0xCCCCCC;
dot.x = -CELL_SIZE * 2 + dotSpacing * (i + 1);
dot.y = CELL_SIZE * 1.3;
if (self.playerIndex !== 0) {
dot.visible = false;
}
self.addChild(dot);
levelIndicators.push(dot);
}
var activationGaugeContainer = new Container();
self.addChild(activationGaugeContainer);
activationGaugeContainer.y = CELL_SIZE * 0.9;
self.activationGauge = activationGaugeContainer;
self.activationGauge.visible = false;
var gunContainer = new Container();
self.addChild(gunContainer);
var turretGraphics;
var gunGraphics;
var prismGraphics;
if (self.isHeadquarters) {
self.isProducing = true;
turretGraphics = gunContainer.attachAsset('turret', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 100
});
gunGraphics = gunContainer.attachAsset('barrel2', {
anchorX: 0,
anchorY: 0.5,
width: 100,
height: 20
});
prismGraphics = gunContainer.attachAsset('prism', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 100
});
} else {
turretGraphics = gunContainer.attachAsset('turret', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
gunGraphics = gunContainer.attachAsset('barrel', {
anchorX: 0,
anchorY: 0.5,
width: 50,
height: 10
});
prismGraphics = gunContainer.attachAsset('prism', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
}
self.turretGraphics = turretGraphics;
self.gunGraphics = gunGraphics;
self.prismGraphics = prismGraphics;
prismGraphics.visible = self.isProducing;
turretGraphics.visible = !self.isProducing;
gunGraphics.visible = !self.isProducing;
if (self.playerIndex === 0) {
gunGraphics.tint = teamColors[0];
turretGraphics.tint = teamColors[0];
prismGraphics.tint = teamColors[0];
} else if (self.playerIndex === 1) {
gunGraphics.tint = teamColors[1];
turretGraphics.tint = teamColors[1];
prismGraphics.tint = teamColors[1];
} else {
gunGraphics.tint = 0xAAAAAA;
turretGraphics.tint = 0xAAAAAA;
prismGraphics.tint = 0xAAAAAA;
}
self.updateLevelIndicators = function () {
for (var i = 0; i < maxDots; i++) {
var dot = levelIndicators[i];
var towerLevelIndicator = dot.children[1];
if (i < self.level) {
towerLevelIndicator.tint = 0xFFFFFF;
} else {
towerLevelIndicator.tint = 0xAAAAAA;
}
}
};
self.updateLevelIndicators();
self.updateActivation = function (fromPlayerIndex) {
if (self.isActivated && self.playerIndex === fromPlayerIndex) {
return false;
}
if (self.capturingPlayer === -1) {
self.capturingPlayer = fromPlayerIndex;
}
if (self.capturingPlayer === fromPlayerIndex) {
if (self.currentShares >= self.activationThreshold) {
if (!self.isActivated) {
self.activate(self.capturingPlayer);
}
return false;
}
self.currentShares++;
} else {
self.currentShares--;
}
if (self.currentShares <= 0) {
self.currentShares = 0;
if (self.isActivated) {
self.deactivate();
}
self.capturingPlayer = -1;
} else if (self.currentShares >= self.activationThreshold) {
self.currentShares = self.activationThreshold;
if (!self.isActivated) {
self.activate(self.capturingPlayer);
}
}
self.updateActivationGaugeVisuals();
return true;
};
self.activate = function (newPlayerIndex) {
self.isActivated = true;
self.playerIndex = newPlayerIndex;
gunContainer.visible = true;
var baseGraphics = self.children[0];
if (self.playerIndex == 0) {
baseGraphics.tint = teamColors[0];
self.turretGraphics.tint = teamColors[0];
self.gunGraphics.tint = teamColors[0];
self.prismGraphics.tint = teamColors[0];
} else if (self.playerIndex == 1) {
baseGraphics.tint = teamColors[1];
self.turretGraphics.tint = teamColors[1];
self.gunGraphics.tint = teamColors[1];
self.prismGraphics.tint = teamColors[1];
}
if (self.activationGauge) {
self.activationGauge.visible = false;
}
};
self.deactivate = function () {
self.isActivated = false;
self.playerIndex = -1;
gunContainer.visible = false;
var baseGraphics = self.children[0];
baseGraphics.tint = 0xAAAAAA;
self.gunGraphics.tint = 0xAAAAAA;
self.turretGraphics.tint = 0xAAAAAA;
self.prismGraphics.tint = 0xAAAAAA;
self.updateActivationGaugeVisuals();
};
self.updateActivationGaugeVisuals = function () {
if (!self.activationGauge) {
return;
}
self.activationGauge.y = 0;
self.activationGauge.removeChildren();
if (self.currentShares > 0 && !self.isActivated) {
self.activationGauge.visible = true;
var totalDots = 12;
var radius = CELL_SIZE / 2.5;
var dotsToShow = Math.ceil(self.currentShares / self.activationThreshold * totalDots);
var playerColor = 0x0000FF;
if (self.capturingPlayer === 0) {
playerColor = teamColors[0];
} else if (self.capturingPlayer === 1) {
playerColor = teamColors[1];
}
for (var i = 0; i < totalDots; i++) {
var angle = i / totalDots * Math.PI * 2 - Math.PI / 2;
var dot = self.activationGauge.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 12
});
dot.x = Math.cos(angle) * radius;
dot.y = Math.sin(angle) * radius;
if (i < dotsToShow) {
dot.tint = playerColor;
dot.alpha = 1.0;
} else {
dot.tint = 0x666666;
dot.alpha = 0.4;
}
}
} else {
self.activationGauge.visible = false;
}
};
self.refreshCellsInRange = function () {
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
var towerIndex = cell.towersInRange.indexOf(self);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
self.cellsInRange = [];
var rangeRadius = self.getRange() / CELL_SIZE;
var centerX = self.gridX + 1;
var centerY = self.gridY + 1;
var minI = Math.floor(centerX - rangeRadius - 0.5);
var maxI = Math.ceil(centerX + rangeRadius + 0.5);
var minJ = Math.floor(centerY - rangeRadius - 0.5);
var maxJ = Math.ceil(centerY + rangeRadius + 0.5);
for (var i = minI; i <= maxI; i++) {
for (var j = minJ; j <= maxJ; j++) {
var closestX = Math.max(i, Math.min(centerX, i + 1));
var closestY = Math.max(j, Math.min(centerY, j + 1));
var deltaX = closestX - centerX;
var deltaY = closestY - centerY;
var distanceSquared = deltaX * deltaX + deltaY * deltaY;
if (distanceSquared <= rangeRadius * rangeRadius) {
var cell = grid.getCell(i, j);
if (cell) {
self.cellsInRange.push(cell);
cell.towersInRange.push(self);
}
}
}
}
grid.renderDebug();
};
self.getTotalValue = function () {
var baseTowerCost = getTowerCost(self.id);
var totalInvestment = baseTowerCost;
var baseUpgradeCost = baseTowerCost;
for (var i = 1; i < self.level; i++) {
totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1));
}
return totalInvestment;
};
self.upgrade = function () {
/*
if (self.level < self.maxLevel) {
var baseUpgradeCost = getTowerCost(self.id);
var upgradeCost;
if (self.level === self.maxLevel - 1) {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2);
} else {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1));
}
if (gold >= upgradeCost) {
setGold(gold - upgradeCost);
self.level++;
if (self.id === 'rapid') {
if (self.level === self.maxLevel) {
self.fireRate = Math.max(4, 30 - self.level * 9);
self.damage = 5 + self.level * 10;
self.bulletSpeed = 7 + self.level * 2.4;
} else {
self.fireRate = Math.max(15, 30 - self.level * 3);
self.damage = 5 + self.level * 3;
self.bulletSpeed = 7 + self.level * 0.7;
}
} else {
if (self.level === self.maxLevel) {
self.fireRate = Math.max(5, 60 - self.level * 24);
self.damage = 10 + self.level * 20;
self.bulletSpeed = 5 + self.level * 2.4;
} else {
self.fireRate = Math.max(20, 60 - self.level * 8);
self.damage = 10 + self.level * 5;
self.bulletSpeed = 5 + self.level * 0.5;
}
}
self.refreshCellsInRange();
self.updateLevelIndicators();
if (self.level > 1) {
var levelDot = levelIndicators[self.level - 1].children[1];
tween(levelDot, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(levelDot, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold to upgrade!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
*/
return false;
};
self.findTarget = function () {
if (unitManager && unitManager.units) {
var closestUnit = null;
var closestUnitDistSq = Infinity;
var range = self.getRange();
var rangeSq = range * range;
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex !== self.playerIndex && unit.parent) {
var dx = unit.x - self.x;
var dy = unit.y - self.y;
var distSq = dx * dx + dy * dy;
if (distSq <= rangeSq && distSq < closestUnitDistSq) {
closestUnitDistSq = distSq;
closestUnit = unit;
}
}
}
if (closestUnit) {
return closestUnit;
}
}
var closestEnemy = null;
var closestScore = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.getRange()) {
if (enemy.isFlying) {
if (enemy.flyingTarget) {
var goalX = enemy.flyingTarget.x;
var goalY = enemy.flyingTarget.y;
var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY));
if (distToGoal < closestScore) {
closestScore = distToGoal;
closestEnemy = enemy;
}
} else {
if (distance < closestScore) {
closestScore = distance;
closestEnemy = enemy;
}
}
} else {
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell && cell.pathId === pathId) {
if (cell.score < closestScore) {
closestScore = cell.score;
closestEnemy = enemy;
}
}
}
}
}
if (!closestEnemy) {
self.targetEnemy = null;
}
return closestEnemy;
};
self.update = function () {
if (!self.isActivated) {
return;
}
if (!self.isProducing) {
self.targetEnemy = self.findTarget();
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var angle = Math.atan2(dy, dx);
gunContainer.rotation = angle;
if (LK.ticks - self.lastFired >= self.fireRate) {
self.fire();
self.lastFired = LK.ticks;
}
}
}
};
self.down = function (x, y, obj) {
if (tutorialActive && self.isHeadquarters && self.playerIndex === 0 && tutorialStep < 6) {
return;
}
if (!self.isActivated) {
return;
}
if (self.playerIndex !== 0) {
return;
}
var existingMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
var hasOwnMenu = false;
var rangeCircle = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self) {
rangeCircle = game.children[i];
break;
}
}
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hasOwnMenu = true;
break;
}
}
if (hasOwnMenu) {
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hideUpgradeMenu(existingMenus[i]);
}
}
if (rangeCircle) {
game.removeChild(rangeCircle);
}
selectedTower = null;
grid.renderDebug();
return;
}
for (var i = 0; i < existingMenus.length; i++) {
existingMenus[i].destroy();
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = self;
var rangeIndicator = new Container();
rangeIndicator.isTowerRange = true;
rangeIndicator.tower = self;
game.addChild(rangeIndicator);
rangeIndicator.x = self.x;
rangeIndicator.y = self.y;
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.width = rangeGraphics.height = self.getRange() * 2;
rangeGraphics.alpha = 0.3;
var upgradeMenu = new UpgradeMenu(self);
game.addChild(upgradeMenu);
upgradeMenu.x = 2048 / 2;
tween(upgradeMenu, {
y: 2732 - 225
}, {
duration: 200,
easing: tween.backOut,
onFinish: function onFinish() {
if (tutorialActive && tutorialStep === 6 && self.isHeadquarters && self.playerIndex === 0) {
executeTutorialStep(7);
}
}
});
grid.renderDebug();
};
self.isInRange = function (enemy) {
if (!enemy) {
return false;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance <= self.getRange();
};
self.fire = function () {
if (self.targetEnemy) {
var potentialDamage = 0;
for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) {
potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage;
}
if (self.targetEnemy.health > 0) {
var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed);
bullet.type = self.id;
/*
if (self.id === 'slow') {
bullet.sourceTowerLevel = self.level;
}
switch (self.id) {
case 'rapid':
bullet.children[0].tint = 0x00AAFF;
bullet.children[0].width = 20;
bullet.children[0].height = 20;
break;
case 'sniper':
bullet.children[0].tint = 0xFF5500;
bullet.children[0].width = 15;
bullet.children[0].height = 15;
break;
case 'splash':
bullet.children[0].tint = 0x33CC00;
bullet.children[0].width = 40;
bullet.children[0].height = 40;
break;
case 'slow':
bullet.children[0].tint = 0x9900FF;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'poison':
bullet.children[0].tint = 0x00FFAA;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
}
*/
game.addChild(bullet);
bullets.push(bullet);
self.targetEnemy.bulletsTargetingThis.push(bullet);
tween.stop(gunContainer, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
if (gunContainer._restX === undefined) {
gunContainer._restX = 0;
}
if (gunContainer._restY === undefined) {
gunContainer._restY = 0;
}
if (gunContainer._restScaleX === undefined) {
gunContainer._restScaleX = 1;
}
if (gunContainer._restScaleY === undefined) {
gunContainer._restScaleY = 1;
}
gunContainer.x = gunContainer._restX;
gunContainer.y = gunContainer._restY;
gunContainer.scaleX = gunContainer._restScaleX;
gunContainer.scaleY = gunContainer._restScaleY;
var recoilDistance = 8;
var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance;
var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance;
tween(gunContainer, {
x: gunContainer._restX + recoilX,
y: gunContainer._restY + recoilY
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(gunContainer, {
x: gunContainer._restX,
y: gunContainer._restY
}, {
duration: 60,
easing: tween.cubicIn
});
}
});
}
}
};
self.placeOnGrid = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cell.type = 1;
}
}
}
self.refreshCellsInRange();
};
self.changeTowerMode = function () {
self.isProducing = !self.isProducing;
if (self.isProducing) {
self.activateGeneratorMode();
} else {
self.activateDefenseMode();
}
};
self.animatePrism = function () {
if (!self.isProducing) {
return;
}
var prismGraphics = self.prismGraphics;
tween(prismGraphics, {
rotation: prismGraphics.rotation + Math.PI * 2
}, {
duration: 8000,
easing: tween.linear,
onFinish: function onFinish() {
self.animatePrism();
}
});
var tintColors = [0xFFFFFF, 0xFFE0E0, 0xE0FFFF, 0xE0FFE0, 0xFFFFE0, 0xF0E0FF];
var currentTintIndex = 0;
function animateTint() {
var nextIndex = (currentTintIndex + 1) % tintColors.length;
tween(prismGraphics, {
tint: tintColors[nextIndex]
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
currentTintIndex = nextIndex;
animateTint();
}
});
}
};
self.animatePrism();
self.activateGeneratorMode = function () {
console.log("Tower activating generator mode at", self.gridX, self.gridY);
self.prismGraphics.visible = true;
self.gunGraphics.visible = false;
self.turretGraphics.visible = false;
if (self.playerIndex === 0) {
self.prismGraphics.tint = teamColors[0];
self.turretGraphics.tint = teamColors[0];
self.gunGraphics.tint = teamColors[0];
} else if (self.playerIndex === 1) {
self.prismGraphics.tint = teamColors[1];
self.turretGraphics.tint = teamColors[1];
self.gunGraphics.tint = teamColors[1];
} else {
self.prismGraphics.tint = 0xAAAAAA;
self.turretGraphics.tint = 0xAAAAAA;
self.gunGraphics.tint = 0xAAAAAA;
}
self.animatePrism();
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu && child.tower === self;
});
for (var i = 0; i < upgradeMenus.length; i++) {
if (upgradeMenus[i].updateButtonText) {
upgradeMenus[i].updateButtonText('Defense mode');
}
}
};
self.activateDefenseMode = function () {
self.prismGraphics.visible = false;
self.turretGraphics.visible = true;
self.gunGraphics.visible = true;
if (self.playerIndex === 0) {
self.turretGraphics.tint = teamColors[0];
self.gunGraphics.tint = teamColors[0];
self.prismGraphics.tint = teamColors[0];
} else if (self.playerIndex === 1) {
self.turretGraphics.tint = teamColors[1];
self.gunGraphics.tint = teamColors[1];
self.prismGraphics.tint = teamColors[1];
} else {
self.turretGraphics.tint = 0xAAAAAA;
self.gunGraphics.tint = 0xAAAAAA;
self.prismGraphics.tint = 0xAAAAAA;
}
tween.stop(self.prismGraphics, {
rotation: true,
tint: true
});
self.prismGraphics.rotation = 0;
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu && child.tower === self;
});
for (var i = 0; i < upgradeMenus.length; i++) {
if (upgradeMenus[i].updateButtonText) {
upgradeMenus[i].updateButtonText('Generator mode');
}
}
};
return self;
});
var TowerPreview = Container.expand(function () {
var self = Container.call(this);
var towerRange = 3;
var rangeInPixels = towerRange * CELL_SIZE;
self.towerType = 'default';
self.hasEnoughGold = true;
var rangeIndicator = new Container();
self.addChild(rangeIndicator);
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.alpha = 0.3;
var previewGraphics = self.attachAsset('towerpreview', {
anchorX: 0.5,
anchorY: 0.5
});
previewGraphics.width = CELL_SIZE * 2;
previewGraphics.height = CELL_SIZE * 2;
self.canPlace = false;
self.gridX = 0;
self.gridY = 0;
self.blockedByEnemy = false;
self.update = function () {
var previousHasEnoughGold = self.hasEnoughGold;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
if (previousHasEnoughGold !== self.hasEnoughGold) {
self.updateAppearance();
}
};
self.updateAppearance = function () {
var tempTower = new Tower(false, undefined, 0);
var previewRange = tempTower.getRange();
if (tempTower && tempTower.destroy) {
tempTower.destroy();
}
rangeGraphics.width = rangeGraphics.height = previewRange * 2;
previewGraphics.tint = 0xAAAAAA;
if (!self.canPlace || !self.hasEnoughGold) {
previewGraphics.tint = 0xFF0000;
}
};
self.updatePlacementStatus = function () {
var validGridPlacement = true;
if (self.gridY <= mapGridOffset || self.gridY + 1 >= grid.cells[0].length - mapGridOffset) {
validGridPlacement = false;
} else {
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(self.gridX + i, self.gridY + j);
if (!cell || cell.type !== 0) {
validGridPlacement = false;
break;
}
}
if (!validGridPlacement) {
break;
}
}
}
self.blockedByEnemy = false;
if (validGridPlacement) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.currentCellY < mapGridOffset) {
continue;
}
if (!enemy.isFlying) {
if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
if (enemy.currentTarget) {
var targetX = enemy.currentTarget.x;
var targetY = enemy.currentTarget.y;
if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
}
}
}
}
self.canPlace = validGridPlacement && !self.blockedByEnemy;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
self.updateAppearance();
};
self.checkPlacement = function () {
self.updatePlacementStatus();
};
self.snapToGrid = function (x, y) {
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
self.gridX = Math.floor(gridPosX / CELL_SIZE);
self.gridY = Math.floor(gridPosY / CELL_SIZE);
self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2;
self.checkPlacement();
};
return self;
});
var TrailParticle = Container.expand(function (x, y, tint, scale) {
var self = Container.call(this);
self.x = x;
self.y = y;
var particleGraphics = self.attachAsset('unit', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.tint = tint;
self.alpha = 0.75;
self.scaleX = (scale || 1) * 0.75;
self.scaleY = (scale || 1) * 0.75;
tween(self, {
alpha: 0,
scaleX: self.scaleX * 0.1,
scaleY: self.scaleY * 0.1
}, {
duration: 300 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
var Unit = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = .01;
self.cellX = 0;
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
self.maxHealth = 1;
self.health = self.maxHealth;
self.bulletsTargetingThis = [];
self.waveNumber = currentWave;
self.health = self.maxHealth;
self.enemyHQ = null;
self.isIdle = false;
self.idleBaseX = 0;
self.idleBaseY = 0;
self.idleAnimationActive = false;
self.isSelected = false;
self.selectionIndicator = null;
self.targetCellX = null;
self.targetCellY = null;
self.movementSpeed = 0.05;
self.lastTrailTime = 0;
var assetId = 'unit';
if (self.type !== 'normal') {
assetId = 'unit_' + self.type;
}
var unitGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 1) {
self.enemyHQ = towers[i];
break;
}
}
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected && !self.selectionIndicator) {
self.selectionIndicator = new Container();
var indicator = self.selectionIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.width = 50;
indicator.height = 50;
indicator.alpha = 0.5;
indicator.tint = 0x00FF00;
self.addChild(self.selectionIndicator);
} else if (!selected && self.selectionIndicator) {
self.removeChild(self.selectionIndicator);
self.selectionIndicator = null;
}
};
self.setTarget = function (cellX, cellY) {
self.targetCellX = cellX;
self.targetCellY = cellY;
self.isIdle = false;
self.idleAnimationActive = false;
tween.stop(self);
self.targetTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.playerIndex !== self.ownerIndex) {
var towerGridX, towerGridY;
var towerSize = tower.isHeadquarters ? 4 : 2;
if (tower.isHeadquarters) {
towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2;
towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset;
} else {
towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
}
if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) {
self.targetTower = tower;
break;
}
}
}
};
self.update = function () {
if (tutorialActive && tutorialBlockUnitUpdates) {
return;
}
if (self.health <= 0) {
self.health = 0;
}
if (self.targetCellX !== null && self.targetCellY !== null) {
var targetX = grid.x + self.targetCellX * CELL_SIZE + CELL_SIZE / 2;
var targetY = grid.y + (self.targetCellY + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
if (self.targetTower && self.targetTower.parent) {
targetX = self.targetTower.x;
targetY = self.targetTower.y;
}
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var attackRange = CELL_SIZE;
if (self.targetTower && distance <= attackRange) {
if (!self.targetTower.isHeadquarters && self.targetTower.updateActivation) {
var consumed = self.targetTower.updateActivation(self.ownerIndex);
if (consumed) {
self.health = 0;
} else {
var tower = self.targetTower;
var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset);
var availableCells = [];
var searchRadius = 1;
var maxSearchRadius = 10;
while (availableCells.length === 0 && searchRadius <= maxSearchRadius) {
for (var dx = -searchRadius; dx <= searchRadius; dx++) {
for (var dy = -searchRadius; dy <= searchRadius; dy++) {
if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) {
continue;
}
var checkX = towerGridX + dx;
var checkY = towerGridY + dy;
var cell = grid.getCell(checkX, checkY);
if (cell && cell.type === 0) {
availableCells.push(cell);
}
}
}
searchRadius++;
}
if (availableCells.length > 0) {
var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)];
self.setTarget(randomCell.x, randomCell.y);
} else {
self.health = 0;
}
}
return;
}
if (!self.targetTower.parent) {
var tower = self.targetTower;
var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset);
var availableCells = [];
var searchRadius = 1;
var maxSearchRadius = 10;
while (availableCells.length === 0 && searchRadius <= maxSearchRadius) {
for (var dx = -searchRadius; dx <= searchRadius; dx++) {
for (var dy = -searchRadius; dy <= searchRadius; dy++) {
if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) {
continue;
}
var checkX = towerGridX + dx;
var checkY = towerGridY + dy;
var cell = grid.getCell(checkX, checkY);
if (cell && cell.type === 0) {
availableCells.push(cell);
}
}
}
searchRadius++;
}
if (availableCells.length > 0) {
var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)];
self.setTarget(randomCell.x, randomCell.y);
} else {
self.health = 0;
}
} else {
var isEnemyTower = self.targetTower.playerIndex !== self.ownerIndex;
console.log("Unit reached tower. Enemy tower:", isEnemyTower, "Tower health:", self.targetTower.health);
self.targetTower.health -= 1;
self.health = 0;
if (self.targetTower.health <= 0) {
if (self.targetTower.isHeadquarters) {
if (self.targetTower.playerIndex === 1) {
handleGameEnd(true);
} else if (self.targetTower.playerIndex === 0) {
handleGameEnd(false);
}
}
var towerIndex = towers.indexOf(self.targetTower);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
game.removeChild(self.targetTower);
var towerGridX, towerGridY;
var towerSize = self.targetTower.isHeadquarters ? 4 : 2;
if (self.targetTower.isHeadquarters) {
towerGridX = Math.round((self.targetTower.x - grid.x) / CELL_SIZE) - towerSize / 2;
towerGridY = Math.round((self.targetTower.y - grid.y) / CELL_SIZE) - towerSize / 2;
} else {
towerGridX = Math.floor((self.targetTower.x - grid.x) / CELL_SIZE);
towerGridY = Math.floor((self.targetTower.y - grid.y) / CELL_SIZE);
}
for (var i = 0; i < towerSize; i++) {
for (var j = 0; j < towerSize; j++) {
var cell = grid.getCell(towerGridX + i, towerGridY + j);
if (cell) {
cell.type = 0;
}
}
}
grid.pathFind();
grid.renderDebug();
}
}
return;
}
if (distance > 2) {
var angle = Math.atan2(dy, dx);
if (LK.ticks - (self.lastTrailTime || 0) > 3) {
self.lastTrailTime = LK.ticks;
var trail = new TrailParticle(self.x, self.y, self.children[0].tint, self.scaleX);
enemyLayerMiddle.addChild(trail);
}
self.x += Math.cos(angle) * self.movementSpeed * CELL_SIZE;
self.y += Math.sin(angle) * self.movementSpeed * CELL_SIZE;
self.currentCellX = (self.x - grid.x) / CELL_SIZE - 0.5;
self.currentCellY = (self.y - grid.y) / CELL_SIZE - 0.5;
self.cellX = Math.round(self.currentCellX);
self.cellY = Math.round(self.currentCellY);
} else {
self.targetCellX = null;
self.targetCellY = null;
self.targetTower = null;
self.isIdle = true;
self.idleBaseX = targetX;
self.idleBaseY = targetY;
if (unitManager) {
unitManager.startIdleAnimation(self);
}
}
}
};
return self;
});
var UnitManager = Container.expand(function () {
var self = Container.call(this);
self.units = [];
self.spawnInterval = 180;
self.lastSpawnTime = 0;
self.maxUnits = 100;
self.spawnUnitsAroundTower = function (tower) {
var actualGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
var actualGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
if (tower.isHeadquarters) {
actualGridX -= 1;
actualGridY -= 1;
}
var totalUnits = self.units.length;
if (totalUnits >= self.maxUnits) {
return;
}
var emptySpawnPositions = [];
var unitOccupiedSpawnPositions = [];
var towerSize = 2;
var maxSearchRadius = 2;
function checkCell(checkX, checkY) {
var cell = grid.getCell(checkX, checkY);
if (!cell || cell.type !== 0) {
return;
}
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === checkX && enemies[e].cellY === checkY) {
return;
}
}
var isOccupiedByUnit = false;
for (var u = 0; u < self.units.length; u++) {
if (self.units[u].cellX === checkX && self.units[u].cellY === checkY) {
isOccupiedByUnit = true;
break;
}
}
var pos = {
x: checkX,
y: checkY
};
if (isOccupiedByUnit) {
unitOccupiedSpawnPositions.push(pos);
} else {
emptySpawnPositions.push(pos);
}
}
for (var r = 1; r <= maxSearchRadius; r++) {
for (var dx = -r; dx < towerSize + r; dx++) {
checkCell(actualGridX + dx, actualGridY - r);
checkCell(actualGridX + dx, actualGridY + towerSize - 1 + r);
}
for (var dy = -r + 1; dy < towerSize + r - 1; dy++) {
checkCell(actualGridX - r, actualGridY + dy);
checkCell(actualGridX + towerSize - 1 + r, actualGridY + dy);
}
if (emptySpawnPositions.length > 0) {
break;
}
}
var spawnPositions = emptySpawnPositions;
if (spawnPositions.length === 0) {
spawnPositions = unitOccupiedSpawnPositions;
}
if (spawnPositions.length > 0) {
var spawnPos = spawnPositions[Math.floor(Math.random() * spawnPositions.length)];
var unit = new Unit('normal');
unit.cellX = spawnPos.x;
unit.cellY = spawnPos.y;
unit.currentCellX = spawnPos.x;
unit.currentCellY = spawnPos.y;
var finalX = grid.x + spawnPos.x * CELL_SIZE + CELL_SIZE / 2;
var finalY = grid.y + (spawnPos.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
unit.x = tower.x;
unit.y = tower.y;
unit.sourceTower = tower;
unit.ownerIndex = tower.playerIndex;
unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio;
enemyLayerTop.addChild(unit);
self.units.push(unit);
unit.alpha = 0;
unit.scaleX = 0.1;
unit.scaleY = 0.1;
if (tower.playerIndex === 0) {
unit.children[0].tint = teamColors[0];
} else if (tower.playerIndex === 1) {
unit.children[0].tint = teamColors[1];
} else {
var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
unit.children[0].tint = playerColors[(tower.playerIndex - 2) % playerColors.length];
}
tween(unit, {
scaleX: 1,
scaleY: 1,
alpha: 1,
x: finalX,
y: finalY
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
unit.isIdle = true;
unit.idleBaseX = finalX;
unit.idleBaseY = finalY;
self.startIdleAnimation(unit);
}
});
}
};
self.spawnUnitsAtRow = function (playerIndex, rowIndex) {
var gridWidth = grid.cells.length;
for (var col = 0; col < gridWidth; col++) {
if (self.units.length >= self.maxUnits) {
break;
}
var cell = grid.getCell(col, rowIndex);
if (cell && cell.type === 0) {
var unit = new Unit('normal');
unit.cellX = col;
unit.cellY = rowIndex;
unit.currentCellX = col;
unit.currentCellY = rowIndex;
var finalX = grid.x + col * CELL_SIZE + CELL_SIZE / 2;
var finalY = grid.y + (rowIndex + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
unit.x = finalX;
unit.y = finalY;
unit.ownerIndex = playerIndex;
unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio;
enemyLayerTop.addChild(unit);
self.units.push(unit);
unit.alpha = 0;
unit.scaleX = 0.1;
unit.scaleY = 0.1;
if (playerIndex === 0) {
unit.children[0].tint = teamColors[0];
} else if (playerIndex === 1) {
unit.children[0].tint = teamColors[1];
} else {
var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
unit.children[0].tint = playerColors[(playerIndex - 2) % playerColors.length];
}
(function (currentUnit, finalUnitX, finalUnitY) {
tween(currentUnit, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
currentUnit.isIdle = true;
currentUnit.idleBaseX = finalUnitX;
currentUnit.idleBaseY = finalUnitY;
self.startIdleAnimation(currentUnit);
}
});
})(unit, finalX, finalY);
}
}
};
self.startIdleAnimation = function (unit) {
if (!unit.isIdle || unit.idleAnimationActive) {
return;
}
unit.idleAnimationActive = true;
var _animateFloat = function animateFloat() {
if (!unit.isIdle || !unit.parent) {
unit.idleAnimationActive = false;
return;
}
var radius = 8;
var angle = Math.random() * Math.PI * 2;
var offsetX = Math.cos(angle) * radius;
var offsetY = Math.sin(angle) * radius;
tween(unit, {
x: unit.idleBaseX + offsetX,
y: unit.idleBaseY + offsetY
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (unit.isIdle && unit.parent) {
_animateFloat();
} else {
unit.idleAnimationActive = false;
}
}
});
};
_animateFloat();
};
self.update = function () {
if (LK.ticks - self.lastSpawnTime >= self.spawnInterval) {
self.lastSpawnTime = LK.ticks;
var generatorTowerCount = 0;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.isProducing && !tutorialBlockUnitProduction) {
generatorTowerCount++;
self.spawnUnitsAroundTower(tower);
}
}
}
for (var i = self.units.length - 1; i >= 0; i--) {
var unit = self.units[i];
if (unit.health <= 0) {
enemyLayerTop.removeChild(unit);
self.units.splice(i, 1);
continue;
}
}
};
return self;
});
var UpgradeMenu = Container.expand(function (tower) {
var self = Container.call(this);
self.tower = tower;
self.y = 2732 + 225;
var menuBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
menuBackground.width = 2048;
menuBackground.height = 500;
menuBackground.tint = 0x444444;
menuBackground.alpha = 0.9;
var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
towerTypeText.anchor.set(0, 0);
towerTypeText.x = -840;
towerTypeText.y = -160;
self.addChild(towerTypeText);
var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', {
size: 70,
fill: 0xFFFFFF,
weight: 400
});
statsText.anchor.set(0, 0.5);
statsText.x = -840;
statsText.y = 50;
self.addChild(statsText);
var buttonsContainer = new Container();
buttonsContainer.x = 500;
self.addChild(buttonsContainer);
var upgradeButton = new Container();
buttonsContainer.addChild(upgradeButton);
var buttonBackground = upgradeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 500;
buttonBackground.height = 150;
buttonBackground.tint = 0x00AA00;
var buttonText = new Text2(self.tower.isProducing ? 'Defense mode' : 'Generator mode', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(buttonText);
var sellButton = new Container();
buttonsContainer.addChild(sellButton);
var sellButtonBackground = sellButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
sellButtonBackground.width = 500;
sellButtonBackground.height = 150;
sellButtonBackground.tint = 0xCC0000;
var sellButtonText = new Text2('Move out', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
if (self.tower.isHeadquarters) {
sellButton.visible = false;
}
upgradeButton.y = -85;
sellButton.y = 85;
var closeButton = new Container();
self.addChild(closeButton);
var closeBackground = closeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
closeBackground.width = 90;
closeBackground.height = 90;
closeBackground.tint = 0xAA0000;
var closeText = new Text2('X', {
size: 68,
fill: 0xFFFFFF,
weight: 800
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
closeButton.x = menuBackground.width / 2 - 57;
closeButton.y = -menuBackground.height / 2 + 57;
upgradeButton.down = function (x, y, obj) {
self.tower.changeTowerMode();
if (self.tower.isHeadquarters) {
if (tutorialActive && tutorialStep === 7 && self.tower.playerIndex === 0 && !self.tower.isProducing) {
executeTutorialStep(8);
}
}
hideUpgradeMenu(self);
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
game.removeChild(game.children[i]);
break;
}
}
selectedTower = null;
grid.renderDebug();
};
sellButton.down = function (x, y, obj) {
if (self.tower.isHeadquarters) {
return;
}
if (unitManager) {
for (var i = 0; i < 10; i++) {
unitManager.spawnUnitsAroundTower(self.tower);
}
}
self.tower.isProducing = false;
self.tower.currentShares = 0;
self.tower.capturingPlayer = -1;
self.tower.deactivate();
hideUpgradeMenu(self);
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
game.removeChild(game.children[i]);
break;
}
}
if (selectedTower === self.tower) {
selectedTower = null;
}
grid.renderDebug();
};
closeButton.down = function (x, y, obj) {
hideUpgradeMenu(self);
selectedTower = null;
grid.renderDebug();
};
self.update = function () {
// This method is now empty as the buttons are static within the menu's lifecycle.
};
self.updateButtonText = function (newText) {
buttonText.setText(newText);
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.alpha = 0.5;
self.startPulseAnimation = function () {
var _pulseAnimation2 = function _pulseAnimation() {
tween(wallGraphics, {
alpha: 1
}, {
duration: 4000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wallGraphics, {
alpha: 0.5
}, {
duration: 4000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
_pulseAnimation2();
}
}
});
}
});
};
_pulseAnimation2();
};
self.startPulseAnimation();
return self;
});
var WaveIndicator = Container.expand(function () {
var self = Container.call(this);
self.gameStarted = false;
self.waveMarkers = [];
self.waveTypes = [];
self.enemyCounts = [];
self.indicatorWidth = 0;
self.lastBossType = null;
var blockWidth = 400;
var totalBlocksWidth = blockWidth * totalWaves;
var startMarker = new Container();
var startBlock = startMarker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
startBlock.width = blockWidth - 10;
startBlock.height = 70 * 2;
startBlock.tint = 0x00AA00;
var startTextShadow = new Text2("Start Game", {
size: 50,
fill: 0x000000,
weight: 800
});
startTextShadow.anchor.set(0.5, 0.5);
startTextShadow.x = 4;
startTextShadow.y = 4;
startMarker.addChild(startTextShadow);
var startText = new Text2("Start Game", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
startText.anchor.set(0.5, 0.5);
startMarker.addChild(startText);
startMarker.x = -self.indicatorWidth;
self.addChild(startMarker);
self.waveMarkers.push(startMarker);
startMarker.down = function () {
if (!self.gameStarted) {
self.gameStarted = true;
currentWave = 0;
waveTimer = nextWaveTime;
startBlock.tint = 0x00FF00;
startText.setText("Started!");
startTextShadow.setText("Started!");
startTextShadow.x = 4;
startTextShadow.y = 4;
var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
};
for (var i = 0; i < totalWaves; i++) {
var marker = new Container();
var block = marker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
block.width = blockWidth - 10;
block.height = 70 * 2;
var waveType = "normal";
var enemyType = "normal";
var enemyCount = 10;
var isBossWave = (i + 1) % 10 === 0;
if (i === 0) {
block.tint = 0xAAAAAA;
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
} else if (isBossWave) {
enemyType = 'normal';
waveType = "Boss Normal";
block.tint = 0xAAAAAA;
enemyCount = 1;
} else {
block.tint = 0xAAAAAA;
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
}
if (isBossWave && enemyType !== 'swarm') {
var bossIndicator = marker.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
bossIndicator.width = 30;
bossIndicator.height = 30;
bossIndicator.tint = 0xFFD700;
bossIndicator.y = -block.height / 2 - 15;
waveType = "BOSS";
}
self.waveTypes[i] = enemyType;
self.enemyCounts[i] = enemyCount;
var waveTypeShadow = new Text2(waveType, {
size: 56,
fill: 0x000000,
weight: 800
});
waveTypeShadow.anchor.set(0.5, 0.5);
waveTypeShadow.x = 4;
waveTypeShadow.y = 4;
marker.addChild(waveTypeShadow);
var waveTypeText = new Text2(waveType, {
size: 56,
fill: 0xFFFFFF,
weight: 800
});
waveTypeText.anchor.set(0.5, 0.5);
waveTypeText.y = 0;
marker.addChild(waveTypeText);
var waveNumShadow = new Text2((i + 1).toString(), {
size: 48,
fill: 0x000000,
weight: 800
});
waveNumShadow.anchor.set(1.0, 1.0);
waveNumShadow.x = blockWidth / 2 - 16 + 5;
waveNumShadow.y = block.height / 2 - 12 + 5;
marker.addChild(waveNumShadow);
var waveNum = new Text2((i + 1).toString(), {
size: 48,
fill: 0xFFFFFF,
weight: 800
});
waveNum.anchor.set(1.0, 1.0);
waveNum.x = blockWidth / 2 - 16;
waveNum.y = block.height / 2 - 12;
marker.addChild(waveNum);
marker.x = -self.indicatorWidth + (i + 1) * blockWidth;
self.addChild(marker);
self.waveMarkers.push(marker);
}
self.getWaveType = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return "normal";
}
var waveType = self.waveTypes[waveNumber - 1];
return waveType;
};
self.getEnemyCount = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return 10;
}
return self.enemyCounts[waveNumber - 1];
};
self.getWaveTypeName = function (waveNumber) {
var type = self.getWaveType(waveNumber);
var typeName = type.charAt(0).toUpperCase() + type.slice(1);
if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') {
typeName = "BOSS";
}
return typeName;
};
self.positionIndicator = new Container();
var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.width = blockWidth - 10;
indicator.height = 16;
indicator.tint = 0xffad0e;
indicator.y = -65;
var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator2.width = blockWidth - 10;
indicator2.height = 16;
indicator2.tint = 0xffad0e;
indicator2.y = 65;
var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
leftWall.width = 16;
leftWall.height = 146;
leftWall.tint = 0xffad0e;
leftWall.x = -(blockWidth - 16) / 2;
var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
rightWall.width = 16;
rightWall.height = 146;
rightWall.tint = 0xffad0e;
rightWall.x = (blockWidth - 16) / 2;
self.addChild(self.positionIndicator);
self.update = function () {
var progress = waveTimer / nextWaveTime;
var moveAmount = (progress + currentWave) * blockWidth;
for (var i = 0; i < self.waveMarkers.length; i++) {
var marker = self.waveMarkers[i];
marker.x = -moveAmount + i * blockWidth;
}
self.positionIndicator.x = 0;
for (var i = 0; i < totalWaves + 1; i++) {
var marker = self.waveMarkers[i];
if (i === 0) {
continue;
}
var block = marker.children[0];
if (i - 1 < currentWave) {
block.alpha = .5;
}
}
self.handleWaveProgression = function () {
if (!self.gameStarted) {
return;
}
if (currentWave < totalWaves) {
waveTimer++;
if (waveTimer >= nextWaveTime) {
waveTimer = 0;
currentWave++;
waveInProgress = true;
waveSpawned = false;
if (currentWave != 1) {
var waveType = self.getWaveTypeName(currentWave);
var enemyCount = self.getEnemyCount(currentWave);
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
}
}
};
self.handleWaveProgression();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x333333
});
/****
* Game Code
****/
var aiPlayer = null;
var teamColors = [0x00AA00, 0xAA0000];
var isHidingUpgradeMenu = false;
var isSelectingUnits = false;
var selectionStartX = 0;
var selectionStartY = 0;
var selectionCircle = null;
var selectedUnits = [];
var isCommandMode = false;
var isEndGame = false;
function handleGameEnd(isVictory) {
if (isEndGame) {
return;
}
isEndGame = true;
var currentLevel = storage.currentLevel || 1;
if (isVictory && currentLevel <= 20) {
storage.currentLevel = currentLevel + 1;
var message = "Level " + (storage.currentLevel - 1) + " Complete!";
var endText = new Text2(message, {
size: 150,
fill: 0x00FF00,
weight: "bold",
align: 'center'
});
endText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(endText);
LK.setTimeout(initNewLevel, 3000);
return;
}
var message = isVictory ? "Victory!" : "Defeat!";
var color = isVictory ? 0x00FF00 : 0xFF0000;
var endText = new Text2(message, {
size: 150,
fill: color,
weight: "bold",
align: 'center'
});
endText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(endText);
LK.setTimeout(function () {
if (isVictory) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}, 3000);
}
function initNewLevel() {
// 1. Clean up from previous level
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].destroy();
}
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
enemyLayerTop.removeChildren();
enemyLayerMiddle.removeChildren();
enemyLayerBottom.removeChildren();
if (grid && grid.parent) {
grid.destroy();
}
if (unitManager && unitManager.parent) {
unitManager.destroy();
}
if (aiPlayer && aiPlayer.parent) {
aiPlayer.destroy();
}
if (towerPreview && towerPreview.parent) {
towerPreview.destroy();
}
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
if (selectionCircle) {
selectionCircle.destroy();
}
LK.gui.center.removeChildren();
// 2. Reset state variables
pathId = 1;
maxScore = 0;
enemies = [];
towers = [];
bullets = [];
defenses = [];
selectedTower = null;
gold = 80;
currentWave = 0;
waveTimer = 0;
waveInProgress = false;
waveSpawned = false;
sourceTower = null;
isSelectingUnits = false;
isCommandMode = false;
selectionCircle = null;
selectedUnits = [];
// 3. Initialize new level from scratch
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * mapGridOffset;
debugLayer.addChild(grid);
unitManager = new UnitManager();
game.addChild(unitManager);
addHeadquarters();
addEmptyTowers();
grid.pathFind();
grid.renderDebug();
aiPlayer = new AIPlayer(1);
game.addChild(aiPlayer);
towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
updateUI();
isEndGame = false;
}
function hideUpgradeMenu(menu) {
if (isHidingUpgradeMenu) {
return;
}
isHidingUpgradeMenu = true;
tween(menu, {
y: 2732 + 225
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
menu.destroy();
isHidingUpgradeMenu = false;
}
});
}
var isDebug = false;
var CELL_SIZE = 76;
var pathId = 1;
var maxScore = 0;
var mapGridOffset = 4;
var enemies = [];
var towers = [];
var bullets = [];
var defenses = [];
var selectedTower = null;
var unitManager = null;
var gold = 80;
var lives = 20;
var score = 0;
var currentWave = 0;
var totalWaves = 50;
var waveTimer = 0;
var waveInProgress = false;
var waveSpawned = false;
var nextWaveTime = 12000 / 2;
var sourceTower = null;
var enemiesToSpawn = 10;
var enemyMovementSpeedRatio = 0.6;
// Map cell values: 0 = empty cell, 1 = player base, 2 = enemy 1 base, 3 = enemy 2 base,
// 4 = player tower, 5 = enemy 1 tower, 6 = enemy 2 tower, 8 = empty tower, 9 = wall,
// 11 = player base (rotated), 22 = enemy 1 base (rotated), 33 = enemy 2 base (rotated)
var MAPS = [
// Level 0: Tutorial tower defense layout (24x31)
[[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 22, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
// Row 0
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 5
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 6
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 7
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 8
[9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 9
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 10
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 11
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 12
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 13
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 14
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 15
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 16
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 17
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 18
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 19
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 20
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 21
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 22
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 23
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 24
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 25
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 26
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 27
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 28
[9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 29
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 30
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 31
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 32
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 33
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] // Row 34 - Player base area
],
// Level 1: Basic tower defense layout (24x31)
[[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 22, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
// Row 0
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 5
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 6
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 7
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 8
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 9
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 10
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 11
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 12
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 13
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 14
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 15
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 16
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 17
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 18
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 19
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 20
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 21
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 22
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 23
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 24
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 25
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 26
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 27
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 28
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 29
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 30
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 31
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 32
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 33
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] // Row 34 - Player base area
]];
function initMapForLevel(level) {
// Level 0 is tutorial, actual levels start at 1
if (level <= 0) {
return MAPS[0]; // Return tutorial map
}
// Use level 1 base map (MAPS[1]) as template
var baseMap = MAPS[1];
var mapHeight = baseMap.length;
var mapWidth = baseMap[0].length;
// Create a deep copy of the base map
var newMap = [];
for (var i = 0; i < mapHeight; i++) {
newMap[i] = [];
for (var j = 0; j < mapWidth; j++) {
newMap[i][j] = baseMap[i][j];
}
}
// Calculate number of towers to add: 2x(3+level-1) = 2x(2+level)
var towersToAdd = 2 * (2 + level);
// Find all empty cells between rows 10 and 26 (excluding walls and bases)
var availableCells = [];
for (var row = 10; row <= 26 - mapGridOffset; row++) {
for (var col = 1; col < mapWidth - 1; col++) {
// Exclude wall boundaries
if (newMap[row][col] === 0) {
// Empty cell
availableCells.push({
row: row,
col: col
});
}
}
}
// Shuffle available cells for random placement
for (var i = availableCells.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = availableCells[i];
availableCells[i] = availableCells[j];
availableCells[j] = temp;
}
// Place towers symmetrically around the center line (column 12)
var centerCol = Math.floor(mapWidth / 2);
var towersPlaced = 0;
for (var i = 0; i < availableCells.length && towersPlaced < towersToAdd; i++) {
var cell = availableCells[i];
var row = cell.row;
var col = cell.col;
// Calculate the symmetric position
var symmetricCol = centerCol + (centerCol - col);
// Check if both positions are valid and empty
if (symmetricCol >= 1 && symmetricCol < mapWidth - 1 && newMap[row][col] === 0 && newMap[row][symmetricCol] === 0) {
// Place towers at both symmetric positions
newMap[row][col] = 8; // Empty tower
if (col !== symmetricCol) {
// Don't place twice if it's on center line
newMap[row][symmetricCol] = 8; // Empty tower
towersPlaced += 2;
} else {
towersPlaced += 1;
}
}
}
return newMap;
}
var goldText = new Text2('Gold: ' + gold, {
size: 60,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
var livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: 0x00FF00,
weight: 800
});
livesText.anchor.set(0.5, 0.5);
var scoreText = new Text2('Score: ' + score, {
size: 60,
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
var topMargin = -75;
var centerX = 2048 / 2;
var spacing = 400;
/*
LK.gui.bottom.addChild(goldText);
LK.gui.bottom.addChild(livesText);
LK.gui.bottom.addChild(scoreText);
*/
livesText.x = 0;
livesText.y = topMargin;
goldText.x = -spacing;
goldText.y = topMargin;
scoreText.x = spacing;
scoreText.y = topMargin;
function updateUI() {
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
}
function setGold(value) {
gold = value;
updateUI();
}
var backgroundManager = new BackgroundManager();
game.addChild(backgroundManager);
var background1 = backgroundManager.addBackground('background_1', {
x: 1024,
y: 1366,
scaleX: 1,
scaleY: 1,
alpha: 0.8,
tint: 0xCCCCCC
});
var background2 = backgroundManager.addBackground('background_2', {
x: 1024,
y: 1366,
scaleX: 1,
scaleY: 1,
alpha: 1
});
var background3 = backgroundManager.addBackground('background_3', {
x: 1024,
y: 1366,
scaleX: 1,
scaleY: 1,
alpha: 1
});
backgroundManager.createParallaxEffect([background1, background2, background3], 1);
var debugLayer = new Container();
var enemyLayerBottom = new Container();
var enemyLayerMiddle = new Container();
var enemyLayerTop = new Container();
var enemyLayer = new Container();
enemyLayer.addChild(enemyLayerBottom);
enemyLayer.addChild(enemyLayerMiddle);
enemyLayer.addChild(enemyLayerTop);
var grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * mapGridOffset;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
game.addChild(debugLayer);
unitManager = new UnitManager();
game.addChild(unitManager);
function addHeadquarters() {
var playerBaseCells = [];
var enemyBaseCells = [];
for (var i = 0; i < grid.cells.length; i++) {
for (var j = 0; j < grid.cells[i].length; j++) {
var cell = grid.cells[i][j];
if (cell.type === 3) {
playerBaseCells.push(cell);
} else if (cell.type === 2) {
enemyBaseCells.push(cell);
}
}
}
if (playerBaseCells.length > 0) {
var playerOrientation = 0;
for (var i = 0; i < playerBaseCells.length; i++) {
if (playerBaseCells[i].orientation) {
playerOrientation = playerBaseCells[i].orientation;
break;
}
}
var playerHQ = new Tower(true, playerOrientation, 0);
var playerAnchorX = 10;
var playerAnchorY = 31;
playerHQ.x = grid.x + (playerAnchorX + 1.5) * CELL_SIZE;
playerHQ.y = grid.y + (playerAnchorY + 2) * CELL_SIZE;
playerHQ.children[0].tint = teamColors[0];
game.addChild(playerHQ);
towers.push(playerHQ);
}
if (enemyBaseCells.length > 0) {
var enemyOrientation = 0;
for (var i = 0; i < enemyBaseCells.length; i++) {
if (enemyBaseCells[i].orientation) {
enemyOrientation = enemyBaseCells[i].orientation;
break;
}
}
var enemyHQ = new Tower(true, enemyOrientation, 1);
var enemyAnchorX = 10;
var enemyAnchorY = 0;
enemyHQ.x = grid.x + (enemyAnchorX + 1.5) * CELL_SIZE;
if (enemyOrientation !== 0) {
enemyHQ.y = grid.y + (enemyAnchorY + 5) * CELL_SIZE;
} else {
enemyHQ.y = grid.y + (enemyAnchorY + 2) * CELL_SIZE;
}
enemyHQ.children[0].tint = teamColors[1];
game.addChild(enemyHQ);
towers.push(enemyHQ);
}
}
function addEmptyTowers() {
var emptyTowerCells = [];
for (var i = 0; i < grid.cells.length; i++) {
for (var j = 0; j < grid.cells[i].length; j++) {
var cell = grid.cells[i][j];
if (cell.type === 4) {
emptyTowerCells.push(cell);
}
}
}
for (var i = 0; i < emptyTowerCells.length; i++) {
var cell = emptyTowerCells[i];
var emptyTower = new Tower(false, 0, -1);
emptyTower.x = grid.x + cell.x * CELL_SIZE + CELL_SIZE / 2;
emptyTower.y = grid.y + (cell.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
var baseGraphics = emptyTower.children[0];
baseGraphics.width = CELL_SIZE;
baseGraphics.height = CELL_SIZE;
baseGraphics.tint = 0xAAAAAA;
var gunContainer = emptyTower.children[emptyTower.children.length - 1];
gunContainer.visible = false;
game.addChild(emptyTower);
towers.push(emptyTower);
cell.type = 1;
cell.isVisuallyEmptyTower = true;
}
if (emptyTowerCells.length > 0) {
grid.pathFind();
grid.renderDebug();
}
}
addHeadquarters();
addEmptyTowers();
var tutorialActive = false;
var tutorialStep = 0;
var tutorialText = null;
var tutorialOverlayTop = null;
var tutorialOverlayBottom = null;
var tutorialWaitForTap = false;
var tutorialBlockUnitProduction = true;
var tutorialBlockUnitUpdates = true;
var tutorialBlockTowerUpdates = true;
function getPlayerHQ() {
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 0) {
return towers[i];
}
}
return null;
}
function findFirstEmptyTower() {
var potentialTowers = [];
for (var i = 0; i < towers.length; i++) {
if (!towers[i].isHeadquarters && towers[i].playerIndex === -1) {
potentialTowers.push(towers[i]);
}
}
if (potentialTowers.length === 0) {
return null;
}
var playerHQ = getPlayerHQ();
if (!playerHQ) {
return potentialTowers[0];
}
var closestTower = null;
var closestDistSq = Infinity;
for (var i = 0; i < potentialTowers.length; i++) {
var tower = potentialTowers[i];
var dx = tower.x - playerHQ.x;
var dy = tower.y - playerHQ.y;
var distSq = dx * dx + dy * dy;
if (distSq < closestDistSq) {
closestDistSq = distSq;
closestTower = tower;
}
}
return closestTower;
}
function executeTutorialStep(step) {
tutorialStep = step;
tutorialOverlayTop.visible = false;
tutorialOverlayBottom.visible = false;
tutorialText.setText('');
tutorialWaitForTap = false;
tutorialBlockUnitProduction = true;
tutorialBlockUnitUpdates = true;
tutorialBlockTowerUpdates = true;
switch (step) {
case 1:
// Enemy Movement
var playerHQ = getPlayerHQ();
if (playerHQ && unitManager) {
var towerSize = playerHQ.isHeadquarters ? 4 : 2;
var hqGridX = Math.round((playerHQ.x - grid.x) / CELL_SIZE) - towerSize / 2;
var hqGridY = Math.round((playerHQ.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset;
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex === 1) {
unit.setTarget(hqGridX, hqGridY);
}
}
}
tutorialBlockUnitUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(2);
}, 3000);
break;
case 2:
// Enemy Alert
var enemyRowY = 700 + (2 + mapGridOffset) * CELL_SIZE;
tutorialOverlayTop.visible = true;
var enemyHQ = null;
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 1) {
enemyHQ = towers[i];
break;
}
}
if (enemyHQ) {
tutorialOverlayTop.height = enemyHQ.y - 150;
} else {
tutorialOverlayTop.height = 150;
}
tutorialOverlayBottom.visible = true;
tutorialOverlayBottom.y = enemyRowY + CELL_SIZE / 2;
tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y;
tutorialText.setText("Enemy Wave Incoming!\n(tap to continue)");
tutorialWaitForTap = true;
break;
case 3:
// Unit Selection
var playerHQ = getPlayerHQ();
if (playerHQ) {
var visibleTopY = playerHQ.y - 300;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = visibleTopY;
}
tutorialText.setText("Select 10 of your units!");
break;
case 4:
// Tower Capture
console.log("Tuto step 4: Tower capture", tutorialBlockUnitUpdates);
var emptyTower = findFirstEmptyTower();
if (emptyTower) {
var towerY = emptyTower.y;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = towerY - 150;
tutorialOverlayBottom.visible = true;
tutorialOverlayBottom.y = towerY + 150;
tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y;
tutorialText.setText("Capture an empty tower.");
/*
tutorialBlockUnitUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(5);
}, 1000);
*/
}
break;
case 5:
// Unit Movement for Capture
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(6);
}, 1000);
break;
case 6:
// HQ Selection
console.log("Tuto step 6: HQ Selection", tutorialBlockUnitUpdates);
var playerHQ = getPlayerHQ();
if (playerHQ) {
var hqY = playerHQ.y;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = hqY - 200;
tutorialOverlayBottom.visible = true;
tutorialOverlayBottom.y = hqY + 200;
tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y;
tutorialText.setText("Select your HQ.");
}
break;
case 7:
// Switch to Defense
var visibleTopY = 2732 - 500;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = visibleTopY;
tutorialText.setText("Switch to Defense mode...");
break;
case 8:
// HQ Attacking
tutorialOverlayTop.visible = false;
tutorialOverlayBottom.visible = false;
tutorialText.setText('');
tutorialBlockTowerUpdates = false;
tutorialBlockUnitUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(9);
}, 10000);
break;
case 9:
tutorialOverlayTop.visible = false;
tutorialOverlayBottom.visible = false;
tutorialBlockTowerUpdates = false;
tutorialBlockUnitUpdates = false;
tutorialText.setText("Wave cleared!");
LK.setTimeout(function () {
executeTutorialStep(10);
}, 2000);
break;
case 10:
// "Now take your revenge!"
tutorialText.setText("Now take your revenge!");
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
LK.setTimeout(function () {
// End Tutorial
tutorialActive = false;
tutorialBlockUnitProduction = false;
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
storage.hasPlayedTutorial = true;
tutorialText.setText('');
var notification = game.addChild(new Notification("Tutorial Complete!"));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
}, 2000);
break;
}
}
function playTutorial() {
tutorialActive = true;
tutorialStep = 0;
tutorialOverlayTop = game.addChild(LK.getAsset('notification', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 100
}));
tutorialOverlayTop.tint = 0x000000;
tutorialOverlayTop.alpha = 0.8;
tutorialOverlayTop.visible = false;
tutorialOverlayBottom = game.addChild(LK.getAsset('notification', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 100
}));
tutorialOverlayBottom.tint = 0x000000;
tutorialOverlayBottom.alpha = 0.8;
tutorialOverlayBottom.visible = false;
tutorialText = new Text2('', {
size: 70,
fill: 0xFFFFFF,
weight: "bold",
align: 'center',
wordWrap: true,
wordWrapWidth: 1200
});
tutorialText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tutorialText);
executeTutorialStep(1);
}
function initLevel0Units() {
unitManager.spawnUnitsAtRow(1, 2);
var playerHQ = null;
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 0) {
playerHQ = towers[i];
break;
}
}
if (playerHQ) {
for (var i = 0; i < 10; i++) {
unitManager.spawnUnitsAroundTower(playerHQ);
}
}
}
var offset = 0;
game.addChild(enemyLayer);
var towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
var isDragging = false;
var hasPlayedTutorial = storage.hasPlayedTutorial || false;
// FOR DEBUGGING
//hasPlayedTutorial = false;
//storage.hasPlayedTutorial = false;
// FOR DEBUGGING
if (!hasPlayedTutorial) {
initLevel0Units();
LK.setTimeout(playTutorial, 1000);
} else {
tutorialActive = false;
tutorialBlockUnitProduction = false;
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
}
aiPlayer = new AIPlayer(1);
game.addChild(aiPlayer);
function wouldBlockPath(gridX, gridY) {
var cells = [];
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cells.push({
cell: cell,
originalType: cell.type
});
cell.type = 1;
}
}
}
var blocked = grid.pathFind();
for (var i = 0; i < cells.length; i++) {
cells[i].cell.type = cells[i].originalType;
}
grid.pathFind();
grid.renderDebug();
return blocked;
}
function getTowerCost(towerType) {
var cost = 5;
cost = 5;
return cost;
}
function getTowerSellValue(totalValue) {
return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue;
}
function placeTower(gridX, gridY, towerType) {
var towerCost = getTowerCost(towerType);
if (gold >= towerCost) {
var tower = new Tower(false, undefined, 0);
tower.placeOnGrid(gridX, gridY);
game.addChild(tower);
towers.push(tower);
setGold(gold - towerCost);
grid.pathFind();
grid.renderDebug();
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
game.down = function (x, y, obj) {
if (tutorialActive && tutorialWaitForTap) {
executeTutorialStep(tutorialStep + 1);
return;
}
var upgradeMenuVisible = game.children.some(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenuVisible) {
return;
}
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var towerLeft = tower.x - CELL_SIZE;
var towerRight = tower.x + CELL_SIZE;
var towerTop = tower.y - CELL_SIZE;
var towerBottom = tower.y + CELL_SIZE;
if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
clickedOnTower = true;
break;
}
}
if (sourceTower) {
var sourceLeft = sourceTower.x - sourceTower.width / 2;
var sourceRight = sourceTower.x + sourceTower.width / 2;
var sourceTop = sourceTower.y - sourceTower.height / 2;
var sourceBottom = sourceTower.y + sourceTower.height / 2;
if (x >= sourceLeft && x <= sourceRight && y >= sourceTop && y <= sourceBottom) {
clickedOnTower = true;
}
}
if (!clickedOnTower && !isDragging && !isCommandMode) {
if (selectionCircle) {
game.removeChild(selectionCircle);
selectionCircle = null;
}
isSelectingUnits = true;
selectionStartX = x;
selectionStartY = y;
selectionCircle = new SelectionCircle();
selectionCircle.setPosition(x, y);
game.addChild(selectionCircle);
}
};
game.move = function (x, y, obj) {
if (isDragging) {
towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
} else if (isSelectingUnits && selectionCircle) {
var screenMargin = 50;
if (x < -screenMargin || x > 2048 + screenMargin || y < -screenMargin || y > 2732 + screenMargin) {
isSelectingUnits = false;
if (selectionCircle) {
game.removeChild(selectionCircle);
selectionCircle = null;
}
} else {
selectionCircle.updateCircle(x, y);
}
}
};
game.up = function (x, y, obj) {
if (isSelectingUnits && selectionCircle) {
isSelectingUnits = false;
for (var i = 0; i < selectedUnits.length; i++) {
selectedUnits[i].setSelected(false);
}
selectedUnits = [];
var centerX = selectionCircle.startX;
var centerY = selectionCircle.startY;
var radius = selectionCircle.currentRadius;
if (unitManager && unitManager.units) {
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex === 0) {
var dx = unit.x - centerX;
var dy = unit.y - centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= radius) {
unit.setSelected(true);
selectedUnits.push(unit);
}
}
}
}
game.removeChild(selectionCircle);
selectionCircle = null;
if (selectedUnits.length > 0) {
var notification = game.addChild(new Notification(selectedUnits.length + " units selected"));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
isCommandMode = true;
if (tutorialActive && tutorialStep === 3 && selectedUnits.length === 10) {
executeTutorialStep(4);
}
}
return;
}
if (isCommandMode && selectedUnits.length > 0) {
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
var cellX = Math.floor(gridPosX / CELL_SIZE);
var cellY = Math.floor(gridPosY / CELL_SIZE) - mapGridOffset;
var targetCell = grid.getCell(cellX, cellY);
if (!targetCell || targetCell.type === 1 || targetCell.type === 9) {
var closestCell = null;
var closestDistance = Infinity;
var searchRadius = 1;
var maxSearchRadius = 10;
while (!closestCell && searchRadius <= maxSearchRadius) {
for (var dx = -searchRadius; dx <= searchRadius; dx++) {
for (var dy = -searchRadius; dy <= searchRadius; dy++) {
if (Math.abs(dx) === searchRadius || Math.abs(dy) === searchRadius) {
var checkX = cellX + dx;
var checkY = cellY + dy;
var checkCell = grid.getCell(checkX, checkY);
if (checkCell && checkCell.type === 0) {
var dist = dx * dx + dy * dy;
if (dist < closestDistance) {
closestDistance = dist;
closestCell = checkCell;
}
}
}
}
}
searchRadius++;
}
targetCell = closestCell;
}
var targetHasTower = false;
var enemyTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.playerIndex !== 0) {
var towerGridX, towerGridY;
var towerSize = tower.isHeadquarters ? 4 : 2;
if (tower.isHeadquarters) {
towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2;
towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset;
} else {
towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
}
if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) {
targetHasTower = true;
enemyTower = tower;
console.log("Empty tower tapped", tutorialActive, tutorialStep === 4, tower.playerIndex === -1, towerGridY, selectedUnits.length === 10);
if (tutorialActive && tutorialStep === 4 && tower.playerIndex === -1 && towerGridY === 25 && selectedUnits.length === 10) {
tutorialBlockUnitUpdates = false;
console.log("Tuto Step 4.5 : Ok units capturing tower", tutorialBlockUnitUpdates);
LK.setTimeout(function () {
executeTutorialStep(5);
}, 2000);
}
break;
}
}
}
if (targetCell && targetCell.type === 0 || targetHasTower) {
for (var i = 0; i < selectedUnits.length; i++) {
var unit = selectedUnits[i];
if (targetHasTower) {
unit.setTarget(cellX, cellY);
} else {
unit.setTarget(targetCell.x, targetCell.y);
}
}
var message;
if (targetHasTower) {
if (enemyTower.playerIndex === -1) {
message = "Units capturing tower!";
} else {
message = "Units attacking enemy tower!";
}
} else {
message = "Units moving to target";
}
if (!(tutorialActive && tutorialStep === 3 && selectedUnits.length < 10)) {
var notification = game.addChild(new Notification(message));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
}
for (var i = 0; i < selectedUnits.length; i++) {
selectedUnits[i].setSelected(false);
}
selectedUnits = [];
isCommandMode = false;
} else {
var notification = game.addChild(new Notification("Invalid target location"));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
}
return;
}
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var towerLeft = tower.x - tower.width / 2;
var towerRight = tower.x + tower.width / 2;
var towerTop = tower.y - tower.height / 2;
var towerBottom = tower.y + tower.height / 2;
if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
clickedOnTower = true;
break;
}
}
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) {
var clickedOnMenu = false;
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
var menuWidth = 2048;
var menuHeight = 450;
var menuLeft = menu.x - menuWidth / 2;
var menuRight = menu.x + menuWidth / 2;
var menuTop = menu.y - menuHeight / 2;
var menuBottom = menu.y + menuHeight / 2;
if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) {
clickedOnMenu = true;
break;
}
}
if (!clickedOnMenu) {
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
hideUpgradeMenu(menu);
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = null;
grid.renderDebug();
}
}
if (isDragging) {
isDragging = false;
if (towerPreview.canPlace) {
if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) {
placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType);
} else {
var notification = game.addChild(new Notification("Tower would block the path!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
} else if (towerPreview.blockedByEnemy) {
var notification = game.addChild(new Notification("Cannot build: Enemy in the way!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
} else if (towerPreview.visible) {
var notification = game.addChild(new Notification("Cannot build here!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
towerPreview.visible = false;
if (isDragging) {
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
}
}
};
var waveIndicator = null;
/*
var waveIndicator = new WaveIndicator();
waveIndicator.x = 2048 / 2;
waveIndicator.y = 2732 - 80;
game.addChild(waveIndicator);
*/
var nextWaveButtonContainer = new Container();
var nextWaveButton = new NextWaveButton();
nextWaveButton.x = 2048 - 200;
nextWaveButton.y = 2732 - 100 + 20;
nextWaveButtonContainer.addChild(nextWaveButton);
game.addChild(nextWaveButtonContainer);
sourceTower = null;
enemiesToSpawn = 10;
game.update = function () {
if (isEndGame) {
return;
}
if (aiPlayer) {
aiPlayer.update();
}
if (waveInProgress) {
if (!waveSpawned) {
waveSpawned = true;
var waveType = waveIndicator.getWaveType(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
var isBossWave = currentWave % 10 === 0 && currentWave > 0;
if (isBossWave) {
enemyCount = 1;
var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️"));
notification.x = 2048 / 2;
notification.y = grid.height - 200;
}
for (var i = 0; i < enemyCount; i++) {
var enemy = new Enemy(waveType);
if (enemy.isFlying) {
enemyLayerTop.addChild(enemy);
if (enemy.shadow) {
enemyLayerMiddle.addChild(enemy.shadow);
}
} else {
enemyLayerBottom.addChild(enemy);
}
var healthMultiplier = Math.pow(1.12, currentWave);
enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier);
enemy.health = enemy.maxHealth;
var gridWidth = 24;
var midPoint = Math.floor(gridWidth / 2);
var availableColumns = [];
for (var col = midPoint - 3; col < midPoint + 3; col++) {
var columnOccupied = false;
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === col && enemies[e].currentCellY < 4) {
columnOccupied = true;
break;
}
}
if (!columnOccupied) {
availableColumns.push(col);
}
}
var spawnX;
if (availableColumns.length > 0) {
spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)];
} else {
spawnX = midPoint - 3 + Math.floor(Math.random() * 6);
}
var spawnY = -1 - Math.random() * 5;
enemy.cellX = spawnX;
enemy.cellY = mapGridOffset;
enemy.currentCellX = spawnX;
enemy.currentCellY = spawnY;
enemy.waveNumber = currentWave;
enemies.push(enemy);
}
}
var currentWaveEnemiesRemaining = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].waveNumber === currentWave) {
currentWaveEnemiesRemaining = true;
break;
}
}
if (waveSpawned && !currentWaveEnemiesRemaining) {
waveInProgress = false;
waveSpawned = false;
}
}
for (var a = enemies.length - 1; a >= 0; a--) {
var enemy = enemies[a];
if (enemy.health <= 0) {
for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) {
var bullet = enemy.bulletsTargetingThis[i];
bullet.targetEnemy = null;
}
var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5);
//var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y);
//game.addChild(goldIndicator);
setGold(gold + goldEarned);
var scoreValue = enemy.isBoss ? 100 : 5;
score += scoreValue;
if (enemy.isBoss) {
var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
updateUI();
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);
}
enemies.splice(a, 1);
continue;
}
if (grid.updateEnemy(enemy)) {
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);
}
enemies.splice(a, 1);
lives = Math.max(0, lives - 1);
updateUI();
if (lives <= 0) {
LK.showGameOver();
}
}
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (!bullets[i].parent) {
if (bullets[i].targetEnemy) {
var targetEnemy = bullets[i].targetEnemy;
var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]);
if (bulletIndex !== -1) {
targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
bullets.splice(i, 1);
}
}
if (towerPreview.visible) {
towerPreview.checkPlacement();
}
if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) {
LK.showYouWin();
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1");
/****
* Classes
****/
var AIPlayer = Container.expand(function (playerIndex) {
var self = Container.call(this);
self.playerIndex = playerIndex;
self.state = 'EXPLORING';
self.hq = null;
self.updateCooldown = 0;
self.attackWaveSize = 15;
self.exploreGroupSize = 10;
self.findHq = function () {
if (self.hq && self.hq.parent) {
return;
}
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === self.playerIndex) {
self.hq = towers[i];
break;
}
}
};
self.getUnits = function (isIdleOnly) {
var myUnits = [];
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex === self.playerIndex) {
if (isIdleOnly && !unit.isIdle) {
continue;
}
myUnits.push(unit);
}
}
return myUnits;
};
self.getTowers = function (ownerIndex) {
var foundTowers = [];
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.isHeadquarters) {
continue;
}
if (ownerIndex === -1 && tower.playerIndex === -1) {
foundTowers.push(tower);
} else if (tower.playerIndex === ownerIndex) {
foundTowers.push(tower);
}
}
return foundTowers;
};
self.isHqUnderAttack = function () {
if (!self.hq) {
return false;
}
if (self.hq.lastHealth === undefined) {
self.hq.lastHealth = self.hq.health;
}
if (self.hq.health < self.hq.lastHealth) {
self.hq.lastHealth = self.hq.health;
return true;
}
self.hq.lastHealth = self.hq.health;
var playerUnits = unitManager.units.filter(function (u) {
return u.ownerIndex === 0;
});
var attackRangeSq = self.hq.getRange() * self.hq.getRange();
for (var i = 0; i < playerUnits.length; i++) {
var unit = playerUnits[i];
var dx = unit.x - self.hq.x;
var dy = unit.y - self.hq.y;
if (dx * dx + dy * dy < attackRangeSq) {
return true;
}
}
return false;
};
self.findClosestTarget = function (source, targets) {
var closestTarget = null;
var closestDistSq = Infinity;
for (var i = 0; i < targets.length; i++) {
var target = targets[i];
var dx = target.x - source.x;
var dy = target.y - source.y;
var distSq = dx * dx + dy * dy;
if (distSq < closestDistSq) {
closestDistSq = distSq;
closestTarget = target;
}
}
return closestTarget;
};
self.commandUnits = function (units, target) {
if (!target) {
return;
}
var targetGridX = Math.floor((target.x - grid.x) / CELL_SIZE);
var targetGridY = Math.floor((target.y - grid.y) / CELL_SIZE) - mapGridOffset;
for (var i = 0; i < units.length; i++) {
var unit = units[i];
unit.setTarget(targetGridX, targetGridY);
unit.isIdle = false;
}
};
self.update = function () {
if (tutorialActive || !self.hq) {
self.findHq();
return;
}
self.updateCooldown--;
if (self.updateCooldown > 0) {
return;
}
self.updateCooldown = 120;
var idleUnits = self.getUnits(true);
if (self.isHqUnderAttack()) {
self.state = 'DEFENDING';
} else {
var neutralTowers = self.getTowers(-1);
if (neutralTowers.length > 0) {
self.state = 'EXPLORING';
} else {
self.state = 'ATTACKING';
}
}
if (self.state === 'DEFENDING') {
if (self.hq.isProducing) {
self.hq.changeTowerMode();
}
} else if (self.state === 'EXPLORING') {
if (!self.hq.isProducing) {
self.hq.changeTowerMode();
}
if (idleUnits.length >= self.exploreGroupSize) {
var neutralTowers = self.getTowers(-1);
if (neutralTowers.length > 0) {
var targetTower = self.findClosestTarget(self.hq, neutralTowers);
if (targetTower) {
var unitsToCommand = idleUnits.slice(0, self.exploreGroupSize);
self.commandUnits(unitsToCommand, targetTower);
}
}
}
} else if (self.state === 'ATTACKING') {
if (!self.hq.isProducing) {
self.hq.changeTowerMode();
}
if (idleUnits.length >= self.attackWaveSize) {
var playerTowers = self.getTowers(0);
var target;
if (playerTowers.length > 0) {
target = self.findClosestTarget(self.hq, playerTowers);
} else {
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 0) {
target = towers[i];
break;
}
}
}
if (target) {
self.commandUnits(idleUnits, target);
}
}
}
};
return self;
});
var BackgroundManager = Container.expand(function () {
var self = Container.call(this);
self.backgrounds = [];
self.currentBackground = null;
self.animationActive = false;
self.addBackground = function (assetId, config) {
config = config || {};
var background = new Container();
var bgGraphics = background.attachAsset(assetId, {
anchorX: config.anchorX || 0.5,
anchorY: config.anchorY || 0.5,
x: config.x || 0,
y: config.y || 0,
scaleX: config.scaleX || 1,
scaleY: config.scaleY || 1,
alpha: config.alpha !== undefined ? config.alpha : 1,
tint: config.tint || 0xFFFFFF
});
background.config = config;
background.graphics = bgGraphics;
background.assetId = assetId;
self.addChild(background);
self.backgrounds.push(background);
return background;
};
self.removeBackground = function (background) {
var index = self.backgrounds.indexOf(background);
if (index !== -1) {
self.backgrounds.splice(index, 1);
self.removeChild(background);
}
};
self.animateBackground = function (background, properties, config) {
if (!background || !background.graphics) {
return;
}
config = config || {};
var duration = config.duration || 1000;
var easing = config.easing || tween.linear;
var loop = config.loop || false;
var _onFinish = config.onFinish;
var _animateFunc = function animateFunc() {
tween(background.graphics, properties, {
duration: duration,
easing: easing,
onFinish: function onFinish() {
if (loop && self.animationActive) {
if (config.resetOnLoop) {
for (var prop in config.resetOnLoop) {
background.graphics[prop] = config.resetOnLoop[prop];
}
}
_animateFunc();
} else if (_onFinish) {
_onFinish();
}
}
});
};
self.animationActive = true;
_animateFunc();
};
self.stopAnimations = function () {
self.animationActive = false;
for (var i = 0; i < self.backgrounds.length; i++) {
if (self.backgrounds[i].graphics) {
tween.stop(self.backgrounds[i].graphics);
}
}
};
self.fadeToBackground = function (newBackground, duration, onComplete) {
duration = duration || 1000;
if (self.currentBackground && self.currentBackground !== newBackground) {
tween(self.currentBackground.graphics, {
alpha: 0
}, {
duration: duration / 2,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.currentBackground.visible = false;
}
});
}
newBackground.visible = true;
newBackground.graphics.alpha = 0;
tween(newBackground.graphics, {
alpha: 1
}, {
duration: duration / 2,
easing: tween.easeInOut,
onFinish: function onFinish() {
self.currentBackground = newBackground;
if (onComplete) {
onComplete();
}
}
});
};
self.createParallaxEffect = function (backgrounds, baseSpeed) {
baseSpeed = baseSpeed || 10;
for (var i = 0; i < backgrounds.length; i++) {
(function (index) {
var bg1 = backgrounds[index];
var speed = baseSpeed * (index + 1) * 0.5;
var bg2 = self.addBackground(bg1.assetId, {
anchorX: bg1.config.anchorX || 0.5,
anchorY: bg1.config.anchorY || 0.5,
x: bg1.graphics.x + bg1.graphics.width,
y: bg1.config.y || 0,
scaleX: -1,
scaleY: bg1.config.scaleY || 1,
alpha: bg1.config.alpha !== undefined ? bg1.config.alpha : 1,
tint: bg1.config.tint || 0xFFFFFF
});
var moveDistance = bg1.graphics.width;
var duration = 80000 / speed;
var _animate = function animate(bg, initialX) {
self.animateBackground(bg, {
x: bg.graphics.x - moveDistance
}, {
duration: duration,
easing: tween.linear,
onFinish: function onFinish() {
if (self.animationActive) {
if (bg.graphics.x <= -moveDistance / 2) {
bg.graphics.x = bg.graphics.x + moveDistance * 2;
}
_animate(bg, initialX);
}
}
});
};
if (index > 0) {
var _animateAlpha = function animateAlpha(bg) {
tween(bg.graphics, {
alpha: 1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(bg.graphics, {
alpha: 0.5
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.animationActive) {
_animateAlpha(bg);
}
}
});
}
});
};
_animateAlpha(bg1);
_animateAlpha(bg2);
}
_animate(bg1, bg1.graphics.x);
_animate(bg2, bg2.graphics.x);
})(i);
}
};
return self;
});
var Bullet = Container.expand(function (startX, startY, targetEnemy, damage, speed) {
var self = Container.call(this);
self.targetEnemy = targetEnemy;
self.damage = damage || 10;
self.speed = speed || 5;
self.x = startX;
self.y = startY;
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.update = function () {
if (!self.targetEnemy || !self.targetEnemy.parent) {
self.destroy();
return;
}
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
bulletGraphics.rotation = angle;
if (distance < self.speed) {
self.targetEnemy.health -= self.damage;
if (self.targetEnemy.health <= 0) {
self.targetEnemy.health = 0;
}
if (self.type === 'splash') {
var splashEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'splash');
game.addChild(splashEffect);
var splashRadius = CELL_SIZE * 1.5;
for (var i = 0; i < enemies.length; i++) {
var otherEnemy = enemies[i];
if (otherEnemy !== self.targetEnemy) {
var splashDx = otherEnemy.x - self.targetEnemy.x;
var splashDy = otherEnemy.y - self.targetEnemy.y;
var splashDistance = Math.sqrt(splashDx * splashDx + splashDy * splashDy);
if (splashDistance <= splashRadius) {
otherEnemy.health -= self.damage * 0.5;
if (otherEnemy.health <= 0) {
otherEnemy.health = 0;
}
}
}
}
} else if (self.type === 'slow') {
if (!self.targetEnemy.isImmune) {
var slowEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'slow');
game.addChild(slowEffect);
var slowPct = 0.5;
if (self.sourceTowerLevel !== undefined) {
var slowLevels = [0.5, 0.6, 0.65, 0.7, 0.75, 0.8];
var idx = Math.max(0, Math.min(5, self.sourceTowerLevel - 1));
slowPct = slowLevels[idx];
}
if (!self.targetEnemy.slowed) {
self.targetEnemy.originalSpeed = self.targetEnemy.speed;
self.targetEnemy.speed *= 1 - slowPct;
self.targetEnemy.slowed = true;
self.targetEnemy.slowDuration = 180;
} else {
self.targetEnemy.slowDuration = 180;
}
}
} else if (self.type === 'poison') {
if (!self.targetEnemy.isImmune) {
var poisonEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'poison');
game.addChild(poisonEffect);
self.targetEnemy.poisoned = true;
self.targetEnemy.poisonDamage = self.damage * 0.2;
self.targetEnemy.poisonDuration = 300;
}
} else if (self.type === 'sniper') {
var sniperEffect = new EffectIndicator(self.targetEnemy.x, self.targetEnemy.y, 'sniper');
game.addChild(sniperEffect);
}
self.destroy();
} else {
self.x += Math.cos(angle) * self.speed;
self.y += Math.sin(angle) * self.speed;
}
};
return self;
});
var DebugCell = Container.expand(function () {
var self = Container.call(this);
var cellGraphics = null;
if (isDebug) {
cellGraphics = self.attachAsset('cell', {
anchorX: 0.5,
anchorY: 0.5,
alpha: 1
});
cellGraphics.tint = Math.random() * 0xffffff;
}
var debugArrows = [];
var numberLabel = new Text2("-", {
size: 20,
fill: 0xFFFFFF
});
self.addChild(numberLabel);
self.update = function () {};
self.down = function () {
return;
if (self.cell.type == 0 || self.cell.type == 1) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
if (grid.pathFind()) {
self.cell.type = self.cell.type == 1 ? 0 : 1;
grid.pathFind();
var notification = game.addChild(new Notification("Path is blocked!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
grid.renderDebug();
}
};
self.removeArrows = function () {
while (debugArrows.length) {
self.removeChild(debugArrows.pop());
}
};
self.render = function (data) {
if (!isDebug) {
numberLabel.visible = false;
numberLabel.alpha = 0;
numberLabel.setText("");
if (data.type === 1 && !data.isVisuallyEmptyTower) {
if (self.wallInstance) {
self.removeChild(self.wallInstance);
self.wallInstance = null;
}
self.wallInstance = new Wall();
self.addChild(self.wallInstance);
} else {
if (self.wallInstance) {
self.removeChild(self.wallInstance);
self.wallInstance = null;
}
}
return;
}
if (cellGraphics) {
cellGraphics.visible = true;
cellGraphics.alpha = 1;
}
switch (data.type) {
case 0:
case 2:
{
if (data.pathId != pathId) {
self.removeArrows();
numberLabel.setText("-");
if (cellGraphics) {
cellGraphics.tint = 0x880000;
}
return;
}
var tint = Math.floor(data.score / maxScore * 0x88);
var towerInRangeHighlight = false;
if (selectedTower && data.towersInRange && data.towersInRange.indexOf(selectedTower) !== -1) {
towerInRangeHighlight = true;
if (cellGraphics) {
cellGraphics.tint = 0x0088ff;
}
} else {
if (cellGraphics) {
cellGraphics.tint = 0x88 - tint << 8 | tint;
}
}
break;
}
case 1:
{
self.removeArrows();
if (cellGraphics) {
cellGraphics.tint = 0xaaaaaa;
}
numberLabel.visible = false;
break;
}
case 3:
{
self.removeArrows();
if (cellGraphics) {
cellGraphics.tint = 0x008800;
}
numberLabel.visible = false;
break;
}
case 4:
{
self.removeArrows();
if (cellGraphics) {
cellGraphics.tint = 0x888888;
}
numberLabel.visible = false;
break;
}
}
};
});
var EffectIndicator = Container.expand(function (x, y, type) {
var self = Container.call(this);
self.x = x;
self.y = y;
var effectGraphics = self.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
effectGraphics.blendMode = 1;
switch (type) {
case 'splash':
effectGraphics.tint = 0x33CC00;
effectGraphics.width = effectGraphics.height = CELL_SIZE * 1.5;
break;
case 'slow':
effectGraphics.tint = 0x9900FF;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'poison':
effectGraphics.tint = 0x00FFAA;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
case 'sniper':
effectGraphics.tint = 0xFF5500;
effectGraphics.width = effectGraphics.height = CELL_SIZE;
break;
}
effectGraphics.alpha = 0.7;
self.alpha = 0;
tween(self, {
alpha: 0.8,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 2,
scaleY: 2
}, {
duration: 300,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = .01;
self.cellX = 0;
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
self.maxHealth = 1;
self.health = self.maxHealth;
self.bulletsTargetingThis = [];
self.waveNumber = currentWave;
self.isFlying = false;
self.isImmune = false;
self.isBoss = false;
if (currentWave % 10 === 0 && currentWave > 0 && type !== 'swarm') {
self.isBoss = true;
self.maxHealth *= 20;
self.speed = self.speed * 0.7;
}
self.health = self.maxHealth;
var assetId = 'enemy';
if (self.type !== 'normal') {
assetId = 'enemy_' + self.type;
}
var enemyGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
if (self.isBoss) {
enemyGraphics.scaleX = 1.8;
enemyGraphics.scaleY = 1.8;
}
/*switch (self.type) {
case 'fast':
enemyGraphics.tint = 0x00AAFF;
break;
case 'immune':
enemyGraphics.tint = 0xAA0000;
break;
case 'flying':
enemyGraphics.tint = 0xFFFF00;
break;
case 'swarm':
enemyGraphics.tint = 0xFF00FF;
break;
}*/
if (self.isFlying) {
self.shadow = new Container();
var shadowGraphics = self.shadow.attachAsset(assetId || 'enemy', {
anchorX: 0.5,
anchorY: 0.5
});
shadowGraphics.tint = 0x000000;
shadowGraphics.alpha = 0.4;
if (self.isBoss) {
shadowGraphics.scaleX = 1.8;
shadowGraphics.scaleY = 1.8;
}
self.shadow.x = 20;
self.shadow.y = 20;
shadowGraphics.rotation = enemyGraphics.rotation;
}
self.update = function () {
if (self.health <= 0) {
self.health = 0;
}
if (self.currentTarget) {
var ox = self.currentTarget.x - self.currentCellX;
var oy = self.currentTarget.y - self.currentCellY;
if (ox !== 0 || oy !== 0) {
var angle = Math.atan2(oy, ox);
if (enemyGraphics.targetRotation === undefined) {
enemyGraphics.targetRotation = angle;
enemyGraphics.rotation = angle;
} else {
if (Math.abs(angle - enemyGraphics.targetRotation) > 0.05) {
tween.stop(enemyGraphics, {
rotation: true
});
var currentRotation = enemyGraphics.rotation;
var angleDiff = angle - currentRotation;
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemyGraphics.targetRotation = angle;
tween(enemyGraphics, {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
}
}
};
return self;
});
var GoldIndicator = Container.expand(function (value, x, y) {
var self = Container.call(this);
var shadowText = new Text2("+" + value, {
size: 45,
fill: 0x000000,
weight: 800
});
shadowText.anchor.set(0.5, 0.5);
shadowText.x = 2;
shadowText.y = 2;
self.addChild(shadowText);
var goldText = new Text2("+" + value, {
size: 45,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
self.addChild(goldText);
self.x = x;
self.y = y;
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
tween(self, {
alpha: 1,
scaleX: 1.2,
scaleY: 1.2,
y: y - 40
}, {
duration: 50,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
y: y - 80
}, {
duration: 600,
easing: tween.easeIn,
delay: 800,
onFinish: function onFinish() {
self.destroy();
}
});
}
});
return self;
});
var Grid = Container.expand(function (gridWidth, gridHeight) {
var self = Container.call(this);
self.cells = [];
self.spawns = [];
self.goals = [];
for (var i = 0; i < gridWidth; i++) {
self.cells[i] = [];
for (var j = 0; j < gridHeight; j++) {
self.cells[i][j] = {
score: 0,
pathId: 0,
towersInRange: []
};
}
}
/*
Cell Types
0: Transparent floor
1: Wall
2: Spawn
3: Goal
4: Empty Tower
9: Wall (from map)
*/
var currentLevel = storage.currentLevel || 1;
// FOR DEBUGGING:
//currentLevel = 1;
//storage.currentLevel = 1;
// FOR DEBUGGING
var currentMap;
if (storage.hasPlayedTutorial) {
currentMap = initMapForLevel(currentLevel);
} else {
currentMap = MAPS[0];
}
var mapHeight = currentMap.length;
var mapWidth = currentMap[0].length;
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var cell = self.cells[i][j];
var cellType = 0;
if (j < mapHeight && i < mapWidth) {
var mapValue = currentMap[j][i];
switch (mapValue) {
case 0:
cellType = 0;
break;
case 11:
cell.orientation = Math.PI;
case 1:
cellType = 3;
self.goals.push(cell);
break;
case 22:
case 33:
cell.orientation = Math.PI;
case 2:
case 3:
cellType = 2;
self.spawns.push(cell);
break;
case 8:
cellType = 4;
break;
case 9:
cellType = 1;
break;
default:
cellType = 0;
}
} else {
cellType = 1;
}
cell.type = cellType;
cell.x = i;
cell.y = j;
cell.upLeft = self.cells[i - 1] && self.cells[i - 1][j - 1];
cell.up = self.cells[i - 1] && self.cells[i - 1][j];
cell.upRight = self.cells[i - 1] && self.cells[i - 1][j + 1];
cell.left = self.cells[i][j - 1];
cell.right = self.cells[i][j + 1];
cell.downLeft = self.cells[i + 1] && self.cells[i + 1][j - 1];
cell.down = self.cells[i + 1] && self.cells[i + 1][j];
cell.downRight = self.cells[i + 1] && self.cells[i + 1][j + 1];
cell.neighbors = [cell.upLeft, cell.up, cell.upRight, cell.right, cell.downRight, cell.down, cell.downLeft, cell.left];
cell.targets = [];
if (j >= 0 && j <= gridHeight - 5) {
var debugCell = new DebugCell();
self.addChild(debugCell);
debugCell.cell = cell;
debugCell.x = i * CELL_SIZE;
debugCell.y = (j + mapGridOffset) * CELL_SIZE;
cell.debugCell = debugCell;
}
}
}
self.getCell = function (x, y) {
return self.cells[x] && self.cells[x][y];
};
self.pathFind = function () {
var before = new Date().getTime();
var toProcess = self.goals.concat([]);
maxScore = 0;
pathId += 1;
for (var a = 0; a < toProcess.length; a++) {
toProcess[a].pathId = pathId;
}
function processNode(node, targetValue, targetNode) {
if (node && node.type != 1) {
if (node.pathId < pathId || targetValue < node.score) {
node.targets = [targetNode];
} else if (node.pathId == pathId && targetValue == node.score) {
node.targets.push(targetNode);
}
if (node.pathId < pathId || targetValue < node.score) {
node.score = targetValue;
if (node.pathId != pathId) {
toProcess.push(node);
}
node.pathId = pathId;
if (targetValue > maxScore) {
maxScore = targetValue;
}
}
}
}
while (toProcess.length) {
var nodes = toProcess;
toProcess = [];
for (var a = 0; a < nodes.length; a++) {
var node = nodes[a];
var targetScore = node.score + 14142;
if (node.up && node.left && node.up.type != 1 && node.left.type != 1) {
processNode(node.upLeft, targetScore, node);
}
if (node.up && node.right && node.up.type != 1 && node.right.type != 1) {
processNode(node.upRight, targetScore, node);
}
if (node.down && node.right && node.down.type != 1 && node.right.type != 1) {
processNode(node.downRight, targetScore, node);
}
if (node.down && node.left && node.down.type != 1 && node.left.type != 1) {
processNode(node.downLeft, targetScore, node);
}
targetScore = node.score + 10000;
processNode(node.up, targetScore, node);
processNode(node.right, targetScore, node);
processNode(node.down, targetScore, node);
processNode(node.left, targetScore, node);
}
}
for (var a = 0; a < self.spawns.length; a++) {
if (self.spawns[a].pathId != pathId) {
console.warn("Spawn blocked");
return true;
}
}
for (var a = 0; a < enemies.length; a++) {
var enemy = enemies[a];
if (enemy.currentCellY < mapGridOffset) {
continue;
}
if (enemy.isFlying) {
continue;
}
var target = self.getCell(enemy.cellX, enemy.cellY);
if (enemy.currentTarget) {
if (enemy.currentTarget.pathId != pathId) {
if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 1 ");
return true;
}
}
} else if (!target || target.pathId != pathId) {
console.warn("Enemy blocked 2");
return true;
}
}
console.log("Speed", new Date().getTime() - before);
};
self.renderDebug = function () {
for (var i = 0; i < gridWidth; i++) {
for (var j = 0; j < gridHeight; j++) {
var debugCell = self.cells[i][j].debugCell;
if (debugCell) {
debugCell.render(self.cells[i][j]);
}
}
}
};
self.updateEnemy = function (enemy) {
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell.type == 3) {
return true;
}
if (enemy.isFlying && enemy.shadow) {
enemy.shadow.x = enemy.x + 20;
enemy.shadow.y = enemy.y + 20;
if (enemy.children[0] && enemy.shadow.children[0]) {
enemy.shadow.children[0].rotation = enemy.children[0].rotation;
}
}
var hasReachedEntryArea = enemy.currentCellY >= mapGridOffset;
if (!hasReachedEntryArea) {
enemy.currentCellY += enemy.speed;
var angle = Math.PI / 2;
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
if (enemy.currentCellY >= mapGridOffset) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
}
return false;
}
if (enemy.isFlying) {
if (!enemy.flyingTarget) {
enemy.flyingTarget = self.goals[0];
if (self.goals.length > 1) {
var closestDist = Infinity;
for (var i = 0; i < self.goals.length; i++) {
var goal = self.goals[i];
var dx = goal.x - enemy.cellX;
var dy = goal.y - enemy.cellY;
var dist = dx * dx + dy * dy;
if (dist < closestDist) {
closestDist = dist;
enemy.flyingTarget = goal;
}
}
}
}
var ox = enemy.flyingTarget.x - enemy.currentCellX;
var oy = enemy.flyingTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
return true;
}
var angle = Math.atan2(oy, ox);
if (enemy.children[0] && enemy.children[0].targetRotation === undefined) {
enemy.children[0].targetRotation = angle;
enemy.children[0].rotation = angle;
} else if (enemy.children[0]) {
if (Math.abs(angle - enemy.children[0].targetRotation) > 0.05) {
tween.stop(enemy.children[0], {
rotation: true
});
var currentRotation = enemy.children[0].rotation;
var angleDiff = angle - currentRotation;
while (angleDiff > Math.PI) {
angleDiff -= Math.PI * 2;
}
while (angleDiff < -Math.PI) {
angleDiff += Math.PI * 2;
}
enemy.children[0].targetRotation = angle;
tween(enemy.children[0], {
rotation: currentRotation + angleDiff
}, {
duration: 250,
easing: tween.easeOut
});
}
}
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
return false;
}
if (!enemy.currentTarget) {
enemy.currentTarget = cell.targets[0];
}
if (enemy.currentTarget) {
if (cell.score < enemy.currentTarget.score) {
enemy.currentTarget = cell;
}
var ox = enemy.currentTarget.x - enemy.currentCellX;
var oy = enemy.currentTarget.y - enemy.currentCellY;
var dist = Math.sqrt(ox * ox + oy * oy);
if (dist < enemy.speed) {
enemy.cellX = Math.round(enemy.currentCellX);
enemy.cellY = Math.round(enemy.currentCellY);
enemy.currentTarget = undefined;
return;
}
var angle = Math.atan2(oy, ox);
enemy.currentCellX += Math.cos(angle) * enemy.speed;
enemy.currentCellY += Math.sin(angle) * enemy.speed;
}
enemy.x = grid.x + enemy.currentCellX * CELL_SIZE;
enemy.y = grid.y + enemy.currentCellY * CELL_SIZE;
};
});
var NextWaveButton = Container.expand(function () {
var self = Container.call(this);
var buttonBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 300;
buttonBackground.height = 100;
buttonBackground.tint = 0x0088FF;
var buttonText = new Text2("Next Wave", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
self.addChild(buttonText);
self.enabled = false;
self.visible = false;
self.update = function () {
if (waveIndicator && waveIndicator.gameStarted && currentWave < totalWaves) {
self.enabled = true;
self.visible = true;
buttonBackground.tint = 0x0088FF;
self.alpha = 1;
} else {
self.enabled = false;
self.visible = false;
buttonBackground.tint = 0x888888;
self.alpha = 0.7;
}
};
self.down = function () {
if (!self.enabled) {
return;
}
if (waveIndicator.gameStarted && currentWave < totalWaves) {
currentWave++;
waveTimer = 0;
waveInProgress = true;
waveSpawned = false;
var waveType = waveIndicator.getWaveTypeName(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) activated!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
};
return self;
});
var Notification = Container.expand(function (message) {
var self = Container.call(this);
var notificationGraphics = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
var notificationText = new Text2(message, {
size: 50,
fill: 0x000000,
weight: 800
});
notificationText.anchor.set(0.5, 0.5);
notificationGraphics.width = notificationText.width + 30;
self.addChild(notificationText);
self.alpha = 1;
var fadeOutTime = 120;
self.update = function () {
if (fadeOutTime > 0) {
fadeOutTime--;
self.alpha = Math.min(fadeOutTime / 120 * 2, 1);
} else {
self.destroy();
}
};
return self;
});
var SelectionCircle = Container.expand(function () {
var self = Container.call(this);
self.startX = 0;
self.startY = 0;
self.currentRadius = 0;
var circleGraphics = self.attachAsset('circleSelector', {
anchorX: 0.5,
anchorY: 0.5
});
circleGraphics.alpha = 0.3;
circleGraphics.tint = 0x00FF00;
circleGraphics.width = 1;
circleGraphics.height = 1;
self.updateCircle = function (endX, endY) {
var dx = endX - self.startX;
var dy = endY - self.startY;
self.currentRadius = Math.sqrt(dx * dx + dy * dy);
circleGraphics.width = self.currentRadius * 2;
circleGraphics.height = self.currentRadius * 2;
};
self.setPosition = function (x, y) {
self.startX = x;
self.startY = y;
self.x = x;
self.y = y;
};
return self;
});
var SourceTower = Container.expand(function (towerType) {
var self = Container.call(this);
self.towerType = towerType || 'default';
var baseGraphics = self.attachAsset('tower', {
anchorX: 0.5,
anchorY: 0.5,
scaleX: 1.3,
scaleY: 1.3
});
baseGraphics.tint = 0xAAAAAA;
var towerCost = getTowerCost(self.towerType);
var typeLabelShadow = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0x000000,
weight: 800
});
typeLabelShadow.anchor.set(0.5, 0.5);
typeLabelShadow.x = 4;
typeLabelShadow.y = -20 + 4;
self.addChild(typeLabelShadow);
var typeLabel = new Text2(self.towerType.charAt(0).toUpperCase() + self.towerType.slice(1), {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
typeLabel.anchor.set(0.5, 0.5);
typeLabel.y = -20;
self.addChild(typeLabel);
var costLabelShadow = new Text2(towerCost, {
size: 50,
fill: 0x000000,
weight: 800
});
costLabelShadow.anchor.set(0.5, 0.5);
costLabelShadow.x = 4;
costLabelShadow.y = 24 + 12;
self.addChild(costLabelShadow);
var costLabel = new Text2(towerCost, {
size: 50,
fill: 0xFFD700,
weight: 800
});
costLabel.anchor.set(0.5, 0.5);
costLabel.y = 20 + 12;
self.addChild(costLabel);
self.update = function () {
var canAfford = gold >= getTowerCost(self.towerType);
self.alpha = canAfford ? 1 : 0.5;
};
return self;
});
var Tower = Container.expand(function (headquarters, orientation, playerIndex) {
var self = Container.call(this);
self.isHeadquarters = headquarters || false;
self.baseOrientation = orientation;
self.playerIndex = playerIndex !== undefined ? playerIndex : 0;
self.isActivated = self.isHeadquarters;
self.activationThreshold = 10;
self.currentShares = 0;
self.capturingPlayer = -1;
self.activationGauge = null;
self.id = self.isHeadquarters ? 'headquarters' : 'default';
self.level = 1;
self.maxLevel = 6;
self.gridX = 0;
self.gridY = 0;
self.isProducing = false;
self.range = 3 * CELL_SIZE;
self.health = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10;
self.maxHealth = isDebug ? self.isHeadquarters ? 4 : 2 : self.isHeadquarters ? 30 : 10;
self.getRange = function () {
if (self.isHeadquarters) {
return (3 + (self.level - 1) * 0.5) * CELL_SIZE * 2;
}
switch (self.id) {
case 'sniper':
if (self.level === self.maxLevel) {
return 12 * CELL_SIZE;
}
return (5 + (self.level - 1) * 0.8) * CELL_SIZE;
case 'splash':
return (2 + (self.level - 1) * 0.2) * CELL_SIZE;
case 'rapid':
return (2.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'slow':
return (3.5 + (self.level - 1) * 0.5) * CELL_SIZE;
case 'poison':
return (3.2 + (self.level - 1) * 0.5) * CELL_SIZE;
default:
return (3 + (self.level - 1) * 0.5) * CELL_SIZE;
}
};
self.cellsInRange = [];
self.fireRate = self.isHeadquarters ? 20 : 60;
self.bulletSpeed = 5;
self.damage = 1;
self.lastFired = 0;
self.targetEnemy = null;
var baseGraphics;
if (self.isHeadquarters) {
baseGraphics = self.attachAsset('headquarterBase', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 150
});
baseGraphics.rotation = orientation || 0;
} else {
baseGraphics = self.attachAsset('towerBase', {
anchorX: 0.5,
anchorY: 0.5
});
}
switch (self.id) {
case 'rapid':
baseGraphics.tint = 0x00AAFF;
break;
case 'sniper':
baseGraphics.tint = 0xFF5500;
break;
case 'splash':
baseGraphics.tint = 0x33CC00;
break;
case 'slow':
baseGraphics.tint = 0x9900FF;
break;
case 'poison':
baseGraphics.tint = 0x00FFAA;
break;
default:
baseGraphics.tint = 0xAAAAAA;
}
var levelIndicators = [];
var maxDots = 0;
var dotSpacing = baseGraphics.width / (maxDots + 1);
var dotSize = CELL_SIZE / 6;
for (var i = 0; i < maxDots; i++) {
var dot = new Container();
var outlineCircle = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
outlineCircle.width = dotSize + 4;
outlineCircle.height = dotSize + 4;
outlineCircle.tint = 0x000000;
var towerLevelIndicator = dot.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
towerLevelIndicator.width = dotSize;
towerLevelIndicator.height = dotSize;
towerLevelIndicator.tint = 0xCCCCCC;
dot.x = -CELL_SIZE * 2 + dotSpacing * (i + 1);
dot.y = CELL_SIZE * 1.3;
if (self.playerIndex !== 0) {
dot.visible = false;
}
self.addChild(dot);
levelIndicators.push(dot);
}
var activationGaugeContainer = new Container();
self.addChild(activationGaugeContainer);
activationGaugeContainer.y = CELL_SIZE * 0.9;
self.activationGauge = activationGaugeContainer;
self.activationGauge.visible = false;
var gunContainer = new Container();
self.addChild(gunContainer);
var turretGraphics;
var gunGraphics;
var prismGraphics;
if (self.isHeadquarters) {
self.isProducing = true;
turretGraphics = gunContainer.attachAsset('turret', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 100
});
gunGraphics = gunContainer.attachAsset('barrel2', {
anchorX: 0,
anchorY: 0.5,
width: 100,
height: 20
});
prismGraphics = gunContainer.attachAsset('prism', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 100
});
} else {
turretGraphics = gunContainer.attachAsset('turret', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
gunGraphics = gunContainer.attachAsset('barrel', {
anchorX: 0,
anchorY: 0.5,
width: 50,
height: 10
});
prismGraphics = gunContainer.attachAsset('prism', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
}
self.turretGraphics = turretGraphics;
self.gunGraphics = gunGraphics;
self.prismGraphics = prismGraphics;
prismGraphics.visible = self.isProducing;
turretGraphics.visible = !self.isProducing;
gunGraphics.visible = !self.isProducing;
if (self.playerIndex === 0) {
gunGraphics.tint = teamColors[0];
turretGraphics.tint = teamColors[0];
prismGraphics.tint = teamColors[0];
} else if (self.playerIndex === 1) {
gunGraphics.tint = teamColors[1];
turretGraphics.tint = teamColors[1];
prismGraphics.tint = teamColors[1];
} else {
gunGraphics.tint = 0xAAAAAA;
turretGraphics.tint = 0xAAAAAA;
prismGraphics.tint = 0xAAAAAA;
}
self.updateLevelIndicators = function () {
for (var i = 0; i < maxDots; i++) {
var dot = levelIndicators[i];
var towerLevelIndicator = dot.children[1];
if (i < self.level) {
towerLevelIndicator.tint = 0xFFFFFF;
} else {
towerLevelIndicator.tint = 0xAAAAAA;
}
}
};
self.updateLevelIndicators();
self.updateActivation = function (fromPlayerIndex) {
if (self.isActivated && self.playerIndex === fromPlayerIndex) {
return false;
}
if (self.capturingPlayer === -1) {
self.capturingPlayer = fromPlayerIndex;
}
if (self.capturingPlayer === fromPlayerIndex) {
if (self.currentShares >= self.activationThreshold) {
if (!self.isActivated) {
self.activate(self.capturingPlayer);
}
return false;
}
self.currentShares++;
} else {
self.currentShares--;
}
if (self.currentShares <= 0) {
self.currentShares = 0;
if (self.isActivated) {
self.deactivate();
}
self.capturingPlayer = -1;
} else if (self.currentShares >= self.activationThreshold) {
self.currentShares = self.activationThreshold;
if (!self.isActivated) {
self.activate(self.capturingPlayer);
}
}
self.updateActivationGaugeVisuals();
return true;
};
self.activate = function (newPlayerIndex) {
self.isActivated = true;
self.playerIndex = newPlayerIndex;
gunContainer.visible = true;
var baseGraphics = self.children[0];
if (self.playerIndex == 0) {
baseGraphics.tint = teamColors[0];
self.turretGraphics.tint = teamColors[0];
self.gunGraphics.tint = teamColors[0];
self.prismGraphics.tint = teamColors[0];
} else if (self.playerIndex == 1) {
baseGraphics.tint = teamColors[1];
self.turretGraphics.tint = teamColors[1];
self.gunGraphics.tint = teamColors[1];
self.prismGraphics.tint = teamColors[1];
}
if (self.activationGauge) {
self.activationGauge.visible = false;
}
};
self.deactivate = function () {
self.isActivated = false;
self.playerIndex = -1;
gunContainer.visible = false;
var baseGraphics = self.children[0];
baseGraphics.tint = 0xAAAAAA;
self.gunGraphics.tint = 0xAAAAAA;
self.turretGraphics.tint = 0xAAAAAA;
self.prismGraphics.tint = 0xAAAAAA;
self.updateActivationGaugeVisuals();
};
self.updateActivationGaugeVisuals = function () {
if (!self.activationGauge) {
return;
}
self.activationGauge.y = 0;
self.activationGauge.removeChildren();
if (self.currentShares > 0 && !self.isActivated) {
self.activationGauge.visible = true;
var totalDots = 12;
var radius = CELL_SIZE / 2.5;
var dotsToShow = Math.ceil(self.currentShares / self.activationThreshold * totalDots);
var playerColor = 0x0000FF;
if (self.capturingPlayer === 0) {
playerColor = teamColors[0];
} else if (self.capturingPlayer === 1) {
playerColor = teamColors[1];
}
for (var i = 0; i < totalDots; i++) {
var angle = i / totalDots * Math.PI * 2 - Math.PI / 2;
var dot = self.activationGauge.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 12
});
dot.x = Math.cos(angle) * radius;
dot.y = Math.sin(angle) * radius;
if (i < dotsToShow) {
dot.tint = playerColor;
dot.alpha = 1.0;
} else {
dot.tint = 0x666666;
dot.alpha = 0.4;
}
}
} else {
self.activationGauge.visible = false;
}
};
self.refreshCellsInRange = function () {
for (var i = 0; i < self.cellsInRange.length; i++) {
var cell = self.cellsInRange[i];
var towerIndex = cell.towersInRange.indexOf(self);
if (towerIndex !== -1) {
cell.towersInRange.splice(towerIndex, 1);
}
}
self.cellsInRange = [];
var rangeRadius = self.getRange() / CELL_SIZE;
var centerX = self.gridX + 1;
var centerY = self.gridY + 1;
var minI = Math.floor(centerX - rangeRadius - 0.5);
var maxI = Math.ceil(centerX + rangeRadius + 0.5);
var minJ = Math.floor(centerY - rangeRadius - 0.5);
var maxJ = Math.ceil(centerY + rangeRadius + 0.5);
for (var i = minI; i <= maxI; i++) {
for (var j = minJ; j <= maxJ; j++) {
var closestX = Math.max(i, Math.min(centerX, i + 1));
var closestY = Math.max(j, Math.min(centerY, j + 1));
var deltaX = closestX - centerX;
var deltaY = closestY - centerY;
var distanceSquared = deltaX * deltaX + deltaY * deltaY;
if (distanceSquared <= rangeRadius * rangeRadius) {
var cell = grid.getCell(i, j);
if (cell) {
self.cellsInRange.push(cell);
cell.towersInRange.push(self);
}
}
}
}
grid.renderDebug();
};
self.getTotalValue = function () {
var baseTowerCost = getTowerCost(self.id);
var totalInvestment = baseTowerCost;
var baseUpgradeCost = baseTowerCost;
for (var i = 1; i < self.level; i++) {
totalInvestment += Math.floor(baseUpgradeCost * Math.pow(2, i - 1));
}
return totalInvestment;
};
self.upgrade = function () {
/*
if (self.level < self.maxLevel) {
var baseUpgradeCost = getTowerCost(self.id);
var upgradeCost;
if (self.level === self.maxLevel - 1) {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1) * 3.5 / 2);
} else {
upgradeCost = Math.floor(baseUpgradeCost * Math.pow(2, self.level - 1));
}
if (gold >= upgradeCost) {
setGold(gold - upgradeCost);
self.level++;
if (self.id === 'rapid') {
if (self.level === self.maxLevel) {
self.fireRate = Math.max(4, 30 - self.level * 9);
self.damage = 5 + self.level * 10;
self.bulletSpeed = 7 + self.level * 2.4;
} else {
self.fireRate = Math.max(15, 30 - self.level * 3);
self.damage = 5 + self.level * 3;
self.bulletSpeed = 7 + self.level * 0.7;
}
} else {
if (self.level === self.maxLevel) {
self.fireRate = Math.max(5, 60 - self.level * 24);
self.damage = 10 + self.level * 20;
self.bulletSpeed = 5 + self.level * 2.4;
} else {
self.fireRate = Math.max(20, 60 - self.level * 8);
self.damage = 10 + self.level * 5;
self.bulletSpeed = 5 + self.level * 0.5;
}
}
self.refreshCellsInRange();
self.updateLevelIndicators();
if (self.level > 1) {
var levelDot = levelIndicators[self.level - 1].children[1];
tween(levelDot, {
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 300,
easing: tween.elasticOut,
onFinish: function onFinish() {
tween(levelDot, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold to upgrade!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
*/
return false;
};
self.findTarget = function () {
if (unitManager && unitManager.units) {
var closestUnit = null;
var closestUnitDistSq = Infinity;
var range = self.getRange();
var rangeSq = range * range;
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex !== self.playerIndex && unit.parent) {
var dx = unit.x - self.x;
var dy = unit.y - self.y;
var distSq = dx * dx + dy * dy;
if (distSq <= rangeSq && distSq < closestUnitDistSq) {
closestUnitDistSq = distSq;
closestUnit = unit;
}
}
}
if (closestUnit) {
return closestUnit;
}
}
var closestEnemy = null;
var closestScore = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.getRange()) {
if (enemy.isFlying) {
if (enemy.flyingTarget) {
var goalX = enemy.flyingTarget.x;
var goalY = enemy.flyingTarget.y;
var distToGoal = Math.sqrt((goalX - enemy.cellX) * (goalX - enemy.cellX) + (goalY - enemy.cellY) * (goalY - enemy.cellY));
if (distToGoal < closestScore) {
closestScore = distToGoal;
closestEnemy = enemy;
}
} else {
if (distance < closestScore) {
closestScore = distance;
closestEnemy = enemy;
}
}
} else {
var cell = grid.getCell(enemy.cellX, enemy.cellY);
if (cell && cell.pathId === pathId) {
if (cell.score < closestScore) {
closestScore = cell.score;
closestEnemy = enemy;
}
}
}
}
}
if (!closestEnemy) {
self.targetEnemy = null;
}
return closestEnemy;
};
self.update = function () {
if (!self.isActivated) {
return;
}
if (!self.isProducing) {
self.targetEnemy = self.findTarget();
if (self.targetEnemy) {
var dx = self.targetEnemy.x - self.x;
var dy = self.targetEnemy.y - self.y;
var angle = Math.atan2(dy, dx);
gunContainer.rotation = angle;
if (LK.ticks - self.lastFired >= self.fireRate) {
self.fire();
self.lastFired = LK.ticks;
}
}
}
};
self.down = function (x, y, obj) {
if (tutorialActive && self.isHeadquarters && self.playerIndex === 0 && tutorialStep < 6) {
return;
}
if (!self.isActivated) {
return;
}
if (self.playerIndex !== 0) {
return;
}
var existingMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
var hasOwnMenu = false;
var rangeCircle = null;
for (var i = 0; i < game.children.length; i++) {
if (game.children[i].isTowerRange && game.children[i].tower === self) {
rangeCircle = game.children[i];
break;
}
}
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hasOwnMenu = true;
break;
}
}
if (hasOwnMenu) {
for (var i = 0; i < existingMenus.length; i++) {
if (existingMenus[i].tower === self) {
hideUpgradeMenu(existingMenus[i]);
}
}
if (rangeCircle) {
game.removeChild(rangeCircle);
}
selectedTower = null;
grid.renderDebug();
return;
}
for (var i = 0; i < existingMenus.length; i++) {
existingMenus[i].destroy();
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = self;
var rangeIndicator = new Container();
rangeIndicator.isTowerRange = true;
rangeIndicator.tower = self;
game.addChild(rangeIndicator);
rangeIndicator.x = self.x;
rangeIndicator.y = self.y;
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.width = rangeGraphics.height = self.getRange() * 2;
rangeGraphics.alpha = 0.3;
var upgradeMenu = new UpgradeMenu(self);
game.addChild(upgradeMenu);
upgradeMenu.x = 2048 / 2;
tween(upgradeMenu, {
y: 2732 - 225
}, {
duration: 200,
easing: tween.backOut,
onFinish: function onFinish() {
if (tutorialActive && tutorialStep === 6 && self.isHeadquarters && self.playerIndex === 0) {
executeTutorialStep(7);
}
}
});
grid.renderDebug();
};
self.isInRange = function (enemy) {
if (!enemy) {
return false;
}
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
return distance <= self.getRange();
};
self.fire = function () {
if (self.targetEnemy) {
var potentialDamage = 0;
for (var i = 0; i < self.targetEnemy.bulletsTargetingThis.length; i++) {
potentialDamage += self.targetEnemy.bulletsTargetingThis[i].damage;
}
if (self.targetEnemy.health > 0) {
var bulletX = self.x + Math.cos(gunContainer.rotation) * 40;
var bulletY = self.y + Math.sin(gunContainer.rotation) * 40;
var bullet = new Bullet(bulletX, bulletY, self.targetEnemy, self.damage, self.bulletSpeed);
bullet.type = self.id;
/*
if (self.id === 'slow') {
bullet.sourceTowerLevel = self.level;
}
switch (self.id) {
case 'rapid':
bullet.children[0].tint = 0x00AAFF;
bullet.children[0].width = 20;
bullet.children[0].height = 20;
break;
case 'sniper':
bullet.children[0].tint = 0xFF5500;
bullet.children[0].width = 15;
bullet.children[0].height = 15;
break;
case 'splash':
bullet.children[0].tint = 0x33CC00;
bullet.children[0].width = 40;
bullet.children[0].height = 40;
break;
case 'slow':
bullet.children[0].tint = 0x9900FF;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
case 'poison':
bullet.children[0].tint = 0x00FFAA;
bullet.children[0].width = 35;
bullet.children[0].height = 35;
break;
}
*/
game.addChild(bullet);
bullets.push(bullet);
self.targetEnemy.bulletsTargetingThis.push(bullet);
tween.stop(gunContainer, {
x: true,
y: true,
scaleX: true,
scaleY: true
});
if (gunContainer._restX === undefined) {
gunContainer._restX = 0;
}
if (gunContainer._restY === undefined) {
gunContainer._restY = 0;
}
if (gunContainer._restScaleX === undefined) {
gunContainer._restScaleX = 1;
}
if (gunContainer._restScaleY === undefined) {
gunContainer._restScaleY = 1;
}
gunContainer.x = gunContainer._restX;
gunContainer.y = gunContainer._restY;
gunContainer.scaleX = gunContainer._restScaleX;
gunContainer.scaleY = gunContainer._restScaleY;
var recoilDistance = 8;
var recoilX = -Math.cos(gunContainer.rotation) * recoilDistance;
var recoilY = -Math.sin(gunContainer.rotation) * recoilDistance;
tween(gunContainer, {
x: gunContainer._restX + recoilX,
y: gunContainer._restY + recoilY
}, {
duration: 60,
easing: tween.cubicOut,
onFinish: function onFinish() {
tween(gunContainer, {
x: gunContainer._restX,
y: gunContainer._restY
}, {
duration: 60,
easing: tween.cubicIn
});
}
});
}
}
};
self.placeOnGrid = function (gridX, gridY) {
self.gridX = gridX;
self.gridY = gridY;
self.x = grid.x + gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + gridY * CELL_SIZE + CELL_SIZE / 2;
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cell.type = 1;
}
}
}
self.refreshCellsInRange();
};
self.changeTowerMode = function () {
self.isProducing = !self.isProducing;
if (self.isProducing) {
self.activateGeneratorMode();
} else {
self.activateDefenseMode();
}
};
self.animatePrism = function () {
if (!self.isProducing) {
return;
}
var prismGraphics = self.prismGraphics;
tween(prismGraphics, {
rotation: prismGraphics.rotation + Math.PI * 2
}, {
duration: 8000,
easing: tween.linear,
onFinish: function onFinish() {
self.animatePrism();
}
});
var tintColors = [0xFFFFFF, 0xFFE0E0, 0xE0FFFF, 0xE0FFE0, 0xFFFFE0, 0xF0E0FF];
var currentTintIndex = 0;
function animateTint() {
var nextIndex = (currentTintIndex + 1) % tintColors.length;
tween(prismGraphics, {
tint: tintColors[nextIndex]
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
currentTintIndex = nextIndex;
animateTint();
}
});
}
};
self.animatePrism();
self.activateGeneratorMode = function () {
console.log("Tower activating generator mode at", self.gridX, self.gridY);
self.prismGraphics.visible = true;
self.gunGraphics.visible = false;
self.turretGraphics.visible = false;
if (self.playerIndex === 0) {
self.prismGraphics.tint = teamColors[0];
self.turretGraphics.tint = teamColors[0];
self.gunGraphics.tint = teamColors[0];
} else if (self.playerIndex === 1) {
self.prismGraphics.tint = teamColors[1];
self.turretGraphics.tint = teamColors[1];
self.gunGraphics.tint = teamColors[1];
} else {
self.prismGraphics.tint = 0xAAAAAA;
self.turretGraphics.tint = 0xAAAAAA;
self.gunGraphics.tint = 0xAAAAAA;
}
self.animatePrism();
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu && child.tower === self;
});
for (var i = 0; i < upgradeMenus.length; i++) {
if (upgradeMenus[i].updateButtonText) {
upgradeMenus[i].updateButtonText('Defense mode');
}
}
};
self.activateDefenseMode = function () {
self.prismGraphics.visible = false;
self.turretGraphics.visible = true;
self.gunGraphics.visible = true;
if (self.playerIndex === 0) {
self.turretGraphics.tint = teamColors[0];
self.gunGraphics.tint = teamColors[0];
self.prismGraphics.tint = teamColors[0];
} else if (self.playerIndex === 1) {
self.turretGraphics.tint = teamColors[1];
self.gunGraphics.tint = teamColors[1];
self.prismGraphics.tint = teamColors[1];
} else {
self.turretGraphics.tint = 0xAAAAAA;
self.gunGraphics.tint = 0xAAAAAA;
self.prismGraphics.tint = 0xAAAAAA;
}
tween.stop(self.prismGraphics, {
rotation: true,
tint: true
});
self.prismGraphics.rotation = 0;
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu && child.tower === self;
});
for (var i = 0; i < upgradeMenus.length; i++) {
if (upgradeMenus[i].updateButtonText) {
upgradeMenus[i].updateButtonText('Generator mode');
}
}
};
return self;
});
var TowerPreview = Container.expand(function () {
var self = Container.call(this);
var towerRange = 3;
var rangeInPixels = towerRange * CELL_SIZE;
self.towerType = 'default';
self.hasEnoughGold = true;
var rangeIndicator = new Container();
self.addChild(rangeIndicator);
var rangeGraphics = rangeIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
rangeGraphics.alpha = 0.3;
var previewGraphics = self.attachAsset('towerpreview', {
anchorX: 0.5,
anchorY: 0.5
});
previewGraphics.width = CELL_SIZE * 2;
previewGraphics.height = CELL_SIZE * 2;
self.canPlace = false;
self.gridX = 0;
self.gridY = 0;
self.blockedByEnemy = false;
self.update = function () {
var previousHasEnoughGold = self.hasEnoughGold;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
if (previousHasEnoughGold !== self.hasEnoughGold) {
self.updateAppearance();
}
};
self.updateAppearance = function () {
var tempTower = new Tower(false, undefined, 0);
var previewRange = tempTower.getRange();
if (tempTower && tempTower.destroy) {
tempTower.destroy();
}
rangeGraphics.width = rangeGraphics.height = previewRange * 2;
previewGraphics.tint = 0xAAAAAA;
if (!self.canPlace || !self.hasEnoughGold) {
previewGraphics.tint = 0xFF0000;
}
};
self.updatePlacementStatus = function () {
var validGridPlacement = true;
if (self.gridY <= mapGridOffset || self.gridY + 1 >= grid.cells[0].length - mapGridOffset) {
validGridPlacement = false;
} else {
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(self.gridX + i, self.gridY + j);
if (!cell || cell.type !== 0) {
validGridPlacement = false;
break;
}
}
if (!validGridPlacement) {
break;
}
}
}
self.blockedByEnemy = false;
if (validGridPlacement) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy.currentCellY < mapGridOffset) {
continue;
}
if (!enemy.isFlying) {
if (enemy.cellX >= self.gridX && enemy.cellX < self.gridX + 2 && enemy.cellY >= self.gridY && enemy.cellY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
if (enemy.currentTarget) {
var targetX = enemy.currentTarget.x;
var targetY = enemy.currentTarget.y;
if (targetX >= self.gridX && targetX < self.gridX + 2 && targetY >= self.gridY && targetY < self.gridY + 2) {
self.blockedByEnemy = true;
break;
}
}
}
}
}
self.canPlace = validGridPlacement && !self.blockedByEnemy;
self.hasEnoughGold = gold >= getTowerCost(self.towerType);
self.updateAppearance();
};
self.checkPlacement = function () {
self.updatePlacementStatus();
};
self.snapToGrid = function (x, y) {
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
self.gridX = Math.floor(gridPosX / CELL_SIZE);
self.gridY = Math.floor(gridPosY / CELL_SIZE);
self.x = grid.x + self.gridX * CELL_SIZE + CELL_SIZE / 2;
self.y = grid.y + self.gridY * CELL_SIZE + CELL_SIZE / 2;
self.checkPlacement();
};
return self;
});
var TrailParticle = Container.expand(function (x, y, tint, scale) {
var self = Container.call(this);
self.x = x;
self.y = y;
var particleGraphics = self.attachAsset('unit', {
anchorX: 0.5,
anchorY: 0.5
});
particleGraphics.tint = tint;
self.alpha = 0.75;
self.scaleX = (scale || 1) * 0.75;
self.scaleY = (scale || 1) * 0.75;
tween(self, {
alpha: 0,
scaleX: self.scaleX * 0.1,
scaleY: self.scaleY * 0.1
}, {
duration: 300 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
var Unit = Container.expand(function (type) {
var self = Container.call(this);
self.type = type || 'normal';
self.speed = .01;
self.cellX = 0;
self.cellY = 0;
self.currentCellX = 0;
self.currentCellY = 0;
self.currentTarget = undefined;
self.maxHealth = 1;
self.health = self.maxHealth;
self.bulletsTargetingThis = [];
self.waveNumber = currentWave;
self.health = self.maxHealth;
self.enemyHQ = null;
self.isIdle = false;
self.idleBaseX = 0;
self.idleBaseY = 0;
self.idleAnimationActive = false;
self.isSelected = false;
self.selectionIndicator = null;
self.targetCellX = null;
self.targetCellY = null;
self.movementSpeed = 0.05;
self.lastTrailTime = 0;
var assetId = 'unit';
if (self.type !== 'normal') {
assetId = 'unit_' + self.type;
}
var unitGraphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 1) {
self.enemyHQ = towers[i];
break;
}
}
self.setSelected = function (selected) {
self.isSelected = selected;
if (selected && !self.selectionIndicator) {
self.selectionIndicator = new Container();
var indicator = self.selectionIndicator.attachAsset('rangeCircle', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.width = 50;
indicator.height = 50;
indicator.alpha = 0.5;
indicator.tint = 0x00FF00;
self.addChild(self.selectionIndicator);
} else if (!selected && self.selectionIndicator) {
self.removeChild(self.selectionIndicator);
self.selectionIndicator = null;
}
};
self.setTarget = function (cellX, cellY) {
self.targetCellX = cellX;
self.targetCellY = cellY;
self.isIdle = false;
self.idleAnimationActive = false;
tween.stop(self);
self.targetTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.playerIndex !== self.ownerIndex) {
var towerGridX, towerGridY;
var towerSize = tower.isHeadquarters ? 4 : 2;
if (tower.isHeadquarters) {
towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2;
towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset;
} else {
towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
}
if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) {
self.targetTower = tower;
break;
}
}
}
};
self.update = function () {
if (tutorialActive && tutorialBlockUnitUpdates) {
return;
}
if (self.health <= 0) {
self.health = 0;
}
if (self.targetCellX !== null && self.targetCellY !== null) {
var targetX = grid.x + self.targetCellX * CELL_SIZE + CELL_SIZE / 2;
var targetY = grid.y + (self.targetCellY + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
if (self.targetTower && self.targetTower.parent) {
targetX = self.targetTower.x;
targetY = self.targetTower.y;
}
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var attackRange = CELL_SIZE;
if (self.targetTower && distance <= attackRange) {
if (!self.targetTower.isHeadquarters && self.targetTower.updateActivation) {
var consumed = self.targetTower.updateActivation(self.ownerIndex);
if (consumed) {
self.health = 0;
} else {
var tower = self.targetTower;
var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset);
var availableCells = [];
var searchRadius = 1;
var maxSearchRadius = 10;
while (availableCells.length === 0 && searchRadius <= maxSearchRadius) {
for (var dx = -searchRadius; dx <= searchRadius; dx++) {
for (var dy = -searchRadius; dy <= searchRadius; dy++) {
if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) {
continue;
}
var checkX = towerGridX + dx;
var checkY = towerGridY + dy;
var cell = grid.getCell(checkX, checkY);
if (cell && cell.type === 0) {
availableCells.push(cell);
}
}
}
searchRadius++;
}
if (availableCells.length > 0) {
var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)];
self.setTarget(randomCell.x, randomCell.y);
} else {
self.health = 0;
}
}
return;
}
if (!self.targetTower.parent) {
var tower = self.targetTower;
var towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
var towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE - mapGridOffset);
var availableCells = [];
var searchRadius = 1;
var maxSearchRadius = 10;
while (availableCells.length === 0 && searchRadius <= maxSearchRadius) {
for (var dx = -searchRadius; dx <= searchRadius; dx++) {
for (var dy = -searchRadius; dy <= searchRadius; dy++) {
if (Math.abs(dx) !== searchRadius && Math.abs(dy) !== searchRadius) {
continue;
}
var checkX = towerGridX + dx;
var checkY = towerGridY + dy;
var cell = grid.getCell(checkX, checkY);
if (cell && cell.type === 0) {
availableCells.push(cell);
}
}
}
searchRadius++;
}
if (availableCells.length > 0) {
var randomCell = availableCells[Math.floor(Math.random() * availableCells.length)];
self.setTarget(randomCell.x, randomCell.y);
} else {
self.health = 0;
}
} else {
var isEnemyTower = self.targetTower.playerIndex !== self.ownerIndex;
console.log("Unit reached tower. Enemy tower:", isEnemyTower, "Tower health:", self.targetTower.health);
self.targetTower.health -= 1;
self.health = 0;
if (self.targetTower.health <= 0) {
if (self.targetTower.isHeadquarters) {
if (self.targetTower.playerIndex === 1) {
handleGameEnd(true);
} else if (self.targetTower.playerIndex === 0) {
handleGameEnd(false);
}
}
var towerIndex = towers.indexOf(self.targetTower);
if (towerIndex !== -1) {
towers.splice(towerIndex, 1);
}
game.removeChild(self.targetTower);
var towerGridX, towerGridY;
var towerSize = self.targetTower.isHeadquarters ? 4 : 2;
if (self.targetTower.isHeadquarters) {
towerGridX = Math.round((self.targetTower.x - grid.x) / CELL_SIZE) - towerSize / 2;
towerGridY = Math.round((self.targetTower.y - grid.y) / CELL_SIZE) - towerSize / 2;
} else {
towerGridX = Math.floor((self.targetTower.x - grid.x) / CELL_SIZE);
towerGridY = Math.floor((self.targetTower.y - grid.y) / CELL_SIZE);
}
for (var i = 0; i < towerSize; i++) {
for (var j = 0; j < towerSize; j++) {
var cell = grid.getCell(towerGridX + i, towerGridY + j);
if (cell) {
cell.type = 0;
}
}
}
grid.pathFind();
grid.renderDebug();
}
}
return;
}
if (distance > 2) {
var angle = Math.atan2(dy, dx);
if (LK.ticks - (self.lastTrailTime || 0) > 3) {
self.lastTrailTime = LK.ticks;
var trail = new TrailParticle(self.x, self.y, self.children[0].tint, self.scaleX);
enemyLayerMiddle.addChild(trail);
}
self.x += Math.cos(angle) * self.movementSpeed * CELL_SIZE;
self.y += Math.sin(angle) * self.movementSpeed * CELL_SIZE;
self.currentCellX = (self.x - grid.x) / CELL_SIZE - 0.5;
self.currentCellY = (self.y - grid.y) / CELL_SIZE - 0.5;
self.cellX = Math.round(self.currentCellX);
self.cellY = Math.round(self.currentCellY);
} else {
self.targetCellX = null;
self.targetCellY = null;
self.targetTower = null;
self.isIdle = true;
self.idleBaseX = targetX;
self.idleBaseY = targetY;
if (unitManager) {
unitManager.startIdleAnimation(self);
}
}
}
};
return self;
});
var UnitManager = Container.expand(function () {
var self = Container.call(this);
self.units = [];
self.spawnInterval = 180;
self.lastSpawnTime = 0;
self.maxUnits = 100;
self.spawnUnitsAroundTower = function (tower) {
var actualGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
var actualGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
if (tower.isHeadquarters) {
actualGridX -= 1;
actualGridY -= 1;
}
var totalUnits = self.units.length;
if (totalUnits >= self.maxUnits) {
return;
}
var emptySpawnPositions = [];
var unitOccupiedSpawnPositions = [];
var towerSize = 2;
var maxSearchRadius = 2;
function checkCell(checkX, checkY) {
var cell = grid.getCell(checkX, checkY);
if (!cell || cell.type !== 0) {
return;
}
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === checkX && enemies[e].cellY === checkY) {
return;
}
}
var isOccupiedByUnit = false;
for (var u = 0; u < self.units.length; u++) {
if (self.units[u].cellX === checkX && self.units[u].cellY === checkY) {
isOccupiedByUnit = true;
break;
}
}
var pos = {
x: checkX,
y: checkY
};
if (isOccupiedByUnit) {
unitOccupiedSpawnPositions.push(pos);
} else {
emptySpawnPositions.push(pos);
}
}
for (var r = 1; r <= maxSearchRadius; r++) {
for (var dx = -r; dx < towerSize + r; dx++) {
checkCell(actualGridX + dx, actualGridY - r);
checkCell(actualGridX + dx, actualGridY + towerSize - 1 + r);
}
for (var dy = -r + 1; dy < towerSize + r - 1; dy++) {
checkCell(actualGridX - r, actualGridY + dy);
checkCell(actualGridX + towerSize - 1 + r, actualGridY + dy);
}
if (emptySpawnPositions.length > 0) {
break;
}
}
var spawnPositions = emptySpawnPositions;
if (spawnPositions.length === 0) {
spawnPositions = unitOccupiedSpawnPositions;
}
if (spawnPositions.length > 0) {
var spawnPos = spawnPositions[Math.floor(Math.random() * spawnPositions.length)];
var unit = new Unit('normal');
unit.cellX = spawnPos.x;
unit.cellY = spawnPos.y;
unit.currentCellX = spawnPos.x;
unit.currentCellY = spawnPos.y;
var finalX = grid.x + spawnPos.x * CELL_SIZE + CELL_SIZE / 2;
var finalY = grid.y + (spawnPos.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
unit.x = tower.x;
unit.y = tower.y;
unit.sourceTower = tower;
unit.ownerIndex = tower.playerIndex;
unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio;
enemyLayerTop.addChild(unit);
self.units.push(unit);
unit.alpha = 0;
unit.scaleX = 0.1;
unit.scaleY = 0.1;
if (tower.playerIndex === 0) {
unit.children[0].tint = teamColors[0];
} else if (tower.playerIndex === 1) {
unit.children[0].tint = teamColors[1];
} else {
var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
unit.children[0].tint = playerColors[(tower.playerIndex - 2) % playerColors.length];
}
tween(unit, {
scaleX: 1,
scaleY: 1,
alpha: 1,
x: finalX,
y: finalY
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
unit.isIdle = true;
unit.idleBaseX = finalX;
unit.idleBaseY = finalY;
self.startIdleAnimation(unit);
}
});
}
};
self.spawnUnitsAtRow = function (playerIndex, rowIndex) {
var gridWidth = grid.cells.length;
for (var col = 0; col < gridWidth; col++) {
if (self.units.length >= self.maxUnits) {
break;
}
var cell = grid.getCell(col, rowIndex);
if (cell && cell.type === 0) {
var unit = new Unit('normal');
unit.cellX = col;
unit.cellY = rowIndex;
unit.currentCellX = col;
unit.currentCellY = rowIndex;
var finalX = grid.x + col * CELL_SIZE + CELL_SIZE / 2;
var finalY = grid.y + (rowIndex + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
unit.x = finalX;
unit.y = finalY;
unit.ownerIndex = playerIndex;
unit.movementSpeed *= unit.ownerIndex == 0 ? 1 : enemyMovementSpeedRatio;
enemyLayerTop.addChild(unit);
self.units.push(unit);
unit.alpha = 0;
unit.scaleX = 0.1;
unit.scaleY = 0.1;
if (playerIndex === 0) {
unit.children[0].tint = teamColors[0];
} else if (playerIndex === 1) {
unit.children[0].tint = teamColors[1];
} else {
var playerColors = [0x0000FF, 0xFFFF00, 0xFF00FF, 0x00FFFF];
unit.children[0].tint = playerColors[(playerIndex - 2) % playerColors.length];
}
(function (currentUnit, finalUnitX, finalUnitY) {
tween(currentUnit, {
scaleX: 1,
scaleY: 1,
alpha: 1
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
currentUnit.isIdle = true;
currentUnit.idleBaseX = finalUnitX;
currentUnit.idleBaseY = finalUnitY;
self.startIdleAnimation(currentUnit);
}
});
})(unit, finalX, finalY);
}
}
};
self.startIdleAnimation = function (unit) {
if (!unit.isIdle || unit.idleAnimationActive) {
return;
}
unit.idleAnimationActive = true;
var _animateFloat = function animateFloat() {
if (!unit.isIdle || !unit.parent) {
unit.idleAnimationActive = false;
return;
}
var radius = 8;
var angle = Math.random() * Math.PI * 2;
var offsetX = Math.cos(angle) * radius;
var offsetY = Math.sin(angle) * radius;
tween(unit, {
x: unit.idleBaseX + offsetX,
y: unit.idleBaseY + offsetY
}, {
duration: 800 + Math.random() * 400,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (unit.isIdle && unit.parent) {
_animateFloat();
} else {
unit.idleAnimationActive = false;
}
}
});
};
_animateFloat();
};
self.update = function () {
if (LK.ticks - self.lastSpawnTime >= self.spawnInterval) {
self.lastSpawnTime = LK.ticks;
var generatorTowerCount = 0;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.isProducing && !tutorialBlockUnitProduction) {
generatorTowerCount++;
self.spawnUnitsAroundTower(tower);
}
}
}
for (var i = self.units.length - 1; i >= 0; i--) {
var unit = self.units[i];
if (unit.health <= 0) {
enemyLayerTop.removeChild(unit);
self.units.splice(i, 1);
continue;
}
}
};
return self;
});
var UpgradeMenu = Container.expand(function (tower) {
var self = Container.call(this);
self.tower = tower;
self.y = 2732 + 225;
var menuBackground = self.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
menuBackground.width = 2048;
menuBackground.height = 500;
menuBackground.tint = 0x444444;
menuBackground.alpha = 0.9;
var towerTypeText = new Text2(self.tower.id.charAt(0).toUpperCase() + self.tower.id.slice(1) + ' Tower', {
size: 80,
fill: 0xFFFFFF,
weight: 800
});
towerTypeText.anchor.set(0, 0);
towerTypeText.x = -840;
towerTypeText.y = -160;
self.addChild(towerTypeText);
var statsText = new Text2('Level: ' + self.tower.level + '/' + self.tower.maxLevel + '\nDamage: ' + self.tower.damage + '\nFire Rate: ' + (60 / self.tower.fireRate).toFixed(1) + '/s', {
size: 70,
fill: 0xFFFFFF,
weight: 400
});
statsText.anchor.set(0, 0.5);
statsText.x = -840;
statsText.y = 50;
self.addChild(statsText);
var buttonsContainer = new Container();
buttonsContainer.x = 500;
self.addChild(buttonsContainer);
var upgradeButton = new Container();
buttonsContainer.addChild(upgradeButton);
var buttonBackground = upgradeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
buttonBackground.width = 500;
buttonBackground.height = 150;
buttonBackground.tint = 0x00AA00;
var buttonText = new Text2(self.tower.isProducing ? 'Defense mode' : 'Generator mode', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
buttonText.anchor.set(0.5, 0.5);
upgradeButton.addChild(buttonText);
var sellButton = new Container();
buttonsContainer.addChild(sellButton);
var sellButtonBackground = sellButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
sellButtonBackground.width = 500;
sellButtonBackground.height = 150;
sellButtonBackground.tint = 0xCC0000;
var sellButtonText = new Text2('Move out', {
size: 60,
fill: 0xFFFFFF,
weight: 800
});
sellButtonText.anchor.set(0.5, 0.5);
sellButton.addChild(sellButtonText);
if (self.tower.isHeadquarters) {
sellButton.visible = false;
}
upgradeButton.y = -85;
sellButton.y = 85;
var closeButton = new Container();
self.addChild(closeButton);
var closeBackground = closeButton.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
closeBackground.width = 90;
closeBackground.height = 90;
closeBackground.tint = 0xAA0000;
var closeText = new Text2('X', {
size: 68,
fill: 0xFFFFFF,
weight: 800
});
closeText.anchor.set(0.5, 0.5);
closeButton.addChild(closeText);
closeButton.x = menuBackground.width / 2 - 57;
closeButton.y = -menuBackground.height / 2 + 57;
upgradeButton.down = function (x, y, obj) {
self.tower.changeTowerMode();
if (self.tower.isHeadquarters) {
if (tutorialActive && tutorialStep === 7 && self.tower.playerIndex === 0 && !self.tower.isProducing) {
executeTutorialStep(8);
}
}
hideUpgradeMenu(self);
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
game.removeChild(game.children[i]);
break;
}
}
selectedTower = null;
grid.renderDebug();
};
sellButton.down = function (x, y, obj) {
if (self.tower.isHeadquarters) {
return;
}
if (unitManager) {
for (var i = 0; i < 10; i++) {
unitManager.spawnUnitsAroundTower(self.tower);
}
}
self.tower.isProducing = false;
self.tower.currentShares = 0;
self.tower.capturingPlayer = -1;
self.tower.deactivate();
hideUpgradeMenu(self);
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange && game.children[i].tower === self.tower) {
game.removeChild(game.children[i]);
break;
}
}
if (selectedTower === self.tower) {
selectedTower = null;
}
grid.renderDebug();
};
closeButton.down = function (x, y, obj) {
hideUpgradeMenu(self);
selectedTower = null;
grid.renderDebug();
};
self.update = function () {
// This method is now empty as the buttons are static within the menu's lifecycle.
};
self.updateButtonText = function (newText) {
buttonText.setText(newText);
};
return self;
});
var Wall = Container.expand(function () {
var self = Container.call(this);
var wallGraphics = self.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallGraphics.alpha = 0.5;
self.startPulseAnimation = function () {
var _pulseAnimation2 = function _pulseAnimation() {
tween(wallGraphics, {
alpha: 1
}, {
duration: 4000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(wallGraphics, {
alpha: 0.5
}, {
duration: 4000,
easing: tween.easeInOut,
onFinish: function onFinish() {
if (self.parent) {
_pulseAnimation2();
}
}
});
}
});
};
_pulseAnimation2();
};
self.startPulseAnimation();
return self;
});
var WaveIndicator = Container.expand(function () {
var self = Container.call(this);
self.gameStarted = false;
self.waveMarkers = [];
self.waveTypes = [];
self.enemyCounts = [];
self.indicatorWidth = 0;
self.lastBossType = null;
var blockWidth = 400;
var totalBlocksWidth = blockWidth * totalWaves;
var startMarker = new Container();
var startBlock = startMarker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
startBlock.width = blockWidth - 10;
startBlock.height = 70 * 2;
startBlock.tint = 0x00AA00;
var startTextShadow = new Text2("Start Game", {
size: 50,
fill: 0x000000,
weight: 800
});
startTextShadow.anchor.set(0.5, 0.5);
startTextShadow.x = 4;
startTextShadow.y = 4;
startMarker.addChild(startTextShadow);
var startText = new Text2("Start Game", {
size: 50,
fill: 0xFFFFFF,
weight: 800
});
startText.anchor.set(0.5, 0.5);
startMarker.addChild(startText);
startMarker.x = -self.indicatorWidth;
self.addChild(startMarker);
self.waveMarkers.push(startMarker);
startMarker.down = function () {
if (!self.gameStarted) {
self.gameStarted = true;
currentWave = 0;
waveTimer = nextWaveTime;
startBlock.tint = 0x00FF00;
startText.setText("Started!");
startTextShadow.setText("Started!");
startTextShadow.x = 4;
startTextShadow.y = 4;
var notification = game.addChild(new Notification("Game started! Wave 1 incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
};
for (var i = 0; i < totalWaves; i++) {
var marker = new Container();
var block = marker.attachAsset('notification', {
anchorX: 0.5,
anchorY: 0.5
});
block.width = blockWidth - 10;
block.height = 70 * 2;
var waveType = "normal";
var enemyType = "normal";
var enemyCount = 10;
var isBossWave = (i + 1) % 10 === 0;
if (i === 0) {
block.tint = 0xAAAAAA;
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
} else if (isBossWave) {
enemyType = 'normal';
waveType = "Boss Normal";
block.tint = 0xAAAAAA;
enemyCount = 1;
} else {
block.tint = 0xAAAAAA;
waveType = "Normal";
enemyType = "normal";
enemyCount = 10;
}
if (isBossWave && enemyType !== 'swarm') {
var bossIndicator = marker.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
bossIndicator.width = 30;
bossIndicator.height = 30;
bossIndicator.tint = 0xFFD700;
bossIndicator.y = -block.height / 2 - 15;
waveType = "BOSS";
}
self.waveTypes[i] = enemyType;
self.enemyCounts[i] = enemyCount;
var waveTypeShadow = new Text2(waveType, {
size: 56,
fill: 0x000000,
weight: 800
});
waveTypeShadow.anchor.set(0.5, 0.5);
waveTypeShadow.x = 4;
waveTypeShadow.y = 4;
marker.addChild(waveTypeShadow);
var waveTypeText = new Text2(waveType, {
size: 56,
fill: 0xFFFFFF,
weight: 800
});
waveTypeText.anchor.set(0.5, 0.5);
waveTypeText.y = 0;
marker.addChild(waveTypeText);
var waveNumShadow = new Text2((i + 1).toString(), {
size: 48,
fill: 0x000000,
weight: 800
});
waveNumShadow.anchor.set(1.0, 1.0);
waveNumShadow.x = blockWidth / 2 - 16 + 5;
waveNumShadow.y = block.height / 2 - 12 + 5;
marker.addChild(waveNumShadow);
var waveNum = new Text2((i + 1).toString(), {
size: 48,
fill: 0xFFFFFF,
weight: 800
});
waveNum.anchor.set(1.0, 1.0);
waveNum.x = blockWidth / 2 - 16;
waveNum.y = block.height / 2 - 12;
marker.addChild(waveNum);
marker.x = -self.indicatorWidth + (i + 1) * blockWidth;
self.addChild(marker);
self.waveMarkers.push(marker);
}
self.getWaveType = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return "normal";
}
var waveType = self.waveTypes[waveNumber - 1];
return waveType;
};
self.getEnemyCount = function (waveNumber) {
if (waveNumber < 1 || waveNumber > totalWaves) {
return 10;
}
return self.enemyCounts[waveNumber - 1];
};
self.getWaveTypeName = function (waveNumber) {
var type = self.getWaveType(waveNumber);
var typeName = type.charAt(0).toUpperCase() + type.slice(1);
if (waveNumber % 10 === 0 && waveNumber > 0 && type !== 'swarm') {
typeName = "BOSS";
}
return typeName;
};
self.positionIndicator = new Container();
var indicator = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator.width = blockWidth - 10;
indicator.height = 16;
indicator.tint = 0xffad0e;
indicator.y = -65;
var indicator2 = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
indicator2.width = blockWidth - 10;
indicator2.height = 16;
indicator2.tint = 0xffad0e;
indicator2.y = 65;
var leftWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
leftWall.width = 16;
leftWall.height = 146;
leftWall.tint = 0xffad0e;
leftWall.x = -(blockWidth - 16) / 2;
var rightWall = self.positionIndicator.attachAsset('towerLevelIndicator', {
anchorX: 0.5,
anchorY: 0.5
});
rightWall.width = 16;
rightWall.height = 146;
rightWall.tint = 0xffad0e;
rightWall.x = (blockWidth - 16) / 2;
self.addChild(self.positionIndicator);
self.update = function () {
var progress = waveTimer / nextWaveTime;
var moveAmount = (progress + currentWave) * blockWidth;
for (var i = 0; i < self.waveMarkers.length; i++) {
var marker = self.waveMarkers[i];
marker.x = -moveAmount + i * blockWidth;
}
self.positionIndicator.x = 0;
for (var i = 0; i < totalWaves + 1; i++) {
var marker = self.waveMarkers[i];
if (i === 0) {
continue;
}
var block = marker.children[0];
if (i - 1 < currentWave) {
block.alpha = .5;
}
}
self.handleWaveProgression = function () {
if (!self.gameStarted) {
return;
}
if (currentWave < totalWaves) {
waveTimer++;
if (waveTimer >= nextWaveTime) {
waveTimer = 0;
currentWave++;
waveInProgress = true;
waveSpawned = false;
if (currentWave != 1) {
var waveType = self.getWaveTypeName(currentWave);
var enemyCount = self.getEnemyCount(currentWave);
var notification = game.addChild(new Notification("Wave " + currentWave + " (" + waveType + " - " + enemyCount + " enemies) incoming!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
}
}
};
self.handleWaveProgression();
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x333333
});
/****
* Game Code
****/
var aiPlayer = null;
var teamColors = [0x00AA00, 0xAA0000];
var isHidingUpgradeMenu = false;
var isSelectingUnits = false;
var selectionStartX = 0;
var selectionStartY = 0;
var selectionCircle = null;
var selectedUnits = [];
var isCommandMode = false;
var isEndGame = false;
function handleGameEnd(isVictory) {
if (isEndGame) {
return;
}
isEndGame = true;
var currentLevel = storage.currentLevel || 1;
if (isVictory && currentLevel <= 20) {
storage.currentLevel = currentLevel + 1;
var message = "Level " + (storage.currentLevel - 1) + " Complete!";
var endText = new Text2(message, {
size: 150,
fill: 0x00FF00,
weight: "bold",
align: 'center'
});
endText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(endText);
LK.setTimeout(initNewLevel, 3000);
return;
}
var message = isVictory ? "Victory!" : "Defeat!";
var color = isVictory ? 0x00FF00 : 0xFF0000;
var endText = new Text2(message, {
size: 150,
fill: color,
weight: "bold",
align: 'center'
});
endText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(endText);
LK.setTimeout(function () {
if (isVictory) {
LK.showYouWin();
} else {
LK.showGameOver();
}
}, 3000);
}
function initNewLevel() {
// 1. Clean up from previous level
for (var i = towers.length - 1; i >= 0; i--) {
towers[i].destroy();
}
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].destroy();
}
enemyLayerTop.removeChildren();
enemyLayerMiddle.removeChildren();
enemyLayerBottom.removeChildren();
if (grid && grid.parent) {
grid.destroy();
}
if (unitManager && unitManager.parent) {
unitManager.destroy();
}
if (aiPlayer && aiPlayer.parent) {
aiPlayer.destroy();
}
if (towerPreview && towerPreview.parent) {
towerPreview.destroy();
}
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
if (selectionCircle) {
selectionCircle.destroy();
}
LK.gui.center.removeChildren();
// 2. Reset state variables
pathId = 1;
maxScore = 0;
enemies = [];
towers = [];
bullets = [];
defenses = [];
selectedTower = null;
gold = 80;
currentWave = 0;
waveTimer = 0;
waveInProgress = false;
waveSpawned = false;
sourceTower = null;
isSelectingUnits = false;
isCommandMode = false;
selectionCircle = null;
selectedUnits = [];
// 3. Initialize new level from scratch
grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * mapGridOffset;
debugLayer.addChild(grid);
unitManager = new UnitManager();
game.addChild(unitManager);
addHeadquarters();
addEmptyTowers();
grid.pathFind();
grid.renderDebug();
aiPlayer = new AIPlayer(1);
game.addChild(aiPlayer);
towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
updateUI();
isEndGame = false;
}
function hideUpgradeMenu(menu) {
if (isHidingUpgradeMenu) {
return;
}
isHidingUpgradeMenu = true;
tween(menu, {
y: 2732 + 225
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
menu.destroy();
isHidingUpgradeMenu = false;
}
});
}
var isDebug = false;
var CELL_SIZE = 76;
var pathId = 1;
var maxScore = 0;
var mapGridOffset = 4;
var enemies = [];
var towers = [];
var bullets = [];
var defenses = [];
var selectedTower = null;
var unitManager = null;
var gold = 80;
var lives = 20;
var score = 0;
var currentWave = 0;
var totalWaves = 50;
var waveTimer = 0;
var waveInProgress = false;
var waveSpawned = false;
var nextWaveTime = 12000 / 2;
var sourceTower = null;
var enemiesToSpawn = 10;
var enemyMovementSpeedRatio = 0.6;
// Map cell values: 0 = empty cell, 1 = player base, 2 = enemy 1 base, 3 = enemy 2 base,
// 4 = player tower, 5 = enemy 1 tower, 6 = enemy 2 tower, 8 = empty tower, 9 = wall,
// 11 = player base (rotated), 22 = enemy 1 base (rotated), 33 = enemy 2 base (rotated)
var MAPS = [
// Level 0: Tutorial tower defense layout (24x31)
[[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 22, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
// Row 0
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 5
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 6
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 7
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 8
[9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 9
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 10
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 11
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 12
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 13
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 14
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 15
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 16
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 17
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 18
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 19
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 20
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 21
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 22
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 23
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 24
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 25
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 26
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 27
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 28
[9, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 29
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 30
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 31
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 32
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 33
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] // Row 34 - Player base area
],
// Level 1: Basic tower defense layout (24x31)
[[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 22, 22, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
// Row 0
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 22, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 5
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 6
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 7
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 8
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 9
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 10
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 11
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 12
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 13
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 14
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 15
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 16
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 17
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 18
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 19
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 20
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 21
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 22
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 23
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 24
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 25
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 26
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 27
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 28
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 29
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 30
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 31
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 32
[9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
// Row 33
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 1, 1, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9] // Row 34 - Player base area
]];
function initMapForLevel(level) {
// Level 0 is tutorial, actual levels start at 1
if (level <= 0) {
return MAPS[0]; // Return tutorial map
}
// Use level 1 base map (MAPS[1]) as template
var baseMap = MAPS[1];
var mapHeight = baseMap.length;
var mapWidth = baseMap[0].length;
// Create a deep copy of the base map
var newMap = [];
for (var i = 0; i < mapHeight; i++) {
newMap[i] = [];
for (var j = 0; j < mapWidth; j++) {
newMap[i][j] = baseMap[i][j];
}
}
// Calculate number of towers to add: 2x(3+level-1) = 2x(2+level)
var towersToAdd = 2 * (2 + level);
// Find all empty cells between rows 10 and 26 (excluding walls and bases)
var availableCells = [];
for (var row = 10; row <= 26 - mapGridOffset; row++) {
for (var col = 1; col < mapWidth - 1; col++) {
// Exclude wall boundaries
if (newMap[row][col] === 0) {
// Empty cell
availableCells.push({
row: row,
col: col
});
}
}
}
// Shuffle available cells for random placement
for (var i = availableCells.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = availableCells[i];
availableCells[i] = availableCells[j];
availableCells[j] = temp;
}
// Place towers symmetrically around the center line (column 12)
var centerCol = Math.floor(mapWidth / 2);
var towersPlaced = 0;
for (var i = 0; i < availableCells.length && towersPlaced < towersToAdd; i++) {
var cell = availableCells[i];
var row = cell.row;
var col = cell.col;
// Calculate the symmetric position
var symmetricCol = centerCol + (centerCol - col);
// Check if both positions are valid and empty
if (symmetricCol >= 1 && symmetricCol < mapWidth - 1 && newMap[row][col] === 0 && newMap[row][symmetricCol] === 0) {
// Place towers at both symmetric positions
newMap[row][col] = 8; // Empty tower
if (col !== symmetricCol) {
// Don't place twice if it's on center line
newMap[row][symmetricCol] = 8; // Empty tower
towersPlaced += 2;
} else {
towersPlaced += 1;
}
}
}
return newMap;
}
var goldText = new Text2('Gold: ' + gold, {
size: 60,
fill: 0xFFD700,
weight: 800
});
goldText.anchor.set(0.5, 0.5);
var livesText = new Text2('Lives: ' + lives, {
size: 60,
fill: 0x00FF00,
weight: 800
});
livesText.anchor.set(0.5, 0.5);
var scoreText = new Text2('Score: ' + score, {
size: 60,
fill: 0xFF0000,
weight: 800
});
scoreText.anchor.set(0.5, 0.5);
var topMargin = -75;
var centerX = 2048 / 2;
var spacing = 400;
/*
LK.gui.bottom.addChild(goldText);
LK.gui.bottom.addChild(livesText);
LK.gui.bottom.addChild(scoreText);
*/
livesText.x = 0;
livesText.y = topMargin;
goldText.x = -spacing;
goldText.y = topMargin;
scoreText.x = spacing;
scoreText.y = topMargin;
function updateUI() {
goldText.setText('Gold: ' + gold);
livesText.setText('Lives: ' + lives);
scoreText.setText('Score: ' + score);
}
function setGold(value) {
gold = value;
updateUI();
}
var backgroundManager = new BackgroundManager();
game.addChild(backgroundManager);
var background1 = backgroundManager.addBackground('background_1', {
x: 1024,
y: 1366,
scaleX: 1,
scaleY: 1,
alpha: 0.8,
tint: 0xCCCCCC
});
var background2 = backgroundManager.addBackground('background_2', {
x: 1024,
y: 1366,
scaleX: 1,
scaleY: 1,
alpha: 1
});
var background3 = backgroundManager.addBackground('background_3', {
x: 1024,
y: 1366,
scaleX: 1,
scaleY: 1,
alpha: 1
});
backgroundManager.createParallaxEffect([background1, background2, background3], 1);
var debugLayer = new Container();
var enemyLayerBottom = new Container();
var enemyLayerMiddle = new Container();
var enemyLayerTop = new Container();
var enemyLayer = new Container();
enemyLayer.addChild(enemyLayerBottom);
enemyLayer.addChild(enemyLayerMiddle);
enemyLayer.addChild(enemyLayerTop);
var grid = new Grid(24, 29 + 6);
grid.x = 150;
grid.y = 200 - CELL_SIZE * mapGridOffset;
grid.pathFind();
grid.renderDebug();
debugLayer.addChild(grid);
game.addChild(debugLayer);
unitManager = new UnitManager();
game.addChild(unitManager);
function addHeadquarters() {
var playerBaseCells = [];
var enemyBaseCells = [];
for (var i = 0; i < grid.cells.length; i++) {
for (var j = 0; j < grid.cells[i].length; j++) {
var cell = grid.cells[i][j];
if (cell.type === 3) {
playerBaseCells.push(cell);
} else if (cell.type === 2) {
enemyBaseCells.push(cell);
}
}
}
if (playerBaseCells.length > 0) {
var playerOrientation = 0;
for (var i = 0; i < playerBaseCells.length; i++) {
if (playerBaseCells[i].orientation) {
playerOrientation = playerBaseCells[i].orientation;
break;
}
}
var playerHQ = new Tower(true, playerOrientation, 0);
var playerAnchorX = 10;
var playerAnchorY = 31;
playerHQ.x = grid.x + (playerAnchorX + 1.5) * CELL_SIZE;
playerHQ.y = grid.y + (playerAnchorY + 2) * CELL_SIZE;
playerHQ.children[0].tint = teamColors[0];
game.addChild(playerHQ);
towers.push(playerHQ);
}
if (enemyBaseCells.length > 0) {
var enemyOrientation = 0;
for (var i = 0; i < enemyBaseCells.length; i++) {
if (enemyBaseCells[i].orientation) {
enemyOrientation = enemyBaseCells[i].orientation;
break;
}
}
var enemyHQ = new Tower(true, enemyOrientation, 1);
var enemyAnchorX = 10;
var enemyAnchorY = 0;
enemyHQ.x = grid.x + (enemyAnchorX + 1.5) * CELL_SIZE;
if (enemyOrientation !== 0) {
enemyHQ.y = grid.y + (enemyAnchorY + 5) * CELL_SIZE;
} else {
enemyHQ.y = grid.y + (enemyAnchorY + 2) * CELL_SIZE;
}
enemyHQ.children[0].tint = teamColors[1];
game.addChild(enemyHQ);
towers.push(enemyHQ);
}
}
function addEmptyTowers() {
var emptyTowerCells = [];
for (var i = 0; i < grid.cells.length; i++) {
for (var j = 0; j < grid.cells[i].length; j++) {
var cell = grid.cells[i][j];
if (cell.type === 4) {
emptyTowerCells.push(cell);
}
}
}
for (var i = 0; i < emptyTowerCells.length; i++) {
var cell = emptyTowerCells[i];
var emptyTower = new Tower(false, 0, -1);
emptyTower.x = grid.x + cell.x * CELL_SIZE + CELL_SIZE / 2;
emptyTower.y = grid.y + (cell.y + mapGridOffset) * CELL_SIZE + CELL_SIZE / 2;
var baseGraphics = emptyTower.children[0];
baseGraphics.width = CELL_SIZE;
baseGraphics.height = CELL_SIZE;
baseGraphics.tint = 0xAAAAAA;
var gunContainer = emptyTower.children[emptyTower.children.length - 1];
gunContainer.visible = false;
game.addChild(emptyTower);
towers.push(emptyTower);
cell.type = 1;
cell.isVisuallyEmptyTower = true;
}
if (emptyTowerCells.length > 0) {
grid.pathFind();
grid.renderDebug();
}
}
addHeadquarters();
addEmptyTowers();
var tutorialActive = false;
var tutorialStep = 0;
var tutorialText = null;
var tutorialOverlayTop = null;
var tutorialOverlayBottom = null;
var tutorialWaitForTap = false;
var tutorialBlockUnitProduction = true;
var tutorialBlockUnitUpdates = true;
var tutorialBlockTowerUpdates = true;
function getPlayerHQ() {
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 0) {
return towers[i];
}
}
return null;
}
function findFirstEmptyTower() {
var potentialTowers = [];
for (var i = 0; i < towers.length; i++) {
if (!towers[i].isHeadquarters && towers[i].playerIndex === -1) {
potentialTowers.push(towers[i]);
}
}
if (potentialTowers.length === 0) {
return null;
}
var playerHQ = getPlayerHQ();
if (!playerHQ) {
return potentialTowers[0];
}
var closestTower = null;
var closestDistSq = Infinity;
for (var i = 0; i < potentialTowers.length; i++) {
var tower = potentialTowers[i];
var dx = tower.x - playerHQ.x;
var dy = tower.y - playerHQ.y;
var distSq = dx * dx + dy * dy;
if (distSq < closestDistSq) {
closestDistSq = distSq;
closestTower = tower;
}
}
return closestTower;
}
function executeTutorialStep(step) {
tutorialStep = step;
tutorialOverlayTop.visible = false;
tutorialOverlayBottom.visible = false;
tutorialText.setText('');
tutorialWaitForTap = false;
tutorialBlockUnitProduction = true;
tutorialBlockUnitUpdates = true;
tutorialBlockTowerUpdates = true;
switch (step) {
case 1:
// Enemy Movement
var playerHQ = getPlayerHQ();
if (playerHQ && unitManager) {
var towerSize = playerHQ.isHeadquarters ? 4 : 2;
var hqGridX = Math.round((playerHQ.x - grid.x) / CELL_SIZE) - towerSize / 2;
var hqGridY = Math.round((playerHQ.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset;
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex === 1) {
unit.setTarget(hqGridX, hqGridY);
}
}
}
tutorialBlockUnitUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(2);
}, 3000);
break;
case 2:
// Enemy Alert
var enemyRowY = 700 + (2 + mapGridOffset) * CELL_SIZE;
tutorialOverlayTop.visible = true;
var enemyHQ = null;
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 1) {
enemyHQ = towers[i];
break;
}
}
if (enemyHQ) {
tutorialOverlayTop.height = enemyHQ.y - 150;
} else {
tutorialOverlayTop.height = 150;
}
tutorialOverlayBottom.visible = true;
tutorialOverlayBottom.y = enemyRowY + CELL_SIZE / 2;
tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y;
tutorialText.setText("Enemy Wave Incoming!\n(tap to continue)");
tutorialWaitForTap = true;
break;
case 3:
// Unit Selection
var playerHQ = getPlayerHQ();
if (playerHQ) {
var visibleTopY = playerHQ.y - 300;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = visibleTopY;
}
tutorialText.setText("Select 10 of your units!");
break;
case 4:
// Tower Capture
console.log("Tuto step 4: Tower capture", tutorialBlockUnitUpdates);
var emptyTower = findFirstEmptyTower();
if (emptyTower) {
var towerY = emptyTower.y;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = towerY - 150;
tutorialOverlayBottom.visible = true;
tutorialOverlayBottom.y = towerY + 150;
tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y;
tutorialText.setText("Capture an empty tower.");
/*
tutorialBlockUnitUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(5);
}, 1000);
*/
}
break;
case 5:
// Unit Movement for Capture
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(6);
}, 1000);
break;
case 6:
// HQ Selection
console.log("Tuto step 6: HQ Selection", tutorialBlockUnitUpdates);
var playerHQ = getPlayerHQ();
if (playerHQ) {
var hqY = playerHQ.y;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = hqY - 200;
tutorialOverlayBottom.visible = true;
tutorialOverlayBottom.y = hqY + 200;
tutorialOverlayBottom.height = 2732 - tutorialOverlayBottom.y;
tutorialText.setText("Select your HQ.");
}
break;
case 7:
// Switch to Defense
var visibleTopY = 2732 - 500;
tutorialOverlayTop.visible = true;
tutorialOverlayTop.height = visibleTopY;
tutorialText.setText("Switch to Defense mode...");
break;
case 8:
// HQ Attacking
tutorialOverlayTop.visible = false;
tutorialOverlayBottom.visible = false;
tutorialText.setText('');
tutorialBlockTowerUpdates = false;
tutorialBlockUnitUpdates = false;
LK.setTimeout(function () {
executeTutorialStep(9);
}, 10000);
break;
case 9:
tutorialOverlayTop.visible = false;
tutorialOverlayBottom.visible = false;
tutorialBlockTowerUpdates = false;
tutorialBlockUnitUpdates = false;
tutorialText.setText("Wave cleared!");
LK.setTimeout(function () {
executeTutorialStep(10);
}, 2000);
break;
case 10:
// "Now take your revenge!"
tutorialText.setText("Now take your revenge!");
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
LK.setTimeout(function () {
// End Tutorial
tutorialActive = false;
tutorialBlockUnitProduction = false;
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
storage.hasPlayedTutorial = true;
tutorialText.setText('');
var notification = game.addChild(new Notification("Tutorial Complete!"));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
}, 2000);
break;
}
}
function playTutorial() {
tutorialActive = true;
tutorialStep = 0;
tutorialOverlayTop = game.addChild(LK.getAsset('notification', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 100
}));
tutorialOverlayTop.tint = 0x000000;
tutorialOverlayTop.alpha = 0.8;
tutorialOverlayTop.visible = false;
tutorialOverlayBottom = game.addChild(LK.getAsset('notification', {
anchorX: 0,
anchorY: 0,
x: 0,
y: 0,
width: 2048,
height: 100
}));
tutorialOverlayBottom.tint = 0x000000;
tutorialOverlayBottom.alpha = 0.8;
tutorialOverlayBottom.visible = false;
tutorialText = new Text2('', {
size: 70,
fill: 0xFFFFFF,
weight: "bold",
align: 'center',
wordWrap: true,
wordWrapWidth: 1200
});
tutorialText.anchor.set(0.5, 0.5);
LK.gui.center.addChild(tutorialText);
executeTutorialStep(1);
}
function initLevel0Units() {
unitManager.spawnUnitsAtRow(1, 2);
var playerHQ = null;
for (var i = 0; i < towers.length; i++) {
if (towers[i].isHeadquarters && towers[i].playerIndex === 0) {
playerHQ = towers[i];
break;
}
}
if (playerHQ) {
for (var i = 0; i < 10; i++) {
unitManager.spawnUnitsAroundTower(playerHQ);
}
}
}
var offset = 0;
game.addChild(enemyLayer);
var towerPreview = new TowerPreview();
game.addChild(towerPreview);
towerPreview.visible = false;
var isDragging = false;
var hasPlayedTutorial = storage.hasPlayedTutorial || false;
// FOR DEBUGGING
//hasPlayedTutorial = false;
//storage.hasPlayedTutorial = false;
// FOR DEBUGGING
if (!hasPlayedTutorial) {
initLevel0Units();
LK.setTimeout(playTutorial, 1000);
} else {
tutorialActive = false;
tutorialBlockUnitProduction = false;
tutorialBlockUnitUpdates = false;
tutorialBlockTowerUpdates = false;
}
aiPlayer = new AIPlayer(1);
game.addChild(aiPlayer);
function wouldBlockPath(gridX, gridY) {
var cells = [];
for (var i = 0; i < 2; i++) {
for (var j = 0; j < 2; j++) {
var cell = grid.getCell(gridX + i, gridY + j);
if (cell) {
cells.push({
cell: cell,
originalType: cell.type
});
cell.type = 1;
}
}
}
var blocked = grid.pathFind();
for (var i = 0; i < cells.length; i++) {
cells[i].cell.type = cells[i].originalType;
}
grid.pathFind();
grid.renderDebug();
return blocked;
}
function getTowerCost(towerType) {
var cost = 5;
cost = 5;
return cost;
}
function getTowerSellValue(totalValue) {
return waveIndicator && waveIndicator.gameStarted ? Math.floor(totalValue * 0.6) : totalValue;
}
function placeTower(gridX, gridY, towerType) {
var towerCost = getTowerCost(towerType);
if (gold >= towerCost) {
var tower = new Tower(false, undefined, 0);
tower.placeOnGrid(gridX, gridY);
game.addChild(tower);
towers.push(tower);
setGold(gold - towerCost);
grid.pathFind();
grid.renderDebug();
return true;
} else {
var notification = game.addChild(new Notification("Not enough gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
return false;
}
}
game.down = function (x, y, obj) {
if (tutorialActive && tutorialWaitForTap) {
executeTutorialStep(tutorialStep + 1);
return;
}
var upgradeMenuVisible = game.children.some(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenuVisible) {
return;
}
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var towerLeft = tower.x - CELL_SIZE;
var towerRight = tower.x + CELL_SIZE;
var towerTop = tower.y - CELL_SIZE;
var towerBottom = tower.y + CELL_SIZE;
if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
clickedOnTower = true;
break;
}
}
if (sourceTower) {
var sourceLeft = sourceTower.x - sourceTower.width / 2;
var sourceRight = sourceTower.x + sourceTower.width / 2;
var sourceTop = sourceTower.y - sourceTower.height / 2;
var sourceBottom = sourceTower.y + sourceTower.height / 2;
if (x >= sourceLeft && x <= sourceRight && y >= sourceTop && y <= sourceBottom) {
clickedOnTower = true;
}
}
if (!clickedOnTower && !isDragging && !isCommandMode) {
if (selectionCircle) {
game.removeChild(selectionCircle);
selectionCircle = null;
}
isSelectingUnits = true;
selectionStartX = x;
selectionStartY = y;
selectionCircle = new SelectionCircle();
selectionCircle.setPosition(x, y);
game.addChild(selectionCircle);
}
};
game.move = function (x, y, obj) {
if (isDragging) {
towerPreview.snapToGrid(x, y - CELL_SIZE * 1.5);
} else if (isSelectingUnits && selectionCircle) {
var screenMargin = 50;
if (x < -screenMargin || x > 2048 + screenMargin || y < -screenMargin || y > 2732 + screenMargin) {
isSelectingUnits = false;
if (selectionCircle) {
game.removeChild(selectionCircle);
selectionCircle = null;
}
} else {
selectionCircle.updateCircle(x, y);
}
}
};
game.up = function (x, y, obj) {
if (isSelectingUnits && selectionCircle) {
isSelectingUnits = false;
for (var i = 0; i < selectedUnits.length; i++) {
selectedUnits[i].setSelected(false);
}
selectedUnits = [];
var centerX = selectionCircle.startX;
var centerY = selectionCircle.startY;
var radius = selectionCircle.currentRadius;
if (unitManager && unitManager.units) {
for (var i = 0; i < unitManager.units.length; i++) {
var unit = unitManager.units[i];
if (unit.ownerIndex === 0) {
var dx = unit.x - centerX;
var dy = unit.y - centerY;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= radius) {
unit.setSelected(true);
selectedUnits.push(unit);
}
}
}
}
game.removeChild(selectionCircle);
selectionCircle = null;
if (selectedUnits.length > 0) {
var notification = game.addChild(new Notification(selectedUnits.length + " units selected"));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
isCommandMode = true;
if (tutorialActive && tutorialStep === 3 && selectedUnits.length === 10) {
executeTutorialStep(4);
}
}
return;
}
if (isCommandMode && selectedUnits.length > 0) {
var gridPosX = x - grid.x;
var gridPosY = y - grid.y;
var cellX = Math.floor(gridPosX / CELL_SIZE);
var cellY = Math.floor(gridPosY / CELL_SIZE) - mapGridOffset;
var targetCell = grid.getCell(cellX, cellY);
if (!targetCell || targetCell.type === 1 || targetCell.type === 9) {
var closestCell = null;
var closestDistance = Infinity;
var searchRadius = 1;
var maxSearchRadius = 10;
while (!closestCell && searchRadius <= maxSearchRadius) {
for (var dx = -searchRadius; dx <= searchRadius; dx++) {
for (var dy = -searchRadius; dy <= searchRadius; dy++) {
if (Math.abs(dx) === searchRadius || Math.abs(dy) === searchRadius) {
var checkX = cellX + dx;
var checkY = cellY + dy;
var checkCell = grid.getCell(checkX, checkY);
if (checkCell && checkCell.type === 0) {
var dist = dx * dx + dy * dy;
if (dist < closestDistance) {
closestDistance = dist;
closestCell = checkCell;
}
}
}
}
}
searchRadius++;
}
targetCell = closestCell;
}
var targetHasTower = false;
var enemyTower = null;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
if (tower.playerIndex !== 0) {
var towerGridX, towerGridY;
var towerSize = tower.isHeadquarters ? 4 : 2;
if (tower.isHeadquarters) {
towerGridX = Math.round((tower.x - grid.x) / CELL_SIZE) - towerSize / 2;
towerGridY = Math.round((tower.y - grid.y) / CELL_SIZE) - towerSize / 2 - mapGridOffset;
} else {
towerGridX = Math.floor((tower.x - grid.x) / CELL_SIZE);
towerGridY = Math.floor((tower.y - grid.y) / CELL_SIZE) - mapGridOffset;
}
if (cellX >= towerGridX && cellX < towerGridX + towerSize && cellY >= towerGridY && cellY < towerGridY + towerSize) {
targetHasTower = true;
enemyTower = tower;
console.log("Empty tower tapped", tutorialActive, tutorialStep === 4, tower.playerIndex === -1, towerGridY, selectedUnits.length === 10);
if (tutorialActive && tutorialStep === 4 && tower.playerIndex === -1 && towerGridY === 25 && selectedUnits.length === 10) {
tutorialBlockUnitUpdates = false;
console.log("Tuto Step 4.5 : Ok units capturing tower", tutorialBlockUnitUpdates);
LK.setTimeout(function () {
executeTutorialStep(5);
}, 2000);
}
break;
}
}
}
if (targetCell && targetCell.type === 0 || targetHasTower) {
for (var i = 0; i < selectedUnits.length; i++) {
var unit = selectedUnits[i];
if (targetHasTower) {
unit.setTarget(cellX, cellY);
} else {
unit.setTarget(targetCell.x, targetCell.y);
}
}
var message;
if (targetHasTower) {
if (enemyTower.playerIndex === -1) {
message = "Units capturing tower!";
} else {
message = "Units attacking enemy tower!";
}
} else {
message = "Units moving to target";
}
if (!(tutorialActive && tutorialStep === 3 && selectedUnits.length < 10)) {
var notification = game.addChild(new Notification(message));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
}
for (var i = 0; i < selectedUnits.length; i++) {
selectedUnits[i].setSelected(false);
}
selectedUnits = [];
isCommandMode = false;
} else {
var notification = game.addChild(new Notification("Invalid target location"));
notification.x = 2048 / 2;
notification.y = grid.height - 250;
}
return;
}
var clickedOnTower = false;
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var towerLeft = tower.x - tower.width / 2;
var towerRight = tower.x + tower.width / 2;
var towerTop = tower.y - tower.height / 2;
var towerBottom = tower.y + tower.height / 2;
if (x >= towerLeft && x <= towerRight && y >= towerTop && y <= towerBottom) {
clickedOnTower = true;
break;
}
}
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
if (upgradeMenus.length > 0 && !isDragging && !clickedOnTower) {
var clickedOnMenu = false;
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
var menuWidth = 2048;
var menuHeight = 450;
var menuLeft = menu.x - menuWidth / 2;
var menuRight = menu.x + menuWidth / 2;
var menuTop = menu.y - menuHeight / 2;
var menuBottom = menu.y + menuHeight / 2;
if (x >= menuLeft && x <= menuRight && y >= menuTop && y <= menuBottom) {
clickedOnMenu = true;
break;
}
}
if (!clickedOnMenu) {
for (var i = 0; i < upgradeMenus.length; i++) {
var menu = upgradeMenus[i];
hideUpgradeMenu(menu);
}
for (var i = game.children.length - 1; i >= 0; i--) {
if (game.children[i].isTowerRange) {
game.removeChild(game.children[i]);
}
}
selectedTower = null;
grid.renderDebug();
}
}
if (isDragging) {
isDragging = false;
if (towerPreview.canPlace) {
if (!wouldBlockPath(towerPreview.gridX, towerPreview.gridY)) {
placeTower(towerPreview.gridX, towerPreview.gridY, towerPreview.towerType);
} else {
var notification = game.addChild(new Notification("Tower would block the path!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
} else if (towerPreview.blockedByEnemy) {
var notification = game.addChild(new Notification("Cannot build: Enemy in the way!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
} else if (towerPreview.visible) {
var notification = game.addChild(new Notification("Cannot build here!"));
notification.x = 2048 / 2;
notification.y = grid.height - 50;
}
towerPreview.visible = false;
if (isDragging) {
var upgradeMenus = game.children.filter(function (child) {
return child instanceof UpgradeMenu;
});
for (var i = 0; i < upgradeMenus.length; i++) {
upgradeMenus[i].destroy();
}
}
}
};
var waveIndicator = null;
/*
var waveIndicator = new WaveIndicator();
waveIndicator.x = 2048 / 2;
waveIndicator.y = 2732 - 80;
game.addChild(waveIndicator);
*/
var nextWaveButtonContainer = new Container();
var nextWaveButton = new NextWaveButton();
nextWaveButton.x = 2048 - 200;
nextWaveButton.y = 2732 - 100 + 20;
nextWaveButtonContainer.addChild(nextWaveButton);
game.addChild(nextWaveButtonContainer);
sourceTower = null;
enemiesToSpawn = 10;
game.update = function () {
if (isEndGame) {
return;
}
if (aiPlayer) {
aiPlayer.update();
}
if (waveInProgress) {
if (!waveSpawned) {
waveSpawned = true;
var waveType = waveIndicator.getWaveType(currentWave);
var enemyCount = waveIndicator.getEnemyCount(currentWave);
var isBossWave = currentWave % 10 === 0 && currentWave > 0;
if (isBossWave) {
enemyCount = 1;
var notification = game.addChild(new Notification("⚠️ BOSS WAVE! ⚠️"));
notification.x = 2048 / 2;
notification.y = grid.height - 200;
}
for (var i = 0; i < enemyCount; i++) {
var enemy = new Enemy(waveType);
if (enemy.isFlying) {
enemyLayerTop.addChild(enemy);
if (enemy.shadow) {
enemyLayerMiddle.addChild(enemy.shadow);
}
} else {
enemyLayerBottom.addChild(enemy);
}
var healthMultiplier = Math.pow(1.12, currentWave);
enemy.maxHealth = Math.round(enemy.maxHealth * healthMultiplier);
enemy.health = enemy.maxHealth;
var gridWidth = 24;
var midPoint = Math.floor(gridWidth / 2);
var availableColumns = [];
for (var col = midPoint - 3; col < midPoint + 3; col++) {
var columnOccupied = false;
for (var e = 0; e < enemies.length; e++) {
if (enemies[e].cellX === col && enemies[e].currentCellY < 4) {
columnOccupied = true;
break;
}
}
if (!columnOccupied) {
availableColumns.push(col);
}
}
var spawnX;
if (availableColumns.length > 0) {
spawnX = availableColumns[Math.floor(Math.random() * availableColumns.length)];
} else {
spawnX = midPoint - 3 + Math.floor(Math.random() * 6);
}
var spawnY = -1 - Math.random() * 5;
enemy.cellX = spawnX;
enemy.cellY = mapGridOffset;
enemy.currentCellX = spawnX;
enemy.currentCellY = spawnY;
enemy.waveNumber = currentWave;
enemies.push(enemy);
}
}
var currentWaveEnemiesRemaining = false;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].waveNumber === currentWave) {
currentWaveEnemiesRemaining = true;
break;
}
}
if (waveSpawned && !currentWaveEnemiesRemaining) {
waveInProgress = false;
waveSpawned = false;
}
}
for (var a = enemies.length - 1; a >= 0; a--) {
var enemy = enemies[a];
if (enemy.health <= 0) {
for (var i = 0; i < enemy.bulletsTargetingThis.length; i++) {
var bullet = enemy.bulletsTargetingThis[i];
bullet.targetEnemy = null;
}
var goldEarned = enemy.isBoss ? Math.floor(50 + (enemy.waveNumber - 1) * 5) : Math.floor(1 + (enemy.waveNumber - 1) * 0.5);
//var goldIndicator = new GoldIndicator(goldEarned, enemy.x, enemy.y);
//game.addChild(goldIndicator);
setGold(gold + goldEarned);
var scoreValue = enemy.isBoss ? 100 : 5;
score += scoreValue;
if (enemy.isBoss) {
var notification = game.addChild(new Notification("Boss defeated! +" + goldEarned + " gold!"));
notification.x = 2048 / 2;
notification.y = grid.height - 150;
}
updateUI();
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);
}
enemies.splice(a, 1);
continue;
}
if (grid.updateEnemy(enemy)) {
if (enemy.isFlying && enemy.shadow) {
enemyLayerMiddle.removeChild(enemy.shadow);
enemy.shadow = null;
}
if (enemy.isFlying) {
enemyLayerTop.removeChild(enemy);
} else {
enemyLayerBottom.removeChild(enemy);
}
enemies.splice(a, 1);
lives = Math.max(0, lives - 1);
updateUI();
if (lives <= 0) {
LK.showGameOver();
}
}
}
for (var i = bullets.length - 1; i >= 0; i--) {
if (!bullets[i].parent) {
if (bullets[i].targetEnemy) {
var targetEnemy = bullets[i].targetEnemy;
var bulletIndex = targetEnemy.bulletsTargetingThis.indexOf(bullets[i]);
if (bulletIndex !== -1) {
targetEnemy.bulletsTargetingThis.splice(bulletIndex, 1);
}
}
bullets.splice(i, 1);
}
}
if (towerPreview.visible) {
towerPreview.checkPlacement();
}
if (currentWave >= totalWaves && enemies.length === 0 && !waveInProgress) {
LK.showYouWin();
}
};