User prompt
Create a separate attack visual effect for the archer tower that is different from the main building’s attack effect. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Increase archer tower attack range from 540 to 380
User prompt
Increase the archer towers’ attack range by 3 times.
User prompt
Implement an upgrade feature that increases the main building’s attack range. The first use costs 5,000 gold. Once used, the upgrade button’s cost updates to 25,000 gold for the second use. The current price is clearly displayed on the button and updates dynamically after each purchase to reflect the new cost requirement. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Enemies in the game prioritize attacking the "glm" asset when they are nearby. Upon detecting proximity to "glm," enemies shift their focus from other targets and aggressively move toward and attack "glm." This behavior adds a strategic element where protecting or utilizing "glm" influences enemy movement and combat dynamics. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In the game, the "glm" asset appears briefly from a random spot on the screen at the start of each wave, moving smoothly onto the screen, then retreating back off after a short duration. The animation is quick and fluid, creating a dynamic visual effect that signals the beginning of a new wave without obstructing gameplay. The movement path varies slightly each time to add visual interest. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Replace all buttons in the game UI with stylized 2D game interface elements. Each button should have a cohesive design featuring vibrant colors, smooth gradients, and subtle shadows for depth. The style is cartoonish yet polished, with rounded edges and clear icons representing their functions. Buttons should include hover and click animations for interactivity, fitting well within a fantasy or adventure game aesthetic. The overall UI should feel modern, user-friendly, and visually appealing. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
A stylized 2D game UI button representing a health bar, featuring vibrant colors with a glossy, slightly rounded rectangular shape. The bar is partially filled with a gradient from green to red indicating health levels. It has subtle shadows and highlights for depth, with a clean and modern look suitable for fantasy or adventure games. The button includes a small heart icon on the left side and a smooth animation effect when health changes. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Create a button above the HP regeneration button, with some spacing between them, priced at 5,000 gold. When clicked, for 5,000 gold, the main building’s attack range and its circle should double in size. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Move it even higher up. HP regeneration
User prompt
Move the health regeneration button to the bottom left of the screen, above the reset button.
User prompt
Move up the health regeneration button to the bottom left of the screen, above the reset button.
User prompt
Move the health regeneration button to the bottom left of the screen, above the reset button.
User prompt
"It works on left-click, change it to right-click instead."
User prompt
"This doesn't work."
User prompt
"Set the gold amount to 10,000 when the player right-clicks on the gold text in the top-right corner of the screen."
User prompt
Add a feature that grants 10,000 gold when the middle mouse button is double-clicked.
User prompt
Add an HP regeneration button below the 10,000 gold mark that allows the character to restore health.
User prompt
Increase the main character’s durability by 5 times.
User prompt
When the character contacts an enemy, they lose 1 HP per second, and this damage rate is multiplied by 1.2 at the start of each wave.
User prompt
Replace the center background with a dungeon image.
User prompt
Remove the cannon button from the game.
User prompt
Reduce the archer tower’s HP by 50%.
User prompt
The main building's HP should be at most half the total HP of the enemies coming in that wave.
User prompt
In wave 10, a total of 30 enemies should come, including 2 bosses, with 5 enemies each.
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var ArrowTrail = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('heroProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.8;
graphics.scaleX = 0.8;
graphics.scaleY = 2.0; // Elongated for arrow trail effect
graphics.tint = 0x8b4513; // Brown color for wooden arrow
self.scaleX = 1.0;
self.scaleY = 1.0;
// Arrow trail effect with slight movement
tween(self, {
scaleX: 1.3,
scaleY: 0.8,
alpha: 0,
rotation: Math.PI * 0.1
}, {
duration: 400,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
var Boss = Container.expand(function () {
var self = Container.call(this);
self.health = Math.floor(8000 * difficultyScaling.healthMultiplier);
self.maxHealth = Math.floor(8000 * difficultyScaling.healthMultiplier);
self.speed = 0.6;
self.goldValue = 100;
self.currentTarget = null;
self.lastX = 0;
self.lastY = 0;
self.dustTimer = 0;
self.motionEffects = [];
var graphics = self.attachAsset('boos', {
anchorX: 0.5,
anchorY: 1
});
// Scale boss to be larger
graphics.scaleX = 1.5;
graphics.scaleY = 1.5;
graphics.tint = 0x800080; // Purple tint for boss
// Add glowing red eyes
self.redEyes = self.attachAsset('redEyes', {
anchorX: 0.5,
anchorY: 0.5
});
self.redEyes.y = -graphics.height * 0.8;
self.redEyes.scaleX = 1.5;
self.redEyes.scaleY = 1.5;
// Make eyes glow with pulsing effect
self.eyeGlowDirection = 1;
self.eyeGlowAlpha = 0.8;
self.targetX = 1024;
self.targetY = 1366;
self.findNearestTarget = function () {
var nearestTarget = null;
var shortestDistance = Infinity;
// First priority: Check for glm asset when nearby (within detection range)
var glmDetectionRange = 400; // Bosses have longer detection range for glm
for (var glmIndex = 0; glmIndex < game.children.length; glmIndex++) {
var child = game.children[glmIndex];
// Check if this child has glm asset attached
if (child && child.attachedAssets && child.attachedAssets.length > 0) {
for (var assetIndex = 0; assetIndex < child.attachedAssets.length; assetIndex++) {
var asset = child.attachedAssets[assetIndex];
if (asset && asset.asset && asset.asset.id === 'glm') {
var dx = child.x - self.x;
var dy = child.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < glmDetectionRange) {
// GLM is nearby - prioritize it as target with aggressive behavior
// Add visual effect to show boss is targeting glm
LK.effects.flashObject(self, 0xff4500, 300);
return child;
}
}
}
}
}
// Check crystal tower
var dx = crystalTower.x - self.x;
var dy = crystalTower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
nearestTarget = crystalTower;
shortestDistance = distance;
// Check hero
dx = hero.x - self.x;
dy = hero.y - self.y;
distance = Math.sqrt(dx * dx + dy * dy);
if (distance < shortestDistance) {
nearestTarget = hero;
shortestDistance = distance;
}
// Check towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
dx = tower.x - self.x;
dy = tower.y - self.y;
distance = Math.sqrt(dx * dx + dy * dy);
if (distance < shortestDistance) {
nearestTarget = tower;
shortestDistance = distance;
}
}
return nearestTarget;
};
self.createDustCloud = function () {
var dust = LK.getAsset('dustCloud', {
anchorX: 0.5,
anchorY: 0.5
});
dust.x = self.x + (Math.random() - 0.5) * 30;
dust.y = self.y + (Math.random() - 0.5) * 20;
dust.alpha = 0.6;
game.addChild(dust);
// Animate dust cloud
tween(dust, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
y: dust.y - 20
}, {
duration: Math.floor(800 / gameSpeed),
easing: tween.easeOut,
onFinish: function onFinish() {
dust.destroy();
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Clean up motion effects
for (var i = 0; i < self.motionEffects.length; i++) {
self.motionEffects[i].destroy();
}
var coin = new GoldCoin();
coin.x = self.x;
coin.y = self.y;
coin.value = self.goldValue;
game.addChild(coin);
goldCoins.push(coin);
LK.getSound('enemyDeath').play();
enemyKills++; // Increment kill counter when boss dies
killsText.setText('Enemies Killed: ' + enemyKills);
// Continue wave progression after boss death and re-enable hero shooting
bossActive = false;
heroCanShoot = true;
// Special wave advancement for wave 5 to 6 transition
if (wave === 5) {
wave++;
enemiesInWave += 2;
enemiesSpawned = 0;
enemyKills = 0; // Reset kill counter for next wave
killsText.setText('Enemies Killed: ' + enemyKills);
// Update crystal tower HP based on new wave
var calculatedHP = calculateWaveEnemyHP();
crystalTower.health = calculatedHP;
crystalTower.maxHealth = calculatedHP;
waveText.setText('Wave: ' + wave);
}
// Remove boss from enemies array
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
self.update = function () {
// Store last position for motion effects
self.lastX = self.x;
self.lastY = self.y;
// Update glowing eyes effect
self.eyeGlowAlpha += self.eyeGlowDirection * 0.05 * gameSpeed;
if (self.eyeGlowAlpha >= 1) {
self.eyeGlowDirection = -1;
} else if (self.eyeGlowAlpha <= 0.5) {
self.eyeGlowDirection = 1;
}
self.redEyes.alpha = self.eyeGlowAlpha;
// Find nearest target and charge toward it
self.currentTarget = self.findNearestTarget();
var targetX = self.currentTarget.x;
var targetY = self.currentTarget.y;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Move toward target with charging behavior
// Check if targeting glm for increased speed and aggressive behavior
var currentSpeed = self.speed;
var isTargetingGlm = false;
if (self.currentTarget && self.currentTarget.attachedAssets) {
for (var assetIndex = 0; assetIndex < self.currentTarget.attachedAssets.length; assetIndex++) {
var asset = self.currentTarget.attachedAssets[assetIndex];
if (asset && asset.asset && asset.asset.id === 'glm') {
isTargetingGlm = true;
currentSpeed = self.speed * 2.0; // 100% speed increase when boss targets glm
// Add intense red glow to show extreme aggression
if (LK.ticks % 20 === 0) {
tween(self, {
tint: 0xff0000
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0x800080
}, {
duration: 150,
easing: tween.easeOut
});
}
});
}
break;
}
}
}
var moveX = dx / distance * currentSpeed * gameSpeed;
var moveY = dy / distance * currentSpeed * gameSpeed;
self.x += moveX;
self.y += moveY;
// Create dust clouds periodically while moving
self.dustTimer += gameSpeed;
if (self.dustTimer >= 15) {
self.createDustCloud();
self.dustTimer = 0;
}
} else {
// Attack the target (boss deals more damage)
if (self.currentTarget === crystalTower) {
crystalTower.takeDamage(40);
enemyAttacks++;
} else if (self.currentTarget === hero) {
// Flash hero red when attacked
LK.effects.flashObject(hero, 0xff0000, 500);
// Damage hero
heroHealth -= 50;
enemyAttacks++;
if (heroHealth <= 0 && !heroCollapsed) {
heroCollapsed = true;
// Hero collapse effect
tween(hero, {
alpha: 0.3,
scaleX: 0.8,
scaleY: 0.4,
rotation: Math.PI / 2
}, {
duration: Math.floor(1000 / gameSpeed),
easing: tween.easeOut
});
// Add smoke effect
var smoke = new SmokeEffect();
smoke.x = hero.x;
smoke.y = hero.y;
game.addChild(smoke);
}
} else {
// Check if target is a tower
for (var i = 0; i < towers.length; i++) {
if (towers[i] === self.currentTarget) {
self.currentTarget.takeDamage();
enemyAttacks++;
break;
}
}
}
}
};
return self;
});
var CrystalTower = Container.expand(function () {
var self = Container.call(this);
self.health = 1500;
self.maxHealth = 1500;
self.range = 200;
self.damage = 40;
self.fireRate = 30;
self.lastShot = 0;
self.hits = 0;
self.maxHits = 10;
// Create magic aura for attack radius visualization
self.magicAura = new MagicAura();
self.addChild(self.magicAura);
// Scale aura to match tower range (range 200 = diameter 400)
self.magicAura.scaleX = self.range * 2 / 400;
self.magicAura.scaleY = self.range * 2 / 400;
var graphics = self.attachAsset('crystalTower', {
anchorX: 0.5,
anchorY: 1
});
self.takeDamage = function (damage) {
self.health -= damage;
self.hits++;
// Tower blinks red when taking damage
tween(graphics, {
tint: 0xff0000
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xffffff
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xff0000
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xffffff
}, {
duration: 150,
easing: tween.easeOut
});
}
});
}
});
}
});
LK.effects.flashObject(self, 0xff0000, 500);
// Add damage effects based on health percentage
var healthPercent = self.health / self.maxHealth;
if (healthPercent < 0.7) {
// Add cracks
var crack = LK.getAsset('crackEffect', {
anchorX: 0.5,
anchorY: 0.5
});
crack.x = self.x + (Math.random() - 0.5) * 100;
crack.y = self.y - Math.random() * 150;
crack.rotation = Math.random() * Math.PI;
crack.alpha = 0.8;
game.addChild(crack);
}
if (healthPercent < 0.5) {
// Add smoke effects
var smoke = new SmokeEffect();
smoke.x = self.x + (Math.random() - 0.5) * 80;
smoke.y = self.y - 100 - Math.random() * 50;
game.addChild(smoke);
}
if (healthPercent < 0.3) {
// Add red alert glow
LK.effects.flashScreen(0xff0000, 300);
}
if (self.health < 1) {
// Create massive explosion effect
for (var i = 0; i < 5; i++) {
var explosion = new ExplosionEffect();
explosion.x = self.x + (Math.random() - 0.5) * 150;
explosion.y = self.y - Math.random() * 200;
game.addChild(explosion);
}
// Play destruction sound
LK.getSound('towerDestroy').play();
// Flash screen red for dramatic effect
LK.effects.flashScreen(0xff0000, 1500);
// Game ends when main building's HP drops below 1
LK.showGameOver();
}
};
self.findTarget = function () {
var closest = null;
var closestDistance = self.range;
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) {
closest = enemy;
closestDistance = distance;
}
}
return closest;
};
self.shoot = function (target) {
if (LK.ticks - self.lastShot >= Math.floor(self.fireRate / gameSpeed)) {
var projectile = new TowerProjectile();
projectile.x = self.x;
projectile.y = self.y - 60;
projectile.target = target;
projectile.damage = self.damage;
game.addChild(projectile);
towerProjectiles.push(projectile);
self.lastShot = LK.ticks;
// Trigger magical attack aura effect
self.magicAura.showAttackPulse();
// Add magical flash effect to tower
LK.effects.flashObject(graphics, 0x4169e1, 500);
// Add muzzle flash
var flash = new MuzzleFlash();
flash.x = self.x;
flash.y = self.y - 80;
game.addChild(flash);
LK.getSound('towerShoot').play();
}
};
self.update = function () {
var target = self.findTarget();
if (target) {
self.shoot(target);
}
};
return self;
});
var Enemy = Container.expand(function (type) {
var self = Container.call(this);
self.enemyType = type || 'goblin';
self.health = 100;
self.maxHealth = 100;
self.speed = 1;
self.goldValue = 10;
self.currentTarget = null;
self.lastX = 0;
self.lastY = 0;
self.dustTimer = 0;
self.motionEffects = [];
if (self.enemyType === 'goblin') {
self.health = Math.floor(160 * difficultyScaling.healthMultiplier);
self.maxHealth = Math.floor(160 * difficultyScaling.healthMultiplier);
self.speed = 1.5;
self.goldValue = 15;
var graphics = self.attachAsset('goblin', {
anchorX: 0.5,
anchorY: 1
});
} else if (self.enemyType === 'troll') {
self.health = Math.floor(400 * difficultyScaling.healthMultiplier);
self.maxHealth = Math.floor(400 * difficultyScaling.healthMultiplier);
self.speed = 0.8;
self.goldValue = 30;
var graphics = self.attachAsset('troll', {
anchorX: 0.5,
anchorY: 1
});
} else if (self.enemyType === 'skeleton') {
self.health = Math.floor(240 * difficultyScaling.healthMultiplier);
self.maxHealth = Math.floor(240 * difficultyScaling.healthMultiplier);
self.speed = 1.2 * difficultyScaling.skeletonSpeedMultiplier;
self.goldValue = 20;
var graphics = self.attachAsset('skeleton', {
anchorX: 0.5,
anchorY: 1
});
}
// Add glowing red eyes
self.redEyes = self.attachAsset('redEyes', {
anchorX: 0.5,
anchorY: 0.5
});
self.redEyes.y = -graphics.height * 0.8;
// Make eyes glow with pulsing effect
self.eyeGlowDirection = 1;
self.eyeGlowAlpha = 0.8;
self.targetX = 1024;
self.targetY = 1366;
self.findNearestTarget = function () {
var nearestTarget = null;
var shortestDistance = Infinity;
// First priority: Check for glm asset when nearby (within detection range)
var glmDetectionRange = 300; // Range within which enemies detect and prioritize glm
for (var glmIndex = 0; glmIndex < game.children.length; glmIndex++) {
var child = game.children[glmIndex];
// Check if this child has glm asset attached
if (child && child.attachedAssets && child.attachedAssets.length > 0) {
for (var assetIndex = 0; assetIndex < child.attachedAssets.length; assetIndex++) {
var asset = child.attachedAssets[assetIndex];
if (asset && asset.asset && asset.asset.id === 'glm') {
var dx = child.x - self.x;
var dy = child.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < glmDetectionRange) {
// GLM is nearby - prioritize it as target
return child;
}
}
}
}
}
// Check crystal tower
var dx = crystalTower.x - self.x;
var dy = crystalTower.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
nearestTarget = crystalTower;
shortestDistance = distance;
// Check hero
dx = hero.x - self.x;
dy = hero.y - self.y;
distance = Math.sqrt(dx * dx + dy * dy);
if (distance < shortestDistance) {
nearestTarget = hero;
shortestDistance = distance;
}
// Check towers
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
dx = tower.x - self.x;
dy = tower.y - self.y;
distance = Math.sqrt(dx * dx + dy * dy);
if (distance < shortestDistance) {
nearestTarget = tower;
shortestDistance = distance;
}
}
return nearestTarget;
};
self.createDustCloud = function () {
var dust = LK.getAsset('dustCloud', {
anchorX: 0.5,
anchorY: 0.5
});
dust.x = self.x + (Math.random() - 0.5) * 30;
dust.y = self.y + (Math.random() - 0.5) * 20;
dust.alpha = 0.6;
game.addChild(dust);
// Animate dust cloud
tween(dust, {
alpha: 0,
scaleX: 1.5,
scaleY: 1.5,
y: dust.y - 20
}, {
duration: 800,
easing: tween.easeOut,
onFinish: function onFinish() {
dust.destroy();
}
});
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health <= 0) {
self.die();
}
};
self.die = function () {
// Clean up motion effects
for (var i = 0; i < self.motionEffects.length; i++) {
self.motionEffects[i].destroy();
}
var coin = new GoldCoin();
coin.x = self.x;
coin.y = self.y;
coin.value = self.goldValue;
game.addChild(coin);
goldCoins.push(coin);
LK.getSound('enemyDeath').play();
enemyKills++; // Increment kill counter when enemy dies
killsText.setText('Enemies Killed: ' + enemyKills);
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
self.destroy();
};
self.update = function () {
// Store last position for motion effects
self.lastX = self.x;
self.lastY = self.y;
// Update glowing eyes effect
self.eyeGlowAlpha += self.eyeGlowDirection * 0.05 * gameSpeed;
if (self.eyeGlowAlpha >= 1) {
self.eyeGlowDirection = -1;
} else if (self.eyeGlowAlpha <= 0.5) {
self.eyeGlowDirection = 1;
}
self.redEyes.alpha = self.eyeGlowAlpha;
// Find nearest target and charge toward it
self.currentTarget = self.findNearestTarget();
var targetX = self.currentTarget.x;
var targetY = self.currentTarget.y;
var dx = targetX - self.x;
var dy = targetY - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 5) {
// Move toward target with charging behavior
// Check if targeting glm for increased speed and aggressive behavior
var currentSpeed = self.speed;
var isTargetingGlm = false;
if (self.currentTarget && self.currentTarget.attachedAssets) {
for (var assetIndex = 0; assetIndex < self.currentTarget.attachedAssets.length; assetIndex++) {
var asset = self.currentTarget.attachedAssets[assetIndex];
if (asset && asset.asset && asset.asset.id === 'glm') {
isTargetingGlm = true;
currentSpeed = self.speed * 1.5; // 50% speed increase when targeting glm
// Add red tint to show aggressive state
if (LK.ticks % 30 === 0) {
tween(self, {
tint: 0xff4500
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
tint: 0xffffff
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
break;
}
}
}
var moveX = dx / distance * currentSpeed * gameSpeed;
var moveY = dy / distance * currentSpeed * gameSpeed;
self.x += moveX;
self.y += moveY;
// Create dust clouds periodically while moving
self.dustTimer += gameSpeed;
if (self.dustTimer >= 15) {
self.createDustCloud();
self.dustTimer = 0;
}
} else {
// Attack the target
if (self.currentTarget === crystalTower) {
crystalTower.takeDamage(20);
enemyAttacks++;
} else if (self.currentTarget === hero) {
// Flash hero red when attacked
LK.effects.flashObject(hero, 0xff0000, 500);
// Damage hero
heroHealth -= 25;
enemyAttacks++;
if (heroHealth <= 0 && !heroCollapsed) {
heroCollapsed = true;
// Hero collapse effect
tween(hero, {
alpha: 0.3,
scaleX: 0.8,
scaleY: 0.4,
rotation: Math.PI / 2
}, {
duration: Math.floor(1000 / gameSpeed),
easing: tween.easeOut
});
// Add smoke effect
var smoke = new SmokeEffect();
smoke.x = hero.x;
smoke.y = hero.y;
game.addChild(smoke);
}
} else {
// Check if target is a tower
for (var i = 0; i < towers.length; i++) {
if (towers[i] === self.currentTarget) {
self.currentTarget.takeDamage();
enemyAttacks++;
break;
}
}
}
}
};
return self;
});
var ExplosionEffect = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('explosion', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.8;
self.scaleX = 0.3;
self.scaleY = 0.3;
// Animate explosion
tween(self, {
scaleX: 2,
scaleY: 2,
alpha: 0
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
var GLMWaveAnnouncement = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('glm', {
anchorX: 0.5,
anchorY: 0.5
});
self.animationDuration = 2000; // Total animation time
self.onScreenDuration = 800; // Time spent visible on screen
self.startAnimation = function () {
// Random starting position from screen edges
var side = Math.floor(Math.random() * 4);
var startX, startY, endX, endY, finalX, finalY;
// Calculate positions for smooth movement path
if (side === 0) {
// Top
startX = Math.random() * 2048;
startY = -100;
endX = 500 + Math.random() * 1048;
endY = 200 + Math.random() * 400;
} else if (side === 1) {
// Right
startX = 2148;
startY = Math.random() * 2732;
endX = 1400 + Math.random() * 400;
endY = 500 + Math.random() * 800;
} else if (side === 2) {
// Bottom
startX = Math.random() * 2048;
startY = 2832;
endX = 500 + Math.random() * 1048;
endY = 1800 + Math.random() * 400;
} else {
// Left
startX = -100;
startY = Math.random() * 2732;
endX = 200 + Math.random() * 400;
endY = 500 + Math.random() * 800;
}
// Exit position (opposite side with slight variation)
var exitSide = (side + 2) % 4;
if (exitSide === 0) {
// Top
finalX = endX + (Math.random() - 0.5) * 300;
finalY = -100;
} else if (exitSide === 1) {
// Right
finalX = 2148;
finalY = endY + (Math.random() - 0.5) * 300;
} else if (exitSide === 2) {
// Bottom
finalX = endX + (Math.random() - 0.5) * 300;
finalY = 2832;
} else {
// Left
finalX = -100;
finalY = endY + (Math.random() - 0.5) * 300;
}
// Set starting position
self.x = startX;
self.y = startY;
self.alpha = 0;
self.scaleX = 0.5;
self.scaleY = 0.5;
// Phase 1: Move onto screen with fade in
tween(self, {
x: endX,
y: endY,
alpha: 1,
scaleX: 1,
scaleY: 1
}, {
duration: 600,
easing: tween.easeOut,
onFinish: function onFinish() {
// Phase 2: Brief pause on screen
LK.setTimeout(function () {
// Phase 3: Move off screen with fade out
tween(self, {
x: finalX,
y: finalY,
alpha: 0,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 800,
easing: tween.easeIn,
onFinish: function onFinish() {
self.destroy();
}
});
}, self.onScreenDuration);
}
});
// Add slight rotation for dynamic effect
tween(self, {
rotation: (Math.random() - 0.5) * 0.4
}, {
duration: self.animationDuration,
easing: tween.easeInOut
});
};
return self;
});
var GoldCoin = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('goldCoin', {
anchorX: 0.5,
anchorY: 0.5
});
self.value = 10;
self.collected = false;
self.collect = function () {
if (!self.collected) {
self.collected = true;
gold += self.value;
goldText.setText('Gold: ' + gold);
LK.getSound('coinCollect').play();
for (var i = goldCoins.length - 1; i >= 0; i--) {
if (goldCoins[i] === self) {
goldCoins.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.down = function (x, y, obj) {
self.collect();
};
return self;
});
var GrassPatch = Container.expand(function (grassType) {
var self = Container.call(this);
self.grassType = grassType || 'grassPatch';
self.swayAmount = 0.1 + Math.random() * 0.15; // Random sway intensity
self.swaySpeed = 2000 + Math.random() * 1000; // Random sway duration
var graphics = self.attachAsset(self.grassType, {
anchorX: 0.5,
anchorY: 1
});
// Start initial sway animation
self.startSway = function () {
var targetRotation = self.swayAmount * (Math.random() > 0.5 ? 1 : -1);
tween(self, {
rotation: targetRotation
}, {
duration: self.swaySpeed,
easing: tween.easeInOut,
onFinish: function onFinish() {
// Reverse sway direction
var reverseRotation = -targetRotation * (0.8 + Math.random() * 0.4);
tween(self, {
rotation: reverseRotation
}, {
duration: self.swaySpeed * (0.8 + Math.random() * 0.4),
easing: tween.easeInOut,
onFinish: function onFinish() {
self.startSway(); // Continue swaying indefinitely
}
});
}
});
};
// Start swaying with random delay to create natural effect
LK.setTimeout(function () {
self.startSway();
}, Math.random() * 2000);
return self;
});
var HealthBarButton = Container.expand(function () {
var self = Container.call(this);
// Create border with subtle shadow effect
var border = self.attachAsset('healthBarBorder', {
anchorX: 0.5,
anchorY: 0.5
});
border.alpha = 0.8;
// Create base/background
var base = self.attachAsset('healthBarBase', {
anchorX: 0.5,
anchorY: 0.5
});
// Create health fill with gradient effect
self.healthFill = self.attachAsset('healthBarFill', {
anchorX: 0,
anchorY: 0.5
});
self.healthFill.x = -99; // Center the fill
self.healthFill.y = 0;
// Create heart icon
self.heartIcon = self.attachAsset('heartIcon', {
anchorX: 0.5,
anchorY: 0.5
});
self.heartIcon.x = -85; // Position on left side
self.heartIcon.y = 0;
self.heartIcon.scaleX = 0.8;
self.heartIcon.scaleY = 0.8;
// Add glossy highlight effect
var highlight = self.attachAsset('healthBarBase', {
anchorX: 0.5,
anchorY: 0.5
});
highlight.tint = 0xffffff;
highlight.alpha = 0.3;
highlight.scaleY = 0.4;
highlight.y = -6;
// Health bar properties
self.currentHealth = 100;
self.maxHealth = 100;
self.lastHealth = 100;
// Animation properties
self.pulseDirection = 1;
self.glowIntensity = 0.8;
self.setHealth = function (current, max) {
if (max !== undefined) {
self.maxHealth = max;
}
self.currentHealth = Math.max(0, Math.min(current, self.maxHealth));
self.updateHealthDisplay();
};
self.updateHealthDisplay = function () {
var healthPercent = self.currentHealth / self.maxHealth;
// Update fill width
self.healthFill.scaleX = healthPercent;
// Update color based on health level
if (healthPercent > 0.6) {
// Green to yellow gradient
var greenComponent = 1.0;
var redComponent = (1.0 - healthPercent) * 2.5;
self.healthFill.tint = Math.floor(redComponent * 255) << 16 | Math.floor(greenComponent * 255) << 8 | 0;
} else if (healthPercent > 0.3) {
// Yellow to orange
self.healthFill.tint = 0xffa500;
} else {
// Red with pulsing effect for critical health
self.healthFill.tint = 0xff0000;
}
// Animate health changes
if (self.currentHealth !== self.lastHealth) {
self.animateHealthChange();
self.lastHealth = self.currentHealth;
}
};
self.animateHealthChange = function () {
// Scale animation for health change
tween(self, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 150,
easing: tween.easeOut
});
}
});
// Heart beat animation
tween(self.heartIcon, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.heartIcon, {
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 200,
easing: tween.easeOut
});
}
});
};
self.update = function () {
// Subtle pulsing glow effect
self.glowIntensity += self.pulseDirection * 0.02;
if (self.glowIntensity >= 1.0) {
self.pulseDirection = -1;
} else if (self.glowIntensity <= 0.6) {
self.pulseDirection = 1;
}
// Apply glow to border
border.alpha = 0.6 + (self.glowIntensity - 0.6) * 0.4;
// Critical health warning pulse
if (self.currentHealth / self.maxHealth <= 0.2) {
var criticalPulse = Math.sin(LK.ticks * 0.3) * 0.3 + 0.7;
self.healthFill.alpha = criticalPulse;
self.heartIcon.alpha = criticalPulse;
} else {
self.healthFill.alpha = 1.0;
self.heartIcon.alpha = 1.0;
}
};
// Initialize display
self.updateHealthDisplay();
return self;
});
var Hero = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1
});
self.speed = 3;
self.range = 450;
self.damage = 35;
self.fireRate = 20;
self.lastShot = 0;
self.findTarget = function () {
var closest = null;
var closestDistance = self.range;
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) {
closest = enemy;
closestDistance = distance;
}
}
return closest;
};
self.shoot = function (target) {
if (LK.ticks - self.lastShot >= Math.floor(self.fireRate / gameSpeed)) {
var projectile = new HeroProjectile();
projectile.x = self.x;
projectile.y = self.y - 25;
projectile.target = target;
projectile.damage = self.damage;
game.addChild(projectile);
heroProjectiles.push(projectile);
self.lastShot = LK.ticks;
}
};
self.update = function () {
// Only shoot if hero is not collapsed (dead) and is allowed to shoot
if (!heroCollapsed && heroCanShoot) {
var target = self.findTarget();
if (target) {
self.shoot(target);
}
}
};
return self;
});
var HeroProjectile = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('heroProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 10;
self.target = null;
self.damage = 35;
self.update = function () {
if (!self.target || self.target.destroyed) {
self.destroy();
for (var i = heroProjectiles.length - 1; i >= 0; i--) {
if (heroProjectiles[i] === self) {
heroProjectiles.splice(i, 1);
break;
}
}
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 < 20) {
self.target.takeDamage(self.damage);
LK.getSound('arrowImpact').play();
self.destroy();
for (var i = heroProjectiles.length - 1; i >= 0; i--) {
if (heroProjectiles[i] === self) {
heroProjectiles.splice(i, 1);
break;
}
}
} else {
self.x += dx / distance * self.speed * gameSpeed;
self.y += dy / distance * self.speed * gameSpeed;
}
};
return self;
});
var MagicAura = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('magicAura', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.2;
self.pulseDirection = 1;
self.basePulse = 0.15;
self.currentPulse = self.basePulse;
self.update = function () {
// Create pulsing effect
self.currentPulse += self.pulseDirection * 0.003;
if (self.currentPulse >= 0.4) {
self.pulseDirection = -1;
} else if (self.currentPulse <= self.basePulse) {
self.pulseDirection = 1;
}
graphics.alpha = self.currentPulse;
// Slight rotation for magical effect
graphics.rotation += 0.01;
};
self.showAttackPulse = function () {
// Flash brighter when tower attacks
tween(graphics, {
alpha: 0.7,
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
alpha: self.currentPulse,
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
};
return self;
});
var MuzzleFlash = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('muzzleFlash', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.9;
self.scaleX = 0.5;
self.scaleY = 0.5;
// Quick flash effect
tween(self, {
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
var SmokeEffect = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('smokeCloud', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.alpha = 0.4;
self.scaleX = 0.8;
self.scaleY = 0.8;
// Rising smoke animation
tween(self, {
y: self.y - 100,
scaleX: 1.5,
scaleY: 1.5,
alpha: 0
}, {
duration: 2000,
easing: tween.easeOut,
onFinish: function onFinish() {
self.destroy();
}
});
return self;
});
var StylizedButton = Container.expand(function (config) {
var self = Container.call(this);
// Default configuration
var buttonConfig = config || {};
self.buttonWidth = buttonConfig.width || 200;
self.buttonHeight = buttonConfig.height || 80;
self.buttonColor = buttonConfig.color || 0x4169e1;
self.textContent = buttonConfig.text || "Button";
self.textSize = buttonConfig.textSize || 28;
// Create button layers for depth effect
self.shadow = self.attachAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0.5
});
self.shadow.scaleX = self.buttonWidth / 200;
self.shadow.scaleY = self.buttonHeight / 100;
self.shadow.tint = 0x000000;
self.shadow.alpha = 0.3;
self.shadow.x = 3;
self.shadow.y = 3;
// Main button background
self.background = self.attachAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0.5
});
self.background.scaleX = self.buttonWidth / 200;
self.background.scaleY = self.buttonHeight / 100;
self.background.tint = self.buttonColor;
// Gradient highlight overlay
self.highlight = self.attachAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0.5
});
self.highlight.scaleX = self.buttonWidth / 200;
self.highlight.scaleY = self.buttonHeight * 0.4 / 100;
self.highlight.tint = 0xffffff;
self.highlight.alpha = 0.3;
self.highlight.y = -self.buttonHeight * 0.15;
// Button border for polish
self.border = self.attachAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0.5
});
self.border.scaleX = (self.buttonWidth + 4) / 200;
self.border.scaleY = (self.buttonHeight + 4) / 100;
self.border.tint = 0xffffff;
self.border.alpha = 0.6;
// Move background to front
self.removeChild(self.background);
self.addChild(self.background);
// Move highlight to front
self.removeChild(self.highlight);
self.addChild(self.highlight);
// Button text
self.buttonText = new Text2(self.textContent, {
size: self.textSize,
fill: 0xFFFFFF
});
self.buttonText.anchor.set(0.5, 0.5);
self.addChild(self.buttonText);
// Animation properties
self.isPressed = false;
self.glowIntensity = 0.8;
self.glowDirection = 1;
// Hover/glow animation
self.update = function () {
// Subtle glow pulsing
self.glowIntensity += self.glowDirection * 0.015;
if (self.glowIntensity >= 1.0) {
self.glowDirection = -1;
} else if (self.glowIntensity <= 0.6) {
self.glowDirection = 1;
}
// Apply glow to border
self.border.alpha = 0.4 + (self.glowIntensity - 0.6) * 0.5;
};
// Press animation
self.playPressAnimation = function () {
if (self.isPressed) return;
self.isPressed = true;
// Scale down effect
tween(self, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
// Scale back up
tween(self, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
self.isPressed = false;
}
});
}
});
// Flash highlight
tween(self.highlight, {
alpha: 0.6
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.highlight, {
alpha: 0.3
}, {
duration: 150,
easing: tween.easeOut
});
}
});
};
// Update text content
self.setText = function (newText) {
self.buttonText.setText(newText);
};
// Update button color
self.setColor = function (newColor) {
self.buttonColor = newColor;
self.background.tint = newColor;
};
return self;
});
var Tower = Container.expand(function (type) {
var self = Container.call(this);
self.towerType = type || 'archer';
self.range = 150;
self.damage = 25;
self.fireRate = 60;
self.lastShot = 0;
self.hits = 0;
self.maxHits = 4;
if (self.towerType === 'archer') {
self.range = 380;
self.damage = 30;
self.fireRate = 45;
self.maxHits = 500;
var graphics = self.attachAsset('archerTower', {
anchorX: 0.5,
anchorY: 1
});
} else if (self.towerType === 'cannon') {
self.range = 120;
self.damage = 60;
self.fireRate = 90;
self.maxHits = 40;
var graphics = self.attachAsset('cannonTower', {
anchorX: 0.5,
anchorY: 1
});
}
self.takeDamage = function () {
self.hits++;
// Tower blinks red when taking damage
tween(graphics, {
tint: 0xff0000
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(graphics, {
tint: 0xffffff
}, {
duration: 200,
easing: tween.easeOut
});
}
});
if (self.hits === self.maxHits) {
// Create explosion effect
var explosion = new ExplosionEffect();
explosion.x = self.x;
explosion.y = self.y;
game.addChild(explosion);
// Play destruction sound
LK.getSound('towerDestroy').play();
// Remove from towers array
for (var i = towers.length - 1; i >= 0; i--) {
if (towers[i] === self) {
towers.splice(i, 1);
break;
}
}
self.destroy();
}
};
self.findTarget = function () {
var closest = null;
var closestDistance = self.range;
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) {
closest = enemy;
closestDistance = distance;
}
}
return closest;
};
self.shoot = function (target) {
if (LK.ticks - self.lastShot >= Math.floor(self.fireRate / gameSpeed)) {
var projectile = new TowerProjectile();
projectile.x = self.x;
projectile.y = self.y - 40;
projectile.target = target;
projectile.damage = self.damage;
game.addChild(projectile);
towerProjectiles.push(projectile);
self.lastShot = LK.ticks;
LK.getSound('towerShoot').play();
// Add different visual effects based on tower type
if (self.towerType === 'archer') {
// Arrow trail effect for archer towers
var arrowTrail = new ArrowTrail();
arrowTrail.x = self.x;
arrowTrail.y = self.y - 30;
game.addChild(arrowTrail);
} else {
// Muzzle flash effect for other tower types
var flash = new MuzzleFlash();
flash.x = self.x;
flash.y = self.y - 30;
game.addChild(flash);
}
}
};
self.update = function () {
var target = self.findTarget();
if (target) {
self.shoot(target);
}
};
return self;
});
var TowerProjectile = Container.expand(function () {
var self = Container.call(this);
var graphics = self.attachAsset('towerProjectile', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 8;
self.target = null;
self.damage = 25;
self.update = function () {
if (!self.target || self.target.destroyed) {
self.destroy();
for (var i = towerProjectiles.length - 1; i >= 0; i--) {
if (towerProjectiles[i] === self) {
towerProjectiles.splice(i, 1);
break;
}
}
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 < 20) {
self.target.takeDamage(self.damage);
self.destroy();
for (var i = towerProjectiles.length - 1; i >= 0; i--) {
if (towerProjectiles[i] === self) {
towerProjectiles.splice(i, 1);
break;
}
}
} else {
self.x += dx / distance * self.speed * gameSpeed;
self.y += dy / distance * self.speed * gameSpeed;
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x1a4d1a
});
/****
* Game Code
****/
var enemies = [];
var towers = [];
var towerProjectiles = [];
var heroProjectiles = [];
var goldCoins = [];
var crystalTower;
var hero;
var gold = 100;
var wave = 1;
var enemiesInWave = 5;
var enemySpawnTimer = 0;
var minEnemiesOnField = 3; // Always maintain minimum enemies
var enemiesSpawned = 0;
var waveComplete = false;
var draggedHero = false;
var heroCollapsed = false;
var heroHealth = 5000;
var heroMaxHealth = 5000;
var enemyAttacks = 0;
var enemyKills = 0;
var bossActive = false;
var difficultyScaling = {
healthMultiplier: 1.0,
skeletonSpeedMultiplier: 1.0
};
var scalingApplied = 0; // Track which wave milestone we last applied scaling for
// Control hero shooting behavior
var heroCanShoot = true;
// Track enemies in contact with crystal tower for continuous damage
var enemiesInContact = [];
var contactDamageTimer = 0;
// Hero contact damage system
var heroContactDamageRate = 1; // Base damage per second
var heroContactDamageTimer = 0;
var enemiesInContactWithHero = [];
// Game speed control
var gameSpeed = 1.0;
var gameSpeedOptions = [1.0, 1.5, 2.0, 3.0, 5.0];
var currentSpeedIndex = 0;
// Sound control
var soundMuted = false;
// Grass background removed - clean battlefield environment
var grassPatches = [];
// Function to calculate total enemy HP for current wave
function calculateWaveEnemyHP() {
var totalHP = 0;
var regularEnemyCount = 0;
var bossCount = 0;
// Determine enemy composition for current wave
if (wave === 5) {
bossCount = 1;
regularEnemyCount = 0;
} else if (wave === 10) {
bossCount = 2;
regularEnemyCount = 28;
} else if (wave === 15) {
bossCount = 1;
regularEnemyCount = 5 + (wave - 1) * 2;
} else if (wave === 100) {
bossCount = 1; // Special boss with 10000 HP
regularEnemyCount = 0;
totalHP += 10000; // Special boss HP
return Math.floor(totalHP / 2);
} else {
bossCount = 0;
regularEnemyCount = 5 + (wave - 1) * 2;
}
// Calculate boss HP
if (bossCount > 0 && wave !== 100) {
var bossHP = Math.floor(8000 * difficultyScaling.healthMultiplier);
totalHP += bossHP * bossCount;
}
// Calculate regular enemy HP (mix of goblins, skeletons, trolls)
if (regularEnemyCount > 0) {
var avgEnemyHP = 0;
if (wave < 3) {
// Only goblins and skeletons
avgEnemyHP = (160 + 240) / 2; // Average of goblin (160) and skeleton (240)
} else {
// All enemy types including trolls
avgEnemyHP = (160 + 240 + 400) / 3; // Average including troll (400)
}
avgEnemyHP = Math.floor(avgEnemyHP * difficultyScaling.healthMultiplier);
totalHP += avgEnemyHP * regularEnemyCount;
}
return Math.floor(totalHP / 2);
}
// Add dungeon background at center
var dungeonBackground = LK.getAsset('dungeonBackground', {
anchorX: 0.5,
anchorY: 0.5
});
dungeonBackground.x = 1024;
dungeonBackground.y = 1366;
game.addChild(dungeonBackground);
// Create crystal tower at center
crystalTower = new CrystalTower();
crystalTower.x = 1024;
crystalTower.y = 1366;
// Set HP to half of total enemy HP for current wave
var calculatedHP = calculateWaveEnemyHP();
crystalTower.health = calculatedHP;
crystalTower.maxHealth = calculatedHP;
game.addChild(crystalTower);
// Create hero
hero = new Hero();
hero.x = 900;
hero.y = 1200;
game.addChild(hero);
// Create hero HP bar that follows hero
var heroHealthBar = LK.getAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0.5
});
heroHealthBar.scaleX = 1.0;
heroHealthBar.scaleY = 0.6;
heroHealthBar.tint = 0x404040;
game.addChild(heroHealthBar);
var heroHealthFill = LK.getAsset('uiPanel', {
anchorX: 0,
anchorY: 0.5
});
heroHealthFill.scaleX = 1.0;
heroHealthFill.scaleY = 0.6;
heroHealthFill.tint = 0x00ff00;
heroHealthBar.addChild(heroHealthFill);
heroHealthFill.x = -100;
heroHealthFill.y = 0;
var heroHealthText = new Text2(heroHealth + '/' + heroMaxHealth, {
size: 28,
fill: 0xFFFFFF
});
heroHealthText.anchor.set(0.5, 0.5);
heroHealthBar.addChild(heroHealthText);
// UI Elements
var goldText = new Text2('Gold: ' + gold, {
size: 40,
fill: 0xFFD700
});
goldText.anchor.set(0, 0);
LK.gui.topRight.addChild(goldText);
goldText.x = -350;
goldText.y = 20;
// Add click handler to gold text to set gold to 10,000 (simplified approach)
goldText.down = function (x, y, obj) {
// Set gold to 10,000 on any click
gold = 10000;
goldText.setText('Gold: ' + gold);
// Flash screen gold to show effect
LK.effects.flashScreen(0xFFD700, 500);
};
var killsText = new Text2('Enemies Killed: ' + enemyKills, {
size: 40,
fill: 0xFFFFFF
});
killsText.anchor.set(0, 0);
LK.gui.topRight.addChild(killsText);
killsText.x = -350;
killsText.y = 70;
var waveText = new Text2('Wave: ' + wave, {
size: 60,
fill: 0xFFFFFF
});
waveText.anchor.set(0, 0);
LK.gui.top.addChild(waveText);
waveText.x = -100;
waveText.y = 50;
var healthText = new Text2('Tower HP: ' + crystalTower.health, {
size: 50,
fill: 0x00FFFF
});
healthText.anchor.set(0, 0);
LK.gui.bottom.addChild(healthText);
healthText.x = -150;
healthText.y = -80;
// Boss health bar (initially hidden)
var bossHealthBar = LK.getAsset('uiPanel', {
anchorX: 0.5,
anchorY: 0.5
});
bossHealthBar.scaleX = 4;
bossHealthBar.scaleY = 0.5;
bossHealthBar.tint = 0x800080;
bossHealthBar.visible = false;
LK.gui.top.addChild(bossHealthBar);
bossHealthBar.x = 0;
bossHealthBar.y = 100;
var bossHealthFill = LK.getAsset('uiPanel', {
anchorX: 0,
anchorY: 0.5
});
bossHealthFill.scaleX = 4;
bossHealthFill.scaleY = 0.4;
bossHealthFill.tint = 0xff0000;
bossHealthFill.visible = false;
bossHealthBar.addChild(bossHealthFill);
bossHealthFill.x = -400;
bossHealthFill.y = 0;
var bossHealthText = new Text2('BOSS', {
size: 40,
fill: 0xFFFFFF
});
bossHealthText.anchor.set(0.5, 0.5);
bossHealthText.visible = false;
LK.gui.top.addChild(bossHealthText);
bossHealthText.x = 0;
bossHealthText.y = 60;
// Tower placement UI
var archerButton = new StylizedButton({
width: 160,
height: 100,
color: 0x8b4513,
text: 'Archer\n50g',
textSize: 30
});
archerButton.x = 150;
archerButton.y = -150;
LK.gui.bottom.addChild(archerButton);
// Game speed button
var speedButton = new StylizedButton({
width: 160,
height: 60,
color: 0x4169e1,
text: 'Speed: 1.0x',
textSize: 28
});
speedButton.x = 150;
speedButton.y = -50;
LK.gui.bottomLeft.addChild(speedButton);
// Restart button
var restartButton = new StylizedButton({
width: 180,
height: 70,
color: 0xff4500,
text: 'Restart',
textSize: 28
});
restartButton.x = 150;
restartButton.y = -200;
LK.gui.bottomLeft.addChild(restartButton);
// Map panning state
var isPanning = false;
var panStartX = 0;
var panStartY = 0;
var panLastX = 0;
var panLastY = 0;
var mapOffsetX = 0;
var mapOffsetY = 0;
// Map expansion variables
var mapBounds = {
left: 0,
right: 2048,
top: 0,
bottom: 2732
};
var mapExpansionDistance = 500; // How much to expand when character reaches edge
// Range upgrade button
// Range upgrade pricing system
var rangeUpgradeCost = 5000;
var rangeUpgradeLevel = 0;
var rangeUpgradeButton = new StylizedButton({
width: 180,
height: 70,
color: 0x9932cc,
text: 'Range+\n5000g',
textSize: 28
});
rangeUpgradeButton.x = 150;
rangeUpgradeButton.y = -400;
LK.gui.bottomLeft.addChild(rangeUpgradeButton);
// HP regeneration button - stylized health bar design
var regenButton = new HealthBarButton();
regenButton.x = 150;
regenButton.y = -300;
regenButton.scaleX = 1.2;
regenButton.scaleY = 1.5;
LK.gui.bottomLeft.addChild(regenButton);
// Set initial health display for the button
regenButton.setHealth(75, 100); // Show 75% health as example
var regenText = new Text2('Heal 10000g', {
size: 24,
fill: 0xFFFFFF
});
regenText.anchor.set(0.5, 0.5);
regenText.y = 25; // Position below the health bar
regenButton.addChild(regenText);
// Range upgrade button handler
rangeUpgradeButton.down = function (x, y, obj) {
// Check if player has enough gold
if (gold >= rangeUpgradeCost) {
// Play stylized button press animation
rangeUpgradeButton.playPressAnimation();
// Deduct gold
gold -= rangeUpgradeCost;
goldText.setText('Gold: ' + gold);
// Double the crystal tower's range and magic aura size
crystalTower.range *= 2;
crystalTower.magicAura.scaleX = crystalTower.range * 2 / 400;
crystalTower.magicAura.scaleY = crystalTower.range * 2 / 400;
// Flash crystal tower blue to show upgrade
LK.effects.flashObject(crystalTower, 0x4169e1, 1000);
// Flash screen blue for upgrade effect
LK.effects.flashScreen(0x4169e1, 500);
// Update upgrade level and cost for next purchase
rangeUpgradeLevel++;
if (rangeUpgradeLevel === 1) {
rangeUpgradeCost = 25000;
rangeUpgradeButton.setText('Range+\n25000g');
}
// Animate button color change to show upgrade progression
tween(rangeUpgradeButton, {
tint: 0xffd700
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rangeUpgradeButton, {
tint: 0xffffff
}, {
duration: 200,
easing: tween.easeOut
});
}
});
}
};
// HP regeneration button handler
regenButton.down = function (x, y, obj) {
// Check if player has enough gold
if (gold >= 10000) {
// Animate health bar filling up
tween(regenButton, {
scaleX: 1.4,
scaleY: 1.7
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
// Animate health bar fill to 100%
var fillTween = function fillTween(startHealth, targetHealth, duration) {
var healthDiff = targetHealth - startHealth;
var steps = 30;
var stepSize = healthDiff / steps;
var currentStep = 0;
var _animateStep = function animateStep() {
if (currentStep < steps) {
regenButton.setHealth(startHealth + stepSize * currentStep, 100);
currentStep++;
LK.setTimeout(_animateStep, duration / steps);
} else {
regenButton.setHealth(targetHealth, 100);
}
};
_animateStep();
};
// Animate from current to full health
fillTween(regenButton.currentHealth, 100, 800);
tween(regenButton, {
scaleX: 1.2,
scaleY: 1.5
}, {
duration: 200,
easing: tween.easeOut
});
}
});
// Deduct gold and restore hero health
gold -= 10000;
heroHealth = heroMaxHealth;
goldText.setText('Gold: ' + gold);
// Flash hero green to show healing
LK.effects.flashObject(hero, 0x00ff00, 1000);
// Flash screen green for healing effect
LK.effects.flashScreen(0x00ff00, 500);
}
};
// Wave selection buttons
var waveButtons = [];
var waveOptions = [1, 5, 10, 15, 20, 30, 40, 50];
for (var w = 0; w < waveOptions.length; w++) {
var waveNumber = waveOptions[w];
var waveButton = new StylizedButton({
width: 80,
height: 50,
color: 0x4b0082,
text: 'W' + waveNumber,
textSize: 24
});
waveButton.waveTarget = waveNumber;
// Position buttons in a row at the top
waveButton.x = -650 + w * 90;
waveButton.y = 220;
LK.gui.top.addChild(waveButton);
// Add click handler for wave selection
waveButton.down = function (x, y, obj) {
var targetWave = this.waveTarget;
// Play stylized button press animation
this.playPressAnimation();
// Jump to selected wave
wave = targetWave;
enemiesInWave = 5 + (wave - 1) * 2;
enemiesSpawned = 0;
enemyKills = 0;
bossActive = false;
heroCanShoot = true;
// Update difficulty scaling based on wave
difficultyScaling.healthMultiplier = 1.0 + (wave - 1) * 0.15;
difficultyScaling.skeletonSpeedMultiplier = 1.0 + (wave - 1) * 0.08;
// Update hero contact damage rate based on wave
heroContactDamageRate = Math.pow(1.2, wave - 1);
// Update crystal tower HP based on new wave
var calculatedHP = calculateWaveEnemyHP();
crystalTower.health = calculatedHP;
crystalTower.maxHealth = calculatedHP;
// Clear existing enemies
for (var i = enemies.length - 1; i >= 0; i--) {
enemies[i].destroy();
enemies.splice(i, 1);
}
// Clear projectiles
for (var i = towerProjectiles.length - 1; i >= 0; i--) {
towerProjectiles[i].destroy();
towerProjectiles.splice(i, 1);
}
for (var i = heroProjectiles.length - 1; i >= 0; i--) {
heroProjectiles[i].destroy();
heroProjectiles.splice(i, 1);
}
// Update UI
waveText.setText('Wave: ' + wave);
killsText.setText('Enemies Killed: ' + enemyKills);
// Create GLM wave announcement for wave selection
var glmAnnouncement = new GLMWaveAnnouncement();
game.addChild(glmAnnouncement);
glmAnnouncement.startAnimation();
// Flash screen to indicate wave change
LK.effects.flashScreen(0x4b0082, 500);
};
waveButtons.push(waveButton);
}
// Speed button handler
speedButton.down = function (x, y, obj) {
// Play stylized button press animation
speedButton.playPressAnimation();
currentSpeedIndex = (currentSpeedIndex + 1) % gameSpeedOptions.length;
gameSpeed = gameSpeedOptions[currentSpeedIndex];
speedButton.setText('Speed: ' + gameSpeed + 'x');
// Note: Game speed functionality not available in LK engine
};
// Restart button handler
restartButton.down = function (x, y, obj) {
// Play stylized button press animation
restartButton.playPressAnimation();
// Trigger game over state first, which will automatically reset the game
LK.showGameOver();
};
// Tower placement handlers
archerButton.down = function (x, y, obj) {
// Play stylized button press animation
archerButton.playPressAnimation();
if (gold >= 50) {
placingTowerType = 'archer';
placingTowerCost = 50;
}
};
var placingTowerType = null;
var placingTowerCost = 0;
function spawnEnemy() {
var enemyTypes = ['goblin', 'goblin', 'skeleton'];
if (wave >= 3) enemyTypes.push('troll');
if (wave >= 5) enemyTypes.push('troll', 'troll');
var randomType = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
var enemy = new Enemy(randomType);
// Spawn from random edge using dynamic map boundaries
var side = Math.floor(Math.random() * 4);
if (side === 0) {
// Top
enemy.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left);
enemy.y = mapBounds.top;
} else if (side === 1) {
// Right
enemy.x = mapBounds.right;
enemy.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top);
} else if (side === 2) {
// Bottom
enemy.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left);
enemy.y = mapBounds.bottom;
} else {
// Left
enemy.x = mapBounds.left;
enemy.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top);
}
game.addChild(enemy);
enemies.push(enemy);
enemiesSpawned++;
}
function expandMapIfNeeded() {
var expanded = false;
var edgeBuffer = 200; // Distance from edge to trigger expansion
// Check if hero is near left edge
if (hero.x - mapBounds.left < edgeBuffer) {
mapBounds.left -= mapExpansionDistance;
expanded = true;
}
// Check if hero is near right edge
if (mapBounds.right - hero.x < edgeBuffer) {
mapBounds.right += mapExpansionDistance;
expanded = true;
}
// Check if hero is near top edge
if (hero.y - mapBounds.top < edgeBuffer) {
mapBounds.top -= mapExpansionDistance;
expanded = true;
}
// Check if hero is near bottom edge
if (mapBounds.bottom - hero.y < edgeBuffer) {
mapBounds.bottom += mapExpansionDistance;
expanded = true;
}
if (expanded) {
// Visual feedback for map expansion
LK.effects.flashScreen(0x4169e1, 300);
}
}
function checkWaveComplete() {
// Special handling for wave 5 to 6 transition - only advance when boss dies
if (wave === 5) {
// Wave 5 to 6 transition is handled in boss.die() method only
return;
}
// Special handling for wave 10 - need 30 kills to advance
var killsNeeded = wave === 10 ? 30 : 30;
if (enemyKills >= killsNeeded) {
// Start next wave after required kills
wave++;
// Create GLM wave announcement for regular wave transitions
var glmAnnouncement = new GLMWaveAnnouncement();
game.addChild(glmAnnouncement);
glmAnnouncement.startAnimation();
// Increase all enemy HP by 10 after each wave
difficultyScaling.healthMultiplier += 10 / 160; // Base goblin health is 160, so this adds roughly 10 HP
// Increase hero contact damage rate by 1.2x each wave
heroContactDamageRate *= 1.2;
enemiesInWave += 2;
enemiesSpawned = 0;
enemyKills = 0; // Reset kill counter for next wave
killsText.setText('Enemies Killed: ' + enemyKills);
bossActive = false; // Reset boss active flag for next wave
// Update crystal tower HP based on new wave
var calculatedHP = calculateWaveEnemyHP();
crystalTower.health = calculatedHP;
crystalTower.maxHealth = calculatedHP;
// Re-enable hero shooting for wave 6 and beyond (after first boss death)
if (wave >= 6) {
heroCanShoot = true;
}
waveText.setText('Wave: ' + wave);
if (wave > 100) {
LK.showYouWin();
}
}
}
game.down = function (x, y, obj) {
// Check for middle mouse button to start panning
if (obj.event && obj.event.button === 1) {
isPanning = true;
panStartX = x;
panStartY = y;
panLastX = x;
panLastY = y;
return;
}
if (placingTowerType) {
// Check if position is valid (not too close to crystal tower or other towers)
var tooClose = false;
var dx = x - crystalTower.x;
var dy = y - crystalTower.y;
if (Math.sqrt(dx * dx + dy * dy) < 200) {
tooClose = true;
}
for (var i = 0; i < towers.length; i++) {
var tower = towers[i];
var dx = x - tower.x;
var dy = y - tower.y;
if (Math.sqrt(dx * dx + dy * dy) < 120) {
tooClose = true;
break;
}
}
if (!tooClose) {
var newTower = new Tower(placingTowerType);
newTower.x = x;
newTower.y = y;
game.addChild(newTower);
towers.push(newTower);
gold -= placingTowerCost;
goldText.setText('Gold: ' + gold);
LK.getSound('towerPlace').play();
}
placingTowerType = null;
placingTowerCost = 0;
} else {
// Check if clicking on hero to start drag - expanded hitbox 4x larger than original visual area
var dx = x - hero.x;
var dy = y - hero.y;
// Hero asset is 125x156, expand hitbox to 4x the size (500x624 total area)
var heroWidth = 125 * 2; // 4x area means 2x width/height
var heroHeight = 156 * 2;
if (Math.abs(dx) < heroWidth / 2 && Math.abs(dy) < heroHeight / 2) {
draggedHero = true;
}
}
};
game.move = function (x, y, obj) {
if (isPanning) {
// Calculate pan delta
var deltaX = x - panLastX;
var deltaY = y - panLastY;
// Update map offset
mapOffsetX += deltaX;
mapOffsetY += deltaY;
// Apply pan offset to game container
game.x = mapOffsetX;
game.y = mapOffsetY;
// Update last position
panLastX = x;
panLastY = y;
} else if (draggedHero) {
hero.x = x - mapOffsetX;
hero.y = y - mapOffsetY;
}
};
game.up = function (x, y, obj) {
if (isPanning) {
isPanning = false;
}
draggedHero = false;
};
game.update = function () {
// Determine boss spawn requirements based on wave
var shouldSpawnBoss = false;
var bossCount = 0;
var shouldSpawnRegularEnemies = true;
if (wave === 5 && !bossActive && enemiesSpawned === 0) {
shouldSpawnBoss = true;
bossCount = 1;
shouldSpawnRegularEnemies = false; // Only boss on wave 5
} else if (wave === 10 && !bossActive && enemiesSpawned === 0) {
shouldSpawnBoss = true;
bossCount = 2;
shouldSpawnRegularEnemies = true; // Bosses plus regular enemies on wave 10
} else if (wave === 15 && !bossActive && enemiesSpawned === 0) {
shouldSpawnBoss = true;
bossCount = 1; // One boss plus regular enemies
shouldSpawnRegularEnemies = true; // Both bosses and regular enemies
} else if (wave === 100 && !bossActive && enemiesSpawned === 0) {
// Special wave 100: spawn main character as boss
shouldSpawnBoss = true;
bossCount = 1;
shouldSpawnRegularEnemies = false;
}
if (shouldSpawnBoss) {
// Create GLM wave announcement for boss waves
var glmAnnouncement = new GLMWaveAnnouncement();
game.addChild(glmAnnouncement);
glmAnnouncement.startAnimation();
for (var bossIndex = 0; bossIndex < bossCount; bossIndex++) {
var boss;
if (wave === 100) {
// Create main character as enemy with 10,000 HP
boss = new Boss();
boss.health = 10000;
boss.maxHealth = 10000;
boss.goldValue = 1000;
// Make it look like the hero
boss.removeChild(boss.attachedAssets[0]); // Remove boss graphics
var heroGraphics = boss.attachAsset('hero', {
anchorX: 0.5,
anchorY: 1
});
heroGraphics.tint = 0x800000; // Dark red tint to show it's evil
heroGraphics.scaleX = 2; // Make it larger
heroGraphics.scaleY = 2;
} else {
boss = new Boss();
}
// Spawn from random edge using dynamic map boundaries
var side = Math.floor(Math.random() * 4);
if (side === 0) {
// Top
boss.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left);
boss.y = mapBounds.top;
} else if (side === 1) {
// Right
boss.x = mapBounds.right;
boss.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top);
} else if (side === 2) {
// Bottom
boss.x = mapBounds.left + Math.random() * (mapBounds.right - mapBounds.left);
boss.y = mapBounds.bottom;
} else {
// Left
boss.x = mapBounds.left;
boss.y = mapBounds.top + Math.random() * (mapBounds.bottom - mapBounds.top);
}
game.addChild(boss);
enemies.push(boss);
enemiesSpawned++; // Count boss as spawned enemy
}
bossActive = true;
// Flash screen to indicate boss spawn
LK.effects.flashScreen(0x800080, 1000);
// Increase difficulty scaling after each boss round
difficultyScaling.healthMultiplier += 0.15; // 15% health increase per round
difficultyScaling.skeletonSpeedMultiplier += 0.08; // 8% speed increase for skeletons per round
}
// Spawn regular enemies continuously - always keep enemies on the field
// Only spawn regular enemies if it's not a boss-only wave (5) or if it's a mixed wave (10, 15)
var shouldSpawnRegularNow = true;
if (wave === 5 && !bossActive) {
shouldSpawnRegularNow = false; // Don't spawn regular enemies on boss-only wave 5 before boss spawns
} else if (wave === 5 && bossActive) {
shouldSpawnRegularNow = false; // Don't spawn regular enemies on boss-only wave 5 after boss spawns
}
// Set proper enemy count for wave 10
var currentWaveEnemyLimit = enemiesInWave;
if (wave === 10) {
currentWaveEnemyLimit = 30; // Total of 30 enemies including 2 bosses
}
if (shouldSpawnRegularNow && (enemiesSpawned < currentWaveEnemyLimit || enemies.length < 3)) {
enemySpawnTimer += gameSpeed;
if (enemySpawnTimer >= 60) {
// Spawn every 1 second for continuous pressure
spawnEnemy();
enemySpawnTimer = 0;
}
}
// Update health display
healthText.setText('Tower HP: ' + crystalTower.health);
// Update boss health bar
var currentBoss = null;
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].health === undefined || enemies[i].maxHealth === undefined) continue;
if (enemies[i].maxHealth >= 800) {
// Boss has 800+ max health
currentBoss = enemies[i];
break;
}
}
if (currentBoss && bossActive) {
bossHealthBar.visible = true;
bossHealthFill.visible = true;
bossHealthText.visible = true;
var healthPercent = currentBoss.health / currentBoss.maxHealth;
bossHealthFill.scaleX = 4 * healthPercent;
bossHealthText.setText('BOSS - ' + currentBoss.health + '/' + currentBoss.maxHealth);
} else {
bossHealthBar.visible = false;
bossHealthFill.visible = false;
bossHealthText.visible = false;
}
// Check for wave 10 boss death logic - if one boss dies, kill the other
if (wave === 10 && bossActive) {
var aliveBosses = [];
for (var i = 0; i < enemies.length; i++) {
if (enemies[i].maxHealth >= 800) {
aliveBosses.push(enemies[i]);
}
}
// If we had 2 bosses but now only have 1, kill the remaining boss
if (aliveBosses.length === 1) {
aliveBosses[0].die();
}
}
// Check for enemies in contact with crystal tower for continuous damage
var currentEnemiesInContact = [];
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - crystalTower.x;
var dy = enemy.y - crystalTower.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
// Contact threshold
currentEnemiesInContact.push(enemy);
}
}
// Apply contact damage every second (60 ticks)
contactDamageTimer += gameSpeed;
if (contactDamageTimer >= 60 && currentEnemiesInContact.length > 0) {
crystalTower.takeDamage(2 * currentEnemiesInContact.length);
contactDamageTimer = 0;
}
// Update enemies in contact array
enemiesInContact = currentEnemiesInContact;
// Check for enemies in contact with hero for continuous damage
var currentEnemiesInContactWithHero = [];
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - hero.x;
var dy = enemy.y - hero.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60) {
// Contact threshold for hero
currentEnemiesInContactWithHero.push(enemy);
}
}
// Apply hero contact damage every second (60 ticks)
heroContactDamageTimer += gameSpeed;
if (heroContactDamageTimer >= 60 && currentEnemiesInContactWithHero.length > 0 && !heroCollapsed) {
var damageAmount = Math.floor(heroContactDamageRate * currentEnemiesInContactWithHero.length);
heroHealth -= damageAmount;
// Flash hero red when taking contact damage
LK.effects.flashObject(hero, 0xff0000, 300);
heroContactDamageTimer = 0;
}
// Update enemies in contact with hero array
enemiesInContactWithHero = currentEnemiesInContactWithHero;
// Check for hero collecting coins
for (var i = goldCoins.length - 1; i >= 0; i--) {
var coin = goldCoins[i];
var dx = coin.x - hero.x;
var dy = coin.y - hero.y;
if (Math.sqrt(dx * dx + dy * dy) < 40) {
coin.collect();
}
}
// Add random battlefield explosions during intense moments
if (enemies.length > 8 && LK.ticks % Math.max(1, Math.floor(180 / gameSpeed)) === 0) {
var explosion = new ExplosionEffect();
explosion.x = mapBounds.left + 200 + Math.random() * (mapBounds.right - mapBounds.left - 400);
explosion.y = mapBounds.top + 200 + Math.random() * (mapBounds.bottom - mapBounds.top - 400);
game.addChild(explosion);
}
// Add red alert glow when tower health is critical
if (crystalTower.health < crystalTower.maxHealth * 0.2 && LK.ticks % Math.max(1, Math.floor(60 / gameSpeed)) === 0) {
LK.effects.flashScreen(0x8b0000, 200);
}
// Check for map expansion based on hero position
expandMapIfNeeded();
// Start background music on loop
LK.playMusic('med');
// Check if hero HP drops below 1 for game over
if (heroHealth < 1) {
LK.showGameOver();
}
// Update hero HP bar position to follow hero
heroHealthBar.x = hero.x;
heroHealthBar.y = hero.y + 40; // Position below hero
// Update hero HP bar display
var heroHealthPercent = Math.max(0, heroHealth) / heroMaxHealth;
heroHealthFill.scaleX = 1.0 * heroHealthPercent;
heroHealthText.setText(Math.max(0, heroHealth) + '/' + heroMaxHealth);
// Change HP bar color based on health
if (heroHealthPercent > 0.6) {
heroHealthFill.tint = 0x00ff00; // Green
} else if (heroHealthPercent > 0.3) {
heroHealthFill.tint = 0xffff00; // Yellow
} else {
heroHealthFill.tint = 0xff0000; // Red
}
// Power scaling every 5 waves
if (wave > 0 && wave % 5 === 0) {
// Check if we haven't already applied scaling for this wave milestone
if (!scalingApplied || scalingApplied < wave) {
// Increase hero attack power
hero.damage *= 1.2; // 20% increase each 5 waves
// Multiply crystal tower HP and attack by 5
crystalTower.health = Math.floor(crystalTower.health * 5);
crystalTower.maxHealth = Math.floor(crystalTower.maxHealth * 5);
crystalTower.damage = Math.floor(crystalTower.damage * 5);
// Multiply all existing towers' damage by 5
for (var i = 0; i < towers.length; i++) {
towers[i].damage = Math.floor(towers[i].damage * 5);
}
scalingApplied = wave;
}
}
checkWaveComplete();
};
// Override LK.getSound to respect mute state
var originalGetSound = LK.getSound;
LK.getSound = function (soundId) {
var sound = originalGetSound(soundId);
var originalPlay = sound.play;
sound.play = function () {
if (!soundMuted) {
originalPlay.call(sound);
}
};
return sound;
}; ===================================================================
--- original.js
+++ change.js
@@ -5,8 +5,35 @@
/****
* Classes
****/
+var ArrowTrail = Container.expand(function () {
+ var self = Container.call(this);
+ var graphics = self.attachAsset('heroProjectile', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ graphics.alpha = 0.8;
+ graphics.scaleX = 0.8;
+ graphics.scaleY = 2.0; // Elongated for arrow trail effect
+ graphics.tint = 0x8b4513; // Brown color for wooden arrow
+ self.scaleX = 1.0;
+ self.scaleY = 1.0;
+ // Arrow trail effect with slight movement
+ tween(self, {
+ scaleX: 1.3,
+ scaleY: 0.8,
+ alpha: 0,
+ rotation: Math.PI * 0.1
+ }, {
+ duration: 400,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ self.destroy();
+ }
+ });
+ return self;
+});
var Boss = Container.expand(function () {
var self = Container.call(this);
self.health = Math.floor(8000 * difficultyScaling.healthMultiplier);
self.maxHealth = Math.floor(8000 * difficultyScaling.healthMultiplier);
@@ -1383,13 +1410,22 @@
game.addChild(projectile);
towerProjectiles.push(projectile);
self.lastShot = LK.ticks;
LK.getSound('towerShoot').play();
- // Add muzzle flash effect
- var flash = new MuzzleFlash();
- flash.x = self.x;
- flash.y = self.y - 30;
- game.addChild(flash);
+ // Add different visual effects based on tower type
+ if (self.towerType === 'archer') {
+ // Arrow trail effect for archer towers
+ var arrowTrail = new ArrowTrail();
+ arrowTrail.x = self.x;
+ arrowTrail.y = self.y - 30;
+ game.addChild(arrowTrail);
+ } else {
+ // Muzzle flash effect for other tower types
+ var flash = new MuzzleFlash();
+ flash.x = self.x;
+ flash.y = self.y - 30;
+ game.addChild(flash);
+ }
}
};
self.update = function () {
var target = self.findTarget();
izometric cannon tower. In-Game asset. 2d. High contrast. No shadows. izometric
archerTower. In-Game asset. 2d. High contrast. No shadows
goblin. In-Game asset. 2d. High contrast. No shadows
goldCoin. In-Game asset. 2d. High contrast. No shadows
Archer hero. In-Game asset. 2d. High contrast. No shadows
skeleton. In-Game asset. 2d. High contrast. No shadows
troll. In-Game asset. 2d. High contrast. No shadows
single arrow image. In-Game asset. 2d. High contrast. No shadows
Create a flying dragon enemy with the following features:. In-Game asset. 2d. High contrast. No shadows
Here’s a prompt for a very distant, wide-angle view of a tree-free forest floor: **Prompt:** A very distant, wide-angle aerial view of a tree-free forest floor, showing expansive grassy plains with patches of dirt, scattered rocks, and low vegetation. The terrain stretches far into the horizon with subtle color variations and soft natural lighting, creating a vast, open, and serene natural environment without any trees.. In-Game asset. 2d. High contrast. No shadows
A stylized full-body illustration of a small hobbit holding a glowing ring in one hand, viewed from a 45-degree angle. The hobbit has curly hair, bare feet, and wears rustic, earth-toned clothing with detailed textures. The scene has warm, soft lighting emphasizing the character’s expressive face and the shining ring. The art style is cartoonish with rich colors, smooth shading, and a fantasy atmosphere.. In-Game asset. 2d. High contrast. No shadows
A magical yellow light glowing softly, with radiant beams and sparkling particles floating around. The light has a warm, enchanting aura that illuminates its surroundings with a golden hue. The atmosphere feels mystical and inviting, perfect for fantasy scenes or magical effects.. In-Game asset. 2d. High contrast. No shadows
A full-body stylized illustration of Smeagol (Gollum), showing his thin, hunched frame and large expressive eyes. He is barefoot and shirtless, wearing ragged shorts, with exaggerated cartoonish features that highlight his creepy yet pitiful nature. He clutches a glowing precious ring tightly in one hand. The art style is dark fantasy with vibrant colors, detailed skin textures, and a shadowy, mysterious background to enhance the eerie atmosphere. Perfect for full-character concept art or game design.. In-Game asset. 2d. High contrast. No shadows
A full-body stylized illustration of an orc warrior, standing in a dynamic pose. The orc has green or grayish skin, muscular build, tusks, and tribal armor made of bone, leather, and metal. The style is fantasy-themed with bold lines, exaggerated proportions, and detailed textures. The lighting is dramatic, emphasizing the orc’s strength and menace. Background is minimal or softly blurred to keep focus on the character. Suitable for fantasy RPG game concept art.. In-Game asset. 2d. High contrast. No shadows
A stylized fantasy axe with a broad, curved blade and intricate engravings. The handle is wrapped in worn leather, and the metal has a slightly weathered look, giving it a battle-worn feel. The design is bold and exaggerated, suitable for an orc warrior, with a glowing rune etched into the blade. The style is high-fantasy with clean lines, vibrant highlights, and a dramatic shadow for depth. Perfect for 2D game assets or concept art.. In-Game asset. 2d. High contrast. No shadows