diff --git a/src/client/graphics/NameBoxCalculator.ts b/src/client/graphics/NameBoxCalculator.ts index 46aed9ef4..9703fa9ec 100644 --- a/src/client/graphics/NameBoxCalculator.ts +++ b/src/client/graphics/NameBoxCalculator.ts @@ -1,5 +1,5 @@ -import { Game, Player, Tile, Cell } from '../../core/game/Game'; -import { GameView, NameViewData } from '../../core/GameView'; +import { Game, Player, Tile, Cell, NameViewData } from '../../core/game/Game'; +import { GameView } from '../../core/GameView'; import { calculateBoundingBox, within } from '../../core/Util'; export interface Point { diff --git a/src/client/graphics/layers/radial/BuildMenu.ts b/src/client/graphics/layers/radial/BuildMenu.ts index 74d6d8ae7..f9e83107a 100644 --- a/src/client/graphics/layers/radial/BuildMenu.ts +++ b/src/client/graphics/layers/radial/BuildMenu.ts @@ -1,7 +1,7 @@ import { LitElement, html, css } from 'lit'; import { customElement, state } from 'lit/decorators.js'; import { EventBus } from '../../../../core/EventBus'; -import { Cell, Game, Player, UnitType } from '../../../../core/game/Game'; +import { Cell, Game, Player, PlayerActions, UnitType } from '../../../../core/game/Game'; import { BuildUnitIntentEvent } from '../../../Transport'; import atomBombIcon from '../../../../../resources/images/NukeIconWhite.svg'; import hydrogenBombIcon from '../../../../../resources/images/MushroomCloudIconWhite.svg'; @@ -14,7 +14,7 @@ import shieldIcon from '../../../../../resources/images/ShieldIconWhite.svg'; import cityIcon from '../../../../../resources/images/CityIconWhite.svg'; import { renderNumber } from '../../../Utils'; import { ContextMenuEvent } from '../../../InputHandler'; -import { GameView, PlayerActions, PlayerView } from '../../../../core/GameView'; +import { GameView, PlayerView } from '../../../../core/GameView'; interface BuildItemDisplay { unitType: UnitType diff --git a/src/client/graphics/layers/radial/RadialMenu.ts b/src/client/graphics/layers/radial/RadialMenu.ts index 0e571eae3..96d4b1d08 100644 --- a/src/client/graphics/layers/radial/RadialMenu.ts +++ b/src/client/graphics/layers/radial/RadialMenu.ts @@ -1,5 +1,5 @@ import { EventBus } from "../../../../core/EventBus"; -import { AllPlayers, Cell, Game, Player, Tile, UnitType } from "../../../../core/game/Game"; +import { AllPlayers, Cell, Game, Player, PlayerActions, Tile, UnitType } from "../../../../core/game/Game"; import { ClientID } from "../../../../core/Schemas"; import { and, bfs, dist, manhattanDist, manhattanDistWrapped, sourceDstOceanShore, targetTransportTile } from "../../../../core/Util"; import { ContextMenuEvent, MouseUpEvent, ShowBuildMenuEvent } from "../../../InputHandler"; @@ -20,7 +20,7 @@ import { EmojiTable } from "./EmojiTable"; import { UIState } from "../../UIState"; import { BuildMenu } from "./BuildMenu"; import { consolex } from "../../../../core/Consolex"; -import { GameView, PlayerActions, PlayerView } from "../../../../core/GameView"; +import { GameView, PlayerView } from "../../../../core/GameView"; enum Slot { diff --git a/src/core/GameRunner.ts b/src/core/GameRunner.ts index dbe1e4bc9..a009b183e 100644 --- a/src/core/GameRunner.ts +++ b/src/core/GameRunner.ts @@ -4,12 +4,12 @@ import { getConfig } from "./configuration/Config"; import { EventBus } from "./EventBus"; import { Executor } from "./execution/ExecutionManager"; import { WinCheckExecution } from "./execution/WinCheckExecution"; -import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, MutableTile, Player, PlayerID, Tile, UnitType } from "./game/Game"; +import { Cell, DisplayMessageUpdate, Game, GameUpdateType, MessageType, MutableGame, MutableTile, NameViewData, Player, PlayerActions, PlayerID, Tile, UnitType } from "./game/Game"; import { createGame } from "./game/GameImpl"; import { loadTerrainMap } from "./game/TerrainMapLoader"; -import { GameUpdateViewData, NameViewData, packTileData, PlayerActions, PlayerViewData } from "./GameView"; import { GameConfig, Turn } from "./Schemas"; import { and, bfs, dist, targetTransportTile } from "./Util"; +import { GameUpdateViewData, packTileData } from "./GameView"; export async function createGameRunner(gameID: string, gameConfig: GameConfig, callBack: (gu: GameUpdateViewData) => void): Promise { const config = getConfig(gameConfig) @@ -69,14 +69,14 @@ export class GameRunner { const playerViewData = {} for (const player of this.game.allPlayers()) { - const viewData = player.toViewData() + const viewData = player.toUpdate() viewData.nameViewData = this.playerToName.get(player.id()) playerViewData[player.id()] = viewData } this.callBack({ tick: this.game.ticks(), - units: this.game.units().map(u => u.toViewData()), + units: this.game.units().map(u => u.toUpdate()), packedTileUpdates: updates.filter(u => u.type == GameUpdateType.Tile).map(u => packTileData(u)), players: playerViewData }) diff --git a/src/core/GameView.ts b/src/core/GameView.ts index a6d14561f..4bc316b97 100644 --- a/src/core/GameView.ts +++ b/src/core/GameView.ts @@ -1,26 +1,10 @@ -import { GameUpdateType, MessageType, Player, Tile, TileUpdate, Unit } from './game/Game'; +import { GameUpdateType, MapPos, MessageType, NameViewData, Player, PlayerActions, PlayerUpdate, Tile, TileUpdate, Unit, UnitUpdate } from './game/Game'; import { Config } from "./configuration/Config"; import { Alliance, AllianceRequest, AllPlayers, Cell, DefenseBonus, EmojiMessage, Execution, ExecutionView, Game, Gold, MutableTile, Nation, PlayerID, PlayerInfo, PlayerType, Relation, TerrainMap, TerrainTile, TerrainType, TerraNullius, Tick, UnitInfo, UnitType } from "./game/Game"; import { ClientID } from "./Schemas"; import { TerraNulliusImpl } from './game/TerraNulliusImpl'; import { WorkerClient } from './worker/WorkerClient'; -export interface ViewSerializable { - toViewData(): T; -} - -export interface ViewData { - // Base view data properties if any -} - -export interface TileViewData extends ViewData { - x: number - y: number - smallID: number, - hasFallout: boolean - hasDefenseBonus: boolean - isBorder: boolean -} export class TileView { @@ -63,21 +47,14 @@ export class TileView { } } -export interface UnitViewData extends ViewData { - id: number, - type: UnitType, - troops: number, - x: number, - y: number, - owner: string, - isActive: boolean, - health?: number -} - export class UnitView implements Unit { - constructor(private gameView: GameView, private data: UnitViewData) { } + constructor(private gameView: GameView, private data: UnitUpdate) { } - update(data: UnitViewData) { + lastTile(): Tile { + return this.gameView.tile(new Cell(this.data.lastPos.x, this.data.lastPos.y)) + } + + update(data: UnitUpdate) { this.data = data } @@ -86,16 +63,16 @@ export class UnitView implements Unit { } type(): UnitType { - return this.data.type + return this.data.unitType } troops(): number { return this.data.troops } tile(): Tile { - return this.gameView.tile(new Cell(this.data.x, this.data.y)) + return this.gameView.tile(new Cell(this.data.pos.x, this.data.pos.y)) } owner(): PlayerView { - return this.gameView.player(this.data.owner) + return this.gameView.playerBySmallID(this.data.ownerID) } isActive(): boolean { return this.data.isActive @@ -108,49 +85,8 @@ export class UnitView implements Unit { } } -export interface NameViewData { - x: number, - y: number, - size: number, -} - -export interface PlayerViewData extends ViewData { - nameViewData?: NameViewData, - - clientID: ClientID, - name: string, - displayName: string, - id: PlayerID, - smallID: number, - type: PlayerType, - isAlive: boolean, - tilesOwned: number, - allies: PlayerID[], - gold: number, - population: number, - workers: number, - troops: number, - targetTroopRatio: number -} - -export interface PlayerActions { - canBoat: boolean - canAttack: boolean - buildableUnits: UnitType[] - interaction?: PlayerInteraction -} - -export interface PlayerInteraction { - sharedBorder: boolean - canSendEmoji: boolean - canSendAllianceRequest: boolean - canBreakAlliance: boolean - canTarget: boolean - canDonate: boolean -} - export class PlayerView implements Player { - constructor(private game: GameView, public data: PlayerViewData) { } + constructor(private game: GameView, public data: PlayerUpdate) { } async actions(tile: Tile): Promise { return this.game.worker.playerInteraction(this.id(), tile) @@ -179,7 +115,7 @@ export class PlayerView implements Player { return this.data.id } type(): PlayerType { - return this.data.type + return this.data.playerType } isAlive(): boolean { return this.data.isAlive @@ -272,11 +208,10 @@ export class PlayerView implements Player { } } -export interface GameUpdateViewData extends ViewData { +export interface GameUpdateViewData { tick: number - units: UnitViewData[] - players: Record - tileUpdates?: TileUpdate[] + units: UnitUpdate[] + players: Record packedTileUpdates: Uint16Array[] } @@ -286,6 +221,7 @@ export class GameView { private smallIDToID = new Map() private _players = new Map() private _units = new Map() + private updatedTiles: TileView[] = [] constructor(public worker: WorkerClient, private _config: Config, private _terrainMap: TerrainMap) { // Initialize the 2D array @@ -300,7 +236,6 @@ export class GameView { this.lastUpdate = { tick: 0, units: [], - tileUpdates: [], packedTileUpdates: [], players: {} } @@ -308,10 +243,14 @@ export class GameView { public update(gu: GameUpdateViewData) { this.lastUpdate = gu - this.lastUpdate.tileUpdates = this.lastUpdate.packedTileUpdates.map(tu => unpackTileData(tu)) - this.lastUpdate.tileUpdates.forEach(tu => { + + const updated = new Set() + this.lastUpdate.packedTileUpdates.map(tu => unpackTileData(tu)).forEach(tu => { this.tiles[tu.pos.x][tu.pos.y].data = tu + updated.add(tu.pos) }) + this.updatedTiles = Array.from(updated).map(pos => this.tiles[pos.x][pos.y]) + Object.entries(gu.players).forEach(([key, value]) => { this.smallIDToID.set(value.smallID, key); if (this._players.has(key)) { @@ -330,7 +269,7 @@ export class GameView { } recentlyUpdatedTiles(): TileView[] { - return this.lastUpdate.tileUpdates.map(tu => new TileView(this, tu, this._terrainMap.terrain(new Cell(tu.pos.x, tu.pos.y)))) + return this.updatedTiles } player(id: PlayerID): PlayerView { diff --git a/src/core/game/Game.ts b/src/core/game/Game.ts index 8e25b2698..a0dbadf15 100644 --- a/src/core/game/Game.ts +++ b/src/core/game/Game.ts @@ -2,7 +2,6 @@ import { Config } from "../configuration/Config" import { GameEvent } from "../EventBus" import { ClientID, GameConfig, GameID } from "../Schemas" import { SearchNode } from "../pathfinding/AStar" -import { PlayerViewData, TileViewData, UnitViewData, ViewSerializable } from "../GameView" export type PlayerID = string export type Tick = number @@ -206,7 +205,7 @@ export interface Tile extends SearchNode { hasDefenseBonus(): boolean } -export interface MutableTile extends Tile, ViewSerializable { +export interface MutableTile extends Tile { // defense bonus against this player defenseBonus(player: Player): number borders(other: Player | TerraNullius): boolean @@ -223,15 +222,17 @@ export interface Unit { isActive(): boolean hasHealth(): boolean health(): number + lastTile(): Tile } -export interface MutableUnit extends Unit, ViewSerializable { +export interface MutableUnit extends Unit { move(tile: Tile): void owner(): MutablePlayer setTroops(troops: number): void info(): UnitInfo delete(displayerMessage?: boolean): void modifyHealth(delta: number): void + toUpdate(): UnitUpdate } export interface TerraNullius { @@ -288,7 +289,7 @@ export interface Player { lastTileChange(): Tick } -export interface MutablePlayer extends Player, ViewSerializable { +export interface MutablePlayer extends Player { // Targets for this player targets(): Player[] // Targets of player and all allies. @@ -328,6 +329,8 @@ export interface MutablePlayer extends Player, ViewSerializable buildUnit(type: UnitType, troops: number, tile: Tile): MutableUnit captureUnit(unit: MutableUnit): void + + toUpdate(): PlayerUpdate } export interface Game { @@ -375,6 +378,7 @@ export interface MutableGame extends Game { export enum GameUpdateType { Tile, Unit, + Player, DisplayEvent, AllianceRequest, AllianceRequestReply, @@ -385,8 +389,31 @@ export enum GameUpdateType { WinUpdate } +export interface NameViewData { + x: number, + y: number, + size: number, +} + +export interface PlayerActions { + canBoat: boolean + canAttack: boolean + buildableUnits: UnitType[] + interaction?: PlayerInteraction +} + +export interface PlayerInteraction { + sharedBorder: boolean + canSendEmoji: boolean + canSendAllianceRequest: boolean + canBreakAlliance: boolean + canTarget: boolean + canDonate: boolean +} + export type GameUpdate = TileUpdate | UnitUpdate + | PlayerUpdate | AllianceRequestUpdate | AllianceRequestReplyUpdate | BrokeAllianceUpdate @@ -412,11 +439,31 @@ export interface UnitUpdate { id: number ownerID: number pos: MapPos - oldPos: MapPos + lastPos: MapPos isActive: boolean - health?: boolean + health?: number } +export interface PlayerUpdate { + type: GameUpdateType.Player + nameViewData?: NameViewData, + clientID: ClientID, + name: string, + displayName: string, + id: PlayerID, + smallID: number, + playerType: PlayerType, + isAlive: boolean, + tilesOwned: number, + allies: PlayerID[], + gold: number, + population: number, + workers: number, + troops: number, + targetTroopRatio: number +} + + export interface AllianceRequestUpdate { type: GameUpdateType.AllianceRequest requestorID: number, diff --git a/src/core/game/GameImpl.ts b/src/core/game/GameImpl.ts index 127a07f53..700cb80a0 100644 --- a/src/core/game/GameImpl.ts +++ b/src/core/game/GameImpl.ts @@ -416,8 +416,8 @@ export class GameImpl implements MutableGame { return false } - public fireUnitUpdateEvent(unit: Unit, oldTile: Tile) { - this.updates.push((unit as UnitImpl).toUpdate(oldTile)) + public fireUnitUpdateEvent(unit: Unit) { + this.updates.push((unit as UnitImpl).toUpdate()) } target(targeter: Player, target: Player) { diff --git a/src/core/game/PlayerImpl.ts b/src/core/game/PlayerImpl.ts index 85eca3ca6..58a2bd202 100644 --- a/src/core/game/PlayerImpl.ts +++ b/src/core/game/PlayerImpl.ts @@ -1,4 +1,4 @@ -import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, EmojiMessage, AllPlayers, Gold, UnitType, Unit, MutableUnit, Relation, MutableTile } from "./Game"; +import { MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, EmojiMessage, AllPlayers, Gold, UnitType, Unit, MutableUnit, Relation, MutableTile, PlayerUpdate, GameUpdateType } from "./Game"; import { ClientID } from "../Schemas"; import { assertNever, bfs, closestOceanShoreFromPlayer, dist, distSortUnit, manhattanDist, manhattanDistWrapped, processName, simpleHash, sourceDstOceanShore, within } from "../Util"; import { CellString, GameImpl } from "./GameImpl"; @@ -6,7 +6,6 @@ import { UnitImpl } from "./UnitImpl"; import { TileImpl } from "./TileImpl"; import { MessageType } from './Game'; import { renderTroops } from "../../client/Utils"; -import { PlayerViewData } from "../GameView"; interface Target { tick: Tick @@ -56,14 +55,15 @@ export class PlayerImpl implements MutablePlayer { this._displayName = this._name // processName(this._name) } - toViewData(): PlayerViewData { + toUpdate(): PlayerUpdate { return { + type: GameUpdateType.Player, clientID: this.clientID(), name: this.name(), displayName: this.displayName(), id: this.id(), smallID: this.smallID(), - type: this.type(), + playerType: this.type(), isAlive: this.isAlive(), tilesOwned: this.numTilesOwned(), allies: this.allies().map(p => p.id()), @@ -417,7 +417,7 @@ export class PlayerImpl implements MutablePlayer { (prev as PlayerImpl)._units = (prev as PlayerImpl)._units.filter(u => u != unit); (unit as UnitImpl)._owner = this this._units.push(unit as UnitImpl) - this.gs.fireUnitUpdateEvent(unit, unit.tile()) + this.gs.fireUnitUpdateEvent(unit) this.gs.displayMessage(`${unit.type()} captured by ${this.displayName()}`, MessageType.ERROR, prev.id()) this.gs.displayMessage(`Captured ${unit.type()} from ${prev.displayName()}`, MessageType.SUCCESS, this.id()) } @@ -428,7 +428,7 @@ export class PlayerImpl implements MutablePlayer { this._units.push(b); this.removeGold(cost) this.removeTroops(troops) - this.gs.fireUnitUpdateEvent(b, b.tile()); + this.gs.fireUnitUpdateEvent(b); return b; } diff --git a/src/core/game/TileImpl.ts b/src/core/game/TileImpl.ts index fa0959026..085f94f85 100644 --- a/src/core/game/TileImpl.ts +++ b/src/core/game/TileImpl.ts @@ -4,7 +4,6 @@ import { TerrainTileImpl } from "./TerrainMapLoader"; import { GameImpl } from "./GameImpl"; import { PlayerImpl } from "./PlayerImpl"; import { TerraNulliusImpl } from "./TerraNulliusImpl"; -import { TileView, TileViewData, ViewData, ViewSerializable } from "../GameView"; export class TileImpl implements MutableTile { @@ -23,10 +22,6 @@ export class TileImpl implements MutableTile { private readonly _terrain: TerrainTileImpl ) { } - toViewData(): TileViewData { - throw new Error("Method not implemented."); - } - toUpdate(): TileUpdate { return { type: GameUpdateType.Tile, diff --git a/src/core/game/UnitImpl.ts b/src/core/game/UnitImpl.ts index 273f84793..1aa0281ff 100644 --- a/src/core/game/UnitImpl.ts +++ b/src/core/game/UnitImpl.ts @@ -1,5 +1,4 @@ import { GameUpdateType, MessageType, UnitUpdate } from './Game'; -import { UnitViewData, ViewData, ViewSerializable } from "../GameView"; import { simpleHash, within } from "../Util"; import { MutableUnit, Tile, TerraNullius, UnitType, Player, UnitInfo } from "./Game"; import { GameImpl } from "./GameImpl"; @@ -9,6 +8,7 @@ import { PlayerImpl } from "./PlayerImpl"; export class UnitImpl implements MutableUnit { private _active = true; private _health: number + private _lastTile: Tile = null constructor( private _type: UnitType, @@ -20,9 +20,11 @@ export class UnitImpl implements MutableUnit { ) { // default to half health (or 1 is no health specified) this._health = (this.g.unitInfo(_type).maxHealth ?? 2) / 2 + this._lastTile = _tile } - toUpdate(oldTile: Tile): UnitUpdate { + + toUpdate(): UnitUpdate { return { type: GameUpdateType.Unit, unitType: this._type, @@ -31,20 +33,7 @@ export class UnitImpl implements MutableUnit { ownerID: this._owner.smallID(), isActive: this._active, pos: this._tile.cell().pos(), - oldPos: oldTile.cell().pos() - } - } - - toViewData(): UnitViewData { - return { - id: this._id, - type: this._type, - troops: this._troops, - x: this.tile().cell().x, - y: this.tile().cell().y, - owner: this.owner().id(), - isActive: this.isActive(), - health: this._health, + lastPos: this._lastTile.cell().pos() } } @@ -52,13 +41,17 @@ export class UnitImpl implements MutableUnit { return this._type } + lastTile(): Tile { + return this._lastTile + } + move(tile: Tile): void { if (tile == null) { throw new Error("tile cannot be null") } - const oldTile = this._tile; + this._lastTile = this._tile this._tile = tile; - this.g.fireUnitUpdateEvent(this, oldTile); + this.g.fireUnitUpdateEvent(this); } setTroops(troops: number): void { this._troops = troops; @@ -87,7 +80,7 @@ export class UnitImpl implements MutableUnit { const oldOwner = this._owner oldOwner._units = oldOwner._units.filter(u => u != this) this._owner = newOwner as PlayerImpl - this.g.fireUnitUpdateEvent(this, this.tile()) + this.g.fireUnitUpdateEvent(this) this.g.displayMessage( `Your ${this.type()} was captured by ${newOwner.displayName()}`, MessageType.ERROR, @@ -110,7 +103,7 @@ export class UnitImpl implements MutableUnit { } this._owner._units = this._owner._units.filter(b => b != this); this._active = false; - this.g.fireUnitUpdateEvent(this, this._tile); + this.g.fireUnitUpdateEvent(this); if (displayMessage) { this.g.displayMessage(`Your ${this.type()} was destroyed`, MessageType.ERROR, this.owner().id()) } diff --git a/src/core/worker/WorkerClient.ts b/src/core/worker/WorkerClient.ts index af0159744..bec500bfa 100644 --- a/src/core/worker/WorkerClient.ts +++ b/src/core/worker/WorkerClient.ts @@ -1,5 +1,5 @@ -import { PlayerID, Tile } from "../game/Game"; -import { GameUpdateViewData, PlayerActions, PlayerInteraction } from "../GameView"; +import { PlayerActions, PlayerID, Tile } from "../game/Game"; +import { GameUpdateViewData } from "../GameView"; import { GameConfig, GameID, Turn } from "../Schemas"; import { generateID } from "../Util"; import { WorkerMessage } from "./WorkerMessages"; diff --git a/src/core/worker/WorkerMessages.ts b/src/core/worker/WorkerMessages.ts index 6f068a5ff..605365c4e 100644 --- a/src/core/worker/WorkerMessages.ts +++ b/src/core/worker/WorkerMessages.ts @@ -1,6 +1,6 @@ -import { GameUpdateViewData, PlayerActions, PlayerInteraction } from "../GameView"; +import { GameUpdateViewData } from "../GameView"; import { GameConfig, GameID, Turn } from "../Schemas"; -import { PlayerID } from "../game/Game"; +import { PlayerActions, PlayerID } from "../game/Game"; export type WorkerMessageType = | 'init'