import { GdRoot } from "../data/GdRoot"; import { EDirection } from "../util/EDirection"; import { Hex } from "../util/Hex"; import { PathFinding } from "../util/PathFinding"; import { ECellType } from "./ECellType"; import { SimCell } from "./SimCell"; import { SimEnemy } from "./SimEnemy"; import { SimProjectile } from "./SimProjectile"; export class SimLevel { paused: boolean = true; currentStep: number = -1; highlightedIndex: number = -1; currency: number = -1; stride: number = -1; cells: SimCell[] = []; enemies: SimEnemy[] = []; projectiles: SimProjectile[] = []; nextWaveStep: number = -1; lastEnemySpawnStep: number = 0; currentWave: number = -1; enemiesLeftToSpawn: number = 0; index: number = 0; radius: number = 0; constructor(gdRoot: GdRoot, levelIdx: number) { const gdLevel = gdRoot.levels[levelIdx]; this.index = levelIdx; this.currency = gdLevel.currency; this.enemiesLeftToSpawn = gdLevel.waves[0].amount; this.radius = gdLevel.radius; this.stride = 2 * this.radius + 1; const h0 = new Hex(0, 0); for (let y = -this.radius; y <= this.radius; ++y) { for (let x = -this.radius; x <= this.radius; ++x) { const hex = new Hex(x, y); const distance = Hex.distance(hex, h0); const type = distance >= this.radius ? ECellType.Blocked : ECellType.Free; const cellIndex = this.getCellIndex(hex); this.cells[cellIndex] = new SimCell(hex, distance, type, cellIndex); } } gdLevel.walls.forEach((wall: Hex) => { const cellIndex = this.getCellIndex(wall); this.cells[cellIndex].type = ECellType.Blocked; }); gdLevel.enemySpawns.forEach((hex: Hex) => { const cellIndex = this.getCellIndex(hex); this.cells[cellIndex].type = ECellType.Entry; }); gdLevel.enemyTargets.forEach((hex: Hex) => { const cellIndex = this.getCellIndex(hex); this.cells[cellIndex].type = ECellType.Entry; }); this.cells.forEach((cell: SimCell) => { this.updateBlockedType(cell); }); this.updatePaths(gdRoot); }; public getCellIndex(hex: Hex) { const x = hex.col + this.radius; const y = hex.row + this.radius; if (x < 0 || x >= this.stride || y < 0 || y >= this.stride) { return -1; } return y * this.stride + x; } public getNeighbourCell(cell: SimCell, direction: EDirection) { const hex = cell.hex; const neighbourHex = Hex.neighbour(hex, direction); const neighbourIndex = this.getCellIndex(neighbourHex); return this.cells[neighbourIndex]; } public updateBlockedType(cell: SimCell) { if (cell.type == ECellType.Free) { cell.blockedType = -1; return; } if (cell.type == ECellType.Entry && cell.blockedType != -1) { return; } let blockedType = 0; for (let direction = 0; direction < 6; ++direction) { const neighbourCell = this.getNeighbourCell(cell, direction); if (!!neighbourCell && neighbourCell.type == ECellType.Free) { blockedType |= 1 << direction; } } cell.blockedType = blockedType; } public reservePaths(gdRoot: GdRoot, hexToBlock: Hex | null): boolean { try { if (!!hexToBlock) { const reservedCell = this.cells[this.getCellIndex(hexToBlock)]; if (reservedCell.type != ECellType.Free || !!reservedCell.tower) return false; reservedCell.type = ECellType.Reserved; } return this.updatePaths(gdRoot); } finally { if (!!hexToBlock) { const reservedCell = this.cells[this.getCellIndex(hexToBlock)]; if (reservedCell.type == ECellType.Reserved) reservedCell.type = ECellType.Free } } } public updatePaths(gdRoot: GdRoot): boolean { const newRoutePaths: number[][] = []; const newEnemyPaths: number[][] = []; const gdLevel = gdRoot.levels[this.index]; let invalid = false; for (const routeIdx in gdLevel.enemyRoutes) { const route = gdLevel.enemyRoutes[routeIdx]; const enemySpawnHex = gdLevel.enemySpawns[route[0]]; const enemySpawnCell = this.cells[this.getCellIndex(enemySpawnHex)]; const enemyTargetHex = gdLevel.enemyTargets[route[1]]; const enemyTargetCell = this.cells[this.getCellIndex(enemyTargetHex)]; const path = PathFinding.bfs(this, enemySpawnCell.index, enemyTargetCell.index); if (path == null) { invalid = true; break; } newRoutePaths[routeIdx] = path; } if (invalid) { return false; } for (let idx in this.enemies) { const simEnemy = this.enemies[idx]; const hex = Hex.fromWorld(simEnemy.position); const startIndex = this.getCellIndex(hex); const endIndex = this.getCellIndex(simEnemy.endHex); const path = PathFinding.bfs(this, startIndex, endIndex); if (path == null) { invalid = true; break; } newEnemyPaths[idx] = path; } if (invalid) { return false; } for (const routeIdx in gdLevel.enemyRoutes) { const route = gdLevel.enemyRoutes[routeIdx]; const enemySpawnHex = gdLevel.enemySpawns[route[0]]; const enemySpawnCell = this.cells[this.getCellIndex(enemySpawnHex)]; enemySpawnCell.pathsToTarget[routeIdx] = newRoutePaths[routeIdx]; } this.enemies.forEach((simEnemy: SimEnemy, idx: number) => { simEnemy.path = newEnemyPaths[idx]; simEnemy.currentPathIndex = 0; simEnemy.onPathUpdated(this); }); return true; } }