input handlers
This commit is contained in:
@@ -3,11 +3,13 @@ root = true
|
|||||||
|
|
||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = tab
|
||||||
indent_size = 2
|
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
# Add this to help VSCode and Copilot use tabs for indentation
|
||||||
|
indent_size = tab
|
||||||
|
|
||||||
[*.ts]
|
[*.ts]
|
||||||
quote_type = single
|
quote_type = single
|
||||||
ij_typescript_use_double_quotes = false
|
ij_typescript_use_double_quotes = false
|
||||||
|
|||||||
15
.vscode/settings.json
vendored
15
.vscode/settings.json
vendored
@@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"json.schemas": [
|
"json.schemas": [
|
||||||
{
|
{
|
||||||
"fileMatch": [ "src/assets/data/*.json" ],
|
"fileMatch": ["src/assets/data/*.json"],
|
||||||
"url": "./schemas/gdRoot.json"
|
"url": "./schemas/gdRoot.json"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"editor.insertSpaces": false,
|
||||||
|
"editor.detectIndentation": false,
|
||||||
|
"editor.tabSize": 4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"tabWidth": 4,
|
"useTabs": true,
|
||||||
"useTabs": true
|
"printWidth": 9999
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<button (click)="step()">Step</button>
|
<button (click)="step()">Step</button>
|
||||||
<button (click)="fastForward()">⏭ Fast Forward</button>
|
<button (click)="fastForward()">⏭ Fast Forward</button>
|
||||||
<div>Step: {{ simMain.currentStep }}</div>
|
<div>Step: {{ simMain.currentStep }}</div>
|
||||||
|
<button (click)="startNextWave()">Start next wave</button>
|
||||||
<button (click)="openOptions()">Options</button>
|
<button (click)="openOptions()">Options</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { GdRoot } from './data/GdRoot';
|
|||||||
import { SimCommand } from './sim/commands/SimCommand';
|
import { SimCommand } from './sim/commands/SimCommand';
|
||||||
import { VisMain } from './vis/VisMain';
|
import { VisMain } from './vis/VisMain';
|
||||||
import { SplashComponent } from '../splash/splash.component';
|
import { SplashComponent } from '../splash/splash.component';
|
||||||
|
import { SimCommandStartNextWave } from './sim/commands/SimCommandStartNextWave';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-game',
|
selector: 'app-game',
|
||||||
@@ -27,7 +28,6 @@ export class GameComponent {
|
|||||||
const gdRoot = await this.loadGdRoot();
|
const gdRoot = await this.loadGdRoot();
|
||||||
this.simMain.setGdRoot(gdRoot);
|
this.simMain.setGdRoot(gdRoot);
|
||||||
this.visMain = new VisMain(this.simMain, SplashComponent.assetPreloader, this.wrapperRef.nativeElement, this.canvasRef.nativeElement);
|
this.visMain = new VisMain(this.simMain, SplashComponent.assetPreloader, this.wrapperRef.nativeElement, this.canvasRef.nativeElement);
|
||||||
window.addEventListener('resize', _ => this.visMain.onResized());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
@@ -35,15 +35,24 @@ export class GameComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
this.visMain.start();
|
this.visMain.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
step() {
|
step() {
|
||||||
this.simMain.step();
|
this.simMain.step();
|
||||||
|
this.visMain.onRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
rewind() {
|
rewind() {
|
||||||
//this.simMain.rewindToZero();
|
if (this.simMain.currentLevel)
|
||||||
|
this.simMain.currentLevel.currentStep = -1;
|
||||||
|
|
||||||
|
this.simMain.currentStep = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
startNextWave() {
|
||||||
|
this.simMain.addCommand(new SimCommandStartNextWave());
|
||||||
|
this.simMain.currentLevel!.paused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fastForward() {
|
fastForward() {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { GdRoot } from "../data/GdRoot";
|
import { GdRoot } from '../data/GdRoot';
|
||||||
import { EDirection } from "../util/EDirection";
|
import { EDirection } from '../util/EDirection';
|
||||||
import { Hex } from "../util/Hex";
|
import { Hex } from '../util/Hex';
|
||||||
import { PathFinding } from "../util/PathFinding";
|
import { PathFinding } from '../util/PathFinding';
|
||||||
import { ECellType } from "./ECellType";
|
import { ECellType } from './ECellType';
|
||||||
import { SimCell } from "./SimCell";
|
import { SimCell } from './SimCell';
|
||||||
import { SimEnemy } from "./SimEnemy";
|
import { SimEnemy } from './SimEnemy';
|
||||||
import { SimProjectile } from "./SimProjectile";
|
import { SimProjectile } from './SimProjectile';
|
||||||
|
|
||||||
export class SimLevel {
|
export class SimLevel {
|
||||||
paused: boolean = true;
|
paused: boolean = true;
|
||||||
@@ -23,159 +23,156 @@ export class SimLevel {
|
|||||||
index: number = 0;
|
index: number = 0;
|
||||||
radius: number = 0;
|
radius: number = 0;
|
||||||
|
|
||||||
constructor(gdRoot: GdRoot, levelIdx: number) {
|
constructor(gdRoot: GdRoot, levelIdx: number) {
|
||||||
const gdLevel = gdRoot.levels[levelIdx];
|
const gdLevel = gdRoot.levels[levelIdx];
|
||||||
this.index = levelIdx;
|
this.index = levelIdx;
|
||||||
this.currency = gdLevel.currency;
|
this.currency = gdLevel.currency;
|
||||||
this.enemiesLeftToSpawn = gdLevel.waves[0].amount;
|
this.enemiesLeftToSpawn = gdLevel.waves[0].amount;
|
||||||
|
|
||||||
this.radius = gdLevel.radius;
|
this.radius = gdLevel.radius;
|
||||||
this.stride = 2 * this.radius + 1;
|
this.stride = 2 * this.radius + 1;
|
||||||
const h0 = new Hex(0, 0);
|
const h0 = new Hex(0, 0);
|
||||||
for (let y = -this.radius; y <= this.radius; ++y) {
|
for (let y = -this.radius; y <= this.radius; ++y) {
|
||||||
for (let x = -this.radius; x <= this.radius; ++x) {
|
for (let x = -this.radius; x <= this.radius; ++x) {
|
||||||
const hex = new Hex(x, y);
|
const hex = new Hex(x, y);
|
||||||
const distance = Hex.distance(hex, h0);
|
const distance = Hex.distance(hex, h0);
|
||||||
const type = distance >= this.radius ? ECellType.Blocked : ECellType.Free;
|
const type = distance >= this.radius ? ECellType.Blocked : ECellType.Free;
|
||||||
const cellIndex = this.getCellIndex(hex);
|
const cellIndex = this.getCellIndex(hex);
|
||||||
this.cells[cellIndex] = new SimCell(hex, distance, type, cellIndex);
|
this.cells[cellIndex] = new SimCell(hex, distance, type, cellIndex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gdLevel.walls.forEach((wall: Hex) => {
|
gdLevel.walls.forEach((wall: Hex) => {
|
||||||
const cellIndex = this.getCellIndex(wall);
|
const cellIndex = this.getCellIndex(wall);
|
||||||
this.cells[cellIndex].type = ECellType.Blocked;
|
this.cells[cellIndex].type = ECellType.Blocked;
|
||||||
});
|
});
|
||||||
|
|
||||||
gdLevel.enemySpawns.forEach((hex: Hex) => {
|
gdLevel.enemySpawns.forEach((hex: Hex) => {
|
||||||
const cellIndex = this.getCellIndex(hex);
|
const cellIndex = this.getCellIndex(hex);
|
||||||
this.cells[cellIndex].type = ECellType.Entry;
|
this.cells[cellIndex].type = ECellType.Entry;
|
||||||
});
|
});
|
||||||
|
|
||||||
gdLevel.enemyTargets.forEach((hex: Hex) => {
|
gdLevel.enemyTargets.forEach((hex: Hex) => {
|
||||||
const cellIndex = this.getCellIndex(hex);
|
const cellIndex = this.getCellIndex(hex);
|
||||||
this.cells[cellIndex].type = ECellType.Entry;
|
this.cells[cellIndex].type = ECellType.Entry;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.cells.forEach((cell: SimCell) => {
|
this.cells.forEach((cell: SimCell) => {
|
||||||
this.updateBlockedType(cell);
|
this.updateBlockedType(cell);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.updatePaths(gdRoot);
|
this.updatePaths(gdRoot);
|
||||||
};
|
}
|
||||||
|
|
||||||
public getCellIndex(hex: Hex) {
|
public getCellIndex(hex: Hex) {
|
||||||
const x = hex.col + this.radius;
|
const x = hex.col + this.radius;
|
||||||
const y = hex.row + this.radius;
|
const y = hex.row + this.radius;
|
||||||
if (x < 0 || x >= this.stride || y < 0 || y >= this.stride) {
|
if (x < 0 || x >= this.stride || y < 0 || y >= this.stride) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
return y * this.stride + x;
|
return y * this.stride + x;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getNeighbourCell(cell: SimCell, direction: EDirection) {
|
public getNeighbourCell(cell: SimCell, direction: EDirection) {
|
||||||
const hex = cell.hex;
|
const hex = cell.hex;
|
||||||
const neighbourHex = Hex.neighbour(hex, direction);
|
const neighbourHex = Hex.neighbour(hex, direction);
|
||||||
const neighbourIndex = this.getCellIndex(neighbourHex);
|
const neighbourIndex = this.getCellIndex(neighbourHex);
|
||||||
return this.cells[neighbourIndex];
|
return this.cells[neighbourIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateBlockedType(cell: SimCell) {
|
public updateBlockedType(cell: SimCell) {
|
||||||
if (cell.type == ECellType.Free) {
|
if (cell.type == ECellType.Free) {
|
||||||
cell.blockedType = -1;
|
cell.blockedType = -1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell.type == ECellType.Entry && cell.blockedType != -1) {
|
if (cell.type == ECellType.Entry && cell.blockedType != -1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let blockedType = 0;
|
let blockedType = 0;
|
||||||
for (let direction = 0; direction < 6; ++direction) {
|
for (let direction = 0; direction < 6; ++direction) {
|
||||||
const neighbourCell = this.getNeighbourCell(cell, direction);
|
const neighbourCell = this.getNeighbourCell(cell, direction);
|
||||||
if (!!neighbourCell && neighbourCell.type == ECellType.Free) {
|
if (!!neighbourCell && neighbourCell.type == ECellType.Free) {
|
||||||
blockedType |= 1 << direction;
|
blockedType |= 1 << direction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.blockedType = blockedType;
|
cell.blockedType = blockedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public reservePaths(gdRoot: GdRoot, hexToBlock: Hex | null): boolean {
|
public reservePaths(gdRoot: GdRoot, hexToBlock: Hex | null): boolean {
|
||||||
try {
|
try {
|
||||||
if (!!hexToBlock) {
|
if (!!hexToBlock) {
|
||||||
const reservedCell = this.cells[this.getCellIndex(hexToBlock)];
|
const reservedCell = this.cells[this.getCellIndex(hexToBlock)];
|
||||||
if (reservedCell.type != ECellType.Free || !!reservedCell.tower)
|
if (reservedCell.type != ECellType.Free || !!reservedCell.tower) return false;
|
||||||
return false;
|
|
||||||
|
|
||||||
reservedCell.type = ECellType.Reserved;
|
reservedCell.type = ECellType.Reserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.updatePaths(gdRoot);
|
return this.updatePaths(gdRoot);
|
||||||
}
|
} finally {
|
||||||
finally {
|
if (!!hexToBlock) {
|
||||||
if (!!hexToBlock) {
|
const reservedCell = this.cells[this.getCellIndex(hexToBlock)];
|
||||||
const reservedCell = this.cells[this.getCellIndex(hexToBlock)];
|
if (reservedCell.type == ECellType.Reserved) reservedCell.type = ECellType.Free;
|
||||||
if (reservedCell.type == ECellType.Reserved)
|
}
|
||||||
reservedCell.type = ECellType.Free
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public updatePaths(gdRoot: GdRoot): boolean {
|
public updatePaths(gdRoot: GdRoot): boolean {
|
||||||
const newRoutePaths: number[][] = [];
|
const newRoutePaths: number[][] = [];
|
||||||
const newEnemyPaths: number[][] = [];
|
const newEnemyPaths: number[][] = [];
|
||||||
const gdLevel = gdRoot.levels[this.index];
|
const gdLevel = gdRoot.levels[this.index];
|
||||||
let invalid = false;
|
let invalid = false;
|
||||||
|
|
||||||
for (const routeIdx in gdLevel.enemyRoutes) {
|
for (const routeIdx in gdLevel.enemyRoutes) {
|
||||||
const route = gdLevel.enemyRoutes[routeIdx];
|
const route = gdLevel.enemyRoutes[routeIdx];
|
||||||
const enemySpawnHex = gdLevel.enemySpawns[route[0]];
|
const enemySpawnHex = gdLevel.enemySpawns[route[0]];
|
||||||
const enemySpawnCell = this.cells[this.getCellIndex(enemySpawnHex)];
|
const enemySpawnCell = this.cells[this.getCellIndex(enemySpawnHex)];
|
||||||
const enemyTargetHex = gdLevel.enemyTargets[route[1]];
|
const enemyTargetHex = gdLevel.enemyTargets[route[1]];
|
||||||
const enemyTargetCell = this.cells[this.getCellIndex(enemyTargetHex)];
|
const enemyTargetCell = this.cells[this.getCellIndex(enemyTargetHex)];
|
||||||
const path = PathFinding.bfs(this, enemySpawnCell.index, enemyTargetCell.index);
|
const path = PathFinding.bfs(this, enemySpawnCell.index, enemyTargetCell.index);
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
invalid = true;
|
invalid = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
newRoutePaths[routeIdx] = path;
|
newRoutePaths[routeIdx] = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let idx in this.enemies) {
|
for (let idx in this.enemies) {
|
||||||
const simEnemy = this.enemies[idx];
|
const simEnemy = this.enemies[idx];
|
||||||
const hex = Hex.fromWorld(simEnemy.position);
|
const hex = Hex.fromWorld(simEnemy.position);
|
||||||
const startIndex = this.getCellIndex(hex);
|
const startIndex = this.getCellIndex(hex);
|
||||||
const endIndex = this.getCellIndex(simEnemy.endHex);
|
const endIndex = this.getCellIndex(simEnemy.endHex);
|
||||||
const path = PathFinding.bfs(this, startIndex, endIndex);
|
const path = PathFinding.bfs(this, startIndex, endIndex);
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
invalid = true;
|
invalid = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
newEnemyPaths[idx] = path;
|
newEnemyPaths[idx] = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (invalid) {
|
if (invalid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const routeIdx in gdLevel.enemyRoutes) {
|
for (const routeIdx in gdLevel.enemyRoutes) {
|
||||||
const route = gdLevel.enemyRoutes[routeIdx];
|
const route = gdLevel.enemyRoutes[routeIdx];
|
||||||
const enemySpawnHex = gdLevel.enemySpawns[route[0]];
|
const enemySpawnHex = gdLevel.enemySpawns[route[0]];
|
||||||
const enemySpawnCell = this.cells[this.getCellIndex(enemySpawnHex)];
|
const enemySpawnCell = this.cells[this.getCellIndex(enemySpawnHex)];
|
||||||
enemySpawnCell.pathsToTarget[routeIdx] = newRoutePaths[routeIdx];
|
enemySpawnCell.pathsToTarget[routeIdx] = newRoutePaths[routeIdx];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.enemies.forEach((simEnemy: SimEnemy, idx: number) => {
|
this.enemies.forEach((simEnemy: SimEnemy, idx: number) => {
|
||||||
simEnemy.path = newEnemyPaths[idx];
|
simEnemy.path = newEnemyPaths[idx];
|
||||||
simEnemy.currentPathIndex = 0;
|
simEnemy.currentPathIndex = 0;
|
||||||
simEnemy.onPathUpdated(this);
|
simEnemy.onPathUpdated(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,29 +36,29 @@ export class SimMain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
executeUntilStep(target: number) {
|
executeUntilStep(target: number) {
|
||||||
this.currentStep = 0;
|
|
||||||
while (this.currentStep < target) {
|
while (this.currentStep < target) {
|
||||||
this.step();
|
this.step();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
step() {
|
step() {
|
||||||
const nextStep = this.currentStep + 1;
|
this.currentStep++;
|
||||||
|
if (this.currentLevel && !this.currentLevel.paused) {
|
||||||
// Get all commands for this step
|
this.currentLevel.currentStep++;
|
||||||
const commands = this.commandHistory.filter((c) => c.step === nextStep);
|
}
|
||||||
|
|
||||||
this.currentStep = nextStep;
|
|
||||||
for (const action of this.actions) {
|
for (const action of this.actions) {
|
||||||
action.execute(this);
|
action.execute(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const commands = this.commandHistory.filter((c) => c.step === this.currentStep);
|
||||||
for (const cmd of commands) {
|
for (const cmd of commands) {
|
||||||
cmd.execute(this);
|
cmd.execute(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addCommand(command: SimCommand) {
|
addCommand(command: SimCommand) {
|
||||||
|
command.step = this.currentStep + 1;
|
||||||
this.commandHistory = this.commandHistory.filter(
|
this.commandHistory = this.commandHistory.filter(
|
||||||
(c) => c.step < command.step
|
(c) => c.step < command.step
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,39 +1,45 @@
|
|||||||
import { EEnemySize } from "../../data/EEnemySize";
|
import { EEnemySize } from '../../data/EEnemySize';
|
||||||
import { SimCommandStartNextWave } from "../commands/SimCommandStartNextWave";
|
import { SimCommandStartNextWave } from '../commands/SimCommandStartNextWave';
|
||||||
import { SimEnemy } from "../SimEnemy";
|
import { SimEnemy } from '../SimEnemy';
|
||||||
import { SimMain } from "../SimMain";
|
import { SimMain } from '../SimMain';
|
||||||
import { ISimAction } from "./ISimAction";
|
import { ISimAction } from './ISimAction';
|
||||||
|
|
||||||
export class SimActionSpawnEnemies implements ISimAction {
|
export class SimActionSpawnEnemies implements ISimAction {
|
||||||
public execute(simMain: SimMain) {
|
public execute(simMain: SimMain) {
|
||||||
const level = simMain.currentLevel;
|
const level = simMain.currentLevel;
|
||||||
if (!level) return;
|
if (!level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const gdLevel = simMain.gdRoot.levels[level.index];
|
const gdLevel = simMain.gdRoot.levels[level.index];
|
||||||
const step = level.currentStep;
|
const step = level.currentStep;
|
||||||
const gdWave = gdLevel.waves[level.currentWave];
|
const gdWave = gdLevel.waves[level.currentWave];
|
||||||
let spawnDelay = simMain.gdRoot.simulation.spawnDelay;
|
if (!gdWave) {
|
||||||
switch (gdWave.size) {
|
return;
|
||||||
case EEnemySize.Huge:
|
}
|
||||||
spawnDelay *= 2;
|
|
||||||
break;
|
let spawnDelay = simMain.gdRoot.simulation.spawnDelay;
|
||||||
case EEnemySize.Tiny:
|
switch (gdWave.size) {
|
||||||
spawnDelay *= 0.5;
|
case EEnemySize.Huge:
|
||||||
break;
|
spawnDelay *= 2;
|
||||||
}
|
break;
|
||||||
|
case EEnemySize.Tiny:
|
||||||
|
spawnDelay *= 0.5;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
if (level.enemiesLeftToSpawn > 0 && level.lastEnemySpawnStep + spawnDelay * simMain.gdRoot.simulation.stepsPerSecond <= level.currentStep) {
|
if (level.enemiesLeftToSpawn > 0 && level.lastEnemySpawnStep + spawnDelay * simMain.gdRoot.simulation.stepsPerSecond <= level.currentStep) {
|
||||||
level.enemiesLeftToSpawn -= 1;
|
level.enemiesLeftToSpawn -= 1;
|
||||||
level.lastEnemySpawnStep = step;
|
level.lastEnemySpawnStep = step;
|
||||||
|
|
||||||
const route = Math.floor(Math.random() * gdLevel.enemyRoutes.length);
|
const route = Math.floor(Math.random() * gdLevel.enemyRoutes.length);
|
||||||
const enemy = new SimEnemy(gdWave.enemy, route);
|
const enemy = new SimEnemy(gdWave.enemy, route);
|
||||||
enemy.onPathUpdated(level);
|
enemy.onPathUpdated(level);
|
||||||
level.enemies.push(enemy);
|
level.enemies.push(enemy);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (level.nextWaveStep == step && !!gdLevel.waves[level.currentWave + 1]) {
|
if (level.nextWaveStep == step && !!gdLevel.waves[level.currentWave + 1]) {
|
||||||
simMain.addCommand(new SimCommandStartNextWave());
|
simMain.addCommand(new SimCommandStartNextWave());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { SimMain } from "../SimMain";
|
import { SimMain } from '../SimMain';
|
||||||
|
|
||||||
export abstract class SimCommand {
|
export abstract class SimCommand {
|
||||||
step: number = -1;
|
step: number = -1;
|
||||||
abstract execute(simMain: SimMain): void;
|
abstract execute(simMain: SimMain): void;
|
||||||
|
abstract check(simMain: SimMain): boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,46 @@
|
|||||||
import { Hex } from "../../util/Hex";
|
import { Hex } from '../../util/Hex';
|
||||||
import { ECellType } from "../ECellType";
|
import { ECellType } from '../ECellType';
|
||||||
import { SimMain } from "../SimMain";
|
import { SimMain } from '../SimMain';
|
||||||
import { SimCommand } from "./SimCommand";
|
import { SimCommand } from './SimCommand';
|
||||||
|
|
||||||
export class SimCommandBlockTerrain extends SimCommand {
|
export class SimCommandBlockTerrain extends SimCommand {
|
||||||
private hex: Hex;
|
private hex: Hex;
|
||||||
|
|
||||||
constructor(hex: Hex) {
|
constructor(hex: Hex) {
|
||||||
super();
|
super();
|
||||||
this.hex = hex;
|
this.hex = hex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public execute(simMain: SimMain) {
|
public execute(simMain: SimMain) {
|
||||||
const level = simMain.currentLevel;
|
const level = simMain.currentLevel;
|
||||||
if (!level) return;
|
if (!level) return;
|
||||||
|
|
||||||
const cellIndex = level.getCellIndex(this.hex);
|
const cellIndex = level.getCellIndex(this.hex);
|
||||||
const cell = level.cells[cellIndex];
|
const cell = level.cells[cellIndex];
|
||||||
if (cell.type != ECellType.Free || cell.tower != null || !level.reservePaths(simMain.gdRoot, this.hex)) {
|
cell.type = ECellType.Blocked;
|
||||||
return;
|
level.updateBlockedType(cell);
|
||||||
}
|
for (let i = 0; i < 6; ++i) {
|
||||||
cell.type = ECellType.Blocked;
|
const neighbourHex = Hex.neighbour(cell.hex, i);
|
||||||
level.updateBlockedType(cell);
|
const neighbourIndex = level.getCellIndex(neighbourHex);
|
||||||
for (let i = 0; i < 6; ++i) {
|
level.updateBlockedType(level.cells[neighbourIndex]);
|
||||||
const neighbourHex = Hex.neighbour(cell.hex, i);
|
}
|
||||||
const neighbourIndex = level.getCellIndex(neighbourHex);
|
level.updatePaths(simMain.gdRoot);
|
||||||
level.updateBlockedType(level.cells[neighbourIndex]);
|
}
|
||||||
}
|
|
||||||
level.updatePaths(simMain.gdRoot);
|
public check(simMain: SimMain): boolean {
|
||||||
}
|
const level = simMain.currentLevel;
|
||||||
|
if (!level) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const cellIndex = level.getCellIndex(this.hex);
|
||||||
|
const cell = level.cells[cellIndex];
|
||||||
|
if (!cell) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.type != ECellType.Free || cell.tower != null || !level.reservePaths(simMain.gdRoot, this.hex)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +1,75 @@
|
|||||||
import { Hex } from "../../util/Hex";
|
import { Hex } from '../../util/Hex';
|
||||||
import { ECellType } from "../ECellType";
|
import { ECellType } from '../ECellType';
|
||||||
import { SimEnemy } from "../SimEnemy";
|
import { SimEnemy } from '../SimEnemy';
|
||||||
import { SimMain } from "../SimMain";
|
import { SimMain } from '../SimMain';
|
||||||
import { SimTower } from "../SimTower";
|
import { SimTower } from '../SimTower';
|
||||||
import { SimCommand } from "./SimCommand";
|
import { SimCommand } from './SimCommand';
|
||||||
|
|
||||||
export class SimCommandCreateTower extends SimCommand {
|
export class SimCommandCreateTower extends SimCommand {
|
||||||
hex: Hex;
|
hex: Hex;
|
||||||
index: number;
|
index: number;
|
||||||
|
|
||||||
constructor(hex: Hex, index: number) {
|
constructor(hex: Hex, index: number) {
|
||||||
super();
|
super();
|
||||||
this.hex = hex;
|
this.hex = hex;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public execute(simMain: SimMain) {
|
public execute(simMain: SimMain) {
|
||||||
const level = simMain.currentLevel;
|
const level = simMain.currentLevel;
|
||||||
if (!level) {
|
if (!level) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cellIndex = level.getCellIndex(this.hex);
|
const cellIndex = level.getCellIndex(this.hex);
|
||||||
const cell = level.cells[cellIndex];
|
const cell = level.cells[cellIndex];
|
||||||
const towerData = simMain.gdRoot.towers[this.index];
|
const towerData = simMain.gdRoot.towers[this.index];
|
||||||
|
|
||||||
if (cell.type != ECellType.Free) {
|
level.enemies.forEach((enemy: SimEnemy) => {
|
||||||
return;
|
const hex = Hex.fromWorld(enemy.position);
|
||||||
}
|
const enemyCellIndex = level.getCellIndex(hex);
|
||||||
|
if (cellIndex == enemyCellIndex) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (cell.tower != null) {
|
if (level.currency < towerData.cost) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
level.enemies.forEach((enemy: SimEnemy) => {
|
if (level.reservePaths(simMain.gdRoot, this.hex)) {
|
||||||
const hex = Hex.fromWorld(enemy.position);
|
level.currency -= towerData.cost;
|
||||||
const enemyCellIndex = level.getCellIndex(hex);
|
cell.tower = new SimTower(this.index, Hex.toWorld(this.hex));
|
||||||
if (cellIndex == enemyCellIndex) {
|
level.updatePaths(simMain.gdRoot);
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
if (level.currency < towerData.cost) {
|
public check(simMain: SimMain): boolean {
|
||||||
return;
|
const level = simMain.currentLevel;
|
||||||
}
|
if (!level) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (level.reservePaths(simMain.gdRoot, this.hex)) {
|
const cellIndex = level.getCellIndex(this.hex);
|
||||||
level.currency -= towerData.cost;
|
const cell = level.cells[cellIndex];
|
||||||
cell.tower = new SimTower(this.index, Hex.toWorld(this.hex));
|
if (!cell) {
|
||||||
level.updatePaths(simMain.gdRoot);
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
const towerData = simMain.gdRoot.towers[this.index];
|
||||||
|
|
||||||
|
if (cell.type != ECellType.Free) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell.tower != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (level.currency < towerData.cost) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
import { GdRoot } from "../../data/GdRoot";
|
import { SimMain } from '../SimMain';
|
||||||
import { SimLevel } from "../SimLevel";
|
import { SimCommand } from './SimCommand';
|
||||||
import { SimMain } from "../SimMain";
|
|
||||||
import { SimCommand } from "./SimCommand";
|
|
||||||
|
|
||||||
export class SimCommandStartNextWave extends SimCommand {
|
export class SimCommandStartNextWave extends SimCommand {
|
||||||
|
public execute(simMain: SimMain) {
|
||||||
|
const level = simMain.currentLevel;
|
||||||
|
if (!level) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
execute(simMain: SimMain) {
|
const data = simMain.gdRoot.levels[level.index];
|
||||||
const level = simMain.currentLevel;
|
level.currentWave += 1;
|
||||||
if (!level) return;
|
if (!data.waves[level.currentWave]) {
|
||||||
|
level.paused = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
level.nextWaveStep = level.currentStep + simMain.gdRoot.simulation.waveDuration * simMain.gdRoot.simulation.stepsPerSecond - 1;
|
||||||
|
level.lastEnemySpawnStep = level.currentStep;
|
||||||
|
level.enemiesLeftToSpawn = data.waves[level.currentWave].amount;
|
||||||
|
}
|
||||||
|
|
||||||
const data = simMain.gdRoot.levels[level.index];
|
public check(simMain: SimMain): boolean {
|
||||||
level.currentWave += 1;
|
const level = simMain.currentLevel;
|
||||||
if (!data.waves[level.currentWave]) {
|
if (!level) {
|
||||||
level.paused = true;
|
return false;
|
||||||
return;
|
}
|
||||||
}
|
return true;
|
||||||
level.nextWaveStep = level.currentStep + simMain.gdRoot.simulation.waveDuration * simMain.gdRoot.simulation.stepsPerSecond - 1;
|
}
|
||||||
level.lastEnemySpawnStep = level.currentStep;
|
|
||||||
level.enemiesLeftToSpawn = data.waves[level.currentWave].amount;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export class VisLevel {
|
|||||||
private simMain: SimMain;
|
private simMain: SimMain;
|
||||||
private gdRoot: GdRoot;
|
private gdRoot: GdRoot;
|
||||||
assets: AssetPreloaderService;
|
assets: AssetPreloaderService;
|
||||||
|
private hoveredHex: Hex | null = null;
|
||||||
|
|
||||||
constructor(visMain: VisMain, simMain: SimMain, gdRoot: GdRoot, assets: AssetPreloaderService) {
|
constructor(visMain: VisMain, simMain: SimMain, gdRoot: GdRoot, assets: AssetPreloaderService) {
|
||||||
this.assets = assets;
|
this.assets = assets;
|
||||||
@@ -60,6 +61,15 @@ export class VisLevel {
|
|||||||
this.drawBackground();
|
this.drawBackground();
|
||||||
ctx.globalCompositeOperation = "source-over";
|
ctx.globalCompositeOperation = "source-over";
|
||||||
|
|
||||||
|
// Highlight hovered cell first (under everything else)
|
||||||
|
if (this.hoveredHex) {
|
||||||
|
const hoveredIdx = simLevel.getCellIndex(this.hoveredHex);
|
||||||
|
const hoveredCell = simLevel.cells[hoveredIdx];
|
||||||
|
if (hoveredCell && hoveredCell.distance <= gdLevel.radius) {
|
||||||
|
this.drawCellImage(ctx, hoveredCell, "cell-highlighted.svg");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
simLevel.cells.forEach((cell: SimCell) => {
|
simLevel.cells.forEach((cell: SimCell) => {
|
||||||
if (cell.distance > gdLevel.radius) {
|
if (cell.distance > gdLevel.radius) {
|
||||||
return;
|
return;
|
||||||
@@ -161,6 +171,10 @@ export class VisLevel {
|
|||||||
this.lastStep = currentStep;
|
this.lastStep = currentStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setHoveredHex(hex: Hex | null) {
|
||||||
|
this.hoveredHex = hex;
|
||||||
|
}
|
||||||
|
|
||||||
public getScreenCoords(hex: Hex): Vector2 {
|
public getScreenCoords(hex: Hex): Vector2 {
|
||||||
const coord = Hex.toPixel(hex, this.hexSize);
|
const coord = Hex.toPixel(hex, this.hexSize);
|
||||||
return new Vector2(coord.x + this.screenXOffset - this.screenCellWidth / 2, coord.y + this.screenYOffset - this.screenCellHeight / 2);
|
return new Vector2(coord.x + this.screenXOffset - this.screenCellWidth / 2, coord.y + this.screenYOffset - this.screenCellHeight / 2);
|
||||||
|
|||||||
@@ -1,115 +1,166 @@
|
|||||||
import { AssetPreloaderService } from "../../../assetPreloaderService";
|
import { AssetPreloaderService } from '../../../assetPreloaderService';
|
||||||
import { SplashComponent } from "../../splash/splash.component";
|
import { SimMain } from '../sim/SimMain';
|
||||||
import { SimMain } from "../sim/SimMain";
|
import { VisLevel } from './VisLevel';
|
||||||
import { VisLevel } from "./VisLevel";
|
import { Vector2 } from '../util/Vector2';
|
||||||
|
import { InputHandler } from './input/InputHandler';
|
||||||
|
import { Hex } from '../util/Hex';
|
||||||
|
import { DefaultInputHandler } from './input/DefaultInputHandler';
|
||||||
|
|
||||||
export class VisMain {
|
export class VisMain {
|
||||||
canvas: HTMLCanvasElement;
|
canvas: HTMLCanvasElement;
|
||||||
context: CanvasRenderingContext2D;
|
context: CanvasRenderingContext2D;
|
||||||
wallPattern: CanvasPattern;
|
wallPattern: CanvasPattern;
|
||||||
visLevel: VisLevel;
|
visLevel: VisLevel;
|
||||||
simMain: SimMain;
|
simMain: SimMain;
|
||||||
private startTimestamp: number = 0;
|
active: boolean = true;
|
||||||
private active: boolean = true;
|
assets: AssetPreloaderService;
|
||||||
private ready: boolean = false;
|
wrapper: HTMLDivElement;
|
||||||
private gap: number = 0;
|
inputHandlers: InputHandler[] = [];
|
||||||
assets: AssetPreloaderService;
|
private startTimestamp: number = 0;
|
||||||
wrapper: HTMLDivElement;
|
private ready: boolean = false;
|
||||||
|
private gap: number = 0;
|
||||||
|
|
||||||
constructor(simMain: SimMain, assets: AssetPreloaderService, wrapper: HTMLDivElement, canvas: HTMLCanvasElement) {
|
constructor(simMain: SimMain, assets: AssetPreloaderService, wrapper: HTMLDivElement, canvas: HTMLCanvasElement) {
|
||||||
this.assets = assets;
|
this.assets = assets;
|
||||||
this.simMain = simMain;
|
this.simMain = simMain;
|
||||||
this.canvas = canvas;
|
this.canvas = canvas;
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
this.context = this.canvas.getContext("2d")!;
|
this.context = this.canvas.getContext('2d')!;
|
||||||
this.context.globalCompositeOperation = "source-over";
|
this.context.globalCompositeOperation = 'source-over';
|
||||||
this.wallPattern = this.createPattern(assets.getImage("images/wall.png"), 48);
|
this.wallPattern = this.createPattern(assets.getImage('images/wall.png'), 48);
|
||||||
this.visLevel = new VisLevel(this, this.simMain, this.simMain.gdRoot, assets);
|
this.visLevel = new VisLevel(this, this.simMain, this.simMain.gdRoot, assets);
|
||||||
this.start();
|
this.setupInput();
|
||||||
}
|
this.start();
|
||||||
|
this.inputHandlers.push(new DefaultInputHandler(this));
|
||||||
|
}
|
||||||
|
|
||||||
private createPattern(image: HTMLImageElement, size: number): CanvasPattern {
|
private createPattern(image: HTMLImageElement, size: number): CanvasPattern {
|
||||||
const tempCanvas = document.createElement("canvas");
|
const tempCanvas = document.createElement('canvas');
|
||||||
const tempContext = tempCanvas.getContext("2d")!;
|
const tempContext = tempCanvas.getContext('2d')!;
|
||||||
|
|
||||||
tempCanvas.width = size;
|
tempCanvas.width = size;
|
||||||
tempCanvas.height = size;
|
tempCanvas.height = size;
|
||||||
tempContext.drawImage(image, 0, 0, image.width, image.height, 0, 0, size, size);
|
tempContext.drawImage(image, 0, 0, image.width, image.height, 0, 0, size, size);
|
||||||
|
|
||||||
return this.context.createPattern(tempCanvas, 'repeat')!;
|
return this.context.createPattern(tempCanvas, 'repeat')!;
|
||||||
}
|
}
|
||||||
|
|
||||||
private step(timestamp: number) {
|
private step(timestamp: number) {
|
||||||
if (!this.active) {
|
if (!this.active) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
requestAnimationFrame((timestamp: number) => {
|
requestAnimationFrame((timestamp: number) => {
|
||||||
this.step(timestamp);
|
this.step(timestamp);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.startTimestamp) {
|
const simLevel = this.simMain.currentLevel;
|
||||||
this.startTimestamp = timestamp;
|
if (!simLevel) {
|
||||||
}
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const simLevel = this.simMain.currentLevel;
|
let targetStep = Math.floor(((timestamp - this.startTimestamp) * this.simMain.gdRoot.simulation.stepsPerSecond) / 1000 - this.gap);
|
||||||
if (!simLevel) {
|
if (simLevel.paused) {
|
||||||
return;
|
this.gap += targetStep - this.simMain.currentStep;
|
||||||
}
|
}
|
||||||
|
this.simMain.executeUntilStep(targetStep);
|
||||||
|
this.visLevel.updateEveryFrame(targetStep);
|
||||||
|
this.onRender();
|
||||||
|
}
|
||||||
|
|
||||||
let targetStep = (timestamp - this.startTimestamp) * this.simMain.gdRoot.simulation.stepsPerSecond / 1000 - this.gap;
|
public onResized() {
|
||||||
if (simLevel.paused) {
|
// Always get the latest wrapper size
|
||||||
this.gap += targetStep - simLevel.currentStep;
|
const width = this.wrapper.clientWidth;
|
||||||
targetStep = simLevel.currentStep;
|
const height = this.wrapper.clientHeight;
|
||||||
}
|
const ratio = window.devicePixelRatio;
|
||||||
this.simMain.executeUntilStep(targetStep);
|
|
||||||
this.visLevel.updateEveryFrame(targetStep);
|
|
||||||
this.onRender();
|
|
||||||
};
|
|
||||||
|
|
||||||
public onResized() {
|
// Set canvas style to always fill the wrapper
|
||||||
// Always get the latest wrapper size
|
this.canvas.width = width * ratio;
|
||||||
const width = this.wrapper.clientWidth;
|
this.canvas.height = height * ratio;
|
||||||
const height = this.wrapper.clientHeight;
|
|
||||||
const ratio = window.devicePixelRatio;
|
|
||||||
|
|
||||||
// Set canvas style to always fill the wrapper
|
// Reset transform before scaling to avoid compounding
|
||||||
this.canvas.width = width * ratio;
|
this.context.setTransform(1, 0, 0, 1, 0, 0);
|
||||||
this.canvas.height = height * ratio;
|
this.context.scale(ratio, ratio);
|
||||||
|
|
||||||
// Reset transform before scaling to avoid compounding
|
this.visLevel.updateSize();
|
||||||
this.context.setTransform(1, 0, 0, 1, 0, 0);
|
}
|
||||||
this.context.scale(ratio, ratio);
|
|
||||||
|
|
||||||
this.visLevel.updateSize();
|
onRender() {
|
||||||
};
|
this.clear();
|
||||||
|
const ctx = this.context;
|
||||||
|
ctx.font = '12px Tahoma';
|
||||||
|
const simLevel = this.simMain.currentLevel;
|
||||||
|
if (!!simLevel) {
|
||||||
|
if (!this.ready) {
|
||||||
|
this.onResized();
|
||||||
|
this.ready = true;
|
||||||
|
}
|
||||||
|
this.visLevel.draw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private onRender() {
|
private clear() {
|
||||||
this.clear();
|
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||||
const ctx = this.context;
|
}
|
||||||
ctx.font = "12px Tahoma";
|
|
||||||
const simLevel = this.simMain.currentLevel;
|
|
||||||
if (!!simLevel) {
|
|
||||||
if (!this.ready) {
|
|
||||||
this.onResized();
|
|
||||||
this.ready = true;
|
|
||||||
}
|
|
||||||
this.visLevel.draw();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private clear() {
|
stop() {
|
||||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
this.active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
start() {
|
||||||
this.active = false;
|
this.active = true;
|
||||||
}
|
requestAnimationFrame((timestamp: number) => {
|
||||||
|
this.startTimestamp = timestamp - (this.simMain.currentStep / this.simMain.gdRoot.simulation.stepsPerSecond) * 1000;
|
||||||
|
this.step(timestamp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
start() {
|
private setupInput() {
|
||||||
this.active = true;
|
this.canvas.addEventListener('mousedown', (e) => this.handleMouseDown(e));
|
||||||
requestAnimationFrame((timestamp: number) => {
|
this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
|
||||||
this.step(timestamp);
|
window.addEventListener('keydown', (e) => this.handleKeyDown(e));
|
||||||
});
|
window.addEventListener('resize', (_) => this.onResized());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getHexFromMouseCoords(event: MouseEvent): Hex {
|
||||||
|
const rect = this.canvas.getBoundingClientRect();
|
||||||
|
const ratio = this.canvas.width / rect.width;
|
||||||
|
const x = ((event.clientX - rect.left) * ratio) / window.devicePixelRatio;
|
||||||
|
const y = ((event.clientY - rect.top) * ratio) / window.devicePixelRatio;
|
||||||
|
const coords = new Vector2(x, y);
|
||||||
|
const hex = this.visLevel.getHexFromScreenCoords(coords);
|
||||||
|
return hex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMouseDown(event: MouseEvent) {
|
||||||
|
const hex = this.getHexFromMouseCoords(event);
|
||||||
|
for (let i = this.inputHandlers.length - 1; i >= 0; i--) {
|
||||||
|
const handled = this.inputHandlers[i].onMouseDown?.(event, hex);
|
||||||
|
if (handled) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleMouseMove(event: MouseEvent) {
|
||||||
|
const hex = this.getHexFromMouseCoords(event);
|
||||||
|
for (let i = this.inputHandlers.length - 1; i >= 0; i--) {
|
||||||
|
const handled = this.inputHandlers[i].onMouseMove?.(event, hex);
|
||||||
|
if (handled) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleKeyDown(event: KeyboardEvent) {
|
||||||
|
for (let i = this.inputHandlers.length - 1; i >= 0; i--) {
|
||||||
|
const handled = this.inputHandlers[i].onKeyDown?.(event);
|
||||||
|
if (handled) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/app/components/game/vis/input/DefaultInputHandler.ts
Normal file
47
src/app/components/game/vis/input/DefaultInputHandler.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { SimCommandBlockTerrain } from '../../sim/commands/SimCommandBlockTerrain';
|
||||||
|
import { SimCommandCreateTower } from '../../sim/commands/SimCommandCreateTower';
|
||||||
|
import { Hex } from '../../util/Hex';
|
||||||
|
import { VisMain } from '../VisMain';
|
||||||
|
import { HexInteractionInputHandler } from './HexInteractionInputHandler';
|
||||||
|
import { InputHandler } from './InputHandler';
|
||||||
|
|
||||||
|
export class DefaultInputHandler implements InputHandler {
|
||||||
|
visMain: VisMain;
|
||||||
|
|
||||||
|
constructor(visMain: VisMain) {
|
||||||
|
this.visMain = visMain;
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event: KeyboardEvent): boolean {
|
||||||
|
if (event.code === 'Escape') {
|
||||||
|
if (this.visMain.inputHandlers.length > 1) {
|
||||||
|
this.visMain.inputHandlers.pop();
|
||||||
|
this.visMain.visLevel.setHoveredHex(null);
|
||||||
|
event.preventDefault();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (event.code === 'Space') {
|
||||||
|
if (this.visMain.active) {
|
||||||
|
this.visMain.stop();
|
||||||
|
} else {
|
||||||
|
this.visMain.start();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (event.code === 'KeyT') {
|
||||||
|
this.visMain.inputHandlers.push(new HexInteractionInputHandler(this.visMain, (hex) => new SimCommandCreateTower(hex, 0)));
|
||||||
|
return true;
|
||||||
|
} else if (event.code === 'KeyB') {
|
||||||
|
this.visMain.inputHandlers.push(new HexInteractionInputHandler(this.visMain, (hex) => new SimCommandBlockTerrain(hex)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown(event: MouseEvent, hex: Hex): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseMove(event: MouseEvent, hex: Hex): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { SimCommand } from '../../sim/commands/SimCommand';
|
||||||
|
import { Hex } from '../../util/Hex';
|
||||||
|
import { VisMain } from '../VisMain';
|
||||||
|
import { InputHandler } from './InputHandler';
|
||||||
|
|
||||||
|
export class HexInteractionInputHandler implements InputHandler {
|
||||||
|
private hexCallback: (hex: Hex) => SimCommand;
|
||||||
|
private command: SimCommand | null = null;
|
||||||
|
visMain: VisMain;
|
||||||
|
|
||||||
|
constructor(visMain: VisMain, hexCallback: (hex: Hex) => SimCommand) {
|
||||||
|
this.visMain = visMain;
|
||||||
|
this.hexCallback = hexCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event: KeyboardEvent): boolean {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseDown(event: MouseEvent, hex: Hex): boolean {
|
||||||
|
if (!this.command) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.visMain.simMain.addCommand(this.command);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseMove(event: MouseEvent, hex: Hex): boolean {
|
||||||
|
this.command = this.hexCallback(hex);
|
||||||
|
if (this.command && this.command.check(this.visMain.simMain)) {
|
||||||
|
this.visMain.visLevel.setHoveredHex(hex);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.visMain.visLevel.setHoveredHex(null);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/app/components/game/vis/input/InputHandler.ts
Normal file
7
src/app/components/game/vis/input/InputHandler.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { Hex } from "../../util/Hex";
|
||||||
|
|
||||||
|
export interface InputHandler {
|
||||||
|
onKeyDown?: (event: KeyboardEvent) => boolean;
|
||||||
|
onMouseDown?: (event: MouseEvent, hex: Hex) => boolean;
|
||||||
|
onMouseMove?: (event: MouseEvent, hex: Hex) => boolean;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user