diff --git a/src/flags.ts b/src/flags.ts index b80d25d..2de5394 100644 --- a/src/flags.ts +++ b/src/flags.ts @@ -14,7 +14,7 @@ class Flags extends EventEmitter { constructor() { super(); // Don't assume that window exists (e.g. searching) - const qs = new URLSearchParams(globalThis?.location?.search ?? ""); + const qs = new URLSearchParams(globalThis.location.hash?.slice?.(1) ?? ''); this.DebugView = qs.get("debug") ? DebugLevel.PhysicsOverlay : DebugLevel.None; diff --git a/src/game.ts b/src/game.ts index 113f51b..e3bfcec 100644 --- a/src/game.ts +++ b/src/game.ts @@ -15,6 +15,7 @@ import { sound } from "@pixi/sound"; import Logger from "./log"; import { CriticalGameError } from "./errors"; import { getGameSettings } from "./settings"; +import { NetGameWorld } from "./net/netGameWorld"; const worldWidth = 1920; const worldHeight = 1080; @@ -70,7 +71,7 @@ export class Game { // the interaction module is important for wheel to work properly when renderer.view is placed or scaled events: this.pixiApp.renderer.events, }); - this.world = new GameWorld(this.rapierWorld, this.pixiApp.ticker); + this.world = netGameInstance ? new NetGameWorld(this.rapierWorld, this.pixiApp.ticker, netGameInstance) : new GameWorld(this.rapierWorld, this.pixiApp.ticker); this.pixiApp.stage.addChild(this.viewport); this.viewport.decelerate().drag(); this.viewport.zoom(8); diff --git a/src/index.css b/src/index.css index 01ed1ec..1565f8b 100644 --- a/src/index.css +++ b/src/index.css @@ -3,7 +3,6 @@ line-height: 1.5; font-weight: 400; - color-scheme: light dark; color: rgba(255, 255, 255, 0.87); background-color: #242424; @@ -14,29 +13,29 @@ -webkit-text-size-adjust: 100%; } -@media (prefers-color-scheme: dark) { - :root { - --text: #bdbdbd; - --bg: #2e2e2e; - --highlight: #b96724; - --links: #df8b1c; - --subheading: #adadad; - - --team-red-bg: #cc3333; - --team-red-fg: #db6f6f; - --team-blue-bg: #2649d9; - --team-blue-fg: #7085db; - --team-purple-bg: #a226d9; - --team-purple-fg: #bb70db; - --team-yellow-bg: #d9c526; - --team-yellow-fg: #dbcf70; - --team-orange-bg: #d97a26; - --team-orange-fg: #dba270; - --team-green-bg: #30d926; - --team-green-fg: #75db70; - --team-unk-bg: #cc00cc; - --team-unk-fg: #111111; - } +:root { + --text: #bdbdbd; + --bg: #2e2e2e; + --highlight: #b96724; + --links: #df8b1c; + --subheading: #adadad; + + --color-danger: #d41414; + + --team-red-bg: #cc3333; + --team-red-fg: #db6f6f; + --team-blue-bg: #2649d9; + --team-blue-fg: #7085db; + --team-purple-bg: #a226d9; + --team-purple-fg: #bb70db; + --team-yellow-bg: #d9c526; + --team-yellow-fg: #dbcf70; + --team-orange-bg: #d97a26; + --team-orange-fg: #dba270; + --team-green-bg: #30d926; + --team-green-fg: #75db70; + --team-unk-bg: #cc00cc; + --team-unk-fg: #111111; } a { diff --git a/src/logic/gamestate.ts b/src/logic/gamestate.ts index 7420ba3..eb85d0f 100644 --- a/src/logic/gamestate.ts +++ b/src/logic/gamestate.ts @@ -3,7 +3,7 @@ import Logger from "../log"; import { EntityType } from "../entities/type"; import { GameWorld } from "../world"; import { IWeaponCode } from "../weapons/weapon"; -import { BehaviorSubject, distinctUntilChanged, map, skip } from "rxjs"; +import { BehaviorSubject, distinctUntilChanged, map, skip, tap } from "rxjs"; export interface GameRules { roundDurationMs?: number; @@ -47,7 +47,11 @@ export class GameState { protected currentTeam = new BehaviorSubject( undefined, ); + protected currentWorm = new BehaviorSubject( + undefined, + ); public readonly currentTeam$ = this.currentTeam.asObservable(); + public readonly currentWorm$ = this.currentWorm.asObservable(); protected readonly teams: Map; protected nextTeamStack: TeamInstance[]; @@ -115,6 +119,8 @@ export class GameState { } this.nextTeamStack = [...this.teams.values()]; this.roundDurationMs = rules.roundDurationMs ?? 45000; + this.roundState.subscribe((s) => logger.info(`Round state changed => ${s}`)); + this.currentTeam.subscribe((s) => logger.info(`Current team is now => ${s?.name} ${s?.playerUserId}`)); } public pauseTimer() { @@ -153,7 +159,6 @@ export class GameState { public markAsFinished() { this.roundState.next(RoundState.Finished); - this.recordGameStare(); } public update(ticker: { deltaMS: number }) { @@ -176,7 +181,6 @@ export class GameState { this.playerMoved(); } else { this.roundState.next(RoundState.Finished); - this.recordGameStare(); } } @@ -184,19 +188,17 @@ export class GameState { this.roundState.next(RoundState.Playing); logger.debug("playerMoved", this.roundDurationMs); this.remainingRoundTimeMs.next(this.roundDurationMs); - this.recordGameStare(); } public beginRound() { if (this.roundState.value !== RoundState.WaitingToBegin) { throw Error( - `Expected round to be WaitingToBegin for advanceRound(), but got ${this.roundState}`, + `Expected round to be WaitingToBegin for advanceRound(), but got ${this.roundState.value}`, ); } this.roundState.next(RoundState.Preround); logger.debug("beginRound", PREROUND_TIMER_MS); this.remainingRoundTimeMs.next(PREROUND_TIMER_MS); - this.recordGameStare(); } public advanceRound(): @@ -204,7 +206,7 @@ export class GameState { | { winningTeams: TeamInstance[] } { if (this.roundState.value !== RoundState.Finished) { throw Error( - `Expected round to be Finished for advanceRound(), but got ${this.roundState}`, + `Expected round to be Finished for advanceRound(), but got ${this.roundState.value}`, ); } logger.debug("Advancing round"); @@ -215,13 +217,13 @@ export class GameState { // 5 seconds preround this.stateIteration++; + const nextWorm = firstTeam.popNextWorm(); this.roundState.next(RoundState.WaitingToBegin); - - this.recordGameStare(); + this.currentWorm.next(nextWorm); return { nextTeam: firstTeam, // Team *should* have at least one healthy worm. - nextWorm: firstTeam.popNextWorm(), + nextWorm: nextWorm, }; } const previousTeam = this.currentTeam.value; @@ -256,14 +258,10 @@ export class GameState { if (this.currentTeam.value === previousTeam) { this.stateIteration++; if (this.rules.winWhenOneGroupRemains) { - // All remaining teams are part of the same group - this.recordGameStare(); return { winningTeams: this.getActiveTeams(), }; } else if (previousTeam.health === 0) { - // This is a draw - this.recordGameStare(); return { winningTeams: [], }; @@ -271,17 +269,14 @@ export class GameState { } this.stateIteration++; // 5 seconds preround - this.remainingRoundTimeMs.next(0); + this.remainingRoundTimeMs.next(PREROUND_TIMER_MS); + const nextWorm = this.currentTeam.value.popNextWorm(); this.roundState.next(RoundState.WaitingToBegin); - this.recordGameStare(); + this.currentWorm.next(nextWorm); return { nextTeam: this.currentTeam.value, // We should have already validated that this team has healthy worms. - nextWorm: this.currentTeam.value.popNextWorm(), + nextWorm: nextWorm, }; } - - protected recordGameStare() { - return; - } } diff --git a/src/logic/worminstance.ts b/src/logic/worminstance.ts index 367fc16..bbd0f23 100644 --- a/src/logic/worminstance.ts +++ b/src/logic/worminstance.ts @@ -51,6 +51,6 @@ export class WormInstance { } setHealth(health: number) { - this.healthSubject.next(health); + this.healthSubject.next(Math.ceil(health)); } } diff --git a/src/net/client.ts b/src/net/client.ts index 4222cdc..4654cdc 100644 --- a/src/net/client.ts +++ b/src/net/client.ts @@ -568,8 +568,9 @@ export class RunningNetGameInstance extends NetGameInstance { if (r?.roomId !== this.roomId) { return; } - if (event.getType() === GameActionEventType && !event.isState()) { - this.player.handleEvent(event.getContent()); + // Filter our won events out. + if (event.getType() === GameActionEventType && !event.isState() && event.getSender() !== this.myUserId) { + void this.player.handleEvent(event.getContent()); } }); } @@ -604,7 +605,7 @@ export class RunningNetGameInstance extends NetGameInstance { ]); const expectedCount = Object.values(this.initialConfig.members).length; - + logger.info("Ready check", expectedCount, setOfReady); if (setOfReady.size === expectedCount) { return; } @@ -614,6 +615,7 @@ export class RunningNetGameInstance extends NetGameInstance { if (event.getType() === GameClientReadyEventType && !event.isState()) { setOfReady.add(event.getSender()!); } + logger.info("Ready check", expectedCount, setOfReady); if (setOfReady.size === expectedCount) { resolve(); } diff --git a/src/net/netGameState.ts b/src/net/netGameState.ts index c9eb453..28b83ef 100644 --- a/src/net/netGameState.ts +++ b/src/net/netGameState.ts @@ -3,10 +3,16 @@ import { StateRecorder } from "../state/recorder"; import { GameWorld } from "../world"; import { Team } from "../logic/teams"; import { StateRecordWormGameState } from "../state/model"; +import { combineLatest, map } from "rxjs"; +import Logger from "../log"; + +const log = new Logger("NetGameState"); export class NetGameState extends GameState { - get clientActive() { - return this.activeTeam?.playerUserId === this.myUserId; + protected clientActive$ = this.currentTeam$.pipe(map(t => t?.playerUserId === this.myUserId)); + + get shouldControlState(): boolean { + return this.currentTeam.value?.playerUserId === this.myUserId; } get peekNextTeam() { @@ -34,6 +40,11 @@ export class NetGameState extends GameState { private readonly myUserId: string, ) { super(teams, world, rules); + combineLatest([this.roundState$]).subscribe(([state]) => { + if (this.shouldControlState) { + this.recordGameState(state); + } + }); } protected networkSelectNextTeam() { @@ -57,7 +68,13 @@ export class NetGameState extends GameState { } } - public applyGameStateUpdate(stateUpdate: StateRecordWormGameState["data"]) { + public applyGameStateUpdate(stateUpdate: StateRecordWormGameState["data"]): ReturnType|null { + // if (this.iteration >= stateUpdate.iteration) { + // log.debug("Ignoring iteration because it's stale", this.iteration, stateUpdate.iteration); + // // Skip + // return; + // } + log.debug("Applying round state", stateUpdate.round_state); for (const teamData of stateUpdate.teams) { const teamWormSet = this.teams.get(teamData.uuid)?.worms; if (!teamWormSet) { @@ -76,22 +93,25 @@ export class NetGameState extends GameState { ) { this.remainingRoundTimeMs.next(5000); } - this.roundState.next(stateUpdate.round_state); - this.wind = stateUpdate.wind; - if (this.roundState.value === RoundState.WaitingToBegin) { - this.networkSelectNextTeam(); + if (stateUpdate.round_state === RoundState.WaitingToBegin) { + const result = this.advanceRound(); + this.wind = stateUpdate.wind; + return result; + } else if (stateUpdate.round_state === RoundState.Playing) { + this.playerMoved(); + } else { + // TODO? + this.roundState.next(stateUpdate.round_state); } + return null; } - protected recordGameStare() { - if (!this.clientActive) { - console.log("Not active client"); - return; - } + protected recordGameState(roundState: RoundState) { + log.debug("Recording round state", roundState); const iteration = this.iteration; const teams = this.getTeams(); this.recorder.recordGameState({ - round_state: this.roundState.value, + round_state: roundState, iteration: iteration, wind: this.currentWind, teams: teams.map((t) => ({ @@ -106,4 +126,35 @@ export class NetGameState extends GameState { })), }); } + + public update(ticker: { deltaMS: number }) { + const roundState = this.roundState.value; + let remainingRoundTimeMs = this.remainingRoundTimeMs.value; + if ( + roundState === RoundState.Finished || + roundState === RoundState.Paused || + roundState === RoundState.WaitingToBegin + ) { + return; + } + + remainingRoundTimeMs = Math.max(0, remainingRoundTimeMs - ticker.deltaMS); + this.remainingRoundTimeMs.next(remainingRoundTimeMs); + if (remainingRoundTimeMs) { + return; + } + + if (!this.shouldControlState) { + // Waiting for other client to make the move. + return; + } else { + + } + + if (roundState === RoundState.Preround) { + this.playerMoved(); + } else { + this.roundState.next(RoundState.Finished); + } + } } diff --git a/src/net/netGameWorld.ts b/src/net/netGameWorld.ts new file mode 100644 index 0000000..41939e5 --- /dev/null +++ b/src/net/netGameWorld.ts @@ -0,0 +1,70 @@ +import { Ticker, UPDATE_PRIORITY } from "pixi.js"; +import { GameWorld } from "../world"; +import RAPIER from "@dimforge/rapier2d-compat"; +import { RecordedEntityState } from "../state/model"; +import { PhysicsEntity } from "../entities/phys/physicsEntity"; +import Logger from "../log"; +import { RunningNetGameInstance } from "./client"; + +const TICK_EVERY_MS = 200; + +const logger = new Logger('NetGameWorld'); + +export class NetGameWorld extends GameWorld { + private broadcasting = false; + private msSinceLastTick = 0; + private entStateHash = new Map(); + + constructor(rapierWorld: RAPIER.World, ticker: Ticker, private readonly instance: RunningNetGameInstance) { + super(rapierWorld, ticker) + } + + public setBroadcasting(isBroadcasting: boolean) { + this.broadcasting = isBroadcasting; + if (this.broadcasting) { + logger.info('Enabled broadcasting from this client'); + this.ticker.add(this.onTick, undefined, UPDATE_PRIORITY.HIGH); + } else { + logger.info('Disabled broadcasting from this client'); + this.ticker.remove(this.onTick); + } + } + + public collectEntityState() { + const state: (RecordedEntityState & { uuid: string })[] = []; + for (const [uuid, ent] of this.entities.entries()) { + if ("recordState" in ent === false) { + // Not recordable. + break; + } + const data = (ent as PhysicsEntity).recordState(); + const hashData = JSON.stringify(data); + if (this.entStateHash.get(uuid) === hashData) { + // No updates - skip. + break; + } + this.entStateHash.set(uuid, hashData); + state.push({ + uuid, + ...data, + }); + } + return state; + } + + public onTick = (t: Ticker) => { + this.msSinceLastTick += t.elapsedMS; + if (this.msSinceLastTick < TICK_EVERY_MS) { + return; + } + this.msSinceLastTick -= TICK_EVERY_MS; + + // Fetch all entities and look for state changes. + const collectedState = this.collectEntityState(); + if (collectedState.length === 0) { + // Nothing to send, skip. + return; + } + logger.info(`Found ${collectedState.length} entity updates to send`); + } +} \ No newline at end of file diff --git a/src/overlays/roundTimer.ts b/src/overlays/roundTimer.ts index e158d7a..eeffc82 100644 --- a/src/overlays/roundTimer.ts +++ b/src/overlays/roundTimer.ts @@ -33,7 +33,6 @@ export class RoundTimer { this.container.addChild(text); this.roundTimeRemaining.subscribe((timeSeconds) => { - console.log("Timer time", timeSeconds); text.text = timeSeconds === 0 ? "--" : timeSeconds; }); diff --git a/src/scenarios/netGame.ts b/src/scenarios/netGame.ts index 93c7426..d8594e7 100644 --- a/src/scenarios/netGame.ts +++ b/src/scenarios/netGame.ts @@ -25,6 +25,9 @@ import { RemoteWorm } from "../entities/playable/remoteWorm"; import { logger } from "matrix-js-sdk/lib/logger"; import { getDefinitionForCode } from "../weapons"; import { NetGameState } from "../net/netGameState"; +import { NetGameWorld } from "../net/netGameWorld"; +import { combineLatest, filter } from "rxjs"; +import { RoundState } from "../logic/gamestate"; const log = new Logger("scenario"); @@ -37,7 +40,7 @@ export default async function runScenario(game: Game) { } const gameInstance = game.netGameInstance; const parent = game.viewport; - const world = game.world; + const world = game.world as NetGameWorld; const { worldWidth } = game.viewport; const player = gameInstance.player; @@ -102,9 +105,6 @@ export default async function runScenario(game: Game) { wormInst.replayFire(duration); }); - let endOfRoundWaitDuration: number | null = null; - let endOfGameFadeOut: number | null = null; - let currentWorm: Worm | undefined; // function applyEntityData() { // console.log("Applying entity state data"); @@ -284,6 +284,9 @@ export default async function runScenario(game: Game) { } } + let endOfGameFadeOut: number | null = null; + let currentWorm: Worm | undefined; + staticController.on("inputEnd", (kind: InputKind) => { if (!currentWorm?.currentState.canFire) { return; @@ -318,37 +321,62 @@ export default async function runScenario(game: Game) { const roundHandlerFn = (dt: Ticker) => { gameState.update(dt); - if (endOfGameFadeOut !== null) { - endOfGameFadeOut -= dt.deltaMS; - if (endOfGameFadeOut < 0) { - game.pixiApp.ticker.remove(roundHandlerFn); - game.goToMenu(gameState.getActiveTeams()); + if (gameState.isPreRound && currentWorm?.hasPerformedAction && currentWorm instanceof RemoteWorm === false) { + gameState.playerMoved(); + } + }; + + player.on("gameState", (s) => { + log.info("New game state recieved:", s.iteration); + gameState.applyGameStateUpdate(s); + }); + + if (gameInstance.isHost) { + await gameInstance.ready(); + await gameInstance.allClientsReady(); + log.info("All clients are ready! Beginning round"); + } else { + await gameInstance.ready(); + log.info("Marked as ready"); + } + + combineLatest([gameState.roundState$, gameState.remainingRoundTimeSeconds$]).pipe(filter(([state, seconds]) => + state === RoundState.Finished && seconds === 0)).subscribe(() => { + if (currentWorm) { + log.info("Timer ran out"); + currentWorm?.onEndOfTurn(); + currentWorm?.currentState.off("transition", transitionHandler); } + }); + + combineLatest([gameState.roundState$, gameState.currentWorm$]).pipe(filter(([roundState, worm]) => { + if (roundState === RoundState.WaitingToBegin && !worm) { + return false; + } + return true; + })).subscribe(([roundState, worm]) => { + log.info("Round state sub fired for", roundState, worm); + if (worm === undefined && RoundState.Finished && gameInstance.isHost) { + log.info("Starting first round as worm was undefined"); + gameState.advanceRound(); return; } - if (currentWorm && currentWorm.currentState.active) { - if (!gameState.isPreRound) { - if (gameState.paused && currentWorm.currentState.timerShouldRun) { - gameState.unpauseTimer(); - } else if ( - !gameState.paused && - !currentWorm.currentState.timerShouldRun - ) { - gameState.pauseTimer(); - } + if (roundState === RoundState.WaitingToBegin) { + log.debug("Round state worm diff", worm?.uuid, currentWorm?.wormIdent.uuid); + if (!worm) { + throw Error('No worm in WaitingToBegin') } - if (gameState.isPreRound && currentWorm.hasPerformedAction) { - gameState.playerMoved(); - return; - } else if (!gameState.isPreRound && gameState.remainingRoundTime <= 0) { - currentWorm?.onEndOfTurn(); - currentWorm = undefined; - endOfRoundWaitDuration = null; - } else { + if (worm?.uuid === currentWorm?.wormIdent.uuid) { + // New worm hasn't appeared yet. return; } - } - if (endOfRoundWaitDuration === null) { + currentWorm = wormInstances.get(worm.uuid); + log.info("Setting next worm", worm.uuid, currentWorm); + currentWorm?.onWormSelected(true); + currentWorm?.currentState.on("transition", transitionHandler); + gameState.beginRound(); + return; + } else if (roundState === RoundState.Finished) { stateRecorder.syncEntityState(world); const nextState = gameState.advanceRound(); if ("winningTeams" in nextState) { @@ -364,82 +392,10 @@ export default async function runScenario(game: Game) { overlay.toaster.pushToast(templateRandomText(GameDrawText), 8000); } endOfGameFadeOut = 8000; - } else { - currentWorm?.onEndOfTurn(); - currentWorm?.currentState.off("transition", transitionHandler); - currentWorm = wormInstances.get(nextState.nextWorm.uuid); - // Turn just ended. - endOfRoundWaitDuration = 5000; - } - return; - } - if (endOfRoundWaitDuration <= 0) { - if (!currentWorm) { - throw Error("Expected next worm"); } - world.setWind(gameState.currentWind); - currentWorm.onWormSelected(); - currentWorm.currentState.on("transition", transitionHandler); - gameState.beginRound(); - endOfRoundWaitDuration = null; - return; } - endOfRoundWaitDuration -= dt.deltaMS; - }; + }); + game.pixiApp.ticker.add((dt) => roundHandlerFn(dt)); game.pixiApp.ticker.add((dt) => camera.update(dt, currentWorm)); - - if (gameInstance.isHost) { - await gameInstance.allClientsReady(); - log.info("All clients are ready! Beginning round"); - game.pixiApp.ticker.add(roundHandlerFn); - } else { - await gameInstance.ready(); - log.info("Marked as ready"); - game.pixiApp.ticker.add((dt) => { - gameState.update(dt); - - if (endOfRoundWaitDuration === null) { - return; - } - - if (endOfRoundWaitDuration <= 0) { - if (!currentWorm) { - throw Error("Expected next worm"); - } - world.setWind(gameState.currentWind); - currentWorm.onWormSelected(); - currentWorm.currentState.on("transition", transitionHandler); - gameState.beginRound(); - endOfRoundWaitDuration = null; - return; - } - endOfRoundWaitDuration -= dt.deltaMS; - }); - // player.on("gameState", (dataUpdate) => { - // const nextState = gameState.applyGameStateUpdate(dataUpdate); - // logger.info("New game state", dataUpdate, nextState); - // if ("winningTeams" in nextState) { - // if (nextState.winningTeams.length) { - // overlay.toaster.pushToast( - // templateRandomText(TeamWinnerText, { - // TeamName: nextState.winningTeams.map((t) => t.name).join(", "), - // }), - // 8000, - // ); - // } else { - // // Draw - // overlay.toaster.pushToast(templateRandomText(GameDrawText), 8000); - // } - // endOfGameFadeOut = 8000; - // } else { - // currentWorm?.onEndOfTurn(); - // currentWorm?.currentState.off("transition", transitionHandler); - // currentWorm = wormInstances.get(nextState.nextWorm.uuid); - // // Turn just ended. - // endOfRoundWaitDuration = 5000; - // } - // return; - // }); - } } diff --git a/src/scenarios/netGameTest.ts b/src/scenarios/netGameTest.ts index 1f22baa..ed5f66a 100644 --- a/src/scenarios/netGameTest.ts +++ b/src/scenarios/netGameTest.ts @@ -13,6 +13,7 @@ import { WeaponTarget } from "../entities/phys/target"; import Logger from "../log"; import { logger } from "matrix-js-sdk/lib/logger"; import { NetGameState } from "../net/netGameState"; +import { WormSpawnRecordedState } from "../entities/state/wormSpawn"; const log = new Logger("scenario"); @@ -96,6 +97,8 @@ export default async function runScenario(game: Game) { const waterLevel = level.objects.find((v) => v.type === "wormgine.water")?.tra.y ?? 0; + const wormSpawn = level.objects.find((v) => v.type === "wormgine.worm_spawn") as WormSpawnRecordedState|undefined; + const water = world.addEntity( new Water( MetersValue.fromPixels(worldWidth * 4), @@ -144,13 +147,5 @@ export default async function runScenario(game: Game) { log.info("Marked as ready"); } - if (gameInstance.isHost) { - setInterval(() => { - gameState.markAsFinished(); - gameState.advanceRound(); - gameState.beginRound(); - }, 3000); - } - log.info("Game can now begin"); } diff --git a/src/state/player.ts b/src/state/player.ts index 65640e7..137dfed 100644 --- a/src/state/player.ts +++ b/src/state/player.ts @@ -89,10 +89,10 @@ export class StateReplay extends EventEmitter { } // Calculate the number of ms to delay since the start of the game. const elapsedRelativeTimeForState = ts - this.hostStartTs; - // and substract the local runtime. (e.g. if the relative time is 10s and we are 5s along, wait 5s) - const waitFor = elapsedRelativeTimeForState - this.elapsedRelativeLocalTime; + // // and substract the local runtime. (e.g. if the relative time is 10s and we are 5s along, wait 5s) + // const waitFor = elapsedRelativeTimeForState - this.elapsedRelativeLocalTime; - await new Promise((r) => setTimeout(r, waitFor)); + // await new Promise((r) => setTimeout(r, waitFor)); this.lastActionTs = ts; log.info(`> ${ts} ${kind} ${index} ${data}`); diff --git a/src/state/recorder.ts b/src/state/recorder.ts index 3cbe725..c786970 100644 --- a/src/state/recorder.ts +++ b/src/state/recorder.ts @@ -44,22 +44,23 @@ export class StateRecorder { } public syncEntityState(gameWorld: GameWorld) { - const stateToSend = []; - for (const entState of gameWorld.collectEntityState()) { - const newHash = hashCode(JSON.stringify(entState)); - if (this.entHashes.get(entState.uuid) !== newHash) { - stateToSend.push(entState); - } - this.entHashes.set(entState.uuid, newHash); - } - this.store.writeLine({ - index: ++this.recordIndex, - data: { - entities: stateToSend, - }, - kind: StateRecordKind.EntitySync, - ts: performance.now(), - } satisfies StateRecordEntitySync); + console.log('Stubbed syncEntityState'); + // const stateToSend = []; + // for (const entState of gameWorld.collectEntityState()) { + // const newHash = hashCode(JSON.stringify(entState)); + // if (this.entHashes.get(entState.uuid) !== newHash) { + // stateToSend.push(entState); + // } + // this.entHashes.set(entState.uuid, newHash); + // } + // this.store.writeLine({ + // index: ++this.recordIndex, + // data: { + // entities: stateToSend, + // }, + // kind: StateRecordKind.EntitySync, + // ts: performance.now(), + // } satisfies StateRecordEntitySync); } public recordWormAction(worm: string, action: StateWormAction) { @@ -133,6 +134,7 @@ export class StateRecorder { } satisfies StateRecordWormSelectWeapon); } public recordGameState(data: StateRecordWormGameState["data"]) { + console.log("Recording game state", data.round_state); this.store.writeLine({ index: ++this.recordIndex, data: data, diff --git a/src/world.ts b/src/world.ts index 89e7103..85d9e72 100644 --- a/src/world.ts +++ b/src/world.ts @@ -75,8 +75,8 @@ export class GameWorld { } constructor( - public readonly rapierWorld: World, - public readonly ticker: Ticker, + private readonly rapierWorld: World, + protected readonly ticker: Ticker, ) {} public setWind(windSpeed: number) { @@ -308,17 +308,4 @@ export class GameWorld { } return null; } - - public collectEntityState() { - const state: (RecordedEntityState & { uuid: string })[] = []; - for (const [uuid, ent] of this.entities.entries()) { - if ("recordState" in ent) { - state.push({ - uuid, - ...(ent as PhysicsEntity).recordState(), - }); - } - } - return state; - } }