import { makeAutoObservable, reaction, toJS } from "mobx"; import type { WorldState } from "./worldState"; import type { Game, Pos3, RuntimeGameScene } from "../types"; import type { CameraProps } from "@react-three/fiber"; import { GameEventBus, GameFactory } from "../model/gameFactory"; import { GameScheduler } from "../model/gameScheduler"; import type { GameEventContext } from "../types/runtime/gameBus"; export class GameState { private readonly world: WorldState; public isPaused: boolean = false; public time: number = 0; public isFirstTick: boolean; public scene: RuntimeGameScene; private eventBus: GameEventBus; private scheduler: GameScheduler; private startAutoSave() { return reaction( () => (this.asGame), (data) => this.saveData(data), { delay: 5000 } ); } // @ts-expect-error -- for future use private withoutAutoSave(fn: () => void) { this._stopAutoSave(); fn(); this._stopAutoSave = this.startAutoSave(); } private _stopAutoSave: () => void = () => { }; constructor(world: WorldState) { this.world = world; const rawWorld = toJS(this.world.data); const game = GameFactory.create(rawWorld); this.isPaused = game.paused; this.time = game.time; this.isFirstTick = true; this.scene = game.scene; const eventBus = new GameEventBus(); this.eventBus = eventBus; this.scheduler = new GameScheduler(); this._stopAutoSave = this.startAutoSave(); makeAutoObservable(this); GameFactory.evalGameScripts(rawWorld, this.scene, eventBus, this.scheduler); } public emitEvent(event: string, context: Omit = {}) { this.eventBus.emit( event, { ...context, world: this.world.data, scene: this.scene, }, ); } public get asGame(): Game { return { paused: toJS(this.isPaused), time: toJS(this.time), scene: toJS(this.scene), }; } public setGame(value: Game) { this.isPaused = value.paused; this.time = value.time; this.scene = value.scene; } public setIsPaused(value: boolean) { this.isPaused = value; } public get camera(): Pos3 { return this.world.data.editorCamera; } public get cameraAsThree(): CameraProps { const cam = this.camera; return { position: cam.position, fov: 50, rotation: cam.look, }; } // public load() { // console.log('Loading game...'); // this.withoutAutoSave(() => { // this.data = GameFactory.load() ?? GameFactory.create(this.world.data); // }); // } private saveData(data: Game): void { console.log('Saving game...'); const stack = new Error('Saving game...').stack!.split('\n').slice(1); const { ...debug } = toJS(data); console.dir({ stack, debug }); GameFactory.save(data); } public save(): void { this.saveData(this.asGame); } public resume(): void { this.isPaused = false; } public pause(): void { this.isPaused = true; } public tick(deltaTime: number): void { if (this.isPaused) return; this.scheduler.tick(deltaTime); this.time += deltaTime; if (this.isFirstTick) { this.isFirstTick = false; this.emitEvent('game_start'); } } }