import Decimal, { DecimalSource } from "@/util/bignum"; import { isPlainObject } from "@/util/common"; import { ProxiedWithState, ProxyPath, ProxyState } from "@/util/proxies"; import { reactive, unref } from "vue"; import transientState from "./state"; export interface PlayerData { id: string; devSpeed: DecimalSource | null; name: string; tabs: Array; time: number; autosave: boolean; offlineProd: boolean; offlineTime: Decimal | null; timePlayed: Decimal; keepGoing: boolean; minimized: Record; modID: string; modVersion: string; layers: Record>; } export type Player = ProxiedWithState; const state = reactive({ id: "", devSpeed: null, name: "", tabs: [], time: -1, autosave: true, offlineProd: true, offlineTime: null, timePlayed: new Decimal(0), keepGoing: false, minimized: {}, modID: "", modVersion: "", layers: {} }); export function stringifySave(player: PlayerData): string { return JSON.stringify((player as Player)[ProxyState], (key, value) => unref(value)); } // eslint-disable-next-line @typescript-eslint/no-explicit-any const playerHandler: ProxyHandler> = { // eslint-disable-next-line @typescript-eslint/no-explicit-any get(target: Record, key: PropertyKey): any { if (key === ProxyState || key === ProxyPath) { return target[key]; } if (target[ProxyState][key] == undefined) { return; } if ( isPlainObject(target[ProxyState][key]) && !(target[ProxyState][key] instanceof Decimal) ) { if (target[ProxyState][key] !== target[key]?.[ProxyState]) { const path = [...target[ProxyPath], key]; target[key] = new Proxy( { [ProxyState]: target[ProxyState][key], [ProxyPath]: path }, playerHandler ); } return target[key]; } return target[ProxyState][key]; }, set( // eslint-disable-next-line @typescript-eslint/no-explicit-any target: Record, property: PropertyKey, // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, receiver: ProxyConstructor ): boolean { if ( !transientState.hasNaN && ((typeof value === "number" && isNaN(value)) || (value instanceof Decimal && (isNaN(value.sign) || isNaN(value.layer) || isNaN(value.mag)))) ) { const currentValue = target[ProxyState][property]; if ( !( (typeof currentValue === "number" && isNaN(currentValue)) || (currentValue instanceof Decimal && (isNaN(currentValue.sign) || isNaN(currentValue.layer) || isNaN(currentValue.mag))) ) ) { state.autosave = false; transientState.hasNaN = true; transientState.NaNPath = [...target[ProxyPath], property]; transientState.NaNReceiver = (receiver as unknown) as Record; console.error( `Attempted to set NaN value`, [...target[ProxyPath], property], target[ProxyState] ); throw "Attempted to set NaN value. See above for details"; } } target[ProxyState][property] = value; return true; }, // eslint-disable-next-line @typescript-eslint/no-explicit-any ownKeys(target: Record) { return Reflect.ownKeys(target[ProxyState]); }, // eslint-disable-next-line @typescript-eslint/no-explicit-any has(target: Record, key: string) { return Reflect.has(target[ProxyState], key); } }; export default window.player = new Proxy( { [ProxyState]: state, [ProxyPath]: ["player"] }, playerHandler ) as PlayerData;