/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (type, damage, target, isChain) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset(type + 'Bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = damage;
self.target = target;
self.speed = 8;
self.isChain = isChain || false;
self.hasHit = false;
self.update = function () {
if (!self.target || self.hasHit) return;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 30) {
self.hitTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.hitTarget = function () {
if (self.hasHit) return;
self.hasHit = true;
self.target.takeDamage(self.damage);
LK.getSound('enemyHit').play();
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
};
return self;
});
var Dragon = Container.expand(function (type, x, y) {
var self = Container.call(this);
var dragonGraphics = self.attachAsset(type + 'Dragon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
self.isUpgraded = false;
self.lastShotTime = 0;
self.shootInterval = 1500;
if (type === 'fire') {
self.damage = 500;
self.range = 500;
self.cost = 100;
} else if (type === 'ice') {
self.damage = 500;
self.range = 500;
self.cost = 120;
} else if (type === 'lightning') {
self.damage = 400;
self.range = 500;
self.cost = 200;
} else if (type === 'beam') {
self.damage = 10;
self.range = 500;
self.cost = 750;
self.beamTarget = null;
self.beamDamageMultiplier = 1;
self.beamStartTime = 0;
self.shootInterval = 100; // Beam updates faster
}
self.x = x;
self.y = y;
self.update = function () {
var currentTime = Date.now();
if (self.type === 'beam') {
// Handle beam dragon logic
if (self.beamTarget && self.beamTarget.health > 0) {
var dx = self.beamTarget.x - self.x;
var dy = self.beamTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
// Create or update beam visual effect
if (!self.beamLaser) {
self.beamLaser = game.addChild(LK.getAsset('beamLaser', {
anchorX: 0,
anchorY: 0.5
}));
}
// Position and rotate the beam
self.beamLaser.x = self.x;
self.beamLaser.y = self.y;
var angle = Math.atan2(dy, dx);
self.beamLaser.rotation = angle;
self.beamLaser.width = distance;
// Calculate damage based on time
var timeElapsed = (currentTime - self.beamStartTime) / 1000;
var currentDamage;
if (timeElapsed < 1) {
currentDamage = self.isUpgraded ? 15 : 10;
} else if (timeElapsed < 2) {
currentDamage = self.isUpgraded ? 300 : 200;
} else if (timeElapsed < 3) {
currentDamage = self.isUpgraded ? 750 : 500;
} else if (timeElapsed < 4) {
currentDamage = self.isUpgraded ? 1200 : 800;
} else {
currentDamage = self.isUpgraded ? 1500 : 1000;
}
// Apply damage every 0.5 seconds after 3rd second, otherwise every second
var shouldDamage = false;
if (timeElapsed >= 3) {
shouldDamage = Math.floor(timeElapsed * 2) > Math.floor((timeElapsed - 0.017) * 2);
} else {
shouldDamage = Math.floor(timeElapsed) > Math.floor(timeElapsed - 0.017);
}
if (shouldDamage) {
self.beamTarget.takeDamage(currentDamage);
LK.getSound('enemyHit').play();
}
} else {
self.beamTarget = null;
if (self.beamLaser) {
self.beamLaser.destroy();
self.beamLaser = null;
}
}
} else {
self.beamTarget = null;
if (self.beamLaser) {
self.beamLaser.destroy();
self.beamLaser = null;
}
}
if (!self.beamTarget) {
var target = self.findTarget();
if (target) {
self.shoot(target);
}
}
} else {
if (currentTime - self.lastShotTime >= self.shootInterval) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShotTime = currentTime;
}
}
}
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = 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 < closestDistance && distance <= self.range) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.shoot = function (target) {
if (self.type === 'beam') {
// Handle beam dragon shooting
if (!self.beamTarget || self.beamTarget.health <= 0) {
self.beamTarget = target;
self.beamStartTime = Date.now();
self.beamDamageMultiplier = 1;
}
return;
}
LK.getSound('shoot').play();
if (self.type === 'lightning') {
// Create visible lightning bullet
var bullet = new Bullet(self.type, 0, target, false);
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
// New lightning system: single target, double hit
var firstDamage = self.isUpgraded ? 750 : 500;
var secondDamage = self.isUpgraded ? 1250 : 1000;
// Apply first damage immediately
target.takeDamage(firstDamage);
LK.getSound('enemyHit').play();
LK.effects.flashObject(target, 0xffff00, 200);
// Schedule second damage after 1 second with freeze effect
LK.setTimeout(function () {
if (target && target.health > 0) {
target.takeDamage(secondDamage);
LK.getSound('enemyHit').play();
LK.effects.flashObject(target, 0xffff00, 200);
// Freeze target for 0.75 seconds
target.applySlowEffect(0);
target.slowDuration = 45; // 0.75 seconds at 60fps
}
}, 1000);
} else {
var bullet = new Bullet(self.type, self.damage, target, false);
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
}
if (self.type === 'ice') {
target.applySlowEffect(self.isUpgraded ? 0.50 : 0.85);
}
};
self.upgrade = function () {
if (self.isUpgraded) return false;
if (playerGold >= 150) {
playerGold -= 150;
self.isUpgraded = true;
if (self.type === 'fire') {
self.damage = 1000;
tween(dragonGraphics, {
tint: 0xcc0000
}, {
duration: 500
});
} else if (self.type === 'ice') {
self.damage = Math.floor(self.damage * 1.5);
tween(dragonGraphics, {
tint: 0x0000cc
}, {
duration: 500
});
} else if (self.type === 'lightning') {
self.damage = Math.floor(self.damage * 1.5);
tween(dragonGraphics, {
tint: 0xcccc00
}, {
duration: 500
});
} else if (self.type === 'beam') {
self.damage = Math.floor(self.damage * 1.5);
tween(dragonGraphics, {
tint: 0xcc00cc
}, {
duration: 500
});
}
updateGoldDisplay();
return true;
}
return false;
};
self.down = function (x, y, obj) {
if (gameState === 'playing') {
selectedDragon = self;
updateUpgradeButton();
}
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
self.pathIndex = 0;
self.speed = 0.968; // Base speed increased by 10% (0.88 * 1.1)
self.slowEffect = 1;
self.slowDuration = 0;
if (type === 'regularSkeleton') {
self.maxHealth = 500;
self.goldValue = 20;
self.speed = 1.95; // Regular skeleton speed 1.50 * 1.30 = 1.95 meter/second
} else if (type === 'shieldedSkeleton') {
self.maxHealth = 3000; // Increased by 50%
self.goldValue = 25;
self.speed = 1.30; // Shielded skeleton speed 1.0 * 1.30 = 1.30 meter/second
} else if (type === 'fastSkeleton') {
self.maxHealth = 2250; // Increased by 50%
self.goldValue = 30;
self.speed = 3.90; // Fast skeleton speed 3.0 * 1.30 = 3.90 meter/second
} else if (type === 'giantSkeleton') {
self.maxHealth = 20000;
self.goldValue = 100;
self.speed = 1.30; // Giant skeleton speed 1.0 * 1.30 = 1.30 meter/second
} else if (type === 'witchBoss') {
self.maxHealth = 104000;
self.goldValue = 50;
self.speed = 1.30; // Witch boss speed 1.0 * 1.30 = 1.30 meter/second
}
self.health = self.maxHealth;
self.x = pathPoints[0].x;
self.y = pathPoints[0].y;
self.update = function () {
if (self.slowDuration > 0) {
self.slowDuration--;
} else {
self.slowEffect = 1;
}
if (self.pathIndex < pathPoints.length - 1) {
var currentPoint = pathPoints[self.pathIndex];
var nextPoint = pathPoints[self.pathIndex + 1];
var dx = nextPoint.x - self.x;
var dy = nextPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
self.pathIndex++;
} else {
var moveSpeed = self.speed * self.slowEffect;
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
} else {
self.reachEnd();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.applySlowEffect = function (slowAmount) {
self.slowEffect = slowAmount;
self.slowDuration = 180;
};
self.die = function () {
playerGold += self.goldValue;
updateGoldDisplay();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
checkWaveComplete();
};
self.reachEnd = function () {
// Only giant skeletons and witch bosses cause immediate game over
if (self.type === 'giantSkeleton' || self.type === 'witchBoss') {
gameState = 'gameOver';
LK.showGameOver();
} else {
// Regular skeletons reduce lives by 1
playerLives--;
updateLivesDisplay();
if (playerLives <= 0) {
gameState = 'gameOver';
LK.showGameOver();
}
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
var gameState = 'playing';
var playerGold = 100;
var playerLives = 5;
var currentWave = 1;
var waveInProgress = false;
var selectedDragon = null;
var dragons = [];
var enemies = [];
var bullets = [];
var placementZones = [];
var pathPoints = [{
x: 2048,
y: 1500
}, {
x: 1400,
y: 1500
}, {
x: 1000,
y: 1200
}, {
x: 700,
y: 800
}, {
x: 1000,
y: 400
}, {
x: 1400,
y: 700
}, {
x: 1000,
y: 1000
}, {
x: 600,
y: 1300
}, {
x: 0,
y: 1500
}];
var placementPositions = [{
// Near entrance area
x: 1600,
y: 1450
}, {
// Upper right strategic position
x: 1550,
y: 1200
}, {
// Mid-path right side
x: 1250,
y: 1350
}, {
// Lower mid right
x: 950,
y: 1150
}, {
// Center strategic position
x: 750,
y: 950
}, {
// Left side mid
x: 550,
y: 750
}, {
// Upper left strategic
x: 850,
y: 500
}, {
// Far upper position
x: 1200,
y: 200
}, {
// Upper mid strategic
x: 1000,
y: 400
}, {
// Right upper curve
x: 1500,
y: 750
}, {
// Left strategic covering exit
x: 400,
y: 1300
}, {
// Final exit coverage
x: 300,
y: 1050
}];
// Create grass background tiles
for (var gx = 0; gx < 2048; gx += 150) {
for (var gy = 0; gy < 2732; gy += 150) {
var grassTile = game.addChild(LK.getAsset('grass', {
anchorX: 0,
anchorY: 0
}));
grassTile.x = gx;
grassTile.y = gy;
}
}
// Add decorative stones scattered around
var stonePositions = [{
x: 300,
y: 400
}, {
x: 800,
y: 300
}, {
x: 1200,
y: 600
}, {
x: 400,
y: 900
}, {
x: 1600,
y: 800
}, {
x: 900,
y: 1600
}, {
x: 1500,
y: 1800
}, {
x: 200,
y: 1200
}, {
x: 1800,
y: 400
}, {
x: 600,
y: 2000
}, {
x: 1400,
y: 2200
}, {
x: 500,
y: 600
}];
for (var s = 0; s < stonePositions.length; s++) {
var stone = game.addChild(LK.getAsset('stone', {
anchorX: 0.5,
anchorY: 0.5
}));
stone.x = stonePositions[s].x;
stone.y = stonePositions[s].y;
}
// Add decorative flowers
var flowerPositions = [{
x: 250,
y: 500
}, {
x: 750,
y: 200
}, {
x: 1100,
y: 700
}, {
x: 350,
y: 1000
}, {
x: 1550,
y: 900
}, {
x: 850,
y: 1700
}, {
x: 1450,
y: 1900
}, {
x: 150,
y: 1300
}, {
x: 1750,
y: 500
}, {
x: 550,
y: 2100
}, {
x: 1350,
y: 2300
}, {
x: 450,
y: 700
}, {
x: 1050,
y: 1400
}, {
x: 650,
y: 1100
}, {
x: 1250,
y: 300
}];
for (var f = 0; f < flowerPositions.length; f++) {
var flower = game.addChild(LK.getAsset('flower', {
anchorX: 0.5,
anchorY: 0.5
}));
flower.x = flowerPositions[f].x;
flower.y = flowerPositions[f].y;
}
// Create visible forest path following the actual route
for (var p = 0; p < pathPoints.length - 1; p++) {
var currentPoint = pathPoints[p];
var nextPoint = pathPoints[p + 1];
var dx = nextPoint.x - currentPoint.x;
var dy = nextPoint.y - currentPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
var pathSegment = game.addChild(LK.getAsset('forestPath', {
anchorX: 0,
anchorY: 0.5,
scaleX: distance / 100,
scaleY: 1.2
}));
pathSegment.x = currentPoint.x;
pathSegment.y = currentPoint.y;
pathSegment.rotation = angle;
}
// Placement zones removed - dragons can be placed anywhere valid
// Create shop panel
var shopPanel = LK.gui.bottom.addChild(LK.getAsset('shopPanel', {
anchorX: 0.5,
anchorY: 1
}));
// Create dragon buttons
var fireButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -450,
y: -150,
tint: 0xff4444,
scaleX: 1.3,
scaleY: 1.3
}));
var iceButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -250,
y: -150,
tint: 0x4444ff,
scaleX: 1.3,
scaleY: 1.3
}));
var lightningButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -50,
y: -150,
tint: 0xffff44,
scaleX: 1.3,
scaleY: 1.3
}));
var beamButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 150,
y: -150,
tint: 0xff44ff,
scaleX: 1.3,
scaleY: 1.3
}));
var upgradeButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 350,
y: -150,
tint: 0x00ff00,
scaleX: 1.3,
scaleY: 1.3
}));
var startWaveButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 550,
y: -150,
tint: 0xff8800,
scaleX: 1.3,
scaleY: 1.3
}));
// Create UI text
var goldText = LK.gui.topRight.addChild(new Text2('Gold: 100', {
size: 60,
fill: 0xFFFF00
}));
goldText.anchor.set(1, 0);
var livesText = LK.gui.topRight.addChild(new Text2('Lives: 5', {
size: 60,
fill: 0xFF0000
}));
livesText.anchor.set(1, 0);
livesText.y = 80;
var waveText = LK.gui.top.addChild(new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
}));
waveText.anchor.set(0.5, 0);
var fireButtonText = LK.gui.bottom.addChild(new Text2('Fire\n100g', {
size: 40,
fill: 0xFFFFFF
}));
fireButtonText.anchor.set(0.5, 0.5);
fireButtonText.x = -450;
fireButtonText.y = -150;
var iceButtonText = LK.gui.bottom.addChild(new Text2('Ice\n120g', {
size: 40,
fill: 0xFFFFFF
}));
iceButtonText.anchor.set(0.5, 0.5);
iceButtonText.x = -250;
iceButtonText.y = -150;
var lightningButtonText = LK.gui.bottom.addChild(new Text2('Lightning\n200g', {
size: 40,
fill: 0xFFFFFF
}));
lightningButtonText.anchor.set(0.5, 0.5);
lightningButtonText.x = -50;
lightningButtonText.y = -150;
var beamButtonText = LK.gui.bottom.addChild(new Text2('Beam\n750g', {
size: 40,
fill: 0xFFFFFF
}));
beamButtonText.anchor.set(0.5, 0.5);
beamButtonText.x = 150;
beamButtonText.y = -150;
var upgradeButtonText = LK.gui.bottom.addChild(new Text2('Upgrade\nSelect', {
size: 40,
fill: 0xFFFFFF
}));
upgradeButtonText.anchor.set(0.5, 0.5);
upgradeButtonText.x = 350;
upgradeButtonText.y = -150;
var startWaveButtonText = LK.gui.bottom.addChild(new Text2('Start\nWave', {
size: 40,
fill: 0xFFFFFF
}));
startWaveButtonText.anchor.set(0.5, 0.5);
startWaveButtonText.x = 550;
startWaveButtonText.y = -150;
var selectedDragonType = null;
var waveSpawnIndex = 0;
var waveSpawnTimer = 0;
function updateGoldDisplay() {
goldText.setText('Gold: ' + playerGold);
}
function updateLivesDisplay() {
livesText.setText('Lives: ' + playerLives);
}
function updateWaveDisplay() {
waveText.setText('Wave: ' + currentWave);
}
function updateUpgradeButton() {
if (selectedDragon && !selectedDragon.isUpgraded) {
upgradeButtonText.setText('Upgrade\n150g');
} else {
upgradeButtonText.setText('Upgrade\nSelect');
}
}
function placeDragon(type, x, y, zoneIndex) {
var cost;
if (type === 'fire') cost = 100;else if (type === 'ice') cost = 120;else if (type === 'lightning') cost = 200;else if (type === 'beam') cost = 750;
if (playerGold >= cost) {
playerGold -= cost;
updateGoldDisplay();
var dragon = game.addChild(new Dragon(type, x, y));
dragons.push(dragon);
selectedDragonType = null;
updateButtonHighlights();
}
}
function updateButtonHighlights() {
fireButton.tint = selectedDragonType === 'fire' ? 0xffaaaa : 0xff4444;
iceButton.tint = selectedDragonType === 'ice' ? 0xaaaaff : 0x4444ff;
lightningButton.tint = selectedDragonType === 'lightning' ? 0xffffaa : 0xffff44;
beamButton.tint = selectedDragonType === 'beam' ? 0xffaaff : 0xff44ff;
}
function startWave() {
if (waveInProgress) return;
waveInProgress = true;
waveSpawnIndex = 0;
waveSpawnTimer = 0;
selectedDragon = null;
updateUpgradeButton();
}
function spawnEnemy() {
var enemyType;
var waveSize;
if (currentWave === 1) {
enemyType = 'regularSkeleton';
waveSize = 15;
} else if (currentWave === 2) {
enemyType = 'shieldedSkeleton';
waveSize = 15;
} else if (currentWave === 3) {
enemyType = 'fastSkeleton';
waveSize = 15;
} else if (currentWave === 4) {
if (waveSpawnIndex < 10) {
enemyType = 'regularSkeleton';
} else if (waveSpawnIndex < 20) {
enemyType = 'shieldedSkeleton';
} else {
enemyType = 'fastSkeleton';
}
waveSize = 30;
} else if (currentWave === 5) {
if (waveSpawnIndex < 2) {
enemyType = 'giantSkeleton';
} else {
enemyType = 'witchBoss';
}
waveSize = 3;
}
if (waveSpawnIndex < waveSize) {
var enemy = game.addChild(new Enemy(enemyType));
enemies.push(enemy);
waveSpawnIndex++;
}
}
function checkWaveComplete() {
if (waveInProgress && enemies.length === 0) {
// Only complete wave if all enemies have been spawned
var expectedWaveSize;
if (currentWave === 1 || currentWave === 2 || currentWave === 3) {
expectedWaveSize = 15;
} else if (currentWave === 4) {
expectedWaveSize = 30;
} else if (currentWave === 5) {
expectedWaveSize = 3;
}
if (waveSpawnIndex >= expectedWaveSize) {
waveInProgress = false;
if (currentWave >= 5) {
gameState = 'victory';
LK.showYouWin();
} else {
currentWave++;
updateWaveDisplay();
}
}
}
}
// Helper function to check if position is on path
function isOnPath(x, y) {
for (var i = 0; i < pathPoints.length - 1; i++) {
var currentPoint = pathPoints[i];
var nextPoint = pathPoints[i + 1];
var dx = nextPoint.x - currentPoint.x;
var dy = nextPoint.y - currentPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate distance from point to line segment
var A = x - currentPoint.x;
var B = y - currentPoint.y;
var C = dx;
var D = dy;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
var param = -1;
if (lenSq !== 0) {
param = dot / lenSq;
}
var xx, yy;
if (param < 0) {
xx = currentPoint.x;
yy = currentPoint.y;
} else if (param > 1) {
xx = nextPoint.x;
yy = nextPoint.y;
} else {
xx = currentPoint.x + param * C;
yy = currentPoint.y + param * D;
}
var distToPath = Math.sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy));
if (distToPath < 80) {
// Path thickness consideration
return true;
}
}
return false;
}
// Game click handler for dragon placement
game.down = function (x, y, obj) {
if (gameState === 'playing' && selectedDragonType) {
// Check if position is valid (not on path and no existing dragon)
if (!isOnPath(x, y)) {
var hasExistingDragon = false;
for (var i = 0; i < dragons.length; i++) {
if (Math.abs(dragons[i].x - x) < 100 && Math.abs(dragons[i].y - y) < 100) {
hasExistingDragon = true;
break;
}
}
if (!hasExistingDragon) {
placeDragon(selectedDragonType, x, y, -1);
}
}
}
};
// Button event handlers
fireButton.down = function () {
if (gameState === 'playing' && playerGold >= 100) {
selectedDragonType = selectedDragonType === 'fire' ? null : 'fire';
updateButtonHighlights();
}
};
iceButton.down = function () {
if (gameState === 'playing' && playerGold >= 120) {
selectedDragonType = selectedDragonType === 'ice' ? null : 'ice';
updateButtonHighlights();
}
};
lightningButton.down = function () {
if (gameState === 'playing' && playerGold >= 200) {
selectedDragonType = selectedDragonType === 'lightning' ? null : 'lightning';
updateButtonHighlights();
}
};
beamButton.down = function () {
if (gameState === 'playing' && playerGold >= 750) {
selectedDragonType = selectedDragonType === 'beam' ? null : 'beam';
updateButtonHighlights();
}
};
upgradeButton.down = function () {
if (gameState === 'playing' && selectedDragon) {
selectedDragon.upgrade();
updateUpgradeButton();
}
};
startWaveButton.down = function () {
if (gameState === 'playing' && !waveInProgress) {
startWave();
}
};
game.update = function () {
if (gameState !== 'playing') return;
// Update dragons
for (var i = 0; i < dragons.length; i++) {
dragons[i].update();
}
// Update enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].update();
}
// Spawn enemies during wave
if (waveInProgress) {
waveSpawnTimer++;
var spawnInterval = currentWave === 1 ? 24 : 60; // Half the spacing: 48/2 = 24, 120/2 = 60
if (waveSpawnTimer >= spawnInterval) {
spawnEnemy();
waveSpawnTimer = 0;
}
}
// Check if wave is complete - only after all enemies are spawned
var expectedWaveSize;
if (currentWave === 1 || currentWave === 2 || currentWave === 3) {
expectedWaveSize = 15;
} else if (currentWave === 4) {
expectedWaveSize = 30;
} else if (currentWave === 5) {
expectedWaveSize = 3;
}
if (waveInProgress && waveSpawnIndex >= expectedWaveSize) {
if (enemies.length === 0) {
checkWaveComplete();
}
}
}; /****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var Bullet = Container.expand(function (type, damage, target, isChain) {
var self = Container.call(this);
var bulletGraphics = self.attachAsset(type + 'Bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.damage = damage;
self.target = target;
self.speed = 8;
self.isChain = isChain || false;
self.hasHit = false;
self.update = function () {
if (!self.target || self.hasHit) return;
var dx = self.target.x - self.x;
var dy = self.target.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 30) {
self.hitTarget();
} else {
self.x += dx / distance * self.speed;
self.y += dy / distance * self.speed;
}
};
self.hitTarget = function () {
if (self.hasHit) return;
self.hasHit = true;
self.target.takeDamage(self.damage);
LK.getSound('enemyHit').play();
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
};
return self;
});
var Dragon = Container.expand(function (type, x, y) {
var self = Container.call(this);
var dragonGraphics = self.attachAsset(type + 'Dragon', {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
self.isUpgraded = false;
self.lastShotTime = 0;
self.shootInterval = 1500;
if (type === 'fire') {
self.damage = 500;
self.range = 500;
self.cost = 100;
} else if (type === 'ice') {
self.damage = 500;
self.range = 500;
self.cost = 120;
} else if (type === 'lightning') {
self.damage = 400;
self.range = 500;
self.cost = 200;
} else if (type === 'beam') {
self.damage = 10;
self.range = 500;
self.cost = 750;
self.beamTarget = null;
self.beamDamageMultiplier = 1;
self.beamStartTime = 0;
self.shootInterval = 100; // Beam updates faster
}
self.x = x;
self.y = y;
self.update = function () {
var currentTime = Date.now();
if (self.type === 'beam') {
// Handle beam dragon logic
if (self.beamTarget && self.beamTarget.health > 0) {
var dx = self.beamTarget.x - self.x;
var dy = self.beamTarget.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.range) {
// Create or update beam visual effect
if (!self.beamLaser) {
self.beamLaser = game.addChild(LK.getAsset('beamLaser', {
anchorX: 0,
anchorY: 0.5
}));
}
// Position and rotate the beam
self.beamLaser.x = self.x;
self.beamLaser.y = self.y;
var angle = Math.atan2(dy, dx);
self.beamLaser.rotation = angle;
self.beamLaser.width = distance;
// Calculate damage based on time
var timeElapsed = (currentTime - self.beamStartTime) / 1000;
var currentDamage;
if (timeElapsed < 1) {
currentDamage = self.isUpgraded ? 15 : 10;
} else if (timeElapsed < 2) {
currentDamage = self.isUpgraded ? 300 : 200;
} else if (timeElapsed < 3) {
currentDamage = self.isUpgraded ? 750 : 500;
} else if (timeElapsed < 4) {
currentDamage = self.isUpgraded ? 1200 : 800;
} else {
currentDamage = self.isUpgraded ? 1500 : 1000;
}
// Apply damage every 0.5 seconds after 3rd second, otherwise every second
var shouldDamage = false;
if (timeElapsed >= 3) {
shouldDamage = Math.floor(timeElapsed * 2) > Math.floor((timeElapsed - 0.017) * 2);
} else {
shouldDamage = Math.floor(timeElapsed) > Math.floor(timeElapsed - 0.017);
}
if (shouldDamage) {
self.beamTarget.takeDamage(currentDamage);
LK.getSound('enemyHit').play();
}
} else {
self.beamTarget = null;
if (self.beamLaser) {
self.beamLaser.destroy();
self.beamLaser = null;
}
}
} else {
self.beamTarget = null;
if (self.beamLaser) {
self.beamLaser.destroy();
self.beamLaser = null;
}
}
if (!self.beamTarget) {
var target = self.findTarget();
if (target) {
self.shoot(target);
}
}
} else {
if (currentTime - self.lastShotTime >= self.shootInterval) {
var target = self.findTarget();
if (target) {
self.shoot(target);
self.lastShotTime = currentTime;
}
}
}
};
self.findTarget = function () {
var closestEnemy = null;
var closestDistance = 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 < closestDistance && distance <= self.range) {
closestDistance = distance;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.shoot = function (target) {
if (self.type === 'beam') {
// Handle beam dragon shooting
if (!self.beamTarget || self.beamTarget.health <= 0) {
self.beamTarget = target;
self.beamStartTime = Date.now();
self.beamDamageMultiplier = 1;
}
return;
}
LK.getSound('shoot').play();
if (self.type === 'lightning') {
// Create visible lightning bullet
var bullet = new Bullet(self.type, 0, target, false);
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
// New lightning system: single target, double hit
var firstDamage = self.isUpgraded ? 750 : 500;
var secondDamage = self.isUpgraded ? 1250 : 1000;
// Apply first damage immediately
target.takeDamage(firstDamage);
LK.getSound('enemyHit').play();
LK.effects.flashObject(target, 0xffff00, 200);
// Schedule second damage after 1 second with freeze effect
LK.setTimeout(function () {
if (target && target.health > 0) {
target.takeDamage(secondDamage);
LK.getSound('enemyHit').play();
LK.effects.flashObject(target, 0xffff00, 200);
// Freeze target for 0.75 seconds
target.applySlowEffect(0);
target.slowDuration = 45; // 0.75 seconds at 60fps
}
}, 1000);
} else {
var bullet = new Bullet(self.type, self.damage, target, false);
bullet.x = self.x;
bullet.y = self.y;
bullets.push(bullet);
game.addChild(bullet);
}
if (self.type === 'ice') {
target.applySlowEffect(self.isUpgraded ? 0.50 : 0.85);
}
};
self.upgrade = function () {
if (self.isUpgraded) return false;
if (playerGold >= 150) {
playerGold -= 150;
self.isUpgraded = true;
if (self.type === 'fire') {
self.damage = 1000;
tween(dragonGraphics, {
tint: 0xcc0000
}, {
duration: 500
});
} else if (self.type === 'ice') {
self.damage = Math.floor(self.damage * 1.5);
tween(dragonGraphics, {
tint: 0x0000cc
}, {
duration: 500
});
} else if (self.type === 'lightning') {
self.damage = Math.floor(self.damage * 1.5);
tween(dragonGraphics, {
tint: 0xcccc00
}, {
duration: 500
});
} else if (self.type === 'beam') {
self.damage = Math.floor(self.damage * 1.5);
tween(dragonGraphics, {
tint: 0xcc00cc
}, {
duration: 500
});
}
updateGoldDisplay();
return true;
}
return false;
};
self.down = function (x, y, obj) {
if (gameState === 'playing') {
selectedDragon = self;
updateUpgradeButton();
}
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
var enemyGraphics = self.attachAsset(type, {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type;
self.pathIndex = 0;
self.speed = 0.968; // Base speed increased by 10% (0.88 * 1.1)
self.slowEffect = 1;
self.slowDuration = 0;
if (type === 'regularSkeleton') {
self.maxHealth = 500;
self.goldValue = 20;
self.speed = 1.95; // Regular skeleton speed 1.50 * 1.30 = 1.95 meter/second
} else if (type === 'shieldedSkeleton') {
self.maxHealth = 3000; // Increased by 50%
self.goldValue = 25;
self.speed = 1.30; // Shielded skeleton speed 1.0 * 1.30 = 1.30 meter/second
} else if (type === 'fastSkeleton') {
self.maxHealth = 2250; // Increased by 50%
self.goldValue = 30;
self.speed = 3.90; // Fast skeleton speed 3.0 * 1.30 = 3.90 meter/second
} else if (type === 'giantSkeleton') {
self.maxHealth = 20000;
self.goldValue = 100;
self.speed = 1.30; // Giant skeleton speed 1.0 * 1.30 = 1.30 meter/second
} else if (type === 'witchBoss') {
self.maxHealth = 104000;
self.goldValue = 50;
self.speed = 1.30; // Witch boss speed 1.0 * 1.30 = 1.30 meter/second
}
self.health = self.maxHealth;
self.x = pathPoints[0].x;
self.y = pathPoints[0].y;
self.update = function () {
if (self.slowDuration > 0) {
self.slowDuration--;
} else {
self.slowEffect = 1;
}
if (self.pathIndex < pathPoints.length - 1) {
var currentPoint = pathPoints[self.pathIndex];
var nextPoint = pathPoints[self.pathIndex + 1];
var dx = nextPoint.x - self.x;
var dy = nextPoint.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 10) {
self.pathIndex++;
} else {
var moveSpeed = self.speed * self.slowEffect;
self.x += dx / distance * moveSpeed;
self.y += dy / distance * moveSpeed;
}
} else {
self.reachEnd();
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.applySlowEffect = function (slowAmount) {
self.slowEffect = slowAmount;
self.slowDuration = 180;
};
self.die = function () {
playerGold += self.goldValue;
updateGoldDisplay();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
checkWaveComplete();
};
self.reachEnd = function () {
// Only giant skeletons and witch bosses cause immediate game over
if (self.type === 'giantSkeleton' || self.type === 'witchBoss') {
gameState = 'gameOver';
LK.showGameOver();
} else {
// Regular skeletons reduce lives by 1
playerLives--;
updateLivesDisplay();
if (playerLives <= 0) {
gameState = 'gameOver';
LK.showGameOver();
}
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x228B22
});
/****
* Game Code
****/
var gameState = 'playing';
var playerGold = 100;
var playerLives = 5;
var currentWave = 1;
var waveInProgress = false;
var selectedDragon = null;
var dragons = [];
var enemies = [];
var bullets = [];
var placementZones = [];
var pathPoints = [{
x: 2048,
y: 1500
}, {
x: 1400,
y: 1500
}, {
x: 1000,
y: 1200
}, {
x: 700,
y: 800
}, {
x: 1000,
y: 400
}, {
x: 1400,
y: 700
}, {
x: 1000,
y: 1000
}, {
x: 600,
y: 1300
}, {
x: 0,
y: 1500
}];
var placementPositions = [{
// Near entrance area
x: 1600,
y: 1450
}, {
// Upper right strategic position
x: 1550,
y: 1200
}, {
// Mid-path right side
x: 1250,
y: 1350
}, {
// Lower mid right
x: 950,
y: 1150
}, {
// Center strategic position
x: 750,
y: 950
}, {
// Left side mid
x: 550,
y: 750
}, {
// Upper left strategic
x: 850,
y: 500
}, {
// Far upper position
x: 1200,
y: 200
}, {
// Upper mid strategic
x: 1000,
y: 400
}, {
// Right upper curve
x: 1500,
y: 750
}, {
// Left strategic covering exit
x: 400,
y: 1300
}, {
// Final exit coverage
x: 300,
y: 1050
}];
// Create grass background tiles
for (var gx = 0; gx < 2048; gx += 150) {
for (var gy = 0; gy < 2732; gy += 150) {
var grassTile = game.addChild(LK.getAsset('grass', {
anchorX: 0,
anchorY: 0
}));
grassTile.x = gx;
grassTile.y = gy;
}
}
// Add decorative stones scattered around
var stonePositions = [{
x: 300,
y: 400
}, {
x: 800,
y: 300
}, {
x: 1200,
y: 600
}, {
x: 400,
y: 900
}, {
x: 1600,
y: 800
}, {
x: 900,
y: 1600
}, {
x: 1500,
y: 1800
}, {
x: 200,
y: 1200
}, {
x: 1800,
y: 400
}, {
x: 600,
y: 2000
}, {
x: 1400,
y: 2200
}, {
x: 500,
y: 600
}];
for (var s = 0; s < stonePositions.length; s++) {
var stone = game.addChild(LK.getAsset('stone', {
anchorX: 0.5,
anchorY: 0.5
}));
stone.x = stonePositions[s].x;
stone.y = stonePositions[s].y;
}
// Add decorative flowers
var flowerPositions = [{
x: 250,
y: 500
}, {
x: 750,
y: 200
}, {
x: 1100,
y: 700
}, {
x: 350,
y: 1000
}, {
x: 1550,
y: 900
}, {
x: 850,
y: 1700
}, {
x: 1450,
y: 1900
}, {
x: 150,
y: 1300
}, {
x: 1750,
y: 500
}, {
x: 550,
y: 2100
}, {
x: 1350,
y: 2300
}, {
x: 450,
y: 700
}, {
x: 1050,
y: 1400
}, {
x: 650,
y: 1100
}, {
x: 1250,
y: 300
}];
for (var f = 0; f < flowerPositions.length; f++) {
var flower = game.addChild(LK.getAsset('flower', {
anchorX: 0.5,
anchorY: 0.5
}));
flower.x = flowerPositions[f].x;
flower.y = flowerPositions[f].y;
}
// Create visible forest path following the actual route
for (var p = 0; p < pathPoints.length - 1; p++) {
var currentPoint = pathPoints[p];
var nextPoint = pathPoints[p + 1];
var dx = nextPoint.x - currentPoint.x;
var dy = nextPoint.y - currentPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
var angle = Math.atan2(dy, dx);
var pathSegment = game.addChild(LK.getAsset('forestPath', {
anchorX: 0,
anchorY: 0.5,
scaleX: distance / 100,
scaleY: 1.2
}));
pathSegment.x = currentPoint.x;
pathSegment.y = currentPoint.y;
pathSegment.rotation = angle;
}
// Placement zones removed - dragons can be placed anywhere valid
// Create shop panel
var shopPanel = LK.gui.bottom.addChild(LK.getAsset('shopPanel', {
anchorX: 0.5,
anchorY: 1
}));
// Create dragon buttons
var fireButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -450,
y: -150,
tint: 0xff4444,
scaleX: 1.3,
scaleY: 1.3
}));
var iceButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -250,
y: -150,
tint: 0x4444ff,
scaleX: 1.3,
scaleY: 1.3
}));
var lightningButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: -50,
y: -150,
tint: 0xffff44,
scaleX: 1.3,
scaleY: 1.3
}));
var beamButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 150,
y: -150,
tint: 0xff44ff,
scaleX: 1.3,
scaleY: 1.3
}));
var upgradeButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 350,
y: -150,
tint: 0x00ff00,
scaleX: 1.3,
scaleY: 1.3
}));
var startWaveButton = shopPanel.addChild(LK.getAsset('dragonButton', {
anchorX: 0.5,
anchorY: 0.5,
x: 550,
y: -150,
tint: 0xff8800,
scaleX: 1.3,
scaleY: 1.3
}));
// Create UI text
var goldText = LK.gui.topRight.addChild(new Text2('Gold: 100', {
size: 60,
fill: 0xFFFF00
}));
goldText.anchor.set(1, 0);
var livesText = LK.gui.topRight.addChild(new Text2('Lives: 5', {
size: 60,
fill: 0xFF0000
}));
livesText.anchor.set(1, 0);
livesText.y = 80;
var waveText = LK.gui.top.addChild(new Text2('Wave: 1', {
size: 60,
fill: 0xFFFFFF
}));
waveText.anchor.set(0.5, 0);
var fireButtonText = LK.gui.bottom.addChild(new Text2('Fire\n100g', {
size: 40,
fill: 0xFFFFFF
}));
fireButtonText.anchor.set(0.5, 0.5);
fireButtonText.x = -450;
fireButtonText.y = -150;
var iceButtonText = LK.gui.bottom.addChild(new Text2('Ice\n120g', {
size: 40,
fill: 0xFFFFFF
}));
iceButtonText.anchor.set(0.5, 0.5);
iceButtonText.x = -250;
iceButtonText.y = -150;
var lightningButtonText = LK.gui.bottom.addChild(new Text2('Lightning\n200g', {
size: 40,
fill: 0xFFFFFF
}));
lightningButtonText.anchor.set(0.5, 0.5);
lightningButtonText.x = -50;
lightningButtonText.y = -150;
var beamButtonText = LK.gui.bottom.addChild(new Text2('Beam\n750g', {
size: 40,
fill: 0xFFFFFF
}));
beamButtonText.anchor.set(0.5, 0.5);
beamButtonText.x = 150;
beamButtonText.y = -150;
var upgradeButtonText = LK.gui.bottom.addChild(new Text2('Upgrade\nSelect', {
size: 40,
fill: 0xFFFFFF
}));
upgradeButtonText.anchor.set(0.5, 0.5);
upgradeButtonText.x = 350;
upgradeButtonText.y = -150;
var startWaveButtonText = LK.gui.bottom.addChild(new Text2('Start\nWave', {
size: 40,
fill: 0xFFFFFF
}));
startWaveButtonText.anchor.set(0.5, 0.5);
startWaveButtonText.x = 550;
startWaveButtonText.y = -150;
var selectedDragonType = null;
var waveSpawnIndex = 0;
var waveSpawnTimer = 0;
function updateGoldDisplay() {
goldText.setText('Gold: ' + playerGold);
}
function updateLivesDisplay() {
livesText.setText('Lives: ' + playerLives);
}
function updateWaveDisplay() {
waveText.setText('Wave: ' + currentWave);
}
function updateUpgradeButton() {
if (selectedDragon && !selectedDragon.isUpgraded) {
upgradeButtonText.setText('Upgrade\n150g');
} else {
upgradeButtonText.setText('Upgrade\nSelect');
}
}
function placeDragon(type, x, y, zoneIndex) {
var cost;
if (type === 'fire') cost = 100;else if (type === 'ice') cost = 120;else if (type === 'lightning') cost = 200;else if (type === 'beam') cost = 750;
if (playerGold >= cost) {
playerGold -= cost;
updateGoldDisplay();
var dragon = game.addChild(new Dragon(type, x, y));
dragons.push(dragon);
selectedDragonType = null;
updateButtonHighlights();
}
}
function updateButtonHighlights() {
fireButton.tint = selectedDragonType === 'fire' ? 0xffaaaa : 0xff4444;
iceButton.tint = selectedDragonType === 'ice' ? 0xaaaaff : 0x4444ff;
lightningButton.tint = selectedDragonType === 'lightning' ? 0xffffaa : 0xffff44;
beamButton.tint = selectedDragonType === 'beam' ? 0xffaaff : 0xff44ff;
}
function startWave() {
if (waveInProgress) return;
waveInProgress = true;
waveSpawnIndex = 0;
waveSpawnTimer = 0;
selectedDragon = null;
updateUpgradeButton();
}
function spawnEnemy() {
var enemyType;
var waveSize;
if (currentWave === 1) {
enemyType = 'regularSkeleton';
waveSize = 15;
} else if (currentWave === 2) {
enemyType = 'shieldedSkeleton';
waveSize = 15;
} else if (currentWave === 3) {
enemyType = 'fastSkeleton';
waveSize = 15;
} else if (currentWave === 4) {
if (waveSpawnIndex < 10) {
enemyType = 'regularSkeleton';
} else if (waveSpawnIndex < 20) {
enemyType = 'shieldedSkeleton';
} else {
enemyType = 'fastSkeleton';
}
waveSize = 30;
} else if (currentWave === 5) {
if (waveSpawnIndex < 2) {
enemyType = 'giantSkeleton';
} else {
enemyType = 'witchBoss';
}
waveSize = 3;
}
if (waveSpawnIndex < waveSize) {
var enemy = game.addChild(new Enemy(enemyType));
enemies.push(enemy);
waveSpawnIndex++;
}
}
function checkWaveComplete() {
if (waveInProgress && enemies.length === 0) {
// Only complete wave if all enemies have been spawned
var expectedWaveSize;
if (currentWave === 1 || currentWave === 2 || currentWave === 3) {
expectedWaveSize = 15;
} else if (currentWave === 4) {
expectedWaveSize = 30;
} else if (currentWave === 5) {
expectedWaveSize = 3;
}
if (waveSpawnIndex >= expectedWaveSize) {
waveInProgress = false;
if (currentWave >= 5) {
gameState = 'victory';
LK.showYouWin();
} else {
currentWave++;
updateWaveDisplay();
}
}
}
}
// Helper function to check if position is on path
function isOnPath(x, y) {
for (var i = 0; i < pathPoints.length - 1; i++) {
var currentPoint = pathPoints[i];
var nextPoint = pathPoints[i + 1];
var dx = nextPoint.x - currentPoint.x;
var dy = nextPoint.y - currentPoint.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate distance from point to line segment
var A = x - currentPoint.x;
var B = y - currentPoint.y;
var C = dx;
var D = dy;
var dot = A * C + B * D;
var lenSq = C * C + D * D;
var param = -1;
if (lenSq !== 0) {
param = dot / lenSq;
}
var xx, yy;
if (param < 0) {
xx = currentPoint.x;
yy = currentPoint.y;
} else if (param > 1) {
xx = nextPoint.x;
yy = nextPoint.y;
} else {
xx = currentPoint.x + param * C;
yy = currentPoint.y + param * D;
}
var distToPath = Math.sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy));
if (distToPath < 80) {
// Path thickness consideration
return true;
}
}
return false;
}
// Game click handler for dragon placement
game.down = function (x, y, obj) {
if (gameState === 'playing' && selectedDragonType) {
// Check if position is valid (not on path and no existing dragon)
if (!isOnPath(x, y)) {
var hasExistingDragon = false;
for (var i = 0; i < dragons.length; i++) {
if (Math.abs(dragons[i].x - x) < 100 && Math.abs(dragons[i].y - y) < 100) {
hasExistingDragon = true;
break;
}
}
if (!hasExistingDragon) {
placeDragon(selectedDragonType, x, y, -1);
}
}
}
};
// Button event handlers
fireButton.down = function () {
if (gameState === 'playing' && playerGold >= 100) {
selectedDragonType = selectedDragonType === 'fire' ? null : 'fire';
updateButtonHighlights();
}
};
iceButton.down = function () {
if (gameState === 'playing' && playerGold >= 120) {
selectedDragonType = selectedDragonType === 'ice' ? null : 'ice';
updateButtonHighlights();
}
};
lightningButton.down = function () {
if (gameState === 'playing' && playerGold >= 200) {
selectedDragonType = selectedDragonType === 'lightning' ? null : 'lightning';
updateButtonHighlights();
}
};
beamButton.down = function () {
if (gameState === 'playing' && playerGold >= 750) {
selectedDragonType = selectedDragonType === 'beam' ? null : 'beam';
updateButtonHighlights();
}
};
upgradeButton.down = function () {
if (gameState === 'playing' && selectedDragon) {
selectedDragon.upgrade();
updateUpgradeButton();
}
};
startWaveButton.down = function () {
if (gameState === 'playing' && !waveInProgress) {
startWave();
}
};
game.update = function () {
if (gameState !== 'playing') return;
// Update dragons
for (var i = 0; i < dragons.length; i++) {
dragons[i].update();
}
// Update enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].update();
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
bullets[i].update();
}
// Spawn enemies during wave
if (waveInProgress) {
waveSpawnTimer++;
var spawnInterval = currentWave === 1 ? 24 : 60; // Half the spacing: 48/2 = 24, 120/2 = 60
if (waveSpawnTimer >= spawnInterval) {
spawnEnemy();
waveSpawnTimer = 0;
}
}
// Check if wave is complete - only after all enemies are spawned
var expectedWaveSize;
if (currentWave === 1 || currentWave === 2 || currentWave === 3) {
expectedWaveSize = 15;
} else if (currentWave === 4) {
expectedWaveSize = 30;
} else if (currentWave === 5) {
expectedWaveSize = 3;
}
if (waveInProgress && waveSpawnIndex >= expectedWaveSize) {
if (enemies.length === 0) {
checkWaveComplete();
}
}
};
basic red fire dragon. 3d. In-Game asset. 2d. High contrast. No shadows
şimşek. In-Game asset. 2d. High contrast. No shadows
skeleton. In-Game asset. 2d. High contrast. No shadows
kalkanlı zırhlı iskelet. In-Game asset. 2d. High contrast. No shadows
cadı boss. In-Game asset. 2d. High contrast. No shadows
şimşek fırlatan bir elektrik ejderhası. In-Game asset. 2d. High contrast. No shadows şimşekleri sil
bu buz fırlatan bir buz ejderhası soğukları iliklerimize kadar hissetiriyor ejderha çizimi buz içermesin. In-Game asset. 2d. High contrast. No shadows bu çizimde ejderhanın ağzı açık olsun ama bir şey çıkmasın
bu bir buz parçası. uzun sivri bir koni gibi. In-Game asset. 2d. High contrast. No shadows
yuvarlak alev topu. In-Game asset. 2d. High contrast. No shadows
dümdüz toprak bir yol taş süslemeleri. In-Game asset. 2d. High contrast. No shadows
papatya. In-Game asset. 2d. High contrast. No shadows
üstten bakılan kaya parçaları. In-Game asset. 2d. High contrast. No shadows
çizimler iyi . ağzı açık olsun ama lazer olmasın. In-Game asset. 2d. High contrast. No shadows