Fix NaN detection
Also removes the proxy around player and cleaned up types
This commit is contained in:
parent
8c8f7f7904
commit
f5a25b2c2d
9 changed files with 130 additions and 158 deletions
|
@ -14,9 +14,12 @@
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<a :href="discordLink" class="nan-modal-discord-link">
|
<a
|
||||||
|
:href="discordLink || 'https://discord.gg/WzejVAx'"
|
||||||
|
class="nan-modal-discord-link"
|
||||||
|
>
|
||||||
<span class="material-icons nan-modal-discord">discord</span>
|
<span class="material-icons nan-modal-discord">discord</span>
|
||||||
{{ discordName }}
|
{{ discordName || "The Paper Pilot Community" }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
@ -50,49 +53,51 @@ import state from "game/state";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { format } from "util/bignum";
|
import Decimal, { format } from "util/bignum";
|
||||||
import type { ComponentPublicInstance } from "vue";
|
import type { ComponentPublicInstance } from "vue";
|
||||||
import { computed, ref, toRef } from "vue";
|
import { computed, ref, toRef, watch } from "vue";
|
||||||
import Toggle from "./fields/Toggle.vue";
|
import Toggle from "./fields/Toggle.vue";
|
||||||
import SavesManager from "./SavesManager.vue";
|
import SavesManager from "./SavesManager.vue";
|
||||||
|
|
||||||
const { discordName, discordLink } = projInfo;
|
const { discordName, discordLink } = projInfo;
|
||||||
const autosave = toRef(player, "autosave");
|
const autosave = ref(true);
|
||||||
|
const isPaused = ref(true);
|
||||||
const hasNaN = toRef(state, "hasNaN");
|
const hasNaN = toRef(state, "hasNaN");
|
||||||
const savesManager = ref<ComponentPublicInstance<typeof SavesManager> | null>(null);
|
const savesManager = ref<ComponentPublicInstance<typeof SavesManager> | null>(null);
|
||||||
|
|
||||||
const path = computed(() => state.NaNPath?.join("."));
|
watch(hasNaN, hasNaN => {
|
||||||
const property = computed(() => state.NaNPath?.slice(-1)[0]);
|
if (hasNaN) {
|
||||||
const previous = computed<DecimalSource | null>(() => {
|
autosave.value = player.autosave;
|
||||||
if (state.NaNReceiver && property.value != null) {
|
isPaused.value = player.devSpeed === 0;
|
||||||
return state.NaNReceiver[property.value] as DecimalSource;
|
} else {
|
||||||
}
|
player.autosave = autosave.value;
|
||||||
return null;
|
player.devSpeed = isPaused.value ? 0 : null;
|
||||||
});
|
|
||||||
const isPaused = computed({
|
|
||||||
get() {
|
|
||||||
return player.devSpeed === 0;
|
|
||||||
},
|
|
||||||
set(value: boolean) {
|
|
||||||
player.devSpeed = value ? null : 0;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const path = computed(() => state.NaNPath?.join("."));
|
||||||
|
const previous = computed<DecimalSource | null>(() => {
|
||||||
|
if (state.NaNPersistent != null) {
|
||||||
|
return state.NaNPersistent.value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
function setZero() {
|
function setZero() {
|
||||||
if (state.NaNReceiver && property.value != null) {
|
if (state.NaNPersistent != null) {
|
||||||
state.NaNReceiver[property.value] = new Decimal(0);
|
state.NaNPersistent.value = new Decimal(0);
|
||||||
state.hasNaN = false;
|
state.hasNaN = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOne() {
|
function setOne() {
|
||||||
if (state.NaNReceiver && property.value != null) {
|
if (state.NaNPersistent) {
|
||||||
state.NaNReceiver[property.value] = new Decimal(1);
|
state.NaNPersistent.value = new Decimal(1);
|
||||||
state.hasNaN = false;
|
state.hasNaN = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ignore() {
|
function ignore() {
|
||||||
if (state.NaNReceiver && property.value != null) {
|
if (state.NaNPersistent) {
|
||||||
state.NaNReceiver[property.value] = new Decimal(NaN);
|
state.NaNPersistent.value = new Decimal(NaN);
|
||||||
state.hasNaN = false;
|
state.hasNaN = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,11 +59,10 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Modal from "components/Modal.vue";
|
import Modal from "components/Modal.vue";
|
||||||
import projInfo from "data/projInfo.json";
|
import projInfo from "data/projInfo.json";
|
||||||
import type { PlayerData } from "game/player";
|
import type { Player } from "game/player";
|
||||||
import player, { stringifySave } from "game/player";
|
import player, { stringifySave } from "game/player";
|
||||||
import settings from "game/settings";
|
import settings from "game/settings";
|
||||||
import LZString from "lz-string";
|
import LZString from "lz-string";
|
||||||
import { ProxyState } from "util/proxies";
|
|
||||||
import { getUniqueID, loadSave, newSave, save } from "util/save";
|
import { getUniqueID, loadSave, newSave, save } from "util/save";
|
||||||
import type { ComponentPublicInstance } from "vue";
|
import type { ComponentPublicInstance } from "vue";
|
||||||
import { computed, nextTick, ref, shallowReactive, watch } from "vue";
|
import { computed, nextTick, ref, shallowReactive, watch } from "vue";
|
||||||
|
@ -72,7 +71,7 @@ import Select from "./fields/Select.vue";
|
||||||
import Text from "./fields/Text.vue";
|
import Text from "./fields/Text.vue";
|
||||||
import Save from "./Save.vue";
|
import Save from "./Save.vue";
|
||||||
|
|
||||||
export type LoadablePlayerData = Omit<Partial<PlayerData>, "id"> & { id: string; error?: unknown };
|
export type LoadablePlayerData = Omit<Partial<Player>, "id"> & { id: string; error?: unknown };
|
||||||
|
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
const modal = ref<ComponentPublicInstance<typeof Modal> | null>(null);
|
const modal = ref<ComponentPublicInstance<typeof Modal> | null>(null);
|
||||||
|
@ -195,7 +194,7 @@ const saves = computed(() =>
|
||||||
function exportSave(id: string) {
|
function exportSave(id: string) {
|
||||||
let saveToExport;
|
let saveToExport;
|
||||||
if (player.id === id) {
|
if (player.id === id) {
|
||||||
saveToExport = stringifySave(player[ProxyState]);
|
saveToExport = stringifySave(player);
|
||||||
} else {
|
} else {
|
||||||
saveToExport = JSON.stringify(saves.value[id]);
|
saveToExport = JSON.stringify(saves.value[id]);
|
||||||
}
|
}
|
||||||
|
@ -228,7 +227,7 @@ function duplicateSave(id: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const playerData = { ...saves.value[id], id: getUniqueID() };
|
const playerData = { ...saves.value[id], id: getUniqueID() };
|
||||||
save(playerData as PlayerData);
|
save(playerData as Player);
|
||||||
|
|
||||||
settings.saves.push(playerData.id);
|
settings.saves.push(playerData.id);
|
||||||
}
|
}
|
||||||
|
@ -272,7 +271,7 @@ function newFromPreset(preset: string) {
|
||||||
}
|
}
|
||||||
const playerData = JSON.parse(preset);
|
const playerData = JSON.parse(preset);
|
||||||
playerData.id = getUniqueID();
|
playerData.id = getUniqueID();
|
||||||
save(playerData as PlayerData);
|
save(playerData as Player);
|
||||||
|
|
||||||
settings.saves.push(playerData.id);
|
settings.saves.push(playerData.id);
|
||||||
|
|
||||||
|
@ -287,7 +286,7 @@ function editSave(id: string, newName: string) {
|
||||||
player.name = newName;
|
player.name = newName;
|
||||||
save();
|
save();
|
||||||
} else {
|
} else {
|
||||||
save(currSave as PlayerData);
|
save(currSave as Player);
|
||||||
cachedSaves[id] = undefined;
|
cachedSaves[id] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { branchedResetPropagation, createTree } from "features/trees/tree";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import type { BaseLayer, GenericLayer } from "game/layers";
|
import type { BaseLayer, GenericLayer } from "game/layers";
|
||||||
import { createLayer } from "game/layers";
|
import { createLayer } from "game/layers";
|
||||||
import type { PlayerData } from "game/player";
|
import type { Player } from "game/player";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { format, formatTime } from "util/bignum";
|
import Decimal, { format, formatTime } from "util/bignum";
|
||||||
|
@ -79,7 +79,7 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
*/
|
*/
|
||||||
export const getInitialLayers = (
|
export const getInitialLayers = (
|
||||||
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
|
||||||
player: Partial<PlayerData>
|
player: Partial<Player>
|
||||||
): Array<GenericLayer> => [main, prestige];
|
): Array<GenericLayer> => [main, prestige];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -97,7 +97,7 @@ export const hasWon = computed(() => {
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
export function fixOldSave(
|
export function fixOldSave(
|
||||||
oldVersion: string | undefined,
|
oldVersion: string | undefined,
|
||||||
player: Partial<PlayerData>
|
player: Partial<Player>
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
): void {}
|
): void {}
|
||||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||||
|
|
|
@ -7,6 +7,8 @@ import Decimal from "util/bignum";
|
||||||
import { ProxyState } from "util/proxies";
|
import { ProxyState } from "util/proxies";
|
||||||
import type { Ref, WritableComputedRef } from "vue";
|
import type { Ref, WritableComputedRef } from "vue";
|
||||||
import { computed, isReactive, isRef, ref } from "vue";
|
import { computed, isReactive, isRef, ref } from "vue";
|
||||||
|
import player from "./player";
|
||||||
|
import state from "./state";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A symbol used in {@link Persistent} objects.
|
* A symbol used in {@link Persistent} objects.
|
||||||
|
@ -56,6 +58,7 @@ export type State =
|
||||||
* A {@link Ref} that has been augmented with properties to allow it to be saved and loaded within the player save data object.
|
* A {@link Ref} that has been augmented with properties to allow it to be saved and loaded within the player save data object.
|
||||||
*/
|
*/
|
||||||
export type Persistent<T extends State = State> = Ref<T> & {
|
export type Persistent<T extends State = State> = Ref<T> & {
|
||||||
|
value: T;
|
||||||
/** A flag that this is a persistent property. Typically a circular reference. */
|
/** A flag that this is a persistent property. Typically a circular reference. */
|
||||||
[PersistentState]: Ref<T>;
|
[PersistentState]: Ref<T>;
|
||||||
/** The value the ref should be set to in a fresh save, or when updating an old save to the current version. */
|
/** The value the ref should be set to in a fresh save, or when updating an old save to the current version. */
|
||||||
|
@ -89,31 +92,65 @@ function getStackTrace() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkNaNAndWrite<T extends State>(persistent: Persistent<T>, value: T) {
|
||||||
|
// Decimal is smart enough to return false on things that aren't supposed to be numbers
|
||||||
|
if (Decimal.isNaN(value as DecimalSource)) {
|
||||||
|
if (!state.hasNaN) {
|
||||||
|
player.autosave = false;
|
||||||
|
state.hasNaN = true;
|
||||||
|
state.NaNPath = persistent[SaveDataPath];
|
||||||
|
state.NaNPersistent = persistent as Persistent<DecimalSource>;
|
||||||
|
}
|
||||||
|
console.error(
|
||||||
|
`Attempted to save NaN value to`,
|
||||||
|
persistent[SaveDataPath]?.join("."),
|
||||||
|
persistent
|
||||||
|
);
|
||||||
|
throw "Attempted to set NaN value. See above for details";
|
||||||
|
}
|
||||||
|
persistent[PersistentState].value = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a persistent ref, which can be saved and loaded.
|
* Create a persistent ref, which can be saved and loaded.
|
||||||
* All (non-deleted) persistent refs must be included somewhere within the layer object returned by that layer's options func.
|
* All (non-deleted) persistent refs must be included somewhere within the layer object returned by that layer's options func.
|
||||||
* @param defaultValue The value the persistent ref should start at on fresh saves or when reset.
|
* @param defaultValue The value the persistent ref should start at on fresh saves or when reset.
|
||||||
*/
|
*/
|
||||||
export function persistent<T extends State>(defaultValue: T | Ref<T>): Persistent<T> {
|
export function persistent<T extends State>(defaultValue: T | Ref<T>): Persistent<T> {
|
||||||
const persistent = (
|
const persistentState: Ref<T> = isRef(defaultValue)
|
||||||
isRef(defaultValue) ? defaultValue : (ref<T>(defaultValue) as unknown)
|
? defaultValue
|
||||||
) as Persistent<T>;
|
: (ref<T>(defaultValue) as Ref<T>);
|
||||||
|
|
||||||
persistent[PersistentState] = persistent;
|
if (isRef(defaultValue)) {
|
||||||
persistent[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue;
|
defaultValue = defaultValue.value;
|
||||||
persistent[StackTrace] = getStackTrace();
|
}
|
||||||
persistent[Deleted] = false;
|
|
||||||
const nonPersistent: Partial<NonPersistent<T>> = computed({
|
const nonPersistent = computed({
|
||||||
get() {
|
get() {
|
||||||
return persistent.value;
|
return persistentState.value;
|
||||||
},
|
},
|
||||||
set(value) {
|
set(value) {
|
||||||
persistent.value = value;
|
checkNaNAndWrite(persistent, value);
|
||||||
}
|
}
|
||||||
});
|
}) as NonPersistent<T>;
|
||||||
nonPersistent[DefaultValue] = persistent[DefaultValue];
|
nonPersistent[DefaultValue] = defaultValue;
|
||||||
persistent[NonPersistent] = nonPersistent as NonPersistent<T>;
|
|
||||||
persistent[SaveDataPath] = undefined;
|
// We're trying to mock a vue ref, which means the type expects a private [RefSymbol] property that we can't access, but the actual implementation of isRef just checks for `__v_isRef`
|
||||||
|
const persistent = {
|
||||||
|
get value() {
|
||||||
|
return persistentState.value as T;
|
||||||
|
},
|
||||||
|
set value(value: T) {
|
||||||
|
checkNaNAndWrite(persistent, value);
|
||||||
|
},
|
||||||
|
__v_isRef: true,
|
||||||
|
[PersistentState]: persistentState,
|
||||||
|
[DefaultValue]: defaultValue,
|
||||||
|
[StackTrace]: getStackTrace(),
|
||||||
|
[Deleted]: false,
|
||||||
|
[NonPersistent]: nonPersistent,
|
||||||
|
[SaveDataPath]: undefined
|
||||||
|
} as unknown as Persistent<T>;
|
||||||
|
|
||||||
if (addingLayers.length === 0) {
|
if (addingLayers.length === 0) {
|
||||||
console.warn(
|
console.warn(
|
||||||
|
@ -125,7 +162,7 @@ export function persistent<T extends State>(defaultValue: T | Ref<T>): Persisten
|
||||||
persistentRefs[addingLayers[addingLayers.length - 1]].add(persistent);
|
persistentRefs[addingLayers[addingLayers.length - 1]].add(persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return persistent as Persistent<T>;
|
return persistent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
import { isPlainObject } from "is-plain-object";
|
|
||||||
import Decimal from "util/bignum";
|
|
||||||
import type { ProxiedWithState } from "util/proxies";
|
|
||||||
import { ProxyPath, ProxyState } from "util/proxies";
|
|
||||||
import { reactive, unref } from "vue";
|
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import transientState from "./state";
|
import { reactive, unref } from "vue";
|
||||||
|
|
||||||
/** The player save data object. */
|
/** The player save data object. */
|
||||||
export interface PlayerData {
|
export interface Player {
|
||||||
/** The ID of this save. */
|
/** The ID of this save. */
|
||||||
id: string;
|
id: string;
|
||||||
/** A multiplier for time passing. Set to 0 when the game is paused. */
|
/** A multiplier for time passing. Set to 0 when the game is paused. */
|
||||||
|
@ -36,9 +31,6 @@ export interface PlayerData {
|
||||||
layers: Record<string, LayerData<unknown>>;
|
layers: Record<string, LayerData<unknown>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The proxied player that is used to track NaN values. */
|
|
||||||
export type Player = ProxiedWithState<PlayerData>;
|
|
||||||
|
|
||||||
/** A layer's save data. Automatically unwraps refs. */
|
/** A layer's save data. Automatically unwraps refs. */
|
||||||
export type LayerData<T> = {
|
export type LayerData<T> = {
|
||||||
[P in keyof T]?: T[P] extends (infer U)[]
|
[P in keyof T]?: T[P] extends (infer U)[]
|
||||||
|
@ -52,7 +44,7 @@ export type LayerData<T> = {
|
||||||
: T[P];
|
: T[P];
|
||||||
};
|
};
|
||||||
|
|
||||||
const state = reactive<PlayerData>({
|
const player = reactive<Player>({
|
||||||
id: "",
|
id: "",
|
||||||
devSpeed: null,
|
devSpeed: null,
|
||||||
name: "",
|
name: "",
|
||||||
|
@ -68,90 +60,16 @@ const state = reactive<PlayerData>({
|
||||||
layers: {}
|
layers: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default window.player = player;
|
||||||
|
|
||||||
/** Convert a player save data object into a JSON string. Unwraps refs. */
|
/** Convert a player save data object into a JSON string. Unwraps refs. */
|
||||||
export function stringifySave(player: PlayerData): string {
|
export function stringifySave(player: Player): string {
|
||||||
return JSON.stringify(player, (key, value) => unref(value));
|
return JSON.stringify(player, (key, value) => unref(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
const playerHandler: ProxyHandler<Record<PropertyKey, any>> = {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
get(target: Record<PropertyKey, any>, key: PropertyKey): any {
|
|
||||||
if (key === ProxyState || key === ProxyPath) {
|
|
||||||
return target[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
const value = target[ProxyState][key];
|
|
||||||
if (key !== "value" && (isPlainObject(value) || Array.isArray(value))) {
|
|
||||||
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<PropertyKey, any>,
|
|
||||||
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<string, unknown>;
|
|
||||||
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<PropertyKey, any>) {
|
|
||||||
return Reflect.ownKeys(target[ProxyState]);
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
has(target: Record<PropertyKey, any>, key: string) {
|
|
||||||
return Reflect.has(target[ProxyState], key);
|
|
||||||
},
|
|
||||||
getOwnPropertyDescriptor(target, key) {
|
|
||||||
return Object.getOwnPropertyDescriptor(target[ProxyState], key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
/** Augment the window object so the player can be accessed from the console. */
|
/** Augment the window object so the player can be accessed from the console. */
|
||||||
interface Window {
|
interface Window {
|
||||||
player: Player;
|
player: Player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/** The player save data object. */
|
|
||||||
export default window.player = new Proxy(
|
|
||||||
{ [ProxyState]: state, [ProxyPath]: ["player"] },
|
|
||||||
playerHandler
|
|
||||||
) as Player;
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import type { DecimalSource } from "util/bignum";
|
||||||
import { shallowReactive } from "vue";
|
import { shallowReactive } from "vue";
|
||||||
|
import type { Persistent } from "./persistence";
|
||||||
|
|
||||||
/** An object of global data that is not persistent. */
|
/** An object of global data that is not persistent. */
|
||||||
export interface Transient {
|
export interface Transient {
|
||||||
|
@ -8,8 +10,8 @@ export interface Transient {
|
||||||
hasNaN: boolean;
|
hasNaN: boolean;
|
||||||
/** The location within the player save data object of the NaN value. */
|
/** The location within the player save data object of the NaN value. */
|
||||||
NaNPath?: string[];
|
NaNPath?: string[];
|
||||||
/** The parent object of the NaN value. */
|
/** The ref that was being set to NaN. */
|
||||||
NaNReceiver?: Record<string, unknown>;
|
NaNPersistent?: Persistent<DecimalSource>;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import type { JSXFunction } from "features/feature";
|
||||||
|
import { isFunction } from "util/common";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import { isFunction } from "util/common";
|
|
||||||
|
|
||||||
export const DoNotCache = Symbol("DoNotCache");
|
export const DoNotCache = Symbol("DoNotCache");
|
||||||
|
|
||||||
|
@ -32,21 +33,22 @@ export function processComputable<T, S extends keyof ComputableKeysOf<T>>(
|
||||||
key: S
|
key: S
|
||||||
): asserts obj is T & { [K in S]: ProcessedComputable<UnwrapComputableType<T[S]>> } {
|
): asserts obj is T & { [K in S]: ProcessedComputable<UnwrapComputableType<T[S]>> } {
|
||||||
const computable = obj[key];
|
const computable = obj[key];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (
|
||||||
if (isFunction(computable) && computable.length === 0 && !(computable as any)[DoNotCache]) {
|
isFunction(computable) &&
|
||||||
|
computable.length === 0 &&
|
||||||
|
!(computable as unknown as JSXFunction)[DoNotCache]
|
||||||
|
) {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
obj[key] = computed(computable.bind(obj));
|
obj[key] = computed(computable.bind(obj));
|
||||||
} else if (isFunction(computable)) {
|
} else if (isFunction(computable)) {
|
||||||
obj[key] = computable.bind(obj) as unknown as T[S];
|
obj[key] = computable.bind(obj) as unknown as T[S];
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
(obj[key] as unknown as JSXFunction)[DoNotCache] = true;
|
||||||
(obj[key] as any)[DoNotCache] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertComputable<T>(obj: Computable<T>): ProcessedComputable<T> {
|
export function convertComputable<T>(obj: Computable<T>): ProcessedComputable<T> {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (isFunction(obj) && !(obj as unknown as JSXFunction)[DoNotCache]) {
|
||||||
if (isFunction(obj) && !(obj as any)[DoNotCache]) {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
obj = computed(obj);
|
obj = computed(obj);
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
|
import type { Persistent } from "game/persistence";
|
||||||
import { NonPersistent } from "game/persistence";
|
import { NonPersistent } from "game/persistence";
|
||||||
import Decimal from "util/bignum";
|
import Decimal from "util/bignum";
|
||||||
|
|
||||||
export const ProxyState = Symbol("ProxyState");
|
export const ProxyState = Symbol("ProxyState");
|
||||||
export const ProxyPath = Symbol("ProxyPath");
|
export const ProxyPath = Symbol("ProxyPath");
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
export type ProxiedWithState<T> = NonNullable<T> extends Record<PropertyKey, unknown>
|
||||||
export type ProxiedWithState<T> = NonNullable<T> extends Record<PropertyKey, any>
|
|
||||||
? NonNullable<T> extends Decimal
|
? NonNullable<T> extends Decimal
|
||||||
? T
|
? T
|
||||||
: {
|
: {
|
||||||
|
@ -16,6 +16,18 @@ export type ProxiedWithState<T> = NonNullable<T> extends Record<PropertyKey, any
|
||||||
}
|
}
|
||||||
: T;
|
: T;
|
||||||
|
|
||||||
|
export type Proxied<T> = NonNullable<T> extends Record<PropertyKey, unknown>
|
||||||
|
? NonNullable<T> extends Persistent<infer S>
|
||||||
|
? NonPersistent<S>
|
||||||
|
: NonNullable<T> extends Decimal
|
||||||
|
? T
|
||||||
|
: {
|
||||||
|
[K in keyof T]: Proxied<T[K]>;
|
||||||
|
} & {
|
||||||
|
[ProxyState]: T;
|
||||||
|
}
|
||||||
|
: T;
|
||||||
|
|
||||||
// Takes a function that returns an object and pretends to be that object
|
// Takes a function that returns an object and pretends to be that object
|
||||||
// Note that the object is lazily calculated
|
// Note that the object is lazily calculated
|
||||||
export function createLazyProxy<T extends object, S extends T>(
|
export function createLazyProxy<T extends object, S extends T>(
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import projInfo from "data/projInfo.json";
|
import projInfo from "data/projInfo.json";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import type { Player, PlayerData } from "game/player";
|
import type { Player } from "game/player";
|
||||||
import player, { stringifySave } from "game/player";
|
import player, { stringifySave } from "game/player";
|
||||||
import settings, { loadSettings } from "game/settings";
|
import settings, { loadSettings } from "game/settings";
|
||||||
import LZString from "lz-string";
|
import LZString from "lz-string";
|
||||||
import { ProxyState } from "util/proxies";
|
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
export function setupInitialStore(player: Partial<Player> = {}): Player {
|
||||||
return Object.assign(
|
return Object.assign(
|
||||||
{
|
{
|
||||||
id: `${projInfo.id}-0`,
|
id: `${projInfo.id}-0`,
|
||||||
|
@ -27,11 +26,9 @@ export function setupInitialStore(player: Partial<PlayerData> = {}): Player {
|
||||||
) as Player;
|
) as Player;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function save(playerData?: PlayerData): string {
|
export function save(playerData?: Player): string {
|
||||||
const stringifiedSave = LZString.compressToUTF16(
|
const stringifiedSave = LZString.compressToUTF16(stringifySave(playerData ?? player));
|
||||||
stringifySave(playerData ?? player[ProxyState])
|
localStorage.setItem((playerData ?? player).id, stringifiedSave);
|
||||||
);
|
|
||||||
localStorage.setItem((playerData ?? player[ProxyState]).id, stringifiedSave);
|
|
||||||
return stringifiedSave;
|
return stringifiedSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +67,7 @@ export async function load(): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function newSave(): PlayerData {
|
export function newSave(): Player {
|
||||||
const id = getUniqueID();
|
const id = getUniqueID();
|
||||||
const player = setupInitialStore({ id });
|
const player = setupInitialStore({ id });
|
||||||
save(player);
|
save(player);
|
||||||
|
@ -91,7 +88,7 @@ export function getUniqueID(): string {
|
||||||
|
|
||||||
export const loadingSave = ref(false);
|
export const loadingSave = ref(false);
|
||||||
|
|
||||||
export async function loadSave(playerObj: Partial<PlayerData>): Promise<void> {
|
export async function loadSave(playerObj: Partial<Player>): Promise<void> {
|
||||||
console.info("Loading save", playerObj);
|
console.info("Loading save", playerObj);
|
||||||
loadingSave.value = true;
|
loadingSave.value = true;
|
||||||
const { layers, removeLayer, addLayer } = await import("game/layers");
|
const { layers, removeLayer, addLayer } = await import("game/layers");
|
||||||
|
|
Loading…
Reference in a new issue