Advertisement

← tower-of-time-game

src/systems/BuildingSystem.ts

10.2 KB Download file
import BasicTower from "../prefabs/BasicTower";
import SniperTower from "../prefabs/SniperTower";
import SlowdownTower from "../prefabs/SlowdownTower";
import SplashTower from "../prefabs/SplashTower";
import Tower from "../prefabs/Tower";
import EnergySystem from "./EnergySystem";
import ConfigSystem from "./ConfigSystem";

// Tower types enum for extensibility
export enum TowerType {
  BASIC_TOWER = "basic_tower",
  SNIPER_TOWER = "sniper_tower",
  SLOWDOWN_TOWER = "slowdown_tower",
  SPLASH_TOWER = "splash_tower",
}

// Tower instance interface
export interface TowerInstance {
  id: string;
  type: TowerType;
  config: any; // TowerConfig from ConfigSystem
  gameObject: Tower;
  tileX: number;
  tileY: number;
  level: number; // For upgrades
}

// Tile coordinate interface
export interface TileCoordinate {
  x: number;
  y: number;
}

// Legacy - keeping for backward compatibility but towers are now managed by ConfigSystem

/**
 * Comprehensive tower management system
 * Handles tower placement, tracking, validation, and future features like upgrades
 */
export class TowerManager {
  private scene: Phaser.Scene;
  private tilemap: Phaser.Tilemaps.Tilemap;
  private towerLayer: Phaser.GameObjects.Group;
  private towers: Map<string, TowerInstance> = new Map();
  private tileToTower: Map<string, string> = new Map(); // tileKey -> towerId
  private nextTowerId: number = 1;
  private energySystem?: EnergySystem;

  // Depth constant for towers (should be below player)
  private static readonly TOWER_DEPTH = 100;

  constructor(
    scene: Phaser.Scene,
    tilemap: Phaser.Tilemaps.Tilemap,
    towerLayer: Phaser.GameObjects.Group,
    energySystem?: EnergySystem
  ) {
    this.scene = scene;
    this.tilemap = tilemap;
    this.towerLayer = towerLayer;
    this.energySystem = energySystem;
  }

  /**
   * Get tower configuration from ConfigSystem
   */
  private getTowerConfig(towerType: TowerType): any | null {
    // Try to get tower configs from ConfigSystem if available
    if (
      this.scene &&
      typeof (this.scene as any).getConfigSystem === "function"
    ) {
      const configSystem = (this.scene as any).getConfigSystem();
      if (configSystem) {
        return configSystem.getTowerConfig(towerType);
      }
    }

    return null;
  }

  /**
   * Set the energy system for building cost management
   */
  public setEnergySystem(energySystem: EnergySystem): void {
    this.energySystem = energySystem;
  }

  /**
   * Check if a tile can have a building placed on it
   */
  public canBuildAt(tileX: number, tileY: number): boolean {
    // Check if tile is within bounds
    if (!this.isValidTileCoordinate(tileX, tileY)) {
      return false;
    }

    // Check if there's already a tower at this tile
    if (this.getTowerAtTile(tileX, tileY)) {
      return false;
    }

    // Check if tile is on the path layer (path tiles cannot have buildings)
    const pathTile = this.tilemap.getTileAt(tileX, tileY, false, "walls");
    return !!pathTile;
  }

  /**
   * Check if a tile can have a building placed on it (with debug info)
   */
  public canBuildAtWithDebug(tileX: number, tileY: number): boolean {
    // Check if tile is within bounds
    if (!this.isValidTileCoordinate(tileX, tileY)) {
      console.log(
        `Cannot build at (${tileX}, ${tileY}): Out of bounds. Map size: ${this.tilemap.width}x${this.tilemap.height}`
      );
      return false;
    }

    // Check if there's already a tower at this tile
    if (this.getTowerAtTile(tileX, tileY)) {
      console.log(`Cannot build at (${tileX}, ${tileY}): Tower already exists`);
      return false;
    }

    // Check if tile is on the path layer (path tiles cannot have buildings)
    const pathTile = this.tilemap.getTileAt(tileX, tileY, false, "path");
    if (pathTile && pathTile.index !== 0) {
      console.log(
        `Cannot build at (${tileX}, ${tileY}): Tile is on path layer (index: ${pathTile.index})`
      );
      return false;
    }

    console.log(`Can build at (${tileX}, ${tileY}): Valid location!`);
    return true;
  }

