76 lines
No EOL
2.6 KiB
TypeScript
76 lines
No EOL
2.6 KiB
TypeScript
import EventEmitter from "node:events"
|
|
import type { PlayerBroadcast } from "../shared/broadcast"
|
|
import { script } from "../shared/script"
|
|
import type { SceneDefinition } from "../shared/script/types"
|
|
import { type SceneEvent, sceneTypesById } from "./scene-types"
|
|
import type { Tagged } from "type-fest"
|
|
import type { SessionId } from "./session"
|
|
|
|
interface Events {
|
|
"player-broadcast": [PlayerBroadcast]
|
|
}
|
|
|
|
export interface CurrentScene<Type extends keyof typeof sceneTypesById> {
|
|
id: string
|
|
definition: SceneDefinition & { type: Type }
|
|
state: ReturnType<typeof sceneTypesById[Type]["createState"]>
|
|
}
|
|
|
|
export class Game {
|
|
// @ts-expect-error
|
|
private currentScene: CurrentScene
|
|
public eventBus = new EventEmitter<Events>({ captureRejections: false })
|
|
|
|
private isVirtualCurtainOpen = false
|
|
|
|
constructor() {
|
|
this.switchScene("pre-start")
|
|
|
|
setInterval(() => {
|
|
this.eventBus.emit("player-broadcast", { type: "keep-alive" })
|
|
}, 10000)
|
|
}
|
|
|
|
getConnectionPlayerBroadcasts(): PlayerBroadcast[] {
|
|
const events: PlayerBroadcast[] = []
|
|
events.push({ type: "scene-changed", sceneId: this.currentScene.id })
|
|
events.push(...this.currentScene.state.getConnectionEvents().map((event: SceneEvent) => ({ type: "scene-event", event })))
|
|
events.push({ type: "virtual-curtain-changed", isOpen: this.isVirtualCurtainOpen })
|
|
|
|
return events
|
|
}
|
|
|
|
removePlayer(sessionId: SessionId) {
|
|
this.currentScene.state.removePlayer(sessionId)
|
|
}
|
|
|
|
switchScene(sceneId: string) {
|
|
const definition = script.scenesById.get(sceneId)
|
|
if (definition === undefined) throw new Error(`Unknown scene: ${sceneId}`)
|
|
const type = sceneTypesById[definition.type]
|
|
|
|
this.eventBus.emit("player-broadcast", { type: "scene-changed", sceneId: sceneId })
|
|
|
|
this.currentScene = {
|
|
id: sceneId,
|
|
definition,
|
|
state: type.createState(this, definition as any)
|
|
}
|
|
}
|
|
|
|
withSceneState<Type extends keyof typeof sceneTypesById, R>(type: Type, block: (state: ReturnType<typeof sceneTypesById[Type]["createState"]>) => R): R | null {
|
|
if (this.currentScene.definition.type === type) {
|
|
return block(this.currentScene.state)
|
|
} else {
|
|
return null
|
|
}
|
|
}
|
|
|
|
setVirtualCurtainOpen(isOpen: boolean) {
|
|
if (this.isVirtualCurtainOpen === isOpen) return
|
|
this.isVirtualCurtainOpen = isOpen
|
|
this.eventBus.emit("player-broadcast", { type: "virtual-curtain-changed", isOpen: this.isVirtualCurtainOpen })
|
|
}
|
|
}
|
|
|
|
export const game = new Game() |