User prompt
Add new upgrades: Wizard tower [gain a wizard tower ally that shoots magic balls that slowdown enemies], Aimbot (Arrows seek out enemies)
User prompt
Poisoned enemies, when dead, don't acctually die; FIX THAT
User prompt
Cannon isn't firing any cannonballs; fix that
User prompt
Add an asset for cannonballs
User prompt
Add new upgrades: More shots [you now shoot 2 arrows at once with this upgrade! Affects Archer allies too!], Poison shots [your shots are now made out of poison! They deal low damage over time {does not affect archer allies' arrows}], Cannon [you gain a cannon that shoots cannonballs that deal massive damage to the stronghest enemy.], Ally ATK speed [all allies {Archer allies, cannons, swordsmen} have an attack speed buff]
User prompt
Add new upgrades: More shots [you now shoot 2 arrows at once with this upgrade! Affects Archer allies too!], Poison shots [your shots are now made out of poison! They deal low damage over time {does not affect archer allies' arrows}], Cannon [you gain a cannon that shoots cannonballs that deal massive damage to the stronghest enemy.], Ally ATK speed [all allies {Archer allies, cannons, swordsmen} have an attack speed buff]
User prompt
Add assets for wizard, shield and elite knight
User prompt
Add new enemies: Shield (high health [20 HP] but really slow; gains 20 HP every 35 score and starts appearing when you have 30 score), Wizard (a slow and fragile enemy that has the ability to teleport every 3 seconds! Gains more HP and speed every 10 score and starts appearing when you have 25 score), Elite knight (Same as knight, but has EVEN MORE HP [45 HP], gains 15 more HP every 30 score and starts appearing on 100 score)
User prompt
Swordsmen allies now chase enemies to attack them
User prompt
Swordsman tower isn't spawning swordsmen
User prompt
Add assets for swordsman tower and swordsman ally
User prompt
Rework the swordsman upgrade so you have a tower that spawns swordsmen that attack enemies on melee; your swordsmen die after 10 seconds
User prompt
Add assets for knight, thief and boss
User prompt
Regular enemies (which now are called swordsmen) only have 1HP; add new enemies: Knight (knights have 5 HP! They start appearing when you have 23 score and Their health increases by 5 every 30 score), Thief (fast little brat that has a 10% chance to dodge attacks that start appearing when you have 15 score, and Their speed increases by 25% every 25 score)
User prompt
Regular enemies still have 1 HP
User prompt
Heat-tipped arrows increase damage
User prompt
For some reason, Heat-tipped arrows doesn't appear when choosing upgrades, fix that
User prompt
Archer ally uses "Archer" asset
User prompt
Archer ally shoots at enemies every 4 seconds; add an asset for him!
User prompt
Now, every 10 score, you can unlock one of these 3 of these 7 upgrades: Faster reaload (decreases the time to reaload), Piercing shots (Increases how many enemies an arrow can pierce through), Quiver (Increases how many arrows you can shoot before realoading), Heat-tipped arrows (Increases damage), Archer ally (You gain an archer ally that shoots arrows at enemies independently; his arrows don't affect the reload counter), Sabotage (enemies are slower), Swordsman (you gain a swordsman that attacks enemies on melee) āŖš” Consider importing and using the following plugins: @upit/tween.v1
User prompt
Archer ally doesn't really do anything, Fix that by making him shoot every 3 seconds and by adding an asset for him āŖš” Consider importing and using the following plugins: @upit/tween.v1
User prompt
Archer ally shoots at enemies every 4 seconds; add an asset for him!
User prompt
Now, every 10 score, you can unlock one upgrade of your choice of these 3 of these 7 upgrades: Faster reaload (decreases the time to reaload), Piercing shots (Increases how many enemies an arrow can pierce through), Quiver (Increases how many arrows you can shoot before realoading), Heat-tipped arrows (Increases damage), Archer ally (You gain an archer ally that shoots arrows at enemies independently; his arrows don't affect the reload counter), Sabotage (enemies are slower), Swordsman (you gain a swordsman that attacks enemies on melee)
User prompt
Now, every 10 score, you can unlock one of these 3 of these 7 upgrades: Faster reaload (decreases the time to reaload), Piercing shots (Increases how many enemies an arrow can pierce through), Quiver (Increases how many arrows you can shoot before realoading), Heat-tipped arrows (Increases damage), Archer ally (You gain an archer ally that shoots arrows at enemies independently; his arrows don't affect the reload counter), Sabotage (enemies are slower), Swordsman (you gain a swordsman that attacks enemies on melee)
User prompt
Now, every 10 score, you can unlock one of these 3 of these 7 upgrades: Faster reaload (decreases the time to reaload), Piercing shots (Increases how many enemies an arrow can pierce through), Quiver (Increases how many arrows you can shoot before realoading), Heat-tipped arrows (Increases damage), Archer ally (You gain an archer ally that shoots arrows at enemies independently; his arrows don't affect the reload counter), Sabotage (enemies are slower), Swordsman (you gain a swordsman that attacks enemies on melee)
/****
* Classes
****/
/**
* Represents an allied archer that shoots arrows independently.
*/
var ArcherAlly = Container.expand(function () {
var self = Container.call(this);
// Create and attach the archer graphic asset
var graphics = self.attachAsset('Archer', {
anchorX: 0.5,
anchorY: 0.5
});
// No rotation needed for 'Archer' asset as it's already upright and flipped
self.fireTimer = 0; // Timer for shooting
self.fireInterval = 180; // Shoot every 3 seconds (3 * 60 ticks)
/**
* Update method called each game tick by the LK engine.
* Handles firing logic.
*/
self.update = function () {
self.fireTimer++;
if (self.fireTimer >= self.fireInterval) {
self.fireTimer = 0; // Reset timer
// Find the closest enemy to shoot at
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Calculate distance to the enemy
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
// If an enemy is found, fire an arrow
if (closestEnemy) {
// Calculate angle towards the closest enemy
var angle = Math.atan2(closestEnemy.x - self.x, -(closestEnemy.y - self.y));
// Create a new arrow instance
var newArrow = new Arrow(angle);
newArrow.x = self.x;
newArrow.y = self.y;
// Ally arrows do not count towards the player's reload counter
// The Arrow class handles piercing level based on player upgrade, but the ally doesn't benefit from player reload.
// For simplicity, we'll let the ally benefit from player's piercing upgrade.
newArrow.lastY = newArrow.y;
newArrow.lastX = newArrow.x;
// Add the arrow to the game scene and the tracking array.
game.addChild(newArrow);
arrows.push(newArrow); // Add to the same arrows array for collision detection
// Ally doesn't play the 'shoot' sound
}
}
};
return self; // Return self for potential inheritance
});
// Sound when an enemy reaches the bastion
// No plugins needed for this version of the game.
/**
* Represents an Arrow fired by the player.
* @param {number} angle - The angle in radians at which the arrow is fired.
*/
var Arrow = Container.expand(function (angle) {
var self = Container.call(this);
// Create and attach the arrow graphic asset
var graphics = self.attachAsset('arrow', {
anchorX: 0.5,
anchorY: 0.5
});
graphics.rotation = angle + Math.PI / 2; // Align arrow graphic with direction
self.speed = 30; // Speed of the arrow in pixels per tick
self.vx = Math.sin(angle) * self.speed; // Horizontal velocity component
self.vy = -Math.cos(angle) * self.speed; // Vertical velocity component (negative Y is up)
self.pierceLeft = arrowPierceLevel; // How many more enemies this arrow can pierce
self.damage = arrowDamage; // Damage dealt by this arrow
/**
* Update method called each game tick by the LK engine.
* Moves the arrow based on its velocity.
*/
self.update = function () {
self.x += self.vx;
self.y += self.vy;
};
return self; // Return self for potential inheritance
});
/**
* Represents an Enemy attacker moving towards the bastion.
* @param {string} type - The type of enemy ('swordsman', 'knight', 'thief', 'boss', 'shield', 'wizard', 'elite_knight').
* @param {number} speed - The final calculated speed of the enemy.
* @param {number} health - The initial and maximum health of the enemy.
* @param {number} dodgeChance - The chance (0 to 1) for the enemy to dodge an attack.
*/
var Enemy = Container.expand(function (type, speed, health, dodgeChance) {
var self = Container.call(this);
// Set asset based on type - Assuming assets 'shield_enemy' and 'wizard_enemy' exist
var assetId = 'enemy'; // Default swordsman asset
if (type === 'knight' || type === 'elite_knight') {
// Elite Knight reuses Knight asset
assetId = 'knight';
} else if (type === 'thief') {
assetId = 'thief';
} else if (type === 'boss') {
assetId = 'boss';
} else if (type === 'shield') {
assetId = 'shield_enemy'; // Placeholder - replace with actual asset ID if needed
} else if (type === 'wizard') {
assetId = 'wizard_enemy'; // Placeholder - replace with actual asset ID if needed
} // Asset setting updated
var graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
self.type = type; // 'swordsman', 'knight', 'thief', 'boss', 'shield', 'wizard', 'elite_knight'
self.speed = speed; // Final calculated speed
self.health = health;
self.maxHealth = health; // Store max health for visual feedback
self.dodgeChance = dodgeChance || 0; // Default to 0 if undefined
// Wizard-specific properties
if (self.type === 'wizard') {
self.teleportTimer = 0;
self.teleportInterval = 180; // 3 seconds * 60 FPS
}
// --- Public Methods (defined before use) ---
/**
* Update method called each game tick by the LK engine.
* Moves the enemy downwards (or teleports for wizard) and updates visual feedback.
*/
self.update = function () {
if (self.type === 'wizard') {
self.teleportTimer = (self.teleportTimer || 0) + 1; // Initialize timer if needed
if (self.teleportTimer >= self.teleportInterval) {
self.teleportTimer = 0;
// Teleport logic: Random X, slightly advanced Y
var oldY = self.y;
var teleportPadding = graphics.width / 2 + 20; // Use actual graphic width
var newX = teleportPadding + Math.random() * (GAME_WIDTH - 2 * teleportPadding);
// Advance Y slightly, but don't teleport past bastion
var newY = Math.min(BASTION_Y - graphics.height, self.y + 100 + Math.random() * 100); // Advance 100-200px
// Ensure not teleporting backwards significantly or offscreen top
newY = Math.max(graphics.height / 2, newY);
self.x = newX;
self.y = newY;
self.lastY = oldY; // Set lastY to pre-teleport position to avoid false bastion triggers
// Add a visual effect for teleport
LK.effects.flashObject(self, 0xAA00FF, 300); // Purple flash
} else {
// Move normally if not teleporting this frame
self.y += self.speed;
}
} else {
// Normal movement for other enemy types
self.y += self.speed;
}
// Visual feedback based on health - alpha fade (applies to all types)
var healthRatio = self.maxHealth > 0 ? self.health / self.maxHealth : 0;
graphics.alpha = 0.4 + healthRatio * 0.6; // Fade from 1.0 down to 0.4
}; //{N} // Adjusted line identifier
/**
* Method called when the enemy is hit by an arrow.
* Handles dodging and health reduction.
* @param {number} damage - The amount of damage the arrow deals.
* @returns {boolean} - True if the enemy is defeated, false otherwise.
*/
self.takeDamage = function (damage) {
// Check for dodge first (only if dodgeChance is positive)
if (self.dodgeChance > 0 && Math.random() < self.dodgeChance) {
// Optional: Add a visual effect for dodge here later
// console.log(self.type + " dodged!");
return false; // Dodged, so not defeated by this hit
}
self.health -= damage;
// No need to update alpha here, self.update handles it
return self.health <= 0; // Return true if health is 0 or less
}; //{O} // Adjusted line identifier
// --- Initialization ---
// Apply visual distinctions based on type
graphics.tint = 0xFFFFFF; // Reset tint
graphics.scale.set(1.0); // Reset scale
if (self.type === 'knight') {
graphics.tint = 0xCCCCCC; // Grey tint for Knights
graphics.scale.set(1.1);
} else if (self.type === 'elite_knight') {
graphics.tint = 0xFFD700; // Gold tint for Elite Knights
graphics.scale.set(1.2); // Slightly larger than normal knight
} else if (self.type === 'thief') {
graphics.tint = 0xCCFFCC; // Pale Green tint for Thieves
graphics.scale.set(0.9);
} else if (self.type === 'boss') {
graphics.tint = 0xFFCCCC; // Pale Red tint for Bosses
graphics.scale.set(1.4); // Make bosses quite large
} else if (self.type === 'shield') {
graphics.tint = 0xADD8E6; // Light Blue tint for Shield
graphics.scale.set(1.2); // Make shield enemies bulky
} else if (self.type === 'wizard') {
graphics.tint = 0xE0B0FF; // Light Purple tint for Wizard
graphics.scale.set(1.0);
} //{11} // Modified original line identifier location
return self; // Return self for potential inheritance
});
/**
* Represents a Swordsman ally that attacks enemies in melee range.
*/
var Swordsman = Container.expand(function () {
var self = Container.call(this);
// Create and attach the swordsman graphic asset (reusing enemy asset for simplicity)
var graphics = self.attachAsset('swordsmanAlly', {
anchorX: 0.5,
anchorY: 0.5
});
// No tint needed as ally asset has unique colors
graphics.scale.set(1.0); // Use the asset's intended scale
self.attackRange = 150; // Melee attack range
self.attackDamage = 1; // Damage per hit
self.attackInterval = 60; // Attack every 1 second (60 ticks)
self.attackTimer = 0; // Timer for attacks
self.lifetime = 10 * 60; // Lifetime in ticks (10 seconds * 60 ticks/sec)
self.lifetimeTimer = 0; // Timer for lifetime
/**
* Update method called each game tick by the LK engine.
* Handles attacking and lifetime.
*/
self.update = function () {
self.attackTimer++;
self.lifetimeTimer++;
// Check for lifetime
if (self.lifetimeTimer >= self.lifetime) {
self.destroy();
// Remove from the swordsmen array in the main game loop
return; // Stop updating this instance
}
// Find the closest enemy to chase or attack
var closestEnemy = null;
var closestDistance = Infinity;
for (var i = 0; i < enemies.length; i++) {
var enemy = enemies[i];
// Calculate distance to the enemy
var dx = enemy.x - self.x;
var dy = enemy.y - self.y;
var distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestEnemy = enemy;
}
}
// If an enemy exists, chase it or attack if within range
if (closestEnemy) {
// Movement speed toward enemies
var moveSpeed = 5;
// If not within attack range, move toward the enemy
if (closestDistance > self.attackRange) {
// Calculate direction vector to enemy
var dirX = closestEnemy.x - self.x;
var dirY = closestEnemy.y - self.y;
// Normalize the direction vector
var length = Math.sqrt(dirX * dirX + dirY * dirY);
dirX = dirX / length;
dirY = dirY / length;
// Move toward the enemy
self.x += dirX * moveSpeed;
self.y += dirY * moveSpeed;
}
// If within attack range and attack timer is ready, attack
else if (self.attackTimer >= self.attackInterval) {
self.attackTimer = 0; // Reset timer
// Apply damage to the enemy and check if it was defeated
var enemyDefeated = closestEnemy.takeDamage(self.attackDamage);
if (enemyDefeated) {
LK.setScore(LK.getScore() + 1); // Increment score.
scoreTxt.setText(LK.getScore()); // Update score display.
// Destroy the enemy
closestEnemy.destroy();
// Remove from the enemies array in the main game loop
}
// Optional: Add a visual/sound effect for attack here later
}
}
};
return self; // Return self for potential inheritance
});
/****
* Initialize Game
****/
// Create the main game instance with a dark background color.
var game = new LK.Game({
backgroundColor: 0x101030 // Dark blue/purple background
});
/****
* Game Code
****/
// Constants defining game dimensions and key vertical positions.
// Define shapes and sounds needed for the game.
// LK engine will automatically initialize these based on usage.
// Yellow arrow shape
// Red enemy shape
// Grey line representing the bastion defense line
// Sound for firing an arrow
// Sound for an arrow hitting an enemy
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BASTION_Y = GAME_HEIGHT - 250; // Y-coordinate representing the defense line. Enemies crossing this trigger game over.
var FIRING_POS_Y = GAME_HEIGHT - 150; // Y-coordinate from where arrows are fired.
var FIRING_POS_X = GAME_WIDTH / 2; // X-coordinate from where arrows are fired (center).
// Arrays to keep track of active arrows and enemies.
var arrows = [];
var enemies = [];
// Variables for game state and difficulty scaling.
var scoreTxt; // Text2 object for displaying the score.
var dragStartX = null; // Starting X coordinate of a touch/mouse drag.
var dragStartY = null; // Starting Y coordinate of a touch/mouse drag.
var enemySpawnInterval = 120; // Initial ticks between enemy spawns (2 seconds at 60 FPS).
var arrowsFired = 0; // Counter for arrows fired since the last cooldown.
var cooldownTimer = 0; // Timer in ticks for the cooldown period.
var cooldownDuration = 6 * 60; // Cooldown duration in ticks (6 seconds * 60 ticks/sec). Min duration enforced in upgrade.
var maxArrowsBeforeCooldown = 15; // Max arrows before reload (Quiver upgrade)
var arrowPierceLevel = 1; // How many enemies an arrow can pierce (Piercing Shots upgrade)
var arrowDamage = 1; // Base damage for arrows (Heat-tipped arrows upgrade)
var enemySpeedMultiplier = 1.0; // Multiplier for enemy speed (Sabotage upgrade) Min multiplier enforced in upgrade.
var minEnemySpawnInterval = 30; // Minimum ticks between enemy spawns (0.5 seconds).
var enemySpawnRateDecrease = 0.08; // Amount to decrease spawn interval each time an enemy spawns.
var currentEnemySpeed = 3; // Initial base speed of enemies.
var maxEnemySpeed = 12; // Maximum base speed enemies can reach.
var enemySpeedIncrease = 0.015; // Amount to increase enemy base speed each time an enemy spawns.
var archerAllies = []; // Array to hold ArcherAlly instances
var swordsmen = []; // Array to hold Swordsman instances
// --- Upgrade System ---
var lastUpgradeScore = -1; // Score at which the last upgrade was offered
var isUpgradePopupActive = false; // Flag indicating if the upgrade choice popup is visible
var upgradePopup = null; // Container for the upgrade UI elements
// Helper function to apply Sabotage effect to existing enemies
function applySabotage() {
for (var i = 0; i < enemies.length; i++) {
// We assume Enemy class will use enemySpeedMultiplier when calculating effective speed
// If Enemy.speed stores base speed, we might need an effectiveSpeed method or update speed directly.
// For simplicity, let's assume Enemy.update uses the global multiplier.
// If direct modification is needed: enemies[i].speed = baseSpeed * enemySpeedMultiplier;
}
// Also update the current base speed calculation baseline if necessary, though modifying the multiplier should suffice.
}
// Helper function placeholder for adding Archer Ally
function addArcherAlly() {
// Implementation requires ArcherAlly class definition
console.log("Archer Ally upgrade chosen!");
var ally = new ArcherAlly();
// Position the ally next to the player's firing position, slightly offset.
ally.x = FIRING_POS_X + 150; // Position to the right of the player
ally.y = FIRING_POS_Y;
game.addChild(ally);
archerAllies.push(ally); // Add to the new archerAllies array
}
// Helper function placeholder for adding Swordsman
function addSwordsman() {
// Create a swordsman tower at the bastion line
var tower = new Container();
var towerGraphics = tower.attachAsset('swordsmanTower', {
anchorX: 0.5,
anchorY: 0.5
});
tower.x = FIRING_POS_X; // Position in the center horizontally
tower.y = BASTION_Y - 50; // Position just above the bastion line
game.addChild(tower);
// Set up interval to spawn swordsmen from the tower
var spawnInterval = LK.setInterval(function () {
var newSwordsman = new Swordsman();
// Position the swordsman near the tower with a slight random offset
newSwordsman.x = tower.x + (Math.random() * 100 - 50); // Random offset of ±50px
newSwordsman.y = tower.y;
game.addChild(newSwordsman);
swordsmen.push(newSwordsman); // Add to the swordsmen array
}, 3000); // Spawn a swordsman every 3 seconds
// Store the interval reference in the tower to potentially clear it later
tower.spawnInterval = spawnInterval;
}
// Define all possible upgrades
var allUpgrades = [{
id: 'faster_reload',
name: 'Faster Reload',
description: 'Decrease reload time by 20%',
apply: function apply() {
cooldownDuration = Math.max(60, Math.floor(cooldownDuration * 0.8));
}
},
// Min 1 sec cooldown
{
id: 'piercing_shots',
name: 'Piercing Shots',
description: 'Arrows pierce +1 enemy',
apply: function apply() {
arrowPierceLevel++;
}
}, {
id: 'quiver',
name: 'Quiver',
description: '+5 Arrows before reload',
apply: function apply() {
maxArrowsBeforeCooldown += 5;
}
}, {
id: 'heat_tipped',
name: 'Heat-tipped Arrows',
description: 'Arrows deal +1 damage',
apply: function apply() {
arrowDamage += 1;
console.log("Heat-tipped Arrows upgrade chosen - damage increased to " + arrowDamage);
}
}, {
id: 'archer_ally',
name: 'Archer Ally',
description: 'Gain an allied archer',
apply: addArcherAlly
}, {
id: 'sabotage',
name: 'Sabotage',
description: 'Enemies 10% slower',
apply: function apply() {
enemySpeedMultiplier = Math.max(0.5, enemySpeedMultiplier * 0.9);
applySabotage();
}
},
// Max 50% slow
{
id: 'swordsman',
name: 'Swordsman',
description: 'Gain a melee defender',
apply: addSwordsman
}];
// Function to create and show the upgrade selection popup
function showUpgradePopup() {
isUpgradePopupActive = true;
// Simple pause: Game logic checks isUpgradePopupActive flag in game.update
// --- Create Popup UI ---
upgradePopup = new Container();
upgradePopup.x = GAME_WIDTH / 2;
upgradePopup.y = GAME_HEIGHT / 2;
game.addChild(upgradePopup); // Add to game layer for positioning relative to center
// Add a semi-transparent background overlay
var bg = upgradePopup.attachAsset('bastionLine', {
// Reusing an asset for shape
width: 1200,
height: 800,
color: 0x000000,
// Black background
anchorX: 0.5,
anchorY: 0.5,
alpha: 0.8
});
// Add title text
var title = new Text2('Choose an Upgrade!', {
size: 80,
fill: 0xFFFFFF
});
title.anchor.set(0.5, 0.5);
title.y = -300; // Position relative to popup center
upgradePopup.addChild(title);
// --- Select 3 Random Unique Upgrades ---
var availableToOffer = allUpgrades.slice(); // Copy available upgrades
var choices = [];
var numChoices = Math.min(3, availableToOffer.length); // Offer up to 3 choices
for (var i = 0; i < numChoices; i++) {
var randomIndex = Math.floor(Math.random() * availableToOffer.length);
choices.push(availableToOffer[randomIndex]);
availableToOffer.splice(randomIndex, 1); // Remove chosen upgrade to ensure uniqueness
}
// --- Create Buttons for Choices ---
var buttonYStart = -150;
var buttonSpacing = 180;
for (var j = 0; j < choices.length; j++) {
var upgrade = choices[j];
var button = createUpgradeButton(upgrade, buttonYStart + j * buttonSpacing);
upgradePopup.addChild(button);
}
}
// Function to create a single upgrade button
function createUpgradeButton(upgradeData, yPos) {
var buttonContainer = new Container();
buttonContainer.y = yPos;
// Button background
var buttonBg = buttonContainer.attachAsset('bastionLine', {
// Reusing asset for shape
width: 800,
height: 150,
color: 0x555555,
anchorX: 0.5,
anchorY: 0.5
});
// Button text (Name + Description)
var nameText = new Text2(upgradeData.name, {
size: 50,
fill: 0xFFFFFF
});
nameText.anchor.set(0.5, 0.5);
nameText.y = -25;
buttonContainer.addChild(nameText);
var descText = new Text2(upgradeData.description, {
size: 35,
fill: 0xCCCCCC
});
descText.anchor.set(0.5, 0.5);
descText.y = 30;
buttonContainer.addChild(descText);
// Make button interactive
buttonContainer.interactive = true; // Needed for down event
// Event handler for button press
buttonContainer.down = function (x, y, obj) {
upgradeData.apply(); // Apply the selected upgrade's effect
hideUpgradePopup(); // Close the popup
};
return buttonContainer;
}
// Function to hide and destroy the upgrade popup
function hideUpgradePopup() {
if (upgradePopup) {
upgradePopup.destroy();
upgradePopup = null;
}
isUpgradePopupActive = false;
// Resume game logic (handled by checking flag in game.update)
}
// --- Visual Setup ---
var bastionLine = game.addChild(LK.getAsset('bastionLine', {
anchorX: 0.0,
// Anchor at the left edge.
anchorY: 0.5,
// Anchor vertically centered.
x: 0,
// Position at the left edge of the screen.
y: BASTION_Y // Position at the defined bastion Y-coordinate.
}));
// Create and configure the score display text.
scoreTxt = new Text2('0', {
size: 150,
// Font size.
fill: 0xFFFFFF // White color.
});
scoreTxt.anchor.set(0.5, 0); // Anchor at the horizontal center, top edge.
// Add score text to the GUI layer at the top-center position.
LK.gui.top.addChild(scoreTxt);
scoreTxt.y = 30; // Add padding below the top edge. Ensure it's clear of the top-left menu icon area.
// --- Input Event Handlers ---
// Handles touch/mouse press down event on the game area.
game.down = function (x, y, obj) {
// Record the starting position of the drag in game coordinates.
var gamePos = game.toLocal({
x: x,
y: y
});
dragStartX = gamePos.x;
dragStartY = gamePos.y;
// Potential enhancement: Show an aiming indicator originating from FIRING_POS_X, FIRING_POS_Y towards the drag point.
};
// Handles touch/mouse move event while dragging on the game area.
game.move = function (x, y, obj) {
// Only process if a drag is currently active.
if (dragStartX !== null) {
var gamePos = game.toLocal({
x: x,
y: y
});
// Potential enhancement: Update the aiming indicator based on the current drag position (gamePos).
}
};
// Handles touch/mouse release event on the game area.
game.up = function (x, y, obj) {
// Only process if a drag was active.
if (dragStartX !== null && cooldownTimer <= 0) {
// Only allow firing if not on cooldown.
var gamePos = game.toLocal({
x: x,
y: y
});
var dragEndX = gamePos.x;
var dragEndY = gamePos.y;
// Calculate the difference between the firing position and the release point to determine direction.
// A longer drag could potentially mean more power, but we'll keep it simple: direction only.
var dx = dragEndX - FIRING_POS_X;
var dy = dragEndY - FIRING_POS_Y;
// Avoid division by zero or zero vector if start and end points are the same.
if (dx === 0 && dy === 0) {
// Optionally handle this case, e.g., fire straight up or do nothing.
// Let's fire straight up if drag distance is negligible.
dy = -1;
}
// Calculate the angle using atan2. Note the order (dx, dy) and the negation of dy
// because the Y-axis is inverted in screen coordinates (positive Y is down).
var angle = Math.atan2(dx, -dy);
// Create a new arrow instance with the calculated angle.
var newArrow = new Arrow(angle);
newArrow.x = FIRING_POS_X;
newArrow.y = FIRING_POS_Y;
// Initialize last position for state tracking (e.g., off-screen detection)
newArrow.lastY = newArrow.y;
newArrow.lastX = newArrow.x;
// Add the arrow to the game scene and the tracking array.
game.addChild(newArrow);
arrows.push(newArrow);
LK.getSound('shoot').play(); // Play shooting sound.
arrowsFired++; // Increment arrow count.
// Check if cooldown needs to start based on Quiver upgrade
if (arrowsFired >= maxArrowsBeforeCooldown) {
cooldownTimer = cooldownDuration; // Start cooldown (duration affected by Faster Reload upgrade)
arrowsFired = 0; // Reset arrow count.
LK.getSound('Reload').play(); // Play reload sound.
}
// Reset drag state.
dragStartX = null;
dragStartY = null;
// Potential enhancement: Hide the aiming indicator.
}
};
// --- Main Game Update Loop ---
// This function is called automatically by the LK engine on every frame (tick).
game.update = function () {
// --- Upgrade System Check ---
var currentScore = LK.getScore();
// Check if score reached a multiple of 10 and is higher than the last upgrade score
if (!isUpgradePopupActive && currentScore > 0 && currentScore % 10 === 0 && currentScore !== lastUpgradeScore) {
lastUpgradeScore = currentScore; // Mark this score level as having triggered an upgrade offer
showUpgradePopup(); // Show the upgrade selection screen
}
// --- Pause Game Logic if Upgrade Popup is Active ---
if (isUpgradePopupActive) {
// Potential: Update UI animations if any
return; // Skip the rest of the game update loop
}
// --- Resume Game Logic ---
// Decrease cooldown timer if active.
if (cooldownTimer > 0) {
cooldownTimer--;
}
// 1. Update and check Arrows
for (var i = arrows.length - 1; i >= 0; i--) {
var arrow = arrows[i];
// Note: LK engine calls arrow.update() automatically as it's added to the game stage.
// Initialize last position if it hasn't been set yet (first frame).
if (arrow.lastY === undefined) {
arrow.lastY = arrow.y;
}
if (arrow.lastX === undefined) {
arrow.lastX = arrow.x;
}
// Check for collisions between the current arrow and all enemies.
var hitEnemy = false;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
// Use the intersects method for collision detection.
if (arrow.intersects(enemy)) {
// Collision detected!
LK.getSound('hit').play(); // Play hit sound.
// Apply damage to the enemy and check if it was defeated
var enemyDefeated = enemy.takeDamage(arrow.damage);
if (enemyDefeated) {
LK.setScore(LK.getScore() + 1); // Increment score.
scoreTxt.setText(LK.getScore()); // Update score display.
// Destroy the enemy
enemy.destroy();
enemies.splice(j, 1); // Remove enemy from array.
}
// Handle arrow piercing
arrow.pierceLeft--; // Decrease remaining pierces
if (arrow.pierceLeft <= 0) {
// Arrow has no pierces left, destroy it
arrow.destroy();
arrows.splice(i, 1); // Remove arrow from array.
hitEnemy = true; // Mark that this arrow is done
break; // Stop checking this arrow against other enemies as it's destroyed
} else {
// Arrow pierced this enemy and can continue
// We don't set hitEnemy = true here because the arrow continues
// We don't break because it might hit another enemy in the same frame further along its path
}
}
}
// If the arrow hit an enemy, it was destroyed, so skip to the next arrow.
if (hitEnemy) {
continue;
}
// Check if the arrow has gone off-screen (top, left, or right).
// Use transition detection: check if it was on screen last frame and is off screen now.
var wasOnScreen = arrow.lastY > -arrow.height / 2 && arrow.lastX > -arrow.width / 2 && arrow.lastX < GAME_WIDTH + arrow.width / 2;
var isOffScreen = arrow.y < -arrow.height / 2 ||
// Off top edge
arrow.x < -arrow.width / 2 ||
// Off left edge
arrow.x > GAME_WIDTH + arrow.width / 2; // Off right edge
if (wasOnScreen && isOffScreen) {
// Arrow is off-screen, destroy it and remove from the array.
arrow.destroy();
arrows.splice(i, 1);
} else if (!isOffScreen) {
// Update last known position only if the arrow is still potentially on screen
arrow.lastY = arrow.y;
arrow.lastX = arrow.x;
}
}
// 2. Update and check Enemies
for (var k = enemies.length - 1; k >= 0; k--) {
var enemy = enemies[k];
// Note: LK engine calls enemy.update() automatically.
// Initialize last position if not set.
if (enemy.lastY === undefined) {
enemy.lastY = enemy.y;
}
// Check if the enemy has reached or passed the bastion line.
// Use transition detection: was the enemy's bottom edge above the line last frame,
// and is it on or below the line now?
var enemyBottomY = enemy.y + enemy.height / 2;
var enemyLastBottomY = enemy.lastY + enemy.height / 2;
var wasAboveBastion = enemyLastBottomY < BASTION_Y;
var isAtOrBelowBastion = enemyBottomY >= BASTION_Y;
if (wasAboveBastion && isAtOrBelowBastion) {
// Enemy reached the bastion! Game Over.
LK.getSound('gameOverSfx').play(); // Play game over sound effect.
LK.showGameOver(); // Trigger the engine's game over sequence.
// LK.showGameOver handles game state reset, no need to manually clear arrays here.
return; // Exit the update loop immediately as the game is over.
} else {
// Update last known position if game is not over for this enemy.
enemy.lastY = enemy.y;
}
}
// 3. Update and check Swordsmen (allies)
for (var l = swordsmen.length - 1; l >= 0; l--) {
var swordsman = swordsmen[l];
// Swordsman update method handles its own lifetime and attacking
// Check if the swordsman has been destroyed by its lifetime timer
if (!swordsman.parent) {
// If it no longer has a parent, it has been destroyed
swordsmen.splice(l, 1); // Remove swordsman from array
}
}
// 4. Spawn new Enemies periodically
// Use LK.ticks and the spawn interval. Ensure interval doesn't go below minimum.
if (LK.ticks % Math.max(minEnemySpawnInterval, Math.floor(enemySpawnInterval)) === 0) {
var currentScore = LK.getScore();
var baseSpeedForSpawn = Math.min(maxEnemySpeed, currentEnemySpeed); // Base speed adjusted by game difficulty progression
// Determine if this spawn *could* be a boss (e.g., every 7th spawn after score 10)
var potentialBoss = (enemies.length + 1) % 7 === 0 && currentScore >= 10;
var typeToSpawn = 'swordsman'; // Default type
var enemyHealth = 1; // Default health
var enemyDodgeChance = 0; // Default dodge chance
var enemySpeed = baseSpeedForSpawn; // Start with base speed
// Determine base type based on score thresholds and randomness
var possibleTypes = ['swordsman'];
if (currentScore >= 15) {
possibleTypes.push('thief');
} //{3s} // Adjusted original line identifier
if (currentScore >= 23) {
possibleTypes.push('knight');
} //{3t} // Adjusted original line identifier
if (currentScore >= 25) {
possibleTypes.push('wizard');
} // Wizards start appearing at score 25
if (currentScore >= 30) {
possibleTypes.push('shield');
} // Shields start appearing at score 30
if (currentScore >= 100) {
possibleTypes.push('elite_knight');
} // Elite Knights start appearing at score 100
// Randomly select a type from the available pool for this score level
var chosenTypeIndex = Math.floor(Math.random() * possibleTypes.length);
typeToSpawn = possibleTypes[chosenTypeIndex];
// Set stats based on chosen type and apply score-based scaling
if (typeToSpawn === 'knight') {
var baseKnightHP = 5;
var hpIncreaseIntervalKnight = 30; // Health increases every 30 score points after appearing
var hpIncreasesKnight = Math.floor(Math.max(0, currentScore - 23) / hpIncreaseIntervalKnight);
enemyHealth = baseKnightHP + hpIncreasesKnight * 5;
enemySpeed *= 0.9; // Knights are slightly slower than base swordsman speed
} else if (typeToSpawn === 'thief') {
enemyHealth = 1; // Thieves are fragile
enemyDodgeChance = 0.10; // 10% dodge chance
var speedIncreaseIntervalThief = 25; // Speed increases every 25 score points after appearing
var speedIncreasePercentThief = 0.25; // 25% speed increase each time
var speedIncreasesThief = Math.floor(Math.max(0, currentScore - 15) / speedIncreaseIntervalThief);
var thiefSpeedMultiplier = Math.pow(1 + speedIncreasePercentThief, speedIncreasesThief);
enemySpeed *= 1.2 * thiefSpeedMultiplier; // Thieves are faster base + scaling speed
} else if (typeToSpawn === 'shield') {
var baseShieldHP = 20;
var hpIncreaseIntervalShield = 35; // Gains HP every 35 score points after appearing
var hpIncreasesShield = Math.floor(Math.max(0, currentScore - 30) / hpIncreaseIntervalShield);
enemyHealth = baseShieldHP + hpIncreasesShield * 20;
enemySpeed *= 0.5; // Shield enemies are very slow
enemyDodgeChance = 0;
} else if (typeToSpawn === 'wizard') {
var baseWizardHP = 2;
var statIncreaseIntervalWizard = 10; // Gains stats every 10 score points after appearing
var statIncreasesWizard = Math.floor(Math.max(0, currentScore - 25) / statIncreaseIntervalWizard);
enemyHealth = baseWizardHP + statIncreasesWizard * 1; // Gains 1 HP per interval
enemySpeed *= 0.7 * Math.pow(1.1, statIncreasesWizard); // Slow base, gains 10% speed per interval
enemyDodgeChance = 0;
} else if (typeToSpawn === 'elite_knight') {
var baseEliteKnightHP = 45;
var hpIncreaseIntervalElite = 30; // Gains HP every 30 score points after appearing
var hpIncreasesElite = Math.floor(Math.max(0, currentScore - 100) / hpIncreaseIntervalElite);
enemyHealth = baseEliteKnightHP + hpIncreasesElite * 15;
enemySpeed *= 0.85; // Slightly slower than base knight speed
enemyDodgeChance = 0;
} else {
// Swordsman (default)
enemyHealth = 1;
// Speed remains baseSpeedForSpawn initially
}
// Check if this spawn should be overridden to be a Boss
if (potentialBoss) {
typeToSpawn = 'boss'; // Set type to boss
enemyHealth = 5 + Math.floor(currentScore / 8); // Boss health scales significantly with score
enemySpeed = baseSpeedForSpawn * 0.7; // Bosses are slower but much tougher (reset speed based on base)
enemyDodgeChance = 0; // Bosses typically don't dodge
}
// Apply the global Sabotage speed multiplier AFTER type-specific adjustments
enemySpeed *= enemySpeedMultiplier;
// Create the new enemy instance with the calculated stats
var newEnemy = new Enemy(typeToSpawn, enemySpeed, enemyHealth, enemyDodgeChance);
// Position the new enemy at the top, random horizontal position with padding
// Use the actual width of the created enemy's graphic for padding calculation
// Need to access width after creation, use a sensible default or estimate if needed before creation
var tempAsset = LK.getAsset(newEnemy.assetId || 'enemy', {}); // Get asset dimensions (might need refinement if assetId isn't on newEnemy yet)
var spawnPadding = (tempAsset ? tempAsset.width / 2 : 100) + 20; // Use default if asset not found easily
newEnemy.x = spawnPadding + Math.random() * (GAME_WIDTH - 2 * spawnPadding);
newEnemy.y = -(tempAsset ? tempAsset.height / 2 : 100); // Start just above the top edge.
// Initialize last position for state tracking (used for bastion collision check).
newEnemy.lastY = newEnemy.y;
// Add the new enemy to the game scene and the tracking array.
game.addChild(newEnemy);
enemies.push(newEnemy);
// Increase difficulty for the next spawn: decrease spawn interval and increase base speed.
// These affect the *next* potential spawn's base calculations.
enemySpawnInterval -= enemySpawnRateDecrease;
currentEnemySpeed += enemySpeedIncrease;
} //{2s} // Adjusted original line identifier
};
// --- Initial Game Setup ---
// Set the initial score text based on the starting score (which is 0).
scoreTxt.setText(LK.getScore());
;
// Play the background music.
LK.playMusic('Gamemusic'); ===================================================================
--- original.js
+++ change.js
@@ -88,41 +88,75 @@
return self; // Return self for potential inheritance
});
/**
* Represents an Enemy attacker moving towards the bastion.
-* @param {string} type - The type of enemy ('swordsman', 'knight', 'thief', 'boss').
+* @param {string} type - The type of enemy ('swordsman', 'knight', 'thief', 'boss', 'shield', 'wizard', 'elite_knight').
* @param {number} speed - The final calculated speed of the enemy.
* @param {number} health - The initial and maximum health of the enemy.
* @param {number} dodgeChance - The chance (0 to 1) for the enemy to dodge an attack.
*/
var Enemy = Container.expand(function (type, speed, health, dodgeChance) {
var self = Container.call(this);
- // Create and attach the enemy graphic asset
+ // Set asset based on type - Assuming assets 'shield_enemy' and 'wizard_enemy' exist
var assetId = 'enemy'; // Default swordsman asset
- if (type === 'knight') {
+ if (type === 'knight' || type === 'elite_knight') {
+ // Elite Knight reuses Knight asset
assetId = 'knight';
} else if (type === 'thief') {
assetId = 'thief';
} else if (type === 'boss') {
assetId = 'boss';
- }
+ } else if (type === 'shield') {
+ assetId = 'shield_enemy'; // Placeholder - replace with actual asset ID if needed
+ } else if (type === 'wizard') {
+ assetId = 'wizard_enemy'; // Placeholder - replace with actual asset ID if needed
+ } // Asset setting updated
var graphics = self.attachAsset(assetId, {
anchorX: 0.5,
anchorY: 0.5
});
- self.type = type; // 'swordsman', 'knight', 'thief', 'boss'
+ self.type = type; // 'swordsman', 'knight', 'thief', 'boss', 'shield', 'wizard', 'elite_knight'
self.speed = speed; // Final calculated speed
self.health = health;
self.maxHealth = health; // Store max health for visual feedback
self.dodgeChance = dodgeChance || 0; // Default to 0 if undefined
+ // Wizard-specific properties
+ if (self.type === 'wizard') {
+ self.teleportTimer = 0;
+ self.teleportInterval = 180; // 3 seconds * 60 FPS
+ }
// --- Public Methods (defined before use) ---
/**
* Update method called each game tick by the LK engine.
- * Moves the enemy downwards and updates visual feedback.
+ * Moves the enemy downwards (or teleports for wizard) and updates visual feedback.
*/
self.update = function () {
- self.y += self.speed; // Speed is pre-calculated including modifiers
- // Visual feedback based on health - alpha fade
+ if (self.type === 'wizard') {
+ self.teleportTimer = (self.teleportTimer || 0) + 1; // Initialize timer if needed
+ if (self.teleportTimer >= self.teleportInterval) {
+ self.teleportTimer = 0;
+ // Teleport logic: Random X, slightly advanced Y
+ var oldY = self.y;
+ var teleportPadding = graphics.width / 2 + 20; // Use actual graphic width
+ var newX = teleportPadding + Math.random() * (GAME_WIDTH - 2 * teleportPadding);
+ // Advance Y slightly, but don't teleport past bastion
+ var newY = Math.min(BASTION_Y - graphics.height, self.y + 100 + Math.random() * 100); // Advance 100-200px
+ // Ensure not teleporting backwards significantly or offscreen top
+ newY = Math.max(graphics.height / 2, newY);
+ self.x = newX;
+ self.y = newY;
+ self.lastY = oldY; // Set lastY to pre-teleport position to avoid false bastion triggers
+ // Add a visual effect for teleport
+ LK.effects.flashObject(self, 0xAA00FF, 300); // Purple flash
+ } else {
+ // Move normally if not teleporting this frame
+ self.y += self.speed;
+ }
+ } else {
+ // Normal movement for other enemy types
+ self.y += self.speed;
+ }
+ // Visual feedback based on health - alpha fade (applies to all types)
var healthRatio = self.maxHealth > 0 ? self.health / self.maxHealth : 0;
graphics.alpha = 0.4 + healthRatio * 0.6; // Fade from 1.0 down to 0.4
}; //{N} // Adjusted line identifier
/**
@@ -148,15 +182,24 @@
graphics.scale.set(1.0); // Reset scale
if (self.type === 'knight') {
graphics.tint = 0xCCCCCC; // Grey tint for Knights
graphics.scale.set(1.1);
+ } else if (self.type === 'elite_knight') {
+ graphics.tint = 0xFFD700; // Gold tint for Elite Knights
+ graphics.scale.set(1.2); // Slightly larger than normal knight
} else if (self.type === 'thief') {
graphics.tint = 0xCCFFCC; // Pale Green tint for Thieves
graphics.scale.set(0.9);
} else if (self.type === 'boss') {
graphics.tint = 0xFFCCCC; // Pale Red tint for Bosses
graphics.scale.set(1.4); // Make bosses quite large
- }
+ } else if (self.type === 'shield') {
+ graphics.tint = 0xADD8E6; // Light Blue tint for Shield
+ graphics.scale.set(1.2); // Make shield enemies bulky
+ } else if (self.type === 'wizard') {
+ graphics.tint = 0xE0B0FF; // Light Purple tint for Wizard
+ graphics.scale.set(1.0);
+ } //{11} // Modified original line identifier location
return self; // Return self for potential inheritance
});
/**
* Represents a Swordsman ally that attacks enemies in melee range.
@@ -249,16 +292,16 @@
/****
* Game Code
****/
-// Sound for an arrow hitting an enemy
-// Sound for firing an arrow
-// Grey line representing the bastion defense line
-// Red enemy shape
-// Yellow arrow shape
-// LK engine will automatically initialize these based on usage.
-// Define shapes and sounds needed for the game.
// Constants defining game dimensions and key vertical positions.
+// Define shapes and sounds needed for the game.
+// LK engine will automatically initialize these based on usage.
+// Yellow arrow shape
+// Red enemy shape
+// Grey line representing the bastion defense line
+// Sound for firing an arrow
+// Sound for an arrow hitting an enemy
var GAME_WIDTH = 2048;
var GAME_HEIGHT = 2732;
var BASTION_Y = GAME_HEIGHT - 250; // Y-coordinate representing the defense line. Enemies crossing this trigger game over.
var FIRING_POS_Y = GAME_HEIGHT - 150; // Y-coordinate from where arrows are fired.
@@ -703,33 +746,61 @@
var enemySpeed = baseSpeedForSpawn; // Start with base speed
// Determine base type based on score thresholds and randomness
var possibleTypes = ['swordsman'];
if (currentScore >= 15) {
- // Thieves start appearing at score 15
possibleTypes.push('thief');
- }
+ } //{3s} // Adjusted original line identifier
if (currentScore >= 23) {
- // Knights start appearing at score 23
possibleTypes.push('knight');
- }
+ } //{3t} // Adjusted original line identifier
+ if (currentScore >= 25) {
+ possibleTypes.push('wizard');
+ } // Wizards start appearing at score 25
+ if (currentScore >= 30) {
+ possibleTypes.push('shield');
+ } // Shields start appearing at score 30
+ if (currentScore >= 100) {
+ possibleTypes.push('elite_knight');
+ } // Elite Knights start appearing at score 100
// Randomly select a type from the available pool for this score level
var chosenTypeIndex = Math.floor(Math.random() * possibleTypes.length);
typeToSpawn = possibleTypes[chosenTypeIndex];
// Set stats based on chosen type and apply score-based scaling
if (typeToSpawn === 'knight') {
var baseKnightHP = 5;
- var hpIncreaseInterval = 30; // Health increases every 30 score points after appearing
- var hpIncreases = Math.floor(Math.max(0, currentScore - 23) / hpIncreaseInterval);
- enemyHealth = baseKnightHP + hpIncreases * 5;
+ var hpIncreaseIntervalKnight = 30; // Health increases every 30 score points after appearing
+ var hpIncreasesKnight = Math.floor(Math.max(0, currentScore - 23) / hpIncreaseIntervalKnight);
+ enemyHealth = baseKnightHP + hpIncreasesKnight * 5;
enemySpeed *= 0.9; // Knights are slightly slower than base swordsman speed
} else if (typeToSpawn === 'thief') {
enemyHealth = 1; // Thieves are fragile
enemyDodgeChance = 0.10; // 10% dodge chance
- var speedIncreaseInterval = 25; // Speed increases every 25 score points after appearing
- var speedIncreasePercent = 0.25; // 25% speed increase each time
- var speedIncreases = Math.floor(Math.max(0, currentScore - 15) / speedIncreaseInterval);
- var thiefSpeedMultiplier = Math.pow(1 + speedIncreasePercent, speedIncreases);
+ var speedIncreaseIntervalThief = 25; // Speed increases every 25 score points after appearing
+ var speedIncreasePercentThief = 0.25; // 25% speed increase each time
+ var speedIncreasesThief = Math.floor(Math.max(0, currentScore - 15) / speedIncreaseIntervalThief);
+ var thiefSpeedMultiplier = Math.pow(1 + speedIncreasePercentThief, speedIncreasesThief);
enemySpeed *= 1.2 * thiefSpeedMultiplier; // Thieves are faster base + scaling speed
+ } else if (typeToSpawn === 'shield') {
+ var baseShieldHP = 20;
+ var hpIncreaseIntervalShield = 35; // Gains HP every 35 score points after appearing
+ var hpIncreasesShield = Math.floor(Math.max(0, currentScore - 30) / hpIncreaseIntervalShield);
+ enemyHealth = baseShieldHP + hpIncreasesShield * 20;
+ enemySpeed *= 0.5; // Shield enemies are very slow
+ enemyDodgeChance = 0;
+ } else if (typeToSpawn === 'wizard') {
+ var baseWizardHP = 2;
+ var statIncreaseIntervalWizard = 10; // Gains stats every 10 score points after appearing
+ var statIncreasesWizard = Math.floor(Math.max(0, currentScore - 25) / statIncreaseIntervalWizard);
+ enemyHealth = baseWizardHP + statIncreasesWizard * 1; // Gains 1 HP per interval
+ enemySpeed *= 0.7 * Math.pow(1.1, statIncreasesWizard); // Slow base, gains 10% speed per interval
+ enemyDodgeChance = 0;
+ } else if (typeToSpawn === 'elite_knight') {
+ var baseEliteKnightHP = 45;
+ var hpIncreaseIntervalElite = 30; // Gains HP every 30 score points after appearing
+ var hpIncreasesElite = Math.floor(Math.max(0, currentScore - 100) / hpIncreaseIntervalElite);
+ enemyHealth = baseEliteKnightHP + hpIncreasesElite * 15;
+ enemySpeed *= 0.85; // Slightly slower than base knight speed
+ enemyDodgeChance = 0;
} else {
// Swordsman (default)
enemyHealth = 1;
// Speed remains baseSpeedForSpawn initially
@@ -737,20 +808,22 @@
// Check if this spawn should be overridden to be a Boss
if (potentialBoss) {
typeToSpawn = 'boss'; // Set type to boss
enemyHealth = 5 + Math.floor(currentScore / 8); // Boss health scales significantly with score
- enemySpeed *= 0.7; // Bosses are slower but much tougher
+ enemySpeed = baseSpeedForSpawn * 0.7; // Bosses are slower but much tougher (reset speed based on base)
enemyDodgeChance = 0; // Bosses typically don't dodge
}
// Apply the global Sabotage speed multiplier AFTER type-specific adjustments
enemySpeed *= enemySpeedMultiplier;
// Create the new enemy instance with the calculated stats
var newEnemy = new Enemy(typeToSpawn, enemySpeed, enemyHealth, enemyDodgeChance);
// Position the new enemy at the top, random horizontal position with padding
// Use the actual width of the created enemy's graphic for padding calculation
- var spawnPadding = newEnemy.width / 2 + 20;
+ // Need to access width after creation, use a sensible default or estimate if needed before creation
+ var tempAsset = LK.getAsset(newEnemy.assetId || 'enemy', {}); // Get asset dimensions (might need refinement if assetId isn't on newEnemy yet)
+ var spawnPadding = (tempAsset ? tempAsset.width / 2 : 100) + 20; // Use default if asset not found easily
newEnemy.x = spawnPadding + Math.random() * (GAME_WIDTH - 2 * spawnPadding);
- newEnemy.y = -newEnemy.height / 2; // Start just above the top edge.
+ newEnemy.y = -(tempAsset ? tempAsset.height / 2 : 100); // Start just above the top edge.
// Initialize last position for state tracking (used for bastion collision check).
newEnemy.lastY = newEnemy.y;
// Add the new enemy to the game scene and the tracking array.
game.addChild(newEnemy);
@@ -758,9 +831,9 @@
// Increase difficulty for the next spawn: decrease spawn interval and increase base speed.
// These affect the *next* potential spawn's base calculations.
enemySpawnInterval -= enemySpawnRateDecrease;
currentEnemySpeed += enemySpeedIncrease;
- } //{2s} // Adjusted line identifier
+ } //{2s} // Adjusted original line identifier
};
// --- Initial Game Setup ---
// Set the initial score text based on the starting score (which is 0).
scoreTxt.setText(LK.getScore());
Arrow. In-Game asset. 2d. High contrast. No shadows. Topdown
Red stickman with a sword. In-Game asset. 2d. High contrast. No shadows. Topdown
Blue stickman with a bow. In-Game asset. 2d. High contrast. No shadows
Red stickman with an iron helmet, iron sword and iron shield. In-Game asset. 2d. High contrast. No shadows. No eyes
Red stickman with a knife and a thief's bandana. In-Game asset. 2d. High contrast. No shadows. No eyes
Purple stickman with a crown. In-Game asset. 2d. High contrast. No shadows
Blue stickman with a sword. In-Game asset. 2d. High contrast. No shadows
Tower. In-Game asset. 2d. High contrast. No shadows
Red stickman with a big wooden shield full of spikes. In-Game asset. 2d. High contrast. No shadows
Green stickman with a blue wizard hat and a staff; no eyes. In-Game asset. 2d. High contrast. No shadows
Red stickman with a golden knight helmet, gold sword and gold shield and gold boots. In-Game asset. 2d. High contrast. No shadows
Cannon. In-Game asset. 2d. High contrast. No shadows
Cannonball. In-Game asset. 2d. High contrast. No shadows
Yellow stickman with an azur wizard hat and staff on a tower. In-Game asset. 2d. High contrast. No shadows
Magic ice ball. In-Game asset. 2d. High contrast. No shadows
Green stickman with a Spartan helmet, Spartan shield and Spartan spear. In-Game asset. 2d. High contrast. No shadows
Green war elephant. In-Game asset. 2d. High contrast. No shadows
Yellow viking stickman holding an axe and is about to throw it. In-Game asset. 2d. High contrast. No shadows
Hatchet. In-Game asset. 2d. High contrast. No shadows
A red stickman with a big golden shield and golden armor. In-Game asset. 2d. High contrast. No shadows
Gray stickman with it's face covered with a black hood equipped with a bow In-Game asset. 2d. High contrast. No shadows, no eyes
Hot air balloon full of red stickmen. In-Game asset. 2d. High contrast. No shadows
Black war elephant with red eyes. In-Game asset. 2d. High contrast. No shadows
Red stickman that is a jester that is on a blue ball and is juggling. In-Game asset. 2d. High contrast. No shadows
Green stickman with a tribal mask and a stick to shoot darts. In-Game asset. 2d. High contrast. No shadows
White female stickman that is an angel with golden armor and a heavenly sword. In-Game asset. 2d. High contrast. No shadows
Wooden dart In-Game asset. 2d. High contrast. No shadows. Topdown
Orb of light. In-Game asset. 2d. High contrast. No shadows
Purple dragon with a red stickman riding it. In-Game asset. 2d. High contrast. No shadows
Add a Roman shield
Bomb. In-Game asset. 2d. High contrast. No shadows
Blue stickman with safety goggles, yellow safety helmet and a bomb. In-Game asset. 2d. High contrast. No shadows
Giant crossbow. In-Game asset. 2d. High contrast. No shadows. Topdown
Green stickman with a British soldier's hat and with a red flag with 2 swords. In-Game asset. 2d. High contrast. No shadows
Baby dragon from clash of clans. In-Game asset. 2d. High contrast. No shadows
Missile launcher BTD6. In-Game asset. 2d. High contrast. No shadows
Red Missile. In-Game asset. 2d. High contrast. No shadows