blockly3d/src/state/gameState.ts

139 lines
3.6 KiB
TypeScript

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<GameEventContext, 'world' | 'scene'> = {}) {
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');
}
}
}