  /**
   * Place a tower at the specified tile coordinates
   */
  public placeTower(
    towerType: TowerType,
    tileX: number,
    tileY: number
  ): TowerInstance | null {
    if (!this.canBuildAt(tileX, tileY)) {
      return null;
    }

    // Get tower config from ConfigSystem
    const towerConfig = this.getTowerConfig(towerType);
    if (!towerConfig) {
      console.error(`Unknown tower type: ${towerType}`);
      return null;
    }

    // Check energy cost if energy system is available
    if (this.energySystem && towerConfig.energyCost > 0) {
      if (!this.energySystem.hasEnergy(towerConfig.energyCost)) {
        console.log(
          `Not enough energy to place ${towerConfig.name}. Required: ${
            towerConfig.energyCost
          }, Available: ${this.energySystem.getCurrentEnergy()}`
        );
        return null;
      }
    }

    // Calculate world position from tile coordinates
    const worldX = tileX * 32;
    const worldY = tileY * 32;

    // Create the tower game object based on type
    let gameObject: Tower;

    switch (towerType) {
      case TowerType.BASIC_TOWER:
        gameObject = new BasicTower(this.scene, worldX, worldY);
        break;
      case TowerType.SNIPER_TOWER:
        gameObject = new SniperTower(this.scene, worldX, worldY);
        break;
      case TowerType.SLOWDOWN_TOWER:
        gameObject = new SlowdownTower(this.scene, worldX, worldY);
        break;
      case TowerType.SPLASH_TOWER:
        gameObject = new SplashTower(this.scene, worldX, worldY);
        break;
      default:
        console.error(
          `No game object creation logic for tower type: ${towerType}`
        );
        return null;
    }

    // Configure tower with config from ConfigSystem
    gameObject.configure(towerConfig);

    // Add to scene and set properties
    this.scene.add.existing(gameObject);
    gameObject.setDepth(TowerManager.TOWER_DEPTH);
    this.towerLayer.add(gameObject);

    // Create tower instance
    const towerId = `tower_${this.nextTowerId++}`;
    const tower: TowerInstance = {
      id: towerId,
      type: towerType,
      config: towerConfig,
      gameObject,
      tileX,
      tileY,
      level: 1,
    };

    // Consume energy for tower placement
    if (this.energySystem && towerConfig.energyCost > 0) {
      this.energySystem.consumeEnergy(
        towerConfig.energyCost,
        `tower_${towerType}`
      );
    }

    // Register the tower
    this.towers.set(towerId, tower);
    this.tileToTower.set(this.getTileKey(tileX, tileY), towerId);

    console.log(
      `Placed ${towerConfig.name} at (${tileX}, ${tileY}) for ${towerConfig.energyCost} energy`
    );
    return tower;
  }

  /**
   * Remove a tower from the specified tile
   */
  public removeTower(tileX: number, tileY: number): boolean {
    const tower = this.getTowerAtTile(tileX, tileY);
    if (!tower) {
      return false;
    }

    // Remove from maps
    this.towers.delete(tower.id);
    this.tileToTower.delete(this.getTileKey(tileX, tileY));

    // Destroy game object
    tower.gameObject.destroy();

    return true;
  }

  /**
   * Get tower at specific tile coordinates
   */
  public getTowerAtTile(tileX: number, tileY: number): TowerInstance | null {
    const tileKey = this.getTileKey(tileX, tileY);
    const towerId = this.tileToTower.get(tileKey);
    if (!towerId) {
      return null;
    }
    return this.towers.get(towerId) || null;
  }

  /**
   * Get tower at player's current position
   */
  public getTowerAtPlayerPosition(
    playerX: number,
    playerY: number
  ): TowerInstance | null {
    const tileX = Math.floor(playerX / 32);
    const tileY = Math.floor(playerY / 32);
    return this.getTowerAtTile(tileX, tileY);
  }

  /**
   * Get all towers
   */
  public getAllTowers(): TowerInstance[] {
    return Array.from(this.towers.values());
  }

  /**
   * Get towers by type
   */
  public getTowersByType(type: TowerType): TowerInstance[] {
    return this.getAllTowers().filter((tower) => tower.type === type);
  }

  /**
   * Check if tile coordinates are valid
   */
  private isValidTileCoordinate(tileX: number, tileY: number): boolean {
    return (
      tileX >= 0 &&
      tileY >= 0 &&
      tileX < this.tilemap.width &&
      tileY < this.tilemap.height
    );
  }

  /**
   * Generate a unique key for tile coordinates
   */
  private getTileKey(tileX: number, tileY: number): string {
    return `${tileX},${tileY}`;
  }

  /**
   * Future method for upgrading towers
   */
  public upgradeTower(towerId: string): boolean {
    const tower = this.towers.get(towerId);
    if (!tower) {
      return false;
    }

    // TODO: Implement upgrade logic
    // tower.level++;
    // Update game object appearance/properties

    return true;
  }

  /**
   * Future method for getting tower stats
   */
  public getTowerStats(towerId: string): any {
    const tower = this.towers.get(towerId);
    if (!tower) {
      return null;
    }

    // TODO: Return tower stats based on type and level
    return {
      type: tower.type,
      level: tower.level,
      // Add more stats as needed
    };
  }

  /**
   * Get the tower layer for external access
   */
  public getTowerLayer(): Phaser.GameObjects.Group {
    return this.towerLayer;
  }

  /**
   * Get the tower depth constant for external use
   */
  public static getTowerDepth(): number {
    return TowerManager.TOWER_DEPTH;
  }

  /**
   * Set pause state for all towers (for rewind system)
   */
  public setPaused(paused: boolean): void {
    this.towers.forEach((tower) => {
      // Check if the tower has a setPaused method
      if (
        tower.gameObject &&
        typeof (tower.gameObject as any).setPaused === "function"
      ) {
        (tower.gameObject as any).setPaused(paused);
      }
    });
  }

  /**
   * Cleanup method
   */
  public destroy(): void {
    // Destroy all tower game objects
    this.towers.forEach((tower) => {
      tower.gameObject.destroy();
    });

    // Clear maps
    this.towers.clear();
    this.tileToTower.clear();
  }
}