import { ENEMIES_CONFIG } from './enemiesConfig.js'; import { TOWERS_CONFIG } from './towerConfig.js'; import { TowerAnims } from './towerAnims.js'; export class TowerManager { constructor(scene) { this.scene = scene; this.lastFired = {}; // Track last fire time for each tower this.following = {}; this.selectedTower = null; this.upgradeCost = null; this.towerAnims = new TowerAnims(scene); // Add global click handler to close upgrade menu this.scene.input.on('pointerdown', (pointer) => { if (this.selectedTower && !this.isPointerOverTower(pointer)) { this.closeUpgradeMenu(); } }); } createTower(type, x, y) { const posX = this.scene.gridToLocation(x); const posY = this.scene.gridToLocation(y); let towerBase; let tower; if (type === 'icbm') { towerBase = this.scene.add.sprite(posX, posY, 'towers', TOWERS_CONFIG[type].spriteStart).setDepth(10); tower = this.scene.add.sprite(posX, posY, 'towers', TOWERS_CONFIG[type].spriteStart+3).setDepth(11); tower.props = { 'type': type, 'level': 1 } } else { towerBase = this.scene.add.sprite(posX, posY, 'towers', 7).setDepth(10); tower = this.scene.add.sprite(posX, posY, 'towers', TOWERS_CONFIG[type].spriteStart).setDepth(11); tower.props = { 'type': type, 'level': 1 } } // Generate unique ID for enemy const uniqueId = Phaser.Math.Between(100000, 999999); tower.id = uniqueId; this.scene.towers.add(tower); tower.setInteractive(); tower.on('pointerdown', () => { if (!this.selectedTower) { this.upgradeMenu(tower); } }); // Draw range circle const config = TOWERS_CONFIG[type].level1; if (config) { const range = config.range; tower.showRange = this.scene.add.circle(posX, posY, range, 0x00ff00, 0); } } upgradeMenu(tower) { this.selectedTower = tower; const currentLevel = `level${tower.props.level}`; const nextLevel = currentLevel === 'level3' ? 'level3' : `level${tower.props.level + 1}`; this.upgradeDetails = this.scene.add.container(); const rangeCircle = this.scene.add.circle(tower.x, tower.y, TOWERS_CONFIG[tower.props.type][currentLevel].range, 0xc009900, 0.2) .setOrigin(0.5) .setDepth(5); this.upgradeDetails.add(rangeCircle); const centerToEdgeLine = this.scene.add.line(tower.x, tower.y, 0, 0, TOWERS_CONFIG[tower.props.type][currentLevel].range, 0, 0x00ff00) .setOrigin(0) .setDepth(6); this.upgradeDetails.add(centerToEdgeLine); this.scene.tweens.add({ targets: centerToEdgeLine, angle: 360, duration: 8000, repeat: -1 }); const intUpgrade = this.scene.add.image(tower.x + 100, tower.y, 'intUpgrade') .setDepth(12) .setTint(0xAAAAAA) .setOrigin(0, 0.5) .setScale(0) .setAlpha(0); this.upgradeDetails.add(intUpgrade); this.upgradeCost = TOWERS_CONFIG[tower.props.type][nextLevel].cost; this.upgradeText = this.scene.add.text(tower.x +175, tower.y - 50, `Upgrade: ${this.upgradeCost}`, { fontFamily: 'neuropol, arial', fontSize: '32px', fill: '#ffd900ff', stroke: '#c48f00ff', strokeThickness: 2, shadow: { offsetX: 5, offsetY: 5, color: '#000000', blur: 5, stroke: false, fill: true } }).setDepth(13).setAlpha(0).setInteractive(); this.upgradeDetails.add(this.upgradeText); if (currentLevel === 'level3') upgradeText.setText('Max Upgrade').disableInteractive(); const upgradeCancelText = this.scene.add.text(tower.x +175, tower.y + 25, `Cancel`, { fontFamily: 'neuropol, arial', fontSize: '32px', fill: '#ff0000ff', stroke: '#c40000ff', strokeThickness: 2, shadow: { offsetX: 5, offsetY: 5, color: '#000000', blur: 5, stroke: false, fill: true } }).setDepth(13).setAlpha(0).setInteractive(); this.upgradeDetails.add(upgradeCancelText); upgradeCancelText.on('pointerdown', () => { this.closeUpgradeMenu(); }); this.scene.tweens.add({ targets:[intUpgrade, this.upgradeText, upgradeCancelText], scale: 1, alpha: 1, duration: 500 }); this.upgradeText.on('pointerdown', () => { this.upgradeTower(tower); }); if (this.scene.UIScene.interfaceManager.gold < TOWERS_CONFIG[tower.props.type][nextLevel].cost) { this.disableUpgrade(this.upgradeText); } } enableUpgrade(text) { text.setInteractive().setTint(0xFFFFFF); } disableUpgrade(text) { text.disableInteractive().setTint(0xAAAAAA); } isPointerOverTower(pointer) { let isOverTower = false; if (this.upgradeDetails.getBounds().contains(pointer.x, pointer.y)) { isOverTower = true; } return isOverTower; } closeUpgradeMenu() { if (this.upgradeDetails) { this.upgradeDetails.destroy(); this.upgradeDetails = null; } this.selectedTower = null; } upgradeTower(tower) { this.closeUpgradeMenu(); if (tower.props.level === 3) return; tower.props.level++; const newLevel = `level${tower.props.level}` tower.setTexture('towers', TOWERS_CONFIG[tower.props.type][newLevel].sprite); this.scene.UIScene.removeGold(TOWERS_CONFIG[tower.props.type][newLevel].cost); } update(time, delta) { // Iterate through all towers this.scene.towers.children.iterate((tower) => { const towerX = tower.x; const towerY = tower.y; // Get tower config const type = tower.props.type; const level = 'level'+tower.props.level; const config = TOWERS_CONFIG[type][level]; if (this.following.hasOwnProperty(tower.id)) { this.scene.enemies.children.iterate((enemy) => { if (this.following[tower.id] === enemy.props.id && type !== 'icbm') { // Rotate tower to face the enemy const angle = Phaser.Math.Angle.Between(towerX, towerY, enemy.x, enemy.y); tower.rotation = angle; } }); } if (!config) return; const range = config.range; const rate = config.rate; // Check if enough time has passed since last fire if (this.lastFired[tower.id] === undefined || time - this.lastFired[tower.id] >= rate) { // Check for enemies in range let enemiesInRange = []; this.scene.enemies.children.iterate((enemy) => { const distanceTraveled = enemy.props.distanceTraveled; const distance = Phaser.Math.Distance.Between( towerX, towerY, enemy.x, enemy.y ); if (distance <= range && enemy.props.health > 0) { enemiesInRange.push(enemy); } }); // Fire if enemies are in range if (enemiesInRange.length > 0) { // Find enemy with greatest distance traveled const furthestEnemy = enemiesInRange.reduce((max, current) => { return current.props.distanceTraveled > max.props.distanceTraveled ? current : max; }); this.following[tower.id] = furthestEnemy.props.id; const dmgType = TOWERS_CONFIG[tower.props.type].dmgType; if (tower.props.type === 'icbm') { this.icbmAttackTarget(tower, furthestEnemy); } else { if (dmgType === 'aoe') { this.aoeAttackTarget(tower, furthestEnemy); } else { this.attackTarget(tower, furthestEnemy); } } this.lastFired[tower.id] = time; } } }); if (this.upgradeDetails && this.upgradeCost <= this.scene.UIScene.interfaceManager.gold) { this.enableUpgrade(this.upgradeText); } } attackTarget(tower, enemy) { // Tower Properties const type = tower.props.type; const level = 'level'+tower.props.level; const config = TOWERS_CONFIG[type][level]; const duration = TOWERS_CONFIG[type][level].duration; if (!config) return; const dmgLow = config.dmgLow; const dmgHigh = config.dmgHigh; const angle = Phaser.Math.Angle.Between(tower.x, tower.y, enemy.x, enemy.y); tower.rotation = angle; const vid = tower.play(config.anim); this.scene.time.delayedCall(config.duration, () => { vid.stop(); }); // Calculate damage (random between low and high) const damage = Phaser.Math.Between(dmgLow, dmgHigh); // Apply damage to enemy enemy.props.health -= damage; if (enemy.props.health > 0) { this.createHealthBar(enemy); if (type === 'gun') { const gunfire = this.scene.physics.add.sprite(enemy.x, enemy.y, 'ammo', 0); gunfire.play('gunfire'); gunfire.setVelocity(enemy.body.velocity.x, enemy.body.velocity.y); this.scene.time.delayedCall(config.duration, () => { gunfire.destroy(); }); } } else { this.destroyEnemy(enemy, tower); } } aoeAttackTarget(tower, enemy) { // Tower Properties const type = tower.props.type; const level = 'level'+tower.props.level; const config = TOWERS_CONFIG[type][level]; const duration = TOWERS_CONFIG[type][level].duration; if (!config) return; const dmgLow = config.dmgLow; const dmgHigh = config.dmgHigh; const angle = Phaser.Math.Angle.Between(tower.x, tower.y, enemy.x, enemy.y); tower.rotation = angle; if (type === 'cannon') { const projDistance = 50; const projX = tower.x + Math.cos(angle) * projDistance; const projY = tower.y + Math.sin(angle) * projDistance; const projectile = this.scene.add.sprite(projX, projY, 'ammo', 3); this.scene.tweens.add({ targets: projectile, x: enemy.x, y: enemy.y, duration: 100, onComplete: () => { projectile.setTexture('ammo', 4); this.scene.time.delayedCall(200, () => { projectile.destroy(); }); } }); tower.play(`${type}-${level}-fire`, { 'duration': duration }); } if (type === 'flame') { const projDistance = 185; const projDistance2 = 220; const projX = tower.x + Math.cos(angle) * projDistance; const projY = tower.y + Math.sin(angle) * projDistance; const proj2X = tower.x + Math.cos(angle) * projDistance2; const proj2Y = tower.y + Math.sin(angle) * projDistance2; const projectile = this.scene.add.sprite(projX, projY, 'towers', 23).setScale(1.5).setAlpha(.5); projectile.rotation = angle; projectile.play(`flamethrower`); this.scene.tweens.add({ targets: projectile, x: proj2X, y: proj2Y, duration:1000, onComplete: () => { projectile.destroy(); } }); } // Calculate damage (random between low and high) const damage = Phaser.Math.Between(dmgLow, dmgHigh); // Get AOE distance const aoeDistance = config.aoe; // Apply damage to all enemies in range const enemiesInRange = this.scene.enemies.getChildren().filter(e => { return Phaser.Math.Distance.Between(e.x, e.y, enemy.x, enemy.y) <= aoeDistance; }); enemiesInRange.forEach(targetEnemy => { targetEnemy.props.health -= damage; if (targetEnemy.props.health > 0) { this.createHealthBar(targetEnemy); } else { this.destroyEnemy(targetEnemy, tower); } }); } icbmAttackTarget(tower, enemy) { // Tower Properties const type = tower.props.type; const level = 'level'+tower.props.level; const config = TOWERS_CONFIG[type][level]; const duration = TOWERS_CONFIG[type][level].duration; if (!config) return; const dmgLow = config.dmgLow; const dmgHigh = config.dmgHigh; const fireMissle = this.scene.add.sprite(tower.x, tower.y, 'towers', 33).setDepth(12); tower.setVisible(false); this.scene.tweens.add({ targets: fireMissle, x: fireMissle.x, y: fireMissle.y - 1200, duration: 2000, ease: 'Cubic.easeIn', onComplete: () => { fireMissle.setFlipY(true); fireMissle.x = enemy.x; this.scene.tweens.add({ targets: fireMissle, x: enemy.x, y: enemy.y, duration: 500, ease: 'Cubic.easeIn', onComplete: () => { fireMissle.destroy(); tower.setVisible(true); const damage = Phaser.Math.Between(dmgLow, dmgHigh); const aoeDistance = config.aoe; const enemiesInRange = this.scene.enemies.getChildren().filter(e => { return Phaser.Math.Distance.Between(e.x, e.y, enemy.x, enemy.y) <= aoeDistance; }); const explosion = this.scene.add.sprite(enemy.x, enemy.y, 'towers', 34).setDepth(12); explosion.play('icbmExplosion'); this.scene.tweens.add({ targets:explosion, duration: 1000, scale: 3, alpha: .2, onComplete: () => { explosion.destroy(); } }); enemiesInRange.forEach(targetEnemy => { targetEnemy.props.health -= damage; if (targetEnemy.props.health > 0) { this.createHealthBar(targetEnemy); } else { this.destroyEnemy(targetEnemy, tower); } }); } }); } }); } createHealthBar(enemy) { const barWidth = 30; const barHeight = 5; const barX = enemy.x - barWidth/2; const barY = enemy.y - enemy.displayHeight/2 - 10; const health = Math.max(enemy.props.health, 0); const fullHealth = ENEMIES_CONFIG[enemy.props.type].fullHealth; // Create health bar container const healthBar = this.scene.add.container(enemy.x, barY); // Background bar (red) const background = this.scene.add.rectangle(0, 0, barWidth, barHeight, 0xe83829); // Health fill (green) const healthFill = this.scene.add.rectangle(0, 0, barWidth, barHeight, 0x00ff00); // Position the fill relative to background if (health === 0) { healthFill.width = 0; } else { healthFill.width = (health / fullHealth) * barWidth; } healthBar.add([background, healthFill]); this.scene.physics.world.enable(healthBar); healthBar.body.setVelocity(enemy.body.velocity.x, enemy.body.velocity.y); this.scene.tweens.add({ targets: healthBar, delay: 1000, alpha: 0, onComplete: () => { healthBar.destroy(); } }); } destroyEnemy(enemy, tower) { const dieX = enemy.x; const dieY = enemy.y; const type = enemy.props.type; const sprite = enemy.texture.key; const firstFrame = enemy.texture.firstFrame; const velocX = enemy.body.velocity.x; const velocY = enemy.body.velocity.y; // Add some Gold const drop = Phaser.Math.Between(ENEMIES_CONFIG[type].dropLow, ENEMIES_CONFIG[type].dropHigh) * 5; // Show the Gold const cashDrop = this.scene.add.image(dieX, dieY, 'gold').setScale(0.5); const angle = Phaser.Math.Angle.Between(tower.x, tower.y, dieX, dieY); // Calculate end position (100px away from original position in the calculated direction) const endX = dieX + Math.cos(angle) * 50; const endY = dieY + Math.sin(angle) * 50; // Get coords of Gold Interface const cam = this.scene.cameras.main; // Animate the cash drop moving to the new position this.scene.tweens.add({ targets: cashDrop, x: endX, y: endY, duration: 500, ease: 'Bounce', onComplete: () => { cashDrop.postFX.addGlow(); this.scene.tweens.add({ targets: cashDrop, x: 1300 + cam.scrollX, y: 100 + cam.scrollY, alpha: 0, duration: 500, onComplete: () => { cashDrop.destroy(); this.scene.UIScene.addGold(drop); this.scene.UIScene.interfaceManager.updateTowerAvail(); } }); } }); enemy.destroy(); const deadEnemy = this.scene.add.sprite(dieX, dieY, sprite, firstFrame); deadEnemy.play(`${type}-die`); this.scene.tweens.add({ targets: deadEnemy, delay: 5000, duration: 2000, alpha: 0, onComplete: () => { deadEnemy.destroy(); } }); this.following[tower.id] = null; } }