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
} ) ;