Profectus-Demo/src/features/feature.ts

182 lines
6.6 KiB
TypeScript
Raw Normal View History

2022-01-14 04:25:47 +00:00
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";
2022-01-25 04:23:30 +00:00
import { ComponentOptions, CSSProperties, DefineComponent, isRef, ref, Ref, UnwrapRef } from "vue";
2022-01-14 04:25:47 +00:00
2022-01-25 04:23:30 +00:00
export const PersistentState = Symbol("PersistentState");
2022-01-14 04:25:47 +00:00
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;
2022-01-25 04:23:30 +00:00
export type StyleValue = string | CSSProperties | Array<string | CSSProperties>;
2022-01-14 04:25:47 +00:00
export type Persistent<T extends State = State> = {
2022-01-25 04:23:30 +00:00
[PersistentState]: Ref<T>;
2022-01-14 04:25:47 +00:00
[DefaultValue]: T;
[SetupPersistence]: () => Ref<T>;
};
export type PersistentRef<T extends State = State> = Ref<T> & {
[DefaultValue]: T;
[SetupPersistence]: () => Ref<T>;
};
// 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<any, any, any>;
// Example usage: `<Upgrade {...wrapComputable<GenericUpgrade>(upgrade)} />`
2022-01-25 04:23:30 +00:00
export function wrapFeature<T>(component: T): UnwrapRef<T> {
2022-01-14 04:25:47 +00:00
// TODO is this okay, or do we actually need to unref each property?
2022-01-25 04:23:30 +00:00
return component as unknown as UnwrapRef<T>;
2022-01-14 04:25:47 +00:00
}
export type FeatureComponent<T> = Omit<
{
[K in keyof T]: T[K] extends ProcessedComputable<infer S> ? S : T[K];
},
typeof Component | typeof DefaultValue | typeof SetupPersistence
>;
export type Replace<T, S> = S & Omit<T, keyof S>;
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<T extends State>(defaultValue: T | Ref<T>): PersistentRef<T> {
const persistent = isRef(defaultValue) ? defaultValue : (ref(defaultValue) as Ref<T>);
2022-01-25 04:23:30 +00:00
(persistent as unknown as PersistentRef<T>)[DefaultValue] = isRef(defaultValue)
2022-01-14 04:25:47 +00:00
? defaultValue.value
: defaultValue;
2022-01-25 04:23:30 +00:00
(persistent as unknown as PersistentRef<T>)[SetupPersistence] = function () {
2022-01-14 04:25:47 +00:00
return persistent;
};
2022-01-25 04:23:30 +00:00
return persistent as unknown as PersistentRef<T>;
2022-01-14 04:25:47 +00:00
}
export function makePersistent<T extends State>(
obj: unknown,
defaultValue: T
): asserts obj is Persistent<T> {
const persistent = obj as Partial<Persistent<T>>;
const state = ref(defaultValue) as Ref<T>;
2022-01-25 04:23:30 +00:00
Object.defineProperty(persistent, PersistentState, {
2022-01-14 04:25:47 +00:00
get: () => {
return state.value;
},
set: (val: T) => {
state.value = val;
}
});
persistent[DefaultValue] = isRef(defaultValue) ? (defaultValue.value as T) : defaultValue;
2022-01-25 04:23:30 +00:00
persistent[SetupPersistence] = function () {
2022-01-14 04:25:47 +00:00
return state;
};
}
export function setDefault<T, K extends keyof T>(
object: T,
key: K,
value: T[K]
): asserts object is Exclude<T, K> & Required<Pick<T, K>> {
if (object[key] === undefined && value != undefined) {
object[key] = value;
}
}
export function findFeatures(obj: Record<string, unknown>, type: symbol): unknown[] {
const objects: unknown[] = [];
const handleObject = (obj: Record<string, unknown>) => {
Object.keys(obj).forEach(key => {
const value = obj[key];
if (value && typeof value === "object") {
if ((value as Record<string, unknown>).type === type) {
objects.push(value);
} else {
handleObject(value as Record<string, unknown>);
}
}
});
};
handleObject(obj);
return objects;
}
globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>) => {
const handleObject = (
obj: Record<string, unknown>,
2022-01-25 04:23:30 +00:00
persistentState: Record<string, unknown>
): boolean => {
let foundPersistent = false;
2022-01-14 04:25:47 +00:00
Object.keys(obj).forEach(key => {
const value = obj[key];
if (value && typeof value === "object") {
if (SetupPersistence in value) {
2022-01-25 04:23:30 +00:00
foundPersistent = true;
2022-01-14 04:25:47 +00:00
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<unknown>).value = savedValue;
}
} else if (!(value instanceof Decimal)) {
if (typeof persistentState[key] !== "object") {
persistentState[key] = {};
}
2022-01-25 04:23:30 +00:00
const foundPersistentInChild = handleObject(
2022-01-14 04:25:47 +00:00
value as Record<string, unknown>,
2022-01-25 04:23:30 +00:00
persistentState[key] as Record<string, unknown>
2022-01-14 04:25:47 +00:00
);
2022-01-25 04:23:30 +00:00
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;
}
}
2022-01-14 04:25:47 +00:00
}
}
});
2022-01-25 04:23:30 +00:00
return foundPersistent;
2022-01-14 04:25:47 +00:00
};
2022-01-25 04:23:30 +00:00
handleObject(layer, saveData);
2022-01-14 04:25:47 +00:00
});