User prompt
When player press star mucsic changes, but it shouldt until player actually press ready
User prompt
Apply th
User prompt
Actually keep using mainmenu_music when game starts, only change to backgorundmusic during battle time, when enemies are spawning, then go back to mainmenu_music
User prompt
backgroundmuisc should play when player touchs ready! and while battle is happening. when there are not enemies just mainmenu music should dplay
User prompt
play backgroundmusic on baltle phase and mainmenu_music on main menu ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
make sure player can move units in any planning phase. So after a wave, the player can move units again,bench them whatever.
User prompt
Please fix the bug: 'Uncaught ReferenceError: waveText is not defined' in or related to this line: 'tween(waveText, {' Line Number: 4661
User prompt
Please fix the bug: 'topUIContainer is not defined' in or related to this line: 'topUIContainer.addChild(stateText);' Line Number: 4060
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'addChild')' in or related to this line: 'topUIContainer.addChild(stateText);' Line Number: 4060
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'addChild')' in or related to this line: 'topUIContainer.addChild(stateText);' Line Number: 4060
User prompt
Please fix the bug: 'Cannot read properties of undefined (reading 'addChild')' in or related to this line: 'topUIContainer.addChild(stateText);' Line Number: 4060
User prompt
Please fix the bug: 'Uncaught TypeError: Cannot read properties of undefined (reading 'setText')' in or related to this line: 'stateText.setText('Battle Phase');' Line Number: 4720
User prompt
Please fix the bug: 'Uncaught ReferenceError: stateText is not defined' in or related to this line: 'stateText.setText('Battle Phase');' Line Number: 4719
User prompt
On planning phases allow player to move units, bench them and swap them
User prompt
Remove planinng phase and ballter phase and wave text from the UI
User prompt
Change colors of top gui to make it look more like the game title screen.
User prompt
Make the other element of the home screen also have an animation like the tile ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Move start game and howt to playr buttons a little lower, more i the center o the screen, and also change their color and animate them to match the rest of the style of the homepage ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
keep animation for game title constant ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Nie! improve the start game and dhow to play buttons too ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
can we improve the game title overlay style, use some tricks to make it looked better, maybe even change the color of the buttons. add some magic. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
In how to play section, the text below GAme Bascis, should be moved to te right so that it is hoziontaly centered in the screen
User prompt
Nice! add same effect to pplayer unit deaths ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Yes, implment particle explosion effect
User prompt
Add some animation to enemy death. Maybe we can make them turn into an ash pile and then dissapear. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
/****
* Classes
****/
var AttackInfo = Container.expand(function () {
var self = Container.call(this);
var background = self.attachAsset('bench_area_bg', {
anchorX: 0,
anchorY: 0,
width: 300,
height: 150
});
background.alpha = 0.8;
self.attackerText = new Text2('', {
size: 36,
fill: 0xFFFFFF
});
self.attackerText.anchor.set(0, 0.5);
self.attackerText.x = 10;
self.attackerText.y = 25;
self.addChild(self.attackerText);
self.damageText = new Text2('', {
size: 36,
fill: 0xFF0000
});
self.damageText.anchor.set(0, 0.5);
self.damageText.x = 10;
self.damageText.y = 100;
self.addChild(self.damageText);
self.updateInfo = function (attacker, damage) {
self.attackerText.setText('Attacker: ' + attacker);
self.damageText.setText('Damage: ' + damage);
};
return self;
});
var BenchSlot = Container.expand(function (index) {
var self = Container.call(this);
var slotGraphics = self.attachAsset('bench_slot', {
anchorX: 0.5,
anchorY: 0.5
});
self.index = index;
self.unit = null;
slotGraphics.alpha = 0.5;
self.down = function (x, y, obj) {
if (self.unit && gameState === 'planning') {
draggedUnit = self.unit;
// Start dragging from current position, not mouse position
draggedUnit.x = self.x;
draggedUnit.y = self.y;
// Store original position and slot
draggedUnit.originalX = self.x;
draggedUnit.originalY = self.y;
draggedUnit.originalSlot = self;
// Free the bench slot immediately when dragging starts
self.unit = null;
}
};
return self;
});
var Bullet = Container.expand(function (damage, target) {
var self = Container.call(this);
// Default bullet type
self.bulletType = 'default';
self.damage = damage;
self.target = target;
self.speed = 8;
// Create bullet graphics based on type
self.createGraphics = function () {
// Clear any existing graphics
self.removeChildren();
if (self.bulletType === 'arrow') {
// Create arrow projectile
var arrowShaft = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
// Arrow head
var arrowHead = self.attachAsset('arrow_head', {
anchorX: 0.5,
anchorY: 0.5
});
arrowHead.x = 15;
arrowHead.rotation = 0.785; // 45 degrees
// Arrow fletching
var fletching1 = self.attachAsset('arrow_feather', {
anchorX: 0.5,
anchorY: 0.5
});
fletching1.x = -10;
fletching1.y = -3;
var fletching2 = self.attachAsset('arrow_feather', {
anchorX: 0.5,
anchorY: 0.5
});
fletching2.x = -10;
fletching2.y = 3;
self.speed = 10; // Arrows are faster
} else if (self.bulletType === 'magic') {
// Create magic projectile
var magicCore = self.attachAsset('magic_core', {
anchorX: 0.5,
anchorY: 0.5
});
// Magic aura
var magicAura = self.attachAsset('magic_aura', {
anchorX: 0.5,
anchorY: 0.5
});
magicAura.alpha = 0.5;
// Magic sparkles
var sparkle1 = self.attachAsset('magic_sparkle', {
anchorX: 0.5,
anchorY: 0.5
});
sparkle1.x = -10;
sparkle1.y = -10;
var sparkle2 = self.attachAsset('magic_sparkle', {
anchorX: 0.5,
anchorY: 0.5
});
sparkle2.x = 10;
sparkle2.y = 10;
// Add tween for magic glow effect
tween(magicAura, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.3
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(magicAura, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.5
}, {
duration: 300,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
self.speed = 6; // Magic is slower but more visible
} else {
// Default bullet
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
}
};
// Create initial graphics
self.createGraphics();
self.update = function () {
if (!self.target || self.target.health <= 0) {
self.destroy();
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 < 10) {
var killed = self.target.takeDamage(self.damage);
if (killed) {
// Don't give gold for killing enemies
LK.getSound('enemyDeath').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self.target) {
// Trigger death animation instead of immediate destroy
enemies[i].deathAnimation();
enemies.splice(i, 1);
break;
}
}
}
self.destroy();
return;
}
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
// Rotate arrow to face target
if (self.bulletType === 'arrow') {
self.rotation = Math.atan2(dy, dx);
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
// Create pixelart enemy character
var enemyContainer = new Container();
self.addChild(enemyContainer);
// Body (red goblin)
var body = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
body.width = 60;
body.height = 60;
body.y = 10;
body.tint = 0xFF4444; // Red tint to match goblin theme
// Head (darker red)
var head = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 35
});
head.y = -30;
head.tint = 0xcc0000;
// Eyes (white dots)
var leftEye = enemyContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8
});
leftEye.x = -10;
leftEye.y = -30;
leftEye.tint = 0xFFFFFF;
var rightEye = enemyContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8
});
rightEye.x = 10;
rightEye.y = -30;
rightEye.tint = 0xFFFFFF;
// Claws
var leftClaw = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 15
});
leftClaw.x = -25;
leftClaw.y = 5;
leftClaw.rotation = 0.5;
leftClaw.tint = 0x800000;
var rightClaw = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 15
});
rightClaw.x = 25;
rightClaw.y = 5;
rightClaw.rotation = -0.5;
rightClaw.tint = 0x800000;
// Initial attributes for the enemy
self.health = 25;
self.maxHealth = 25;
self.damage = 15;
self.armor = 3;
self.criticalChance = 0.15;
self.magicResist = 3;
self.mana = 40;
self.speed = 2;
self.range = 1; // Enemy always melee (adjacent cell)
self.goldValue = 2;
self.name = "Enemy";
self.healthBar = null; // Initialize health bar as null
self.isAttacking = false; // Flag to track if enemy is currently attacking
self.baseScale = 1.3; // Base scale multiplier for all enemies (30% larger)
// Apply base scale
self.scaleX = self.baseScale;
self.scaleY = self.baseScale;
self.showHealthBar = function () {
if (!self.healthBar) {
self.healthBar = new HealthBar(60, 8); // Create a health bar instance (smaller than units)
self.healthBar.x = -30; // Position relative to the enemy's center
self.healthBar.y = -60; // Position above the enemy
self.addChild(self.healthBar);
}
self.healthBar.visible = true; // Ensure health bar is visible
self.healthBar.updateHealth(self.health, self.maxHealth);
};
self.updateHealthBar = function () {
if (self.healthBar) {
self.healthBar.updateHealth(self.health, self.maxHealth);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar(); // Update health bar when taking damage
if (self.health <= 0) {
self.health = 0;
return true;
}
return false;
};
self.deathAnimation = function () {
// Hide health bar immediately
if (self.healthBar) {
self.healthBar.destroy();
self.healthBar = null;
}
// Create particle explosion effect
var particleContainer = new Container();
particleContainer.x = self.x;
particleContainer.y = self.y;
self.parent.addChild(particleContainer);
// Number of particles based on enemy type
var particleCount = 12; // Default for regular enemies
var particleColors = [0xFF4444, 0xFF6666, 0xFF8888, 0xFFAAAA]; // Red shades for regular enemies
var particleSize = 15;
var explosionRadius = 80;
// Customize based on enemy type
if (self.name === "Ranged Enemy") {
particleColors = [0x8A2BE2, 0x9370DB, 0xBA55D3, 0xDDA0DD]; // Purple shades
particleCount = 10;
} else if (self.name === "Boss Monster") {
particleColors = [0x800000, 0xA52A2A, 0xDC143C, 0xFF0000]; // Dark red to bright red
particleCount = 20; // More particles for boss
particleSize = 20;
explosionRadius = 120;
}
// Create particles
var particles = [];
for (var i = 0; i < particleCount; i++) {
var particle = particleContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: particleSize + Math.random() * 10,
height: particleSize + Math.random() * 10
});
// Set particle color
particle.tint = particleColors[Math.floor(Math.random() * particleColors.length)];
particle.alpha = 0.8 + Math.random() * 0.2;
// Calculate random direction
var angle = Math.PI * 2 * i / particleCount + (Math.random() - 0.5) * 0.5;
var speed = explosionRadius * (0.7 + Math.random() * 0.3);
// Store particle data
particles.push({
sprite: particle,
targetX: Math.cos(angle) * speed,
targetY: Math.sin(angle) * speed,
rotationSpeed: (Math.random() - 0.5) * 0.2
});
}
// Animate enemy shrinking and fading
tween(self, {
alpha: 0,
scaleX: self.scaleX * 0.5,
scaleY: self.scaleY * 0.5
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
// Destroy the enemy
self.destroy();
}
});
// Animate particles bursting outward
for (var i = 0; i < particles.length; i++) {
var particleData = particles[i];
var particle = particleData.sprite;
// Burst outward animation
tween(particle, {
x: particleData.targetX,
y: particleData.targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: particle.rotation + particleData.rotationSpeed * 10
}, {
duration: 600 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Clean up after last particle
if (i === particles.length - 1) {
particleContainer.destroy();
}
}
});
}
// Add a brief flash effect at the center
var flash = particleContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: particleSize * 3,
height: particleSize * 3
});
flash.tint = 0xFFFFFF;
flash.alpha = 0.8;
tween(flash, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
};
self.update = function () {
if (!self.gridMovement) {
self.gridMovement = new GridMovement(self, grid);
}
self.gridMovement.update();
// Castle is now treated as a unit on the grid, no special collision logic needed
// Find and attack units within range (cell-based)
var closestUnit = null;
var closestCellDistance = self.range + 1; // Only units within range (cell count) are valid
// First check if king exists and is in range
if (kings.length > 0 && kings[0]) {
var king = kings[0];
if (typeof king.gridX === "number" && typeof king.gridY === "number") {
var kingCellDist = Math.abs(self.gridX - king.gridX) + Math.abs(self.gridY - king.gridY);
if (kingCellDist <= self.range && kingCellDist < closestCellDistance && self.gridMovement.isMoving === false) {
closestCellDistance = kingCellDist;
closestUnit = king;
}
}
}
// Then check other units on the grid
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x]) {
var gridSlot = grid[y][x];
if (gridSlot.unit) {
// Calculate cell-based Manhattan distance
var cellDist = Math.abs(self.gridX - x) + Math.abs(self.gridY - y);
// Check if the enemy is completely inside the adjacent cell before attacking
if (cellDist <= self.range && cellDist < closestCellDistance && self.gridMovement.isMoving === false) {
closestCellDistance = cellDist;
closestUnit = gridSlot.unit;
}
}
}
}
}
// Attack the closest unit if found
if (closestUnit) {
if (!self.lastAttack) {
self.lastAttack = 0;
}
if (LK.ticks - self.lastAttack >= 60) {
self.lastAttack = LK.ticks;
// Add tween animation for enemy attack
var originalX = self.x;
var originalY = self.y;
// Calculate lunge direction towards target
var dx = closestUnit.x - self.x;
var dy = closestUnit.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var offset = Math.min(30, dist * 0.25);
var lungeX = self.x + (dist > 0 ? dx / dist * offset : 0);
var lungeY = self.y + (dist > 0 ? dy / dist * offset : 0);
// Prevent multiple attack tweens at once
if (!self._isAttackTweening) {
self._isAttackTweening = true;
self.isAttacking = true; // Set attacking flag
// Flash enemy red briefly during attack using LK effects
LK.effects.flashObject(self, 0xFF4444, 300);
// Lunge towards target
tween(self, {
x: lungeX,
y: lungeY
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
// Apply damage after lunge
var killed = closestUnit.takeDamage(self.damage);
if (killed) {
self.isAttacking = false; // Clear attacking flag if target dies
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x]) {
var gridSlot = grid[y][x];
if (gridSlot.unit === closestUnit) {
gridSlot.unit = null;
// Trigger death animation instead of immediate destroy
closestUnit.deathAnimation();
for (var i = bench.length - 1; i >= 0; i--) {
if (bench[i] === closestUnit) {
bench.splice(i, 1);
break;
}
}
break;
}
}
}
}
}
// Return to original position
tween(self, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
self._isAttackTweening = false;
self.isAttacking = false; // Clear attacking flag after animation
}
});
}
});
}
}
}
};
return self;
});
var RangedEnemy = Enemy.expand(function () {
var self = Enemy.call(this);
// Remove default enemy graphics
self.removeChildren();
// Create pixelart ranged enemy character
var enemyContainer = new Container();
self.addChild(enemyContainer);
// Body (purple archer)
var body = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
body.width = 55;
body.height = 70;
body.y = 10;
body.tint = 0x8A2BE2; // Purple tint to match archer theme
// Head with hood
var head = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 35,
height: 30
});
head.y = -35;
head.tint = 0x6A1B9A;
// Eyes (glowing yellow)
var leftEye = enemyContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 6,
height: 6
});
leftEye.x = -8;
leftEye.y = -35;
leftEye.tint = 0xFFFF00;
var rightEye = enemyContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 6,
height: 6
});
rightEye.x = 8;
rightEye.y = -35;
rightEye.tint = 0xFFFF00;
// Bow
var bow = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 45,
height: 12
});
bow.x = -25;
bow.y = 0;
bow.rotation = 1.57; // 90 degrees
bow.tint = 0x4B0082;
// Bowstring
var bowstring = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 2,
height: 40
});
bowstring.x = -25;
bowstring.y = 0;
bowstring.rotation = 1.57;
bowstring.tint = 0xDDDDDD; // Light grey string
// Quiver on back
var quiver = enemyContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 30
});
quiver.x = 20;
quiver.y = -10;
quiver.tint = 0x2B0040; // Dark purple
// Override attributes for ranged enemy
self.health = 20;
self.maxHealth = 20;
self.damage = 12;
self.armor = 2;
self.criticalChance = 0.1;
self.magicResist = 2;
self.mana = 30;
self.speed = 1.5; // Slightly slower movement
self.range = 4; // Long range for shooting
self.goldValue = 3; // More valuable than melee enemies
self.name = "Ranged Enemy";
self.fireRate = 75; // Shoots faster - every 1.25 seconds
self.lastShot = 0;
self.baseScale = 1.3; // Base scale multiplier for all enemies (30% larger)
// Apply base scale
self.scaleX = self.baseScale;
self.scaleY = self.baseScale;
// Override update to include ranged attack behavior
self.update = function () {
if (!self.gridMovement) {
self.gridMovement = new GridMovement(self, grid);
}
self.gridMovement.update();
// Find and attack units within range (cell-based)
var closestUnit = null;
var closestCellDistance = self.range + 1; // Only units within range (cell count) are valid
// First check if king exists and is in range
if (kings.length > 0 && kings[0]) {
var king = kings[0];
if (typeof king.gridX === "number" && typeof king.gridY === "number") {
var kingCellDist = Math.abs(self.gridX - king.gridX) + Math.abs(self.gridY - king.gridY);
if (kingCellDist <= self.range && kingCellDist < closestCellDistance) {
closestCellDistance = kingCellDist;
closestUnit = king;
}
}
}
// Then check other units on the grid
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x]) {
var gridSlot = grid[y][x];
if (gridSlot.unit) {
// Calculate cell-based Manhattan distance
var cellDist = Math.abs(self.gridX - x) + Math.abs(self.gridY - y);
// Check if the enemy can shoot (doesn't need to be stationary like melee)
if (cellDist <= self.range && cellDist < closestCellDistance) {
closestCellDistance = cellDist;
closestUnit = gridSlot.unit;
}
}
}
}
}
// Attack the closest unit if found
if (closestUnit) {
if (!self.lastShot) {
self.lastShot = 0;
}
if (LK.ticks - self.lastShot >= self.fireRate) {
self.lastShot = LK.ticks;
// Create projectile
var projectile = new EnemyProjectile(self.damage, closestUnit);
projectile.x = self.x;
projectile.y = self.y;
enemyProjectiles.push(projectile);
enemyContainer.addChild(projectile);
// Add shooting animation
if (!self._isShootingTweening) {
self._isShootingTweening = true;
// Flash enemy blue briefly during shooting using LK effects
LK.effects.flashObject(self, 0x4444FF, 200);
self._isShootingTweening = false;
}
}
}
};
return self;
});
var Boss = Enemy.expand(function () {
var self = Enemy.call(this);
// Remove default enemy graphics
self.removeChildren();
// Create massive boss container
var bossContainer = new Container();
self.addChild(bossContainer);
// Main body (much larger and darker)
var body = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
body.width = 120;
body.height = 120;
body.y = 15;
body.tint = 0x800000; // Dark red
// Boss head (menacing)
var head = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 70
});
head.y = -60;
head.tint = 0x4a0000; // Very dark red
// Glowing eyes (intimidating)
var leftEye = bossContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 15
});
leftEye.x = -20;
leftEye.y = -60;
leftEye.tint = 0xFF0000; // Bright red glowing eyes
var rightEye = bossContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 15
});
rightEye.x = 20;
rightEye.y = -60;
rightEye.tint = 0xFF0000;
// Massive claws (larger and more threatening)
var leftClaw = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 30
});
leftClaw.x = -50;
leftClaw.y = 10;
leftClaw.rotation = 0.7;
leftClaw.tint = 0x2a0000; // Very dark
var rightClaw = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 30
});
rightClaw.x = 50;
rightClaw.y = 10;
rightClaw.rotation = -0.7;
rightClaw.tint = 0x2a0000;
// Boss spikes on shoulders
var leftSpike = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 40
});
leftSpike.x = -40;
leftSpike.y = -20;
leftSpike.rotation = 0.3;
leftSpike.tint = 0x1a1a1a; // Dark spikes
var rightSpike = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 40
});
rightSpike.x = 40;
rightSpike.y = -20;
rightSpike.rotation = -0.3;
rightSpike.tint = 0x1a1a1a;
// Boss crown/horns
var horn1 = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 35
});
horn1.x = -15;
horn1.y = -85;
horn1.rotation = 0.2;
horn1.tint = 0x000000; // Black horns
var horn2 = bossContainer.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 35
});
horn2.x = 15;
horn2.y = -85;
horn2.rotation = -0.2;
horn2.tint = 0x000000;
// Override boss attributes - much more powerful
self.health = 200;
self.maxHealth = 200;
self.damage = 60;
self.armor = 15;
self.criticalChance = 0.35;
self.magicResist = 15;
self.mana = 100;
self.speed = 1.2; // Slightly slower but hits harder
self.range = 3; // Even longer reach than normal enemies
self.goldValue = 25; // Very high reward for defeating boss
self.name = "Boss Monster";
self.fireRate = 75; // Faster attack rate with devastating damage
self.baseScale = 2.0; // Much larger than regular enemies (100% larger)
// Boss special abilities
self.lastSpecialAttack = 0;
self.specialAttackCooldown = 300; // 5 seconds
self.isEnraged = false;
self.rageThreshold = 0.3; // Enrage when below 30% health
// Apply base scale
self.scaleX = self.baseScale;
self.scaleY = self.baseScale;
// Add menacing glow effect to eyes
tween(leftEye, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(leftEye, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
tween(rightEye, {
scaleX: 1.3,
scaleY: 1.3,
alpha: 0.7
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(rightEye, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 1.0
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
// Boss special attack - area damage
self.performAreaAttack = function () {
// Get all units within 2 cells
var affectedUnits = [];
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x] && grid[y][x].unit) {
var unit = grid[y][x].unit;
var cellDist = Math.abs(self.gridX - x) + Math.abs(self.gridY - y);
if (cellDist <= 2) {
affectedUnits.push(unit);
}
}
}
}
// Check king separately
if (kings.length > 0 && kings[0]) {
var king = kings[0];
if (typeof king.gridX === "number" && typeof king.gridY === "number") {
var kingCellDist = Math.abs(self.gridX - king.gridX) + Math.abs(self.gridY - king.gridY);
if (kingCellDist <= 2) {
affectedUnits.push(king);
}
}
}
// Visual warning effect
LK.effects.flashScreen(0x660000, 200);
// Perform ground slam animation
var originalScale = self.baseScale * (self.isEnraged ? 1.2 : 1.0);
tween(self, {
scaleX: originalScale * 0.8,
scaleY: originalScale * 1.3
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Damage all nearby units
for (var i = 0; i < affectedUnits.length; i++) {
var unit = affectedUnits[i];
var areaDamage = Math.floor(self.damage * 0.5); // Half damage for area attack
var killed = unit.takeDamage(areaDamage);
if (killed) {
// Handle unit death
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x] && grid[y][x].unit === unit) {
grid[y][x].unit = null;
// Trigger death animation instead of immediate destroy
unit.deathAnimation();
for (var j = bench.length - 1; j >= 0; j--) {
if (bench[j] === unit) {
bench.splice(j, 1);
break;
}
}
break;
}
}
}
}
// Flash effect on damaged unit
LK.effects.flashObject(unit, 0xFF0000, 300);
}
// Return to normal size
tween(self, {
scaleX: originalScale,
scaleY: originalScale
}, {
duration: 150,
easing: tween.easeIn
});
}
});
};
// Override takeDamage to handle enrage mechanic
self.takeDamage = function (damage) {
self.health -= damage;
self.updateHealthBar();
// Check for enrage
if (!self.isEnraged && self.health / self.maxHealth <= self.rageThreshold) {
self.isEnraged = true;
// Visual enrage effect
body.tint = 0xFF0000; // Bright red when enraged
head.tint = 0x990000; // Dark red head
// Increase stats when enraged
self.damage = Math.floor(self.damage * 1.5);
self.fireRate = Math.floor(self.fireRate * 0.7); // Attack faster
self.speed = self.speed * 1.3;
// Scale up slightly
var enragedScale = self.baseScale * 1.2;
tween(self, {
scaleX: enragedScale,
scaleY: enragedScale
}, {
duration: 500,
easing: tween.easeOut
});
// Flash effect
LK.effects.flashObject(self, 0xFF0000, 1000);
LK.effects.flashScreen(0x660000, 500);
}
if (self.health <= 0) {
self.health = 0;
return true;
}
return false;
};
// Override attack with more devastating effects
self.update = function () {
if (!self.gridMovement) {
self.gridMovement = new GridMovement(self, grid);
}
self.gridMovement.update();
// Check for special area attack
if (LK.ticks - self.lastSpecialAttack >= self.specialAttackCooldown) {
// Check if there are multiple units nearby
var nearbyUnits = 0;
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x] && grid[y][x].unit) {
var cellDist = Math.abs(self.gridX - x) + Math.abs(self.gridY - y);
if (cellDist <= 2) {
nearbyUnits++;
}
}
}
}
// Perform area attack if 2+ units are nearby
if (nearbyUnits >= 2) {
self.lastSpecialAttack = LK.ticks;
self.performAreaAttack();
return; // Skip normal attack this frame
}
}
// Find and attack units within range (cell-based)
var closestUnit = null;
var closestCellDistance = self.range + 1; // Only units within range (cell count) are valid
// Smart target prioritization
var targets = [];
// First check if king exists and is in range
if (kings.length > 0 && kings[0]) {
var king = kings[0];
if (typeof king.gridX === "number" && typeof king.gridY === "number") {
var kingCellDist = Math.abs(self.gridX - king.gridX) + Math.abs(self.gridY - king.gridY);
if (kingCellDist <= self.range && self.gridMovement.isMoving === false) {
targets.push({
unit: king,
distance: kingCellDist,
priority: 2 // Medium priority for king
});
}
}
}
// Then check other units on the grid
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x]) {
var gridSlot = grid[y][x];
if (gridSlot.unit) {
// Calculate cell-based Manhattan distance
var cellDist = Math.abs(self.gridX - x) + Math.abs(self.gridY - y);
// Check if the enemy is completely inside the adjacent cell before attacking
if (cellDist <= self.range && self.gridMovement.isMoving === false) {
var unit = gridSlot.unit;
var priority = 3; // Default priority
// Prioritize high-damage units
if (unit.name === "Wizard" || unit.name === "Ranger") {
priority = 1; // Highest priority
} else if (unit.name === "Wall") {
priority = 4; // Lowest priority
}
targets.push({
unit: unit,
distance: cellDist,
priority: priority
});
}
}
}
}
}
// Sort targets by priority, then by distance
targets.sort(function (a, b) {
if (a.priority !== b.priority) {
return a.priority - b.priority;
}
return a.distance - b.distance;
});
// Select best target
if (targets.length > 0) {
closestUnit = targets[0].unit;
}
// Attack the closest unit if found
if (closestUnit) {
if (!self.lastAttack) {
self.lastAttack = 0;
}
if (LK.ticks - self.lastAttack >= self.fireRate) {
self.lastAttack = LK.ticks;
// Add devastating boss attack animation
var originalX = self.x;
var originalY = self.y;
// Calculate lunge direction towards target
var dx = closestUnit.x - self.x;
var dy = closestUnit.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var offset = Math.min(60, dist * 0.5); // Even larger lunge for boss
var lungeX = self.x + (dist > 0 ? dx / dist * offset : 0);
var lungeY = self.y + (dist > 0 ? dy / dist * offset : 0);
// Prevent multiple attack tweens at once
if (!self._isAttackTweening) {
self._isAttackTweening = true;
self.isAttacking = true; // Set attacking flag
// Flash boss with intense red and shake screen
LK.effects.flashObject(self, 0xFF0000, 500);
if (self.isEnraged) {
LK.effects.flashScreen(0x880000, 400); // Darker red screen flash when enraged
} else {
LK.effects.flashScreen(0x440000, 300); // Dark red screen flash
}
// Lunge towards target with more force
tween(self, {
x: lungeX,
y: lungeY,
rotation: self.rotation + (Math.random() > 0.5 ? 0.1 : -0.1)
}, {
duration: 120,
easing: tween.easeOut,
onFinish: function onFinish() {
// Apply massive damage after lunge
var actualDamage = self.damage;
// Critical hit chance
if (Math.random() < self.criticalChance) {
actualDamage = Math.floor(actualDamage * 2);
LK.effects.flashObject(closestUnit, 0xFFFF00, 200); // Yellow flash for crit
}
var killed = closestUnit.takeDamage(actualDamage);
if (killed) {
self.isAttacking = false; // Clear attacking flag if target dies
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x]) {
var gridSlot = grid[y][x];
if (gridSlot.unit === closestUnit) {
gridSlot.unit = null;
// Trigger death animation instead of immediate destroy
closestUnit.deathAnimation();
for (var i = bench.length - 1; i >= 0; i--) {
if (bench[i] === closestUnit) {
bench.splice(i, 1);
break;
}
}
break;
}
}
}
}
}
// Return to original position
tween(self, {
x: originalX,
y: originalY,
rotation: 0
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
self._isAttackTweening = false;
self.isAttacking = false; // Clear attacking flag after animation
}
});
}
});
}
}
}
};
return self;
});
var EnemyProjectile = Container.expand(function (damage, target) {
var self = Container.call(this);
// Create arrow projectile for enemies
var arrowContainer = new Container();
self.addChild(arrowContainer);
// Arrow shaft
var arrowShaft = arrowContainer.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5,
width: 25,
height: 6
});
arrowShaft.tint = 0x4B0082; // Dark purple for enemy arrows
// Arrow head
var arrowHead = arrowContainer.attachAsset('arrow_head', {
anchorX: 0.5,
anchorY: 0.5,
width: 10,
height: 10
});
arrowHead.x = 12;
arrowHead.rotation = 0.785; // 45 degrees
arrowHead.tint = 0xFF0000; // Red tip for enemy
// Arrow fletching
var fletching = arrowContainer.attachAsset('arrow_feather', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 5
});
fletching.x = -8;
fletching.tint = 0x8B008B; // Dark magenta feathers
self.damage = damage;
self.target = target;
self.speed = 6;
self.update = function () {
if (!self.target || self.target.health <= 0) {
self.destroy();
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 < 15) {
// Hit target
var killed = self.target.takeDamage(self.damage);
if (killed) {
// Handle target death - remove from grid if it's a unit
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x]) {
var gridSlot = grid[y][x];
if (gridSlot.unit === self.target) {
gridSlot.unit = null;
// Trigger death animation instead of immediate destroy
self.target.deathAnimation();
for (var i = bench.length - 1; i >= 0; i--) {
if (bench[i] === self.target) {
bench.splice(i, 1);
break;
}
}
break;
}
}
}
}
}
self.destroy();
return;
}
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
// Rotate arrow to face target
self.rotation = Math.atan2(dy, dx);
};
return self;
});
var GridMovement = Container.expand(function (entity, gridRef) {
var self = Container.call(this);
self.entity = entity;
self.gridRef = gridRef;
self.currentCell = {
x: entity.gridX,
y: entity.gridY
};
self.targetCell = {
x: entity.gridX,
y: entity.gridY
};
self.moveSpeed = 2; // Pixels per frame for continuous movement
self.isMoving = false; // Track if entity is currently moving
self.pathToTarget = []; // Store calculated path
self.pathIndex = 0; // Current position in path
// Helper method to check if a cell is occupied
self.isCellOccupied = function (x, y) {
// Check if out of bounds
if (x < 0 || x >= GRID_COLS || y < 0 || y >= GRID_ROWS) {
return true;
}
// Check if position is occupied by any entity using position tracking
if (isPositionOccupied(x, y, self.entity)) {
return true;
}
// Check if cell is reserved by another entity
var cellKey = x + ',' + y;
if (cellReservations[cellKey] && cellReservations[cellKey] !== self.entity) {
return true;
}
// Check if occupied by a player unit on grid
if (self.gridRef[y] && self.gridRef[y][x] && self.gridRef[y][x].unit && self.gridRef[y][x].unit !== self.entity) {
return true;
}
return false;
};
// A* pathfinding implementation
self.findPath = function (startX, startY, goalX, goalY) {
var openSet = [];
var closedSet = [];
var cameFrom = {};
// Helper to create node
function createNode(x, y, g, h) {
return {
x: x,
y: y,
g: g,
// Cost from start
h: h,
// Heuristic cost to goal
f: g + h // Total cost
};
}
// Manhattan distance heuristic
function heuristic(x1, y1, x2, y2) {
return Math.abs(x2 - x1) + Math.abs(y2 - y1);
}
// Get neighbors of a cell
function getNeighbors(x, y) {
var neighbors = [];
var directions = [{
x: 0,
y: -1
},
// Up
{
x: 1,
y: 0
},
// Right
{
x: 0,
y: 1
},
// Down
{
x: -1,
y: 0
} // Left
];
// Check if this entity is a player unit
var isPlayerUnit = self.entity.name && (self.entity.name === "Soldier" || self.entity.name === "Knight" || self.entity.name === "Wizard" || self.entity.name === "Paladin" || self.entity.name === "Ranger" || self.entity.name === "King" || self.entity.name === "Wall" || self.entity.name === "Crossbow Tower");
for (var i = 0; i < directions.length; i++) {
var newX = x + directions[i].x;
var newY = y + directions[i].y;
// Player units cannot path into fog of war (top 2 rows)
if (isPlayerUnit && newY < 2) {
continue;
}
// Skip if occupied (but allow goal position even if occupied)
if (newX === goalX && newY === goalY || !self.isCellOccupied(newX, newY)) {
neighbors.push({
x: newX,
y: newY
});
}
}
return neighbors;
}
// Initialize start node
var startNode = createNode(startX, startY, 0, heuristic(startX, startY, goalX, goalY));
openSet.push(startNode);
// Track best g scores
var gScore = {};
gScore[startX + ',' + startY] = 0;
while (openSet.length > 0) {
// Find node with lowest f score
var current = openSet[0];
var currentIndex = 0;
for (var i = 1; i < openSet.length; i++) {
if (openSet[i].f < current.f) {
current = openSet[i];
currentIndex = i;
}
}
// Check if we reached goal
if (current.x === goalX && current.y === goalY) {
// Reconstruct path
var path = [];
var key = current.x + ',' + current.y;
while (key && key !== startX + ',' + startY) {
var coords = key.split(',');
path.unshift({
x: parseInt(coords[0]),
y: parseInt(coords[1])
});
key = cameFrom[key];
}
return path;
}
// Move current from open to closed
openSet.splice(currentIndex, 1);
closedSet.push(current);
// Check neighbors
var neighbors = getNeighbors(current.x, current.y);
for (var i = 0; i < neighbors.length; i++) {
var neighbor = neighbors[i];
var neighborKey = neighbor.x + ',' + neighbor.y;
// Skip if in closed set
var inClosed = false;
for (var j = 0; j < closedSet.length; j++) {
if (closedSet[j].x === neighbor.x && closedSet[j].y === neighbor.y) {
inClosed = true;
break;
}
}
if (inClosed) {
continue;
}
// Calculate tentative g score
var tentativeG = current.g + 1;
// Check if this path is better
if (!gScore[neighborKey] || tentativeG < gScore[neighborKey]) {
// Record best path
cameFrom[neighborKey] = current.x + ',' + current.y;
gScore[neighborKey] = tentativeG;
// Add to open set if not already there
var inOpen = false;
for (var j = 0; j < openSet.length; j++) {
if (openSet[j].x === neighbor.x && openSet[j].y === neighbor.y) {
inOpen = true;
openSet[j].g = tentativeG;
openSet[j].f = tentativeG + openSet[j].h;
break;
}
}
if (!inOpen) {
var h = heuristic(neighbor.x, neighbor.y, goalX, goalY);
openSet.push(createNode(neighbor.x, neighbor.y, tentativeG, h));
}
}
}
}
// No path found - try simple alternative
return self.findAlternativePath(goalX, goalY);
};
// Helper method to find alternative paths (fallback for when A* fails)
self.findAlternativePath = function (targetX, targetY) {
var currentX = self.currentCell.x;
var currentY = self.currentCell.y;
// Calculate primary direction
var dx = targetX - currentX;
var dy = targetY - currentY;
// Try moving in the primary direction first
var moves = [];
if (Math.abs(dx) > Math.abs(dy)) {
// Prioritize horizontal movement
if (dx > 0 && !self.isCellOccupied(currentX + 1, currentY)) {
return [{
x: currentX + 1,
y: currentY
}];
}
if (dx < 0 && !self.isCellOccupied(currentX - 1, currentY)) {
return [{
x: currentX - 1,
y: currentY
}];
}
// Try vertical as alternative
if (dy > 0 && !self.isCellOccupied(currentX, currentY + 1)) {
return [{
x: currentX,
y: currentY + 1
}];
}
if (dy < 0 && !self.isCellOccupied(currentX, currentY - 1)) {
return [{
x: currentX,
y: currentY - 1
}];
}
} else {
// Prioritize vertical movement
if (dy > 0 && !self.isCellOccupied(currentX, currentY + 1)) {
return [{
x: currentX,
y: currentY + 1
}];
}
if (dy < 0 && !self.isCellOccupied(currentX, currentY - 1)) {
return [{
x: currentX,
y: currentY - 1
}];
}
// Try horizontal as alternative
if (dx > 0 && !self.isCellOccupied(currentX + 1, currentY)) {
return [{
x: currentX + 1,
y: currentY
}];
}
if (dx < 0 && !self.isCellOccupied(currentX - 1, currentY)) {
return [{
x: currentX - 1,
y: currentY
}];
}
}
return null;
};
self.moveToNextCell = function () {
// Check if this is a structure unit that shouldn't move
if (self.entity.isStructure) {
return false; // Structures don't move
}
// Check if this is a player unit or enemy
var isPlayerUnit = self.entity.name && (self.entity.name === "Soldier" || self.entity.name === "Knight" || self.entity.name === "Wizard" || self.entity.name === "Paladin" || self.entity.name === "Ranger" || self.entity.name === "King");
var targetGridX = -1;
var targetGridY = -1;
var shouldMove = false;
if (isPlayerUnit) {
// Player units move towards enemies
var closestEnemy = null;
var closestDist = 99999;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (typeof enemy.gridX === "number" && typeof enemy.gridY === "number") {
var dx = enemy.gridX - self.currentCell.x;
var dy = enemy.gridY - self.currentCell.y;
var dist = Math.abs(dx) + Math.abs(dy);
if (dist < closestDist) {
closestDist = dist;
closestEnemy = enemy;
targetGridX = enemy.gridX;
targetGridY = enemy.gridY;
}
}
}
// Only move if enemy found and not in range
if (closestEnemy && closestDist > self.entity.range) {
// Check if unit is currently attacking
if (self.entity.isAttacking) {
return false; // Don't move while attacking
}
shouldMove = true;
} else {
// Already in range or no enemy, don't move
return false;
}
} else {
// Smarter enemy movement logic
// First, check if enemy is currently in combat (attacking or being attacked)
if (self.entity.isAttacking) {
// Stay in place while attacking
return false;
}
// Find closest player unit (excluding king initially)
var closestUnit = null;
var closestDist = 99999;
var kingUnit = null;
var kingDist = 99999;
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (self.gridRef[y] && self.gridRef[y][x] && self.gridRef[y][x].unit) {
var unit = self.gridRef[y][x].unit;
var dx = x - self.currentCell.x;
var dy = y - self.currentCell.y;
var dist = Math.abs(dx) + Math.abs(dy);
if (unit.name === "King") {
// Track king separately
if (dist < kingDist) {
kingDist = dist;
kingUnit = unit;
}
} else {
// Track other player units
if (dist < closestDist) {
closestDist = dist;
closestUnit = unit;
targetGridX = x;
targetGridY = y;
}
}
}
}
}
// Determine target based on smart AI logic
if (closestUnit) {
// Found a non-king player unit
var entityRange = self.entity.range || 1;
// If enemy is within range, stay and fight
if (closestDist <= entityRange) {
// Stay in current position - engage in combat
return false;
}
// Move towards the closest player unit
shouldMove = true;
} else if (kingUnit) {
// No other player units remaining, go for the king
targetGridX = kingUnit.gridX;
targetGridY = kingUnit.gridY;
var entityRange = self.entity.range || 1;
// If enemy is within range of king, stay and fight
if (kingDist <= entityRange) {
// Stay in current position - engage king in combat
return false;
}
// Move towards the king
shouldMove = true;
} else {
// No target found, move down as fallback
targetGridX = self.currentCell.x;
targetGridY = self.currentCell.y + 1;
shouldMove = true;
}
}
if (!shouldMove) {
return false;
}
// Use A* pathfinding to find best path
if (!self.pathToTarget || self.pathToTarget.length === 0 || self.pathIndex >= self.pathToTarget.length) {
// Calculate new path
var path = self.findPath(self.currentCell.x, self.currentCell.y, targetGridX, targetGridY);
if (path && path.length > 0) {
self.pathToTarget = path;
self.pathIndex = 0;
} else {
// No path found
return false;
}
}
// Get next cell from path
var nextCell = self.pathToTarget[self.pathIndex];
if (!nextCell) {
return false;
}
var nextX = nextCell.x;
var nextY = nextCell.y;
// Check if desired cell is still unoccupied
if (self.isCellOccupied(nextX, nextY)) {
// Path is blocked, recalculate
self.pathToTarget = [];
self.pathIndex = 0;
return false;
}
// Final check if the cell is valid and unoccupied
if (!self.isCellOccupied(nextX, nextY) && self.gridRef[nextY] && self.gridRef[nextY][nextX]) {
// Prevent player units from moving into fog of war (top 2 rows)
var isPlayerUnit = self.entity.name && (self.entity.name === "Soldier" || self.entity.name === "Knight" || self.entity.name === "Wizard" || self.entity.name === "Paladin" || self.entity.name === "Ranger" || self.entity.name === "King" || self.entity.name === "Wall" || self.entity.name === "Crossbow Tower");
if (isPlayerUnit && nextY < 2) {
// Player units cannot move into fog of war
self.pathToTarget = [];
self.pathIndex = 0;
return false;
}
var targetGridCell = self.gridRef[nextY][nextX];
// Double-check that no other entity is trying to move to same position at same time
if (isPositionOccupied(nextX, nextY, self.entity)) {
return false;
}
// Unregister old position
unregisterEntityPosition(self.entity, self.currentCell.x, self.currentCell.y);
// Register new position immediately to prevent conflicts
registerEntityPosition(self.entity);
// Release old reservation
var oldCellKey = self.currentCell.x + ',' + self.currentCell.y;
if (cellReservations[oldCellKey] === self.entity) {
delete cellReservations[oldCellKey];
}
// Reserve new cell
var newCellKey = nextX + ',' + nextY;
cellReservations[newCellKey] = self.entity;
// Update logical position immediately
self.currentCell.x = nextX;
self.currentCell.y = nextY;
self.entity.gridX = nextX;
self.entity.gridY = nextY;
// Set target position for smooth movement
self.targetCell.x = targetGridCell.x;
self.targetCell.y = targetGridCell.y;
self.isMoving = true;
// Add smooth transition animation when starting movement
if (!self.entity._isMoveTweening) {
self.entity._isMoveTweening = true;
// Get base scale (default to 1 if not set)
var baseScale = self.entity.baseScale || 1;
// Slight scale and rotation animation when moving
tween(self.entity, {
scaleX: baseScale * 1.1,
scaleY: baseScale * 0.9,
rotation: self.entity.rotation + (Math.random() > 0.5 ? 0.05 : -0.05)
}, {
duration: 150,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(self.entity, {
scaleX: baseScale,
scaleY: baseScale,
rotation: 0
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
self.entity._isMoveTweening = false;
}
});
}
});
}
return true;
}
return false;
};
self.update = function () {
// Periodically check if we need to recalculate path (every 30 ticks)
if (LK.ticks % 30 === 0 && self.pathToTarget && self.pathToTarget.length > 0) {
// Check if current path is still valid
var stillValid = true;
for (var i = self.pathIndex; i < self.pathToTarget.length && i < self.pathIndex + 3; i++) {
if (self.pathToTarget[i] && self.isCellOccupied(self.pathToTarget[i].x, self.pathToTarget[i].y)) {
stillValid = false;
break;
}
}
if (!stillValid) {
// Path blocked, clear it to force recalculation
self.pathToTarget = [];
self.pathIndex = 0;
}
}
// If we have a target position, move towards it smoothly
if (self.isMoving) {
var dx = self.targetCell.x - self.entity.x;
var dy = self.targetCell.y - self.entity.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > self.moveSpeed) {
// Continue moving towards target
var moveX = dx / distance * self.moveSpeed;
var moveY = dy / distance * self.moveSpeed;
self.entity.x += moveX;
self.entity.y += moveY;
} else {
// Reached target cell - ensure exact grid alignment
var finalGridX = self.currentCell.x;
var finalGridY = self.currentCell.y;
var finalX = GRID_START_X + finalGridX * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
var finalY = GRID_START_Y + finalGridY * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
self.entity.x = finalX;
self.entity.y = finalY;
self.isMoving = false;
self.pathIndex++; // Move to next cell in path
// Wait a tick before trying next move to avoid simultaneous conflicts
if (LK.ticks % 2 === 0) {
// Immediately try to move to next cell for continuous movement
self.moveToNextCell();
}
}
} else {
// Not moving, try to start moving to next cell
// Add slight randomization to prevent all entities from moving at exact same time
if (LK.ticks % (2 + Math.floor(Math.random() * 3)) === 0) {
self.moveToNextCell();
}
}
};
return self;
});
var GridSlot = Container.expand(function (lane, gridX, gridY) {
var self = Container.call(this);
var slotGraphics = self.attachAsset('grid_slot', {
anchorX: 0.5,
anchorY: 0.5
});
self.lane = lane;
self.gridX = gridX;
self.gridY = gridY;
self.unit = null;
slotGraphics.alpha = 0.3;
self.down = function (x, y, obj) {
if (self.unit && !draggedUnit && gameState === 'planning') {
// Prevent moving king after the first placement
if (self.unit.name === 'King' && wave > 1) {
return; // King can only be moved at the beginning of the game
}
// Allow dragging any unit during planning phase
draggedUnit = self.unit;
draggedUnit.originalX = self.x;
draggedUnit.originalY = self.y;
draggedUnit.originalGridSlot = self;
draggedFromGrid = true;
}
};
self.up = function (x, y, obj) {
if (draggedUnit && !self.unit) {
self.placeUnit(draggedUnit);
draggedUnit = null;
}
};
self.placeUnit = function (unit) {
if (self.unit) {
return false;
}
// Prevent placement in fog of war (top 2 rows)
if (self.gridY < 2) {
// Show message about fog of war
var fogMessage = new Text2('Cannot place units in the fog of war!', {
size: 72,
fill: 0xFF4444
});
fogMessage.anchor.set(0.5, 0.5);
fogMessage.x = 1024; // Center of screen
fogMessage.y = 800; // Middle of screen
game.addChild(fogMessage);
// Flash the message and fade it out
LK.effects.flashObject(fogMessage, 0xFFFFFF, 300);
tween(fogMessage, {
alpha: 0,
y: 700
}, {
duration: 3000,
easing: tween.easeOut,
onFinish: function onFinish() {
fogMessage.destroy();
}
});
// Return unit to bench if it was being dragged
if (unit.originalSlot) {
unit.originalSlot.unit = unit;
unit.x = unit.originalSlot.x;
unit.y = unit.originalSlot.y;
} else {
// Find first available bench slot
for (var i = 0; i < benchSlots.length; i++) {
if (!benchSlots[i].unit) {
benchSlots[i].unit = unit;
unit.x = benchSlots[i].x;
unit.y = benchSlots[i].y;
unit.gridX = -1;
unit.gridY = -1;
unit.lane = -1;
unit.hideHealthBar();
unit.hideStarIndicator();
updateBenchDisplay();
break;
}
}
}
return false;
}
// Check if position is already occupied by another entity
if (isPositionOccupied(self.gridX, self.gridY, unit)) {
return false;
}
// Check unit placement limits (don't apply to king)
if (unit.name !== "King" && !canPlaceMoreUnits(unit.isStructure)) {
// Show message about reaching unit limit
var limitType = unit.isStructure ? 'structures' : 'units';
var limitMessage = new Text2('You have no more room for ' + limitType + ' in the battlefield, level up to increase it!', {
size: 72,
fill: 0xFF4444
});
limitMessage.anchor.set(0.5, 0.5);
limitMessage.x = 1024; // Center of screen
limitMessage.y = 800; // Middle of screen
game.addChild(limitMessage);
// Flash the message and fade it out
LK.effects.flashObject(limitMessage, 0xFFFFFF, 300);
tween(limitMessage, {
alpha: 0,
y: 700
}, {
duration: 5000,
easing: tween.easeOut,
onFinish: function onFinish() {
limitMessage.destroy();
}
});
// Return unit to bench - find first available bench slot
for (var i = 0; i < benchSlots.length; i++) {
if (!benchSlots[i].unit) {
benchSlots[i].unit = unit;
unit.x = benchSlots[i].x;
unit.y = benchSlots[i].y;
unit.gridX = -1;
unit.gridY = -1;
unit.lane = -1;
unit.hideHealthBar();
unit.hideStarIndicator();
updateBenchDisplay();
break;
}
}
return false; // Can't place more units
}
self.unit = unit;
// Unregister old position if unit was previously on grid
if (unit.gridX >= 0 && unit.gridY >= 0) {
unregisterEntityPosition(unit, unit.gridX, unit.gridY);
}
// Calculate exact grid cell center position
var gridCenterX = GRID_START_X + self.gridX * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
var gridCenterY = GRID_START_Y + self.gridY * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
// Update unit's grid coordinates
unit.gridX = self.gridX;
unit.gridY = self.gridY;
unit.lane = self.lane;
// Force unit to exact grid position (snap to grid)
unit.x = gridCenterX;
unit.y = gridCenterY;
// Ensure the grid slot also has the correct position
self.x = gridCenterX;
self.y = gridCenterY;
// Add placement animation with bounce effect
var targetScale = unit.baseScale || 1.3; // Use base scale or default to 1.3
if (unit.tier === 2) {
targetScale = (unit.baseScale || 1.3) * 1.2;
} else if (unit.tier === 3) {
targetScale = (unit.baseScale || 1.3) * 1.3;
}
// Stop any ongoing scale tweens before starting new bounce
tween.stop(unit, {
scaleX: true,
scaleY: true
});
// Always reset scale to 0 before bounce (fixes bug where scale is not reset)
unit.scaleX = 0;
unit.scaleY = 0;
// Capture unit reference before any async operations
var unitToAnimate = unit;
// Start bounce animation immediately
// Set bounce animation flag to prevent updateUnitScale interference
unitToAnimate._isBounceAnimating = true;
tween(unitToAnimate, {
scaleX: targetScale * 1.2,
scaleY: targetScale * 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unitToAnimate, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Register new position
registerEntityPosition(unit);
unit.showHealthBar(); // Show health bar when placed on grid
unit.updateHealthBar(); // Update health bar to show current health
// Delay showStarIndicator until after bounce animation completes
LK.setTimeout(function () {
if (unitToAnimate && unitToAnimate.parent) {
// Check if unit still exists
unitToAnimate.showStarIndicator(); // Show star indicator after bounce animation
}
}, 400); // Wait for bounce animation to complete (200ms + 150ms + small buffer)
// Don't remove from bench - units stay in both places
updateBenchDisplay();
countUnitsOnField(); // Update unit counts
return true;
};
return self;
});
var HealthBar = Container.expand(function (width, height) {
var self = Container.call(this);
self.maxWidth = width;
// Background of the health bar
self.background = self.attachAsset('healthbar_bg', {
anchorX: 0,
anchorY: 0.5,
width: width,
height: height
});
// Foreground of the health bar
self.foreground = self.attachAsset('healthbar_fg', {
anchorX: 0,
anchorY: 0.5,
width: width,
height: height
});
self.updateHealth = function (currentHealth, maxHealth) {
var healthRatio = currentHealth / maxHealth;
self.foreground.width = self.maxWidth * healthRatio;
if (healthRatio > 0.6) {
self.foreground.tint = 0x00ff00; // Green
} else if (healthRatio > 0.3) {
self.foreground.tint = 0xffff00; // Yellow
} else {
self.foreground.tint = 0xff0000; // Red
}
};
return self;
});
var StarIndicator = Container.expand(function (tier) {
var self = Container.call(this);
self.tier = tier || 1;
self.dots = [];
self.updateStars = function (newTier) {
self.tier = newTier;
// Remove existing dots
for (var i = 0; i < self.dots.length; i++) {
self.dots[i].destroy();
}
self.dots = [];
// Create new dots based on tier
var dotSpacing = 12;
var totalWidth = (self.tier - 1) * dotSpacing;
var startX = -totalWidth / 2;
for (var i = 0; i < self.tier; i++) {
var dot = self.attachAsset('star_dot', {
anchorX: 0.5,
anchorY: 0.5
});
dot.x = startX + i * dotSpacing;
dot.y = 0;
self.dots.push(dot);
}
};
// Initialize with current tier
self.updateStars(self.tier);
return self;
});
var Unit = Container.expand(function (tier) {
var self = Container.call(this);
self.tier = tier || 1;
var assetName = 'unit_tier' + self.tier;
var unitGraphics = self.attachAsset(assetName, {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 100;
self.maxHealth = 100;
self.damage = self.tier;
self.speed = 1; // Attack speed multiplier
self.fireRate = 60; // Base fire rate in ticks
self.lastShot = 0;
self.gridX = -1;
self.gridY = -1;
self.lane = -1;
self.range = 1; // Default range in cells (adjacent/melee). Override in subclasses for ranged units.
self.healthBar = null; // Initialize health bar as null
self.starIndicator = null; // Initialize star indicator as null
self.isAttacking = false; // Flag to track if unit is currently attacking
self.baseScale = 1.3; // Base scale multiplier for all units (30% larger)
// Apply base scale
self.scaleX = self.baseScale;
self.scaleY = self.baseScale;
self.showHealthBar = function () {
if (!self.healthBar) {
self.healthBar = new HealthBar(80, 10); // Create a health bar instance
self.healthBar.x = -40; // Position relative to the unit's center
self.healthBar.y = -60; // Position above the unit
self.addChild(self.healthBar);
}
self.healthBar.visible = true; // Ensure health bar is visible
self.healthBar.updateHealth(self.health, self.maxHealth);
};
self.showStarIndicator = function () {
if (!self.starIndicator) {
self.starIndicator = new StarIndicator(self.tier);
self.starIndicator.x = 0; // Center relative to unit
self.starIndicator.y = 55; // Position below the unit
self.addChild(self.starIndicator);
}
self.starIndicator.visible = true;
self.starIndicator.updateStars(self.tier);
// Scale unit based on tier - two star units are 30% larger
self.updateUnitScale();
};
self.updateUnitScale = function () {
// Check if bounce animation is in progress - if so, don't interfere
if (self._isBounceAnimating) {
return; // Skip scale update during bounce animation
}
// Stop any ongoing scale tweens first
tween.stop(self, {
scaleX: true,
scaleY: true
});
if (self.tier === 2) {
// Two star units are 20% larger on top of base scale
var targetScale = self.baseScale * 1.2;
self.scaleX = targetScale;
self.scaleY = targetScale;
tween(self, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 300,
easing: tween.easeOut
});
} else if (self.tier === 3) {
// Three star units are 30% larger on top of base scale
var targetScale = self.baseScale * 1.3;
self.scaleX = targetScale;
self.scaleY = targetScale;
tween(self, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 300,
easing: tween.easeOut
});
} else {
// Tier 1 units use base scale
self.scaleX = self.baseScale;
self.scaleY = self.baseScale;
tween(self, {
scaleX: self.baseScale,
scaleY: self.baseScale
}, {
duration: 300,
easing: tween.easeOut
});
}
};
self.hideHealthBar = function () {
if (self.healthBar) {
self.healthBar.destroy();
self.healthBar = null;
}
};
self.hideStarIndicator = function () {
if (self.starIndicator) {
self.starIndicator.destroy();
self.starIndicator = null;
}
};
self.updateHealthBar = function () {
if (self.healthBar) {
self.healthBar.updateHealth(self.health, self.maxHealth);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
} // Ensure health doesn't go below 0
self.updateHealthBar(); // Update health bar when taking damage
if (self.health <= 0) {
self.hideHealthBar(); // Hide health bar when destroyed
return true;
}
return false;
};
self.deathAnimation = function () {
// Hide health bar and star indicator immediately
if (self.healthBar) {
self.healthBar.destroy();
self.healthBar = null;
}
if (self.starIndicator) {
self.starIndicator.destroy();
self.starIndicator = null;
}
// Create particle explosion effect
var particleContainer = new Container();
particleContainer.x = self.x;
particleContainer.y = self.y;
self.parent.addChild(particleContainer);
// Number of particles based on unit type
var particleCount = 10; // Default for regular units
var particleColors = [];
var particleSize = 12;
var explosionRadius = 60;
// Customize particle colors based on unit type
if (self.name === "Soldier" || self.name === "Knight") {
particleColors = [0x32CD32, 0x228B22, 0x66ff66, 0x4da24d]; // Green shades
} else if (self.name === "Wizard") {
particleColors = [0x191970, 0x000066, 0x00FFFF, 0x4444FF]; // Blue/cyan shades
} else if (self.name === "Ranger") {
particleColors = [0x87CEEB, 0x9d9dff, 0x4169E1, 0x6495ED]; // Light blue shades
} else if (self.name === "Paladin") {
particleColors = [0x4169E1, 0x4444FF, 0xFFD700, 0x1E90FF]; // Royal blue with gold
} else if (self.name === "Wall") {
particleColors = [0x808080, 0x696969, 0x606060, 0x505050]; // Grey stone shades
particleSize = 15;
} else if (self.name === "Crossbow Tower") {
particleColors = [0x8B4513, 0x654321, 0x696969, 0x5C4033]; // Brown/grey shades
particleSize = 15;
} else if (self.name === "King") {
// King gets special treatment
particleColors = [0xFFD700, 0xFFFF00, 0xFF0000, 0x4B0082]; // Gold, yellow, red, purple
particleCount = 20;
particleSize = 18;
explosionRadius = 100;
} else {
// Default colors
particleColors = [0xFFFFFF, 0xCCCCCC, 0x999999, 0x666666];
}
// Create particles
var particles = [];
for (var i = 0; i < particleCount; i++) {
var particle = particleContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: particleSize + Math.random() * 8,
height: particleSize + Math.random() * 8
});
// Set particle color
particle.tint = particleColors[Math.floor(Math.random() * particleColors.length)];
particle.alpha = 0.8 + Math.random() * 0.2;
// Calculate random direction
var angle = Math.PI * 2 * i / particleCount + (Math.random() - 0.5) * 0.5;
var speed = explosionRadius * (0.7 + Math.random() * 0.3);
// Store particle data
particles.push({
sprite: particle,
targetX: Math.cos(angle) * speed,
targetY: Math.sin(angle) * speed,
rotationSpeed: (Math.random() - 0.5) * 0.2
});
}
// Animate unit shrinking and fading
tween(self, {
alpha: 0,
scaleX: self.scaleX * 0.5,
scaleY: self.scaleY * 0.5
}, {
duration: 200,
easing: tween.easeIn,
onFinish: function onFinish() {
// Destroy the unit
self.destroy();
}
});
// Animate particles bursting outward
for (var i = 0; i < particles.length; i++) {
var particleData = particles[i];
var particle = particleData.sprite;
// Burst outward animation
tween(particle, {
x: particleData.targetX,
y: particleData.targetY,
scaleX: 0.1,
scaleY: 0.1,
alpha: 0,
rotation: particle.rotation + particleData.rotationSpeed * 10
}, {
duration: 600 + Math.random() * 200,
easing: tween.easeOut,
onFinish: function onFinish() {
// Clean up after last particle
if (i === particles.length - 1) {
particleContainer.destroy();
}
}
});
}
// Add a brief flash effect at the center
var flash = particleContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: particleSize * 3,
height: particleSize * 3
});
flash.tint = 0xFFFFFF;
flash.alpha = 0.8;
tween(flash, {
scaleX: 3,
scaleY: 3,
alpha: 0
}, {
duration: 300,
easing: tween.easeOut
});
};
self.canShoot = function () {
// Use speed to modify fire rate - higher speed means faster attacks
var adjustedFireRate = Math.floor(self.fireRate / self.speed);
return LK.ticks - self.lastShot >= adjustedFireRate;
};
self.shoot = function (target) {
if (!self.canShoot()) {
return null;
}
self.lastShot = LK.ticks;
// For melee units (range 1), don't create bullets
if (self.range <= 1) {
// Melee attack: do incline movement using tween, then apply damage
var originalX = self.x;
var originalY = self.y;
// Calculate incline offset (move 30% toward target, max 40px)
var dx = target.x - self.x;
var dy = target.y - self.y;
var dist = Math.sqrt(dx * dx + dy * dy);
var offset = Math.min(40, dist * 0.3);
var inclineX = self.x + (dist > 0 ? dx / dist * offset : 0);
var inclineY = self.y + (dist > 0 ? dy / dist * offset : 0);
// Prevent multiple tweens at once
if (!self._isMeleeTweening) {
self._isMeleeTweening = true;
self.isAttacking = true; // Set attacking flag
tween(self, {
x: inclineX,
y: inclineY
}, {
duration: 80,
easing: tween.easeOut,
onFinish: function onFinish() {
// Apply damage after lunge
var killed = target.takeDamage(self.damage);
if (killed) {
self.isAttacking = false; // Clear attacking flag if target dies
// Don't give gold for killing enemies
LK.getSound('enemyDeath').play();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === target) {
// Trigger death animation instead of immediate destroy
enemies[i].deathAnimation();
enemies.splice(i, 1);
break;
}
}
}
// Tween back to original position
tween(self, {
x: originalX,
y: originalY
}, {
duration: 100,
easing: tween.easeIn,
onFinish: function onFinish() {
self._isMeleeTweening = false;
self.isAttacking = false; // Clear attacking flag after animation
}
});
}
});
}
return null; // No bullet for melee
}
// Ranged attack - create bullet
var bullet = new Bullet(self.damage, target);
bullet.x = self.x;
bullet.y = self.y;
// Customize bullet appearance based on unit type
if (self.name === "Ranger") {
// Archer units shoot arrows
bullet.bulletType = 'arrow';
bullet.createGraphics(); // Recreate graphics with correct type
} else if (self.name === "Wizard") {
// Wizard units shoot magic
bullet.bulletType = 'magic';
bullet.createGraphics(); // Recreate graphics with correct type
}
return bullet;
};
self.findTarget = function () {
var closestEnemy = null;
var closestCellDistance = self.range + 1; // Only enemies within range (cell count) are valid
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Calculate cell-based Manhattan distance
var cellDist = -1;
if (typeof self.gridX === "number" && typeof self.gridY === "number" && typeof enemy.gridX === "number" && typeof enemy.gridY === "number") {
cellDist = Math.abs(self.gridX - enemy.gridX) + Math.abs(self.gridY - enemy.gridY);
}
if (cellDist >= 0 && cellDist <= self.range && cellDist < closestCellDistance) {
closestCellDistance = cellDist;
closestEnemy = enemy;
}
}
return closestEnemy;
};
self.update = function () {
// Only initialize and update GridMovement if unit is placed on grid and during battle phase
if (self.gridX >= 0 && self.gridY >= 0 && gameState === 'battle') {
if (!self.gridMovement) {
self.gridMovement = new GridMovement(self, grid);
}
self.gridMovement.update();
}
// Add idle animation when not attacking
if (!self.isAttacking && self.gridX >= 0 && self.gridY >= 0) {
// Only start idle animation if not already animating
if (!self._isIdleAnimating) {
self._isIdleAnimating = true;
// Random delay before starting idle animation
var delay = Math.random() * 2000;
LK.setTimeout(function () {
if (!self.isAttacking && self._isIdleAnimating) {
// Calculate target scale based on tier
var targetScale = self.baseScale;
if (self.tier === 2) {
targetScale = self.baseScale * 1.2;
} else if (self.tier === 3) {
targetScale = self.baseScale * 1.3;
}
// Gentle bobbing animation that respects tier scale
tween(self, {
scaleX: targetScale * 1.05,
scaleY: targetScale * 0.95
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(self, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 800,
easing: tween.easeInOut,
onFinish: function onFinish() {
self._isIdleAnimating = false;
}
});
}
});
}
}, delay);
}
}
};
return self;
});
var Wizard = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart character container
var characterContainer = new Container();
self.addChild(characterContainer);
// Body (blue mage robe)
var body = characterContainer.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5
});
body.width = 65;
body.height = 85;
body.y = 10;
body.tint = 0x191970; // Midnight blue to match mage robe theme
// Head with wizard hat
var head = characterContainer.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
width: 30,
height: 30
});
head.y = -35;
head.tint = 0xFFDBB5; // Skin color
// Wizard hat (triangle)
var hat = characterContainer.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 35
});
hat.y = -55;
hat.tint = 0x000066;
// Staff (right side)
var staff = characterContainer.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
width: 10,
height: 80
});
staff.x = 35;
staff.y = -10;
staff.tint = 0x8B4513;
// Staff crystal
var staffCrystal = characterContainer.attachAsset('wizard', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 20
});
staffCrystal.x = 35;
staffCrystal.y = -50;
staffCrystal.tint = 0x00FFFF; // Cyan crystal
// Initial attributes for the unit
self.health = 150;
self.maxHealth = 150;
self.damage = 15;
self.armor = 8;
self.criticalChance = 0.18;
self.magicResist = 8;
self.mana = 70;
self.speed = 0.7;
self.range = 3; // Example: medium range (3 cells)
self.name = "Wizard";
return self;
});
var Wall = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart wall structure
var wallContainer = new Container();
self.addChild(wallContainer);
// Wall base (stone blocks)
var wallBase = wallContainer.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5
});
wallBase.width = 90;
wallBase.height = 80;
wallBase.y = 10;
wallBase.tint = 0x808080; // Grey stone
// Wall top section
var wallTop = wallContainer.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
width: 100,
height: 30
});
wallTop.y = -35;
wallTop.tint = 0x696969; // Darker grey
// Stone texture details (left)
var stoneLeft = wallContainer.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
width: 25,
height: 20
});
stoneLeft.x = -25;
stoneLeft.y = 0;
stoneLeft.tint = 0x606060;
// Stone texture details (right)
var stoneRight = wallContainer.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
width: 25,
height: 20
});
stoneRight.x = 25;
stoneRight.y = 0;
stoneRight.tint = 0x606060;
// Stone texture details (center)
var stoneCenter = wallContainer.attachAsset('wall', {
anchorX: 0.5,
anchorY: 0.5,
width: 30,
height: 15
});
stoneCenter.x = 0;
stoneCenter.y = 20;
stoneCenter.tint = 0x505050;
// Initial attributes for the wall
self.health = 300;
self.maxHealth = 300;
self.damage = 0; // Walls don't attack
self.armor = 20; // High defense
self.criticalChance = 0; // No critical chance
self.magicResist = 15;
self.mana = 0; // No mana
self.speed = 0; // Doesn't attack
self.range = 0; // No range
self.name = "Wall";
self.isStructure = true; // Mark as structure
// Override update to prevent movement
self.update = function () {
// Walls don't move or attack, just exist as obstacles
// Still need to show health bar during battle
if (gameState === 'battle' && self.gridX >= 0 && self.gridY >= 0) {
self.showHealthBar();
self.updateHealthBar();
self.showStarIndicator();
}
};
// Override shoot to prevent attacking
self.shoot = function (target) {
return null; // Walls can't shoot
};
// Override findTarget since walls don't attack
self.findTarget = function () {
return null;
};
return self;
});
var Soldier = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart character container
var characterContainer = new Container();
self.addChild(characterContainer);
// Body (green base)
var body = characterContainer.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 80
});
body.y = 10;
body.tint = 0x32CD32; // Lime green to match theme
// Head (lighter green circle)
var head = characterContainer.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
width: 40,
height: 40
});
head.y = -30;
head.tint = 0x66ff66;
// Arms (small rectangles)
var leftArm = characterContainer.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 40
});
leftArm.x = -30;
leftArm.y = 0;
leftArm.rotation = 0.3;
var rightArm = characterContainer.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 40
});
rightArm.x = 30;
rightArm.y = 0;
rightArm.rotation = -0.3;
// Sword (held in right hand)
var sword = characterContainer.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 50
});
sword.x = 35;
sword.y = -10;
sword.rotation = -0.2;
sword.tint = 0xC0C0C0; // Silver color
// Sword hilt
var swordHilt = characterContainer.attachAsset('soldier', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 8
});
swordHilt.x = 35;
swordHilt.y = 15;
swordHilt.rotation = -0.2;
swordHilt.tint = 0x8B4513; // Brown hilt
// Initial attributes for the unit
self.health = 120;
self.maxHealth = 120;
self.damage = 12;
self.armor = 6;
self.criticalChance = 0.15;
self.magicResist = 6;
self.mana = 60;
self.speed = 0.7;
self.range = 1; // Melee (adjacent cell)
self.name = "Soldier";
return self;
});
var Ranger = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart character container
var characterContainer = new Container();
self.addChild(characterContainer);
// Body (light blue ranger outfit)
var body = characterContainer.attachAsset('ranger', {
anchorX: 0.5,
anchorY: 0.5
});
body.width = 60;
body.height = 80;
body.y = 10;
body.tint = 0x87CEEB; // Sky blue to match ranger theme
// Head with hood
var head = characterContainer.attachAsset('ranger', {
anchorX: 0.5,
anchorY: 0.5,
width: 30,
height: 30
});
head.y = -35;
head.tint = 0xFFDBB5; // Skin color
// Hood
var hood = characterContainer.attachAsset('ranger', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 40
});
hood.y = -40;
hood.tint = 0x9d9dff;
// Bow (left side)
var bow = characterContainer.attachAsset('ranger', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 15
});
bow.x = -30;
bow.y = 0;
bow.rotation = 1.57; // 90 degrees
bow.tint = 0x8B4513;
// Bowstring
var bowstring = characterContainer.attachAsset('ranger', {
anchorX: 0.5,
anchorY: 0.5,
width: 2,
height: 45
});
bowstring.x = -30;
bowstring.y = 0;
bowstring.rotation = 1.57;
bowstring.tint = 0xFFFFFF; // White string
// Arrow on right hand
var arrow = characterContainer.attachAsset('ranger', {
anchorX: 0.5,
anchorY: 0.5,
width: 4,
height: 35
});
arrow.x = 25;
arrow.y = -5;
arrow.rotation = -0.1;
arrow.tint = 0x654321; // Dark brown arrow
// Arrow tip
var arrowTip = characterContainer.attachAsset('ranger', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8
});
arrowTip.x = 25;
arrowTip.y = -22;
arrowTip.rotation = 0.785; // 45 degrees
arrowTip.tint = 0x808080; // Grey tip
// Initial attributes for the unit
self.health = 170;
self.maxHealth = 170;
self.damage = 17;
self.armor = 10;
self.criticalChance = 0.22;
self.magicResist = 10;
self.mana = 80;
self.speed = 0.7;
self.range = 2; // Example: short range (2 cells)
self.name = "Ranger";
return self;
});
var Paladin = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart character container
var characterContainer = new Container();
self.addChild(characterContainer);
// Body (blue armor)
var body = characterContainer.attachAsset('paladin', {
anchorX: 0.5,
anchorY: 0.5
});
body.width = 75;
body.height = 75;
body.y = 10;
body.tint = 0x4169E1; // Royal blue to match armor theme
// Head
var head = characterContainer.attachAsset('paladin', {
anchorX: 0.5,
anchorY: 0.5,
width: 35,
height: 35
});
head.y = -35;
head.tint = 0xFFDBB5; // Skin color
// Helmet
var helmet = characterContainer.attachAsset('paladin', {
anchorX: 0.5,
anchorY: 0.5,
width: 45,
height: 25
});
helmet.y = -45;
helmet.tint = 0x4444FF;
// Sword (right side)
var sword = characterContainer.attachAsset('paladin', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 60
});
sword.x = 35;
sword.y = -5;
sword.rotation = -0.2;
sword.tint = 0xC0C0C0;
// Sword pommel
var swordPommel = characterContainer.attachAsset('paladin', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 12
});
swordPommel.x = 35;
swordPommel.y = 20;
swordPommel.tint = 0xFFD700; // Gold pommel
// Initial attributes for the unit
self.health = 160;
self.maxHealth = 160;
self.damage = 16;
self.armor = 9;
self.criticalChance = 0.2;
self.magicResist = 9;
self.mana = 75;
self.speed = 0.7;
self.range = 1; // Example: short range (2 cells)
self.name = "Paladin";
return self;
});
var Knight = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart character container
var characterContainer = new Container();
self.addChild(characterContainer);
// Body (darker green base)
var body = characterContainer.attachAsset('knight', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 70
});
body.y = 10;
body.tint = 0x228B22; // Forest green to match theme
// Head (round shape)
var head = characterContainer.attachAsset('knight', {
anchorX: 0.5,
anchorY: 0.5,
width: 35,
height: 35
});
head.y = -35;
head.tint = 0x4da24d;
// Shield (left side)
var shield = characterContainer.attachAsset('knight', {
anchorX: 0.5,
anchorY: 0.5,
width: 30,
height: 45
});
shield.x = -35;
shield.y = 0;
shield.tint = 0x666666;
// Sword (right side)
var sword = characterContainer.attachAsset('knight', {
anchorX: 0.5,
anchorY: 0.5,
width: 10,
height: 45
});
sword.x = 30;
sword.y = -5;
sword.rotation = -0.15;
sword.tint = 0xC0C0C0; // Silver color
// Sword guard
var swordGuard = characterContainer.attachAsset('knight', {
anchorX: 0.5,
anchorY: 0.5,
width: 18,
height: 6
});
swordGuard.x = 30;
swordGuard.y = 13;
swordGuard.rotation = -0.15;
swordGuard.tint = 0x808080; // Dark grey
// Initial attributes for the unit
self.health = 110;
self.maxHealth = 110;
self.damage = 11;
self.armor = 5;
self.criticalChance = 0.12;
self.magicResist = 5;
self.mana = 55;
self.speed = 0.7;
self.range = 1; // Melee (adjacent cell)
self.name = "Knight";
return self;
});
var King = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart king character
var kingContainer = new Container();
self.addChild(kingContainer);
// King body (royal robe)
var body = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5
});
body.width = 80;
body.height = 100;
body.y = 15;
body.tint = 0x4B0082; // Royal purple
// King head (flesh tone)
var head = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
head.x = 0;
head.y = -40;
head.tint = 0xFFDBB5; // Skin color
// Crown base
var crownBase = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 20
});
crownBase.x = 0;
crownBase.y = -65;
crownBase.tint = 0xFFFF00; // Yellow
// Crown points (left)
var crownLeft = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 25
});
crownLeft.x = -20;
crownLeft.y = -75;
crownLeft.tint = 0xFFFF00; // Yellow
// Crown points (center - tallest)
var crownCenter = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 35
});
crownCenter.x = 0;
crownCenter.y = -80;
crownCenter.tint = 0xFFFF00; // Yellow
// Crown points (right)
var crownRight = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 25
});
crownRight.x = 20;
crownRight.y = -75;
crownRight.tint = 0xFFFF00; // Yellow
// Crown jewel (center)
var crownJewel = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 8
});
crownJewel.x = 0;
crownJewel.y = -80;
crownJewel.tint = 0xFF0000; // Red jewel
// Beard
var beard = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 30,
height: 20
});
beard.x = 0;
beard.y = -25;
beard.tint = 0xC0C0C0; // Grey beard
// Sword (right hand)
var sword = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 80
});
sword.x = 45;
sword.y = 0;
sword.rotation = -0.2;
sword.tint = 0xC0C0C0; // Silver blade
// Sword hilt
var swordHilt = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 25,
height: 12
});
swordHilt.x = 45;
swordHilt.y = 35;
swordHilt.rotation = -0.2;
swordHilt.tint = 0xC0C0C0; // Silver hilt
// Royal cape/cloak
var cape = kingContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 80
});
cape.x = -5;
cape.y = 20;
cape.tint = 0x8B0000; // Dark red cape
// Initial attributes for the king (like a very strong unit)
self.health = 500;
self.maxHealth = 500;
self.damage = 100;
self.armor = 10;
self.criticalChance = 0.05;
self.magicResist = 8;
self.mana = 100;
self.speed = 0.5; // Slower attack speed
self.range = 1; // Melee range
self.name = "King";
self.fireRate = 120; // Slower fire rate
// Override takeDamage to handle king-specific game over
self.takeDamage = function (damage) {
self.health -= damage;
if (self.health < 0) {
self.health = 0;
}
self.updateHealthBar();
if (self.health <= 0) {
LK.effects.flashScreen(0xFF0000, 500);
LK.showGameOver();
return true;
}
return false;
};
// Override update - King can move if it's the only unit left
self.update = function () {
// Check if king is the only player unit left on the battlefield
var playerUnitsOnGrid = 0;
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x] && grid[y][x].unit) {
playerUnitsOnGrid++;
}
}
}
// If king is the only unit left, allow movement
if (playerUnitsOnGrid === 1) {
// Only initialize and update GridMovement if unit is placed on grid
if (self.gridX >= 0 && self.gridY >= 0) {
if (!self.gridMovement) {
self.gridMovement = new GridMovement(self, grid);
}
self.gridMovement.update();
}
}
// King stays in place if other units exist
};
// Override showStarIndicator to prevent King from showing stars
self.showStarIndicator = function () {
// King doesn't show star indicators
};
// Override showHealthBar for king-specific styling
self.showHealthBar = function () {
if (!self.healthBar) {
self.healthBar = new HealthBar(150, 10); // Larger health bar for king
self.healthBar.x = -75; // Position relative to the king's center
self.healthBar.y = -110; // Position above the king (higher due to crown)
self.addChild(self.healthBar);
}
self.healthBar.visible = true;
self.healthBar.updateHealth(self.health, self.maxHealth);
};
// Override hideStarIndicator to handle the case where it might be called
self.hideStarIndicator = function () {
// King doesn't have star indicators, so nothing to hide
};
// Override updateUnitScale to prevent tier-based scaling for King
self.updateUnitScale = function () {
// King doesn't scale based on tier, only through levelUpKing function
};
return self;
});
var CrossbowTower = Unit.expand(function () {
var self = Unit.call(this, 1);
// Remove default unit graphics
self.removeChildren();
// Create pixelart crossbow tower structure
var towerContainer = new Container();
self.addChild(towerContainer);
// Tower base (stone foundation)
var towerBase = towerContainer.attachAsset('crossbow_tower', {
anchorX: 0.5,
anchorY: 0.5
});
towerBase.width = 80;
towerBase.height = 60;
towerBase.y = 30;
towerBase.tint = 0x696969; // Dark grey stone
// Tower middle section
var towerMiddle = towerContainer.attachAsset('crossbow_tower', {
anchorX: 0.5,
anchorY: 0.5,
width: 70,
height: 50
});
towerMiddle.y = -10;
towerMiddle.tint = 0x808080; // Grey stone
// Tower top platform
var towerTop = towerContainer.attachAsset('crossbow_tower', {
anchorX: 0.5,
anchorY: 0.5,
width: 90,
height: 20
});
towerTop.y = -40;
towerTop.tint = 0x5C4033; // Brown wood platform
// Crossbow mechanism (horizontal)
var crossbowBase = towerContainer.attachAsset('crossbow_tower', {
anchorX: 0.5,
anchorY: 0.5,
width: 60,
height: 15
});
crossbowBase.y = -55;
crossbowBase.rotation = 0;
crossbowBase.tint = 0x8B4513; // Saddle brown
// Crossbow arms (vertical)
var crossbowArms = towerContainer.attachAsset('crossbow_tower', {
anchorX: 0.5,
anchorY: 0.5,
width: 8,
height: 50
});
crossbowArms.y = -55;
crossbowArms.rotation = 1.57; // 90 degrees
crossbowArms.tint = 0x654321; // Dark brown
// Crossbow string
var crossbowString = towerContainer.attachAsset('crossbow_tower', {
anchorX: 0.5,
anchorY: 0.5,
width: 2,
height: 48
});
crossbowString.y = -55;
crossbowString.rotation = 1.57;
crossbowString.tint = 0xDDDDDD; // Light grey string
// Loaded bolt
var loadedBolt = towerContainer.attachAsset('crossbow_tower', {
anchorX: 0.5,
anchorY: 0.5,
width: 4,
height: 30
});
loadedBolt.x = 0;
loadedBolt.y = -55;
loadedBolt.rotation = 0;
loadedBolt.tint = 0x4B0082; // Dark purple bolt
// Initial attributes for the crossbow tower
self.health = 200;
self.maxHealth = 200;
self.damage = 25; // Higher damage than regular ranged units
self.armor = 12;
self.criticalChance = 0.25; // High critical chance
self.magicResist = 10;
self.mana = 0; // No mana
self.speed = 0.8; // Slower attack speed
self.range = 4; // Long range
self.name = "Crossbow Tower";
self.isStructure = true; // Mark as structure
self.fireRate = 90; // Slower fire rate than mobile units
// Override update to prevent movement but allow shooting
self.update = function () {
// Towers don't move but can attack
if (gameState === 'battle' && self.gridX >= 0 && self.gridY >= 0) {
self.showHealthBar();
self.updateHealthBar();
self.showStarIndicator();
}
};
// Override shoot to create arrow projectiles
self.shoot = function (target) {
if (!self.canShoot()) {
return null;
}
self.lastShot = LK.ticks;
// Create arrow projectile
var bullet = new Bullet(self.damage, target);
bullet.x = self.x;
bullet.y = self.y - 55; // Shoot from crossbow position
// Crossbow towers shoot arrows
bullet.bulletType = 'arrow';
bullet.createGraphics();
// Add shooting animation - rotate crossbow slightly
tween(crossbowBase, {
rotation: -0.1
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(crossbowBase, {
rotation: 0
}, {
duration: 200,
easing: tween.easeIn
});
}
});
return bullet;
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
// Create game title overlay that appears on top of everything
var titleOverlay = new Container();
// Create gradient-like background effect with multiple layers
var titleOverlayBg = titleOverlay.attachAsset('shop_area_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732
});
titleOverlayBg.alpha = 0.95;
titleOverlayBg.tint = 0x0a0a1a; // Deep dark blue instead of pure black
titleOverlayBg.x = 0; // Center relative to parent
titleOverlayBg.y = 0; // Center relative to parent
// Add subtle vignette effect
var vignette = titleOverlay.attachAsset('shop_area_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 2200,
height: 2900
});
vignette.alpha = 0.3;
vignette.tint = 0x000000;
vignette.x = 0;
vignette.y = 0;
// Create magical particle system for background
var particleContainer = new Container();
titleOverlay.addChild(particleContainer);
// Create floating magical particles
function createMagicalParticle() {
var particle = particleContainer.attachAsset('star_dot', {
anchorX: 0.5,
anchorY: 0.5,
width: 6 + Math.random() * 8,
height: 6 + Math.random() * 8
});
// Random colors for magical effect
var colors = [0xFFD700, 0x4169E1, 0xFF69B4, 0x00CED1, 0xFFA500];
particle.tint = colors[Math.floor(Math.random() * colors.length)];
particle.alpha = 0;
// Random starting position
particle.x = (Math.random() - 0.5) * 2048;
particle.y = 1366 + Math.random() * 800;
// Animate particle floating up with glow
tween(particle, {
y: particle.y - 2000,
alpha: 0.8
}, {
duration: 2000,
easing: tween.easeIn,
onFinish: function onFinish() {
tween(particle, {
alpha: 0
}, {
duration: 1000,
easing: tween.easeOut,
onFinish: function onFinish() {
particle.destroy();
}
});
}
});
// Add gentle horizontal sway
tween(particle, {
x: particle.x + (Math.random() - 0.5) * 200
}, {
duration: 4000,
easing: tween.easeInOut
});
}
// Create particles periodically
var particleTimer = LK.setInterval(function () {
if (titleOverlay.parent) {
createMagicalParticle();
} else {
LK.clearInterval(particleTimer);
}
}, 200);
// Create title container for effects
var titleContainer = new Container();
titleOverlay.addChild(titleContainer);
titleContainer.y = -566;
// Add golden glow behind title
var titleGlow = titleContainer.attachAsset('shop_area_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 800,
height: 200
});
titleGlow.alpha = 0.3;
titleGlow.tint = 0xFFD700;
// Animate glow pulse
tween(titleGlow, {
scaleX: 1.2,
scaleY: 1.2,
alpha: 0.5
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(titleGlow, {
scaleX: 1.0,
scaleY: 1.0,
alpha: 0.3
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
// Game title with shadow effect
var gameTitleShadow = new Text2('Protect Your King!', {
size: 120,
fill: 0x000000
});
gameTitleShadow.anchor.set(0.5, 0.5);
gameTitleShadow.x = 4;
gameTitleShadow.y = 4;
gameTitleShadow.alpha = 0.5;
titleContainer.addChild(gameTitleShadow);
// Main game title
var gameTitle = new Text2('Protect Your King!', {
size: 120,
fill: 0xFFD700
});
gameTitle.anchor.set(0.5, 0.5);
gameTitle.x = 0;
gameTitle.y = 0;
titleContainer.addChild(gameTitle);
// Add sparkle effects around title
function createTitleSparkle() {
var sparkle = titleContainer.attachAsset('star_dot', {
anchorX: 0.5,
anchorY: 0.5,
width: 10,
height: 10
});
sparkle.tint = 0xFFFFFF;
sparkle.alpha = 0;
sparkle.x = (Math.random() - 0.5) * 600;
sparkle.y = (Math.random() - 0.5) * 150;
tween(sparkle, {
alpha: 1,
scaleX: 1.5,
scaleY: 1.5
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(sparkle, {
alpha: 0,
scaleX: 0.5,
scaleY: 0.5
}, {
duration: 500,
easing: tween.easeIn,
onFinish: function onFinish() {
sparkle.destroy();
}
});
}
});
}
// Create sparkles periodically
var sparkleTimer = LK.setInterval(function () {
if (titleOverlay.parent) {
createTitleSparkle();
} else {
LK.clearInterval(sparkleTimer);
}
}, 300);
// Subtitle with shadow
var subtitleShadow = new Text2('Tower Defense', {
size: 60,
fill: 0x000000
});
subtitleShadow.anchor.set(0.5, 0.5);
subtitleShadow.x = 2;
subtitleShadow.y = -464;
subtitleShadow.alpha = 0.5;
titleOverlay.addChild(subtitleShadow);
// Subtitle
var gameSubtitle = new Text2('Tower Defense', {
size: 60,
fill: 0x87CEEB
});
gameSubtitle.anchor.set(0.5, 0.5);
gameSubtitle.x = 0; // Center relative to overlay
gameSubtitle.y = -466; // Adjusted position relative to center
titleOverlay.addChild(gameSubtitle);
// Add subtle animation to subtitle
tween(gameSubtitle, {
scaleX: 1.05,
scaleY: 1.05
}, {
duration: 3000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(gameSubtitle, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 3000,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
// Start Game button container
var startGameButton = new Container();
// Button shadow
var startButtonShadow = startGameButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 100
});
startButtonShadow.tint = 0x000000;
startButtonShadow.alpha = 0.3;
startButtonShadow.x = 4;
startButtonShadow.y = 4;
// Button gradient effect background
var startButtonGlow = startGameButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 420,
height: 120
});
startButtonGlow.tint = 0x66FF66;
startButtonGlow.alpha = 0;
// Main button background
var startGameButtonBg = startGameButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 100
});
startGameButtonBg.tint = 0x228B22; // Forest green
// Button text with shadow
var startButtonTextShadow = new Text2('Start Game', {
size: 60,
fill: 0x000000
});
startButtonTextShadow.anchor.set(0.5, 0.5);
startButtonTextShadow.x = 2;
startButtonTextShadow.y = 2;
startButtonTextShadow.alpha = 0.5;
startGameButton.addChild(startButtonTextShadow);
var startGameButtonText = new Text2('Start Game', {
size: 60,
fill: 0xFFFFFF
});
startGameButtonText.anchor.set(0.5, 0.5);
startGameButton.addChild(startGameButtonText);
startGameButton.x = 0; // Center relative to overlay
startGameButton.y = -266; // Adjusted position relative to center
titleOverlay.addChild(startGameButton);
// Add hover animation
startGameButton.down = function () {
tween(startGameButtonBg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut
});
tween(startButtonGlow, {
alpha: 0.5,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut
});
};
// How to Play button container
var howToPlayButton = new Container();
// Button shadow
var howToPlayShadow = howToPlayButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 100
});
howToPlayShadow.tint = 0x000000;
howToPlayShadow.alpha = 0.3;
howToPlayShadow.x = 4;
howToPlayShadow.y = 4;
// Button glow effect
var howToPlayGlow = howToPlayButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 420,
height: 120
});
howToPlayGlow.tint = 0x6666FF;
howToPlayGlow.alpha = 0;
// Main button background
var howToPlayButtonBg = howToPlayButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 100
});
howToPlayButtonBg.tint = 0x4169E1; // Royal blue
// Button text with shadow
var howToPlayTextShadow = new Text2('How to Play', {
size: 60,
fill: 0x000000
});
howToPlayTextShadow.anchor.set(0.5, 0.5);
howToPlayTextShadow.x = 2;
howToPlayTextShadow.y = 2;
howToPlayTextShadow.alpha = 0.5;
howToPlayButton.addChild(howToPlayTextShadow);
var howToPlayButtonText = new Text2('How to Play', {
size: 60,
fill: 0xFFFFFF
});
howToPlayButtonText.anchor.set(0.5, 0.5);
howToPlayButton.addChild(howToPlayButtonText);
howToPlayButton.x = 0; // Center relative to overlay
howToPlayButton.y = -116; // Adjusted position relative to center
titleOverlay.addChild(howToPlayButton);
// Add hover animation
howToPlayButton.down = function () {
tween(howToPlayButtonBg, {
scaleX: 0.95,
scaleY: 0.95
}, {
duration: 100,
easing: tween.easeOut
});
tween(howToPlayGlow, {
alpha: 0.5,
scaleX: 1.1,
scaleY: 1.1
}, {
duration: 200,
easing: tween.easeOut
});
};
// How to Play button click handler
howToPlayButton.up = function () {
// Reset button scale
tween(howToPlayButtonBg, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
tween(howToPlayGlow, {
alpha: 0
}, {
duration: 200,
easing: tween.easeIn
});
// Play sound effect
LK.getSound('purchase').play();
// Create how to play modal
var howToPlayModal = new Container();
var howToPlayModalBg = howToPlayModal.attachAsset('shop_area_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 1600,
height: 2000
});
howToPlayModalBg.alpha = 0.95;
howToPlayModalBg.tint = 0x1a1a2e; // Dark blue theme
howToPlayModalBg.x = 0;
howToPlayModalBg.y = 0;
// Modal title with fun subtitle
var modalTitle = new Text2('How to Play', {
size: 80,
fill: 0xFFD700
});
modalTitle.anchor.set(0.5, 0.5);
modalTitle.x = 0; // Center relative to modal
modalTitle.y = -900; // Adjusted position relative to center
howToPlayModal.addChild(modalTitle);
// Fun subtitle
var subtitle = new Text2('Master the Art of Tower Defense!', {
size: 40,
fill: 0x44FF44
});
subtitle.anchor.set(0.5, 0.5);
subtitle.x = 0;
subtitle.y = -820;
howToPlayModal.addChild(subtitle);
// Game mechanics section
var mechanicsTitle = new Text2('— Game Basics —', {
size: 48,
fill: 0xFFD700
});
mechanicsTitle.anchor.set(0.5, 0.5);
mechanicsTitle.x = 0;
mechanicsTitle.y = -700;
howToPlayModal.addChild(mechanicsTitle);
// Instructions with better spacing
var basicInstructions = ['💰 Purchase units from the shop using gold', '🎯 Drag units to the battlefield grid', '⚔️ Units auto-battle enemies in range', '⭐ Merge 3 identical units for upgrades', '👑 Level up your King for more capacity', '🛡️ Protect your King at all costs!', '🌊 Survive 10 waves to claim victory!'];
for (var i = 0; i < basicInstructions.length; i++) {
var instructionText = new Text2(basicInstructions[i], {
size: 36,
fill: 0xFFFFFF
});
instructionText.anchor.set(0.5, 0.5);
instructionText.x = 0; // Horizontally centered
instructionText.y = -550 + i * 60; // Better spacing
howToPlayModal.addChild(instructionText);
}
// Unit showcase title
var unitsTitle = new Text2('— Meet Your Units —', {
size: 48,
fill: 0xFFD700
});
unitsTitle.anchor.set(0.5, 0.5);
unitsTitle.x = 0;
unitsTitle.y = -50;
howToPlayModal.addChild(unitsTitle);
// Create unit showcase with actual unit graphics
var unitShowcase = [{
unit: new Soldier(),
desc: 'Soldier: Swift melee warrior',
x: -600,
y: 100
}, {
unit: new Knight(),
desc: 'Knight: Tanky defender',
x: -200,
y: 100
}, {
unit: new Wizard(),
desc: 'Wizard: Magic damage dealer',
x: 200,
y: 100
}, {
unit: new Ranger(),
desc: 'Ranger: Long-range archer',
x: 600,
y: 100
}, {
unit: new Paladin(),
desc: 'Paladin: Elite warrior',
x: -400,
y: 350
}, {
unit: new Wall(),
desc: 'Wall: Defensive structure',
x: 0,
y: 350
}, {
unit: new CrossbowTower(),
desc: 'Tower: Area controller',
x: 400,
y: 350
}];
// Add units with animations
for (var i = 0; i < unitShowcase.length; i++) {
var showcase = unitShowcase[i];
var unitDisplay = showcase.unit;
unitDisplay.x = showcase.x;
unitDisplay.y = showcase.y;
unitDisplay.scaleX = 0.8;
unitDisplay.scaleY = 0.8;
howToPlayModal.addChild(unitDisplay);
// Add floating animation
var baseY = unitDisplay.y;
tween(unitDisplay, {
y: baseY - 15,
scaleX: 0.85,
scaleY: 0.85
}, {
duration: 1500 + Math.random() * 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(unitDisplay, {
y: baseY,
scaleX: 0.8,
scaleY: 0.8
}, {
duration: 1500 + Math.random() * 500,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
// Add unit description
var unitDesc = new Text2(showcase.desc, {
size: 28,
fill: 0xFFFFFF
});
unitDesc.anchor.set(0.5, 0);
unitDesc.x = showcase.x;
unitDesc.y = showcase.y + 60;
howToPlayModal.addChild(unitDesc);
}
// Enemy warning section
var enemyWarning = new Text2('⚠️ Enemies spawn from the fog of war above! ⚠️', {
size: 40,
fill: 0xFF4444
});
enemyWarning.anchor.set(0.5, 0.5);
enemyWarning.x = 0;
enemyWarning.y = 600;
howToPlayModal.addChild(enemyWarning);
// Tips section
var tipsText = new Text2('💡 Pro Tip: Save gold for interest bonus!', {
size: 36,
fill: 0x44FF44
});
tipsText.anchor.set(0.5, 0.5);
tipsText.x = 0;
tipsText.y = 700;
howToPlayModal.addChild(tipsText);
// Close button
var closeButton = new Container();
var closeButtonBg = closeButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100
});
closeButtonBg.tint = 0xFF4444;
var closeButtonText = new Text2('Got it!', {
size: 60,
fill: 0xFFFFFF
});
closeButtonText.anchor.set(0.5, 0.5);
closeButton.addChild(closeButtonText);
closeButton.x = 0; // Center relative to modal
closeButton.y = 850; // Bottom of modal
howToPlayModal.addChild(closeButton);
closeButton.up = function () {
howToPlayModal.destroy();
};
titleOverlay.addChild(howToPlayModal);
};
// Start Game button click handler
startGameButton.up = function () {
// Reset button scale
tween(startGameButtonBg, {
scaleX: 1.0,
scaleY: 1.0
}, {
duration: 100,
easing: tween.easeIn
});
tween(startButtonGlow, {
alpha: 0
}, {
duration: 200,
easing: tween.easeIn
});
// Play purchase sound for feedback
LK.getSound('purchase').play();
// Create expanding circle transition effect
var transitionCircle = titleOverlay.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 50,
height: 50
});
transitionCircle.tint = 0xFFD700;
transitionCircle.x = 0;
transitionCircle.y = -266; // Start from button position
tween(transitionCircle, {
scaleX: 100,
scaleY: 100,
alpha: 0.8
}, {
duration: 600,
easing: tween.easeOut
});
// Hide title overlay with fade animation
tween(titleOverlay, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
// Clear timers
LK.clearInterval(particleTimer);
LK.clearInterval(sparkleTimer);
titleOverlay.destroy();
}
});
};
// Add title overlay to LK.gui.center to ensure it renders above all game elements
LK.gui.center.addChild(titleOverlay);
// No need to set x,y as gui.center is already centered
// Add enhanced title animation with rotation
tween(titleContainer, {
scaleX: 1.05,
scaleY: 1.05,
rotation: 0.02
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(titleContainer, {
scaleX: 0.95,
scaleY: 0.95,
rotation: -0.02
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
// Add decorative crown above title
var crownContainer = new Container();
titleOverlay.addChild(crownContainer);
crownContainer.y = -666;
// Create pixelart crown
var crownBase = crownContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 30
});
crownBase.tint = 0xFFD700;
var crownLeft = crownContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 40
});
crownLeft.x = -25;
crownLeft.y = -20;
crownLeft.tint = 0xFFD700;
var crownCenter = crownContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 25,
height: 50
});
crownCenter.x = 0;
crownCenter.y = -25;
crownCenter.tint = 0xFFD700;
var crownRight = crownContainer.attachAsset('king', {
anchorX: 0.5,
anchorY: 0.5,
width: 20,
height: 40
});
crownRight.x = 25;
crownRight.y = -20;
crownRight.tint = 0xFFD700;
// Add jewels to crown
var jewelLeft = crownContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 12
});
jewelLeft.x = -25;
jewelLeft.y = -35;
jewelLeft.tint = 0xFF0000;
var jewelCenter = crownContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 15,
height: 15
});
jewelCenter.x = 0;
jewelCenter.y = -45;
jewelCenter.tint = 0x4169E1;
var jewelRight = crownContainer.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5,
width: 12,
height: 12
});
jewelRight.x = 25;
jewelRight.y = -35;
jewelRight.tint = 0xFF0000;
// Animate crown floating
tween(crownContainer, {
y: -656,
rotation: 0.05
}, {
duration: 2500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(crownContainer, {
y: -666,
rotation: -0.05
}, {
duration: 2500,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
var GRID_COLS = 9;
var GRID_ROWS = 9;
var GRID_CELL_SIZE = 227; // 2048 / 9 ≈ 227
var GRID_START_X = (2048 - GRID_COLS * GRID_CELL_SIZE) / 2;
var GRID_START_Y = 100;
var PLAYABLE_HEIGHT = 2400; // Define playable game area
var enemies = [];
var units = [];
var bullets = [];
var enemyProjectiles = [];
var bench = [];
var grid = [];
var benchSlots = [];
var gold = 5;
var wave = 1;
var enemiesSpawned = 0;
var enemiesPerWave = 5;
var waveDelay = 0;
var draggedUnit = null;
var draggedFromGrid = false;
var gameState = 'planning'; // 'planning' or 'battle'
var goldText; // Declare goldText variable
var kingInfoButton; // Declare kingInfoButton variable
var cellReservations = {}; // Track reserved cells during movement
var entityPositions = {}; // Track exact entity positions to prevent overlap
var movementQueue = []; // Queue movements to prevent simultaneous conflicts
var playerLevel = 1; // Player's current level
var maxUnitsOnField = 2; // Starting with 2 units max
var maxStructuresOnField = 2; // Starting with 2 structures max
var structuresOnField = 0; // Track structures placed
var unitsOnField = 0; // Track regular units placed
var enemyContainer = new Container(); // Container for enemies to render below fog
var topUIContainer = new Container(); // Container for top UI elements
var isUIMinimized = false; // Track UI state
// Helper function to register entity position
function registerEntityPosition(entity) {
var posKey = entity.gridX + ',' + entity.gridY;
entityPositions[posKey] = entity;
}
// Helper function to unregister entity position
function unregisterEntityPosition(entity, oldGridX, oldGridY) {
var posKey = oldGridX + ',' + oldGridY;
if (entityPositions[posKey] === entity) {
delete entityPositions[posKey];
}
}
// Helper function to check if position is occupied by any entity
function isPositionOccupied(gridX, gridY, excludeEntity) {
var posKey = gridX + ',' + gridY;
var occupant = entityPositions[posKey];
return occupant && occupant !== excludeEntity;
}
// Function to calculate level up cost
function getLevelUpCost(level) {
return level * 4; // Cost increases by 4 gold per level
}
// Function to count units on field
function countUnitsOnField() {
structuresOnField = 0;
unitsOnField = 0;
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
if (grid[y] && grid[y][x] && grid[y][x].unit) {
var unit = grid[y][x].unit;
// Skip king - it doesn't count towards any limit
if (unit.name === "King") {
continue; // Don't count king
}
if (unit.isStructure) {
structuresOnField++;
} else {
unitsOnField++;
}
}
}
}
// Update the max units text display
maxUnitsText.setText('Units: ' + unitsOnField + '/' + maxUnitsOnField + ' | Structures: ' + structuresOnField + '/' + maxStructuresOnField);
}
// Function to check if can place more units
function canPlaceMoreUnits(isStructure) {
countUnitsOnField();
if (isStructure) {
return structuresOnField < maxStructuresOnField;
} else {
return unitsOnField < maxUnitsOnField;
}
}
// Function to level up player
function levelUpPlayer() {
var cost = getLevelUpCost(playerLevel);
if (gold >= cost) {
gold -= cost;
playerLevel++;
maxUnitsOnField++; // Increase unit limit by 1
maxStructuresOnField++; // Increase structure limit by 1
goldText.setText(gold.toString());
levelText.setText('King Lvl: ' + playerLevel);
countUnitsOnField(); // Update counts and display
// Level up the king
levelUpKing();
// Play purchase sound
LK.getSound('purchase').play();
// Visual effect
LK.effects.flashScreen(0xFFD700, 500);
}
}
// Function to level up king
function levelUpKing() {
if (kings.length > 0) {
var king = kings[0];
// Increase king stats
king.health = Math.floor(king.health * 1.1);
king.maxHealth = Math.floor(king.maxHealth * 1.1);
king.damage = Math.floor(king.damage * 1.1);
king.armor = Math.floor(king.armor * 1.1);
king.magicResist = Math.floor(king.magicResist * 1.1);
// Increase king scale by 10%
var currentScale = king.scaleX;
var newScale = currentScale * 1.1;
tween(king, {
scaleX: newScale,
scaleY: newScale
}, {
duration: 500,
easing: tween.easeOut
});
// Update health bar
king.updateHealthBar();
// Flash effect
LK.effects.flashObject(king, 0xFFD700, 800);
}
}
// AttackInfo display removed
// Double-tap detection variables
var lastTapTime = 0;
var lastTapX = 0;
var lastTapY = 0;
var doubleTapDelay = 300; // 300ms window for double tap
var doubleTapDistance = 50; // Max distance between taps
// Add enemy container to game first (so it renders below everything else)
game.addChild(enemyContainer);
// Create grid slots
for (var y = 0; y < GRID_ROWS; y++) {
grid[y] = [];
for (var x = 0; x < GRID_COLS; x++) {
var gridSlot = game.addChild(new GridSlot(0, x, y)); // Using lane 0 for all slots
// Calculate exact position for grid slot
var slotX = GRID_START_X + x * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
var slotY = GRID_START_Y + y * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
gridSlot.x = slotX;
gridSlot.y = slotY;
grid[y][x] = gridSlot;
}
}
// Create fog of war containers array to add later (after enemies spawn)
var fogOverlays = [];
// Create fog of war for top two rows
for (var y = 0; y < 2; y++) {
for (var x = 0; x < GRID_COLS; x++) {
var fogOverlay = new Container();
var fogGraphics = fogOverlay.attachAsset('grid_slot', {
anchorX: 0.5,
anchorY: 0.5,
width: GRID_CELL_SIZE - 10,
height: GRID_CELL_SIZE - 10
});
fogGraphics.tint = 0x000000; // Black fog
fogGraphics.alpha = 0.3; // More transparent to see enemies underneath
fogOverlay.x = GRID_START_X + x * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
fogOverlay.y = GRID_START_Y + y * GRID_CELL_SIZE + GRID_CELL_SIZE / 2;
// Add some fog texture variation
var fogDetail = fogOverlay.attachAsset('grid_slot', {
anchorX: 0.5,
anchorY: 0.5,
width: GRID_CELL_SIZE * 0.8,
height: GRID_CELL_SIZE * 0.8
});
fogDetail.tint = 0x222222;
fogDetail.alpha = 0.2; // More transparent detail layer
fogDetail.rotation = Math.random() * 0.2 - 0.1;
// Store fog overlays to add later
fogOverlays.push(fogOverlay);
}
}
// Create king at bottom center of grid
var kings = [];
var king = game.addChild(new King());
// Place king at center column (column 4 for 9x9 grid)
var kingGridX = 4;
var kingGridY = GRID_ROWS - 1;
// Get the exact grid slot and place king there
var kingSlot = grid[kingGridY][kingGridX];
kingSlot.unit = king;
king.gridX = kingGridX;
king.gridY = kingGridY;
king.lane = 0;
// Set king position to match grid slot exactly
king.x = kingSlot.x;
king.y = kingSlot.y;
// King already has baseScale from Unit class, ensure it's applied
king.updateUnitScale();
king.showHealthBar(); // Show health bar for king from start
kings.push(king);
// Register king position
registerEntityPosition(king);
// Create info button for king
var kingInfoButton = new Container();
var kingInfoButtonBg = kingInfoButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 40
});
kingInfoButtonBg.tint = 0x4444FF;
var kingInfoButtonText = new Text2('Info', {
size: 32,
fill: 0xFFFFFF
});
kingInfoButtonText.anchor.set(0.5, 0.5);
kingInfoButton.addChild(kingInfoButtonText);
kingInfoButton.x = king.x;
kingInfoButton.y = king.y + 100; // Position below king
game.addChild(kingInfoButton);
kingInfoButton.up = function () {
showUnitInfoModal(king);
};
// Create bench area background
var benchAreaBg = game.addChild(LK.getAsset('bench_area_bg', {
anchorX: 0.5,
anchorY: 0.5
}));
benchAreaBg.x = 1024; // Center of screen
benchAreaBg.y = PLAYABLE_HEIGHT - 120; // Move bench up by 40px
benchAreaBg.alpha = 0.7;
// Create bench slots
var benchTotalWidth = 9 * 120 + 8 * 40; // 9 slots of 120px width with 40px spacing
var benchStartX = (2048 - benchTotalWidth) / 2 + 60; // Center horizontally
for (var i = 0; i < 9; i++) {
var benchSlot = game.addChild(new BenchSlot(i));
benchSlot.x = benchStartX + i * 160;
benchSlot.y = PLAYABLE_HEIGHT - 120; // Move bench up by 40px
benchSlots[i] = benchSlot;
}
// Shop configuration
var shopSlots = [];
var unitPool = [1, 1, 1, 1, 1, 2, 2, 2, 2, 2]; // Pool of available unit tiers
var refreshCost = 2;
// Calculate shop area dimensions
var benchBottomY = PLAYABLE_HEIGHT - 120 + 100; // Bench center + half height
var screenBottomY = 2732; // Full screen height
var shopAreaHeight = screenBottomY - benchBottomY;
var shopCenterY = benchBottomY + shopAreaHeight / 2;
// Create shop area background
var shopAreaBg = game.addChild(LK.getAsset('shop_area_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: shopAreaHeight
}));
shopAreaBg.x = 1024; // Center of screen
shopAreaBg.y = shopCenterY; // Center vertically in available space
shopAreaBg.alpha = 0.6;
// Create shop slots
var shopTotalWidth = 5 * 200 + 4 * 40; // 5 slots of 200px width with 40px spacing
var shopStartX = (2048 - shopTotalWidth) / 2 + 60; // Center horizontally
for (var i = 0; i < 5; i++) {
var shopSlot = game.addChild(new Container());
shopSlot.x = shopStartX + i * 240;
shopSlot.y = PLAYABLE_HEIGHT + 150; // Position shop units further down
shopSlot.index = i;
shopSlot.unit = null;
shopSlot.tierText = null;
shopSlot.nameText = null;
shopSlots.push(shopSlot);
}
// Create refresh button
var refreshButton = game.addChild(new Container());
var refreshButtonBg = refreshButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 80
});
refreshButtonBg.tint = 0xFF8800;
var refreshText = new Text2('Refresh', {
size: 40,
fill: 0xFFFFFF
});
refreshText.anchor.set(0.5, 0.5);
refreshButton.addChild(refreshButtonBg);
refreshButton.addChild(refreshText);
refreshButton.x = 2048 - 200;
refreshButton.y = PLAYABLE_HEIGHT + 150; // Match shop area position
refreshButton.up = function () {
if (gold >= refreshCost) {
gold -= refreshCost;
goldText.setText(gold.toString());
refreshShop();
LK.getSound('purchase').play();
}
};
// Create level up button
var levelUpButton = game.addChild(new Container());
var levelUpButtonBg = levelUpButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 200,
height: 80
});
levelUpButtonBg.tint = 0xFF8800;
var levelUpText = new Text2('Level Up', {
size: 40,
fill: 0xFFFFFF
});
levelUpText.anchor.set(0.5, 0.5);
levelUpButton.addChild(levelUpText);
levelUpButton.x = 200;
levelUpButton.y = PLAYABLE_HEIGHT + 150; // Position in shop area
levelUpButton.up = function () {
levelUpPlayer();
};
// Create level up cost text
var levelUpCostText = new Text2('Cost: ' + getLevelUpCost(playerLevel) + 'g', {
size: 32,
fill: 0xFFD700
});
levelUpCostText.anchor.set(0.5, 0);
levelUpCostText.x = 200;
levelUpCostText.y = PLAYABLE_HEIGHT + 200;
game.addChild(levelUpCostText);
// Create refresh cost text
var refreshCostText = new Text2('Cost: ' + refreshCost + 'g', {
size: 32,
fill: 0xFFD700
});
refreshCostText.anchor.set(0.5, 0);
refreshCostText.x = 2048 - 200;
refreshCostText.y = PLAYABLE_HEIGHT + 200;
game.addChild(refreshCostText);
// Function to refresh shop with new units
function refreshShop() {
// Clear existing shop units
for (var i = 0; i < shopSlots.length; i++) {
if (shopSlots[i].unit) {
shopSlots[i].unit.destroy();
shopSlots[i].unit = null;
}
if (shopSlots[i].tierText) {
shopSlots[i].tierText.destroy();
shopSlots[i].tierText = null;
}
if (shopSlots[i].nameText) {
shopSlots[i].nameText.destroy();
shopSlots[i].nameText = null;
}
if (shopSlots[i].infoButton) {
shopSlots[i].infoButton.destroy();
shopSlots[i].infoButton = null;
}
}
// Fill shop with new random units
for (var i = 0; i < shopSlots.length; i++) {
var randomTier = unitPool[Math.floor(Math.random() * unitPool.length)];
var shopUnit;
var unitConstructor;
switch (randomTier) {
case 1:
var unitTypes = [Soldier, Knight, Wall];
unitConstructor = unitTypes[Math.floor(Math.random() * unitTypes.length)];
shopUnit = new unitConstructor();
break;
case 2:
var unitTypes = [Wizard, Paladin, Ranger, CrossbowTower];
unitConstructor = unitTypes[Math.floor(Math.random() * unitTypes.length)];
shopUnit = new unitConstructor();
break;
}
shopUnit.x = shopSlots[i].x;
shopUnit.y = shopSlots[i].y; // Center the unit in the shop slot
game.addChild(shopUnit);
shopSlots[i].unit = shopUnit;
shopSlots[i].unitConstructor = unitConstructor; // Store the constructor reference
// Add gentle floating animation for shop units
var baseY = shopUnit.y;
tween(shopUnit, {
y: baseY - 10
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(shopUnit, {
y: baseY
}, {
duration: 1000 + Math.random() * 500,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
// Add unit name text above the image with more spacing
var nameText = new Text2(shopUnit.name, {
size: 36,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 1); //{3P} // Anchor to bottom center
nameText.x = shopSlots[i].x;
nameText.y = shopSlots[i].y - 100; // Position higher above the unit image
game.addChild(nameText);
shopSlots[i].nameText = nameText;
// Add cost text below the image with more spacing
var cost = randomTier;
// Special cost for Wall (1g) and CrossbowTower (2g)
if (shopUnit.name === "Wall") {
cost = 1;
} else if (shopUnit.name === "CrossbowTower") {
cost = 2;
}
var costText = new Text2(cost + 'g', {
size: 36,
fill: 0xFFD700
});
costText.anchor.set(0.5, 0);
costText.x = shopSlots[i].x;
costText.y = shopSlots[i].y + 70; // Position well below the unit image
game.addChild(costText);
shopSlots[i].tierText = costText;
// Add info button below cost
var infoButton = new Container();
var infoButtonBg = infoButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 80,
height: 40
});
infoButtonBg.tint = 0x4444FF;
var infoButtonText = new Text2('Info', {
size: 32,
fill: 0xFFFFFF
});
infoButtonText.anchor.set(0.5, 0.5);
infoButton.addChild(infoButtonText);
infoButton.x = shopSlots[i].x;
infoButton.y = shopSlots[i].y + 130; // Position below cost text with more spacing
game.addChild(infoButton);
shopSlots[i].infoButton = infoButton;
// Store unit reference on info button for click handler
infoButton.shopUnit = shopUnit;
infoButton.up = function () {
showUnitInfoModal(this.shopUnit);
};
}
}
// Function to show unit info modal
function showUnitInfoModal(unit) {
// Create modal overlay
var modalOverlay = game.addChild(new Container());
modalOverlay.x = 1024; // Center of screen
modalOverlay.y = 1366; // Center of screen
// Create modal background
var modalBg = modalOverlay.attachAsset('shop_area_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 600,
height: 800
});
modalBg.alpha = 0.95;
// Add title
var titleText = new Text2(unit.name, {
size: 48,
fill: 0xFFFFFF
});
titleText.anchor.set(0.5, 0.5);
titleText.y = -350;
modalOverlay.addChild(titleText);
// Add attributes
var attributes = ['Health: ' + unit.maxHealth, 'Damage: ' + unit.damage, 'Armor: ' + unit.armor, 'Magic Resist: ' + unit.magicResist, 'Critical Chance: ' + unit.criticalChance * 100 + '%', 'Mana: ' + unit.mana, 'Attack Speed: ' + unit.speed, 'Range: ' + unit.range];
for (var i = 0; i < attributes.length; i++) {
var attrText = new Text2(attributes[i], {
size: 36,
fill: 0xFFFFFF
});
attrText.anchor.set(0.5, 0.5);
attrText.y = -250 + i * 60;
modalOverlay.addChild(attrText);
}
// Add close button
var closeButton = new Container();
var closeButtonBg = closeButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 150,
height: 60
});
closeButtonBg.tint = 0xFF4444;
var closeButtonText = new Text2('Close', {
size: 36,
fill: 0xFFFFFF
});
closeButtonText.anchor.set(0.5, 0.5);
closeButton.addChild(closeButtonText);
closeButton.y = 330;
modalOverlay.addChild(closeButton);
closeButton.up = function () {
modalOverlay.destroy();
};
}
// Initialize shop with units
refreshShop();
// Add fog overlays on top of everything to ensure they render above enemies
for (var i = 0; i < fogOverlays.length; i++) {
game.addChild(fogOverlays[i]);
}
// Create top UI container
topUIContainer = game.addChild(topUIContainer);
topUIContainer.x = 1024; // Center of screen
topUIContainer.y = 10; // Top of screen with small margin
// Create top UI container background - bigger initially for planning phase
var topUIBg = topUIContainer.addChild(LK.getAsset('bench_area_bg', {
anchorX: 0.5,
anchorY: 0,
width: 1800,
height: 240 // Bigger height for planning phase
}));
topUIBg.alpha = 0.7;
topUIBg.tint = 0x3a3a3a; // Darker tint for top section
// Create top row elements (Coin, Level, Max Units)
var goldContainer = new Container();
goldContainer.x = 650; // Relative to topUIContainer
goldContainer.y = 60; // Adjusted for bigger UI
topUIContainer.addChild(goldContainer);
// Create detailed pixelart coin
var coinContainer = new Container();
goldContainer.addChild(coinContainer);
coinContainer.x = -50; // Move coin further left to increase spacing
// Coin base (outer ring)
var coinBase = coinContainer.attachAsset('coin_base', {
anchorX: 0.5,
anchorY: 0.5
});
// Coin inner ring
var coinInner = coinContainer.attachAsset('coin_inner', {
anchorX: 0.5,
anchorY: 0.5
});
// Coin center
var coinCenter = coinContainer.attachAsset('coin_center', {
anchorX: 0.5,
anchorY: 0.5
});
// Coin highlight for shine effect
var coinHighlight = coinContainer.attachAsset('coin_highlight', {
anchorX: 0.5,
anchorY: 0.5
});
coinHighlight.x = -6;
coinHighlight.y = -6;
// Add subtle rotation animation to make coin feel alive
tween(coinContainer, {
rotation: 0.1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(coinContainer, {
rotation: -0.1
}, {
duration: 2000,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
goldText = new Text2(gold.toString(), {
size: 50,
fill: 0xFFD700
});
goldText.anchor.set(0.5, 0.5);
goldText.x = 40; // Move number further right to create more space
goldContainer.addChild(goldText);
var levelText = new Text2('King Lvl: ' + playerLevel, {
size: 50,
fill: 0x44FF44
});
levelText.anchor.set(0.5, 0.5);
levelText.x = 0; // Center relative to topUIContainer
levelText.y = 60; // Adjusted for bigger UI
topUIContainer.addChild(levelText);
var maxUnitsText = new Text2('Units: 0/' + maxUnitsOnField + ' | Structures: 0/' + maxStructuresOnField, {
size: 50,
fill: 0xFFFFFF
});
maxUnitsText.anchor.set(0.5, 0.5);
maxUnitsText.x = -500; // Left of center relative to topUIContainer
maxUnitsText.y = 60; // Adjusted for bigger UI
topUIContainer.addChild(maxUnitsText);
// Create bottom row elements (Planning Phase, Next Wave)
var stateText = new Text2('Planning Phase', {
size: 40,
fill: 0x44FF44
});
stateText.anchor.set(0.5, 0.5);
stateText.x = -300; // Left of center relative to topUIContainer
stateText.y = 140; // Adjusted for bigger UI
topUIContainer.addChild(stateText);
var waveText = new Text2('Wave: ' + wave, {
size: 40,
fill: 0xFFFFFF
});
waveText.anchor.set(0.5, 0.5);
waveText.x = 300; // Right of center relative to topUIContainer
waveText.y = 140; // Adjusted for bigger UI
topUIContainer.addChild(waveText);
// Function to minimize UI during battle
function minimizeUI() {
// Tween background to smaller size
tween(topUIBg, {
height: 120
}, {
duration: 400,
easing: tween.easeInOut
});
// Move ready button up
tween(readyButton, {
y: 90
}, {
duration: 400,
easing: tween.easeInOut
});
// Move state and wave text up
tween(stateText, {
y: 90
}, {
duration: 400,
easing: tween.easeInOut
});
tween(waveText, {
y: 90
}, {
duration: 400,
easing: tween.easeInOut
});
}
// Function to maximize UI during planning
function maximizeUI() {
// Tween background to bigger size
tween(topUIBg, {
height: 240
}, {
duration: 400,
easing: tween.easeInOut
});
// Move ready button down
tween(readyButton, {
y: 190
}, {
duration: 400,
easing: tween.easeInOut
});
// Move state and wave text down
tween(stateText, {
y: 140
}, {
duration: 400,
easing: tween.easeInOut
});
tween(waveText, {
y: 140
}, {
duration: 400,
easing: tween.easeInOut
});
}
// Function to update state indicator
function updateStateIndicator() {
if (gameState === 'planning') {
stateText.setText('Planning Phase');
// Remove old text and create new one with correct color
var parent = stateText.parent;
var x = stateText.x;
var y = stateText.y;
stateText.destroy();
stateText = new Text2('Planning Phase', {
size: 40,
fill: 0x44FF44 // Green for planning
});
stateText.anchor.set(0.5, 0.5);
stateText.x = x;
stateText.y = y;
parent.addChild(stateText);
// Enable ready button during planning
readyButtonBg.tint = 0x44AA44; // Green tint
readyButtonText.setText('Ready!');
// Show king info button during planning
if (kingInfoButton) {
kingInfoButton.visible = true;
}
// Maximize UI for planning phase
if (isUIMinimized) {
isUIMinimized = false;
maximizeUI();
}
} else {
stateText.setText('Battle Phase');
// Remove old text and create new one with correct color
var parent = stateText.parent;
var x = stateText.x;
var y = stateText.y;
stateText.destroy();
stateText = new Text2('Battle Phase', {
size: 40,
fill: 0xFF4444 // Red for battle
});
stateText.anchor.set(0.5, 0.5);
stateText.x = x;
stateText.y = y;
parent.addChild(stateText);
// Grey out ready button during battle
readyButtonBg.tint = 0x666666; // Grey tint
readyButtonText.setText('Battle');
// Hide king info button during battle
if (kingInfoButton) {
kingInfoButton.visible = false;
}
// Minimize UI for battle phase
if (!isUIMinimized) {
isUIMinimized = true;
minimizeUI();
}
}
}
var readyButton = new Container();
var readyButtonBg = LK.getAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 300,
height: 100
});
readyButtonBg.tint = 0x44AA44;
var readyButtonText = new Text2('Ready!', {
size: 72,
fill: 0xFFFFFF
});
readyButtonText.anchor.set(0.5, 0.5);
readyButton.addChild(readyButtonBg);
readyButton.addChild(readyButtonText);
topUIContainer.addChild(readyButton);
readyButton.x = 0; // Center relative to topUIContainer
readyButton.y = 190; // Position in bigger UI
readyButton.up = function () {
if (gameState === 'planning') {
// Check for upgrades before starting battle
checkAndUpgradeUnits();
gameState = 'battle';
waveDelay = 0;
enemiesSpawned = 0; // Reset enemies spawned for new wave
updateStateIndicator(); // Update UI to show battle phase
}
// Do nothing if gameState is 'battle' (button is disabled)
};
// Function to check and perform unit upgrades
function checkAndUpgradeUnits() {
// Group units by name and type
var unitGroups = {};
// Count units in bench
for (var i = 0; i < bench.length; i++) {
var unit = bench[i];
if (unit.gridX === -1 && unit.gridY === -1) {
// Only count bench units
var key = unit.name + '_' + unit.tier;
if (!unitGroups[key]) {
unitGroups[key] = [];
}
unitGroups[key].push({
unit: unit,
location: 'bench',
index: i
});
}
}
// Count units on grid
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
var gridSlot = grid[y][x];
if (gridSlot.unit) {
var unit = gridSlot.unit;
var key = unit.name + '_' + unit.tier;
if (!unitGroups[key]) {
unitGroups[key] = [];
}
unitGroups[key].push({
unit: unit,
location: 'grid',
gridX: x,
gridY: y,
slot: gridSlot
});
}
}
}
// Check for upgrades
for (var key in unitGroups) {
var group = unitGroups[key];
if (group.length >= 3) {
// Find upgrade priority: bench first, then grid
var benchUnits = group.filter(function (item) {
return item.location === 'bench';
});
var gridUnits = group.filter(function (item) {
return item.location === 'grid';
});
var unitsToMerge = [];
var upgradeTarget = null;
var upgradeLocation = null;
if (gridUnits.length >= 2 && benchUnits.length >= 1) {
// Priority: Upgrade units on grid when 2 are on field and 1 in bench
unitsToMerge = gridUnits.slice(0, 2).concat(benchUnits.slice(0, 1));
upgradeTarget = unitsToMerge[0];
upgradeLocation = 'grid';
} else if (gridUnits.length >= 1 && benchUnits.length >= 2) {
// Upgrade unit on grid using bench units
unitsToMerge = [gridUnits[0]].concat(benchUnits.slice(0, 2));
upgradeTarget = unitsToMerge[0];
upgradeLocation = 'grid';
} else if (benchUnits.length >= 3) {
// Upgrade in bench
unitsToMerge = benchUnits.slice(0, 3);
upgradeTarget = unitsToMerge[0];
upgradeLocation = 'bench';
}
if (upgradeTarget && unitsToMerge.length === 3) {
// Perform upgrade
var baseUnit = upgradeTarget.unit;
// Upgrade attributes based on tier
if (baseUnit.tier === 2) {
// Three star upgrade - multiply stats by 3
baseUnit.health = Math.floor(baseUnit.health * 3);
baseUnit.maxHealth = Math.floor(baseUnit.maxHealth * 3);
baseUnit.damage = Math.floor(baseUnit.damage * 3);
baseUnit.armor = Math.floor(baseUnit.armor * 3);
baseUnit.magicResist = Math.floor(baseUnit.magicResist * 3);
baseUnit.mana = Math.floor(baseUnit.mana * 3);
} else {
// Two star upgrade - multiply by 1.8x
baseUnit.health = Math.floor(baseUnit.health * 1.8);
baseUnit.maxHealth = Math.floor(baseUnit.maxHealth * 1.8);
baseUnit.damage = Math.floor(baseUnit.damage * 1.8);
baseUnit.armor = Math.floor(baseUnit.armor * 1.8);
baseUnit.magicResist = Math.floor(baseUnit.magicResist * 1.8);
baseUnit.mana = Math.floor(baseUnit.mana * 1.8);
}
baseUnit.tier = baseUnit.tier + 1;
// Update health bar to reflect new max health
if (baseUnit.healthBar) {
baseUnit.updateHealthBar();
}
// Update star indicator to reflect new tier
if (baseUnit.starIndicator) {
baseUnit.starIndicator.updateStars(baseUnit.tier);
}
// Force immediate scale application by stopping any ongoing scale tweens
tween.stop(baseUnit, {
scaleX: true,
scaleY: true
});
// Apply the scale immediately based on tier
var baseScale = baseUnit.baseScale || 1.3;
var finalScale = baseScale;
if (baseUnit.tier === 2) {
finalScale = baseScale * 1.2;
} else if (baseUnit.tier === 3) {
finalScale = baseScale * 1.3;
}
// Set scale immediately
baseUnit.scaleX = finalScale;
baseUnit.scaleY = finalScale;
// Update unit scale method to ensure consistency
baseUnit.updateUnitScale();
// Visual upgrade effect with bounce animation that respects final scale
LK.effects.flashObject(baseUnit, 0xFFD700, 800); // Gold flash
tween(baseUnit, {
scaleX: finalScale * 1.2,
scaleY: finalScale * 1.2
}, {
duration: 300,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(baseUnit, {
scaleX: finalScale,
scaleY: finalScale
}, {
duration: 200,
easing: tween.easeIn
});
}
});
// Remove the other two units
for (var i = 1; i < unitsToMerge.length; i++) {
var unitToRemove = unitsToMerge[i];
if (unitToRemove.location === 'bench') {
// Remove from bench
for (var j = bench.length - 1; j >= 0; j--) {
if (bench[j] === unitToRemove.unit) {
bench[j].destroy();
bench.splice(j, 1);
break;
}
}
} else if (unitToRemove.location === 'grid') {
// Remove from grid
unitToRemove.slot.unit = null;
unregisterEntityPosition(unitToRemove.unit, unitToRemove.gridX, unitToRemove.gridY);
unitToRemove.unit.destroy();
}
}
// Update displays
updateBenchDisplay();
}
}
}
}
// Function to move units to closest grid spots after battle
function moveUnitsToClosestGridSpots() {
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
var gridSlot = grid[y][x];
if (gridSlot.unit && !gridSlot.unit.isStructure && gridSlot.unit.name !== "King") {
var unit = gridSlot.unit;
var currentGridX = unit.gridX;
var currentGridY = unit.gridY;
// Find closest valid grid position
var closestGridX = currentGridX;
var closestGridY = currentGridY;
var minDistance = 999;
for (var checkY = 2; checkY < GRID_ROWS; checkY++) {
// Start from row 2 to avoid fog of war
for (var checkX = 0; checkX < GRID_COLS; checkX++) {
var checkSlot = grid[checkY][checkX];
// Check if position is valid (not occupied or same unit)
if (!checkSlot.unit || checkSlot.unit === unit) {
var distance = Math.abs(checkX - currentGridX) + Math.abs(checkY - currentGridY);
if (distance < minDistance) {
minDistance = distance;
closestGridX = checkX;
closestGridY = checkY;
}
}
}
}
// Only move if we found a different position
if (closestGridX !== currentGridX || closestGridY !== currentGridY) {
var targetSlot = grid[closestGridY][closestGridX];
var targetX = targetSlot.x;
var targetY = targetSlot.y;
// Clear old position
gridSlot.unit = null;
unregisterEntityPosition(unit, currentGridX, currentGridY);
// Set new position
targetSlot.unit = unit;
unit.gridX = closestGridX;
unit.gridY = closestGridY;
registerEntityPosition(unit);
// Animate unit to new position
tween(unit, {
x: targetX,
y: targetY
}, {
duration: 800,
easing: tween.easeInOut
});
}
}
}
}
}
// Function to show ready button for next wave
function showReadyButton() {
if (gameState === 'battle') {
readyButtonBg.tint = 0x666666; // Grey tint during battle
readyButtonText.setText('Battle');
} else {
readyButtonBg.tint = 0x44AA44; // Green tint for next wave
readyButtonText.setText('Next Wave');
}
readyButton.visible = true;
}
// Hide ready button initially (will show after first wave)
function hideReadyButton() {
readyButton.visible = false;
}
function updateBenchDisplay() {
// Clear all bench slot references
for (var i = 0; i < benchSlots.length; i++) {
benchSlots[i].unit = null;
}
// Only display units in bench that are not on the battlefield
for (var i = 0; i < bench.length; i++) {
// Skip units that are already placed on the grid
if (bench[i].gridX !== -1 && bench[i].gridY !== -1) {
continue;
}
// Find the first empty bench slot
for (var j = 0; j < benchSlots.length; j++) {
if (!benchSlots[j].unit) {
benchSlots[j].unit = bench[i];
// Center the unit in the bench slot
bench[i].x = benchSlots[j].x;
bench[i].y = benchSlots[j].y;
// Show star indicator for units in bench
bench[i].showStarIndicator();
break;
}
}
}
}
function spawnEnemy() {
var spawnX = Math.floor(Math.random() * GRID_COLS);
var enemy;
// Spawn boss monster every 5 waves
if (wave % 5 === 0 && enemiesSpawned === Math.floor(enemiesPerWave / 2)) {
enemy = new Boss();
// Scale boss stats based on wave
if (wave > 1) {
var waveMultiplier = 1 + (wave - 1) * 0.2;
enemy.health = Math.floor(enemy.health * waveMultiplier);
enemy.maxHealth = enemy.health;
enemy.damage = Math.floor(enemy.damage * waveMultiplier);
enemy.armor = Math.floor(enemy.armor * (1 + (wave - 1) * 0.1));
enemy.magicResist = Math.floor(enemy.magicResist * (1 + (wave - 1) * 0.1));
enemy.goldValue = Math.floor(enemy.goldValue * waveMultiplier);
}
} else {
// Increase ranged enemy chance as waves progress
var rangedChance = Math.min(0.3 + wave * 0.05, 0.7);
if (Math.random() < rangedChance) {
enemy = new RangedEnemy();
} else {
enemy = new Enemy();
}
// Significantly increase enemy scaling per wave
enemy.health = 20 + Math.floor(wave * 10) + Math.floor(Math.pow(wave, 1.5) * 5);
enemy.maxHealth = enemy.health;
enemy.damage = 15 + Math.floor(wave * 5) + Math.floor(Math.pow(wave, 1.3) * 3);
enemy.goldValue = Math.max(2, Math.floor(wave * 1.5));
}
// Try to spawn in a strategic position
var bestSpawnX = spawnX;
var minPlayerUnits = 999;
// Check each column for player units
for (var x = 0; x < GRID_COLS; x++) {
var playerUnitsInColumn = 0;
for (var y = 0; y < GRID_ROWS; y++) {
if (grid[y] && grid[y][x] && grid[y][x].unit) {
playerUnitsInColumn++;
}
}
// Boss prefers columns with fewer player units
if (enemy.name === "Boss Monster" && playerUnitsInColumn < minPlayerUnits) {
minPlayerUnits = playerUnitsInColumn;
bestSpawnX = x;
}
}
// Position enemy at the first row (top) of the grid
enemy.gridX = enemy.name === "Boss Monster" ? bestSpawnX : spawnX;
enemy.gridY = 0;
// Set visual position to match grid position
var topCell = grid[0][enemy.gridX];
enemy.x = topCell.x;
enemy.y = topCell.y;
enemy.lane = 0; // Single grid, so lane is always 0
// Apply base scale
if (enemy.name !== "Boss Monster") {
// Scale up enemy by 30%
enemy.scaleX = 1.3;
enemy.scaleY = 1.3;
}
enemy.showHealthBar(); // Show health bar when enemy is spawned
enemy.updateHealthBar();
// Initialize GridMovement immediately for the enemy
enemy.gridMovement = new GridMovement(enemy, grid);
enemies.push(enemy);
enemyContainer.addChild(enemy);
// Add spawn animation
enemy.alpha = 0;
var baseScale = enemy.baseScale || 1.3;
enemy.scaleX = baseScale * 0.5;
enemy.scaleY = baseScale * 1.5;
tween(enemy, {
alpha: 1,
scaleX: baseScale,
scaleY: baseScale
}, {
duration: 300,
easing: tween.easeOut
});
// Register enemy position
registerEntityPosition(enemy);
}
game.move = function (x, y, obj) {
if (draggedUnit) {
draggedUnit.x = x;
draggedUnit.y = y;
}
};
game.up = function (x, y, obj) {
// Check for double-tap on grid units when not dragging
if (!draggedUnit) {
var currentTime = Date.now();
var timeDiff = currentTime - lastTapTime;
var dx = x - lastTapX;
var dy = y - lastTapY;
var distance = Math.sqrt(dx * dx + dy * dy);
// Check if this is a double-tap (within time window and close to last tap)
if (timeDiff < doubleTapDelay && distance < doubleTapDistance) {
// Check if double-tap is on a unit in the grid
for (var gridY = 0; gridY < GRID_ROWS; gridY++) {
for (var gridX = 0; gridX < GRID_COLS; gridX++) {
var slot = grid[gridY][gridX];
if (slot.unit) {
var unitDx = x - slot.x;
var unitDy = y - slot.y;
var unitDistance = Math.sqrt(unitDx * unitDx + unitDy * unitDy);
if (unitDistance < GRID_CELL_SIZE / 2) {
// Double-tapped on this unit - show info modal
showUnitInfoModal(slot.unit);
lastTapTime = 0; // Reset to prevent triple-tap
return;
}
}
}
}
// Check if double-tap is on a unit in the bench during planning phase
if (gameState === 'planning') {
for (var i = 0; i < benchSlots.length; i++) {
var benchSlot = benchSlots[i];
if (benchSlot.unit) {
var unitDx = x - benchSlot.x;
var unitDy = y - benchSlot.y;
var unitDistance = Math.sqrt(unitDx * unitDx + unitDy * unitDy);
if (unitDistance < 60) {
// Double-tapped on this bench unit - show info modal
showUnitInfoModal(benchSlot.unit);
lastTapTime = 0; // Reset to prevent triple-tap
return;
}
}
}
}
// Check if double-tap is on an enemy
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var enemyDx = x - enemy.x;
var enemyDy = y - enemy.y;
var enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
if (enemyDistance < 60) {
// Double-tapped on this enemy - show info modal
showUnitInfoModal(enemy);
lastTapTime = 0; // Reset to prevent triple-tap
return;
}
}
}
// Update last tap info for next potential double-tap
lastTapTime = currentTime;
lastTapX = x;
lastTapY = y;
}
if (draggedUnit) {
var dropped = false;
// Check if dropped on a valid grid slot
for (var gridY = 0; gridY < GRID_ROWS; gridY++) {
for (var gridX = 0; gridX < GRID_COLS; gridX++) {
var slot = grid[gridY][gridX];
var dx = x - slot.x;
var dy = y - slot.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < GRID_CELL_SIZE / 2 && !slot.unit) {
// Valid empty grid slot - ensure exact positioning
slot.placeUnit(draggedUnit);
if (draggedFromGrid && draggedUnit.originalGridSlot) {
// Unregister from old position before clearing slot
unregisterEntityPosition(draggedUnit, draggedUnit.originalGridSlot.gridX, draggedUnit.originalGridSlot.gridY);
draggedUnit.originalGridSlot.unit = null;
}
dropped = true;
break;
}
}
if (dropped) {
break;
}
}
// Check if dropped on bench slot
if (!dropped) {
// Prevent King from being moved to bench
if (draggedUnit.name !== 'King') {
for (var i = 0; i < benchSlots.length; i++) {
var benchSlot = benchSlots[i];
var dx = x - benchSlot.x;
var dy = y - benchSlot.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 60 && !benchSlot.unit) {
// Valid empty bench slot
benchSlot.unit = draggedUnit;
draggedUnit.x = benchSlot.x;
draggedUnit.y = benchSlot.y;
// Remove from grid if it was on grid
if (draggedFromGrid && draggedUnit.originalGridSlot) {
// Unregister from position tracking
unregisterEntityPosition(draggedUnit, draggedUnit.originalGridSlot.gridX, draggedUnit.originalGridSlot.gridY);
draggedUnit.originalGridSlot.unit = null;
}
draggedUnit.gridX = -1;
draggedUnit.gridY = -1;
draggedUnit.lane = -1;
draggedUnit.hideHealthBar(); // Hide health bar when moved to bench
draggedUnit.hideStarIndicator(); // Hide star indicator when moved to bench
dropped = true;
updateBenchDisplay();
// Add bounce effect when unit is moved to bench
var targetScale = draggedUnit.baseScale || 1.3;
if (draggedUnit.tier === 2) {
targetScale = draggedUnit.baseScale * 1.2;
} else if (draggedUnit.tier === 3) {
targetScale = draggedUnit.baseScale * 1.3;
}
draggedUnit.scaleX = targetScale * 0.8;
draggedUnit.scaleY = targetScale * 0.8;
// Capture unit reference before clearing draggedUnit
var unitToAnimate = draggedUnit;
tween(unitToAnimate, {
scaleX: targetScale * 1.2,
scaleY: targetScale * 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unitToAnimate, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 150,
easing: tween.easeIn,
onFinish: function onFinish() {
// Clear bounce animation flag when bounce is complete
unitToAnimate._isBounceAnimating = false;
}
});
}
});
break;
}
}
}
}
// If not dropped on valid slot, return to original position
if (!dropped) {
// Handle King separately - must return to grid
if (draggedUnit.name === 'King') {
// King must return to original grid position
if (draggedFromGrid && draggedUnit.originalGridSlot) {
draggedUnit.x = draggedUnit.originalX;
draggedUnit.y = draggedUnit.originalY;
draggedUnit.originalGridSlot.unit = draggedUnit; // Restore unit to original grid slot
}
} else {
// Always return to bench if not placed in a valid cell
if (draggedUnit.originalSlot) {
// Return to original bench slot
draggedUnit.x = draggedUnit.originalX;
draggedUnit.y = draggedUnit.originalY;
draggedUnit.originalSlot.unit = draggedUnit;
} else if (draggedFromGrid && draggedUnit.originalGridSlot) {
// Unit was dragged from grid but not placed in valid cell - return to bench
// Find first available bench slot
var returnedToBench = false;
for (var i = 0; i < benchSlots.length; i++) {
if (!benchSlots[i].unit) {
benchSlots[i].unit = draggedUnit;
draggedUnit.x = benchSlots[i].x;
draggedUnit.y = benchSlots[i].y;
// Unregister from grid position
unregisterEntityPosition(draggedUnit, draggedUnit.originalGridSlot.gridX, draggedUnit.originalGridSlot.gridY);
draggedUnit.originalGridSlot.unit = null;
draggedUnit.gridX = -1;
draggedUnit.gridY = -1;
draggedUnit.lane = -1;
draggedUnit.hideHealthBar();
draggedUnit.hideStarIndicator(); // Hide star indicator when moved to bench
returnedToBench = true;
updateBenchDisplay();
// Add bounce effect when unit is returned to bench
var targetScale = draggedUnit.baseScale || 1.3;
if (draggedUnit.tier === 2) {
targetScale = draggedUnit.baseScale * 1.2;
} else if (draggedUnit.tier === 3) {
targetScale = draggedUnit.baseScale * 1.3;
}
draggedUnit.scaleX = targetScale * 0.8;
draggedUnit.scaleY = targetScale * 0.8;
// Capture unit reference before clearing draggedUnit
var unitToAnimate = draggedUnit;
tween(unitToAnimate, {
scaleX: targetScale * 1.2,
scaleY: targetScale * 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unitToAnimate, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 150,
easing: tween.easeIn
});
}
});
break;
}
}
// If no bench slot available, return to original grid position
if (!returnedToBench) {
draggedUnit.x = draggedUnit.originalX;
draggedUnit.y = draggedUnit.originalY;
draggedUnit.originalGridSlot.unit = draggedUnit; // Restore unit to original grid slot
}
} else {
// Unit doesn't have original position - find first available bench slot
for (var i = 0; i < benchSlots.length; i++) {
if (!benchSlots[i].unit) {
benchSlots[i].unit = draggedUnit;
draggedUnit.x = benchSlots[i].x;
draggedUnit.y = benchSlots[i].y;
draggedUnit.gridX = -1;
draggedUnit.gridY = -1;
draggedUnit.lane = -1;
draggedUnit.hideHealthBar();
draggedUnit.hideStarIndicator(); // Hide star indicator when moved to bench
updateBenchDisplay();
break;
}
}
}
}
}
// Clean up
draggedUnit.originalX = undefined;
draggedUnit.originalY = undefined;
draggedUnit.originalSlot = undefined;
draggedUnit.originalGridSlot = undefined;
draggedUnit = null;
draggedFromGrid = false;
} else {
// Check if clicking on a shop unit
for (var i = 0; i < shopSlots.length; i++) {
var slot = shopSlots[i];
if (slot.unit) {
var dx = x - slot.x;
var dy = y - slot.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 80) {
// Increase click area for better usability
// Within click range of shop unit
var unitCost = slot.unit.tier;
// Special cost for Wall (1g) and CrossbowTower (2g)
if (slot.unit.name === "Wall") {
unitCost = 1;
} else if (slot.unit.name === "CrossbowTower") {
unitCost = 2;
}
if (gold >= unitCost) {
// Find available bench slot
var availableBenchSlot = false;
for (var slotIndex = 0; slotIndex < benchSlots.length; slotIndex++) {
if (!benchSlots[slotIndex].unit) {
availableBenchSlot = true;
break;
}
}
if (availableBenchSlot) {
gold -= unitCost;
goldText.setText(gold.toString());
// Create new unit for bench using the same constructor as the shop unit
var newUnit = new slot.unitConstructor();
// All units purchased from shop start at tier 1 (1 star) regardless of unit type
newUnit.tier = 1;
// Update star indicator to show tier 1
if (newUnit.starIndicator) {
newUnit.starIndicator.updateStars(1);
}
// Initialize grid position properties
newUnit.gridX = -1;
newUnit.gridY = -1;
newUnit.lane = -1;
bench.push(newUnit);
game.addChild(newUnit);
updateBenchDisplay();
// Add bounce effect when unit is added to bench
newUnit.scaleX = 0;
newUnit.scaleY = 0;
// Capture unit reference for animation
var unitToAnimate = newUnit;
var targetScale = unitToAnimate.baseScale || 1.3;
tween(unitToAnimate, {
scaleX: targetScale * 1.2,
scaleY: targetScale * 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(unitToAnimate, {
scaleX: targetScale,
scaleY: targetScale
}, {
duration: 150,
easing: tween.easeIn
});
}
});
// Remove purchased unit from shop
slot.unit.destroy();
slot.unit = null;
if (slot.tierText) {
slot.tierText.destroy();
slot.tierText = null;
}
if (slot.nameText) {
slot.nameText.destroy();
slot.nameText = null;
}
if (slot.infoButton) {
slot.infoButton.destroy();
slot.infoButton = null;
}
LK.getSound('purchase').play();
// Check for upgrades after purchasing a unit
checkAndUpgradeUnits();
} else {
// No bench space available - could add visual feedback here
}
}
break;
}
}
}
}
};
game.update = function () {
// Spawn enemies
if (gameState === 'battle') {
// Ensure health bars are visible for all enemies
for (var i = 0; i < enemies.length; i++) {
enemies[i].showHealthBar();
}
// Ensure health bars are visible for all units on the grid
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
var gridSlot = grid[y][x];
if (gridSlot.unit) {
gridSlot.unit.showHealthBar();
}
}
}
if (waveDelay <= 0) {
if (enemiesSpawned < enemiesPerWave) {
if (LK.ticks % 60 === 0) {
spawnEnemy();
enemiesSpawned++;
}
} else if (enemies.length === 0) {
// Wave complete - move units to closest grid spots first
moveUnitsToClosestGridSpots();
// Transition to planning phase
gameState = 'planning';
wave++;
enemiesPerWave = Math.min(5 + wave * 2, 30);
waveText.setText('Wave: ' + wave);
// Calculate interest: 1 gold per 10 saved, max 5
var interest = Math.min(Math.floor(gold / 10), 5);
var waveReward = 5 + interest;
gold += waveReward;
goldText.setText(gold.toString());
// Check for unit upgrades when entering planning phase
checkAndUpgradeUnits();
// Show reward message
var rewardText = new Text2('Wave Complete! +' + waveReward + ' Gold (5 + ' + interest + ' interest)', {
size: 96,
fill: 0xFFD700
});
rewardText.anchor.set(0.5, 0.5);
rewardText.x = 1024;
rewardText.y = 800;
game.addChild(rewardText);
// Fade out reward text with longer duration
tween(rewardText, {
alpha: 0,
y: 700
}, {
duration: 4000,
easing: tween.easeOut,
onFinish: function onFinish() {
rewardText.destroy();
}
});
// Refresh shop with new units after wave completion
refreshShop();
showReadyButton(); // Show button for next wave
updateStateIndicator(); // Update UI to show planning phase
}
} else {
waveDelay--;
}
// Castle health bar is now handled as a regular unit
}
// Update enemies
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
enemy.update();
// Castle is now a regular unit on the grid, no special handling needed
enemy.updateHealthBar();
enemy.showHealthBar(); // Ensure health bar is shown during battle
}
// Update unit counts in case any units were destroyed
countUnitsOnField();
// Clean up reservations and position tracking for destroyed entities
var keysToRemove = [];
for (var key in cellReservations) {
var entity = cellReservations[key];
if (!entity.parent) {
keysToRemove.push(key);
}
}
for (var i = 0; i < keysToRemove.length; i++) {
delete cellReservations[keysToRemove[i]];
}
// Clean up position tracking for destroyed entities
var posKeysToRemove = [];
for (var posKey in entityPositions) {
var entity = entityPositions[posKey];
if (!entity.parent) {
posKeysToRemove.push(posKey);
}
}
for (var i = 0; i < posKeysToRemove.length; i++) {
delete entityPositions[posKeysToRemove[i]];
}
// Update units
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
var gridSlot = grid[y][x];
if (gridSlot.unit) {
gridSlot.unit.update();
if (gameState === 'battle') {
gridSlot.unit.showHealthBar(); // Ensure health bar is shown during battle
gridSlot.unit.updateHealthBar();
gridSlot.unit.showStarIndicator(); // Ensure star indicator is shown during battle
} else {
// Hide health bars during planning phase
if (gridSlot.unit.healthBar) {
gridSlot.unit.healthBar.visible = false;
}
// Show star indicators during planning phase for easy tier identification
gridSlot.unit.showStarIndicator();
}
// Update king info button position if this is the king
if (gridSlot.unit.name === 'King' && kingInfoButton) {
kingInfoButton.x = gridSlot.unit.x;
kingInfoButton.y = gridSlot.unit.y + 100; // Position below king
}
}
}
}
// Unit shooting (only during battle)
if (gameState === 'battle') {
for (var y = 0; y < GRID_ROWS; y++) {
for (var x = 0; x < GRID_COLS; x++) {
var gridSlot = grid[y][x];
if (gridSlot.unit) {
var target = gridSlot.unit.findTarget();
if (target) {
var bullet = gridSlot.unit.shoot(target);
if (bullet) {
bullets.push(bullet);
game.addChild(bullet);
LK.getSound('shoot').play();
} else if (gridSlot.unit.range <= 100) {
// Melee attack sound effect (no bullet created)
if (gridSlot.unit.canShoot()) {
LK.getSound('shoot').play();
}
}
}
}
}
}
}
// Update bullets
for (var i = bullets.length - 1; i >= 0; i--) {
var bullet = bullets[i];
if (bullet && bullet.update) {
bullet.update();
}
if (!bullet.parent) {
bullets.splice(i, 1);
}
}
// Update enemy projectiles
for (var i = enemyProjectiles.length - 1; i >= 0; i--) {
var projectile = enemyProjectiles[i];
if (projectile && projectile.update) {
projectile.update();
}
if (!projectile.parent) {
enemyProjectiles.splice(i, 1);
}
}
// Update level up cost text
levelUpCostText.setText('Cost: ' + getLevelUpCost(playerLevel) + 'g');
// Check win condition
if (wave >= 10 && enemies.length === 0 && enemiesSpawned >= enemiesPerWave) {
LK.showYouWin();
}
}; ===================================================================
--- original.js
+++ change.js
@@ -3143,45 +3143,254 @@
* Game Code
****/
// Create game title overlay that appears on top of everything
var titleOverlay = new Container();
+// Create gradient-like background effect with multiple layers
var titleOverlayBg = titleOverlay.attachAsset('shop_area_bg', {
anchorX: 0.5,
anchorY: 0.5,
width: 2048,
height: 2732
});
titleOverlayBg.alpha = 0.95;
-titleOverlayBg.tint = 0x000000; // Black background
+titleOverlayBg.tint = 0x0a0a1a; // Deep dark blue instead of pure black
titleOverlayBg.x = 0; // Center relative to parent
titleOverlayBg.y = 0; // Center relative to parent
-// Game title
+// Add subtle vignette effect
+var vignette = titleOverlay.attachAsset('shop_area_bg', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 2200,
+ height: 2900
+});
+vignette.alpha = 0.3;
+vignette.tint = 0x000000;
+vignette.x = 0;
+vignette.y = 0;
+// Create magical particle system for background
+var particleContainer = new Container();
+titleOverlay.addChild(particleContainer);
+// Create floating magical particles
+function createMagicalParticle() {
+ var particle = particleContainer.attachAsset('star_dot', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 6 + Math.random() * 8,
+ height: 6 + Math.random() * 8
+ });
+ // Random colors for magical effect
+ var colors = [0xFFD700, 0x4169E1, 0xFF69B4, 0x00CED1, 0xFFA500];
+ particle.tint = colors[Math.floor(Math.random() * colors.length)];
+ particle.alpha = 0;
+ // Random starting position
+ particle.x = (Math.random() - 0.5) * 2048;
+ particle.y = 1366 + Math.random() * 800;
+ // Animate particle floating up with glow
+ tween(particle, {
+ y: particle.y - 2000,
+ alpha: 0.8
+ }, {
+ duration: 2000,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ tween(particle, {
+ alpha: 0
+ }, {
+ duration: 1000,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ particle.destroy();
+ }
+ });
+ }
+ });
+ // Add gentle horizontal sway
+ tween(particle, {
+ x: particle.x + (Math.random() - 0.5) * 200
+ }, {
+ duration: 4000,
+ easing: tween.easeInOut
+ });
+}
+// Create particles periodically
+var particleTimer = LK.setInterval(function () {
+ if (titleOverlay.parent) {
+ createMagicalParticle();
+ } else {
+ LK.clearInterval(particleTimer);
+ }
+}, 200);
+// Create title container for effects
+var titleContainer = new Container();
+titleOverlay.addChild(titleContainer);
+titleContainer.y = -566;
+// Add golden glow behind title
+var titleGlow = titleContainer.attachAsset('shop_area_bg', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 800,
+ height: 200
+});
+titleGlow.alpha = 0.3;
+titleGlow.tint = 0xFFD700;
+// Animate glow pulse
+tween(titleGlow, {
+ scaleX: 1.2,
+ scaleY: 1.2,
+ alpha: 0.5
+}, {
+ duration: 2000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ tween(titleGlow, {
+ scaleX: 1.0,
+ scaleY: 1.0,
+ alpha: 0.3
+ }, {
+ duration: 2000,
+ easing: tween.easeInOut,
+ onFinish: onFinish
+ });
+ }
+});
+// Game title with shadow effect
+var gameTitleShadow = new Text2('Protect Your King!', {
+ size: 120,
+ fill: 0x000000
+});
+gameTitleShadow.anchor.set(0.5, 0.5);
+gameTitleShadow.x = 4;
+gameTitleShadow.y = 4;
+gameTitleShadow.alpha = 0.5;
+titleContainer.addChild(gameTitleShadow);
+// Main game title
var gameTitle = new Text2('Protect Your King!', {
size: 120,
fill: 0xFFD700
});
gameTitle.anchor.set(0.5, 0.5);
-gameTitle.x = 0; // Center relative to overlay
-gameTitle.y = -566; // Adjusted position relative to center
-titleOverlay.addChild(gameTitle);
+gameTitle.x = 0;
+gameTitle.y = 0;
+titleContainer.addChild(gameTitle);
+// Add sparkle effects around title
+function createTitleSparkle() {
+ var sparkle = titleContainer.attachAsset('star_dot', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 10,
+ height: 10
+ });
+ sparkle.tint = 0xFFFFFF;
+ sparkle.alpha = 0;
+ sparkle.x = (Math.random() - 0.5) * 600;
+ sparkle.y = (Math.random() - 0.5) * 150;
+ tween(sparkle, {
+ alpha: 1,
+ scaleX: 1.5,
+ scaleY: 1.5
+ }, {
+ duration: 500,
+ easing: tween.easeOut,
+ onFinish: function onFinish() {
+ tween(sparkle, {
+ alpha: 0,
+ scaleX: 0.5,
+ scaleY: 0.5
+ }, {
+ duration: 500,
+ easing: tween.easeIn,
+ onFinish: function onFinish() {
+ sparkle.destroy();
+ }
+ });
+ }
+ });
+}
+// Create sparkles periodically
+var sparkleTimer = LK.setInterval(function () {
+ if (titleOverlay.parent) {
+ createTitleSparkle();
+ } else {
+ LK.clearInterval(sparkleTimer);
+ }
+}, 300);
+// Subtitle with shadow
+var subtitleShadow = new Text2('Tower Defense', {
+ size: 60,
+ fill: 0x000000
+});
+subtitleShadow.anchor.set(0.5, 0.5);
+subtitleShadow.x = 2;
+subtitleShadow.y = -464;
+subtitleShadow.alpha = 0.5;
+titleOverlay.addChild(subtitleShadow);
// Subtitle
var gameSubtitle = new Text2('Tower Defense', {
size: 60,
- fill: 0xFFFFFF
+ fill: 0x87CEEB
});
gameSubtitle.anchor.set(0.5, 0.5);
gameSubtitle.x = 0; // Center relative to overlay
gameSubtitle.y = -466; // Adjusted position relative to center
titleOverlay.addChild(gameSubtitle);
-// Start Game button
+// Add subtle animation to subtitle
+tween(gameSubtitle, {
+ scaleX: 1.05,
+ scaleY: 1.05
+}, {
+ duration: 3000,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ tween(gameSubtitle, {
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 3000,
+ easing: tween.easeInOut,
+ onFinish: onFinish
+ });
+ }
+});
+// Start Game button container
var startGameButton = new Container();
+// Button shadow
+var startButtonShadow = startGameButton.attachAsset('shop_button', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 400,
+ height: 100
+});
+startButtonShadow.tint = 0x000000;
+startButtonShadow.alpha = 0.3;
+startButtonShadow.x = 4;
+startButtonShadow.y = 4;
+// Button gradient effect background
+var startButtonGlow = startGameButton.attachAsset('shop_button', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 420,
+ height: 120
+});
+startButtonGlow.tint = 0x66FF66;
+startButtonGlow.alpha = 0;
+// Main button background
var startGameButtonBg = startGameButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 100
});
-startGameButtonBg.tint = 0x44AA44;
+startGameButtonBg.tint = 0x228B22; // Forest green
+// Button text with shadow
+var startButtonTextShadow = new Text2('Start Game', {
+ size: 60,
+ fill: 0x000000
+});
+startButtonTextShadow.anchor.set(0.5, 0.5);
+startButtonTextShadow.x = 2;
+startButtonTextShadow.y = 2;
+startButtonTextShadow.alpha = 0.5;
+startGameButton.addChild(startButtonTextShadow);
var startGameButtonText = new Text2('Start Game', {
size: 60,
fill: 0xFFFFFF
});
@@ -3189,17 +3398,66 @@
startGameButton.addChild(startGameButtonText);
startGameButton.x = 0; // Center relative to overlay
startGameButton.y = -266; // Adjusted position relative to center
titleOverlay.addChild(startGameButton);
-// How to Play button
+// Add hover animation
+startGameButton.down = function () {
+ tween(startGameButtonBg, {
+ scaleX: 0.95,
+ scaleY: 0.95
+ }, {
+ duration: 100,
+ easing: tween.easeOut
+ });
+ tween(startButtonGlow, {
+ alpha: 0.5,
+ scaleX: 1.1,
+ scaleY: 1.1
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
+};
+// How to Play button container
var howToPlayButton = new Container();
+// Button shadow
+var howToPlayShadow = howToPlayButton.attachAsset('shop_button', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 400,
+ height: 100
+});
+howToPlayShadow.tint = 0x000000;
+howToPlayShadow.alpha = 0.3;
+howToPlayShadow.x = 4;
+howToPlayShadow.y = 4;
+// Button glow effect
+var howToPlayGlow = howToPlayButton.attachAsset('shop_button', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 420,
+ height: 120
+});
+howToPlayGlow.tint = 0x6666FF;
+howToPlayGlow.alpha = 0;
+// Main button background
var howToPlayButtonBg = howToPlayButton.attachAsset('shop_button', {
anchorX: 0.5,
anchorY: 0.5,
width: 400,
height: 100
});
-howToPlayButtonBg.tint = 0x4444FF;
+howToPlayButtonBg.tint = 0x4169E1; // Royal blue
+// Button text with shadow
+var howToPlayTextShadow = new Text2('How to Play', {
+ size: 60,
+ fill: 0x000000
+});
+howToPlayTextShadow.anchor.set(0.5, 0.5);
+howToPlayTextShadow.x = 2;
+howToPlayTextShadow.y = 2;
+howToPlayTextShadow.alpha = 0.5;
+howToPlayButton.addChild(howToPlayTextShadow);
var howToPlayButtonText = new Text2('How to Play', {
size: 60,
fill: 0xFFFFFF
});
@@ -3207,10 +3465,44 @@
howToPlayButton.addChild(howToPlayButtonText);
howToPlayButton.x = 0; // Center relative to overlay
howToPlayButton.y = -116; // Adjusted position relative to center
titleOverlay.addChild(howToPlayButton);
+// Add hover animation
+howToPlayButton.down = function () {
+ tween(howToPlayButtonBg, {
+ scaleX: 0.95,
+ scaleY: 0.95
+ }, {
+ duration: 100,
+ easing: tween.easeOut
+ });
+ tween(howToPlayGlow, {
+ alpha: 0.5,
+ scaleX: 1.1,
+ scaleY: 1.1
+ }, {
+ duration: 200,
+ easing: tween.easeOut
+ });
+};
// How to Play button click handler
howToPlayButton.up = function () {
+ // Reset button scale
+ tween(howToPlayButtonBg, {
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 100,
+ easing: tween.easeIn
+ });
+ tween(howToPlayGlow, {
+ alpha: 0
+ }, {
+ duration: 200,
+ easing: tween.easeIn
+ });
+ // Play sound effect
+ LK.getSound('purchase').play();
// Create how to play modal
var howToPlayModal = new Container();
var howToPlayModalBg = howToPlayModal.attachAsset('shop_area_bg', {
anchorX: 0.5,
@@ -3218,9 +3510,9 @@
width: 1600,
height: 2000
});
howToPlayModalBg.alpha = 0.95;
- howToPlayModalBg.tint = 0x2a2a2a;
+ howToPlayModalBg.tint = 0x1a1a2e; // Dark blue theme
howToPlayModalBg.x = 0;
howToPlayModalBg.y = 0;
// Modal title with fun subtitle
var modalTitle = new Text2('How to Play', {
@@ -3389,40 +3681,164 @@
titleOverlay.addChild(howToPlayModal);
};
// Start Game button click handler
startGameButton.up = function () {
+ // Reset button scale
+ tween(startGameButtonBg, {
+ scaleX: 1.0,
+ scaleY: 1.0
+ }, {
+ duration: 100,
+ easing: tween.easeIn
+ });
+ tween(startButtonGlow, {
+ alpha: 0
+ }, {
+ duration: 200,
+ easing: tween.easeIn
+ });
+ // Play purchase sound for feedback
+ LK.getSound('purchase').play();
+ // Create expanding circle transition effect
+ var transitionCircle = titleOverlay.attachAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 50,
+ height: 50
+ });
+ transitionCircle.tint = 0xFFD700;
+ transitionCircle.x = 0;
+ transitionCircle.y = -266; // Start from button position
+ tween(transitionCircle, {
+ scaleX: 100,
+ scaleY: 100,
+ alpha: 0.8
+ }, {
+ duration: 600,
+ easing: tween.easeOut
+ });
// Hide title overlay with fade animation
tween(titleOverlay, {
alpha: 0
}, {
duration: 500,
easing: tween.easeOut,
onFinish: function onFinish() {
+ // Clear timers
+ LK.clearInterval(particleTimer);
+ LK.clearInterval(sparkleTimer);
titleOverlay.destroy();
}
});
};
// Add title overlay to LK.gui.center to ensure it renders above all game elements
LK.gui.center.addChild(titleOverlay);
// No need to set x,y as gui.center is already centered
-// Add title glow animation
-tween(gameTitle, {
- scaleX: 1.1,
- scaleY: 1.1
+// Add enhanced title animation with rotation
+tween(titleContainer, {
+ scaleX: 1.05,
+ scaleY: 1.05,
+ rotation: 0.02
}, {
- duration: 1500,
+ duration: 2000,
easing: tween.easeInOut,
onFinish: function onFinish() {
- tween(gameTitle, {
- scaleX: 1.0,
- scaleY: 1.0
+ tween(titleContainer, {
+ scaleX: 0.95,
+ scaleY: 0.95,
+ rotation: -0.02
}, {
- duration: 1500,
+ duration: 2000,
easing: tween.easeInOut,
onFinish: onFinish
});
}
});
+// Add decorative crown above title
+var crownContainer = new Container();
+titleOverlay.addChild(crownContainer);
+crownContainer.y = -666;
+// Create pixelart crown
+var crownBase = crownContainer.attachAsset('king', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 80,
+ height: 30
+});
+crownBase.tint = 0xFFD700;
+var crownLeft = crownContainer.attachAsset('king', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 20,
+ height: 40
+});
+crownLeft.x = -25;
+crownLeft.y = -20;
+crownLeft.tint = 0xFFD700;
+var crownCenter = crownContainer.attachAsset('king', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 25,
+ height: 50
+});
+crownCenter.x = 0;
+crownCenter.y = -25;
+crownCenter.tint = 0xFFD700;
+var crownRight = crownContainer.attachAsset('king', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 20,
+ height: 40
+});
+crownRight.x = 25;
+crownRight.y = -20;
+crownRight.tint = 0xFFD700;
+// Add jewels to crown
+var jewelLeft = crownContainer.attachAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 12,
+ height: 12
+});
+jewelLeft.x = -25;
+jewelLeft.y = -35;
+jewelLeft.tint = 0xFF0000;
+var jewelCenter = crownContainer.attachAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 15,
+ height: 15
+});
+jewelCenter.x = 0;
+jewelCenter.y = -45;
+jewelCenter.tint = 0x4169E1;
+var jewelRight = crownContainer.attachAsset('bullet', {
+ anchorX: 0.5,
+ anchorY: 0.5,
+ width: 12,
+ height: 12
+});
+jewelRight.x = 25;
+jewelRight.y = -35;
+jewelRight.tint = 0xFF0000;
+// Animate crown floating
+tween(crownContainer, {
+ y: -656,
+ rotation: 0.05
+}, {
+ duration: 2500,
+ easing: tween.easeInOut,
+ onFinish: function onFinish() {
+ tween(crownContainer, {
+ y: -666,
+ rotation: -0.05
+ }, {
+ duration: 2500,
+ easing: tween.easeInOut,
+ onFinish: onFinish
+ });
+ }
+});
var GRID_COLS = 9;
var GRID_ROWS = 9;
var GRID_CELL_SIZE = 227; // 2048 / 9 ≈ 227
var GRID_START_X = (2048 - GRID_COLS * GRID_CELL_SIZE) / 2;