Files
test1/game/towers/TowerManager.ts

264 lines
7.6 KiB
TypeScript

import type Phaser from 'phaser'
import { GameManager } from '../GameManager'
import { getCellSize } from '../mapRenderer'
import { COFFEE_COST } from '../constants'
import type { EnemyBase } from '../enemies/EnemyBase'
import { TowerBase } from './TowerBase'
import { InternTower } from './InternTower'
import { SeniorDevTower } from './SeniorDevTower'
import { PPTMasterTower } from './PPTMasterTower'
import { HRBPTower } from './HRBPTower'
import { PMTower } from './PMTower'
import { OpsTower } from './OpsTower'
import { OutsourceTower } from './OutsourceTower'
export type TowerType = 'intern' | 'senior' | 'ppt' | 'hrbp' | 'pm' | 'ops' | 'outsource'
export const TOWER_COSTS: Record<TowerType, number> = {
outsource: 30,
intern: 50,
hrbp: 80,
ops: 90,
ppt: 100,
senior: 120,
pm: 160,
}
export class TowerManager {
private scene: Phaser.Scene
private towers: TowerBase[] = []
private occupiedCells: Set<string> = new Set()
private infoLabel: Phaser.GameObjects.Text | null = null
constructor(scene: Phaser.Scene) {
this.scene = scene
}
canPlace(gridX: number, gridY: number): boolean {
const key = `${gridX},${gridY}`
// 用 occupiedCells 判断,路径判断由 GameScene 传入的 currentPathTiles 负责
return !this.occupiedCells.has(key)
}
setCurrentPathTiles(tiles: Set<string>): void {
this._pathTiles = tiles
}
private _pathTiles: Set<string> = new Set()
canPlaceWithPath(gridX: number, gridY: number): boolean {
const key = `${gridX},${gridY}`
return !this._pathTiles.has(key) && !this.occupiedCells.has(key)
}
placeTower(gridX: number, gridY: number, type: TowerType): boolean {
if (!this.canPlaceWithPath(gridX, gridY)) return false
const cost = TOWER_COSTS[type]
const manager = GameManager.getInstance()
if (!manager.spendHC(cost)) return false
const tower = this.createTower(type, gridX, gridY)
this.towers.push(tower)
this.occupiedCells.add(`${gridX},${gridY}`)
// 监听实习生自毁事件
if (tower instanceof InternTower) {
tower.onSelfDestroy = (t) => this.removeTower(t)
}
return true
}
private createTower(type: TowerType, gridX: number, gridY: number): TowerBase {
switch (type) {
case 'senior': return new SeniorDevTower(this.scene, gridX, gridY)
case 'ppt': return new PPTMasterTower(this.scene, gridX, gridY)
case 'hrbp': return new HRBPTower(this.scene, gridX, gridY)
case 'pm': return new PMTower(this.scene, gridX, gridY)
case 'ops': return new OpsTower(this.scene, gridX, gridY)
case 'outsource': return new OutsourceTower(this.scene, gridX, gridY)
default: return new InternTower(this.scene, gridX, gridY)
}
}
update(delta: number, enemies: EnemyBase[]): void {
// 更新 HRBP 的周围塔列表
for (const tower of this.towers) {
if (tower instanceof HRBPTower) {
const nearby = this.getTowersNear(tower.gridX, tower.gridY, 1, tower)
tower.setNearbyTowers(nearby)
}
}
for (const tower of this.towers) {
if (tower instanceof PPTMasterTower) {
// PPT 大师特殊更新:如果可以攻击就 AOE
this.updatePPTTower(tower, delta, enemies)
} else {
tower.update(delta, enemies)
}
}
}
private updatePPTTower(
tower: PPTMasterTower,
delta: number,
enemies: EnemyBase[]
): void {
// 直接调用 super 逻辑(手动处理冷却)
tower['attackCooldown'] -= delta
if (tower['stamina'] <= 0) tower['isActive'] = false
if (!tower['isActive']) {
tower['stamina'] = Math.min(
tower.maxStamina,
tower['stamina'] + (tower['staminaRegen'] * delta) / 1000
)
if (tower['stamina'] > 20) tower['isActive'] = true
tower['updateStaminaBar']()
return
}
const { cellW } = getCellSize()
const hasTarget = enemies.some((e) => {
if (e.isDead) return false
const dx = e.x - tower['px']
const dy = e.y - tower['py']
return Math.sqrt(dx * dx + dy * dy) <= tower.attackRange * cellW
})
if (hasTarget && tower['attackCooldown'] <= 0) {
tower.attackAoe(enemies)
tower['stamina'] -= 5
tower['attackCooldown'] = 1000 / tower.attackSpeed
tower['updateStaminaBar']()
} else if (!hasTarget) {
tower['stamina'] = Math.min(
tower.maxStamina,
tower['stamina'] + (tower['staminaRegen'] * delta) / 1000
)
tower['updateStaminaBar']()
}
}
private getTowersNear(
gx: number,
gy: number,
range: number,
exclude: TowerBase
): TowerBase[] {
return this.towers.filter(
(t) =>
t !== exclude &&
Math.abs(t.gridX - gx) <= range &&
Math.abs(t.gridY - gy) <= range
)
}
/** 点击格子时检查是否有塔,显示信息 */
handleTileClick(gridX: number, gridY: number): boolean {
const tower = this.getTowerAt(gridX, gridY)
if (!tower) return false
this.showTowerInfo(tower)
return true
}
private showTowerInfo(tower: TowerBase): void {
this.infoLabel?.destroy()
const { cellW, cellH } = getCellSize()
const { x: cx, y: cy } = tower.getPixelCenter()
void cx // suppress unused warning (used in label position below)
const label = this.scene.add
.text(
cx,
cy - 40,
`精力: ${Math.floor(tower.stamina)}% [点击购买咖啡 ${COFFEE_COST}HC]`,
{
fontFamily: 'VT323, monospace',
fontSize: '14px',
color: '#F59E0B',
backgroundColor: '#0A1628',
padding: { x: 6, y: 3 },
}
)
.setOrigin(0.5, 1)
.setDepth(30)
.setInteractive()
label.on('pointerdown', () => {
const ok = tower.buyCoffee()
if (ok) {
label.setText(`☕ 喝了咖啡!精力满了!`)
this.scene.time.delayedCall(1000, () => label.destroy())
} else {
label.setText('HC 不足!')
this.scene.time.delayedCall(800, () => label.destroy())
}
})
this.infoLabel = label
this.scene.time.delayedCall(3000, () => {
if (this.infoLabel === label) this.infoLabel = null
label.destroy()
})
}
getTowerAt(gridX: number, gridY: number): TowerBase | null {
return (
this.towers.find((t) => t.gridX === gridX && t.gridY === gridY) ?? null
)
}
removeTower(tower: TowerBase): void {
const idx = this.towers.indexOf(tower)
if (idx !== -1) {
this.towers.splice(idx, 1)
this.occupiedCells.delete(`${tower.gridX},${tower.gridY}`)
}
}
removeRandomTower(): void {
if (this.towers.length === 0) return
const idx = Math.floor(Math.random() * this.towers.length)
const tower = this.towers[idx]
this.showDestroyEffect(tower)
tower.destroy()
this.removeTower(tower)
}
private showDestroyEffect(tower: TowerBase): void {
const { x, y } = tower.getPixelCenter()
const txt = this.scene.add
.text(x, y - 20, '☠ 组织架构调整!被裁了!', {
fontFamily: 'VT323, monospace',
fontSize: '16px',
color: '#EF4444',
backgroundColor: '#7F1D1D',
padding: { x: 6, y: 3 },
})
.setOrigin(0.5, 1)
.setDepth(30)
this.scene.tweens.add({
targets: txt,
y: y - 60,
alpha: 0,
duration: 2000,
onComplete: () => txt.destroy(),
})
}
getAllTowers(): TowerBase[] {
return this.towers
}
hasTowerAt(gridX: number, gridY: number): boolean {
return this.occupiedCells.has(`${gridX},${gridY}`)
}
getOccupiedCells(): Set<string> {
return this.occupiedCells
}
}