import Decimal, { DecimalSource } from "@/util/bignum"; import { isPlainObject } from "@/util/common"; import { ProxiedWithState, ProxyPath, ProxyState } from "@/util/proxies"; import { shallowReactive, 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: DecimalSource | null; timePlayed: DecimalSource; keepGoing: boolean; minimized: Record; modID: string; modVersion: string; layers: Record>; } export type Player = ProxiedWithState; const state = shallowReactive({ 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]; } const value = target[ProxyState][key]; if (isPlainObject(value) && !(value instanceof Decimal)) { if (value !== target[key]?.[ProxyState]) { const path = [...target[ProxyPath], key]; target[key] = new Proxy({ [ProxyState]: value, [ProxyPath]: path }, playerHandler); } return target[key]; } return value; }, 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;