workspaces folders
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
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');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './rootState';
|
||||
@@ -0,0 +1,127 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import { state } from "./rootState";
|
||||
import type { ReactNode } from "react";
|
||||
import { IconRun, IconTrash, IconPlus, IconCubePlus, IconCubeOff, IconEdit } from '@tabler/icons-react';
|
||||
|
||||
export type MenuNodeAction = {
|
||||
id: string;
|
||||
content: ReactNode;
|
||||
tooltip: string;
|
||||
onClick?: () => void;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
export type MenuNode = {
|
||||
id: string;
|
||||
title: ReactNode;
|
||||
className?: string;
|
||||
actions?: MenuNodeAction[];
|
||||
onClick?: () => void;
|
||||
selected?: () => boolean;
|
||||
children?: MenuNode[];
|
||||
}
|
||||
|
||||
export class MenuState {
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
private get editorObjectTypesMenu(): MenuNode[] {
|
||||
const editor = state.worldEditor;
|
||||
const scene = editor.scene;
|
||||
|
||||
return Object.values(state.world.data.objectTypes)
|
||||
.map((ot) => {
|
||||
const children = Object.values(scene.objects)
|
||||
.filter((o) => o.typeId === ot.id)
|
||||
.map((o, idx) => {
|
||||
const isPlayer = scene.playerObjectId === o.id;
|
||||
|
||||
const actions: MenuNodeAction[] = [];
|
||||
if (!isPlayer) {
|
||||
actions.push({
|
||||
id: 'delete-object-instance',
|
||||
content: <IconTrash size="1em" />,
|
||||
tooltip: 'Delete the object',
|
||||
onClick: () => { editor.deleteObject(o.id); },
|
||||
});
|
||||
actions.push({
|
||||
id: 'control-object-instance-by-player',
|
||||
content: <IconRun size="1em" />,
|
||||
tooltip: 'Mark as player',
|
||||
onClick: () => { scene.playerObjectId = o.id; },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
id: `o-${o.id}`,
|
||||
title: <>
|
||||
{isPlayer && <IconRun size="1em" />}
|
||||
{`${ot.name} ${idx + 1}`}
|
||||
</>,
|
||||
className: isPlayer ? 'player-controlled-object' : undefined,
|
||||
actions,
|
||||
onClick: () => { state.worldEditor.setSelectedObject({ id: o.id, editMode: 'translate' }); },
|
||||
selected: () => state.worldEditor.selection?.type === 'object' && state.worldEditor.selection?.id === o.id,
|
||||
} as MenuNode;
|
||||
});
|
||||
|
||||
return {
|
||||
id: `ot-${ot.id}`,
|
||||
title: `${ot.name} (${children.length ? children.length : 'no'} instance${children.length != 1 ? 's' : ''})`,
|
||||
actions: [
|
||||
{
|
||||
id: 'create-object-instance',
|
||||
content: <IconPlus size="1em" />,
|
||||
tooltip: 'Create new object instance',
|
||||
onClick: () => { editor.addObjectInstanceAtRandomPosition(ot.id); },
|
||||
},
|
||||
{
|
||||
id: 'edit-object-type-voxels',
|
||||
content: <IconEdit size="1em" />,
|
||||
tooltip: 'Edit object type voxels',
|
||||
link: `/editor/objectType/${ot.id}/voxels`,
|
||||
},
|
||||
{
|
||||
id: 'delete-object-type',
|
||||
content: <IconCubeOff size="1em" />,
|
||||
tooltip: 'Delete object type',
|
||||
onClick: () => { editor.deleteObjectType(ot.id); },
|
||||
},
|
||||
],
|
||||
onClick: () => { editor.setSelectedObjectType({ id: ot.id, editMode: 'scripts' }); },
|
||||
selected: () => editor.selection?.type === 'objectType' && editor.selection?.id === ot.id,
|
||||
children,
|
||||
} as MenuNode;
|
||||
});
|
||||
}
|
||||
|
||||
private get editorMenu(): MenuNode[] {
|
||||
return [
|
||||
{
|
||||
id: 'editor-scene-menu',
|
||||
title: 'Scene',
|
||||
onClick: () => { state.worldEditor.resetSelection(); },
|
||||
selected: () => !state.worldEditor.selection,
|
||||
children: this.editorObjectTypesMenu,
|
||||
actions: [{
|
||||
id: 'create-object-type',
|
||||
content: <IconCubePlus size="1em" />,
|
||||
tooltip: 'Create new object type',
|
||||
onClick: () => { state.worldEditor.addObjectType(); },
|
||||
}],
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
public get nodes(): MenuNode[] {
|
||||
return [
|
||||
...this.editorMenu,
|
||||
];
|
||||
}
|
||||
|
||||
public nodeContainsSelected(node: MenuNode): boolean {
|
||||
return !!(node.selected?.() || node.children?.some((child) => this.nodeContainsSelected(child)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { makeAutoObservable } from "mobx";
|
||||
import { WorldState } from "./worldState";
|
||||
import { WorldEditorState } from "./worldEditorState";
|
||||
import { GameState } from "./gameState";
|
||||
import { MenuState } from "./menuState";
|
||||
import type { RuntimeObjectInstance } from "../types";
|
||||
|
||||
export type RenderInfo = {
|
||||
calls: number,
|
||||
triangles: number,
|
||||
shaders: number,
|
||||
geometries: number,
|
||||
textures: number,
|
||||
}
|
||||
|
||||
export class RootState {
|
||||
public readonly world = new WorldState();
|
||||
public readonly worldEditor: WorldEditorState;
|
||||
public game: GameState | undefined;
|
||||
public renderInfo: RenderInfo | undefined;
|
||||
public readonly menu = new MenuState();
|
||||
|
||||
constructor() {
|
||||
this.worldEditor = new WorldEditorState(this.world);
|
||||
|
||||
makeAutoObservable(
|
||||
this,
|
||||
{
|
||||
world: false,
|
||||
menu: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
public startGame(): void {
|
||||
state.worldEditor.resetSelection();
|
||||
if (this.game)
|
||||
this.stopGame();
|
||||
this.game = new GameState(this.world);
|
||||
}
|
||||
|
||||
public stopGame(): void {
|
||||
if (!this.game)
|
||||
return;
|
||||
|
||||
this.game.pause();
|
||||
this.game = undefined;
|
||||
}
|
||||
|
||||
public setRenderInfo(value: RenderInfo | undefined) {
|
||||
this.renderInfo = value;
|
||||
}
|
||||
|
||||
public markObjectAsPlayer(object: RuntimeObjectInstance) {
|
||||
if (this.game)
|
||||
this.game.scene.playerObjectId = object.id;
|
||||
else
|
||||
this.world.data.initialScene.playerObjectId = object.id;
|
||||
}
|
||||
}
|
||||
|
||||
export const state = new RootState();
|
||||
|
||||
state.world.load();
|
||||
@@ -0,0 +1,219 @@
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import type { WorldState } from "./worldState";
|
||||
import { type ObjectInstance, type Pos3, type R3, type V3, type RuntimeScene, type ObjectType, type Voxel } from "../types";
|
||||
import { createObjectInstance } from "../utils/object";
|
||||
import { randomId } from "../utils";
|
||||
import { state } from "./rootState";
|
||||
import { populateRuntimeObject, computeBoundingBox } from "../utils/runtime";
|
||||
import { DEFAULT_VOXEL_TYPE } from "../model/defaultVoxelTypes";
|
||||
import { getObjectVoxelGroups } from "../utils/graphics/voxelGroup";
|
||||
import { buildObjectTrimesh } from "../utils/graphics/mesh";
|
||||
|
||||
export const ObjectEditModeEnum = [
|
||||
'translate',
|
||||
'rotate',
|
||||
'scale',
|
||||
] as const;
|
||||
type ObjectEditModeTuple = typeof ObjectEditModeEnum;
|
||||
export type ObjectEditMode = ObjectEditModeTuple[number];
|
||||
|
||||
export function nextObjectEditMode(mode: ObjectEditMode | undefined): ObjectEditMode {
|
||||
if (mode === undefined)
|
||||
return ObjectEditModeEnum[0];
|
||||
|
||||
const idx = ObjectEditModeEnum.indexOf(mode);
|
||||
return ObjectEditModeEnum[(idx + 1) % ObjectEditModeEnum.length];
|
||||
}
|
||||
|
||||
export const ObjectTypeEditModeEnum = [
|
||||
'scripts',
|
||||
] as const;
|
||||
type ObjectTypeEditModeTuple = typeof ObjectTypeEditModeEnum;
|
||||
export type ObjectTypeEditMode = ObjectTypeEditModeTuple[number];
|
||||
|
||||
export function nextObjectTypeEditMode(mode: ObjectTypeEditMode | undefined): ObjectTypeEditMode {
|
||||
if (mode === undefined)
|
||||
return ObjectTypeEditModeEnum[0];
|
||||
|
||||
const idx = ObjectTypeEditModeEnum.indexOf(mode);
|
||||
return ObjectTypeEditModeEnum[(idx + 1) % ObjectTypeEditModeEnum.length];
|
||||
}
|
||||
|
||||
export type SelectedObject = {
|
||||
type: 'object',
|
||||
id: string;
|
||||
editMode: ObjectEditMode;
|
||||
}
|
||||
|
||||
export type SelectedObjectType = {
|
||||
type: 'objectType',
|
||||
id: string;
|
||||
editMode: ObjectTypeEditMode;
|
||||
}
|
||||
|
||||
export type Selection = SelectedObject | SelectedObjectType;
|
||||
|
||||
export class WorldEditorState {
|
||||
private readonly world: WorldState;
|
||||
|
||||
public selection: Selection | undefined;
|
||||
|
||||
constructor(world: WorldState) {
|
||||
this.world = world;
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
public get isEnabled(): boolean {
|
||||
return !state.game;
|
||||
}
|
||||
|
||||
public setCamera(value: Pos3): void {
|
||||
this.world.data.editorCamera = value;
|
||||
}
|
||||
|
||||
public setSelection(value: Selection | undefined): void {
|
||||
this.selection = value;
|
||||
}
|
||||
|
||||
public setSelectedObject(value: Omit<SelectedObject, 'type'> | undefined): void {
|
||||
this.setSelection(value
|
||||
? {
|
||||
type: 'object',
|
||||
...value,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
public setSelectedObjectType(value: Omit<SelectedObjectType, 'type'> | undefined): void {
|
||||
this.setSelection(value
|
||||
? {
|
||||
type: 'objectType',
|
||||
...value,
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
}
|
||||
|
||||
public resetSelection() {
|
||||
this.setSelection(undefined);
|
||||
}
|
||||
|
||||
public get scene(): RuntimeScene {
|
||||
return this.world.data.initialScene;
|
||||
}
|
||||
|
||||
public get camera(): Pos3 {
|
||||
return this.world.data.editorCamera;
|
||||
}
|
||||
|
||||
public setObjectTransform(id: string, position: V3, rotation: R3, scale: V3): void {
|
||||
const obj = this.scene.objects[id];
|
||||
if (!obj)
|
||||
return;
|
||||
|
||||
runInAction(() => { // all in one go
|
||||
obj.position = position;
|
||||
obj.rotation = rotation;
|
||||
obj.scale = scale;
|
||||
});
|
||||
}
|
||||
|
||||
public addObjectInstanceAtRandomPosition(typeId: string): ObjectInstance {
|
||||
return this.addObjectInstance(
|
||||
typeId,
|
||||
{ x: Math.random() * 3, y: Math.random() * 3, z: Math.random() * 1 },
|
||||
);
|
||||
}
|
||||
|
||||
public addObjectInstance(
|
||||
typeId: string,
|
||||
pos: { x: number; y: number; z: number; },
|
||||
): ObjectInstance {
|
||||
const obj = populateRuntimeObject(createObjectInstance(randomId(), typeId), this.world.data);
|
||||
obj.position = [pos.x, pos.y, pos.z];
|
||||
obj.rotation = [0, 0, 0];
|
||||
|
||||
this.scene.objects[obj.id] = obj;
|
||||
|
||||
this.setSelectedObject({ id: obj.id, editMode: 'translate' });
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public deleteObject(objectId: string) {
|
||||
if (this.selection?.type === 'object' && this.selection.id === objectId)
|
||||
this.resetSelection();
|
||||
delete (this.scene.objects[objectId]);
|
||||
}
|
||||
|
||||
public addObjectType(): ObjectType {
|
||||
const objectType: ObjectType = {
|
||||
id: randomId(),
|
||||
name: 'Unnamed',
|
||||
voxels: [
|
||||
{
|
||||
typeId: DEFAULT_VOXEL_TYPE.id,
|
||||
position: [0, 0, 0],
|
||||
}
|
||||
],
|
||||
};
|
||||
this.world.data.objectTypes[objectType.id] = objectType;
|
||||
this.setSelectedObjectType({ id: objectType.id, editMode: 'scripts' });
|
||||
return objectType;
|
||||
}
|
||||
|
||||
public addVoxelToObjectType(typeId: string, voxel: Voxel): void {
|
||||
const objectType = this.world.getObjectTypeById(typeId);
|
||||
if (!objectType)
|
||||
return;
|
||||
const occupied = objectType.voxels.some(v =>
|
||||
v.position[0] === voxel.position[0] &&
|
||||
v.position[1] === voxel.position[1] &&
|
||||
v.position[2] === voxel.position[2]
|
||||
);
|
||||
if (!occupied)
|
||||
objectType.voxels.push(voxel);
|
||||
}
|
||||
|
||||
public removeVoxelFromObjectType(typeId: string, position: V3): void {
|
||||
const objectType = this.world.getObjectTypeById(typeId);
|
||||
if (!objectType)
|
||||
return;
|
||||
const idx = objectType.voxels.findIndex(v =>
|
||||
v.position[0] === position[0] &&
|
||||
v.position[1] === position[1] &&
|
||||
v.position[2] === position[2]
|
||||
);
|
||||
if (idx !== -1)
|
||||
objectType.voxels.splice(idx, 1);
|
||||
}
|
||||
|
||||
public refreshObjectTypeCaches(typeId: string): void {
|
||||
const objectType = this.world.getObjectTypeById(typeId);
|
||||
if (!objectType)
|
||||
return;
|
||||
const world = this.world.data;
|
||||
const now = Date.now();
|
||||
for (const obj of Object.values(this.scene.objects)) {
|
||||
if (obj.typeId !== typeId)
|
||||
continue;
|
||||
obj.cache.voxelGroups = getObjectVoxelGroups(objectType, world.voxelTypes);
|
||||
obj.cache.colliderMesh = buildObjectTrimesh(objectType, world.voxelTypes);
|
||||
obj.cache.boundingBox = computeBoundingBox(objectType);
|
||||
obj.cache.updatedAt = now;
|
||||
}
|
||||
}
|
||||
|
||||
public deleteObjectType(typeId: string) {
|
||||
if (this.selection?.type === 'objectType' && this.selection.id === typeId)
|
||||
this.resetSelection();
|
||||
|
||||
for (const id in this.scene.objects)
|
||||
if (this.scene.objects[id].typeId === typeId)
|
||||
this.deleteObject(id);
|
||||
|
||||
delete (this.world.data.objectTypes[typeId]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
import { makeAutoObservable, reaction, toJS } from "mobx";
|
||||
import { WorldFactory } from "../model/worldFactory";
|
||||
import { type ObjectInstance, type ObjectType, type World, type VoxelType, type RuntimeWorld } from "../types";
|
||||
import { state } from "./rootState";
|
||||
import { DEFAULT_VOXEL_TYPES } from "../model/defaultVoxelTypes";
|
||||
import { wolf } from "../model/objectPrefabs/wolf";
|
||||
import { terrainXZ } from "../model/objectPrefabs/terrain";
|
||||
import { populateRuntimeWorld } from "../utils/runtime";
|
||||
|
||||
export class WorldState {
|
||||
public data: RuntimeWorld = WorldFactory.create();
|
||||
|
||||
private startAutoSave() {
|
||||
return reaction(() => toJS(this.data), (data) => this.saveData(data), { delay: 500 });
|
||||
}
|
||||
|
||||
private withoutAutoSave(fn: () => void) {
|
||||
this._stopAutoSave();
|
||||
fn();
|
||||
this._stopAutoSave = this.startAutoSave();
|
||||
}
|
||||
|
||||
private _stopAutoSave: () => void = () => { };
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this);
|
||||
this._stopAutoSave = this.startAutoSave();
|
||||
}
|
||||
|
||||
public reset() {
|
||||
console.log('Resetting world...');
|
||||
|
||||
this.withoutAutoSave(() => {
|
||||
this.data = WorldFactory.create();
|
||||
});
|
||||
state.worldEditor.resetSelection();
|
||||
}
|
||||
|
||||
public loadMock() {
|
||||
console.log('Mocking world...');
|
||||
|
||||
const objects = Array(3).fill(0)
|
||||
.map((_, idx) => ({
|
||||
id: `obj${idx}`,
|
||||
typeId: 'wolf',
|
||||
physics: true,
|
||||
gravityScale: 1,
|
||||
position: [idx * 10 - 10, 5, 0],
|
||||
rotation: [0, 0, 0],
|
||||
scale: [1, 1, 1],
|
||||
} as ObjectInstance));
|
||||
const objectMap = Object.fromEntries(
|
||||
objects.map((obj) => [obj.id, obj]),
|
||||
) as Record<string, ObjectInstance>;
|
||||
|
||||
this.data = populateRuntimeWorld({
|
||||
objectTypes: {
|
||||
wolf: {
|
||||
id: 'wolf',
|
||||
name: 'Wolf',
|
||||
voxels: wolf,
|
||||
},
|
||||
terrain: {
|
||||
id: 'terrain',
|
||||
name: 'Terrain',
|
||||
voxels: terrainXZ(50, 50),
|
||||
}
|
||||
},
|
||||
voxelTypes: DEFAULT_VOXEL_TYPES,
|
||||
editorCamera: {
|
||||
position: [14, 17, -25],
|
||||
look: [-2.5, 0.31, 3],
|
||||
},
|
||||
initialScene: {
|
||||
objects: {
|
||||
terrain: {
|
||||
id: 'terrain',
|
||||
typeId: 'terrain',
|
||||
physics: false, // pinned
|
||||
gravityScale: 0, // pinned
|
||||
position: [0, -1, 0],
|
||||
rotation: [0, 0, 0],
|
||||
scale: [1, 1, 1],
|
||||
},
|
||||
...objectMap,
|
||||
},
|
||||
playerObjectId: 'obj1',
|
||||
},
|
||||
gameRules: {
|
||||
gravity: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log(objects);
|
||||
state.worldEditor.resetSelection();
|
||||
}
|
||||
|
||||
public load() {
|
||||
console.log('Loading world...');
|
||||
this.withoutAutoSave(() => {
|
||||
this.data = WorldFactory.load() ?? WorldFactory.create();
|
||||
});
|
||||
state.worldEditor.resetSelection();
|
||||
}
|
||||
|
||||
private saveData(data: World): void {
|
||||
console.log('Saving world...');
|
||||
const stack = new Error('Saving world...').stack!.split('\n').slice(1);
|
||||
const { voxelTypes: _vts, initialScene: _is, ...debug } = toJS(data);
|
||||
console.dir({ stack, debug });
|
||||
WorldFactory.save(toJS(this.data));
|
||||
}
|
||||
|
||||
public save(): void {
|
||||
this.saveData(this.data);
|
||||
}
|
||||
|
||||
public getObjectTypeById(id: string): ObjectType | undefined {
|
||||
return this.data.objectTypes[id];
|
||||
}
|
||||
|
||||
public getVoxelTypeById(id: string): VoxelType | undefined {
|
||||
return this.data.voxelTypes[id];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user