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)
User prompt
When playing, the song that plays now is "Game music"
User prompt
Now, when realoading, we do the "Reload" sound effect
User prompt
Add a 6 second cooldown after firing 15 arrows
Code edit (1 edits merged)
Please save this source code
User prompt
Archery Bastions
Initial prompt
Archery bastions
/**** * Classes ****/ /** * Represents an Archer Ally that automatically shoots arrows at enemies. */ var ArcherAlly = Container.expand(function () { var self = Container.call(this); // Define methods first /** * Update method called each game tick by the LK engine. * Handles shooting logic for the ally. */ self.update = function () { // Decrement cooldown timer if (self.shootCooldown > 0) { self.shootCooldown--; return; // Exit if still on cooldown } // Cooldown finished, check if there are enemies to shoot at if (enemies.length > 0) { // Target the first enemy in the array (simplest approach) var targetEnemy = enemies[0]; // Calculate angle to the target enemy var dx = targetEnemy.x - self.x; var dy = targetEnemy.y - self.y; // Target is likely below the ally // Avoid shooting straight down if target is directly below if (dx === 0 && dy === 0) { dy = 1; // Adjust slightly if needed, though unlikely } var angle = Math.atan2(dx, -dy); // Y is inverted // Create a new arrow instance var newArrow = new Arrow(angle); newArrow.x = self.x; // Fire from ally's position newArrow.y = self.y; // Initialize last position for state tracking newArrow.lastY = newArrow.y; newArrow.lastX = newArrow.x; // Arrow uses the global pierceLevel automatically // Add the arrow to the game scene and the tracking array. game.addChild(newArrow); arrows.push(newArrow); LK.getSound('shoot').play(); // Play shooting sound. // Reset cooldown self.shootCooldown = self.shootInterval; } }; // Attach asset var graphics = self.attachAsset('archer_ally', { anchorX: 0.5, anchorY: 0.5 }); // Properties self.shootInterval = 4 * 60; // 4 seconds * 60 ticks/sec self.shootCooldown = Math.floor(Math.random() * self.shootInterval); // Start with random initial cooldown return self; // Return self for potential inheritance }); // Placeholder asset ID // 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 /** * 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 {number} speed - The vertical speed of the enemy in pixels per tick. */ var Enemy = Container.expand(function (speed) { var self = Container.call(this); // Create and attach the enemy graphic asset var graphics = self.attachAsset('enemy', { anchorX: 0.5, anchorY: 0.5 }); self.speed = speed; // Vertical speed (pixels per tick) /** * Update method called each game tick by the LK engine. * Moves the enemy downwards. */ self.update = function () { // Apply the global speed multiplier for Sabotage upgrade var effectiveSpeed = self.speed * enemySpeedMultiplier; self.y += effectiveSpeed; }; 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 ****/ // 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. 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 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 to add an Archer Ally function addArcherAlly() { console.log("Archer Ally upgrade chosen - Adding ally."); var ally = new ArcherAlly(); // Position the ally slightly to the left of the main firing position ally.x = FIRING_POS_X - 150; // Adjust position as needed ally.y = FIRING_POS_Y; game.addChild(ally); // Add ally to the game stage so its update runs archerAllies.push(ally); // Add to tracking array } // Helper function placeholder for adding Swordsman function addSwordsman() { // Implementation requires Swordsman class definition console.log("Swordsman upgrade chosen - placeholder"); // Example: var swordsman = new Swordsman(); game.addChild(swordsman); swordsmen.push(swordsman); } // 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: 'Increases damage' }, // Skipping for now due to lack of enemy health { 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.setScore(LK.getScore() + 1); // Increment score. scoreTxt.setText(LK.getScore()); // Update score display. LK.getSound('hit').play(); // Play hit sound. // Collision detected! LK.setScore(LK.getScore() + 1); // Increment score. scoreTxt.setText(LK.getScore()); // Update score display. LK.getSound('hit').play(); // Play hit sound. // 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. 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) { // Determine the base speed for the new enemy, capping at max base speed. var baseSpeed = Math.min(maxEnemySpeed, currentEnemySpeed); // The Enemy class constructor expects the base speed. The multiplier is applied in Enemy.update. var newEnemy = new Enemy(baseSpeed); // Position the new enemy at the top of the screen at a random horizontal position. // Add padding to avoid spawning exactly at the screen edges. var spawnPadding = newEnemy.width / 2 + 20; // Add a little extra padding newEnemy.x = spawnPadding + Math.random() * (GAME_WIDTH - 2 * spawnPadding); newEnemy.y = -newEnemy.height / 2; // Start just above the top edge. // Initialize last position for state tracking. 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 speed. enemySpawnInterval -= enemySpawnRateDecrease; currentEnemySpeed += enemySpeedIncrease; } }; // --- 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
@@ -1,65 +1,62 @@
/****
-* Plugins
-****/
-var tween = LK.import("@upit/tween.v1");
-
-/****
* Classes
****/
/**
-* Represents an archer ally that automatically fires arrows at enemies.
+* Represents an Archer Ally that automatically shoots arrows at enemies.
*/
var ArcherAlly = Container.expand(function () {
var self = Container.call(this);
- // Create and attach the archer graphic
- var graphics = self.attachAsset('enemy', {
- anchorX: 0.5,
- anchorY: 0.5,
- tint: 0x00FF00 // Green tint to distinguish from enemies
- });
- self.fireRate = 90; // Ticks between shots (1.5 seconds)
- self.lastFired = 0;
+ // Define methods first
+ /**
+ * Update method called each game tick by the LK engine.
+ * Handles shooting logic for the ally.
+ */
self.update = function () {
- self.lastFired++;
- if (self.lastFired >= self.fireRate && enemies.length > 0) {
- self.lastFired = 0;
- self.fireArrow();
+ // Decrement cooldown timer
+ if (self.shootCooldown > 0) {
+ self.shootCooldown--;
+ return; // Exit if still on cooldown
}
- };
- self.fireArrow = function () {
- // Find the closest enemy to target
- var target = null;
- var minDist = Number.MAX_VALUE;
- 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 dist = Math.sqrt(dx * dx + dy * dy);
- if (dist < minDist) {
- minDist = dist;
- target = enemy;
+ // Cooldown finished, check if there are enemies to shoot at
+ if (enemies.length > 0) {
+ // Target the first enemy in the array (simplest approach)
+ var targetEnemy = enemies[0];
+ // Calculate angle to the target enemy
+ var dx = targetEnemy.x - self.x;
+ var dy = targetEnemy.y - self.y; // Target is likely below the ally
+ // Avoid shooting straight down if target is directly below
+ if (dx === 0 && dy === 0) {
+ dy = 1; // Adjust slightly if needed, though unlikely
}
+ var angle = Math.atan2(dx, -dy); // Y is inverted
+ // Create a new arrow instance
+ var newArrow = new Arrow(angle);
+ newArrow.x = self.x; // Fire from ally's position
+ newArrow.y = self.y;
+ // Initialize last position for state tracking
+ newArrow.lastY = newArrow.y;
+ newArrow.lastX = newArrow.x;
+ // Arrow uses the global pierceLevel automatically
+ // Add the arrow to the game scene and the tracking array.
+ game.addChild(newArrow);
+ arrows.push(newArrow);
+ LK.getSound('shoot').play(); // Play shooting sound.
+ // Reset cooldown
+ self.shootCooldown = self.shootInterval;
}
- if (target) {
- // Calculate angle to target
- var dx = target.x - self.x;
- var dy = target.y - self.y;
- var angle = Math.atan2(dx, -dy);
- // Create and fire arrow
- var arrow = new Arrow(angle);
- arrow.x = self.x;
- arrow.y = self.y;
- arrow.lastX = arrow.x;
- arrow.lastY = arrow.y;
- arrow.allyArrow = true; // Mark as ally arrow
- game.addChild(arrow);
- arrows.push(arrow);
- LK.getSound('shoot').play();
- }
};
- return self;
+ // Attach asset
+ var graphics = self.attachAsset('archer_ally', {
+ anchorX: 0.5,
+ anchorY: 0.5
+ });
+ // Properties
+ self.shootInterval = 4 * 60; // 4 seconds * 60 ticks/sec
+ self.shootCooldown = Math.floor(Math.random() * self.shootInterval); // Start with random initial cooldown
+ return self; // Return self for potential inheritance
});
+// Placeholder asset ID
// Sound when an enemy reaches the bastion
// No plugins needed for this version of the game.
/**
* Represents an Arrow fired by the player.
@@ -75,10 +72,9 @@
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.piercingCount = 0; // Number of enemies this arrow can pierce through
- self.damage = 1; // Base damage of arrow
+ self.pierceLeft = arrowPierceLevel; // How many more enemies this arrow can pierce
/**
* Update method called each game tick by the LK engine.
* Moves the arrow based on its velocity.
*/
@@ -99,195 +95,235 @@
anchorX: 0.5,
anchorY: 0.5
});
self.speed = speed; // Vertical speed (pixels per tick)
- self.maxHealth = 1;
- self.health = 1;
/**
* Update method called each game tick by the LK engine.
* Moves the enemy downwards.
*/
self.update = function () {
- self.y += self.speed;
+ // Apply the global speed multiplier for Sabotage upgrade
+ var effectiveSpeed = self.speed * enemySpeedMultiplier;
+ self.y += effectiveSpeed;
};
- // Method to apply damage to the enemy
- self.takeDamage = function (amount) {
- self.health -= amount;
- // Flash red to indicate damage
- tween(graphics, {
- alpha: 0.5
- }, {
- duration: 100,
- onFinish: function onFinish() {
- tween(graphics, {
- alpha: 1
- }, {
- duration: 100
- });
- }
- });
- return self.health <= 0; // Return true if enemy died
- };
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
- var graphics = self.attachAsset('enemy', {
- anchorX: 0.5,
- anchorY: 0.5,
- tint: 0x0000FF // Blue tint to distinguish from enemies
- });
- self.attackRange = 200; // Range at which swordsman can attack enemies
- self.attackRate = 60; // Ticks between attacks (1 second)
- self.lastAttacked = 0;
- self.damage = 2; // Damage per attack
- self.update = function () {
- self.lastAttacked++;
- if (self.lastAttacked >= self.attackRate) {
- // Find any enemy in range to attack
- 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 dist = Math.sqrt(dx * dx + dy * dy);
- if (dist <= self.attackRange) {
- self.lastAttacked = 0;
- // Attack animation (slight scale up and down)
- tween(self, {
- scaleX: 1.2,
- scaleY: 1.2
- }, {
- duration: 150,
- onFinish: function onFinish() {
- tween(self, {
- scaleX: 1,
- scaleY: 1
- }, {
- duration: 150
- });
- }
- });
- // Deal damage to enemy
- if (enemy.takeDamage(self.damage)) {
- LK.setScore(LK.getScore() + 1);
- scoreTxt.setText(LK.getScore());
- LK.getSound('hit').play();
- enemy.destroy();
- enemies.splice(i, 1);
- }
- break;
- }
- }
- }
- };
- return self;
-});
-// Upgrade class to handle available upgrades
-var Upgrade = Container.expand(function (type, description, x, y) {
- var self = Container.call(this);
- self.type = type;
- // Background for the upgrade button
- var background = self.attachAsset('bastionLine', {
- anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 0.3,
- scaleY: 5,
- tint: 0x444444
- });
- // Text showing the upgrade type
- var titleText = new Text2(type, {
- size: 40,
- fill: 0xFFFFFF
- });
- titleText.anchor.set(0.5, 0);
- titleText.y = -90;
- self.addChild(titleText);
- // Text showing the upgrade description
- var descText = new Text2(description, {
- size: 30,
- fill: 0xFFFFFF
- });
- descText.anchor.set(0.5, 0);
- descText.y = -40;
- self.addChild(descText);
- // Position the upgrade button
- self.x = x;
- self.y = y;
- // Handle touch events
- self.down = function (x, y, obj) {
- background.tint = 0x888888;
- };
- self.up = function (x, y, obj) {
- background.tint = 0x444444;
- applyUpgrade(self.type);
- hideUpgradeMenu();
- };
- return self;
-});
/****
* Initialize Game
****/
-// --- Visual Setup ---
-// Create a visual line representing the bastion defense zone.
// 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.
// 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.
-// Import tween plugin for animations
+// Constants defining game dimensions and key vertical positions.
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 = [];
-var allies = []; // Array for archer and swordsman allies
// 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).
+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 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 speed of enemies.
-var maxEnemySpeed = 12; // Maximum speed enemies can reach.
-var enemySpeedIncrease = 0.015; // Amount to increase enemy speed each time an enemy spawns.
-// Upgrade system variables
-var lastUpgradeScore = 0;
-var scorePerUpgrade = 10;
-var maxUpgradesPerCategory = 3;
-var upgradablePiercing = 0;
-var upgradableReload = 0;
-var upgradableQuiver = 0;
-var upgradableDamage = 0;
-var upgradableArchers = 0;
-var upgradableSabotage = 0;
-var upgradableSwordsmen = 0;
-var upgradeMenuVisible = false;
-var upgradeOptions = [];
+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 to add an Archer Ally
+function addArcherAlly() {
+ console.log("Archer Ally upgrade chosen - Adding ally.");
+ var ally = new ArcherAlly();
+ // Position the ally slightly to the left of the main firing position
+ ally.x = FIRING_POS_X - 150; // Adjust position as needed
+ ally.y = FIRING_POS_Y;
+ game.addChild(ally); // Add ally to the game stage so its update runs
+ archerAllies.push(ally); // Add to tracking array
+}
+// Helper function placeholder for adding Swordsman
+function addSwordsman() {
+ // Implementation requires Swordsman class definition
+ console.log("Swordsman upgrade chosen - placeholder");
+ // Example: var swordsman = new Swordsman(); game.addChild(swordsman); swordsmen.push(swordsman);
+}
+// 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: 'Increases damage' }, // Skipping for now due to lack of enemy health
+{
+ 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 ---
-// Create a visual line representing the bastion defense zone.
var bastionLine = game.addChild(LK.getAsset('bastionLine', {
anchorX: 0.0,
// Anchor at the left edge.
anchorY: 0.5,
@@ -305,179 +341,8 @@
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.
-// Create overlay container for the upgrade menu
-var upgradeOverlay = new Container();
-upgradeOverlay.visible = false;
-game.addChild(upgradeOverlay);
-// Create semi-transparent background for upgrade menu
-var menuBackground = LK.getAsset('bastionLine', {
- anchorX: 0.5,
- anchorY: 0.5,
- scaleX: 20,
- scaleY: 27,
- tint: 0x000000,
- alpha: 0.7
-});
-menuBackground.x = GAME_WIDTH / 2;
-menuBackground.y = GAME_HEIGHT / 2;
-upgradeOverlay.addChild(menuBackground);
-// Title for upgrade menu
-var upgradeTitle = new Text2("Choose an Upgrade", {
- size: 80,
- fill: 0xFFFFFF
-});
-upgradeTitle.anchor.set(0.5, 0);
-upgradeTitle.x = GAME_WIDTH / 2;
-upgradeTitle.y = 200;
-upgradeOverlay.addChild(upgradeTitle);
-// Function to show upgrade menu
-function showUpgradeMenu() {
- if (upgradeMenuVisible) {
- return;
- }
- upgradeMenuVisible = true;
- upgradeOverlay.visible = true;
- // Pause game functionality by hiding cooldown
- var oldCooldown = cooldownTimer;
- cooldownTimer = cooldownDuration; // Prevent firing while upgrade menu is open
- // Clear previous upgrade options
- for (var i = upgradeOptions.length - 1; i >= 0; i--) {
- upgradeOptions[i].destroy();
- }
- upgradeOptions = [];
- // Available upgrade types
- var availableUpgrades = [];
- // Add upgrades that haven't reached max level
- if (upgradableReload < maxUpgradesPerCategory) {
- availableUpgrades.push({
- type: "Faster Reload",
- description: "Decrease reload time by 15%"
- });
- }
- if (upgradablePiercing < maxUpgradesPerCategory) {
- availableUpgrades.push({
- type: "Piercing Shots",
- description: "Arrows pierce through " + (upgradablePiercing + 1) + " enemies"
- });
- }
- if (upgradableQuiver < maxUpgradesPerCategory) {
- availableUpgrades.push({
- type: "Larger Quiver",
- description: "Shoot " + (5 + upgradableQuiver * 5) + " more arrows before reloading"
- });
- }
- if (upgradableDamage < maxUpgradesPerCategory) {
- availableUpgrades.push({
- type: "Heat-tipped Arrows",
- description: "Arrows deal " + (upgradableDamage + 1) + " extra damage"
- });
- }
- if (upgradableArchers < maxUpgradesPerCategory) {
- availableUpgrades.push({
- type: "Archer Ally",
- description: "Add an archer to help you"
- });
- }
- if (upgradableSabotage < maxUpgradesPerCategory) {
- availableUpgrades.push({
- type: "Sabotage",
- description: "Enemies move 15% slower"
- });
- }
- if (upgradableSwordsmen < maxUpgradesPerCategory) {
- availableUpgrades.push({
- type: "Swordsman",
- description: "Add a swordsman to protect your bastion"
- });
- }
- // Shuffle and take 3 random upgrades
- for (var i = availableUpgrades.length - 1; i > 0; i--) {
- var j = Math.floor(Math.random() * (i + 1));
- var temp = availableUpgrades[i];
- availableUpgrades[i] = availableUpgrades[j];
- availableUpgrades[j] = temp;
- }
- var upgradesToShow = Math.min(3, availableUpgrades.length);
- // No more upgrades available
- if (upgradesToShow === 0) {
- var noUpgradesText = new Text2("All upgrades maxed out!", {
- size: 60,
- fill: 0xFFFFFF
- });
- noUpgradesText.anchor.set(0.5, 0.5);
- noUpgradesText.x = GAME_WIDTH / 2;
- noUpgradesText.y = GAME_HEIGHT / 2;
- upgradeOverlay.addChild(noUpgradesText);
- // Auto-hide after 2 seconds
- LK.setTimeout(function () {
- hideUpgradeMenu();
- cooldownTimer = oldCooldown; // Restore cooldown
- }, 2000);
- return;
- }
- // Create upgrade options
- for (var i = 0; i < upgradesToShow; i++) {
- var xPos = GAME_WIDTH / 4 + GAME_WIDTH / 2 * (i / (upgradesToShow - 1));
- if (upgradesToShow === 1) {
- xPos = GAME_WIDTH / 2;
- }
- var upgrade = new Upgrade(availableUpgrades[i].type, availableUpgrades[i].description, xPos, GAME_HEIGHT / 2);
- upgradeOverlay.addChild(upgrade);
- upgradeOptions.push(upgrade);
- }
-}
-// Function to hide upgrade menu
-function hideUpgradeMenu() {
- upgradeMenuVisible = false;
- upgradeOverlay.visible = false;
-}
-// Function to apply selected upgrade
-function applyUpgrade(type) {
- switch (type) {
- case "Faster Reload":
- cooldownDuration = Math.floor(cooldownDuration * 0.85); // 15% reduction
- upgradableReload++;
- break;
- case "Piercing Shots":
- upgradablePiercing++;
- break;
- case "Larger Quiver":
- // 5, 10, 15 more arrows before reload
- upgradableQuiver++;
- break;
- case "Heat-tipped Arrows":
- upgradableDamage++;
- break;
- case "Archer Ally":
- var archer = new ArcherAlly();
- // Position the archer along the bastion line
- archer.x = 400 + Math.random() * (GAME_WIDTH - 800);
- archer.y = BASTION_Y - 50;
- game.addChild(archer);
- allies.push(archer);
- upgradableArchers++;
- break;
- case "Sabotage":
- // Reduce enemy speed by 15%
- currentEnemySpeed *= 0.85;
- for (var i = 0; i < enemies.length; i++) {
- enemies[i].speed *= 0.85;
- }
- upgradableSabotage++;
- break;
- case "Swordsman":
- var swordsman = new Swordsman();
- // Position the swordsman in front of the bastion
- swordsman.x = 600 + Math.random() * (GAME_WIDTH - 1200);
- swordsman.y = BASTION_Y - 100;
- game.addChild(swordsman);
- allies.push(swordsman);
- upgradableSwordsmen++;
- break;
- }
-}
// --- 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.
@@ -501,10 +366,10 @@
}
};
// Handles touch/mouse release event on the game area.
game.up = function (x, y, obj) {
- // Only process if a drag was active and not in upgrade menu
- if (dragStartX !== null && cooldownTimer <= 0 && !upgradeMenuVisible) {
+ // 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
@@ -530,21 +395,16 @@
newArrow.y = FIRING_POS_Y;
// Initialize last position for state tracking (e.g., off-screen detection)
newArrow.lastY = newArrow.y;
newArrow.lastX = newArrow.x;
- // Apply piercing upgrade
- newArrow.piercingCount = upgradablePiercing;
- // Apply damage upgrade
- newArrow.damage = 1 + upgradableDamage;
// 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.
- // Larger quiver upgrade (15 + additional from upgrade)
- var maxArrows = 15 + upgradableQuiver * 5;
- if (arrowsFired >= maxArrows) {
- cooldownTimer = cooldownDuration; // Start cooldown.
+ // 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.
@@ -555,20 +415,25 @@
};
// --- Main Game Update Loop ---
// This function is called automatically by the LK engine on every frame (tick).
game.update = function () {
- // Don't update game logic if upgrade menu is visible
- if (upgradeMenuVisible) {
- return;
+ // --- 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--;
}
- // Update allies
- for (var i = 0; i < allies.length; i++) {
- allies[i].update();
- }
// 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.
@@ -580,40 +445,40 @@
arrow.lastX = arrow.x;
}
// Check for collisions between the current arrow and all enemies.
var hitEnemy = false;
- var pierceCount = 0;
for (var j = enemies.length - 1; j >= 0; j--) {
var enemy = enemies[j];
// Use the intersects method for collision detection.
if (arrow.intersects(enemy)) {
- // Apply damage and check if enemy died
- if (enemy.takeDamage(arrow.damage)) {
- // Enemy died
- LK.setScore(LK.getScore() + 1); // Increment score.
- scoreTxt.setText(LK.getScore()); // Update score display.
- // Check if we should show upgrade menu (every 10 points)
- var currentScore = LK.getScore();
- if (Math.floor(currentScore / scorePerUpgrade) > Math.floor(lastUpgradeScore / scorePerUpgrade)) {
- lastUpgradeScore = currentScore;
- showUpgradeMenu();
- }
- LK.getSound('hit').play(); // Play hit sound.
- enemy.destroy();
- enemies.splice(j, 1); // Remove enemy from array.
- }
- hitEnemy = true; // Mark that a hit occurred.
- pierceCount++;
- // If the arrow is not a piercing arrow or has reached its piercing limit, destroy it
- if (!arrow.allyArrow && (arrow.piercingCount <= 0 || pierceCount > arrow.piercingCount)) {
+ // Collision detected!
+ LK.setScore(LK.getScore() + 1); // Increment score.
+ scoreTxt.setText(LK.getScore()); // Update score display.
+ LK.getSound('hit').play(); // Play hit sound.
+ // Collision detected!
+ LK.setScore(LK.getScore() + 1); // Increment score.
+ scoreTxt.setText(LK.getScore()); // Update score display.
+ LK.getSound('hit').play(); // Play hit sound.
+ // 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.
- break; // Stop checking more enemies for this arrow
+ 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 && !arrow.allyArrow && (arrow.piercingCount <= 0 || pierceCount > arrow.piercingCount)) {
+ 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.
@@ -661,15 +526,12 @@
}
// 3. 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) {
- // Determine the speed for the new enemy, capping at max speed.
- var newEnemySpeed = Math.min(maxEnemySpeed, currentEnemySpeed);
- var newEnemy = new Enemy(newEnemySpeed);
- // Scale enemy health based on score/progress
- var scoreBasedHealth = 1 + Math.floor(LK.getScore() / 30);
- newEnemy.maxHealth = scoreBasedHealth;
- newEnemy.health = scoreBasedHealth;
+ // Determine the base speed for the new enemy, capping at max base speed.
+ var baseSpeed = Math.min(maxEnemySpeed, currentEnemySpeed);
+ // The Enemy class constructor expects the base speed. The multiplier is applied in Enemy.update.
+ var newEnemy = new Enemy(baseSpeed);
// Position the new enemy at the top of the screen at a random horizontal position.
// Add padding to avoid spawning exactly at the screen edges.
var spawnPadding = newEnemy.width / 2 + 20; // Add a little extra padding
newEnemy.x = spawnPadding + Math.random() * (GAME_WIDTH - 2 * spawnPadding);
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