Files
2026-04-19 01:16:27 +02:00

262 lines
8.9 KiB
JavaScript

import { Hex, PathFinding } from "../../Util/index.js";
import { ECellType, SimCell } from "../index.js";
export class SimLevel {
static version = 2;
_paused = true;
_currentStep = -1;
_gdLevel;
_highlightedIndex = -1;
_currency = -1;
_stride = -1;
_simCells;
_simEnemies;
_simProjectiles;
_nextWaveStep = -1;
_lastEnemySpawnStep = 0;
_currentWave;
_enemiesLeftToSpawn;
_levelIdx;
_gdRoot;
constructor(gdRoot, levelIdx) {
this._levelIdx = levelIdx;
this._gdRoot = gdRoot;
this._gdLevel = gdRoot.levels[levelIdx];
this._simCells = [];
this._simEnemies = [];
this._simProjectiles = [];
this._currentStep = 0;
this._currency = this._gdLevel.currency;
this._currentWave = 0;
this._enemiesLeftToSpawn = this._gdLevel.waves[0].amount;
const radius = this._gdLevel.radius;
this._stride = 2 * radius + 1;
const h0 = new Hex(0, 0);
for (let y = -radius; y <= radius; ++y) {
for (let x = -radius; x <= radius; ++x) {
const hex = new Hex(x, y);
const distance = Hex.distance(hex, h0);
const type = distance >= radius ? ECellType.Blocked : ECellType.Free;
const cellIndex = this.getCellIndex(hex);
this._simCells[cellIndex] = new SimCell(hex, distance, type, cellIndex);
}
}
this._gdLevel.walls.forEach((wall) => {
const cellIndex = this.getCellIndex(wall);
this._simCells[cellIndex].type = ECellType.Blocked;
});
this._gdLevel.enemySpawns.forEach((hex) => {
const cellIndex = this.getCellIndex(hex);
this._simCells[cellIndex].type = ECellType.Entry;
});
this._gdLevel.enemyTargets.forEach((hex) => {
const cellIndex = this.getCellIndex(hex);
this._simCells[cellIndex].type = ECellType.Entry;
});
this._simCells.forEach((cell) => {
this.updateBlockedType(cell);
});
this.updatePaths();
}
;
get gdLevel() {
return this._gdLevel;
}
get simCells() {
return this._simCells;
}
get simEnemies() {
return this._simEnemies;
}
get simProjectiles() {
return this._simProjectiles;
}
get currentStep() {
return this._currentStep;
}
set currentStep(value) {
this._currentStep = value;
}
get currency() {
return this._currency;
}
set currency(value) {
this._currency = value;
}
get highlightedIndex() {
return this._highlightedIndex;
}
set highlightedIndex(value) {
this._highlightedIndex = value;
}
get paused() {
return this._paused;
}
set paused(value) {
this._paused = value;
}
get currentWave() {
return this._currentWave;
}
set currentWave(value) {
this._currentWave = value;
}
get nextWaveStep() {
return this._nextWaveStep;
}
set nextWaveStep(value) {
this._nextWaveStep = value;
}
get lastEnemySpawnStep() {
return this._lastEnemySpawnStep;
}
set lastEnemySpawnStep(value) {
this._lastEnemySpawnStep = value;
}
get enemiesLeftToSpawn() {
return this._enemiesLeftToSpawn;
}
set enemiesLeftToSpawn(value) {
this._enemiesLeftToSpawn = value;
}
serialize() {
const data = {
version: SimLevel.version,
levelIdx: this._levelIdx,
paused: this._paused,
gdLevel: this._gdLevel,
currentStep: this._currentStep,
highlightedIndex: this._highlightedIndex,
currency: this._currency,
stride: this._stride,
cells: this._simCells,
enemies: this._simEnemies,
projectiles: this._simProjectiles,
nextWaveStep: this._nextWaveStep,
lastEnemySpawnStep: this._lastEnemySpawnStep,
currentWave: this._currentWave,
enemiesLeftToSpawn: this._enemiesLeftToSpawn
};
return btoa(JSON.stringify(data));
}
static deserialize(gdRoot, data) {
const parsedData = JSON.parse(atob(data));
if (parsedData.version != SimLevel.version) {
console.warn('Unsupported version:', parsedData.version);
return null;
}
let level = new SimLevel(gdRoot, parsedData.levelIdx);
level._paused = parsedData.paused;
level._gdLevel = parsedData.gdLevel;
level._currentStep = parsedData.currentStep;
level._highlightedIndex = parsedData.highlightedIndex;
level._currency = parsedData.currency;
level._stride = parsedData.stride;
level._simCells = parsedData.cells;
level._simEnemies = parsedData.enemies;
level._simProjectiles = parsedData.projectiles;
level._nextWaveStep = parsedData.nextWaveStep;
level._lastEnemySpawnStep = parsedData.lastEnemySpawnStep;
level._currentWave = parsedData.currentWave;
level._enemiesLeftToSpawn = parsedData.enemiesLeftToSpawn;
return level;
}
getCellIndex(hex) {
const x = hex.col + this._gdLevel.radius;
const y = hex.row + this._gdLevel.radius;
if (x < 0 || x >= this._stride || y < 0 || y >= this._stride) {
return -1;
}
return y * this._stride + x;
}
getNeighbourCell(cell, direction) {
const hex = cell.hex;
const neighbourHex = Hex.neighbour(hex, direction);
const neighbourIndex = this.getCellIndex(neighbourHex);
return this._simCells[neighbourIndex];
}
updateBlockedType(cell) {
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;
}
reservePaths(hexToBlock) {
try {
if (!!hexToBlock) {
const reservedCell = this._simCells[this.getCellIndex(hexToBlock)];
if (reservedCell.type != ECellType.Free || !!reservedCell.simTower)
return false;
reservedCell.type = ECellType.Reserved;
}
return this.updatePaths();
}
finally {
if (!!hexToBlock) {
const reservedCell = this._simCells[this.getCellIndex(hexToBlock)];
if (reservedCell.type == ECellType.Reserved)
reservedCell.type = ECellType.Free;
}
}
}
updatePaths() {
const level = this._gdLevel;
const newRoutePaths = [];
const newEnemyPaths = [];
let invalid = false;
for (const routeIdx in level.enemyRoutes) {
const route = level.enemyRoutes[routeIdx];
const enemySpawnHex = level.enemySpawns[route[0]];
const enemySpawnCell = this._simCells[this.getCellIndex(enemySpawnHex)];
const enemyTargetHex = level.enemyTargets[route[1]];
const enemyTargetCell = this._simCells[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._simEnemies) {
const simEnemy = this._simEnemies[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 level.enemyRoutes) {
const route = level.enemyRoutes[routeIdx];
const enemySpawnHex = level.enemySpawns[route[0]];
const enemySpawnCell = this._simCells[this.getCellIndex(enemySpawnHex)];
enemySpawnCell.pathsToTarget[routeIdx] = newRoutePaths[routeIdx];
}
this._simEnemies.forEach((simEnemy, idx) => {
simEnemy.path = newEnemyPaths[idx];
simEnemy.currentPathIndex = 0;
simEnemy.onPathUpdated();
});
return true;
}
}
//# sourceMappingURL=SimLevel.js.map