import { globalBus } from "@/game/events"; import { GenericLayer } from "@/game/layers"; import Decimal, { DecimalSource } from "@/util/bignum"; import { ProcessedComputable } from "@/util/computed"; import { isArray } from "@vue/shared"; import { ComponentOptions, CSSProperties, DefineComponent, isRef, ref, Ref, UnwrapRef } from "vue"; export const PersistentState = Symbol("PersistentState"); export const SetupPersistence = Symbol("SetupPersistence"); export const DefaultValue = Symbol("DefaultValue"); export const Component = Symbol("Component"); // Note: This is a union of things that should be safely stringifiable without needing // special processes for knowing what to load them in as // - Decimals aren't allowed because we'd need to know to parse them back. // - DecimalSources are allowed because the string is a valid value for them export type State = | string | number | boolean | DecimalSource | { [key: string]: State } | { [key: number]: State }; export type CoercableComponent = string | ComponentOptions | DefineComponent | JSX.Element; export type StyleValue = string | CSSProperties | Array; export type Persistent = { [PersistentState]: Ref; [DefaultValue]: T; [SetupPersistence]: () => Ref; }; export type PersistentRef = Ref & { [DefaultValue]: T; [SetupPersistence]: () => Ref; }; // TODO if importing .vue components in .tsx can become type safe, // this type can probably be safely removed // eslint-disable-next-line @typescript-eslint/no-explicit-any export type GenericComponent = DefineComponent; // Example usage: `(upgrade)} />` export function wrapFeature(component: T): UnwrapRef { // TODO is this okay, or do we actually need to unref each property? return component as unknown as UnwrapRef; } export type FeatureComponent = Omit< { [K in keyof T]: T[K] extends ProcessedComputable ? S : T[K]; }, typeof Component | typeof DefaultValue | typeof SetupPersistence >; export type Replace = S & Omit; let id = 0; // Get a unique ID to allow a feature to be found for creating branches // and any other uses requiring unique identifiers for each feature // IDs are gauranteed unique, but should not be saved as they are not // guaranteed to be persistent through updates and such export function getUniqueID(prefix = "feature-"): string { return prefix + id++; } export enum Visibility { Visible, Hidden, None } export function showIf(condition: boolean, otherwise = Visibility.None): Visibility { return condition ? Visibility.Visible : otherwise; } export function persistent(defaultValue: T | Ref): PersistentRef { const persistent = isRef(defaultValue) ? defaultValue : (ref(defaultValue) as Ref); (persistent as unknown as PersistentRef)[DefaultValue] = isRef(defaultValue) ? defaultValue.value : defaultValue; (persistent as unknown as PersistentRef)[SetupPersistence] = function () { return persistent; }; return persistent as unknown as PersistentRef; } export function makePersistent( obj: unknown, defaultValue: T ): asserts obj is Persistent { const persistent = obj as Partial>; const state = ref(defaultValue) as Ref; Object.defineProperty(persistent, PersistentState, { get: () => { return state.value; }, set: (val: T) => { state.value = val; } }); persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue; persistent[SetupPersistence] = function () { return state; }; } export function setDefault( object: T, key: K, value: T[K] ): asserts object is Exclude & Required> { if (object[key] === undefined && value != undefined) { object[key] = value; } } export function findFeatures(obj: Record, type: symbol): unknown[] { const objects: unknown[] = []; const handleObject = (obj: Record) => { Object.keys(obj).forEach(key => { const value = obj[key]; if (value && typeof value === "object") { if ((value as Record).type === type) { objects.push(value); } else { handleObject(value as Record); } } }); }; handleObject(obj); return objects; } globalBus.on("addLayer", (layer: GenericLayer, saveData: Record) => { const handleObject = ( obj: Record, persistentState: Record ): boolean => { let foundPersistent = false; Object.keys(obj).forEach(key => { const value = obj[key]; if (value && typeof value === "object") { if (SetupPersistence in value) { foundPersistent = true; const savedValue = persistentState[key]; // eslint-disable-next-line @typescript-eslint/no-explicit-any persistentState[key] = (value as PersistentRef | Persistent)[ SetupPersistence ](); if (savedValue != null) { (persistentState[key] as Ref).value = savedValue; } } else if (!(value instanceof Decimal)) { if (typeof persistentState[key] !== "object") { persistentState[key] = {}; } const foundPersistentInChild = handleObject( value as Record, persistentState[key] as Record ); if (foundPersistentInChild) { if (isArray(value)) { console.warn( "Found array that contains persistent values when adding layer. Keep in mind changing the order of elements in the array will mess with existing player saves.", obj, key ); } else { foundPersistent = true; } } } } }); return foundPersistent; }; handleObject(layer, saveData); });