Advertisement

← tower-of-time-game

src/prefabs/Tower.ts

6.0 KB Download file
// Base Tower class - common functionality for all tower types

/* START-USER-IMPORTS */
import Enemy from "./Enemy";
import { TowerConfig } from "../systems/ConfigSystem";
/* END-USER-IMPORTS */

export default abstract class Tower extends Phaser.GameObjects.Sprite {
  constructor(
    scene: Phaser.Scene,
    x?: number,
    y?: number,
    texture?: string,
    frame?: number | string
  ) {
    super(scene, x ?? 0, y ?? 0, texture || "tower1", frame);

    this.setOrigin(0, 0);

    /* START-USER-CTR-CODE */
    // Initialize tower properties
    this.initializeTower();
    /* END-USER-CTR-CODE */
  }

  /* START-USER-CODE */

  // Tower properties - will be configured from TowerConfig
  protected range: number = 100;
  protected damage: number = 1;
  protected shootingInterval: number = 1000;
  protected lastShotTime: number = 0;
  protected targetingCheckInterval: number = 200; // Check for targets every 200ms for performance
  protected lastTargetingCheck: number = 0;
  protected currentTarget: Enemy | null = null;
  protected isPaused: boolean = false; // Track if tower is paused (during rewind)
  protected config: TowerConfig | null = null;

  // Performance optimization: squared range to avoid sqrt calculations
  protected rangeSquared: number = this.range * this.range;

  /**
   * Initialize tower properties and start update loop
   */
  protected initializeTower(): void {
    // Update range squared when range changes
    this.updateRangeSquared();

    // Add this tower to the scene's update loop
    this.scene.events.on("postupdate", this.updateTower, this);
  }

  /**
   * Configure tower with TowerConfig
   */
  public configure(config: TowerConfig): void {
    this.config = config;
    this.range = config.range;
    this.damage = config.damage;
    this.shootingInterval = config.shootingInterval;
    this.updateRangeSquared();
  }

  /**
   * Update range squared for performance optimization
   */
  protected updateRangeSquared(): void {
    this.rangeSquared = this.range * this.range;
  }

  /**
   * Set the tower's range
   */
  public setRange(range: number): void {
    this.range = range;
    this.updateRangeSquared();
  }

  /**
   * Get the tower's range
   */
  public getRange(): number {
    return this.range;
  }

  /**
   * Set the tower's shooting interval
   */
  public setShootingInterval(interval: number): void {
    this.shootingInterval = interval;
  }

  /**
   * Get the tower's shooting interval
   */
  public getShootingInterval(): number {
    return this.shootingInterval;
  }

  /**
   * Set tower pause state (for rewind system)
   */
  public setPaused(paused: boolean): void {
    this.isPaused = paused;
  }

  /**
   * Check if tower is paused
   */
  public isPaused_(): boolean {
    return this.isPaused;
  }

  /**
   * Main update loop for the tower
   */
  protected updateTower(): void {
    if (!this.active || !this.visible || this.isPaused) return;

    const currentTime = Date.now();

    // Check for targets periodically for performance
    if (currentTime - this.lastTargetingCheck >= this.targetingCheckInterval) {
      this.findTarget();
      this.lastTargetingCheck = currentTime;
    }

    // Validate current target (may have died or moved out of range)
    if (this.currentTarget && !this.isValidTarget(this.currentTarget)) {
      this.currentTarget = null;
    }

    // Perform tower-specific update logic
    this.updateTowerLogic(currentTime);
  }

  /**
   * Abstract method for tower-specific update logic
   * Each tower type implements its own behavior
   */
  protected abstract updateTowerLogic(currentTime: number): void;

  /**
   * Find the closest valid target within range
   */
  protected findTarget(): void {
    const enemies = this.getAllEnemies();
    let closestEnemy: Enemy | null = null;
    let closestDistanceSquared = this.rangeSquared;

    // Use squared distance to avoid expensive sqrt calculations
    for (const enemy of enemies) {
      if (!this.isValidTarget(enemy)) continue;

      const distanceSquared = this.getDistanceSquaredToEnemy(enemy);
      if (distanceSquared <= closestDistanceSquared) {
        closestEnemy = enemy;
        closestDistanceSquared = distanceSquared;
      }
    }

    this.currentTarget = closestEnemy;
  }

  /**
   * Check if an enemy is a valid target
   */
  protected isValidTarget(enemy: Enemy): boolean {
    return (
      enemy &&
      enemy.active &&
      enemy.canBeTargeted() &&
      this.getDistanceSquaredToEnemy(enemy) <= this.rangeSquared
    );
  }

  /**
   * Get squared distance to enemy for performance
   */
  protected getDistanceSquaredToEnemy(enemy: Enemy): number {
    const dx = enemy.x - (this.x + this.width / 2); // Use tower center
    const dy = enemy.y - (this.y + this.height / 2);
    return dx * dx + dy * dy;
  }

  /**
   * Get all enemies in the scene
   */
  protected getAllEnemies(): Enemy[] {
    const enemies: Enemy[] = [];
    
    // Try to get enemies from scene layers first
    const layers = (this.scene as any).getLayers?.();
    if (layers && layers.building) {
      layers.building.children.entries.forEach((child: any) => {
        if (child instanceof Enemy) {
          enemies.push(child);
        }
      });
    }

    // Fallback: search all scene children
    if (enemies.length === 0) {
      this.scene.children.getAll().forEach((child) => {
        if (child instanceof Enemy) {
          enemies.push(child);
        }
      });
    }

    return enemies;
  }

  /**
   * Get all enemies within range
   */
  protected getEnemiesInRange(): Enemy[] {
    return this.getAllEnemies().filter(enemy => this.isValidTarget(enemy));
  }

  /**
   * Override destroy to clean up event listeners
   */
  public destroy(fromScene?: boolean): void {
    // Remove event listener (check if scene still exists)
    if (this.scene && this.scene.events) {
      this.scene.events.off("postupdate", this.updateTower, this);
    }

    super.destroy(fromScene);
  }

  /* END-USER-CODE */
}