alien-rush/src/support/towerManager.js

552 lines
19 KiB
JavaScript

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;
}
}