2022-01-14 04:25:47 +00:00
import { globalBus } from "@/game/events" ;
import { GenericLayer } from "@/game/layers" ;
import Decimal , { DecimalSource } from "@/util/bignum" ;
2022-02-27 19:49:34 +00:00
import { DoNotCache , ProcessedComputable } from "@/util/computed" ;
import { ProxyState } from "@/util/proxies" ;
2022-01-14 04:25:47 +00:00
import { isArray } from "@vue/shared" ;
2022-02-27 19:49:34 +00:00
import { CSSProperties , DefineComponent , isRef , ref , Ref } 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 DefaultValue = Symbol ( "DefaultValue" ) ;
export const Component = Symbol ( "Component" ) ;
2022-02-27 19:49:34 +00:00
export const GatherProps = Symbol ( "GatherProps" ) ;
2022-01-14 04:25:47 +00:00
// 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 } ;
2022-02-27 19:49:34 +00:00
export type JSXFunction = ( ( ) = > JSX . Element ) & { [ DoNotCache ] : true } ;
export type CoercableComponent = string | DefineComponent | JSXFunction ;
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 ;
} ;
2022-01-25 04:25:34 +00:00
export type PersistentRef < T extends State = State > = Ref < T > & Persistent < T > ;
2022-01-14 04:25:47 +00:00
// 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 > ;
export type FeatureComponent < T > = Omit <
{
[ K in keyof T ] : T [ K ] extends ProcessedComputable < infer S > ? S : T [ K ] ;
} ,
2022-01-25 04:25:34 +00:00
typeof Component | typeof DefaultValue
2022-01-14 04:25:47 +00:00
> ;
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
}
2022-02-27 19:49:34 +00:00
export function jsx ( func : ( ) = > JSX . Element | "" ) : JSXFunction {
( func as Partial < JSXFunction > ) [ DoNotCache ] = true ;
return func as JSXFunction ;
}
2022-01-14 04:25:47 +00:00
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 > {
2022-01-25 04:25:34 +00:00
const persistent = (
isRef ( defaultValue ) ? defaultValue : ( ref < T > ( defaultValue ) as unknown )
) as PersistentRef < T > ;
persistent [ PersistentState ] = persistent ;
persistent [ DefaultValue ] = isRef ( defaultValue ) ? defaultValue.value : defaultValue ;
return persistent 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:25:34 +00:00
persistent [ PersistentState ] = state ;
2022-01-14 04:25:47 +00:00
persistent [ DefaultValue ] = isRef ( defaultValue ) ? ( defaultValue . value as T ) : defaultValue ;
}
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 ) ;
2022-02-27 19:49:34 +00:00
} else if ( ! ( value instanceof Decimal ) && ! isRef ( value ) ) {
2022-01-14 04:25:47 +00:00
handleObject ( value as Record < string , unknown > ) ;
}
}
} ) ;
} ;
handleObject ( obj ) ;
return objects ;
}
globalBus . on ( "addLayer" , ( layer : GenericLayer , saveData : Record < string , unknown > ) = > {
2022-01-25 04:25:34 +00:00
const handleObject = ( obj : Record < string , unknown > , path : string [ ] = [ ] ) : boolean = > {
2022-01-25 04:23:30 +00:00
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" ) {
2022-01-25 04:25:34 +00:00
if ( PersistentState in value ) {
2022-01-25 04:23:30 +00:00
foundPersistent = true ;
2022-01-25 04:25:34 +00:00
// Construct save path if it doesn't exist
const persistentState = path . reduce < Record < string , unknown > > ( ( acc , curr ) = > {
if ( ! ( curr in acc ) ) {
acc [ curr ] = { } ;
}
return acc [ curr ] as Record < string , unknown > ;
} , saveData ) ;
// Cache currently saved value
2022-01-14 04:25:47 +00:00
const savedValue = persistentState [ key ] ;
2022-01-25 04:25:34 +00:00
// Add ref to save data
persistentState [ key ] = ( value as Persistent ) [ PersistentState ] ;
// Load previously saved value
2022-01-14 04:25:47 +00:00
if ( savedValue != null ) {
( persistentState [ key ] as Ref < unknown > ) . value = savedValue ;
2022-02-27 19:49:34 +00:00
} else {
( persistentState [ key ] as Ref < unknown > ) . value = ( value as Persistent ) [
DefaultValue
] ;
2022-01-14 04:25:47 +00:00
}
2022-02-27 19:49:34 +00:00
} else if ( ! ( value instanceof Decimal ) && ! isRef ( value ) ) {
2022-01-25 04:25:34 +00:00
// Continue traversing
const foundPersistentInChild = handleObject ( value as Record < string , unknown > , [
. . . path ,
key
] ) ;
// Show warning for persistent values inside arrays
// TODO handle arrays better
2022-01-25 04:23:30 +00:00
if ( foundPersistentInChild ) {
2022-02-27 19:49:34 +00:00
if ( isArray ( value ) && ! isArray ( obj ) ) {
2022-01-25 04:23:30 +00:00
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." ,
2022-02-27 19:49:34 +00:00
ProxyState in obj
? ( obj as Record < PropertyKey , unknown > ) [ ProxyState ]
: obj ,
2022-01-25 04:23:30 +00:00
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:25:34 +00:00
handleObject ( layer ) ;
2022-01-14 04:25:47 +00:00
} ) ;