User prompt
1. First, replace the Game.UpgradeData object with this updated version. It adds the add_component_on_impact data for the final Rock Launcher upgrade. // === SECTION: UPGRADE DATA === Game.UpgradeData = { 'ArrowTower': { 'PathA': [ { cost: 60, effects: { AttackComponent: { damage: 5 } } }, // Level 1: +5 Dmg { cost: 110, effects: { AttackComponent: { damage: 10 } } } // Level 2: +10 Dmg ], 'PathB': [ { cost: 75, effects: { AttackComponent: { attack_speed: 0.2 } } }, // Level 1: +0.2 AS { cost: 125, effects: { AttackComponent: { attack_speed: 0.3 } } } // Level 2: +0.3 AS ] }, 'RockLauncher': { 'PathA': [ // Heavy Boulders - More Damage & Range { cost: 150, effects: { AttackComponent: { damage: 15, range: 20 } } }, { cost: 250, effects: { AttackComponent: { damage: 25, range: 30 } } }, // --- UPDATED UPGRADE --- { cost: 500, effects: { AttackComponent: { damage: 50, range: 50 } }, add_component_on_impact: { name: 'FireZoneOnImpactComponent', args: [10, 3, 0.5] } // dmg, duration, tick_rate } ], 'PathB': [ // Lighter Munitions - Faster Attack Speed { cost: 140, effects: { AttackComponent: { damage: 5, attack_speed: 0.2 } } }, { cost: 220, effects: { AttackComponent: { damage: 10, attack_speed: 0.3 } } }, { cost: 450, effects: { AttackComponent: { damage: 15, attack_speed: 0.5 } } } ] } };
User prompt
Following our pattern, let's define these new components first. Please replace the entire Game.Components object with the following updated version. Game.Components = { TransformComponent: function TransformComponent(x, y, rotation) { return { name: 'TransformComponent', x: x || 0, y: y || 0, rotation: rotation || 0 }; }, RenderComponent: function RenderComponent(sprite_id, layer, scale, is_visible) { return { name: 'RenderComponent', sprite_id: sprite_id || '', layer: layer || 0, scale: scale || 1.0, is_visible: is_visible !== undefined ? is_visible : true }; }, HealthComponent: function HealthComponent(current_hp, max_hp) { return { name: 'HealthComponent', current_hp: current_hp || 100, max_hp: max_hp || 100 }; }, AttackComponent: function AttackComponent(damage, range, attack_speed, last_attack_time, projectile_id, target_id, splash_radius) { return { name: 'AttackComponent', damage: damage || 10, range: range || 100, attack_speed: attack_speed || 1.0, last_attack_time: last_attack_time || 0, projectile_id: projectile_id || '', target_id: target_id || -1, splash_radius: splash_radius || 0 // Default to 0 (no splash) }; }, MovementComponent: function MovementComponent(speed, path_index) { return { name: 'MovementComponent', speed: speed || 50, path_index: path_index || 0 }; }, PowerProductionComponent: function PowerProductionComponent(rate) { return { name: 'PowerProductionComponent', production_rate: rate || 0 }; }, PowerConsumptionComponent: function PowerConsumptionComponent(rate) { return { name: 'PowerConsumptionComponent', drain_rate: rate || 0 }; }, TowerComponent: function TowerComponent(cost, sellValue) { return { name: 'TowerComponent', cost: cost || 50, sell_value: sellValue || 25, upgrade_level: 1, upgrade_path_A_level: 0, upgrade_path_B_level: 0 }; }, ProjectileComponent: function ProjectileComponent(target_id, speed, damage) { return { name: 'ProjectileComponent', target_id: target_id || -1, speed: speed || 8, // Let's keep the speed lower damage: damage || 10 }; }, EnemyComponent: function EnemyComponent(goldValue, livesCost) { return { name: 'EnemyComponent', gold_value: goldValue || 5, lives_cost: livesCost || 1 }; }, PowerStorageComponent: function PowerStorageComponent(capacity) { return { name: 'PowerStorageComponent', capacity: capacity || 0 }; }, PoisonOnImpactComponent: function PoisonOnImpactComponent(damage_per_second, duration) { return { name: 'PoisonOnImpactComponent', damage_per_second: damage_per_second || 0, duration: duration || 0 }; }, PoisonDebuffComponent: function PoisonDebuffComponent(damage_per_second, duration_remaining, last_tick_time) { return { name: 'PoisonDebuffComponent', damage_per_second: damage_per_second || 0, duration_remaining: duration_remaining || 0, last_tick_time: last_tick_time || 0 }; }, SlowOnImpactComponent: function SlowOnImpactComponent(slow_percentage, duration) { return { name: 'SlowOnImpactComponent', slow_percentage: slow_percentage || 0, duration: duration || 0 }; }, SlowDebuffComponent: function SlowDebuffComponent(slow_percentage, duration_remaining) { return { name: 'SlowDebuffComponent', slow_percentage: slow_percentage || 0, duration_remaining: duration_remaining || 0 }; }, AuraComponent: function AuraComponent(range, effect_type) { return { name: 'AuraComponent', range: range || 100, effect_type: effect_type || '' }; }, DamageBuffComponent: function DamageBuffComponent(multiplier, source_aura_id) { return { name: 'DamageBuffComponent', multiplier: multiplier || 0, source_aura_id: source_aura_id || -1 }; }, GoldGenerationComponent: function GoldGenerationComponent(gold_per_second) { return { name: 'GoldGenerationComponent', gold_per_second: gold_per_second || 0 }; }, // --- NEW COMPONENTS --- FireZoneOnImpactComponent: function FireZoneOnImpactComponent(damage_per_tick, duration, tick_rate) { return { name: 'FireZoneOnImpactComponent', damage_per_tick: damage_per_tick || 0, duration: duration || 0, tick_rate: tick_rate || 1.0 }; }, FireZoneComponent: function FireZoneComponent(damage_per_tick, duration_remaining, tick_rate, last_tick_time) { return { name: 'FireZoneComponent', damage_per_tick: damage_per_tick || 0, duration_remaining: duration_remaining || 0, tick_rate: tick_rate || 1.0, last_tick_time: last_tick_time || 0 }; } };
User prompt
Please execute the following command: LK.init.shape('fire_zone', {width:80, height:80, color:0xe74c3c, shape:'ellipse'})
User prompt
Please replace the entire game.update function with the following updated version. The only change is to the setText calls for the upgrade buttons. // Set up main game loop var powerUpdateTimer = 0; game.update = function () { if (Game.GameManager.isGameOver) { return; } Game.Systems.WaveSpawnerSystem.update(); Game.Systems.TargetingSystem.update(); Game.Systems.AuraSystem.update(); Game.Systems.CombatSystem.update(); Game.Systems.MovementSystem.update(); Game.Systems.RenderSystem.update(); // --- UI UPDATE LOGIC --- if (Game.GameplayManager.selectedTowerId !== -1) { upgradePanel.visible = true; // --- START OF LOGIC --- var towerId = Game.GameplayManager.selectedTowerId; var renderComp = Game.EntityManager.componentStores['RenderComponent'][towerId]; var towerComp = Game.EntityManager.componentStores['TowerComponent'][towerId]; if (renderComp && towerComp) { // 1. Find the tower's type name var towerType = null; for (var type in Game.TowerData) { if (Game.TowerData[type].sprite_id === renderComp.sprite_id) { towerType = type; break; } } // 2. Update the main tower name text towerNameText.setText(towerType + " (Lvl " + towerComp.upgrade_level + ")"); // 3. Update Path A button text var upgradePathAData = Game.UpgradeData[towerType] ? Game.UpgradeData[towerType]['PathA'] : null; if (upgradePathAData && towerComp.upgrade_path_A_level < upgradePathAData.length) { var upgradeInfoA = upgradePathAData[towerComp.upgrade_path_A_level]; // --- UPDATED TEXT --- upgradeAPathButton.setText("Upgrade Path A ($" + upgradeInfoA.cost + ")"); upgradeAPathButton.visible = true; } else { upgradeAPathButton.visible = false; // Hide if path is maxed } // 4. Update Path B button text var upgradePathBData = Game.UpgradeData[towerType] ? Game.UpgradeData[towerType]['PathB'] : null; if (upgradePathBData && towerComp.upgrade_path_B_level < upgradePathBData.length) { var upgradeInfoB = upgradePathBData[towerComp.upgrade_path_B_level]; // --- UPDATED TEXT --- upgradeBPathButton.setText("Upgrade Path B ($" + upgradeInfoB.cost + ")"); upgradeBPathButton.visible = true; } else { upgradeBPathButton.visible = false; // Hide if path is maxed } } // --- END OF LOGIC --- } else { upgradePanel.visible = false; } // Update HUD every frame goldText.setText('Gold: ' + Game.ResourceManager.gold); livesText.setText('Lives: ' + Game.ResourceManager.lives); // Update timed systems once per second (every 60 frames) powerUpdateTimer++; if (powerUpdateTimer >= 60) { Game.ResourceManager.updatePower(); if (Game.Systems.WaveSpawnerSystem.isSpawning) { Game.Systems.EconomySystem.update(); } powerUpdateTimer = 0; } Game.Systems.CleanupSystem.update(); };
User prompt
Please replace the entire Game.UpgradeData object with the following updated version. // === SECTION: UPGRADE DATA === Game.UpgradeData = { 'ArrowTower': { 'PathA': [ { cost: 60, effects: { AttackComponent: { damage: 5 } } }, // Level 1: +5 Dmg { cost: 110, effects: { AttackComponent: { damage: 10 } } } // Level 2: +10 Dmg ], 'PathB': [ { cost: 75, effects: { AttackComponent: { attack_speed: 0.2 } } }, // Level 1: +0.2 AS { cost: 125, effects: { AttackComponent: { attack_speed: 0.3 } } } // Level 2: +0.3 AS ] }, // --- NEW UPGRADE PATHS --- 'RockLauncher': { 'PathA': [ // Heavy Boulders - More Damage & Range { cost: 150, effects: { AttackComponent: { damage: 15, range: 20 } } }, { cost: 250, effects: { AttackComponent: { damage: 25, range: 30 } } }, { cost: 500, effects: { AttackComponent: { damage: 50, range: 50 } } } ], 'PathB': [ // Lighter Munitions - Faster Attack Speed { cost: 140, effects: { AttackComponent: { damage: 5, attack_speed: 0.2 } } }, { cost: 220, effects: { AttackComponent: { damage: 10, attack_speed: 0.3 } } }, { cost: 450, effects: { AttackComponent: { damage: 15, attack_speed: 0.5 } } } ] } };
User prompt
Please replace the entire game.update function with this final version. It contains the complete logic for dynamically updating the upgrade panel's text content based on the selected tower's data and current upgrade level. // Set up main game loop var powerUpdateTimer = 0; game.update = function () { if (Game.GameManager.isGameOver) { return; } Game.Systems.WaveSpawnerSystem.update(); Game.Systems.TargetingSystem.update(); Game.Systems.AuraSystem.update(); Game.Systems.CombatSystem.update(); Game.Systems.MovementSystem.update(); Game.Systems.RenderSystem.update(); // --- UI UPDATE LOGIC --- if (Game.GameplayManager.selectedTowerId !== -1) { upgradePanel.visible = true; // --- START OF NEW TEXT UPDATE LOGIC --- var towerId = Game.GameplayManager.selectedTowerId; var renderComp = Game.EntityManager.componentStores['RenderComponent'][towerId]; var towerComp = Game.EntityManager.componentStores['TowerComponent'][towerId]; if (renderComp && towerComp) { // 1. Find the tower's type name var towerType = null; for (var type in Game.TowerData) { if (Game.TowerData[type].sprite_id === renderComp.sprite_id) { towerType = type; break; } } // 2. Update the main tower name text towerNameText.setText(towerType + " (Lvl " + towerComp.upgrade_level + ")"); // 3. Update Path A button text var upgradePathAData = Game.UpgradeData[towerType] ? Game.UpgradeData[towerType]['PathA'] : null; if (upgradePathAData && towerComp.upgrade_path_A_level < upgradePathAData.length) { var upgradeInfoA = upgradePathAData[towerComp.upgrade_path_A_level]; upgradeAPathButton.setText("Upgrade A: +DMG ($" + upgradeInfoA.cost + ")"); upgradeAPathButton.visible = true; } else { upgradeAPathButton.visible = false; // Hide if path is maxed } // 4. Update Path B button text var upgradePathBData = Game.UpgradeData[towerType] ? Game.UpgradeData[towerType]['PathB'] : null; if (upgradePathBData && towerComp.upgrade_path_B_level < upgradePathBData.length) { var upgradeInfoB = upgradePathBData[towerComp.upgrade_path_B_level]; upgradeBPathButton.setText("Upgrade B: +SPD ($" + upgradeInfoB.cost + ")"); upgradeBPathButton.visible = true; } else { upgradeBPathButton.visible = false; // Hide if path is maxed } } // --- END OF NEW TEXT UPDATE LOGIC --- } else { upgradePanel.visible = false; } // Update HUD every frame goldText.setText('Gold: ' + Game.ResourceManager.gold); livesText.setText('Lives: ' + Game.ResourceManager.lives); // Update timed systems once per second (every 60 frames) powerUpdateTimer++; if (powerUpdateTimer >= 60) { Game.ResourceManager.updatePower(); if (Game.Systems.WaveSpawnerSystem.isSpawning) { Game.Systems.EconomySystem.update(); } powerUpdateTimer = 0; } Game.Systems.CleanupSystem.update(); };
User prompt
Please replace the entire game.update function with the following updated version. This new code adds a simple if/else block to check Game.GameplayManager.selectedTowerId and toggle the upgradePanel.visible property accordingly. // Set up main game loop var powerUpdateTimer = 0; game.update = function () { if (Game.GameManager.isGameOver) { return; } Game.Systems.WaveSpawnerSystem.update(); Game.Systems.TargetingSystem.update(); Game.Systems.AuraSystem.update(); Game.Systems.CombatSystem.update(); Game.Systems.MovementSystem.update(); Game.Systems.RenderSystem.update(); // --- NEW: UI UPDATE LOGIC --- if (Game.GameplayManager.selectedTowerId !== -1) { // A tower is selected, so make the panel visible. upgradePanel.visible = true; // In the next step, we will add the logic here to update the panel's text. } else { // No tower is selected, so hide the panel. upgradePanel.visible = false; } // --- END NEW --- // Update HUD every frame goldText.setText('Gold: ' + Game.ResourceManager.gold); livesText.setText('Lives: ' + Game.ResourceManager.lives); // Update timed systems once per second (every 60 frames) powerUpdateTimer++; if (powerUpdateTimer >= 60) { Game.ResourceManager.updatePower(); if (Game.Systems.WaveSpawnerSystem.isSpawning) { Game.Systems.EconomySystem.update(); } powerUpdateTimer = 0; } Game.Systems.CleanupSystem.update(); };
User prompt
Please add the following new code block at the very end of your script, after the main game.update function. // === SECTION: UPGRADE & SELL UI === var upgradePanel = new LK.Container(); LK.gui.bottom.addChild(upgradePanel); upgradePanel.x = -300; // Positioned to the right of center upgradePanel.y = -50; upgradePanel.visible = false; // Initially hidden var towerNameText = new Text2('Tower Name', { size: 40, fill: 0xFFFFFF }); towerNameText.anchor.set(0, 0); upgradePanel.addChild(towerNameText); var upgradeAPathButton = new Text2('Upgrade Path A', { size: 35, fill: 0x27AE60 }); upgradeAPathButton.anchor.set(0, 0); upgradeAPathButton.y = 50; upgradePanel.addChild(upgradeAPathButton); var upgradeBPathButton = new Text2('Upgrade Path B', { size: 35, fill: 0x2980B9 }); upgradeBPathButton.anchor.set(0, 0); upgradeBPathButton.y = 100; upgradePanel.addChild(upgradeBPathButton); var sellButton = new Text2('Sell Tower', { size: 35, fill: 0xC0392B }); sellButton.anchor.set(0, 0); sellButton.y = 150; upgradePanel.addChild(sellButton); // Assign click handlers that will call the TowerSystem upgradeAPathButton.down = function() { if (Game.GameplayManager.selectedTowerId !== -1) { Game.Systems.TowerSystem.tryUpgradeTower(Game.GameplayManager.selectedTowerId, 'PathA'); } }; upgradeBPathButton.down = function() { if (Game.GameplayManager.selectedTowerId !== -1) { Game.Systems.TowerSystem.tryUpgradeTower(Game.GameplayManager.selectedTowerId, 'PathB'); } };
User prompt
Please replace the entire drawGrid function with the following updated version. The core change is within the cellSprite.down function. function drawGrid() { // Draw the grid cells for (var x = 0; x < Game.GridManager.grid.length; x++) { for (var y = 0; y < Game.GridManager.grid[x].length; y++) { var cellSprite = game.attachAsset('grid_cell', {}); cellSprite.x = x * 64; cellSprite.y = y * 64; cellSprite.anchor.set(0, 0); // Anchor to top-left cellSprite.gridX = x; // Store grid coordinates on the sprite cellSprite.gridY = y; // --- UPDATED CLICK LOGIC --- cellSprite.down = function () { var gridX = this.gridX; var gridY = this.gridY; var cellState = Game.GridManager.grid[gridX][gridY]; if (cellState === Game.GridManager.CELL_STATES.TOWER) { // Logic to SELECT a tower // Find the entity ID of the tower at this grid location var allTowers = Game.EntityManager.getEntitiesWithComponents(['TowerComponent', 'TransformComponent']); var foundTowerId = -1; for (var i = 0; i < allTowers.length; i++) { var towerId = allTowers[i]; var transform = Game.EntityManager.componentStores['TransformComponent'][towerId]; // Convert pixel coordinates back to grid coordinates to find a match var towerGridX = Math.floor(transform.x / 64); var towerGridY = Math.floor(transform.y / 64); if (towerGridX === gridX && towerGridY === gridY) { foundTowerId = towerId; break; } } Game.GameplayManager.selectedTowerId = foundTowerId; } else if (cellState === Game.GridManager.CELL_STATES.EMPTY) { // Logic to BUILD a tower Game.Systems.TowerBuildSystem.tryBuildAt(gridX, gridY, buildMode); Game.GameplayManager.selectedTowerId = -1; // Deselect after building } else { // Clicked on a path or blocked cell, so DESELECT Game.GameplayManager.selectedTowerId = -1; } }; // --- END OF UPDATED CLICK LOGIC --- } } // Highlight the path cells if (Game.PathManager.currentPath) { for (var i = 0; i < Game.PathManager.currentPath.length; i++) { var pathNode = Game.PathManager.currentPath[i]; var pathSprite = game.attachAsset('path_cell', {}); // Note: PathManager coordinates are already in pixels pathSprite.x = pathNode.x; pathSprite.y = pathNode.y; pathSprite.anchor.set(0, 0); } } }
User prompt
Please replace the entire Game.GameplayManager object with the following updated version. Game.GameplayManager = { selectedTowerId: -1, // NEW: To track the currently selected tower enemiesSpawnedThisWave: 0, enemiesKilledThisWave: 0, enemiesLeakedThisWave: 0, init: function init() { Game.EventBus.subscribe('EnemyKilled', this.onEnemyKilled.bind(this)); Game.EventBus.subscribe('EnemyReachedEnd', this.onEnemyReachedEnd.bind(this)); }, onEnemyKilled: function onEnemyKilled(event) { this.enemiesKilledThisWave++; if (this.enemiesKilledThisWave + this.enemiesLeakedThisWave === this.enemiesSpawnedThisWave) { Game.EventBus.publish(Game.Events.WaveCompletedEvent(Game.Systems.WaveSpawnerSystem.currentWaveIndex + 1)); this.enemiesSpawnedThisWave = 0; this.enemiesKilledThisWave = 0; this.enemiesLeakedThisWave = 0; startWaveButton.visible = true; } }, onEnemyReachedEnd: function onEnemyReachedEnd(event) { this.enemiesLeakedThisWave++; Game.ResourceManager.loseLife(1); Game.EntityManager.destroyEntity(event.enemy_id); if (this.enemiesKilledThisWave + this.enemiesLeakedThisWave === this.enemiesSpawnedThisWave) { Game.EventBus.publish(Game.Events.WaveCompletedEvent(Game.Systems.WaveSpawnerSystem.currentWaveIndex + 1)); this.enemiesSpawnedThisWave = 0; this.enemiesKilledThisWave = 0; this.enemiesLeakedThisWave = 0; startWaveButton.visible = true; } } };
User prompt
This logic is the heart of the new upgrade feature. Please replace the entire Game.Systems object with the following updated version. The only change is the implementation of the tryUpgradeTower function inside the TowerSystem. Game.Systems = { MovementSystem: { update: function update() { var movableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var i = 0; i < movableEntities.length; i++) { var entityId = movableEntities[i]; var transformComponent = Game.EntityManager.componentStores['TransformComponent'][entityId]; var movementComponent = Game.EntityManager.componentStores['MovementComponent'][entityId]; // --- START OF NEW SLOW LOGIC --- // 1. Get base speed and check for slow debuff var currentSpeed = movementComponent.speed; var slowDebuff = Game.EntityManager.componentStores['SlowDebuffComponent'] ? Game.EntityManager.componentStores['SlowDebuffComponent'][entityId] : null; if (slowDebuff) { // 2. Apply the speed reduction currentSpeed = movementComponent.speed * (1 - slowDebuff.slow_percentage); // 3. Update the debuff's duration and remove it if it expires slowDebuff.duration_remaining -= 1.0 / 60; // Reduce duration by frame time (1/60th of a second) if (slowDebuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['SlowDebuffComponent'][entityId]; } } // --- END OF NEW SLOW LOGIC --- if (Game.PathManager.currentPath && Game.PathManager.currentPath.length > 0) { var targetWaypoint = Game.PathManager.currentPath[movementComponent.path_index]; if (targetWaypoint) { var dx = targetWaypoint.x - transformComponent.x; var dy = targetWaypoint.y - transformComponent.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 1) { // 4. Use the calculated currentSpeed for movement var moveX = dx / distance * currentSpeed; var moveY = dy / distance * currentSpeed; transformComponent.x += moveX; transformComponent.y += moveY; } else { movementComponent.path_index++; if (movementComponent.path_index >= Game.PathManager.currentPath.length) { Game.EventBus.publish(Game.Events.EnemyReachedEndEvent(entityId)); } } } } } } }, RenderSystem: { displayObjects: {}, update: function update() { var renderableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'RenderComponent']); for (var i = 0; i < renderableEntities.length; i++) { var entityId = renderableEntities[i]; var transform = Game.EntityManager.componentStores['TransformComponent'][entityId]; var render = Game.EntityManager.componentStores['RenderComponent'][entityId]; // Get the visual sprite, or create it if it doesn't exist var sprite = this.displayObjects[entityId]; if (!sprite) { sprite = game.attachAsset(render.sprite_id, {}); this.displayObjects[entityId] = sprite; } // Sync the sprite's properties with the component data sprite.x = transform.x; sprite.y = transform.y; sprite.visible = render.is_visible; } } }, TowerBuildSystem: { tryBuildAt: function tryBuildAt(gridX, gridY, towerType) { // 0. Look up tower data var towerData = Game.TowerData[towerType]; if (!towerData) { return; // Exit if tower type is invalid } // 1. Check if the location is buildable if (!Game.GridManager.isBuildable(gridX, gridY)) { return; // Exit if not buildable } // 2. Check if the player can afford the tower if (!Game.ResourceManager.spendGold(towerData.cost)) { return; // Exit if not enough gold } // 3. If checks pass, create the tower entity var towerId = Game.EntityManager.createEntity(); // 4. Mark the grid cell as occupied by a tower Game.GridManager.grid[gridX][gridY] = Game.GridManager.CELL_STATES.TOWER; // 5. Add universal components that all towers have var transform = Game.Components.TransformComponent(gridX * 64, gridY * 64); Game.EntityManager.addComponent(towerId, transform); var render = Game.Components.RenderComponent(towerData.sprite_id, 2, 1.0, true); Game.EntityManager.addComponent(towerId, render); // 6. Add specific components from the tower's data definition for (var i = 0; i < towerData.components.length; i++) { var componentDef = towerData.components[i]; // Create the component by calling its factory function with the specified arguments var component = Game.Components[componentDef.name].apply(null, componentDef.args); Game.EntityManager.addComponent(towerId, component); } // 7. Announce that a tower has been placed var event = Game.Events.TowerPlacedEvent(towerId, towerData.cost, transform.x, transform.y); Game.EventBus.publish(event); } }, TargetingSystem: { update: function update() { var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; // Find best target (closest to end of path) var bestTarget = -1; var bestPathIndex = -1; var enemies = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var j = 0; j < enemies.length; j++) { var enemyId = enemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var enemyMovement = Game.EntityManager.componentStores['MovementComponent'][enemyId]; // Check if enemy is within range var dx = enemyTransform.x - towerTransform.x; var dy = enemyTransform.y - towerTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= towerAttack.range) { // Check if this enemy is closer to the end if (enemyMovement.path_index > bestPathIndex) { bestTarget = enemyId; bestPathIndex = enemyMovement.path_index; } } } // Update tower's target towerAttack.target_id = bestTarget; } } }, CleanupSystem: { update: function update() { for (var i = 0; i < Game.EntityManager.entitiesToDestroy.length; i++) { var entityId = Game.EntityManager.entitiesToDestroy[i]; // Remove from active entities list var index = Game.EntityManager.activeEntities.indexOf(entityId); if (index > -1) { Game.EntityManager.activeEntities.splice(index, 1); } // Remove from all component stores for (var componentName in Game.EntityManager.componentStores) { if (Game.EntityManager.componentStores[componentName][entityId]) { delete Game.EntityManager.componentStores[componentName][entityId]; } } // Remove from render system's display objects if (Game.Systems.RenderSystem.displayObjects[entityId]) { Game.Systems.RenderSystem.displayObjects[entityId].destroy(); delete Game.Systems.RenderSystem.displayObjects[entityId]; } } // Important: Clear the list for the next frame if (Game.EntityManager.entitiesToDestroy.length > 0) { Game.EntityManager.entitiesToDestroy = []; } } }, WaveSpawnerSystem: { currentWaveIndex: -1, isSpawning: false, spawnTimer: 0, subWaveIndex: 0, spawnedInSubWave: 0, startNextWave: function startNextWave() { this.currentWaveIndex++; this.isSpawning = true; this.spawnTimer = 0; this.subWaveIndex = 0; this.spawnedInSubWave = 0; }, update: function update() { if (!this.isSpawning || this.currentWaveIndex >= Game.WaveData.length) { return; } var currentWave = Game.WaveData[this.currentWaveIndex]; if (!currentWave || this.subWaveIndex >= currentWave.sub_waves.length) { this.isSpawning = false; return; } var currentSubWave = currentWave.sub_waves[this.subWaveIndex]; // Handle start delay if (this.spawnedInSubWave === 0 && this.spawnTimer < currentSubWave.start_delay) { this.spawnTimer += 1 / 60; // Increment by frame time return; } // Handle spawn delay between enemies if (this.spawnedInSubWave > 0 && this.spawnTimer < currentSubWave.spawn_delay) { this.spawnTimer += 1 / 60; return; } // Spawn enemy if (this.spawnedInSubWave < currentSubWave.count) { var enemyId = Game.EntityManager.createEntity(); var transform = Game.Components.TransformComponent(0 * 64, 30 * 64); var movement = Game.Components.MovementComponent(2, 0); var render = Game.Components.RenderComponent('enemy', 1, 1.0, true); var health = Game.Components.HealthComponent(30, 30); // 30 current, 30 max var enemy = Game.Components.EnemyComponent(5, 1); Game.EntityManager.addComponent(enemyId, transform); Game.EntityManager.addComponent(enemyId, movement); Game.EntityManager.addComponent(enemyId, render); Game.EntityManager.addComponent(enemyId, health); // Add the health component Game.EntityManager.addComponent(enemyId, enemy); this.spawnedInSubWave++; this.spawnTimer = 0; Game.GameplayManager.enemiesSpawnedThisWave++; } else { // Move to next sub-wave this.subWaveIndex++; this.spawnedInSubWave = 0; this.spawnTimer = 0; } } }, CombatSystem: { update: function update() { var currentTime = LK.ticks / 60; // Convert ticks to seconds // --- Tower Firing Logic (UPDATED FOR DAMAGE BUFF) --- var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; if (towerAttack.target_id !== -1 && currentTime - towerAttack.last_attack_time >= 1.0 / towerAttack.attack_speed) { var targetTransform = Game.EntityManager.componentStores['TransformComponent'][towerAttack.target_id]; if (targetTransform) { // --- NEW: Calculate final damage including buffs --- var baseDamage = towerAttack.damage; var finalDamage = baseDamage; var buffComp = Game.EntityManager.componentStores['DamageBuffComponent'] ? Game.EntityManager.componentStores['DamageBuffComponent'][towerId] : null; if (buffComp) { finalDamage = baseDamage * (1 + buffComp.multiplier); } // --- END NEW --- var projectileId = Game.EntityManager.createEntity(); var projectileTransform = Game.Components.TransformComponent(towerTransform.x, towerTransform.y); var projectileRender = Game.Components.RenderComponent(towerAttack.projectile_id, 1, 1.0, true); // Use finalDamage when creating the projectile var projectileComponent = Game.Components.ProjectileComponent(towerAttack.target_id, undefined, finalDamage); projectileComponent.source_tower_id = towerId; Game.EntityManager.addComponent(projectileId, projectileTransform); Game.EntityManager.addComponent(projectileId, projectileRender); Game.EntityManager.addComponent(projectileId, projectileComponent); if (towerAttack.projectile_id === 'chemical_projectile') { var poisonImpactComp = Game.Components.PoisonOnImpactComponent(4, 5); Game.EntityManager.addComponent(projectileId, poisonImpactComp); } if (towerAttack.projectile_id === 'slow_projectile') { var slowImpactComp = Game.Components.SlowOnImpactComponent(0.4, 2); Game.EntityManager.addComponent(projectileId, slowImpactComp); } towerAttack.last_attack_time = currentTime; } } } // --- Projectile Movement and Impact Logic (No changes here) --- var projectiles = Game.EntityManager.getEntitiesWithComponents(['ProjectileComponent', 'TransformComponent']); for (var i = projectiles.length - 1; i >= 0; i--) { var projectileId = projectiles[i]; var projectileComp = Game.EntityManager.componentStores['ProjectileComponent'][projectileId]; var projectileTransform = Game.EntityManager.componentStores['TransformComponent'][projectileId]; var targetExists = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; if (!targetExists) { Game.EntityManager.destroyEntity(projectileId); continue; } var targetTransform = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; var dx = targetTransform.x - projectileTransform.x; var dy = targetTransform.y - projectileTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { var impactPoint = { x: targetTransform.x, y: targetTransform.y }; var sourceTowerAttack = Game.EntityManager.componentStores['AttackComponent'][projectileComp.source_tower_id]; if (sourceTowerAttack && sourceTowerAttack.splash_radius > 0) { var allEnemies = Game.EntityManager.getEntitiesWithComponents(['HealthComponent', 'TransformComponent']); for (var j = 0; j < allEnemies.length; j++) { var enemyId = allEnemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var splash_dx = enemyTransform.x - impactPoint.x; var splash_dy = enemyTransform.y - impactPoint.y; var splash_dist = Math.sqrt(splash_dx * splash_dx + splash_dy * splash_dy); if (splash_dist <= sourceTowerAttack.splash_radius) { this.dealDamage(enemyId, projectileComp.damage); } } } else { this.dealDamage(projectileComp.target_id, projectileComp.damage); } var targetHealth = Game.EntityManager.componentStores['HealthComponent'][projectileComp.target_id]; if (targetHealth) { var poisonImpactComp = Game.EntityManager.componentStores['PoisonOnImpactComponent'] ? Game.EntityManager.componentStores['PoisonOnImpactComponent'][projectileId] : null; if (poisonImpactComp) { var debuff = Game.Components.PoisonDebuffComponent(poisonImpactComp.damage_per_second, poisonImpactComp.duration, currentTime); Game.EntityManager.addComponent(projectileComp.target_id, debuff); } var slowImpactComp = Game.EntityManager.componentStores['SlowOnImpactComponent'] ? Game.EntityManager.componentStores['SlowOnImpactComponent'][projectileId] : null; if (slowImpactComp) { var slowDebuff = Game.Components.SlowDebuffComponent(slowImpactComp.slow_percentage, slowImpactComp.duration); Game.EntityManager.addComponent(projectileComp.target_id, slowDebuff); } } Game.EntityManager.destroyEntity(projectileId); } else { var moveX = dx / distance * projectileComp.speed; var moveY = dy / distance * projectile_comp.speed; projectileTransform.x += moveX; projectileTransform.y += moveY; } } // --- Poison Debuff Processing Logic (No changes here) --- var poisonedEnemies = Game.EntityManager.getEntitiesWithComponents(['PoisonDebuffComponent']); for (var i = poisonedEnemies.length - 1; i >= 0; i--) { var enemyId = poisonedEnemies[i]; var debuff = Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; if (!debuff) { continue; } if (currentTime - debuff.last_tick_time >= 1.0) { this.dealDamage(enemyId, debuff.damage_per_second); debuff.last_tick_time = currentTime; debuff.duration_remaining -= 1.0; } if (debuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; } } }, dealDamage: function dealDamage(enemyId, damage) { var targetHealth = Game.EntityManager.componentStores['HealthComponent'][enemyId]; if (!targetHealth) { return; } targetHealth.current_hp -= damage; if (targetHealth.current_hp <= 0) { var enemyComp = Game.EntityManager.componentStores['EnemyComponent'][enemyId]; var goldValue = enemyComp ? enemyComp.gold_value : 0; Game.EventBus.publish(Game.Events.EnemyKilledEvent(enemyId, goldValue)); Game.EntityManager.destroyEntity(enemyId); } } }, AuraSystem: { update: function update() { // Step 1: Clean up all existing damage buffs from the previous frame. var buffedTowers = Game.EntityManager.getEntitiesWithComponents(['DamageBuffComponent']); for (var i = 0; i < buffedTowers.length; i++) { var towerId = buffedTowers[i]; delete Game.EntityManager.componentStores['DamageBuffComponent'][towerId]; } // Step 2: Find all aura-providing towers. var auraTowers = Game.EntityManager.getEntitiesWithComponents(['AuraComponent', 'TransformComponent']); if (auraTowers.length === 0) { return; // No auras to process. } // Step 3: Get all attack towers that could potentially be buffed. var attackTowers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); // Step 4: Iterate through each aura and apply its effect to nearby towers. for (var i = 0; i < auraTowers.length; i++) { var auraTowerId = auraTowers[i]; var auraComponent = Game.EntityManager.componentStores['AuraComponent'][auraTowerId]; var auraTransform = Game.EntityManager.componentStores['TransformComponent'][auraTowerId]; // Only process damage buff auras for now if (auraComponent.effect_type !== 'damage_buff') { continue; } for (var j = 0; j < attackTowers.length; j++) { var attackTowerId = attackTowers[j]; // An aura tower cannot buff itself. if (attackTowerId === auraTowerId) { continue; } var attackTransform = Game.EntityManager.componentStores['TransformComponent'][attackTowerId]; // Check distance var dx = auraTransform.x - attackTransform.x; var dy = auraTransform.y - attackTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= auraComponent.range) { // Apply the buff var buff = Game.Components.DamageBuffComponent(0.2, auraTowerId); // 20% damage bonus Game.EntityManager.addComponent(attackTowerId, buff); } } } } }, EconomySystem: { update: function update() { // Find all towers that generate gold var goldGenerators = Game.EntityManager.getEntitiesWithComponents(['GoldGenerationComponent']); // Sum up the gold from all of them for (var i = 0; i < goldGenerators.length; i++) { var entityId = goldGenerators[i]; var goldGenComp = Game.EntityManager.componentStores['GoldGenerationComponent'][entityId]; if (goldGenComp) { // Add the gold to the player's resources. // Since this runs once per second, we add the gold_per_second value directly. Game.ResourceManager.addGold(goldGenComp.gold_per_second); } } } }, TowerSystem: { tryUpgradeTower: function tryUpgradeTower(towerId, path) { // 1. Get the tower's type by looking up its sprite_id in TowerData var renderComp = Game.EntityManager.componentStores['RenderComponent'][towerId]; if (!renderComp) { return; } var towerSpriteId = renderComp.sprite_id; var towerType = null; for (var type in Game.TowerData) { if (Game.TowerData[type].sprite_id === towerSpriteId) { towerType = type; break; } } if (!towerType) { return; } // 2. Get the upgrade data for this tower type and path var upgradePathData = Game.UpgradeData[towerType] ? Game.UpgradeData[towerType][path] : null; if (!upgradePathData) { return; } // 3. Get the tower's own TowerComponent var towerComp = Game.EntityManager.componentStores['TowerComponent'][towerId]; if (!towerComp) { return; } // 4. Determine the next upgrade level for the chosen path var nextUpgradeIndex = (path === 'PathA') ? towerComp.upgrade_path_A_level : towerComp.upgrade_path_B_level; // 5. Check if the next upgrade exists in the data if (nextUpgradeIndex >= upgradePathData.length) { return; // This path is maxed out } var upgradeInfo = upgradePathData[nextUpgradeIndex]; // 6. Check for gold and spend it if (!Game.ResourceManager.spendGold(upgradeInfo.cost)) { return; // Not enough gold } // 7. Apply the upgrade effects to the tower's components for (var componentName in upgradeInfo.effects) { var componentToModify = Game.EntityManager.componentStores[componentName] ? Game.EntityManager.componentStores[componentName][towerId] : null; if (componentToModify) { var effectsToApply = upgradeInfo.effects[componentName]; for (var property in effectsToApply) { if (componentToModify.hasOwnProperty(property)) { componentToModify[property] += effectsToApply[property]; } } } } // 8. Update the tower's level trackers towerComp.upgrade_level++; if (path === 'PathA') { towerComp.upgrade_path_A_level++; } else { towerComp.upgrade_path_B_level++; } } } };
User prompt
Please replace the entire Game.Systems object with the following version, which adds the new, empty TowerSystem at the end. Game.Systems = { MovementSystem: { update: function update() { var movableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var i = 0; i < movableEntities.length; i++) { var entityId = movableEntities[i]; var transformComponent = Game.EntityManager.componentStores['TransformComponent'][entityId]; var movementComponent = Game.EntityManager.componentStores['MovementComponent'][entityId]; // --- START OF NEW SLOW LOGIC --- // 1. Get base speed and check for slow debuff var currentSpeed = movementComponent.speed; var slowDebuff = Game.EntityManager.componentStores['SlowDebuffComponent'] ? Game.EntityManager.componentStores['SlowDebuffComponent'][entityId] : null; if (slowDebuff) { // 2. Apply the speed reduction currentSpeed = movementComponent.speed * (1 - slowDebuff.slow_percentage); // 3. Update the debuff's duration and remove it if it expires slowDebuff.duration_remaining -= 1.0 / 60; // Reduce duration by frame time (1/60th of a second) if (slowDebuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['SlowDebuffComponent'][entityId]; } } // --- END OF NEW SLOW LOGIC --- if (Game.PathManager.currentPath && Game.PathManager.currentPath.length > 0) { var targetWaypoint = Game.PathManager.currentPath[movementComponent.path_index]; if (targetWaypoint) { var dx = targetWaypoint.x - transformComponent.x; var dy = targetWaypoint.y - transformComponent.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 1) { // 4. Use the calculated currentSpeed for movement var moveX = dx / distance * currentSpeed; var moveY = dy / distance * currentSpeed; transformComponent.x += moveX; transformComponent.y += moveY; } else { movementComponent.path_index++; if (movementComponent.path_index >= Game.PathManager.currentPath.length) { Game.EventBus.publish(Game.Events.EnemyReachedEndEvent(entityId)); } } } } } } }, RenderSystem: { displayObjects: {}, update: function update() { var renderableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'RenderComponent']); for (var i = 0; i < renderableEntities.length; i++) { var entityId = renderableEntities[i]; var transform = Game.EntityManager.componentStores['TransformComponent'][entityId]; var render = Game.EntityManager.componentStores['RenderComponent'][entityId]; // Get the visual sprite, or create it if it doesn't exist var sprite = this.displayObjects[entityId]; if (!sprite) { sprite = game.attachAsset(render.sprite_id, {}); this.displayObjects[entityId] = sprite; } // Sync the sprite's properties with the component data sprite.x = transform.x; sprite.y = transform.y; sprite.visible = render.is_visible; } } }, TowerBuildSystem: { tryBuildAt: function tryBuildAt(gridX, gridY, towerType) { // 0. Look up tower data var towerData = Game.TowerData[towerType]; if (!towerData) { return; // Exit if tower type is invalid } // 1. Check if the location is buildable if (!Game.GridManager.isBuildable(gridX, gridY)) { return; // Exit if not buildable } // 2. Check if the player can afford the tower if (!Game.ResourceManager.spendGold(towerData.cost)) { return; // Exit if not enough gold } // 3. If checks pass, create the tower entity var towerId = Game.EntityManager.createEntity(); // 4. Mark the grid cell as occupied by a tower Game.GridManager.grid[gridX][gridY] = Game.GridManager.CELL_STATES.TOWER; // 5. Add universal components that all towers have var transform = Game.Components.TransformComponent(gridX * 64, gridY * 64); Game.EntityManager.addComponent(towerId, transform); var render = Game.Components.RenderComponent(towerData.sprite_id, 2, 1.0, true); Game.EntityManager.addComponent(towerId, render); // 6. Add specific components from the tower's data definition for (var i = 0; i < towerData.components.length; i++) { var componentDef = towerData.components[i]; // Create the component by calling its factory function with the specified arguments var component = Game.Components[componentDef.name].apply(null, componentDef.args); Game.EntityManager.addComponent(towerId, component); } // 7. Announce that a tower has been placed var event = Game.Events.TowerPlacedEvent(towerId, towerData.cost, transform.x, transform.y); Game.EventBus.publish(event); } }, TargetingSystem: { update: function update() { var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; // Find best target (closest to end of path) var bestTarget = -1; var bestPathIndex = -1; var enemies = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var j = 0; j < enemies.length; j++) { var enemyId = enemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var enemyMovement = Game.EntityManager.componentStores['MovementComponent'][enemyId]; // Check if enemy is within range var dx = enemyTransform.x - towerTransform.x; var dy = enemyTransform.y - towerTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= towerAttack.range) { // Check if this enemy is closer to the end if (enemyMovement.path_index > bestPathIndex) { bestTarget = enemyId; bestPathIndex = enemyMovement.path_index; } } } // Update tower's target towerAttack.target_id = bestTarget; } } }, CleanupSystem: { update: function update() { for (var i = 0; i < Game.EntityManager.entitiesToDestroy.length; i++) { var entityId = Game.EntityManager.entitiesToDestroy[i]; // Remove from active entities list var index = Game.EntityManager.activeEntities.indexOf(entityId); if (index > -1) { Game.EntityManager.activeEntities.splice(index, 1); } // Remove from all component stores for (var componentName in Game.EntityManager.componentStores) { if (Game.EntityManager.componentStores[componentName][entityId]) { delete Game.EntityManager.componentStores[componentName][entityId]; } } // Remove from render system's display objects if (Game.Systems.RenderSystem.displayObjects[entityId]) { Game.Systems.RenderSystem.displayObjects[entityId].destroy(); delete Game.Systems.RenderSystem.displayObjects[entityId]; } } // Important: Clear the list for the next frame if (Game.EntityManager.entitiesToDestroy.length > 0) { Game.EntityManager.entitiesToDestroy = []; } } }, WaveSpawnerSystem: { currentWaveIndex: -1, isSpawning: false, spawnTimer: 0, subWaveIndex: 0, spawnedInSubWave: 0, startNextWave: function startNextWave() { this.currentWaveIndex++; this.isSpawning = true; this.spawnTimer = 0; this.subWaveIndex = 0; this.spawnedInSubWave = 0; }, update: function update() { if (!this.isSpawning || this.currentWaveIndex >= Game.WaveData.length) { return; } var currentWave = Game.WaveData[this.currentWaveIndex]; if (!currentWave || this.subWaveIndex >= currentWave.sub_waves.length) { this.isSpawning = false; return; } var currentSubWave = currentWave.sub_waves[this.subWaveIndex]; // Handle start delay if (this.spawnedInSubWave === 0 && this.spawnTimer < currentSubWave.start_delay) { this.spawnTimer += 1 / 60; // Increment by frame time return; } // Handle spawn delay between enemies if (this.spawnedInSubWave > 0 && this.spawnTimer < currentSubWave.spawn_delay) { this.spawnTimer += 1 / 60; return; } // Spawn enemy if (this.spawnedInSubWave < currentSubWave.count) { var enemyId = Game.EntityManager.createEntity(); var transform = Game.Components.TransformComponent(0 * 64, 30 * 64); var movement = Game.Components.MovementComponent(2, 0); var render = Game.Components.RenderComponent('enemy', 1, 1.0, true); var health = Game.Components.HealthComponent(30, 30); // 30 current, 30 max var enemy = Game.Components.EnemyComponent(5, 1); Game.EntityManager.addComponent(enemyId, transform); Game.EntityManager.addComponent(enemyId, movement); Game.EntityManager.addComponent(enemyId, render); Game.EntityManager.addComponent(enemyId, health); // Add the health component Game.EntityManager.addComponent(enemyId, enemy); this.spawnedInSubWave++; this.spawnTimer = 0; Game.GameplayManager.enemiesSpawnedThisWave++; } else { // Move to next sub-wave this.subWaveIndex++; this.spawnedInSubWave = 0; this.spawnTimer = 0; } } }, CombatSystem: { update: function update() { var currentTime = LK.ticks / 60; // Convert ticks to seconds // --- Tower Firing Logic (UPDATED FOR DAMAGE BUFF) --- var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; if (towerAttack.target_id !== -1 && currentTime - towerAttack.last_attack_time >= 1.0 / towerAttack.attack_speed) { var targetTransform = Game.EntityManager.componentStores['TransformComponent'][towerAttack.target_id]; if (targetTransform) { // --- NEW: Calculate final damage including buffs --- var baseDamage = towerAttack.damage; var finalDamage = baseDamage; var buffComp = Game.EntityManager.componentStores['DamageBuffComponent'] ? Game.EntityManager.componentStores['DamageBuffComponent'][towerId] : null; if (buffComp) { finalDamage = baseDamage * (1 + buffComp.multiplier); } // --- END NEW --- var projectileId = Game.EntityManager.createEntity(); var projectileTransform = Game.Components.TransformComponent(towerTransform.x, towerTransform.y); var projectileRender = Game.Components.RenderComponent(towerAttack.projectile_id, 1, 1.0, true); // Use finalDamage when creating the projectile var projectileComponent = Game.Components.ProjectileComponent(towerAttack.target_id, undefined, finalDamage); projectileComponent.source_tower_id = towerId; Game.EntityManager.addComponent(projectileId, projectileTransform); Game.EntityManager.addComponent(projectileId, projectileRender); Game.EntityManager.addComponent(projectileId, projectileComponent); if (towerAttack.projectile_id === 'chemical_projectile') { var poisonImpactComp = Game.Components.PoisonOnImpactComponent(4, 5); Game.EntityManager.addComponent(projectileId, poisonImpactComp); } if (towerAttack.projectile_id === 'slow_projectile') { var slowImpactComp = Game.Components.SlowOnImpactComponent(0.4, 2); Game.EntityManager.addComponent(projectileId, slowImpactComp); } towerAttack.last_attack_time = currentTime; } } } // --- Projectile Movement and Impact Logic (No changes here) --- var projectiles = Game.EntityManager.getEntitiesWithComponents(['ProjectileComponent', 'TransformComponent']); for (var i = projectiles.length - 1; i >= 0; i--) { var projectileId = projectiles[i]; var projectileComp = Game.EntityManager.componentStores['ProjectileComponent'][projectileId]; var projectileTransform = Game.EntityManager.componentStores['TransformComponent'][projectileId]; var targetExists = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; if (!targetExists) { Game.EntityManager.destroyEntity(projectileId); continue; } var targetTransform = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; var dx = targetTransform.x - projectileTransform.x; var dy = targetTransform.y - projectileTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { var impactPoint = { x: targetTransform.x, y: targetTransform.y }; var sourceTowerAttack = Game.EntityManager.componentStores['AttackComponent'][projectileComp.source_tower_id]; if (sourceTowerAttack && sourceTowerAttack.splash_radius > 0) { var allEnemies = Game.EntityManager.getEntitiesWithComponents(['HealthComponent', 'TransformComponent']); for (var j = 0; j < allEnemies.length; j++) { var enemyId = allEnemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var splash_dx = enemyTransform.x - impactPoint.x; var splash_dy = enemyTransform.y - impactPoint.y; var splash_dist = Math.sqrt(splash_dx * splash_dx + splash_dy * splash_dy); if (splash_dist <= sourceTowerAttack.splash_radius) { this.dealDamage(enemyId, projectileComp.damage); } } } else { this.dealDamage(projectileComp.target_id, projectileComp.damage); } var targetHealth = Game.EntityManager.componentStores['HealthComponent'][projectileComp.target_id]; if (targetHealth) { var poisonImpactComp = Game.EntityManager.componentStores['PoisonOnImpactComponent'] ? Game.EntityManager.componentStores['PoisonOnImpactComponent'][projectileId] : null; if (poisonImpactComp) { var debuff = Game.Components.PoisonDebuffComponent(poisonImpactComp.damage_per_second, poisonImpactComp.duration, currentTime); Game.EntityManager.addComponent(projectileComp.target_id, debuff); } var slowImpactComp = Game.EntityManager.componentStores['SlowOnImpactComponent'] ? Game.EntityManager.componentStores['SlowOnImpactComponent'][projectileId] : null; if (slowImpactComp) { var slowDebuff = Game.Components.SlowDebuffComponent(slowImpactComp.slow_percentage, slowImpactComp.duration); Game.EntityManager.addComponent(projectileComp.target_id, slowDebuff); } } Game.EntityManager.destroyEntity(projectileId); } else { var moveX = dx / distance * projectileComp.speed; var moveY = dy / distance * projectileComp.speed; projectileTransform.x += moveX; projectileTransform.y += moveY; } } // --- Poison Debuff Processing Logic (No changes here) --- var poisonedEnemies = Game.EntityManager.getEntitiesWithComponents(['PoisonDebuffComponent']); for (var i = poisonedEnemies.length - 1; i >= 0; i--) { var enemyId = poisonedEnemies[i]; var debuff = Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; if (!debuff) { continue; } if (currentTime - debuff.last_tick_time >= 1.0) { this.dealDamage(enemyId, debuff.damage_per_second); debuff.last_tick_time = currentTime; debuff.duration_remaining -= 1.0; } if (debuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; } } }, dealDamage: function dealDamage(enemyId, damage) { var targetHealth = Game.EntityManager.componentStores['HealthComponent'][enemyId]; if (!targetHealth) { return; } targetHealth.current_hp -= damage; if (targetHealth.current_hp <= 0) { var enemyComp = Game.EntityManager.componentStores['EnemyComponent'][enemyId]; var goldValue = enemyComp ? enemyComp.gold_value : 0; Game.EventBus.publish(Game.Events.EnemyKilledEvent(enemyId, goldValue)); Game.EntityManager.destroyEntity(enemyId); } } }, AuraSystem: { update: function update() { // Step 1: Clean up all existing damage buffs from the previous frame. var buffedTowers = Game.EntityManager.getEntitiesWithComponents(['DamageBuffComponent']); for (var i = 0; i < buffedTowers.length; i++) { var towerId = buffedTowers[i]; delete Game.EntityManager.componentStores['DamageBuffComponent'][towerId]; } // Step 2: Find all aura-providing towers. var auraTowers = Game.EntityManager.getEntitiesWithComponents(['AuraComponent', 'TransformComponent']); if (auraTowers.length === 0) { return; // No auras to process. } // Step 3: Get all attack towers that could potentially be buffed. var attackTowers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); // Step 4: Iterate through each aura and apply its effect to nearby towers. for (var i = 0; i < auraTowers.length; i++) { var auraTowerId = auraTowers[i]; var auraComponent = Game.EntityManager.componentStores['AuraComponent'][auraTowerId]; var auraTransform = Game.EntityManager.componentStores['TransformComponent'][auraTowerId]; // Only process damage buff auras for now if (auraComponent.effect_type !== 'damage_buff') { continue; } for (var j = 0; j < attackTowers.length; j++) { var attackTowerId = attackTowers[j]; // An aura tower cannot buff itself. if (attackTowerId === auraTowerId) { continue; } var attackTransform = Game.EntityManager.componentStores['TransformComponent'][attackTowerId]; // Check distance var dx = auraTransform.x - attackTransform.x; var dy = auraTransform.y - attackTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= auraComponent.range) { // Apply the buff var buff = Game.Components.DamageBuffComponent(0.2, auraTowerId); // 20% damage bonus Game.EntityManager.addComponent(attackTowerId, buff); } } } } }, EconomySystem: { update: function update() { // Find all towers that generate gold var goldGenerators = Game.EntityManager.getEntitiesWithComponents(['GoldGenerationComponent']); // Sum up the gold from all of them for (var i = 0; i < goldGenerators.length; i++) { var entityId = goldGenerators[i]; var goldGenComp = Game.EntityManager.componentStores['GoldGenerationComponent'][entityId]; if (goldGenComp) { // Add the gold to the player's resources. // Since this runs once per second, we add the gold_per_second value directly. Game.ResourceManager.addGold(goldGenComp.gold_per_second); } } } }, // --- NEW SYSTEM --- TowerSystem: { tryUpgradeTower: function tryUpgradeTower(towerId, path) { // Logic will be implemented here in the next step } } };
User prompt
Please replace the entire Game.Systems object with the following version, which adds the new, empty TowerSystem at the end. Game.Systems = { MovementSystem: { update: function update() { var movableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var i = 0; i < movableEntities.length; i++) { var entityId = movableEntities[i]; var transformComponent = Game.EntityManager.componentStores['TransformComponent'][entityId]; var movementComponent = Game.EntityManager.componentStores['MovementComponent'][entityId]; // --- START OF NEW SLOW LOGIC --- // 1. Get base speed and check for slow debuff var currentSpeed = movementComponent.speed; var slowDebuff = Game.EntityManager.componentStores['SlowDebuffComponent'] ? Game.EntityManager.componentStores['SlowDebuffComponent'][entityId] : null; if (slowDebuff) { // 2. Apply the speed reduction currentSpeed = movementComponent.speed * (1 - slowDebuff.slow_percentage); // 3. Update the debuff's duration and remove it if it expires slowDebuff.duration_remaining -= 1.0 / 60; // Reduce duration by frame time (1/60th of a second) if (slowDebuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['SlowDebuffComponent'][entityId]; } } // --- END OF NEW SLOW LOGIC --- if (Game.PathManager.currentPath && Game.PathManager.currentPath.length > 0) { var targetWaypoint = Game.PathManager.currentPath[movementComponent.path_index]; if (targetWaypoint) { var dx = targetWaypoint.x - transformComponent.x; var dy = targetWaypoint.y - transformComponent.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 1) { // 4. Use the calculated currentSpeed for movement var moveX = dx / distance * currentSpeed; var moveY = dy / distance * currentSpeed; transformComponent.x += moveX; transformComponent.y += moveY; } else { movementComponent.path_index++; if (movementComponent.path_index >= Game.PathManager.currentPath.length) { Game.EventBus.publish(Game.Events.EnemyReachedEndEvent(entityId)); } } } } } } }, RenderSystem: { displayObjects: {}, update: function update() { var renderableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'RenderComponent']); for (var i = 0; i < renderableEntities.length; i++) { var entityId = renderableEntities[i]; var transform = Game.EntityManager.componentStores['TransformComponent'][entityId]; var render = Game.EntityManager.componentStores['RenderComponent'][entityId]; // Get the visual sprite, or create it if it doesn't exist var sprite = this.displayObjects[entityId]; if (!sprite) { sprite = game.attachAsset(render.sprite_id, {}); this.displayObjects[entityId] = sprite; } // Sync the sprite's properties with the component data sprite.x = transform.x; sprite.y = transform.y; sprite.visible = render.is_visible; } } }, TowerBuildSystem: { tryBuildAt: function tryBuildAt(gridX, gridY, towerType) { // 0. Look up tower data var towerData = Game.TowerData[towerType]; if (!towerData) { return; // Exit if tower type is invalid } // 1. Check if the location is buildable if (!Game.GridManager.isBuildable(gridX, gridY)) { return; // Exit if not buildable } // 2. Check if the player can afford the tower if (!Game.ResourceManager.spendGold(towerData.cost)) { return; // Exit if not enough gold } // 3. If checks pass, create the tower entity var towerId = Game.EntityManager.createEntity(); // 4. Mark the grid cell as occupied by a tower Game.GridManager.grid[gridX][gridY] = Game.GridManager.CELL_STATES.TOWER; // 5. Add universal components that all towers have var transform = Game.Components.TransformComponent(gridX * 64, gridY * 64); Game.EntityManager.addComponent(towerId, transform); var render = Game.Components.RenderComponent(towerData.sprite_id, 2, 1.0, true); Game.EntityManager.addComponent(towerId, render); // 6. Add specific components from the tower's data definition for (var i = 0; i < towerData.components.length; i++) { var componentDef = towerData.components[i]; // Create the component by calling its factory function with the specified arguments var component = Game.Components[componentDef.name].apply(null, componentDef.args); Game.EntityManager.addComponent(towerId, component); } // 7. Announce that a tower has been placed var event = Game.Events.TowerPlacedEvent(towerId, towerData.cost, transform.x, transform.y); Game.EventBus.publish(event); } }, TargetingSystem: { update: function update() { var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; // Find best target (closest to end of path) var bestTarget = -1; var bestPathIndex = -1; var enemies = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var j = 0; j < enemies.length; j++) { var enemyId = enemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var enemyMovement = Game.EntityManager.componentStores['MovementComponent'][enemyId]; // Check if enemy is within range var dx = enemyTransform.x - towerTransform.x; var dy = enemyTransform.y - towerTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= towerAttack.range) { // Check if this enemy is closer to the end if (enemyMovement.path_index > bestPathIndex) { bestTarget = enemyId; bestPathIndex = enemyMovement.path_index; } } } // Update tower's target towerAttack.target_id = bestTarget; } } }, CleanupSystem: { update: function update() { for (var i = 0; i < Game.EntityManager.entitiesToDestroy.length; i++) { var entityId = Game.EntityManager.entitiesToDestroy[i]; // Remove from active entities list var index = Game.EntityManager.activeEntities.indexOf(entityId); if (index > -1) { Game.EntityManager.activeEntities.splice(index, 1); } // Remove from all component stores for (var componentName in Game.EntityManager.componentStores) { if (Game.EntityManager.componentStores[componentName][entityId]) { delete Game.EntityManager.componentStores[componentName][entityId]; } } // Remove from render system's display objects if (Game.Systems.RenderSystem.displayObjects[entityId]) { Game.Systems.RenderSystem.displayObjects[entityId].destroy(); delete Game.Systems.RenderSystem.displayObjects[entityId]; } } // Important: Clear the list for the next frame if (Game.EntityManager.entitiesToDestroy.length > 0) { Game.EntityManager.entitiesToDestroy = []; } } }, WaveSpawnerSystem: { currentWaveIndex: -1, isSpawning: false, spawnTimer: 0, subWaveIndex: 0, spawnedInSubWave: 0, startNextWave: function startNextWave() { this.currentWaveIndex++; this.isSpawning = true; this.spawnTimer = 0; this.subWaveIndex = 0; this.spawnedInSubWave = 0; }, update: function update() { if (!this.isSpawning || this.currentWaveIndex >= Game.WaveData.length) { return; } var currentWave = Game.WaveData[this.currentWaveIndex]; if (!currentWave || this.subWaveIndex >= currentWave.sub_waves.length) { this.isSpawning = false; return; } var currentSubWave = currentWave.sub_waves[this.subWaveIndex]; // Handle start delay if (this.spawnedInSubWave === 0 && this.spawnTimer < currentSubWave.start_delay) { this.spawnTimer += 1 / 60; // Increment by frame time return; } // Handle spawn delay between enemies if (this.spawnedInSubWave > 0 && this.spawnTimer < currentSubWave.spawn_delay) { this.spawnTimer += 1 / 60; return; } // Spawn enemy if (this.spawnedInSubWave < currentSubWave.count) { var enemyId = Game.EntityManager.createEntity(); var transform = Game.Components.TransformComponent(0 * 64, 30 * 64); var movement = Game.Components.MovementComponent(2, 0); var render = Game.Components.RenderComponent('enemy', 1, 1.0, true); var health = Game.Components.HealthComponent(30, 30); // 30 current, 30 max var enemy = Game.Components.EnemyComponent(5, 1); Game.EntityManager.addComponent(enemyId, transform); Game.EntityManager.addComponent(enemyId, movement); Game.EntityManager.addComponent(enemyId, render); Game.EntityManager.addComponent(enemyId, health); // Add the health component Game.EntityManager.addComponent(enemyId, enemy); this.spawnedInSubWave++; this.spawnTimer = 0; Game.GameplayManager.enemiesSpawnedThisWave++; } else { // Move to next sub-wave this.subWaveIndex++; this.spawnedInSubWave = 0; this.spawnTimer = 0; } } }, CombatSystem: { update: function update() { var currentTime = LK.ticks / 60; // Convert ticks to seconds // --- Tower Firing Logic (UPDATED FOR DAMAGE BUFF) --- var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; if (towerAttack.target_id !== -1 && currentTime - towerAttack.last_attack_time >= 1.0 / towerAttack.attack_speed) { var targetTransform = Game.EntityManager.componentStores['TransformComponent'][towerAttack.target_id]; if (targetTransform) { // --- NEW: Calculate final damage including buffs --- var baseDamage = towerAttack.damage; var finalDamage = baseDamage; var buffComp = Game.EntityManager.componentStores['DamageBuffComponent'] ? Game.EntityManager.componentStores['DamageBuffComponent'][towerId] : null; if (buffComp) { finalDamage = baseDamage * (1 + buffComp.multiplier); } // --- END NEW --- var projectileId = Game.EntityManager.createEntity(); var projectileTransform = Game.Components.TransformComponent(towerTransform.x, towerTransform.y); var projectileRender = Game.Components.RenderComponent(towerAttack.projectile_id, 1, 1.0, true); // Use finalDamage when creating the projectile var projectileComponent = Game.Components.ProjectileComponent(towerAttack.target_id, undefined, finalDamage); projectileComponent.source_tower_id = towerId; Game.EntityManager.addComponent(projectileId, projectileTransform); Game.EntityManager.addComponent(projectileId, projectileRender); Game.EntityManager.addComponent(projectileId, projectileComponent); if (towerAttack.projectile_id === 'chemical_projectile') { var poisonImpactComp = Game.Components.PoisonOnImpactComponent(4, 5); Game.EntityManager.addComponent(projectileId, poisonImpactComp); } if (towerAttack.projectile_id === 'slow_projectile') { var slowImpactComp = Game.Components.SlowOnImpactComponent(0.4, 2); Game.EntityManager.addComponent(projectileId, slowImpactComp); } towerAttack.last_attack_time = currentTime; } } } // --- Projectile Movement and Impact Logic (No changes here) --- var projectiles = Game.EntityManager.getEntitiesWithComponents(['ProjectileComponent', 'TransformComponent']); for (var i = projectiles.length - 1; i >= 0; i--) { var projectileId = projectiles[i]; var projectileComp = Game.EntityManager.componentStores['ProjectileComponent'][projectileId]; var projectileTransform = Game.EntityManager.componentStores['TransformComponent'][projectileId]; var targetExists = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; if (!targetExists) { Game.EntityManager.destroyEntity(projectileId); continue; } var targetTransform = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; var dx = targetTransform.x - projectileTransform.x; var dy = targetTransform.y - projectileTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { var impactPoint = { x: targetTransform.x, y: targetTransform.y }; var sourceTowerAttack = Game.EntityManager.componentStores['AttackComponent'][projectileComp.source_tower_id]; if (sourceTowerAttack && sourceTowerAttack.splash_radius > 0) { var allEnemies = Game.EntityManager.getEntitiesWithComponents(['HealthComponent', 'TransformComponent']); for (var j = 0; j < allEnemies.length; j++) { var enemyId = allEnemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var splash_dx = enemyTransform.x - impactPoint.x; var splash_dy = enemyTransform.y - impactPoint.y; var splash_dist = Math.sqrt(splash_dx * splash_dx + splash_dy * splash_dy); if (splash_dist <= sourceTowerAttack.splash_radius) { this.dealDamage(enemyId, projectileComp.damage); } } } else { this.dealDamage(projectileComp.target_id, projectileComp.damage); } var targetHealth = Game.EntityManager.componentStores['HealthComponent'][projectileComp.target_id]; if (targetHealth) { var poisonImpactComp = Game.EntityManager.componentStores['PoisonOnImpactComponent'] ? Game.EntityManager.componentStores['PoisonOnImpactComponent'][projectileId] : null; if (poisonImpactComp) { var debuff = Game.Components.PoisonDebuffComponent(poisonImpactComp.damage_per_second, poisonImpactComp.duration, currentTime); Game.EntityManager.addComponent(projectileComp.target_id, debuff); } var slowImpactComp = Game.EntityManager.componentStores['SlowOnImpactComponent'] ? Game.EntityManager.componentStores['SlowOnImpactComponent'][projectileId] : null; if (slowImpactComp) { var slowDebuff = Game.Components.SlowDebuffComponent(slowImpactComp.slow_percentage, slowImpactComp.duration); Game.EntityManager.addComponent(projectileComp.target_id, slowDebuff); } } Game.EntityManager.destroyEntity(projectileId); } else { var moveX = dx / distance * projectileComp.speed; var moveY = dy / distance * projectileComp.speed; projectileTransform.x += moveX; projectileTransform.y += moveY; } } // --- Poison Debuff Processing Logic (No changes here) --- var poisonedEnemies = Game.EntityManager.getEntitiesWithComponents(['PoisonDebuffComponent']); for (var i = poisonedEnemies.length - 1; i >= 0; i--) { var enemyId = poisonedEnemies[i]; var debuff = Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; if (!debuff) { continue; } if (currentTime - debuff.last_tick_time >= 1.0) { this.dealDamage(enemyId, debuff.damage_per_second); debuff.last_tick_time = currentTime; debuff.duration_remaining -= 1.0; } if (debuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; } } }, dealDamage: function dealDamage(enemyId, damage) { var targetHealth = Game.EntityManager.componentStores['HealthComponent'][enemyId]; if (!targetHealth) { return; } targetHealth.current_hp -= damage; if (targetHealth.current_hp <= 0) { var enemyComp = Game.EntityManager.componentStores['EnemyComponent'][enemyId]; var goldValue = enemyComp ? enemyComp.gold_value : 0; Game.EventBus.publish(Game.Events.EnemyKilledEvent(enemyId, goldValue)); Game.EntityManager.destroyEntity(enemyId); } } }, AuraSystem: { update: function update() { // Step 1: Clean up all existing damage buffs from the previous frame. var buffedTowers = Game.EntityManager.getEntitiesWithComponents(['DamageBuffComponent']); for (var i = 0; i < buffedTowers.length; i++) { var towerId = buffedTowers[i]; delete Game.EntityManager.componentStores['DamageBuffComponent'][towerId]; } // Step 2: Find all aura-providing towers. var auraTowers = Game.EntityManager.getEntitiesWithComponents(['AuraComponent', 'TransformComponent']); if (auraTowers.length === 0) { return; // No auras to process. } // Step 3: Get all attack towers that could potentially be buffed. var attackTowers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); // Step 4: Iterate through each aura and apply its effect to nearby towers. for (var i = 0; i < auraTowers.length; i++) { var auraTowerId = auraTowers[i]; var auraComponent = Game.EntityManager.componentStores['AuraComponent'][auraTowerId]; var auraTransform = Game.EntityManager.componentStores['TransformComponent'][auraTowerId]; // Only process damage buff auras for now if (auraComponent.effect_type !== 'damage_buff') { continue; } for (var j = 0; j < attackTowers.length; j++) { var attackTowerId = attackTowers[j]; // An aura tower cannot buff itself. if (attackTowerId === auraTowerId) { continue; } var attackTransform = Game.EntityManager.componentStores['TransformComponent'][attackTowerId]; // Check distance var dx = auraTransform.x - attackTransform.x; var dy = auraTransform.y - attackTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= auraComponent.range) { // Apply the buff var buff = Game.Components.DamageBuffComponent(0.2, auraTowerId); // 20% damage bonus Game.EntityManager.addComponent(attackTowerId, buff); } } } } }, EconomySystem: { update: function update() { // Find all towers that generate gold var goldGenerators = Game.EntityManager.getEntitiesWithComponents(['GoldGenerationComponent']); // Sum up the gold from all of them for (var i = 0; i < goldGenerators.length; i++) { var entityId = goldGenerators[i]; var goldGenComp = Game.EntityManager.componentStores['GoldGenerationComponent'][entityId]; if (goldGenComp) { // Add the gold to the player's resources. // Since this runs once per second, we add the gold_per_second value directly. Game.ResourceManager.addGold(goldGenComp.gold_per_second); } } } }, // --- NEW SYSTEM --- TowerSystem: { tryUpgradeTower: function tryUpgradeTower(towerId, path) { // Logic will be implemented here in the next step } } };
User prompt
Please add the following new code block for Game.UpgradeData to your script. The best place for it is right after the Game.TowerData object. // === SECTION: UPGRADE DATA === Game.UpgradeData = { 'ArrowTower': { 'PathA': [ { cost: 60, effects: { AttackComponent: { damage: 5 } } }, // Level 1: +5 Dmg { cost: 110, effects: { AttackComponent: { damage: 10 } } } // Level 2: +10 Dmg ], 'PathB': [ { cost: 75, effects: { AttackComponent: { attack_speed: 0.2 } } }, // Level 1: +0.2 AS { cost: 125, effects: { AttackComponent: { attack_speed: 0.3 } } } // Level 2: +0.3 AS ] } // Other tower upgrades will be added here later };
User prompt
Please replace the game.update function with the following corrected version. // Set up main game loop var powerUpdateTimer = 0; game.update = function () { if (Game.GameManager.isGameOver) { return; } Game.Systems.WaveSpawnerSystem.update(); Game.Systems.TargetingSystem.update(); Game.Systems.AuraSystem.update(); Game.Systems.CombatSystem.update(); Game.Systems.MovementSystem.update(); Game.Systems.RenderSystem.update(); // Update HUD every frame goldText.setText('Gold: ' + Game.ResourceManager.gold); livesText.setText('Lives: ' + Game.ResourceManager.lives); // Update timed systems once per second (every 60 frames) powerUpdateTimer++; if (powerUpdateTimer >= 60) { Game.ResourceManager.updatePower(); // --- CORRECTED LOGIC --- // Only run the economy system if a wave is currently active. if (Game.Systems.WaveSpawnerSystem.isSpawning) { Game.Systems.EconomySystem.update(); } // --- END CORRECTION --- powerUpdateTimer = 0; } Game.Systems.CleanupSystem.update(); };
User prompt
Please replace the entire Game.Components object with the following updated version. The only change is the addition of these new fields to the TowerComponent definition. Game.Components = { TransformComponent: function TransformComponent(x, y, rotation) { return { name: 'TransformComponent', x: x || 0, y: y || 0, rotation: rotation || 0 }; }, RenderComponent: function RenderComponent(sprite_id, layer, scale, is_visible) { return { name: 'RenderComponent', sprite_id: sprite_id || '', layer: layer || 0, scale: scale || 1.0, is_visible: is_visible !== undefined ? is_visible : true }; }, HealthComponent: function HealthComponent(current_hp, max_hp) { return { name: 'HealthComponent', current_hp: current_hp || 100, max_hp: max_hp || 100 }; }, AttackComponent: function AttackComponent(damage, range, attack_speed, last_attack_time, projectile_id, target_id, splash_radius) { return { name: 'AttackComponent', damage: damage || 10, range: range || 100, attack_speed: attack_speed || 1.0, last_attack_time: last_attack_time || 0, projectile_id: projectile_id || '', target_id: target_id || -1, splash_radius: splash_radius || 0 // Default to 0 (no splash) }; }, MovementComponent: function MovementComponent(speed, path_index) { return { name: 'MovementComponent', speed: speed || 50, path_index: path_index || 0 }; }, PowerProductionComponent: function PowerProductionComponent(rate) { return { name: 'PowerProductionComponent', production_rate: rate || 0 }; }, PowerConsumptionComponent: function PowerConsumptionComponent(rate) { return { name: 'PowerConsumptionComponent', drain_rate: rate || 0 }; }, // --- UPDATED COMPONENT --- TowerComponent: function TowerComponent(cost, sellValue) { return { name: 'TowerComponent', cost: cost || 50, sell_value: sellValue || 25, upgrade_level: 1, upgrade_path_A_level: 0, upgrade_path_B_level: 0 }; }, ProjectileComponent: function ProjectileComponent(target_id, speed, damage) { return { name: 'ProjectileComponent', target_id: target_id || -1, speed: speed || 8, // Let's keep the speed lower damage: damage || 10 }; }, EnemyComponent: function EnemyComponent(goldValue, livesCost) { return { name: 'EnemyComponent', gold_value: goldValue || 5, lives_cost: livesCost || 1 }; }, PowerStorageComponent: function PowerStorageComponent(capacity) { return { name: 'PowerStorageComponent', capacity: capacity || 0 }; }, PoisonOnImpactComponent: function PoisonOnImpactComponent(damage_per_second, duration) { return { name: 'PoisonOnImpactComponent', damage_per_second: damage_per_second || 0, duration: duration || 0 }; }, PoisonDebuffComponent: function PoisonDebuffComponent(damage_per_second, duration_remaining, last_tick_time) { return { name: 'PoisonDebuffComponent', damage_per_second: damage_per_second || 0, duration_remaining: duration_remaining || 0, last_tick_time: last_tick_time || 0 }; }, SlowOnImpactComponent: function SlowOnImpactComponent(slow_percentage, duration) { return { name: 'SlowOnImpactComponent', slow_percentage: slow_percentage || 0, duration: duration || 0 }; }, SlowDebuffComponent: function SlowDebuffComponent(slow_percentage, duration_remaining) { return { name: 'SlowDebuffComponent', slow_percentage: slow_percentage || 0, duration_remaining: duration_remaining || 0 }; }, AuraComponent: function AuraComponent(range, effect_type) { return { name: 'AuraComponent', range: range || 100, effect_type: effect_type || '' }; }, DamageBuffComponent: function DamageBuffComponent(multiplier, source_aura_id) { return { name: 'DamageBuffComponent', multiplier: multiplier || 0, source_aura_id: source_aura_id || -1 }; }, GoldGenerationComponent: function GoldGenerationComponent(gold_per_second) { return { name: 'GoldGenerationComponent', gold_per_second: gold_per_second || 0 }; } };
User prompt
Please replace the entire // === SECTION: TEMP BUILD UI === block with the following updated version. This adds the new button and adjusts the vertical spacing of the entire list to accommodate it. // === SECTION: TEMP BUILD UI === var buildMode = 'ArrowTower'; // Keep this variable var arrowButton = new Text2('Build: Arrow', { size: 40, fill: 0x2ecc71 }); arrowButton.anchor.set(0, 0.5); LK.gui.left.addChild(arrowButton); arrowButton.x = 20; arrowButton.y = -350; arrowButton.down = function () { buildMode = 'ArrowTower'; }; var generatorButton = new Text2('Build: Generator', { size: 40, fill: 0x3498db }); generatorButton.anchor.set(0, 0.5); LK.gui.left.addChild(generatorButton); generatorButton.x = 20; generatorButton.y = -280; generatorButton.down = function () { buildMode = 'GeneratorTower'; }; var capacitorButton = new Text2('Build: Capacitor', { size: 40, fill: 0xf1c40f }); capacitorButton.anchor.set(0, 0.5); LK.gui.left.addChild(capacitorButton); capacitorButton.x = 20; capacitorButton.y = -210; capacitorButton.down = function () { buildMode = 'CapacitorTower'; }; var rockLauncherButton = new Text2('Build: Rock Launcher', { size: 40, fill: 0x8D6E63 }); rockLauncherButton.anchor.set(0, 0.5); LK.gui.left.addChild(rockLauncherButton); rockLauncherButton.x = 20; rockLauncherButton.y = -140; rockLauncherButton.down = function () { buildMode = 'RockLauncher'; }; var chemicalTowerButton = new Text2('Build: Chemical', { size: 40, fill: 0xb87333 }); chemicalTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(chemicalTowerButton); chemicalTowerButton.x = 20; chemicalTowerButton.y = -70; chemicalTowerButton.down = function () { buildMode = 'ChemicalTower'; }; var slowTowerButton = new Text2('Build: Slow', { size: 40, fill: 0x5DADE2 }); slowTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(slowTowerButton); slowTowerButton.x = 20; slowTowerButton.y = 0; slowTowerButton.down = function () { buildMode = 'SlowTower'; }; var darkTowerButton = new Text2('Build: Dark', { size: 40, fill: 0x9b59b6 }); darkTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(darkTowerButton); darkTowerButton.x = 20; darkTowerButton.y = 70; darkTowerButton.down = function () { buildMode = 'DarkTower'; }; // --- NEW BUTTON --- var graveyardTowerButton = new Text2('Build: Graveyard', { size: 40, fill: 0x607d8b }); graveyardTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(graveyardTowerButton); graveyardTowerButton.x = 20; graveyardTowerButton.y = 140; graveyardTowerButton.down = function () { buildMode = 'GraveyardTower'; };
User prompt
Please replace the entire Game.Systems object with the following version. The only change is the implementation of the EconomySystem.update function. Game.Systems = { MovementSystem: { update: function update() { var movableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var i = 0; i < movableEntities.length; i++) { var entityId = movableEntities[i]; var transformComponent = Game.EntityManager.componentStores['TransformComponent'][entityId]; var movementComponent = Game.EntityManager.componentStores['MovementComponent'][entityId]; // --- START OF NEW SLOW LOGIC --- // 1. Get base speed and check for slow debuff var currentSpeed = movementComponent.speed; var slowDebuff = Game.EntityManager.componentStores['SlowDebuffComponent'] ? Game.EntityManager.componentStores['SlowDebuffComponent'][entityId] : null; if (slowDebuff) { // 2. Apply the speed reduction currentSpeed = movementComponent.speed * (1 - slowDebuff.slow_percentage); // 3. Update the debuff's duration and remove it if it expires slowDebuff.duration_remaining -= 1.0 / 60; // Reduce duration by frame time (1/60th of a second) if (slowDebuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['SlowDebuffComponent'][entityId]; } } // --- END OF NEW SLOW LOGIC --- if (Game.PathManager.currentPath && Game.PathManager.currentPath.length > 0) { var targetWaypoint = Game.PathManager.currentPath[movementComponent.path_index]; if (targetWaypoint) { var dx = targetWaypoint.x - transformComponent.x; var dy = targetWaypoint.y - transformComponent.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 1) { // 4. Use the calculated currentSpeed for movement var moveX = dx / distance * currentSpeed; var moveY = dy / distance * currentSpeed; transformComponent.x += moveX; transformComponent.y += moveY; } else { movementComponent.path_index++; if (movementComponent.path_index >= Game.PathManager.currentPath.length) { Game.EventBus.publish(Game.Events.EnemyReachedEndEvent(entityId)); } } } } } } }, RenderSystem: { displayObjects: {}, update: function update() { var renderableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'RenderComponent']); for (var i = 0; i < renderableEntities.length; i++) { var entityId = renderableEntities[i]; var transform = Game.EntityManager.componentStores['TransformComponent'][entityId]; var render = Game.EntityManager.componentStores['RenderComponent'][entityId]; // Get the visual sprite, or create it if it doesn't exist var sprite = this.displayObjects[entityId]; if (!sprite) { sprite = game.attachAsset(render.sprite_id, {}); this.displayObjects[entityId] = sprite; } // Sync the sprite's properties with the component data sprite.x = transform.x; sprite.y = transform.y; sprite.visible = render.is_visible; } } }, TowerBuildSystem: { tryBuildAt: function tryBuildAt(gridX, gridY, towerType) { // 0. Look up tower data var towerData = Game.TowerData[towerType]; if (!towerData) { return; // Exit if tower type is invalid } // 1. Check if the location is buildable if (!Game.GridManager.isBuildable(gridX, gridY)) { return; // Exit if not buildable } // 2. Check if the player can afford the tower if (!Game.ResourceManager.spendGold(towerData.cost)) { return; // Exit if not enough gold } // 3. If checks pass, create the tower entity var towerId = Game.EntityManager.createEntity(); // 4. Mark the grid cell as occupied by a tower Game.GridManager.grid[gridX][gridY] = Game.GridManager.CELL_STATES.TOWER; // 5. Add universal components that all towers have var transform = Game.Components.TransformComponent(gridX * 64, gridY * 64); Game.EntityManager.addComponent(towerId, transform); var render = Game.Components.RenderComponent(towerData.sprite_id, 2, 1.0, true); Game.EntityManager.addComponent(towerId, render); // 6. Add specific components from the tower's data definition for (var i = 0; i < towerData.components.length; i++) { var componentDef = towerData.components[i]; // Create the component by calling its factory function with the specified arguments var component = Game.Components[componentDef.name].apply(null, componentDef.args); Game.EntityManager.addComponent(towerId, component); } // 7. Announce that a tower has been placed var event = Game.Events.TowerPlacedEvent(towerId, towerData.cost, transform.x, transform.y); Game.EventBus.publish(event); } }, TargetingSystem: { update: function update() { var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; // Find best target (closest to end of path) var bestTarget = -1; var bestPathIndex = -1; var enemies = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var j = 0; j < enemies.length; j++) { var enemyId = enemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var enemyMovement = Game.EntityManager.componentStores['MovementComponent'][enemyId]; // Check if enemy is within range var dx = enemyTransform.x - towerTransform.x; var dy = enemyTransform.y - towerTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= towerAttack.range) { // Check if this enemy is closer to the end if (enemyMovement.path_index > bestPathIndex) { bestTarget = enemyId; bestPathIndex = enemyMovement.path_index; } } } // Update tower's target towerAttack.target_id = bestTarget; } } }, CleanupSystem: { update: function update() { for (var i = 0; i < Game.EntityManager.entitiesToDestroy.length; i++) { var entityId = Game.EntityManager.entitiesToDestroy[i]; // Remove from active entities list var index = Game.EntityManager.activeEntities.indexOf(entityId); if (index > -1) { Game.EntityManager.activeEntities.splice(index, 1); } // Remove from all component stores for (var componentName in Game.EntityManager.componentStores) { if (Game.EntityManager.componentStores[componentName][entityId]) { delete Game.EntityManager.componentStores[componentName][entityId]; } } // Remove from render system's display objects if (Game.Systems.RenderSystem.displayObjects[entityId]) { Game.Systems.RenderSystem.displayObjects[entityId].destroy(); delete Game.Systems.RenderSystem.displayObjects[entityId]; } } // Important: Clear the list for the next frame if (Game.EntityManager.entitiesToDestroy.length > 0) { Game.EntityManager.entitiesToDestroy = []; } } }, WaveSpawnerSystem: { currentWaveIndex: -1, isSpawning: false, spawnTimer: 0, subWaveIndex: 0, spawnedInSubWave: 0, startNextWave: function startNextWave() { this.currentWaveIndex++; this.isSpawning = true; this.spawnTimer = 0; this.subWaveIndex = 0; this.spawnedInSubWave = 0; }, update: function update() { if (!this.isSpawning || this.currentWaveIndex >= Game.WaveData.length) { return; } var currentWave = Game.WaveData[this.currentWaveIndex]; if (!currentWave || this.subWaveIndex >= currentWave.sub_waves.length) { this.isSpawning = false; return; } var currentSubWave = currentWave.sub_waves[this.subWaveIndex]; // Handle start delay if (this.spawnedInSubWave === 0 && this.spawnTimer < currentSubWave.start_delay) { this.spawnTimer += 1 / 60; // Increment by frame time return; } // Handle spawn delay between enemies if (this.spawnedInSubWave > 0 && this.spawnTimer < currentSubWave.spawn_delay) { this.spawnTimer += 1 / 60; return; } // Spawn enemy if (this.spawnedInSubWave < currentSubWave.count) { var enemyId = Game.EntityManager.createEntity(); var transform = Game.Components.TransformComponent(0 * 64, 30 * 64); var movement = Game.Components.MovementComponent(2, 0); var render = Game.Components.RenderComponent('enemy', 1, 1.0, true); var health = Game.Components.HealthComponent(30, 30); // 30 current, 30 max var enemy = Game.Components.EnemyComponent(5, 1); Game.EntityManager.addComponent(enemyId, transform); Game.EntityManager.addComponent(enemyId, movement); Game.EntityManager.addComponent(enemyId, render); Game.EntityManager.addComponent(enemyId, health); // Add the health component Game.EntityManager.addComponent(enemyId, enemy); this.spawnedInSubWave++; this.spawnTimer = 0; Game.GameplayManager.enemiesSpawnedThisWave++; } else { // Move to next sub-wave this.subWaveIndex++; this.spawnedInSubWave = 0; this.spawnTimer = 0; } } }, CombatSystem: { update: function update() { var currentTime = LK.ticks / 60; // Convert ticks to seconds // --- Tower Firing Logic (UPDATED FOR DAMAGE BUFF) --- var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; if (towerAttack.target_id !== -1 && currentTime - towerAttack.last_attack_time >= 1.0 / towerAttack.attack_speed) { var targetTransform = Game.EntityManager.componentStores['TransformComponent'][towerAttack.target_id]; if (targetTransform) { // --- NEW: Calculate final damage including buffs --- var baseDamage = towerAttack.damage; var finalDamage = baseDamage; var buffComp = Game.EntityManager.componentStores['DamageBuffComponent'] ? Game.EntityManager.componentStores['DamageBuffComponent'][towerId] : null; if (buffComp) { finalDamage = baseDamage * (1 + buffComp.multiplier); } // --- END NEW --- var projectileId = Game.EntityManager.createEntity(); var projectileTransform = Game.Components.TransformComponent(towerTransform.x, towerTransform.y); var projectileRender = Game.Components.RenderComponent(towerAttack.projectile_id, 1, 1.0, true); // Use finalDamage when creating the projectile var projectileComponent = Game.Components.ProjectileComponent(towerAttack.target_id, undefined, finalDamage); projectileComponent.source_tower_id = towerId; Game.EntityManager.addComponent(projectileId, projectileTransform); Game.EntityManager.addComponent(projectileId, projectileRender); Game.EntityManager.addComponent(projectileId, projectileComponent); if (towerAttack.projectile_id === 'chemical_projectile') { var poisonImpactComp = Game.Components.PoisonOnImpactComponent(4, 5); Game.EntityManager.addComponent(projectileId, poisonImpactComp); } if (towerAttack.projectile_id === 'slow_projectile') { var slowImpactComp = Game.Components.SlowOnImpactComponent(0.4, 2); Game.EntityManager.addComponent(projectileId, slowImpactComp); } towerAttack.last_attack_time = currentTime; } } } // --- Projectile Movement and Impact Logic (No changes here) --- var projectiles = Game.EntityManager.getEntitiesWithComponents(['ProjectileComponent', 'TransformComponent']); for (var i = projectiles.length - 1; i >= 0; i--) { var projectileId = projectiles[i]; var projectileComp = Game.EntityManager.componentStores['ProjectileComponent'][projectileId]; var projectileTransform = Game.EntityManager.componentStores['TransformComponent'][projectileId]; var targetExists = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; if (!targetExists) { Game.EntityManager.destroyEntity(projectileId); continue; } var targetTransform = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; var dx = targetTransform.x - projectileTransform.x; var dy = targetTransform.y - projectileTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { var impactPoint = { x: targetTransform.x, y: targetTransform.y }; var sourceTowerAttack = Game.EntityManager.componentStores['AttackComponent'][projectileComp.source_tower_id]; if (sourceTowerAttack && sourceTowerAttack.splash_radius > 0) { var allEnemies = Game.EntityManager.getEntitiesWithComponents(['HealthComponent', 'TransformComponent']); for (var j = 0; j < allEnemies.length; j++) { var enemyId = allEnemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var splash_dx = enemyTransform.x - impactPoint.x; var splash_dy = enemyTransform.y - impactPoint.y; var splash_dist = Math.sqrt(splash_dx * splash_dx + splash_dy * splash_dy); if (splash_dist <= sourceTowerAttack.splash_radius) { this.dealDamage(enemyId, projectileComp.damage); } } } else { this.dealDamage(projectileComp.target_id, projectileComp.damage); } var targetHealth = Game.EntityManager.componentStores['HealthComponent'][projectileComp.target_id]; if (targetHealth) { var poisonImpactComp = Game.EntityManager.componentStores['PoisonOnImpactComponent'] ? Game.EntityManager.componentStores['PoisonOnImpactComponent'][projectileId] : null; if (poisonImpactComp) { var debuff = Game.Components.PoisonDebuffComponent(poisonImpactComp.damage_per_second, poisonImpactComp.duration, currentTime); Game.EntityManager.addComponent(projectileComp.target_id, debuff); } var slowImpactComp = Game.EntityManager.componentStores['SlowOnImpactComponent'] ? Game.EntityManager.componentStores['SlowOnImpactComponent'][projectileId] : null; if (slowImpactComp) { var slowDebuff = Game.Components.SlowDebuffComponent(slowImpactComp.slow_percentage, slowImpactComp.duration); Game.EntityManager.addComponent(projectileComp.target_id, slowDebuff); } } Game.EntityManager.destroyEntity(projectileId); } else { var moveX = dx / distance * projectileComp.speed; var moveY = dy / distance * projectileComp.speed; projectileTransform.x += moveX; projectileTransform.y += moveY; } } // --- Poison Debuff Processing Logic (No changes here) --- var poisonedEnemies = Game.EntityManager.getEntitiesWithComponents(['PoisonDebuffComponent']); for (var i = poisonedEnemies.length - 1; i >= 0; i--) { var enemyId = poisonedEnemies[i]; var debuff = Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; if (!debuff) { continue; } if (currentTime - debuff.last_tick_time >= 1.0) { this.dealDamage(enemyId, debuff.damage_per_second); debuff.last_tick_time = currentTime; debuff.duration_remaining -= 1.0; } if (debuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; } } }, dealDamage: function dealDamage(enemyId, damage) { var targetHealth = Game.EntityManager.componentStores['HealthComponent'][enemyId]; if (!targetHealth) { return; } targetHealth.current_hp -= damage; if (targetHealth.current_hp <= 0) { var enemyComp = Game.EntityManager.componentStores['EnemyComponent'][enemyId]; var goldValue = enemyComp ? enemyComp.gold_value : 0; Game.EventBus.publish(Game.Events.EnemyKilledEvent(enemyId, goldValue)); Game.EntityManager.destroyEntity(enemyId); } } }, AuraSystem: { update: function update() { // Step 1: Clean up all existing damage buffs from the previous frame. var buffedTowers = Game.EntityManager.getEntitiesWithComponents(['DamageBuffComponent']); for (var i = 0; i < buffedTowers.length; i++) { var towerId = buffedTowers[i]; delete Game.EntityManager.componentStores['DamageBuffComponent'][towerId]; } // Step 2: Find all aura-providing towers. var auraTowers = Game.EntityManager.getEntitiesWithComponents(['AuraComponent', 'TransformComponent']); if (auraTowers.length === 0) { return; // No auras to process. } // Step 3: Get all attack towers that could potentially be buffed. var attackTowers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); // Step 4: Iterate through each aura and apply its effect to nearby towers. for (var i = 0; i < auraTowers.length; i++) { var auraTowerId = auraTowers[i]; var auraComponent = Game.EntityManager.componentStores['AuraComponent'][auraTowerId]; var auraTransform = Game.EntityManager.componentStores['TransformComponent'][auraTowerId]; // Only process damage buff auras for now if (auraComponent.effect_type !== 'damage_buff') { continue; } for (var j = 0; j < attackTowers.length; j++) { var attackTowerId = attackTowers[j]; // An aura tower cannot buff itself. if (attackTowerId === auraTowerId) { continue; } var attackTransform = Game.EntityManager.componentStores['TransformComponent'][attackTowerId]; // Check distance var dx = auraTransform.x - attackTransform.x; var dy = auraTransform.y - attackTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= auraComponent.range) { // Apply the buff var buff = Game.Components.DamageBuffComponent(0.2, auraTowerId); // 20% damage bonus Game.EntityManager.addComponent(attackTowerId, buff); } } } } }, EconomySystem: { update: function update() { // Find all towers that generate gold var goldGenerators = Game.EntityManager.getEntitiesWithComponents(['GoldGenerationComponent']); // Sum up the gold from all of them for (var i = 0; i < goldGenerators.length; i++) { var entityId = goldGenerators[i]; var goldGenComp = Game.EntityManager.componentStores['GoldGenerationComponent'][entityId]; if (goldGenComp) { // Add the gold to the player's resources. // Since this runs once per second, we add the gold_per_second value directly. Game.ResourceManager.addGold(goldGenComp.gold_per_second); } } } } };
User prompt
2. Replace the main game.update function with this version, which adds the EconomySystem.update() call to the timer: // Set up main game loop var powerUpdateTimer = 0; game.update = function () { if (Game.GameManager.isGameOver) { return; } Game.Systems.WaveSpawnerSystem.update(); Game.Systems.TargetingSystem.update(); Game.Systems.AuraSystem.update(); Game.Systems.CombatSystem.update(); Game.Systems.MovementSystem.update(); Game.Systems.RenderSystem.update(); // Update HUD every frame goldText.setText('Gold: ' + Game.ResourceManager.gold); livesText.setText('Lives: ' + Game.ResourceManager.lives); // Update power system once per second (every 60 frames) powerUpdateTimer++; if (powerUpdateTimer >= 60) { Game.ResourceManager.updatePower(); Game.Systems.EconomySystem.update(); // NEW powerUpdateTimer = 0; } Game.Systems.CleanupSystem.update(); };
User prompt
1. Replace the Game.Systems object with this version, which adds the empty EconomySystem: Game.Systems = { MovementSystem: { update: function update() { var movableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var i = 0; i < movableEntities.length; i++) { var entityId = movableEntities[i]; var transformComponent = Game.EntityManager.componentStores['TransformComponent'][entityId]; var movementComponent = Game.EntityManager.componentStores['MovementComponent'][entityId]; // --- START OF NEW SLOW LOGIC --- // 1. Get base speed and check for slow debuff var currentSpeed = movementComponent.speed; var slowDebuff = Game.EntityManager.componentStores['SlowDebuffComponent'] ? Game.EntityManager.componentStores['SlowDebuffComponent'][entityId] : null; if (slowDebuff) { // 2. Apply the speed reduction currentSpeed = movementComponent.speed * (1 - slowDebuff.slow_percentage); // 3. Update the debuff's duration and remove it if it expires slowDebuff.duration_remaining -= 1.0 / 60; // Reduce duration by frame time (1/60th of a second) if (slowDebuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['SlowDebuffComponent'][entityId]; } } // --- END OF NEW SLOW LOGIC --- if (Game.PathManager.currentPath && Game.PathManager.currentPath.length > 0) { var targetWaypoint = Game.PathManager.currentPath[movementComponent.path_index]; if (targetWaypoint) { var dx = targetWaypoint.x - transformComponent.x; var dy = targetWaypoint.y - transformComponent.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 1) { // 4. Use the calculated currentSpeed for movement var moveX = dx / distance * currentSpeed; var moveY = dy / distance * currentSpeed; transformComponent.x += moveX; transformComponent.y += moveY; } else { movementComponent.path_index++; if (movementComponent.path_index >= Game.PathManager.currentPath.length) { Game.EventBus.publish(Game.Events.EnemyReachedEndEvent(entityId)); } } } } } } }, RenderSystem: { displayObjects: {}, update: function update() { var renderableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'RenderComponent']); for (var i = 0; i < renderableEntities.length; i++) { var entityId = renderableEntities[i]; var transform = Game.EntityManager.componentStores['TransformComponent'][entityId]; var render = Game.EntityManager.componentStores['RenderComponent'][entityId]; // Get the visual sprite, or create it if it doesn't exist var sprite = this.displayObjects[entityId]; if (!sprite) { sprite = game.attachAsset(render.sprite_id, {}); this.displayObjects[entityId] = sprite; } // Sync the sprite's properties with the component data sprite.x = transform.x; sprite.y = transform.y; sprite.visible = render.is_visible; } } }, TowerBuildSystem: { tryBuildAt: function tryBuildAt(gridX, gridY, towerType) { // 0. Look up tower data var towerData = Game.TowerData[towerType]; if (!towerData) { return; // Exit if tower type is invalid } // 1. Check if the location is buildable if (!Game.GridManager.isBuildable(gridX, gridY)) { return; // Exit if not buildable } // 2. Check if the player can afford the tower if (!Game.ResourceManager.spendGold(towerData.cost)) { return; // Exit if not enough gold } // 3. If checks pass, create the tower entity var towerId = Game.EntityManager.createEntity(); // 4. Mark the grid cell as occupied by a tower Game.GridManager.grid[gridX][gridY] = Game.GridManager.CELL_STATES.TOWER; // 5. Add universal components that all towers have var transform = Game.Components.TransformComponent(gridX * 64, gridY * 64); Game.EntityManager.addComponent(towerId, transform); var render = Game.Components.RenderComponent(towerData.sprite_id, 2, 1.0, true); Game.EntityManager.addComponent(towerId, render); // 6. Add specific components from the tower's data definition for (var i = 0; i < towerData.components.length; i++) { var componentDef = towerData.components[i]; // Create the component by calling its factory function with the specified arguments var component = Game.Components[componentDef.name].apply(null, componentDef.args); Game.EntityManager.addComponent(towerId, component); } // 7. Announce that a tower has been placed var event = Game.Events.TowerPlacedEvent(towerId, towerData.cost, transform.x, transform.y); Game.EventBus.publish(event); } }, TargetingSystem: { update: function update() { var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; // Find best target (closest to end of path) var bestTarget = -1; var bestPathIndex = -1; var enemies = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var j = 0; j < enemies.length; j++) { var enemyId = enemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var enemyMovement = Game.EntityManager.componentStores['MovementComponent'][enemyId]; // Check if enemy is within range var dx = enemyTransform.x - towerTransform.x; var dy = enemyTransform.y - towerTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= towerAttack.range) { // Check if this enemy is closer to the end if (enemyMovement.path_index > bestPathIndex) { bestTarget = enemyId; bestPathIndex = enemyMovement.path_index; } } } // Update tower's target towerAttack.target_id = bestTarget; } } }, CleanupSystem: { update: function update() { for (var i = 0; i < Game.EntityManager.entitiesToDestroy.length; i++) { var entityId = Game.EntityManager.entitiesToDestroy[i]; // Remove from active entities list var index = Game.EntityManager.activeEntities.indexOf(entityId); if (index > -1) { Game.EntityManager.activeEntities.splice(index, 1); } // Remove from all component stores for (var componentName in Game.EntityManager.componentStores) { if (Game.EntityManager.componentStores[componentName][entityId]) { delete Game.EntityManager.componentStores[componentName][entityId]; } } // Remove from render system's display objects if (Game.Systems.RenderSystem.displayObjects[entityId]) { Game.Systems.RenderSystem.displayObjects[entityId].destroy(); delete Game.Systems.RenderSystem.displayObjects[entityId]; } } // Important: Clear the list for the next frame if (Game.EntityManager.entitiesToDestroy.length > 0) { Game.EntityManager.entitiesToDestroy = []; } } }, WaveSpawnerSystem: { currentWaveIndex: -1, isSpawning: false, spawnTimer: 0, subWaveIndex: 0, spawnedInSubWave: 0, startNextWave: function startNextWave() { this.currentWaveIndex++; this.isSpawning = true; this.spawnTimer = 0; this.subWaveIndex = 0; this.spawnedInSubWave = 0; }, update: function update() { if (!this.isSpawning || this.currentWaveIndex >= Game.WaveData.length) { return; } var currentWave = Game.WaveData[this.currentWaveIndex]; if (!currentWave || this.subWaveIndex >= currentWave.sub_waves.length) { this.isSpawning = false; return; } var currentSubWave = currentWave.sub_waves[this.subWaveIndex]; // Handle start delay if (this.spawnedInSubWave === 0 && this.spawnTimer < currentSubWave.start_delay) { this.spawnTimer += 1 / 60; // Increment by frame time return; } // Handle spawn delay between enemies if (this.spawnedInSubWave > 0 && this.spawnTimer < currentSubWave.spawn_delay) { this.spawnTimer += 1 / 60; return; } // Spawn enemy if (this.spawnedInSubWave < currentSubWave.count) { var enemyId = Game.EntityManager.createEntity(); var transform = Game.Components.TransformComponent(0 * 64, 30 * 64); var movement = Game.Components.MovementComponent(2, 0); var render = Game.Components.RenderComponent('enemy', 1, 1.0, true); var health = Game.Components.HealthComponent(30, 30); // 30 current, 30 max var enemy = Game.Components.EnemyComponent(5, 1); Game.EntityManager.addComponent(enemyId, transform); Game.EntityManager.addComponent(enemyId, movement); Game.EntityManager.addComponent(enemyId, render); Game.EntityManager.addComponent(enemyId, health); // Add the health component Game.EntityManager.addComponent(enemyId, enemy); this.spawnedInSubWave++; this.spawnTimer = 0; Game.GameplayManager.enemiesSpawnedThisWave++; } else { // Move to next sub-wave this.subWaveIndex++; this.spawnedInSubWave = 0; this.spawnTimer = 0; } } }, CombatSystem: { update: function update() { var currentTime = LK.ticks / 60; // Convert ticks to seconds // --- Tower Firing Logic (UPDATED FOR DAMAGE BUFF) --- var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; if (towerAttack.target_id !== -1 && currentTime - towerAttack.last_attack_time >= 1.0 / towerAttack.attack_speed) { var targetTransform = Game.EntityManager.componentStores['TransformComponent'][towerAttack.target_id]; if (targetTransform) { // --- NEW: Calculate final damage including buffs --- var baseDamage = towerAttack.damage; var finalDamage = baseDamage; var buffComp = Game.EntityManager.componentStores['DamageBuffComponent'] ? Game.EntityManager.componentStores['DamageBuffComponent'][towerId] : null; if (buffComp) { finalDamage = baseDamage * (1 + buffComp.multiplier); } // --- END NEW --- var projectileId = Game.EntityManager.createEntity(); var projectileTransform = Game.Components.TransformComponent(towerTransform.x, towerTransform.y); var projectileRender = Game.Components.RenderComponent(towerAttack.projectile_id, 1, 1.0, true); // Use finalDamage when creating the projectile var projectileComponent = Game.Components.ProjectileComponent(towerAttack.target_id, undefined, finalDamage); projectileComponent.source_tower_id = towerId; Game.EntityManager.addComponent(projectileId, projectileTransform); Game.EntityManager.addComponent(projectileId, projectileRender); Game.EntityManager.addComponent(projectileId, projectileComponent); if (towerAttack.projectile_id === 'chemical_projectile') { var poisonImpactComp = Game.Components.PoisonOnImpactComponent(4, 5); Game.EntityManager.addComponent(projectileId, poisonImpactComp); } if (towerAttack.projectile_id === 'slow_projectile') { var slowImpactComp = Game.Components.SlowOnImpactComponent(0.4, 2); Game.EntityManager.addComponent(projectileId, slowImpactComp); } towerAttack.last_attack_time = currentTime; } } } // --- Projectile Movement and Impact Logic (No changes here) --- var projectiles = Game.EntityManager.getEntitiesWithComponents(['ProjectileComponent', 'TransformComponent']); for (var i = projectiles.length - 1; i >= 0; i--) { var projectileId = projectiles[i]; var projectileComp = Game.EntityManager.componentStores['ProjectileComponent'][projectileId]; var projectileTransform = Game.EntityManager.componentStores['TransformComponent'][projectileId]; var targetExists = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; if (!targetExists) { Game.EntityManager.destroyEntity(projectileId); continue; } var targetTransform = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; var dx = targetTransform.x - projectileTransform.x; var dy = targetTransform.y - projectileTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { var impactPoint = { x: targetTransform.x, y: targetTransform.y }; var sourceTowerAttack = Game.EntityManager.componentStores['AttackComponent'][projectileComp.source_tower_id]; if (sourceTowerAttack && sourceTowerAttack.splash_radius > 0) { var allEnemies = Game.EntityManager.getEntitiesWithComponents(['HealthComponent', 'TransformComponent']); for (var j = 0; j < allEnemies.length; j++) { var enemyId = allEnemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var splash_dx = enemyTransform.x - impactPoint.x; var splash_dy = enemyTransform.y - impactPoint.y; var splash_dist = Math.sqrt(splash_dx * splash_dx + splash_dy * splash_dy); if (splash_dist <= sourceTowerAttack.splash_radius) { this.dealDamage(enemyId, projectileComp.damage); } } } else { this.dealDamage(projectileComp.target_id, projectileComp.damage); } var targetHealth = Game.EntityManager.componentStores['HealthComponent'][projectileComp.target_id]; if (targetHealth) { var poisonImpactComp = Game.EntityManager.componentStores['PoisonOnImpactComponent'] ? Game.EntityManager.componentStores['PoisonOnImpactComponent'][projectileId] : null; if (poisonImpactComp) { var debuff = Game.Components.PoisonDebuffComponent(poisonImpactComp.damage_per_second, poisonImpactComp.duration, currentTime); Game.EntityManager.addComponent(projectileComp.target_id, debuff); } var slowImpactComp = Game.EntityManager.componentStores['SlowOnImpactComponent'] ? Game.EntityManager.componentStores['SlowOnImpactComponent'][projectileId] : null; if (slowImpactComp) { var slowDebuff = Game.Components.SlowDebuffComponent(slowImpactComp.slow_percentage, slowImpactComp.duration); Game.EntityManager.addComponent(projectileComp.target_id, slowDebuff); } } Game.EntityManager.destroyEntity(projectileId); } else { var moveX = dx / distance * projectileComp.speed; var moveY = dy / distance * projectileComp.speed; projectileTransform.x += moveX; projectileTransform.y += moveY; } } // --- Poison Debuff Processing Logic (No changes here) --- var poisonedEnemies = Game.EntityManager.getEntitiesWithComponents(['PoisonDebuffComponent']); for (var i = poisonedEnemies.length - 1; i >= 0; i--) { var enemyId = poisonedEnemies[i]; var debuff = Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; if (!debuff) { continue; } if (currentTime - debuff.last_tick_time >= 1.0) { this.dealDamage(enemyId, debuff.damage_per_second); debuff.last_tick_time = currentTime; debuff.duration_remaining -= 1.0; } if (debuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; } } }, dealDamage: function dealDamage(enemyId, damage) { var targetHealth = Game.EntityManager.componentStores['HealthComponent'][enemyId]; if (!targetHealth) { return; } targetHealth.current_hp -= damage; if (targetHealth.current_hp <= 0) { var enemyComp = Game.EntityManager.componentStores['EnemyComponent'][enemyId]; var goldValue = enemyComp ? enemyComp.gold_value : 0; Game.EventBus.publish(Game.Events.EnemyKilledEvent(enemyId, goldValue)); Game.EntityManager.destroyEntity(enemyId); } } }, AuraSystem: { update: function update() { // Step 1: Clean up all existing damage buffs from the previous frame. var buffedTowers = Game.EntityManager.getEntitiesWithComponents(['DamageBuffComponent']); for (var i = 0; i < buffedTowers.length; i++) { var towerId = buffedTowers[i]; delete Game.EntityManager.componentStores['DamageBuffComponent'][towerId]; } // Step 2: Find all aura-providing towers. var auraTowers = Game.EntityManager.getEntitiesWithComponents(['AuraComponent', 'TransformComponent']); if (auraTowers.length === 0) { return; // No auras to process. } // Step 3: Get all attack towers that could potentially be buffed. var attackTowers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); // Step 4: Iterate through each aura and apply its effect to nearby towers. for (var i = 0; i < auraTowers.length; i++) { var auraTowerId = auraTowers[i]; var auraComponent = Game.EntityManager.componentStores['AuraComponent'][auraTowerId]; var auraTransform = Game.EntityManager.componentStores['TransformComponent'][auraTowerId]; // Only process damage buff auras for now if (auraComponent.effect_type !== 'damage_buff') { continue; } for (var j = 0; j < attackTowers.length; j++) { var attackTowerId = attackTowers[j]; // An aura tower cannot buff itself. if (attackTowerId === auraTowerId) { continue; } var attackTransform = Game.EntityManager.componentStores['TransformComponent'][attackTowerId]; // Check distance var dx = auraTransform.x - attackTransform.x; var dy = auraTransform.y - attackTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= auraComponent.range) { // Apply the buff var buff = Game.Components.DamageBuffComponent(0.2, auraTowerId); // 20% damage bonus Game.EntityManager.addComponent(attackTowerId, buff); } } } } }, // --- NEW SYSTEM --- EconomySystem: { update: function update() {} } };
User prompt
Please replace the entire Game.TowerData object with the following updated version, which now includes the entry for 'GraveyardTower'. Game.TowerData = { 'ArrowTower': { cost: 50, sprite_id: 'arrow_tower', components: [{ name: 'TowerComponent', args: [50] }, // cost { name: 'AttackComponent', args: [10, 150, 1.0, 0, 'arrow_projectile'] }, // dmg, range, speed, last_attack_time, projectile_id { name: 'PowerConsumptionComponent', args: [2] } // drain_rate ] }, 'GeneratorTower': { cost: 75, sprite_id: 'generator_tower', components: [{ name: 'TowerComponent', args: [75] }, // cost { name: 'PowerProductionComponent', args: [5] } // production_rate ] }, 'CapacitorTower': { cost: 40, sprite_id: 'capacitor_tower', components: [{ name: 'TowerComponent', args: [40] }, // cost { name: 'PowerStorageComponent', args: [20] } // capacity ] }, 'RockLauncher': { cost: 125, sprite_id: 'rock_launcher_tower', components: [{ name: 'TowerComponent', args: [125] }, { name: 'AttackComponent', args: [25, 200, 0.5, 0, 'rock_projectile', -1, 50] }, // dmg, range, speed, last_attack_time, projectile_id, target_id, splash_radius { name: 'PowerConsumptionComponent', args: [10] }] }, 'ChemicalTower': { cost: 100, sprite_id: 'chemical_tower', components: [{ name: 'TowerComponent', args: [100] }, { name: 'AttackComponent', args: [5, 150, 0.8, 0, 'chemical_projectile', -1, 0] }, // dmg, range, speed, last_attack_time, projectile_id, target_id, splash_radius { name: 'PowerConsumptionComponent', args: [8] }] }, 'SlowTower': { cost: 80, sprite_id: 'slow_tower', components: [{ name: 'TowerComponent', args: [80] }, { name: 'AttackComponent', args: [2, 160, 1.0, 0, 'slow_projectile'] // Low damage, good range, standard speed }, { name: 'PowerConsumptionComponent', args: [4] }] }, 'DarkTower': { cost: 150, sprite_id: 'dark_tower', components: [{ name: 'TowerComponent', args: [150] }, { name: 'AuraComponent', args: [175, 'damage_buff'] // range, effect_type }, { name: 'PowerConsumptionComponent', args: [12] }] }, // --- NEW TOWER --- 'GraveyardTower': { cost: 200, sprite_id: 'graveyard_tower', components: [{ name: 'TowerComponent', args: [200] }, { name: 'GoldGenerationComponent', args: [1.5] // gold_per_second }, { name: 'PowerConsumptionComponent', args: [15] // High power cost for economic advantage }] } };
User prompt
Please replace the entire Game.Components object with the following version, which adds the GoldGenerationComponent to the list. Game.Components = { TransformComponent: function TransformComponent(x, y, rotation) { return { name: 'TransformComponent', x: x || 0, y: y || 0, rotation: rotation || 0 }; }, RenderComponent: function RenderComponent(sprite_id, layer, scale, is_visible) { return { name: 'RenderComponent', sprite_id: sprite_id || '', layer: layer || 0, scale: scale || 1.0, is_visible: is_visible !== undefined ? is_visible : true }; }, HealthComponent: function HealthComponent(current_hp, max_hp) { return { name: 'HealthComponent', current_hp: current_hp || 100, max_hp: max_hp || 100 }; }, AttackComponent: function AttackComponent(damage, range, attack_speed, last_attack_time, projectile_id, target_id, splash_radius) { return { name: 'AttackComponent', damage: damage || 10, range: range || 100, attack_speed: attack_speed || 1.0, last_attack_time: last_attack_time || 0, projectile_id: projectile_id || '', target_id: target_id || -1, splash_radius: splash_radius || 0 // Default to 0 (no splash) }; }, MovementComponent: function MovementComponent(speed, path_index) { return { name: 'MovementComponent', speed: speed || 50, path_index: path_index || 0 }; }, PowerProductionComponent: function PowerProductionComponent(rate) { return { name: 'PowerProductionComponent', production_rate: rate || 0 }; }, PowerConsumptionComponent: function PowerConsumptionComponent(rate) { return { name: 'PowerConsumptionComponent', drain_rate: rate || 0 }; }, TowerComponent: function TowerComponent(cost, sellValue) { return { name: 'TowerComponent', cost: cost || 50, sell_value: sellValue || 25 }; }, ProjectileComponent: function ProjectileComponent(target_id, speed, damage) { return { name: 'ProjectileComponent', target_id: target_id || -1, speed: speed || 8, // Let's keep the speed lower damage: damage || 10 }; }, EnemyComponent: function EnemyComponent(goldValue, livesCost) { return { name: 'EnemyComponent', gold_value: goldValue || 5, lives_cost: livesCost || 1 }; }, PowerStorageComponent: function PowerStorageComponent(capacity) { return { name: 'PowerStorageComponent', capacity: capacity || 0 }; }, PoisonOnImpactComponent: function PoisonOnImpactComponent(damage_per_second, duration) { return { name: 'PoisonOnImpactComponent', damage_per_second: damage_per_second || 0, duration: duration || 0 }; }, PoisonDebuffComponent: function PoisonDebuffComponent(damage_per_second, duration_remaining, last_tick_time) { return { name: 'PoisonDebuffComponent', damage_per_second: damage_per_second || 0, duration_remaining: duration_remaining || 0, last_tick_time: last_tick_time || 0 }; }, SlowOnImpactComponent: function SlowOnImpactComponent(slow_percentage, duration) { return { name: 'SlowOnImpactComponent', slow_percentage: slow_percentage || 0, duration: duration || 0 }; }, SlowDebuffComponent: function SlowDebuffComponent(slow_percentage, duration_remaining) { return { name: 'SlowDebuffComponent', slow_percentage: slow_percentage || 0, duration_remaining: duration_remaining || 0 }; }, AuraComponent: function AuraComponent(range, effect_type) { return { name: 'AuraComponent', range: range || 100, effect_type: effect_type || '' }; }, DamageBuffComponent: function DamageBuffComponent(multiplier, source_aura_id) { return { name: 'DamageBuffComponent', multiplier: multiplier || 0, source_aura_id: source_aura_id || -1 }; }, // --- NEW COMPONENT --- GoldGenerationComponent: function GoldGenerationComponent(gold_per_second) { return { name: 'GoldGenerationComponent', gold_per_second: gold_per_second || 0 }; } };
User prompt
Please execute the following command: LK.init.shape('graveyard_tower', {width:50, height:50, color:0x607d8b, shape:'box'})
User prompt
Please replace the entire // === SECTION: TEMP BUILD UI === block with the following updated version. This adds the new button and adjusts the vertical spacing of the entire list. // === SECTION: TEMP BUILD UI === var buildMode = 'ArrowTower'; // Keep this variable var arrowButton = new Text2('Build: Arrow', { size: 40, fill: 0x2ecc71 }); arrowButton.anchor.set(0, 0.5); LK.gui.left.addChild(arrowButton); arrowButton.x = 20; arrowButton.y = -350; // Adjusted y-position arrowButton.down = function () { buildMode = 'ArrowTower'; }; var generatorButton = new Text2('Build: Generator', { size: 40, fill: 0x3498db }); generatorButton.anchor.set(0, 0.5); LK.gui.left.addChild(generatorButton); generatorButton.x = 20; generatorButton.y = -280; // Adjusted y-position generatorButton.down = function () { buildMode = 'GeneratorTower'; }; var capacitorButton = new Text2('Build: Capacitor', { size: 40, fill: 0xf1c40f }); capacitorButton.anchor.set(0, 0.5); LK.gui.left.addChild(capacitorButton); capacitorButton.x = 20; capacitorButton.y = -210; // Adjusted y-position capacitorButton.down = function () { buildMode = 'CapacitorTower'; }; var rockLauncherButton = new Text2('Build: Rock Launcher', { size: 40, fill: 0x8D6E63 }); rockLauncherButton.anchor.set(0, 0.5); LK.gui.left.addChild(rockLauncherButton); rockLauncherButton.x = 20; rockLauncherButton.y = -140; // Adjusted y-position rockLauncherButton.down = function () { buildMode = 'RockLauncher'; }; var chemicalTowerButton = new Text2('Build: Chemical', { size: 40, fill: 0xb87333 }); chemicalTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(chemicalTowerButton); chemicalTowerButton.x = 20; chemicalTowerButton.y = -70; // Adjusted y-position chemicalTowerButton.down = function () { buildMode = 'ChemicalTower'; }; var slowTowerButton = new Text2('Build: Slow', { size: 40, fill: 0x5DADE2 }); slowTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(slowTowerButton); slowTowerButton.x = 20; slowTowerButton.y = 0; // Adjusted y-position slowTowerButton.down = function () { buildMode = 'SlowTower'; }; // --- NEW BUTTON --- var darkTowerButton = new Text2('Build: Dark', { size: 40, fill: 0x9b59b6 }); darkTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(darkTowerButton); darkTowerButton.x = 20; darkTowerButton.y = 70; // Adjusted y-position darkTowerButton.down = function () { buildMode = 'DarkTower'; };
User prompt
This change is small but crucial for making the Dark Tower functional. Please replace the entire Game.Systems.CombatSystem object with the following updated version. The modification is located in the tower firing logic. Game.Systems.CombatSystem = { update: function update() { var currentTime = LK.ticks / 60; // Convert ticks to seconds // --- Tower Firing Logic (UPDATED FOR DAMAGE BUFF) --- var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; if (towerAttack.target_id !== -1 && currentTime - towerAttack.last_attack_time >= 1.0 / towerAttack.attack_speed) { var targetTransform = Game.EntityManager.componentStores['TransformComponent'][towerAttack.target_id]; if (targetTransform) { // --- NEW: Calculate final damage including buffs --- var baseDamage = towerAttack.damage; var finalDamage = baseDamage; var buffComp = Game.EntityManager.componentStores['DamageBuffComponent'] ? Game.EntityManager.componentStores['DamageBuffComponent'][towerId] : null; if (buffComp) { finalDamage = baseDamage * (1 + buffComp.multiplier); } // --- END NEW --- var projectileId = Game.EntityManager.createEntity(); var projectileTransform = Game.Components.TransformComponent(towerTransform.x, towerTransform.y); var projectileRender = Game.Components.RenderComponent(towerAttack.projectile_id, 1, 1.0, true); // Use finalDamage when creating the projectile var projectileComponent = Game.Components.ProjectileComponent(towerAttack.target_id, undefined, finalDamage); projectileComponent.source_tower_id = towerId; Game.EntityManager.addComponent(projectileId, projectileTransform); Game.EntityManager.addComponent(projectileId, projectileRender); Game.EntityManager.addComponent(projectileId, projectileComponent); if (towerAttack.projectile_id === 'chemical_projectile') { var poisonImpactComp = Game.Components.PoisonOnImpactComponent(4, 5); Game.EntityManager.addComponent(projectileId, poisonImpactComp); } if (towerAttack.projectile_id === 'slow_projectile') { var slowImpactComp = Game.Components.SlowOnImpactComponent(0.4, 2); Game.EntityManager.addComponent(projectileId, slowImpactComp); } towerAttack.last_attack_time = currentTime; } } } // --- Projectile Movement and Impact Logic (No changes here) --- var projectiles = Game.EntityManager.getEntitiesWithComponents(['ProjectileComponent', 'TransformComponent']); for (var i = projectiles.length - 1; i >= 0; i--) { var projectileId = projectiles[i]; var projectileComp = Game.EntityManager.componentStores['ProjectileComponent'][projectileId]; var projectileTransform = Game.EntityManager.componentStores['TransformComponent'][projectileId]; var targetExists = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; if (!targetExists) { Game.EntityManager.destroyEntity(projectileId); continue; } var targetTransform = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; var dx = targetTransform.x - projectileTransform.x; var dy = targetTransform.y - projectileTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { var impactPoint = { x: targetTransform.x, y: targetTransform.y }; var sourceTowerAttack = Game.EntityManager.componentStores['AttackComponent'][projectileComp.source_tower_id]; if (sourceTowerAttack && sourceTowerAttack.splash_radius > 0) { var allEnemies = Game.EntityManager.getEntitiesWithComponents(['HealthComponent', 'TransformComponent']); for (var j = 0; j < allEnemies.length; j++) { var enemyId = allEnemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var splash_dx = enemyTransform.x - impactPoint.x; var splash_dy = enemyTransform.y - impactPoint.y; var splash_dist = Math.sqrt(splash_dx * splash_dx + splash_dy * splash_dy); if (splash_dist <= sourceTowerAttack.splash_radius) { this.dealDamage(enemyId, projectileComp.damage); } } } else { this.dealDamage(projectileComp.target_id, projectileComp.damage); } var targetHealth = Game.EntityManager.componentStores['HealthComponent'][projectileComp.target_id]; if (targetHealth) { var poisonImpactComp = Game.EntityManager.componentStores['PoisonOnImpactComponent'] ? Game.EntityManager.componentStores['PoisonOnImpactComponent'][projectileId] : null; if (poisonImpactComp) { var debuff = Game.Components.PoisonDebuffComponent(poisonImpactComp.damage_per_second, poisonImpactComp.duration, currentTime); Game.EntityManager.addComponent(projectileComp.target_id, debuff); } var slowImpactComp = Game.EntityManager.componentStores['SlowOnImpactComponent'] ? Game.EntityManager.componentStores['SlowOnImpactComponent'][projectileId] : null; if (slowImpactComp) { var slowDebuff = Game.Components.SlowDebuffComponent(slowImpactComp.slow_percentage, slowImpactComp.duration); Game.EntityManager.addComponent(projectileComp.target_id, slowDebuff); } } Game.EntityManager.destroyEntity(projectileId); } else { var moveX = dx / distance * projectileComp.speed; var moveY = dy / distance * projectileComp.speed; projectileTransform.x += moveX; projectileTransform.y += moveY; } } // --- Poison Debuff Processing Logic (No changes here) --- var poisonedEnemies = Game.EntityManager.getEntitiesWithComponents(['PoisonDebuffComponent']); for (var i = poisonedEnemies.length - 1; i >= 0; i--) { var enemyId = poisonedEnemies[i]; var debuff = Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; if (!debuff) { continue; } if (currentTime - debuff.last_tick_time >= 1.0) { this.dealDamage(enemyId, debuff.damage_per_second); debuff.last_tick_time = currentTime; debuff.duration_remaining -= 1.0; } if (debuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; } } }, dealDamage: function dealDamage(enemyId, damage) { var targetHealth = Game.EntityManager.componentStores['HealthComponent'][enemyId]; if (!targetHealth) { return; } targetHealth.current_hp -= damage; if (targetHealth.current_hp <= 0) { var enemyComp = Game.EntityManager.componentStores['EnemyComponent'][enemyId]; var goldValue = enemyComp ? enemyComp.gold_value : 0; Game.EventBus.publish(Game.Events.EnemyKilledEvent(enemyId, goldValue)); Game.EntityManager.destroyEntity(enemyId); } } };
/**** * Initialize Game ****/ var game = new LK.Game({ backgroundColor: 0x000000 }); /**** * Game Code ****/ // === SECTION: GLOBAL NAMESPACE === // === SECTION: ASSETS === var Game = {}; // === SECTION: GLOBAL MANAGERS === Game.ResourceManager = { gold: 100, lives: 20, powerProduction: 0, powerConsumption: 0, powerAvailable: 0, addGold: function addGold(amount) { this.gold += amount; }, spendGold: function spendGold(amount) { if (this.gold >= amount) { this.gold -= amount; return true; } ; return false; }, loseLife: function loseLife(amount) { this.lives -= amount; if (this.lives <= 0) { Game.EventBus.publish(Game.Events.GameOverEvent()); } }, updatePower: function updatePower() { // 1. Calculate total power production var totalProduction = 0; var productionEntities = Game.EntityManager.getEntitiesWithComponents(['PowerProductionComponent']); for (var i = 0; i < productionEntities.length; i++) { var entityId = productionEntities[i]; var powerComp = Game.EntityManager.componentStores['PowerProductionComponent'][entityId]; totalProduction += powerComp.production_rate; } this.powerProduction = totalProduction; // 2. Calculate total power consumption var totalConsumption = 0; var consumptionEntities = Game.EntityManager.getEntitiesWithComponents(['PowerConsumptionComponent']); for (var i = 0; i < consumptionEntities.length; i++) { var entityId = consumptionEntities[i]; var powerComp = Game.EntityManager.componentStores['PowerConsumptionComponent'][entityId]; totalConsumption += powerComp.drain_rate; } this.powerConsumption = totalConsumption; // 3. Calculate total storage capacity var totalCapacity = 0; var storageEntities = Game.EntityManager.getEntitiesWithComponents(['PowerStorageComponent']); for (var i = 0; i < storageEntities.length; i++) { var entityId = storageEntities[i]; var storageComp = Game.EntityManager.componentStores['PowerStorageComponent'][entityId]; totalCapacity += storageComp.capacity; } // 4. Evaluate power status and update available buffer var surplus = totalProduction - totalConsumption; if (surplus >= 0) { // We have enough or extra power this.powerAvailable += surplus; // Clamp the available power to the max capacity if (this.powerAvailable > totalCapacity) { this.powerAvailable = totalCapacity; } Game.EventBus.publish(Game.Events.PowerStatusChangedEvent('surplus')); } else { // We have a deficit, drain from the buffer this.powerAvailable += surplus; // surplus is negative, so this subtracts if (this.powerAvailable < 0) { // Buffer is empty and we still have a deficit this.powerAvailable = 0; Game.EventBus.publish(Game.Events.PowerStatusChangedEvent('deficit')); } else { // Deficit was covered by the buffer Game.EventBus.publish(Game.Events.PowerStatusChangedEvent('surplus')); } } }, onEnemyKilled: function onEnemyKilled(event) { this.addGold(event.gold_value); }, onWaveCompleted: function onWaveCompleted(event) { this.addGold(50); }, init: function init() { Game.EventBus.subscribe('EnemyKilled', this.onEnemyKilled.bind(this)); Game.EventBus.subscribe('WaveCompleted', this.onWaveCompleted.bind(this)); } }; Game.GridManager = { grid: [], CELL_STATES: { EMPTY: 0, PATH: 1, BLOCKED: 2, TOWER: 3 }, init: function init(width, height) { this.grid = []; for (var x = 0; x < width; x++) { this.grid[x] = []; for (var y = 0; y < height; y++) { this.grid[x][y] = this.CELL_STATES.EMPTY; } } }, isBuildable: function isBuildable(x, y) { if (x >= 0 && x < this.grid.length && y >= 0 && y < this.grid[x].length) { return this.grid[x][y] === this.CELL_STATES.EMPTY; } return false; } }; Game.PathManager = { currentPath: [], init: function init() { Game.EventBus.subscribe('TowerPlaced', this.onTowerPlaced.bind(this)); }, onTowerPlaced: function onTowerPlaced(event) { this.recalculatePath({ x: 0, y: 30 }, { x: 39, y: 30 }); }, recalculatePath: function recalculatePath(start, end) { this.currentPath = this.findPath(start, end); if (this.currentPath) { for (var i = 0; i < this.currentPath.length; i++) { this.currentPath[i].x = this.currentPath[i].x * 64; this.currentPath[i].y = this.currentPath[i].y * 64; } } }, findPath: function findPath(start, end) { var openSet = []; var closedSet = []; var grid = Game.GridManager.grid; var gridWidth = grid.length; var gridHeight = grid[0].length; function heuristic(a, b) { return Math.abs(a.x - b.x) + Math.abs(a.y - b.y); } function getNeighbors(node) { var neighbors = []; var directions = [{ x: -1, y: 0, cost: 1 }, { x: 1, y: 0, cost: 1 }, { x: 0, y: -1, cost: 1 }, { x: 0, y: 1, cost: 1 }, { x: -1, y: -1, cost: 1.4 }, { x: 1, y: -1, cost: 1.4 }, { x: -1, y: 1, cost: 1.4 }, { x: 1, y: 1, cost: 1.4 }]; for (var i = 0; i < directions.length; i++) { var dir = directions[i]; var newX = node.x + dir.x; var newY = node.y + dir.y; if (newX >= 0 && newX < gridWidth && newY >= 0 && newY < gridHeight) { var cellState = grid[newX][newY]; if (cellState !== Game.GridManager.CELL_STATES.BLOCKED && cellState !== Game.GridManager.CELL_STATES.TOWER) { neighbors.push({ x: newX, y: newY, cost: dir.cost }); } } } return neighbors; } function findNodeInArray(array, node) { for (var i = 0; i < array.length; i++) { if (array[i].x === node.x && array[i].y === node.y) { return array[i]; } } return null; } function reconstructPath(cameFrom, current) { var path = []; while (current) { path.unshift({ x: current.x, y: current.y }); current = cameFrom[current.x + '_' + current.y]; } return path; } var startNode = { x: start.x, y: start.y, g: 0, h: heuristic(start, end), f: 0 }; startNode.f = startNode.g + startNode.h; openSet.push(startNode); var cameFrom = {}; while (openSet.length > 0) { var current = openSet[0]; var currentIndex = 0; for (var i = 1; i < openSet.length; i++) { if (openSet[i].f < current.f) { current = openSet[i]; currentIndex = i; } } openSet.splice(currentIndex, 1); closedSet.push(current); if (current.x === end.x && current.y === end.y) { return reconstructPath(cameFrom, current); } var neighbors = getNeighbors(current); for (var i = 0; i < neighbors.length; i++) { var neighbor = neighbors[i]; if (findNodeInArray(closedSet, neighbor)) { continue; } var tentativeG = current.g + neighbor.cost; var existingNode = findNodeInArray(openSet, neighbor); if (!existingNode) { var newNode = { x: neighbor.x, y: neighbor.y, g: tentativeG, h: heuristic(neighbor, end), f: 0 }; newNode.f = newNode.g + newNode.h; openSet.push(newNode); cameFrom[neighbor.x + '_' + neighbor.y] = current; } else if (tentativeG < existingNode.g) { existingNode.g = tentativeG; existingNode.f = existingNode.g + existingNode.h; cameFrom[neighbor.x + '_' + neighbor.y] = current; } } } return null; // No path found } }; Game.GameplayManager = { enemiesSpawnedThisWave: 0, enemiesKilledThisWave: 0, enemiesLeakedThisWave: 0, init: function init() { Game.EventBus.subscribe('EnemyKilled', this.onEnemyKilled.bind(this)); Game.EventBus.subscribe('EnemyReachedEnd', this.onEnemyReachedEnd.bind(this)); }, onEnemyKilled: function onEnemyKilled(event) { this.enemiesKilledThisWave++; if (this.enemiesKilledThisWave + this.enemiesLeakedThisWave === this.enemiesSpawnedThisWave) { Game.EventBus.publish(Game.Events.WaveCompletedEvent(Game.Systems.WaveSpawnerSystem.currentWaveIndex + 1)); this.enemiesSpawnedThisWave = 0; this.enemiesKilledThisWave = 0; this.enemiesLeakedThisWave = 0; startWaveButton.visible = true; } }, onEnemyReachedEnd: function onEnemyReachedEnd(event) { this.enemiesLeakedThisWave++; Game.ResourceManager.loseLife(1); Game.EntityManager.destroyEntity(event.enemy_id); if (this.enemiesKilledThisWave + this.enemiesLeakedThisWave === this.enemiesSpawnedThisWave) { Game.EventBus.publish(Game.Events.WaveCompletedEvent(Game.Systems.WaveSpawnerSystem.currentWaveIndex + 1)); this.enemiesSpawnedThisWave = 0; this.enemiesKilledThisWave = 0; this.enemiesLeakedThisWave = 0; startWaveButton.visible = true; } } }; Game.GameManager = { isGameOver: false, init: function init() { Game.EventBus.subscribe('GameOver', this.onGameOver.bind(this)); }, onGameOver: function onGameOver(event) { this.isGameOver = true; LK.showGameOver(); } }; Game.Debug = { logText: null, init: function init() { this.logText = new Text2('Debug Log Initialized', { size: 40, fill: 0x00FF00 }); // Green text this.logText.anchor.set(0, 1); // Anchor to bottom-left LK.gui.bottomLeft.addChild(this.logText); this.logText.x = 20; this.logText.y = -20; }, log: function log(message) { if (this.logText) { this.logText.setText(String(message)); } } }; // === SECTION: ENTITY-COMPONENT-SYSTEM SCAFFOLDING === Game.EntityManager = { nextEntityId: 1, activeEntities: [], componentStores: {}, entitiesToDestroy: [], createEntity: function createEntity() { var entityId = this.nextEntityId++; this.activeEntities.push(entityId); return entityId; }, addComponent: function addComponent(entityId, component) { if (!this.componentStores[component.name]) { this.componentStores[component.name] = {}; } this.componentStores[component.name][entityId] = component; }, getEntitiesWithComponents: function getEntitiesWithComponents(componentNames) { var result = []; for (var i = 0; i < this.activeEntities.length; i++) { var entityId = this.activeEntities[i]; var hasAllComponents = true; for (var j = 0; j < componentNames.length; j++) { var componentName = componentNames[j]; if (!this.componentStores[componentName] || !this.componentStores[componentName][entityId]) { hasAllComponents = false; break; } } if (hasAllComponents) { result.push(entityId); } } return result; }, destroyEntity: function destroyEntity(entityId) { // Avoid adding duplicates if (this.entitiesToDestroy.indexOf(entityId) === -1) { this.entitiesToDestroy.push(entityId); } } }; Game.Components = { TransformComponent: function TransformComponent(x, y, rotation) { return { name: 'TransformComponent', x: x || 0, y: y || 0, rotation: rotation || 0 }; }, RenderComponent: function RenderComponent(sprite_id, layer, scale, is_visible) { return { name: 'RenderComponent', sprite_id: sprite_id || '', layer: layer || 0, scale: scale || 1.0, is_visible: is_visible !== undefined ? is_visible : true }; }, HealthComponent: function HealthComponent(current_hp, max_hp) { return { name: 'HealthComponent', current_hp: current_hp || 100, max_hp: max_hp || 100 }; }, AttackComponent: function AttackComponent(damage, range, attack_speed, last_attack_time, projectile_id, target_id, splash_radius) { return { name: 'AttackComponent', damage: damage || 10, range: range || 100, attack_speed: attack_speed || 1.0, last_attack_time: last_attack_time || 0, projectile_id: projectile_id || '', target_id: target_id || -1, splash_radius: splash_radius || 0 // Default to 0 (no splash) }; }, MovementComponent: function MovementComponent(speed, path_index) { return { name: 'MovementComponent', speed: speed || 50, path_index: path_index || 0 }; }, PowerProductionComponent: function PowerProductionComponent(rate) { return { name: 'PowerProductionComponent', production_rate: rate || 0 }; }, PowerConsumptionComponent: function PowerConsumptionComponent(rate) { return { name: 'PowerConsumptionComponent', drain_rate: rate || 0 }; }, TowerComponent: function TowerComponent(cost, sellValue) { return { name: 'TowerComponent', cost: cost || 50, sell_value: sellValue || 25, upgrade_level: 1, upgrade_path_A_level: 0, upgrade_path_B_level: 0 }; }, ProjectileComponent: function ProjectileComponent(target_id, speed, damage) { return { name: 'ProjectileComponent', target_id: target_id || -1, speed: speed || 8, // Let's keep the speed lower damage: damage || 10 }; }, EnemyComponent: function EnemyComponent(goldValue, livesCost) { return { name: 'EnemyComponent', gold_value: goldValue || 5, lives_cost: livesCost || 1 }; }, PowerStorageComponent: function PowerStorageComponent(capacity) { return { name: 'PowerStorageComponent', capacity: capacity || 0 }; }, PoisonOnImpactComponent: function PoisonOnImpactComponent(damage_per_second, duration) { return { name: 'PoisonOnImpactComponent', damage_per_second: damage_per_second || 0, duration: duration || 0 }; }, PoisonDebuffComponent: function PoisonDebuffComponent(damage_per_second, duration_remaining, last_tick_time) { return { name: 'PoisonDebuffComponent', damage_per_second: damage_per_second || 0, duration_remaining: duration_remaining || 0, last_tick_time: last_tick_time || 0 }; }, SlowOnImpactComponent: function SlowOnImpactComponent(slow_percentage, duration) { return { name: 'SlowOnImpactComponent', slow_percentage: slow_percentage || 0, duration: duration || 0 }; }, SlowDebuffComponent: function SlowDebuffComponent(slow_percentage, duration_remaining) { return { name: 'SlowDebuffComponent', slow_percentage: slow_percentage || 0, duration_remaining: duration_remaining || 0 }; }, AuraComponent: function AuraComponent(range, effect_type) { return { name: 'AuraComponent', range: range || 100, effect_type: effect_type || '' }; }, DamageBuffComponent: function DamageBuffComponent(multiplier, source_aura_id) { return { name: 'DamageBuffComponent', multiplier: multiplier || 0, source_aura_id: source_aura_id || -1 }; }, // --- NEW COMPONENT --- GoldGenerationComponent: function GoldGenerationComponent(gold_per_second) { return { name: 'GoldGenerationComponent', gold_per_second: gold_per_second || 0 }; } }; Game.Systems = { MovementSystem: { update: function update() { var movableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var i = 0; i < movableEntities.length; i++) { var entityId = movableEntities[i]; var transformComponent = Game.EntityManager.componentStores['TransformComponent'][entityId]; var movementComponent = Game.EntityManager.componentStores['MovementComponent'][entityId]; // --- START OF NEW SLOW LOGIC --- // 1. Get base speed and check for slow debuff var currentSpeed = movementComponent.speed; var slowDebuff = Game.EntityManager.componentStores['SlowDebuffComponent'] ? Game.EntityManager.componentStores['SlowDebuffComponent'][entityId] : null; if (slowDebuff) { // 2. Apply the speed reduction currentSpeed = movementComponent.speed * (1 - slowDebuff.slow_percentage); // 3. Update the debuff's duration and remove it if it expires slowDebuff.duration_remaining -= 1.0 / 60; // Reduce duration by frame time (1/60th of a second) if (slowDebuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['SlowDebuffComponent'][entityId]; } } // --- END OF NEW SLOW LOGIC --- if (Game.PathManager.currentPath && Game.PathManager.currentPath.length > 0) { var targetWaypoint = Game.PathManager.currentPath[movementComponent.path_index]; if (targetWaypoint) { var dx = targetWaypoint.x - transformComponent.x; var dy = targetWaypoint.y - transformComponent.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance > 1) { // 4. Use the calculated currentSpeed for movement var moveX = dx / distance * currentSpeed; var moveY = dy / distance * currentSpeed; transformComponent.x += moveX; transformComponent.y += moveY; } else { movementComponent.path_index++; if (movementComponent.path_index >= Game.PathManager.currentPath.length) { Game.EventBus.publish(Game.Events.EnemyReachedEndEvent(entityId)); } } } } } } }, RenderSystem: { displayObjects: {}, update: function update() { var renderableEntities = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'RenderComponent']); for (var i = 0; i < renderableEntities.length; i++) { var entityId = renderableEntities[i]; var transform = Game.EntityManager.componentStores['TransformComponent'][entityId]; var render = Game.EntityManager.componentStores['RenderComponent'][entityId]; // Get the visual sprite, or create it if it doesn't exist var sprite = this.displayObjects[entityId]; if (!sprite) { sprite = game.attachAsset(render.sprite_id, {}); this.displayObjects[entityId] = sprite; } // Sync the sprite's properties with the component data sprite.x = transform.x; sprite.y = transform.y; sprite.visible = render.is_visible; } } }, TowerBuildSystem: { tryBuildAt: function tryBuildAt(gridX, gridY, towerType) { // 0. Look up tower data var towerData = Game.TowerData[towerType]; if (!towerData) { return; // Exit if tower type is invalid } // 1. Check if the location is buildable if (!Game.GridManager.isBuildable(gridX, gridY)) { return; // Exit if not buildable } // 2. Check if the player can afford the tower if (!Game.ResourceManager.spendGold(towerData.cost)) { return; // Exit if not enough gold } // 3. If checks pass, create the tower entity var towerId = Game.EntityManager.createEntity(); // 4. Mark the grid cell as occupied by a tower Game.GridManager.grid[gridX][gridY] = Game.GridManager.CELL_STATES.TOWER; // 5. Add universal components that all towers have var transform = Game.Components.TransformComponent(gridX * 64, gridY * 64); Game.EntityManager.addComponent(towerId, transform); var render = Game.Components.RenderComponent(towerData.sprite_id, 2, 1.0, true); Game.EntityManager.addComponent(towerId, render); // 6. Add specific components from the tower's data definition for (var i = 0; i < towerData.components.length; i++) { var componentDef = towerData.components[i]; // Create the component by calling its factory function with the specified arguments var component = Game.Components[componentDef.name].apply(null, componentDef.args); Game.EntityManager.addComponent(towerId, component); } // 7. Announce that a tower has been placed var event = Game.Events.TowerPlacedEvent(towerId, towerData.cost, transform.x, transform.y); Game.EventBus.publish(event); } }, TargetingSystem: { update: function update() { var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; // Find best target (closest to end of path) var bestTarget = -1; var bestPathIndex = -1; var enemies = Game.EntityManager.getEntitiesWithComponents(['TransformComponent', 'MovementComponent']); for (var j = 0; j < enemies.length; j++) { var enemyId = enemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var enemyMovement = Game.EntityManager.componentStores['MovementComponent'][enemyId]; // Check if enemy is within range var dx = enemyTransform.x - towerTransform.x; var dy = enemyTransform.y - towerTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= towerAttack.range) { // Check if this enemy is closer to the end if (enemyMovement.path_index > bestPathIndex) { bestTarget = enemyId; bestPathIndex = enemyMovement.path_index; } } } // Update tower's target towerAttack.target_id = bestTarget; } } }, CleanupSystem: { update: function update() { for (var i = 0; i < Game.EntityManager.entitiesToDestroy.length; i++) { var entityId = Game.EntityManager.entitiesToDestroy[i]; // Remove from active entities list var index = Game.EntityManager.activeEntities.indexOf(entityId); if (index > -1) { Game.EntityManager.activeEntities.splice(index, 1); } // Remove from all component stores for (var componentName in Game.EntityManager.componentStores) { if (Game.EntityManager.componentStores[componentName][entityId]) { delete Game.EntityManager.componentStores[componentName][entityId]; } } // Remove from render system's display objects if (Game.Systems.RenderSystem.displayObjects[entityId]) { Game.Systems.RenderSystem.displayObjects[entityId].destroy(); delete Game.Systems.RenderSystem.displayObjects[entityId]; } } // Important: Clear the list for the next frame if (Game.EntityManager.entitiesToDestroy.length > 0) { Game.EntityManager.entitiesToDestroy = []; } } }, WaveSpawnerSystem: { currentWaveIndex: -1, isSpawning: false, spawnTimer: 0, subWaveIndex: 0, spawnedInSubWave: 0, startNextWave: function startNextWave() { this.currentWaveIndex++; this.isSpawning = true; this.spawnTimer = 0; this.subWaveIndex = 0; this.spawnedInSubWave = 0; }, update: function update() { if (!this.isSpawning || this.currentWaveIndex >= Game.WaveData.length) { return; } var currentWave = Game.WaveData[this.currentWaveIndex]; if (!currentWave || this.subWaveIndex >= currentWave.sub_waves.length) { this.isSpawning = false; return; } var currentSubWave = currentWave.sub_waves[this.subWaveIndex]; // Handle start delay if (this.spawnedInSubWave === 0 && this.spawnTimer < currentSubWave.start_delay) { this.spawnTimer += 1 / 60; // Increment by frame time return; } // Handle spawn delay between enemies if (this.spawnedInSubWave > 0 && this.spawnTimer < currentSubWave.spawn_delay) { this.spawnTimer += 1 / 60; return; } // Spawn enemy if (this.spawnedInSubWave < currentSubWave.count) { var enemyId = Game.EntityManager.createEntity(); var transform = Game.Components.TransformComponent(0 * 64, 30 * 64); var movement = Game.Components.MovementComponent(2, 0); var render = Game.Components.RenderComponent('enemy', 1, 1.0, true); var health = Game.Components.HealthComponent(30, 30); // 30 current, 30 max var enemy = Game.Components.EnemyComponent(5, 1); Game.EntityManager.addComponent(enemyId, transform); Game.EntityManager.addComponent(enemyId, movement); Game.EntityManager.addComponent(enemyId, render); Game.EntityManager.addComponent(enemyId, health); // Add the health component Game.EntityManager.addComponent(enemyId, enemy); this.spawnedInSubWave++; this.spawnTimer = 0; Game.GameplayManager.enemiesSpawnedThisWave++; } else { // Move to next sub-wave this.subWaveIndex++; this.spawnedInSubWave = 0; this.spawnTimer = 0; } } }, CombatSystem: { update: function update() { var currentTime = LK.ticks / 60; // Convert ticks to seconds // --- Tower Firing Logic (UPDATED FOR DAMAGE BUFF) --- var towers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); for (var i = 0; i < towers.length; i++) { var towerId = towers[i]; var towerAttack = Game.EntityManager.componentStores['AttackComponent'][towerId]; var towerTransform = Game.EntityManager.componentStores['TransformComponent'][towerId]; if (towerAttack.target_id !== -1 && currentTime - towerAttack.last_attack_time >= 1.0 / towerAttack.attack_speed) { var targetTransform = Game.EntityManager.componentStores['TransformComponent'][towerAttack.target_id]; if (targetTransform) { // --- NEW: Calculate final damage including buffs --- var baseDamage = towerAttack.damage; var finalDamage = baseDamage; var buffComp = Game.EntityManager.componentStores['DamageBuffComponent'] ? Game.EntityManager.componentStores['DamageBuffComponent'][towerId] : null; if (buffComp) { finalDamage = baseDamage * (1 + buffComp.multiplier); } // --- END NEW --- var projectileId = Game.EntityManager.createEntity(); var projectileTransform = Game.Components.TransformComponent(towerTransform.x, towerTransform.y); var projectileRender = Game.Components.RenderComponent(towerAttack.projectile_id, 1, 1.0, true); // Use finalDamage when creating the projectile var projectileComponent = Game.Components.ProjectileComponent(towerAttack.target_id, undefined, finalDamage); projectileComponent.source_tower_id = towerId; Game.EntityManager.addComponent(projectileId, projectileTransform); Game.EntityManager.addComponent(projectileId, projectileRender); Game.EntityManager.addComponent(projectileId, projectileComponent); if (towerAttack.projectile_id === 'chemical_projectile') { var poisonImpactComp = Game.Components.PoisonOnImpactComponent(4, 5); Game.EntityManager.addComponent(projectileId, poisonImpactComp); } if (towerAttack.projectile_id === 'slow_projectile') { var slowImpactComp = Game.Components.SlowOnImpactComponent(0.4, 2); Game.EntityManager.addComponent(projectileId, slowImpactComp); } towerAttack.last_attack_time = currentTime; } } } // --- Projectile Movement and Impact Logic (No changes here) --- var projectiles = Game.EntityManager.getEntitiesWithComponents(['ProjectileComponent', 'TransformComponent']); for (var i = projectiles.length - 1; i >= 0; i--) { var projectileId = projectiles[i]; var projectileComp = Game.EntityManager.componentStores['ProjectileComponent'][projectileId]; var projectileTransform = Game.EntityManager.componentStores['TransformComponent'][projectileId]; var targetExists = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; if (!targetExists) { Game.EntityManager.destroyEntity(projectileId); continue; } var targetTransform = Game.EntityManager.componentStores['TransformComponent'][projectileComp.target_id]; var dx = targetTransform.x - projectileTransform.x; var dy = targetTransform.y - projectileTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < 10) { var impactPoint = { x: targetTransform.x, y: targetTransform.y }; var sourceTowerAttack = Game.EntityManager.componentStores['AttackComponent'][projectileComp.source_tower_id]; if (sourceTowerAttack && sourceTowerAttack.splash_radius > 0) { var allEnemies = Game.EntityManager.getEntitiesWithComponents(['HealthComponent', 'TransformComponent']); for (var j = 0; j < allEnemies.length; j++) { var enemyId = allEnemies[j]; var enemyTransform = Game.EntityManager.componentStores['TransformComponent'][enemyId]; var splash_dx = enemyTransform.x - impactPoint.x; var splash_dy = enemyTransform.y - impactPoint.y; var splash_dist = Math.sqrt(splash_dx * splash_dx + splash_dy * splash_dy); if (splash_dist <= sourceTowerAttack.splash_radius) { this.dealDamage(enemyId, projectileComp.damage); } } } else { this.dealDamage(projectileComp.target_id, projectileComp.damage); } var targetHealth = Game.EntityManager.componentStores['HealthComponent'][projectileComp.target_id]; if (targetHealth) { var poisonImpactComp = Game.EntityManager.componentStores['PoisonOnImpactComponent'] ? Game.EntityManager.componentStores['PoisonOnImpactComponent'][projectileId] : null; if (poisonImpactComp) { var debuff = Game.Components.PoisonDebuffComponent(poisonImpactComp.damage_per_second, poisonImpactComp.duration, currentTime); Game.EntityManager.addComponent(projectileComp.target_id, debuff); } var slowImpactComp = Game.EntityManager.componentStores['SlowOnImpactComponent'] ? Game.EntityManager.componentStores['SlowOnImpactComponent'][projectileId] : null; if (slowImpactComp) { var slowDebuff = Game.Components.SlowDebuffComponent(slowImpactComp.slow_percentage, slowImpactComp.duration); Game.EntityManager.addComponent(projectileComp.target_id, slowDebuff); } } Game.EntityManager.destroyEntity(projectileId); } else { var moveX = dx / distance * projectileComp.speed; var moveY = dy / distance * projectileComp.speed; projectileTransform.x += moveX; projectileTransform.y += moveY; } } // --- Poison Debuff Processing Logic (No changes here) --- var poisonedEnemies = Game.EntityManager.getEntitiesWithComponents(['PoisonDebuffComponent']); for (var i = poisonedEnemies.length - 1; i >= 0; i--) { var enemyId = poisonedEnemies[i]; var debuff = Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; if (!debuff) { continue; } if (currentTime - debuff.last_tick_time >= 1.0) { this.dealDamage(enemyId, debuff.damage_per_second); debuff.last_tick_time = currentTime; debuff.duration_remaining -= 1.0; } if (debuff.duration_remaining <= 0) { delete Game.EntityManager.componentStores['PoisonDebuffComponent'][enemyId]; } } }, dealDamage: function dealDamage(enemyId, damage) { var targetHealth = Game.EntityManager.componentStores['HealthComponent'][enemyId]; if (!targetHealth) { return; } targetHealth.current_hp -= damage; if (targetHealth.current_hp <= 0) { var enemyComp = Game.EntityManager.componentStores['EnemyComponent'][enemyId]; var goldValue = enemyComp ? enemyComp.gold_value : 0; Game.EventBus.publish(Game.Events.EnemyKilledEvent(enemyId, goldValue)); Game.EntityManager.destroyEntity(enemyId); } } }, AuraSystem: { update: function update() { // Step 1: Clean up all existing damage buffs from the previous frame. var buffedTowers = Game.EntityManager.getEntitiesWithComponents(['DamageBuffComponent']); for (var i = 0; i < buffedTowers.length; i++) { var towerId = buffedTowers[i]; delete Game.EntityManager.componentStores['DamageBuffComponent'][towerId]; } // Step 2: Find all aura-providing towers. var auraTowers = Game.EntityManager.getEntitiesWithComponents(['AuraComponent', 'TransformComponent']); if (auraTowers.length === 0) { return; // No auras to process. } // Step 3: Get all attack towers that could potentially be buffed. var attackTowers = Game.EntityManager.getEntitiesWithComponents(['AttackComponent', 'TransformComponent']); // Step 4: Iterate through each aura and apply its effect to nearby towers. for (var i = 0; i < auraTowers.length; i++) { var auraTowerId = auraTowers[i]; var auraComponent = Game.EntityManager.componentStores['AuraComponent'][auraTowerId]; var auraTransform = Game.EntityManager.componentStores['TransformComponent'][auraTowerId]; // Only process damage buff auras for now if (auraComponent.effect_type !== 'damage_buff') { continue; } for (var j = 0; j < attackTowers.length; j++) { var attackTowerId = attackTowers[j]; // An aura tower cannot buff itself. if (attackTowerId === auraTowerId) { continue; } var attackTransform = Game.EntityManager.componentStores['TransformComponent'][attackTowerId]; // Check distance var dx = auraTransform.x - attackTransform.x; var dy = auraTransform.y - attackTransform.y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance <= auraComponent.range) { // Apply the buff var buff = Game.Components.DamageBuffComponent(0.2, auraTowerId); // 20% damage bonus Game.EntityManager.addComponent(attackTowerId, buff); } } } } }, EconomySystem: { update: function update() { // Find all towers that generate gold var goldGenerators = Game.EntityManager.getEntitiesWithComponents(['GoldGenerationComponent']); // Sum up the gold from all of them for (var i = 0; i < goldGenerators.length; i++) { var entityId = goldGenerators[i]; var goldGenComp = Game.EntityManager.componentStores['GoldGenerationComponent'][entityId]; if (goldGenComp) { // Add the gold to the player's resources. // Since this runs once per second, we add the gold_per_second value directly. Game.ResourceManager.addGold(goldGenComp.gold_per_second); } } } } }; // === SECTION: GAME DATA === Game.WaveData = [ // Wave 1 { wave_number: 1, sub_waves: [{ enemy_type: 'enemy', count: 10, spawn_delay: 1.0, start_delay: 0 }] }, // Wave 2 { wave_number: 2, sub_waves: [{ enemy_type: 'enemy', count: 15, spawn_delay: 0.8, start_delay: 0 }] }, // Wave 3 { wave_number: 3, sub_waves: [{ enemy_type: 'enemy', count: 20, spawn_delay: 0.5, start_delay: 0 }] }]; // === SECTION: TOWER DATA === Game.TowerData = { 'ArrowTower': { cost: 50, sprite_id: 'arrow_tower', components: [{ name: 'TowerComponent', args: [50] }, // cost { name: 'AttackComponent', args: [10, 150, 1.0, 0, 'arrow_projectile'] }, // dmg, range, speed, last_attack_time, projectile_id { name: 'PowerConsumptionComponent', args: [2] } // drain_rate ] }, 'GeneratorTower': { cost: 75, sprite_id: 'generator_tower', components: [{ name: 'TowerComponent', args: [75] }, // cost { name: 'PowerProductionComponent', args: [5] } // production_rate ] }, 'CapacitorTower': { cost: 40, sprite_id: 'capacitor_tower', components: [{ name: 'TowerComponent', args: [40] }, // cost { name: 'PowerStorageComponent', args: [20] } // capacity ] }, 'RockLauncher': { cost: 125, sprite_id: 'rock_launcher_tower', components: [{ name: 'TowerComponent', args: [125] }, { name: 'AttackComponent', args: [25, 200, 0.5, 0, 'rock_projectile', -1, 50] }, // dmg, range, speed, last_attack_time, projectile_id, target_id, splash_radius { name: 'PowerConsumptionComponent', args: [10] }] }, 'ChemicalTower': { cost: 100, sprite_id: 'chemical_tower', components: [{ name: 'TowerComponent', args: [100] }, { name: 'AttackComponent', args: [5, 150, 0.8, 0, 'chemical_projectile', -1, 0] }, // dmg, range, speed, last_attack_time, projectile_id, target_id, splash_radius { name: 'PowerConsumptionComponent', args: [8] }] }, 'SlowTower': { cost: 80, sprite_id: 'slow_tower', components: [{ name: 'TowerComponent', args: [80] }, { name: 'AttackComponent', args: [2, 160, 1.0, 0, 'slow_projectile'] // Low damage, good range, standard speed }, { name: 'PowerConsumptionComponent', args: [4] }] }, 'DarkTower': { cost: 150, sprite_id: 'dark_tower', components: [{ name: 'TowerComponent', args: [150] }, { name: 'AuraComponent', args: [175, 'damage_buff'] // range, effect_type }, { name: 'PowerConsumptionComponent', args: [12] }] }, // --- NEW TOWER --- 'GraveyardTower': { cost: 200, sprite_id: 'graveyard_tower', components: [{ name: 'TowerComponent', args: [200] }, { name: 'GoldGenerationComponent', args: [1.5] // gold_per_second }, { name: 'PowerConsumptionComponent', args: [15] // High power cost for economic advantage }] } }; // === SECTION: UPGRADE DATA === Game.UpgradeData = { 'ArrowTower': { 'PathA': [{ cost: 60, effects: { AttackComponent: { damage: 5 } } }, // Level 1: +5 Dmg { cost: 110, effects: { AttackComponent: { damage: 10 } } } // Level 2: +10 Dmg ], 'PathB': [{ cost: 75, effects: { AttackComponent: { attack_speed: 0.2 } } }, // Level 1: +0.2 AS { cost: 125, effects: { AttackComponent: { attack_speed: 0.3 } } } // Level 2: +0.3 AS ] } // Other tower upgrades will be added here later }; // === SECTION: GRID DRAWING === function drawGrid() { // Draw the grid cells for (var x = 0; x < Game.GridManager.grid.length; x++) { for (var y = 0; y < Game.GridManager.grid[x].length; y++) { var cellSprite = game.attachAsset('grid_cell', {}); cellSprite.x = x * 64; cellSprite.y = y * 64; cellSprite.anchor.set(0, 0); // Anchor to top-left // Add these new lines to make cells interactive cellSprite.gridX = x; // Store grid coordinates on the sprite cellSprite.gridY = y; cellSprite.down = function () { Game.Systems.TowerBuildSystem.tryBuildAt(this.gridX, this.gridY, buildMode); }; } } // Highlight the path cells if (Game.PathManager.currentPath) { for (var i = 0; i < Game.PathManager.currentPath.length; i++) { var pathNode = Game.PathManager.currentPath[i]; var pathSprite = game.attachAsset('path_cell', {}); // Note: PathManager coordinates are already in pixels pathSprite.x = pathNode.x; pathSprite.y = pathNode.y; pathSprite.anchor.set(0, 0); } } } // === SECTION: GLOBAL EVENT BUS === Game.EventBus = { listeners: {}, subscribe: function subscribe(eventType, listener) { if (!this.listeners[eventType]) { this.listeners[eventType] = []; } this.listeners[eventType].push(listener); }, unsubscribe: function unsubscribe(eventType, listener) { if (this.listeners[eventType]) { var index = this.listeners[eventType].indexOf(listener); if (index !== -1) { this.listeners[eventType].splice(index, 1); } } }, publish: function publish(event) { if (this.listeners[event.type]) { for (var i = 0; i < this.listeners[event.type].length; i++) { this.listeners[event.type][i](event); } } } }; Game.Events = { EnemyReachedEndEvent: function EnemyReachedEndEvent(enemy_id) { return { type: 'EnemyReachedEnd', enemy_id: enemy_id }; }, EnemyKilledEvent: function EnemyKilledEvent(enemy_id, gold_value) { return { type: 'EnemyKilled', enemy_id: enemy_id, gold_value: gold_value }; }, TowerPlacedEvent: function TowerPlacedEvent(tower_id, cost, position_x, position_y) { return { type: 'TowerPlaced', tower_id: tower_id, cost: cost, position_x: position_x, position_y: position_y }; }, TowerSoldEvent: function TowerSoldEvent(tower_id, refund_value) { return { type: 'TowerSold', tower_id: tower_id, refund_value: refund_value }; }, PowerStatusChangedEvent: function PowerStatusChangedEvent(new_status) { return { type: 'PowerStatusChanged', new_status: new_status }; }, WaveCompletedEvent: function WaveCompletedEvent(wave_number) { return { type: 'WaveCompleted', wave_number: wave_number }; }, GameOverEvent: function GameOverEvent() { return { type: 'GameOver' }; } }; // === SECTION: STACK-BASED GAME STATE MANAGER === // === SECTION: DIRECT GAME INITIALIZATION === // Initialize managers Game.GridManager.init(40, 60); Game.ResourceManager.init(); Game.PathManager.init(); Game.PathManager.recalculatePath({ x: 0, y: 30 }, { x: 39, y: 30 }); drawGrid(); // Create the Heads-Up Display (HUD) var goldText = new Text2('Gold: 100', { size: 40, fill: 0xFFD700 }); goldText.anchor.set(1, 0); // Align to the right LK.gui.topRight.addChild(goldText); goldText.x = -20; // 20px padding from the right edge goldText.y = 20; // 20px padding from the top edge var livesText = new Text2('Lives: 20', { size: 40, fill: 0xFF4136 }); livesText.anchor.set(1, 0); // Align to the right LK.gui.topRight.addChild(livesText); livesText.x = -20; // 20px padding from the right edge livesText.y = 70; // Position below the gold text // Create Start Wave button var startWaveButton = new Text2('START NEXT WAVE', { size: 50, fill: 0x00FF00 }); startWaveButton.anchor.set(0.5, 1); LK.gui.bottom.addChild(startWaveButton); startWaveButton.y = -20; startWaveButton.visible = false; startWaveButton.down = function () { Game.Systems.WaveSpawnerSystem.startNextWave(); startWaveButton.visible = false; }; // Initialize GameplayManager Game.GameplayManager.init(); // Initialize GameManager Game.GameManager.init(); // Start first wave Game.Systems.WaveSpawnerSystem.startNextWave(); // === SECTION: TEMP BUILD UI === var buildMode = 'ArrowTower'; // Keep this variable var arrowButton = new Text2('Build: Arrow', { size: 40, fill: 0x2ecc71 }); arrowButton.anchor.set(0, 0.5); LK.gui.left.addChild(arrowButton); arrowButton.x = 20; arrowButton.y = -350; arrowButton.down = function () { buildMode = 'ArrowTower'; }; var generatorButton = new Text2('Build: Generator', { size: 40, fill: 0x3498db }); generatorButton.anchor.set(0, 0.5); LK.gui.left.addChild(generatorButton); generatorButton.x = 20; generatorButton.y = -280; generatorButton.down = function () { buildMode = 'GeneratorTower'; }; var capacitorButton = new Text2('Build: Capacitor', { size: 40, fill: 0xf1c40f }); capacitorButton.anchor.set(0, 0.5); LK.gui.left.addChild(capacitorButton); capacitorButton.x = 20; capacitorButton.y = -210; capacitorButton.down = function () { buildMode = 'CapacitorTower'; }; var rockLauncherButton = new Text2('Build: Rock Launcher', { size: 40, fill: 0x8D6E63 }); rockLauncherButton.anchor.set(0, 0.5); LK.gui.left.addChild(rockLauncherButton); rockLauncherButton.x = 20; rockLauncherButton.y = -140; rockLauncherButton.down = function () { buildMode = 'RockLauncher'; }; var chemicalTowerButton = new Text2('Build: Chemical', { size: 40, fill: 0xb87333 }); chemicalTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(chemicalTowerButton); chemicalTowerButton.x = 20; chemicalTowerButton.y = -70; chemicalTowerButton.down = function () { buildMode = 'ChemicalTower'; }; var slowTowerButton = new Text2('Build: Slow', { size: 40, fill: 0x5DADE2 }); slowTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(slowTowerButton); slowTowerButton.x = 20; slowTowerButton.y = 0; slowTowerButton.down = function () { buildMode = 'SlowTower'; }; var darkTowerButton = new Text2('Build: Dark', { size: 40, fill: 0x9b59b6 }); darkTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(darkTowerButton); darkTowerButton.x = 20; darkTowerButton.y = 70; darkTowerButton.down = function () { buildMode = 'DarkTower'; }; // --- NEW BUTTON --- var graveyardTowerButton = new Text2('Build: Graveyard', { size: 40, fill: 0x607d8b }); graveyardTowerButton.anchor.set(0, 0.5); LK.gui.left.addChild(graveyardTowerButton); graveyardTowerButton.x = 20; graveyardTowerButton.y = 140; graveyardTowerButton.down = function () { buildMode = 'GraveyardTower'; }; // Set up main game loop var powerUpdateTimer = 0; game.update = function () { if (Game.GameManager.isGameOver) { return; } Game.Systems.WaveSpawnerSystem.update(); Game.Systems.TargetingSystem.update(); Game.Systems.AuraSystem.update(); Game.Systems.CombatSystem.update(); Game.Systems.MovementSystem.update(); Game.Systems.RenderSystem.update(); // Update HUD every frame goldText.setText('Gold: ' + Game.ResourceManager.gold); livesText.setText('Lives: ' + Game.ResourceManager.lives); // Update timed systems once per second (every 60 frames) powerUpdateTimer++; if (powerUpdateTimer >= 60) { Game.ResourceManager.updatePower(); // --- CORRECTED LOGIC --- // Only run the economy system if a wave is currently active. if (Game.Systems.WaveSpawnerSystem.isSpawning) { Game.Systems.EconomySystem.update(); } // --- END CORRECTION --- powerUpdateTimer = 0; } Game.Systems.CleanupSystem.update(); };
===================================================================
--- original.js
+++ change.js
@@ -1081,8 +1081,50 @@
args: [15] // High power cost for economic advantage
}]
}
};
+// === SECTION: UPGRADE DATA ===
+Game.UpgradeData = {
+ 'ArrowTower': {
+ 'PathA': [{
+ cost: 60,
+ effects: {
+ AttackComponent: {
+ damage: 5
+ }
+ }
+ },
+ // Level 1: +5 Dmg
+ {
+ cost: 110,
+ effects: {
+ AttackComponent: {
+ damage: 10
+ }
+ }
+ } // Level 2: +10 Dmg
+ ],
+ 'PathB': [{
+ cost: 75,
+ effects: {
+ AttackComponent: {
+ attack_speed: 0.2
+ }
+ }
+ },
+ // Level 1: +0.2 AS
+ {
+ cost: 125,
+ effects: {
+ AttackComponent: {
+ attack_speed: 0.3
+ }
+ }
+ } // Level 2: +0.3 AS
+ ]
+ }
+ // Other tower upgrades will be added here later
+};
// === SECTION: GRID DRAWING ===
function drawGrid() {
// Draw the grid cells
for (var x = 0; x < Game.GridManager.grid.length; x++) {