import { AssetPreloaderService } from "../../../assetPreloaderService"; import { GdRoot } from "../data/GdRoot"; import { ECellType } from "../sim/ECellType"; import { SimCell } from "../sim/SimCell"; import { SimEnemy } from "../sim/SimEnemy"; import { SimLevel } from "../sim/SimLevel"; import { SimMain } from "../sim/SimMain"; import { SimProjectile } from "../sim/SimProjectile"; import { Hex } from "../util/Hex"; import { Vector2 } from "../util/Vector2"; import { VisEnemy } from "./VisEnemy"; import { VisMain } from "./VisMain"; import { VisProjectile } from "./VisProjectile"; export class VisLevel { private screenCellWidth: number = -1; private screenCellHeight: number = -1; private screenXOffset: number = -1; private screenYOffset: number = -1; private hexSize: number = -1; private lastStep: number = -1; private projectileMap: Map; private enemyMap: Map; private background: HTMLCanvasElement | null = null; private simLevel: SimLevel | null = null; private visMain: VisMain; private simMain: SimMain; private gdRoot: GdRoot; assets: AssetPreloaderService; constructor(visMain: VisMain, simMain: SimMain, gdRoot: GdRoot, assets: AssetPreloaderService) { this.assets = assets; this.visMain = visMain; this.simMain = simMain; this.gdRoot = gdRoot this.enemyMap = new Map(); this.projectileMap = new Map(); this.reset(); } public reset() { this.projectileMap.clear(); this.enemyMap.clear(); this.background = null; } public draw() { const ctx = this.visMain.context; const simLevel = this.simMain.currentLevel; if (simLevel == null) { return; } const gdLevel = this.gdRoot.levels[simLevel.index]; if (simLevel != this.simLevel) { this.reset(); this.simLevel = simLevel; } this.drawBackground(); ctx.globalCompositeOperation = "source-over"; simLevel.cells.forEach((cell: SimCell) => { if (cell.distance > gdLevel.radius) { return; } this.drawCell(cell); }); simLevel.enemies.forEach((enemy: SimEnemy) => { this.drawEnemy(enemy); }); simLevel.projectiles.forEach((projectile: SimProjectile) => { this.drawProjectile(projectile); }); ctx.fillStyle = "white"; ctx.fillText("Currency: " + simLevel.currency, 5, 15); ctx.fillText("Current wave: " + simLevel.currentWave, 5, 35); ctx.fillText("Enemies left: " + simLevel.enemiesLeftToSpawn, 5, 55); ctx.fillText("Current step: " + simLevel.currentStep, 5, 75); } public updateSize() { const simLevel = this.simMain.currentLevel; if (simLevel == null) { return; } const gdLevel = this.gdRoot.levels[simLevel.index]; const minSize = Math.min(this.visMain.canvas.height, this.visMain.canvas.width * Math.sqrt(3) / 2); this.hexSize = Math.floor(minSize / (4 * (gdLevel.radius - 1) - 4)); this.screenCellHeight = Math.ceil(2 * this.hexSize); this.screenCellHeight = Math.ceil(this.screenCellHeight * 0.5) * 2; this.screenCellWidth = Math.ceil(Math.sqrt(3) / 2 * this.screenCellHeight); this.screenCellWidth = Math.ceil(this.screenCellWidth * 0.5) * 2; this.screenXOffset = this.screenCellWidth * (gdLevel.radius + 0.5); this.screenYOffset = this.screenCellHeight * (gdLevel.radius + 0.5); const width = this.screenCellWidth * (gdLevel.radius * 2 + 1); const height = this.screenCellHeight * (gdLevel.radius * 2 + 1); this.screenXOffset += (this.visMain.canvas.width - width) * 0.5; this.screenYOffset += (this.visMain.canvas.height - height) * 0.5; this.screenXOffset = Math.floor(this.screenXOffset); this.screenYOffset = Math.floor(this.screenYOffset); this.background = null; this.enemyMap.clear(); } public updateEveryFrame(currentStep: number) { const simLevel = this.simMain.currentLevel; if (simLevel == null) { return; } const t = currentStep - Math.floor(currentStep); const deadEnemies: SimEnemy[] = []; simLevel.enemies.forEach((simEnemy: SimEnemy) => { if (simEnemy.dead) { deadEnemies.push(simEnemy); return; } const visEnemy = this.enemyMap.get(simEnemy); if (!visEnemy) { this.enemyMap.set(simEnemy, new VisEnemy(this.gdRoot, this.assets, simEnemy, this.screenCellWidth, this.screenCellHeight)); } else if (Math.floor(currentStep) != Math.floor(this.lastStep)) { visEnemy.advanceStep(); } else { visEnemy.update(t); } }); for (const deadEnemy of deadEnemies) { this.enemyMap.delete(deadEnemy); } const deadProjectiles: SimProjectile[] = []; simLevel.projectiles.forEach((simProjectile: SimProjectile) => { if (simProjectile.dead) { deadProjectiles.push(simProjectile); return; } const visProjectile = this.projectileMap.get(simProjectile); if (!visProjectile) { this.projectileMap.set(simProjectile, new VisProjectile(simProjectile)); } else if (Math.floor(currentStep) != Math.floor(this.lastStep)) { visProjectile.advanceStep(); } }); for (const deadProjectile of deadProjectiles) { this.projectileMap.delete(deadProjectile); } this.lastStep = currentStep; } public getScreenCoords(hex: Hex): Vector2 { const coord = Hex.toPixel(hex, this.hexSize); return new Vector2(coord.x + this.screenXOffset - this.screenCellWidth / 2, coord.y + this.screenYOffset - this.screenCellHeight / 2); } public getHexFromScreenCoords(coords: Vector2): Hex { const x = coords.x - this.screenXOffset; const y = coords.y - this.screenYOffset; return Hex.fromPixel(new Vector2(x, y), this.hexSize); } private drawBackground() { const ctx = this.visMain.context; const simLevel = this.simMain.currentLevel; if (simLevel == null) { return; } const gdLevel = this.gdRoot.levels[simLevel.index]; if (this.background == null) { const backgroundCanvas = this.visMain.canvas.cloneNode() as HTMLCanvasElement; const backgroundContext = backgroundCanvas.getContext("2d")!; this.background = backgroundCanvas; simLevel.cells.forEach((cell: SimCell) => { if (cell.distance > gdLevel.radius) { return; } if (cell.blockedType != -1 && cell.type == ECellType.Blocked) { this.drawCellImage(backgroundContext, cell, "cell-blocked-" + (cell.blockedType | 0) + ".svg"); } }); backgroundContext.globalCompositeOperation = "source-atop"; backgroundContext.fillStyle = this.visMain.wallPattern; backgroundContext.fillRect(0, 0, this.visMain.canvas.width, this.visMain.canvas.height); const cellCanvas = this.visMain.canvas.cloneNode() as HTMLCanvasElement; const cellContext = cellCanvas.getContext("2d")!; simLevel.cells.forEach((cell: SimCell) => { if (cell.distance > gdLevel.radius) { return; } if (cell.type != ECellType.Entry) { this.drawCellImage(cellContext, cell, "cell.svg"); } }); backgroundContext.globalCompositeOperation = "destination-over"; backgroundContext.drawImage(cellCanvas, 0, 0); backgroundContext.globalCompositeOperation = "source-over"; } ctx.drawImage(this.background, 0, 0); } private drawProjectile(simProjectile: SimProjectile) { const visProjectile = this.projectileMap.get(simProjectile); if (!visProjectile) { return; } const t = this.lastStep - Math.floor(this.lastStep); const positions = visProjectile.positions; if (!positions) { return; } const pos = Vector2.lerp(positions[0], positions[1], t); const width = this.screenCellWidth * simProjectile.size; const height = this.screenCellHeight * simProjectile.size; this.visMain.context.drawImage(this.assets.getImage("images/projectile.svg"), this.screenXOffset + pos.x * this.hexSize - width / 2, this.screenYOffset + pos.y * this.hexSize - height / 2, width, height); } private drawEnemy(simEnemy: SimEnemy) { const visEnemy = this.enemyMap.get(simEnemy); if (!visEnemy) { return; } const t = this.lastStep - Math.floor(this.lastStep); const positions = visEnemy.positions; if (!positions) { return; } const pos = Vector2.lerp(positions[0], positions[1], t); this.visMain.context.drawImage( visEnemy.image, this.screenXOffset + pos.x * this.hexSize - this.screenCellWidth / 2, this.screenYOffset + pos.y * this.hexSize - this.screenCellHeight / 2, this.screenCellWidth, this.screenCellHeight); } private drawCell(cell: SimCell) { const simLevel = this.simMain.currentLevel; if (simLevel == null) { return; } this.visMain.context.fillStyle = "rgba(192, 192, 192, 0.25)"; if (cell.type == ECellType.Entry) { this.drawCellImage(this.visMain.context, cell, "cell-entry-" + (cell.blockedType | 0) + ".svg"); } const highlightedCell = simLevel.cells[simLevel.highlightedIndex]; if (!!highlightedCell) { let draw = highlightedCell.index == cell.index; if (draw && highlightedCell.pathsToTarget != null) { for (const routeIdx in highlightedCell.pathsToTarget) { for (const idx in highlightedCell.pathsToTarget[routeIdx]) { if (highlightedCell.pathsToTarget[routeIdx][idx] == highlightedCell.index) { draw = true; break; } } } } if (draw) { this.drawCellImage(this.visMain.context, highlightedCell, "cell-highlighted.svg"); this.visMain.context.fillStyle = "rgba(0, 0, 0, 1)"; } } if (cell.tower != null) { this.drawCellImage(this.visMain.context, cell, "tower-" + (cell.tower.index | 0) + ".svg"); } const coords = this.getScreenCoords(cell.hex); this.visMain.context.fillText("(" + cell.hex.col + ", " + cell.hex.row + ")", coords.x + 10, coords.y + this.screenCellHeight / 2 + 5); } private drawCellImage(context: CanvasRenderingContext2D, cell: SimCell, name: string) { const coords = this.getScreenCoords(cell.hex); context.drawImage(this.assets.getImage("images/" + name), coords.x, coords.y, this.screenCellWidth, this.screenCellHeight); } }