Files
HexTowerDefense3/src/app/components/game/sim/SimLevel.ts

202 lines
5.9 KiB
TypeScript

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 { SimMain } from './SimMain';
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 startNextWave(simMain: SimMain) {
const data = simMain.gdRoot.levels[this.index];
this.currentWave += 1;
if (!data.waves[this.currentWave]) {
this.paused = true;
return;
}
this.nextWaveStep = this.currentStep + simMain.gdRoot.simulation.waveDuration * simMain.gdRoot.simulation.stepsPerSecond - 1;
this.lastEnemySpawnStep = this.currentStep;
this.enemiesLeftToSpawn = data.waves[this.currentWave].amount;
this.paused = false;
}
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 (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];
}
for (let idx in this.enemies) {
const simEnemy = this.enemies[idx];
const currentIndex = simEnemy.path[simEnemy.currentPathIndex];
const nextIndex = simEnemy.path[simEnemy.currentPathIndex + 1];
const nextCell = this.cells[nextIndex];
const endIndex = this.getCellIndex(simEnemy.endHex);
let startIndex = nextCell.type != ECellType.Free ? currentIndex : nextIndex;
let path = PathFinding.bfs(this, startIndex, endIndex);
if (path == null) {
invalid = true;
break;
}
if (nextCell.type != ECellType.Free) {
path = [nextIndex, ...path];
}
else {
path = [currentIndex, ...path];
}
newEnemyPaths[idx] = path;
}
if (invalid) {
return false;
}
this.enemies.forEach((simEnemy: SimEnemy, idx: number) => {
const newPath = newEnemyPaths[idx];
simEnemy.path = newPath;
simEnemy.currentPathIndex = 0;
simEnemy.onPathUpdated(this);
});
return true;
}
}