User prompt
Damage: Increases the base damage of each bullet.
User prompt
Fire Rate: Decreases the time between shots (e.g., from 1 second to 0.9, then 0.8, etc.).
User prompt
Resource Drops: When enemies are defeated, they have a chance to drop "Scrap Metal" (a new collectible resource). Different enemy types could drop varying amounts of Scrap Metal, with Bosses dropping significant quantities.
User prompt
Please fix the bug: 'playerData is not defined' in or related to this line: 'var fireRateBonus = (playerData.fireRateLevel - 1) * 0.15; // 15% faster per level' Line Number: 1300 ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Score & Economy: "Score increases based on enemy type defeated" is good. We could also introduce a currency dropped by enemies that players can collect (like the power-ups) to purchase permanent upgrades for their castle (e.g., increased fire rate, more health, faster movement speed, or unlocking new targeting priorities). This would add a much-needed long-term progression system beyond just survival. ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
Enemy Scaling: Instead of just "stronger, faster," let's specify. For example, "Enemies gain +5% health and +2% speed per wave."
User prompt
Reward Spawning & Expiration: "Maximum 3 rewards can exist on screen at once" and "Rewards expire after 7 seconds if not collected" are great constraints. We can introduce a visual cue for expiring rewards (e.g., flashing, fading). The spawn rate of rewards should be balanced – perhaps tied to enemy defeats or time elapsed. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Green Bullets Reward (70% chance): This is a direct damage increase. Let's define the increment: does it add 1 more bullet, or double the current bullets? We could also introduce a duration for this power-up, or make it a permanent upgrade that stacks. If it stacks, there should be a maximum number of bullets the castle can fire simultaneously.
User prompt
Enemy Waves & Progression: The 30-second wave timer is a good start. We can make the wave progression more engaging by introducing "mini-boss" waves every few waves (e.g., every 5th or 10th wave) where a Boss Enemy or a high concentration of Tank/Shield enemies appears. This provides a clear milestone for players. The "stronger, faster enemies" can be quantified: perhaps enemies gain a certain percentage more health and speed with each new wave.
User prompt
Castle Movement: The ability to move the castle is key for strategic positioning. The "smooth walking animation" is a great visual cue. We should also define the movement speed of the castle and consider if it can be upgraded. This movement will be crucial for dodging, collecting power-ups, and escaping high-density enemy areas. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
Auto-Fire Defense: The core remains that your castle automatically shoots at the closest enemy every second. To add a layer of depth, let's introduce a targeting priority system. Instead of always the closest, the castle could prioritize enemies based on danger level (e.g., Snipers or Bombers first, then Fast Enemies, then Basic). This could even be a toggle or upgrade the player unlocks.
User prompt
Optimize the game's running speed
User prompt
add nickname login screen before game start ↪💡 Consider importing and using the following plugins: @upit/storage.v1
User prompt
When you pick up the bomb, make its radius small and when it explodes, make its radius larger. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
the killing radius of the bomb is **1000 pixels**
User prompt
Add another reward item, when it is taken, a bomb will appear where the character is after 3 seconds, and only clear the enemies in a certain radius. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
increase movement speed
User prompt
Apply realistic walking effect to character. Don't move like teleportation ↪💡 Consider importing and using the following plugins: @upit/tween.v1
User prompt
double the size of the grid
User prompt
make the grid in the background random and make it look natural as it looks crooked
User prompt
creating a visual for the game background
User prompt
add award receiving sound effect
User prompt
create simple 8 bit exciting theme music
User prompt
If no reward is received, it will disappear after 7 seconds.
User prompt
If no reward is received, it will disappear after 2 seconds. ↪💡 Consider importing and using the following plugins: @upit/tween.v1
/****
* Plugins
****/
var tween = LK.import("@upit/tween.v1");
var storage = LK.import("@upit/storage.v1", {
coins: 0,
fireRateLevel: 1,
movementSpeedLevel: 1,
healthLevel: 1,
damageLevel: 1
});
/****
* Classes
****/
var Bomb = Container.expand(function () {
var self = Container.call(this);
var bombGraphics = self.attachAsset('bomb', {
anchorX: 0.5,
anchorY: 0.5
});
bombGraphics.tint = 0xFF0000;
self.explosionRadius = 1000;
self.fuseTime = 120; // 2 seconds at 60fps
self.blinkSpeed = 10;
// Start with small scale
self.scaleX = 0.3;
self.scaleY = 0.3;
self.update = function () {
self.fuseTime--;
// Blinking effect that gets faster as explosion approaches
var blinkInterval = Math.max(5, Math.floor(self.fuseTime / 10));
if (LK.ticks % blinkInterval === 0) {
bombGraphics.alpha = bombGraphics.alpha === 1 ? 0.3 : 1;
}
// Scale pulsing effect
var pulseScale = 1 + Math.sin(LK.ticks * 0.3) * 0.1;
self.scaleX = pulseScale;
self.scaleY = pulseScale;
// Explode when fuse runs out
if (self.fuseTime <= 0) {
self.explode();
}
};
self.explode = function () {
// Visual explosion effect
LK.effects.flashScreen(0xFF4400, 600);
// Scale up rapidly during explosion
tween(self, {
scaleX: 3,
scaleY: 3
}, {
duration: 300,
easing: tween.easeOut
});
// Damage enemies in radius
for (var i = enemies.length - 1; i >= 0; i--) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosionRadius) {
// Instant kill for enemies in blast radius
enemy.takeDamage(999);
}
}
// Remove bomb from game after explosion animation
LK.setTimeout(function () {
self.destroy();
for (var i = bombs.length - 1; i >= 0; i--) {
if (bombs[i] === self) {
bombs.splice(i, 1);
break;
}
}
}, 300);
};
return self;
});
var BombReward = Container.expand(function () {
var self = Container.call(this);
var rewardGraphics = self.attachAsset('bombReward', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it visually distinct with orange tint
rewardGraphics.tint = 0xFF8800;
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0;
self.lifetime = 420; // 7 seconds at 60fps
self.update = function () {
// Gentle bobbing animation
self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Decrease lifetime
self.lifetime--;
// Start fading when close to expiring
if (self.lifetime <= 120) {
var alpha = self.lifetime / 120;
rewardGraphics.alpha = alpha;
// Add flashing animation when close to expiring
var flashInterval = Math.max(5, Math.floor(self.lifetime / 20));
if (LK.ticks % flashInterval === 0) {
tween(rewardGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: flashInterval * 8,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rewardGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: flashInterval * 8,
easing: tween.easeIn
});
}
});
}
}
// Remove when lifetime expires
if (self.lifetime <= 0) {
tween(self, {
scaleX: 0,
scaleY: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
for (var i = bombRewards.length - 1; i >= 0; i--) {
if (bombRewards[i] === self) {
bombRewards.splice(i, 1);
break;
}
}
}
});
}
};
self.down = function (x, y, obj) {
// Schedule bomb to appear at character position after 3 seconds
var bombX = castle.x;
var bombY = castle.y;
LK.setTimeout(function () {
var bomb = new Bomb();
bomb.x = bombX;
bomb.y = bombY;
bombs.push(bomb);
game.addChild(bomb);
}, 3000);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFF8800, 500);
self.destroy();
for (var i = bombRewards.length - 1; i >= 0; i--) {
if (bombRewards[i] === self) {
bombRewards.splice(i, 1);
break;
}
}
};
return self;
});
var BomberEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('bomberEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.speed = 1.2;
self.damage = 12;
self.explosionRadius = 100;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.explode();
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.explode = function () {
LK.effects.flashScreen(0xFF8800, 300);
// Damage nearby enemies
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self) {
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.explosionRadius) {
enemy.takeDamage(2);
}
}
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 30);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.explode();
}
};
return self;
});
var BossEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('bossEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 15;
self.maxHealth = 15;
self.speed = 0.6;
self.damage = 30;
self.spawnCooldown = 0;
self.spawnInterval = 300; // Spawns minions every 5 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Spawn minion enemies
if (self.spawnCooldown > 0) {
self.spawnCooldown--;
}
if (self.spawnCooldown <= 0 && enemies.length < 15) {
var minion = new Enemy();
var angle = Math.random() * Math.PI * 2;
minion.x = self.x + Math.cos(angle) * 60;
minion.y = self.y + Math.sin(angle) * 60;
minion.lastX = minion.x;
minion.lastY = minion.y;
var dx2 = castle.x - minion.x;
var dy2 = castle.y - minion.y;
minion.lastCastleDistance = Math.sqrt(dx2 * dx2 + dy2 * dy2);
enemies.push(minion);
game.addChild(minion);
self.spawnCooldown = self.spawnInterval;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
var reducedDamage = damage * 0.7; // Boss takes 30% less damage
self.health -= reducedDamage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 100);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
LK.effects.flashScreen(0x440044, 1000);
// Drop multiple currency (guaranteed)
for (var c = 0; c < 3; c++) {
var coin = new CurrencyDrop();
var angle = Math.random() * Math.PI * 2;
var distance = 20 + Math.random() * 40;
coin.x = self.x + Math.cos(angle) * distance;
coin.y = self.y + Math.sin(angle) * distance;
coin.baseY = coin.y;
coin.value = 15; // High value for boss
currencyDrops.push(coin);
game.addChild(coin);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Bullet = Container.expand(function () {
var self = Container.call(this);
var bulletGraphics = self.attachAsset('bullet', {
anchorX: 0.5,
anchorY: 0.5
});
self.speed = 50;
self.velocityX = 0;
self.velocityY = 0;
self.damage = 1.01;
self.lastX = 0;
self.lastY = 0;
self.update = function () {
self.lastX = self.x;
self.lastY = self.y;
self.x += self.velocityX;
self.y += self.velocityY;
// Add spinning animation
self.rotation += 0.2;
// Remove if off screen
if (self.x < -50 || self.x > 2098 || self.y < -50 || self.y > 2782) {
self.destroy();
for (var i = bullets.length - 1; i >= 0; i--) {
if (bullets[i] === self) {
bullets.splice(i, 1);
break;
}
}
}
};
return self;
});
var Castle = Container.expand(function () {
var self = Container.call(this);
var castleGraphics = self.attachAsset('castle', {
anchorX: 0.5,
anchorY: 0.5
});
// Set physical diameter to match image dimensions
self.width = castleGraphics.width;
self.height = castleGraphics.height;
self.health = 100;
self.maxHealth = 100;
self.bulletsPerShot = 1;
self.shootCooldown = 0;
self.shootInterval = 60; // Fire every second at 60fps
self.update = function () {
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
// Auto-fire with priority targeting system
if (self.shootCooldown <= 0 && enemies.length > 0) {
var targetEnemy = null;
var bestPriority = -1;
var bestDistance = Infinity;
// Priority levels: Higher number = higher priority
var priorityMap = {
'SniperEnemy': 5,
// Highest priority - long range damage
'BomberEnemy': 4,
// High priority - explosive threat
'BossEnemy': 4,
// High priority - powerful enemy
'FastEnemy': 3,
// Medium-high priority - quick threat
'HealerEnemy': 3,
// Medium-high priority - supports others
'TeleporterEnemy': 2,
// Medium priority - unpredictable
'SplitterEnemy': 2,
// Medium priority - creates more enemies
'TankEnemy': 1,
// Low priority - slow but tough
'ShieldEnemy': 1,
// Low priority - tough but manageable
'Enemy': 0 // Lowest priority - basic enemy
};
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Determine enemy type by checking constructor name
var enemyType = 'Enemy'; // Default
if (enemy.constructor === SniperEnemy) enemyType = 'SniperEnemy';else if (enemy.constructor === BomberEnemy) enemyType = 'BomberEnemy';else if (enemy.constructor === BossEnemy) enemyType = 'BossEnemy';else if (enemy.constructor === FastEnemy) enemyType = 'FastEnemy';else if (enemy.constructor === HealerEnemy) enemyType = 'HealerEnemy';else if (enemy.constructor === TeleporterEnemy) enemyType = 'TeleporterEnemy';else if (enemy.constructor === SplitterEnemy) enemyType = 'SplitterEnemy';else if (enemy.constructor === TankEnemy) enemyType = 'TankEnemy';else if (enemy.constructor === ShieldEnemy) enemyType = 'ShieldEnemy';
var priority = priorityMap[enemyType] || 0;
// Select target based on priority first, then distance
if (priority > bestPriority || priority === bestPriority && distance < bestDistance) {
bestPriority = priority;
bestDistance = distance;
targetEnemy = enemy;
}
}
if (targetEnemy) {
self.fireAtTarget(targetEnemy);
self.shootCooldown = self.shootInterval;
}
}
};
self.fireAtTarget = function (target) {
var baseAngle = Math.atan2(target.y - self.y, target.x - self.x);
var spreadAngle = Math.PI / 6; // 30 degrees spread
for (var i = 0; i < self.bulletsPerShot; i++) {
var bullet = new Bullet();
bullet.x = self.x;
bullet.y = self.y;
// Apply upgraded damage if available
if (self.upgradedBulletDamage) {
bullet.damage = self.upgradedBulletDamage;
}
var offset = 0;
if (self.bulletsPerShot > 1) {
offset = (i - (self.bulletsPerShot - 1) / 2) * (spreadAngle / Math.max(1, self.bulletsPerShot - 1));
}
var angle = baseAngle + offset;
bullet.velocityX = Math.cos(angle) * bullet.speed;
bullet.velocityY = Math.sin(angle) * bullet.speed;
// Set bullet rotation to match direction
bullet.rotation = angle;
bullets.push(bullet);
game.addChild(bullet);
}
LK.getSound('shoot').play();
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 300);
if (self.health <= 0) {
// Save collected coins to persistent storage
storage.coins += currentCoins;
LK.showGameOver();
}
};
return self;
});
var CurrencyDrop = Container.expand(function () {
var self = Container.call(this);
var coinGraphics = self.attachAsset('reward', {
anchorX: 0.5,
anchorY: 0.5
});
// Make it golden for currency
coinGraphics.tint = 0xFFD700;
coinGraphics.scaleX = 0.6;
coinGraphics.scaleY = 0.6;
self.value = 5; // Base currency value
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0;
self.lifetime = 600; // 10 seconds at 60fps
self.magnetRange = 120; // Range at which coin is attracted to castle
self.update = function () {
// Gentle bobbing animation
self.y = self.baseY + Math.sin(LK.ticks * 0.15 + self.bobOffset) * 3;
// Decrease lifetime
self.lifetime--;
// Magnet effect when castle is nearby
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= self.magnetRange) {
var magnetSpeed = 3;
var moveX = dx / distance * magnetSpeed;
var moveY = dy / distance * magnetSpeed;
self.x += moveX;
self.y += moveY;
}
// Start fading when close to expiring
if (self.lifetime <= 120) {
var alpha = self.lifetime / 120;
coinGraphics.alpha = alpha;
}
// Remove when lifetime expires
if (self.lifetime <= 0) {
self.destroy();
for (var i = currencyDrops.length - 1; i >= 0; i--) {
if (currencyDrops[i] === self) {
currencyDrops.splice(i, 1);
break;
}
}
}
};
return self;
});
var Enemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('enemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 3;
self.maxHealth = 3;
self.speed = 1;
self.damage = 10;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistanceSquared = Infinity;
self.update = function () {
// Move toward castle
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distanceSquared = dx * dx + dy * dy;
if (distanceSquared > 3600) {
// 60^2 = 3600
// Don't overlap with castle
var distance = Math.sqrt(distanceSquared);
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
if (self.lastCastleDistanceSquared > 3600 && distanceSquared <= 3600) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistanceSquared = distanceSquared;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 10);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
// Drop currency (60% chance)
if (Math.random() < 0.6) {
var coin = new CurrencyDrop();
coin.x = self.x;
coin.y = self.y;
coin.baseY = coin.y;
currencyDrops.push(coin);
game.addChild(coin);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var FastEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('fastEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 1;
self.maxHealth = 1;
self.speed = 3;
self.damage = 5;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
// Move toward castle with higher speed
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 15);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
// Drop currency (50% chance, less than basic enemy)
if (Math.random() < 0.5) {
var coin = new CurrencyDrop();
coin.x = self.x;
coin.y = self.y;
coin.baseY = coin.y;
coin.value = 3; // Less value than basic enemy
currencyDrops.push(coin);
game.addChild(coin);
}
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var HealerEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('healerEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 4;
self.maxHealth = 4;
self.speed = 0.8;
self.damage = 8;
self.healCooldown = 0;
self.healInterval = 180; // Heals every 3 seconds
self.healRadius = 150;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Heal nearby enemies
if (self.healCooldown > 0) {
self.healCooldown--;
}
if (self.healCooldown <= 0) {
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
if (enemy !== self && enemy.health < enemy.maxHealth) {
var dx2 = enemy.x - self.x;
var dy2 = enemy.y - self.y;
var healDistance = Math.sqrt(dx2 * dx2 + dy2 * dy2);
if (healDistance <= self.healRadius) {
enemy.health = Math.min(enemy.maxHealth, enemy.health + 1);
LK.effects.flashObject(enemy, 0x88FF00, 200);
}
}
}
self.healCooldown = self.healInterval;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 35);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var Reward = Container.expand(function () {
var self = Container.call(this);
var rewardGraphics = self.attachAsset('reward', {
anchorX: 0.5,
anchorY: 0.5
});
self.bobOffset = Math.random() * Math.PI * 2;
self.baseY = 0;
self.lifetime = 420; // 7 seconds at 60fps
self.update = function () {
// Gentle bobbing animation
self.y = self.baseY + Math.sin(LK.ticks * 0.1 + self.bobOffset) * 5;
// Decrease lifetime
self.lifetime--;
// Start fading when close to expiring
if (self.lifetime <= 120) {
var alpha = self.lifetime / 120;
rewardGraphics.alpha = alpha;
// Add flashing animation when close to expiring
var flashInterval = Math.max(5, Math.floor(self.lifetime / 20));
if (LK.ticks % flashInterval === 0) {
tween(rewardGraphics, {
scaleX: 1.3,
scaleY: 1.3
}, {
duration: flashInterval * 8,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(rewardGraphics, {
scaleX: 1,
scaleY: 1
}, {
duration: flashInterval * 8,
easing: tween.easeIn
});
}
});
}
}
// Remove when lifetime expires
if (self.lifetime <= 0) {
tween(self, {
scaleX: 0,
scaleY: 0
}, {
duration: 200,
onFinish: function onFinish() {
self.destroy();
for (var i = rewards.length - 1; i >= 0; i--) {
if (rewards[i] === self) {
rewards.splice(i, 1);
break;
}
}
}
});
}
};
self.down = function (x, y, obj) {
// Define maximum bullet limit to prevent excessive stacking
var maxBullets = 8;
if (castle.bulletsPerShot < maxBullets) {
// Add 1 bullet per reward (consistent increment)
castle.bulletsPerShot++;
bulletsText.setText('Bullets: ' + castle.bulletsPerShot);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0x00FF00, 500);
// Visual feedback for bullet upgrade with scaling effect
tween(castle, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(castle, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
} else {
// At maximum bullets - give score bonus instead
LK.setScore(LK.getScore() + 100);
scoreText.setText(LK.getScore());
bulletsText.setText('Bullets: ' + castle.bulletsPerShot + ' (MAX)');
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFFD700, 500); // Gold flash for max bonus
// Brief text notification
var maxText = new Text2('+100 SCORE!', {
size: 40,
fill: 0xFFD700
});
maxText.anchor.set(0.5, 0.5);
maxText.x = castle.x;
maxText.y = castle.y - 60;
game.addChild(maxText);
// Animate and remove notification
tween(maxText, {
y: maxText.y - 50,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
maxText.destroy();
}
});
}
self.destroy();
for (var i = rewards.length - 1; i >= 0; i--) {
if (rewards[i] === self) {
rewards.splice(i, 1);
break;
}
}
};
return self;
});
var ShieldEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('shieldEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 6;
self.maxHealth = 6;
self.speed = 0.7;
self.damage = 15;
self.shieldRadius = 80;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 40);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var SniperEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('sniperEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.maxHealth = 2;
self.speed = 1.5;
self.damage = 15;
self.shootCooldown = 0;
self.shootInterval = 120; // Shoots every 2 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Move toward castle until in range (300 pixels)
if (distance > 300) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
} else {
// In range, shoot at castle
if (self.shootCooldown > 0) {
self.shootCooldown--;
}
if (self.shootCooldown <= 0) {
castle.takeDamage(self.damage);
LK.effects.flashObject(self, 0x00FFFF, 300);
self.shootCooldown = self.shootInterval;
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = distance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 20);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var SplitterEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('splitterEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 5;
self.maxHealth = 5;
self.speed = 0.9;
self.damage = 18;
self.splitCount = 2;
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.split = function () {
for (var i = 0; i < self.splitCount; i++) {
var smallEnemy = new FastEnemy();
var angle = Math.PI * 2 / self.splitCount * i;
smallEnemy.x = self.x + Math.cos(angle) * 30;
smallEnemy.y = self.y + Math.sin(angle) * 30;
smallEnemy.lastX = smallEnemy.x;
smallEnemy.lastY = smallEnemy.y;
var dx = castle.x - smallEnemy.x;
var dy = castle.y - smallEnemy.y;
smallEnemy.lastCastleDistance = Math.sqrt(dx * dx + dy * dy);
enemies.push(smallEnemy);
game.addChild(smallEnemy);
}
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 50);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.split();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var TankEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('tankEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 8;
self.maxHealth = 8;
self.speed = 0.5;
self.damage = 20;
self.armor = 0.5; // Takes 50% less damage
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
// Move toward castle slowly
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Check if reached castle
var currentDistance = Math.sqrt(dx * dx + dy * dy);
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
var reducedDamage = damage * (1 - self.armor);
self.health -= reducedDamage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 25);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
var TeleporterEnemy = Container.expand(function () {
var self = Container.call(this);
var enemyGraphics = self.attachAsset('teleporterEnemy', {
anchorX: 0.5,
anchorY: 0.5
});
self.health = 2;
self.maxHealth = 2;
self.speed = 1;
self.damage = 12;
self.teleportCooldown = 0;
self.teleportInterval = 240; // Teleports every 4 seconds
self.lastX = 0;
self.lastY = 0;
self.lastCastleDistance = Infinity;
self.update = function () {
var dx = castle.x - self.x;
var dy = castle.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance > 60) {
var moveX = dx / distance * self.speed;
var moveY = dy / distance * self.speed;
self.x += moveX;
self.y += moveY;
}
// Teleport randomly
if (self.teleportCooldown > 0) {
self.teleportCooldown--;
}
if (self.teleportCooldown <= 0) {
var angle = Math.random() * Math.PI * 2;
var teleportDistance = 200 + Math.random() * 200;
self.x = castle.x + Math.cos(angle) * teleportDistance;
self.y = castle.y + Math.sin(angle) * teleportDistance;
// Keep within bounds
self.x = Math.max(50, Math.min(1998, self.x));
self.y = Math.max(50, Math.min(2682, self.y));
LK.effects.flashObject(self, 0xFF00FF, 400);
self.teleportCooldown = self.teleportInterval;
}
var currentDistance = Math.sqrt((castle.x - self.x) * (castle.x - self.x) + (castle.y - self.y) * (castle.y - self.y));
if (self.lastCastleDistance > 60 && currentDistance <= 60) {
castle.takeDamage(self.damage);
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
self.lastX = self.x;
self.lastY = self.y;
self.lastCastleDistance = currentDistance;
};
self.takeDamage = function (damage) {
self.health -= damage;
LK.effects.flashObject(self, 0xFFFFFF, 200);
if (self.health <= 0) {
LK.setScore(LK.getScore() + 25);
scoreText.setText(LK.getScore());
LK.getSound('hit').play();
self.destroy();
for (var i = enemies.length - 1; i >= 0; i--) {
if (enemies[i] === self) {
enemies.splice(i, 1);
break;
}
}
}
};
return self;
});
/****
* Initialize Game
****/
var game = new LK.Game({
backgroundColor: 0x2F4F2F
});
/****
* Game Code
****/
// Game variables
var castle;
var enemies = [];
var bullets = [];
var rewards = [];
var bombRewards = [];
var bombs = [];
var currencyDrops = [];
var waveTimer = 0;
var enemySpawnTimer = 0;
var rewardSpawnTimer = 0;
var waveNumber = 1;
var isMiniBossWave = false;
var miniBossSpawned = false;
// Initialize persistent storage with defaults
// Current game currency (resets each game)
var currentCoins = 0;
// Create background pattern
function createBackground() {
var bgContainer = new Container();
// Create simpler grid pattern with fewer random calculations
for (var x = 0; x <= 2048; x += 240) {
for (var y = 0; y <= 2732; y += 240) {
// Main background tiles with minimal randomization
var tile = LK.getAsset('bgTile', {
anchorX: 0.5,
anchorY: 0.5
});
tile.x = x;
tile.y = y;
bgContainer.addChild(tile);
// Reduce accent frequency for better performance
if ((x + y) % 960 === 0) {
// Every 4th tile instead of random
var accent = LK.getAsset('bgAccent', {
anchorX: 0.5,
anchorY: 0.5
});
accent.x = x;
accent.y = y;
accent.alpha = 0.5;
bgContainer.addChild(accent);
}
}
}
// Set background container to lowest z-index
game.addChildAt(bgContainer, 0);
}
// Initialize background
createBackground();
// UI elements
var scoreText = new Text2('0', {
size: 60,
fill: 0xFFFFFF
});
scoreText.anchor.set(0.5, 0);
LK.gui.top.addChild(scoreText);
var bulletsText = new Text2('Bullets: 1', {
size: 50,
fill: 0x00FF00
});
bulletsText.anchor.set(0, 0);
bulletsText.x = 50;
bulletsText.y = 50;
LK.gui.topLeft.addChild(bulletsText);
var healthText = new Text2('Health: 100', {
size: 50,
fill: 0xFF0000
});
healthText.anchor.set(1, 0);
LK.gui.topRight.addChild(healthText);
var coinsText = new Text2('Coins: 0', {
size: 45,
fill: 0xFFD700
});
coinsText.anchor.set(0, 0);
coinsText.x = 50;
coinsText.y = 120;
LK.gui.topLeft.addChild(coinsText);
// Initialize castle
castle = game.addChild(new Castle());
castle.x = 1024;
castle.y = 1366;
// Apply permanent upgrades from storage
var fireRateBonus = (storage.fireRateLevel - 1) * 0.15; // 15% faster per level
castle.shootInterval = Math.max(20, Math.floor(castle.shootInterval * (1 - fireRateBonus)));
var healthBonus = (storage.healthLevel - 1) * 20; // +20 health per level
castle.health += healthBonus;
castle.maxHealth = castle.health;
var damageBonus = (storage.damageLevel - 1) * 0.2; // +20% damage per level
var baseBulletDamage = 1.01;
// Store upgraded damage for bullets
castle.upgradedBulletDamage = baseBulletDamage * (1 + damageBonus);
// Movement speed will be applied in the movement system
var movementSpeedBonus = (storage.movementSpeedLevel - 1) * 0.25; // 25% faster per level
castleMovementSpeed = 400 * (1 + movementSpeedBonus);
// Start background music
LK.playMusic('bgmusic');
// Spawn functions
function spawnEnemy() {
var enemy;
// Mini-boss wave logic - every 5th wave
if (isMiniBossWave) {
if (!miniBossSpawned) {
// Always spawn a boss enemy as the first enemy of mini-boss wave
enemy = new BossEnemy();
miniBossSpawned = true;
} else {
// Higher chance for tank/shield enemies in mini-boss waves
var enemyType = Math.random();
if (enemyType < 0.4) {
enemy = new TankEnemy();
} else if (enemyType < 0.7) {
enemy = new ShieldEnemy();
} else if (enemyType < 0.85) {
enemy = new BossEnemy();
} else {
enemy = new BomberEnemy();
}
}
} else {
// Normal wave enemy distribution
var enemyType = Math.random();
if (enemyType < 0.3) {
enemy = new Enemy();
} else if (enemyType < 0.45) {
enemy = new FastEnemy();
} else if (enemyType < 0.55) {
enemy = new TankEnemy();
} else if (enemyType < 0.65) {
enemy = new SniperEnemy();
} else if (enemyType < 0.73) {
enemy = new BomberEnemy();
} else if (enemyType < 0.8) {
enemy = new HealerEnemy();
} else if (enemyType < 0.86) {
enemy = new ShieldEnemy();
} else if (enemyType < 0.92) {
enemy = new TeleporterEnemy();
} else if (enemyType < 0.97) {
enemy = new SplitterEnemy();
} else {
enemy = new BossEnemy();
}
}
// Spawn from random edge
var side = Math.floor(Math.random() * 4);
switch (side) {
case 0:
// Top
enemy.x = Math.random() * 2048;
enemy.y = -20;
break;
case 1:
// Right
enemy.x = 2068;
enemy.y = Math.random() * 2732;
break;
case 2:
// Bottom
enemy.x = Math.random() * 2048;
enemy.y = 2752;
break;
case 3:
// Left
enemy.x = -20;
enemy.y = Math.random() * 2732;
break;
}
// Quantified enemy scaling: +5% health and +2% speed per wave
var healthMultiplier = 1 + (waveNumber - 1) * 0.05;
var speedMultiplier = 1 + (waveNumber - 1) * 0.02;
var damageMultiplier = 1 + (waveNumber - 1) * 0.05;
// Apply scaling with proper rounding
enemy.health = Math.ceil(enemy.health * healthMultiplier);
enemy.maxHealth = enemy.health;
enemy.speed = enemy.speed * speedMultiplier;
enemy.damage = Math.ceil(enemy.damage * damageMultiplier);
// Extra scaling for mini-boss waves
if (isMiniBossWave) {
enemy.health = Math.ceil(enemy.health * 1.25); // 25% more health
enemy.maxHealth = enemy.health;
enemy.damage = Math.ceil(enemy.damage * 1.15); // 15% more damage
}
enemy.lastX = enemy.x;
enemy.lastY = enemy.y;
var dx = castle.x - enemy.x;
var dy = castle.y - enemy.y;
enemy.lastCastleDistanceSquared = dx * dx + dy * dy;
enemies.push(enemy);
game.addChild(enemy);
}
function spawnReward() {
var rewardType = Math.random();
var reward;
// 70% chance for regular reward, 30% chance for bomb reward
if (rewardType < 0.7) {
reward = new Reward();
rewards.push(reward);
} else {
reward = new BombReward();
bombRewards.push(reward);
}
// Spawn in accessible areas further away from the castle
var angle = Math.random() * Math.PI * 2;
var distance = 400 + Math.random() * 500;
reward.x = castle.x + Math.cos(angle) * distance;
reward.y = castle.y + Math.sin(angle) * distance;
// Keep within bounds
reward.x = Math.max(100, Math.min(1948, reward.x));
reward.y = Math.max(100, Math.min(2632, reward.y));
reward.baseY = reward.y;
game.addChild(reward);
// Add spawn animation with tween
reward.scaleX = 0;
reward.scaleY = 0;
tween(reward, {
scaleX: 1,
scaleY: 1
}, {
duration: 300,
easing: tween.easeOut
});
}
// Castle movement system with configurable speed
var castleMovementSpeed = 400; // pixels per second - can be upgraded later
var targetX = 1024;
var targetY = 1366;
var isMoving = false;
game.down = function (x, y, obj) {
// Set new target position
targetX = x;
targetY = y;
// Calculate distance to determine movement duration
var dx = targetX - castle.x;
var dy = targetY - castle.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Calculate duration based on configurable movement speed
var duration = Math.max(150, distance / castleMovementSpeed * 1000); // minimum 150ms for responsiveness
// Stop any existing movement tween
tween.stop(castle, {
x: true,
y: true
});
isMoving = true;
// Add walking animation with slight bobbing effect
var originalY = castle.y;
// Start smooth walking animation with subtle bounce
tween(castle, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
isMoving = false;
// Subtle landing effect
tween(castle, {
scaleX: 1.1,
scaleY: 0.9
}, {
duration: 100,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(castle, {
scaleX: 1,
scaleY: 1
}, {
duration: 100,
easing: tween.easeOut
});
}
});
}
});
// Add walking bobbing animation during movement
if (duration > 300) {
var bobDuration = duration / 3;
tween(castle, {
scaleY: 1.05
}, {
duration: bobDuration,
easing: tween.easeInOut,
onFinish: function onFinish() {
tween(castle, {
scaleY: 1
}, {
duration: bobDuration,
easing: tween.easeInOut
});
}
});
}
};
game.move = function (x, y, obj) {
// Update target while moving (allows for path correction)
if (isMoving) {
targetX = x;
targetY = y;
// Calculate new distance and duration
var dx = targetX - castle.x;
var dy = targetY - castle.y;
var distance = Math.sqrt(dx * dx + dy * dy);
// Only update if the new target is significantly different
if (distance > 40) {
var duration = Math.max(150, distance / castleMovementSpeed * 1000);
// Stop current tween and start new one
tween.stop(castle, {
x: true,
y: true
});
tween(castle, {
x: targetX,
y: targetY
}, {
duration: duration,
easing: tween.easeInOut,
onFinish: function onFinish() {
isMoving = false;
}
});
}
}
};
game.up = function (x, y, obj) {
// Optional: Could add final position adjustment here if needed
};
// Game update loop
game.update = function () {
// Update wave timer
waveTimer++;
// Spawn enemies
enemySpawnTimer++;
var spawnRate = Math.max(30, 120 - waveNumber * 5); // Faster spawning each wave
// Increase spawn rate for mini-boss waves
if (isMiniBossWave) {
spawnRate = Math.max(20, spawnRate * 0.7); // 30% faster spawning in mini-boss waves
}
if (enemySpawnTimer >= spawnRate) {
spawnEnemy();
enemySpawnTimer = 0;
}
// Spawn rewards occasionally - tied to enemy defeats for better balance
rewardSpawnTimer++;
var enemiesDefeated = Math.floor(LK.getScore() / 10); // Approximate enemies defeated based on score
var rewardThreshold = 600 - Math.min(300, enemiesDefeated * 5); // Faster spawning as more enemies defeated
if (rewardSpawnTimer >= rewardThreshold && rewards.length + bombRewards.length < 3) {
// Spawn rate increases with progress, max 3 rewards total
spawnReward();
rewardSpawnTimer = 0;
}
// Advance wave every 30 seconds
if (waveTimer >= 1800) {
// 30 seconds at 60fps
waveNumber++;
waveTimer = 0;
miniBossSpawned = false; // Reset mini-boss spawn flag
// Check if this is a mini-boss wave (every 5th wave)
isMiniBossWave = waveNumber % 5 === 0;
if (isMiniBossWave) {
// Special visual effect for mini-boss waves
LK.effects.flashScreen(0xFF4400, 1000); // Orange flash for mini-boss
// Show wave announcement
var waveText = new Text2('MINI-BOSS WAVE ' + waveNumber, {
size: 80,
fill: 0xFF4400
});
waveText.anchor.set(0.5, 0.5);
waveText.x = 1024;
waveText.y = 800;
game.addChild(waveText);
// Fade out wave text after 2 seconds
tween(waveText, {
alpha: 0
}, {
duration: 2000,
onFinish: function onFinish() {
waveText.destroy();
}
});
} else {
// Normal wave flash
LK.effects.flashScreen(0x0066FF, 500);
}
}
// Check bullet-enemy collisions
for (var b = bullets.length - 1; b >= 0; b--) {
var bullet = bullets[b];
var bulletHit = false;
// Early exit if bullet is off-screen
if (bullet.x < -100 || bullet.x > 2148 || bullet.y < -100 || bullet.y > 2832) {
bullet.destroy();
bullets.splice(b, 1);
continue;
}
for (var e = enemies.length - 1; e >= 0; e--) {
var enemy = enemies[e];
if (bullet.intersects(enemy)) {
enemy.takeDamage(bullet.damage);
bullet.destroy();
bullets.splice(b, 1);
bulletHit = true;
break;
}
}
if (bulletHit) continue;
}
// Check castle-reward collisions for pickup
for (var r = rewards.length - 1; r >= 0; r--) {
var reward = rewards[r];
if (castle.intersects(reward)) {
// Define maximum bullet limit to prevent excessive stacking
var maxBullets = 8;
if (castle.bulletsPerShot < maxBullets) {
// Add 1 bullet per reward (consistent increment)
castle.bulletsPerShot++;
bulletsText.setText('Bullets: ' + castle.bulletsPerShot);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0x00FF00, 500);
// Visual feedback for bullet upgrade with scaling effect
tween(castle, {
scaleX: 1.2,
scaleY: 1.2
}, {
duration: 200,
easing: tween.easeOut,
onFinish: function onFinish() {
tween(castle, {
scaleX: 1,
scaleY: 1
}, {
duration: 200,
easing: tween.easeIn
});
}
});
} else {
// At maximum bullets - give score bonus instead
LK.setScore(LK.getScore() + 100);
scoreText.setText(LK.getScore());
bulletsText.setText('Bullets: ' + castle.bulletsPerShot + ' (MAX)');
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFFD700, 500); // Gold flash for max bonus
// Brief text notification
var maxText = new Text2('+100 SCORE!', {
size: 40,
fill: 0xFFD700
});
maxText.anchor.set(0.5, 0.5);
maxText.x = castle.x;
maxText.y = castle.y - 60;
game.addChild(maxText);
// Animate and remove notification
tween(maxText, {
y: maxText.y - 50,
alpha: 0
}, {
duration: 1500,
onFinish: function onFinish() {
maxText.destroy();
}
});
}
reward.destroy();
rewards.splice(r, 1);
}
}
// Check castle-bomb reward collisions for pickup
for (var br = bombRewards.length - 1; br >= 0; br--) {
var bombReward = bombRewards[br];
if (castle.intersects(bombReward)) {
// Schedule bomb to appear at character position after 3 seconds
var bombX = castle.x;
var bombY = castle.y;
LK.setTimeout(function () {
var bomb = new Bomb();
bomb.x = bombX;
bomb.y = bombY;
bombs.push(bomb);
game.addChild(bomb);
}, 3000);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFF8800, 500);
bombReward.destroy();
bombRewards.splice(br, 1);
}
}
// Check castle-currency collisions for pickup
for (var cd = currencyDrops.length - 1; cd >= 0; cd--) {
var currencyDrop = currencyDrops[cd];
if (castle.intersects(currencyDrop)) {
currentCoins += currencyDrop.value;
coinsText.setText('Coins: ' + currentCoins);
LK.getSound('award').play();
LK.effects.flashObject(castle, 0xFFD700, 300);
currencyDrop.destroy();
currencyDrops.splice(cd, 1);
}
}
// Update UI
healthText.setText('Health: ' + castle.health);
scoreText.setText(LK.getScore());
}; ===================================================================
--- original.js
+++ change.js
@@ -445,9 +445,9 @@
self.health -= damage;
LK.effects.flashObject(self, 0xFF0000, 300);
if (self.health <= 0) {
// Save collected coins to persistent storage
- playerData.coins += currentCoins;
+ storage.coins += currentCoins;
LK.showGameOver();
}
};
return self;
@@ -1252,19 +1252,19 @@
castle = game.addChild(new Castle());
castle.x = 1024;
castle.y = 1366;
// Apply permanent upgrades from storage
-var fireRateBonus = (playerData.fireRateLevel - 1) * 0.15; // 15% faster per level
+var fireRateBonus = (storage.fireRateLevel - 1) * 0.15; // 15% faster per level
castle.shootInterval = Math.max(20, Math.floor(castle.shootInterval * (1 - fireRateBonus)));
-var healthBonus = (playerData.healthLevel - 1) * 20; // +20 health per level
+var healthBonus = (storage.healthLevel - 1) * 20; // +20 health per level
castle.health += healthBonus;
castle.maxHealth = castle.health;
-var damageBonus = (playerData.damageLevel - 1) * 0.2; // +20% damage per level
+var damageBonus = (storage.damageLevel - 1) * 0.2; // +20% damage per level
var baseBulletDamage = 1.01;
// Store upgraded damage for bullets
castle.upgradedBulletDamage = baseBulletDamage * (1 + damageBonus);
// Movement speed will be applied in the movement system
-var movementSpeedBonus = (playerData.movementSpeedLevel - 1) * 0.25; // 25% faster per level
+var movementSpeedBonus = (storage.movementSpeedLevel - 1) * 0.25; // 25% faster per level
castleMovementSpeed = 400 * (1 + movementSpeedBonus);
// Start background music
LK.playMusic('bgmusic');
// Spawn functions
square smiley face thick eyebrows. In-Game asset
pink stone round. In-Game asset
purple square crab. In-Game asset
round mouse face. In-Game asset
bright green fat star. In-Game asset
round red lava stone. In-Game asset
square yellow poisonous stone top view. In-Game asset
yellow flame ze poisonous bead top view. In-Game asset
grass top view. In-Game asset. 2d. High contrast. No shadows
bomb in green star