2022-12-31 20:57:09 +00:00
import { isArray } from "@vue/shared" ;
2022-03-04 03:39:48 +00:00
import ClickableComponent from "features/clickables/Clickable.vue" ;
2022-12-31 20:57:09 +00:00
import type { CoercableComponent , OptionsFunc , Replace , StyleValue } from "features/feature" ;
2022-06-27 00:17:22 +00:00
import { Component , GatherProps , getUniqueID , jsx , setDefault , Visibility } from "features/feature" ;
2022-12-31 20:57:09 +00:00
import { DefaultValue , Persistent , persistent } from "game/persistence" ;
import {
createVisibilityRequirement ,
displayRequirements ,
2023-02-05 08:51:07 +00:00
maxRequirementsMet ,
2022-12-31 20:57:09 +00:00
payRequirements ,
Requirements ,
requirementsMet
} from "game/requirements" ;
2022-06-27 00:17:22 +00:00
import type { DecimalSource } from "util/bignum" ;
2022-12-31 20:57:09 +00:00
import Decimal , { formatWhole } from "util/bignum" ;
2022-06-27 00:17:22 +00:00
import type {
2022-01-14 04:25:47 +00:00
Computable ,
GetComputableType ,
GetComputableTypeWithDefault ,
ProcessedComputable
2022-03-04 03:39:48 +00:00
} from "util/computed" ;
2022-06-27 00:17:22 +00:00
import { processComputable } from "util/computed" ;
2022-03-04 03:39:48 +00:00
import { createLazyProxy } from "util/proxies" ;
import { coerceComponent , isCoercableComponent } from "util/vue" ;
2022-06-27 00:17:22 +00:00
import type { Ref } from "vue" ;
import { computed , unref } from "vue" ;
2022-01-14 04:25:47 +00:00
2023-02-15 16:17:07 +00:00
/** A symbol used to identify {@link Repeatable} features. */
2023-02-14 19:23:59 +00:00
export const RepeatableType = Symbol ( "Repeatable" ) ;
2022-01-14 04:25:47 +00:00
2023-02-15 16:17:07 +00:00
/** A type that can be used to customize the {@link Repeatable} display. */
2023-02-14 19:23:59 +00:00
export type RepeatableDisplay =
2022-01-14 04:25:47 +00:00
| CoercableComponent
| {
2023-02-15 16:17:07 +00:00
/** A header to appear at the top of the display. */
2022-01-14 04:25:47 +00:00
title? : CoercableComponent ;
2023-02-15 16:17:07 +00:00
/** The main text that appears in the display. */
2022-07-07 15:39:45 +00:00
description? : CoercableComponent ;
2023-02-15 16:17:07 +00:00
/** A description of the current effect of this repeatable, bsed off its amount. */
2022-01-14 04:25:47 +00:00
effectDisplay? : CoercableComponent ;
2023-02-15 16:17:07 +00:00
/** Whether or not to show the current amount of this repeatable at the bottom of the display. */
2022-07-06 15:09:37 +00:00
showAmount? : boolean ;
2022-01-14 04:25:47 +00:00
} ;
2023-02-15 16:17:07 +00:00
/** An object that configures a {@link Repeatable}. */
2023-02-14 19:23:59 +00:00
export interface RepeatableOptions {
2023-02-15 16:17:07 +00:00
/** Whether this repeatable should be visible. */
2023-02-16 02:00:36 +00:00
visibility? : Computable < Visibility | boolean > ;
2023-02-15 16:17:07 +00:00
/** The requirement(s) to increase this repeatable. */
2022-12-31 20:57:09 +00:00
requirements : Requirements ;
2023-02-15 16:17:07 +00:00
/** The maximum amount obtainable for this repeatable. */
2023-02-15 03:35:05 +00:00
limit? : Computable < DecimalSource > ;
2023-02-15 16:17:07 +00:00
/** The initial amount this repeatable has on a new save / after reset. */
2023-02-15 03:35:05 +00:00
initialAmount? : DecimalSource ;
2023-02-15 16:17:07 +00:00
/** Dictionary of CSS classes to apply to this feature. */
2022-01-14 04:25:47 +00:00
classes? : Computable < Record < string , boolean > > ;
2023-02-15 16:17:07 +00:00
/** CSS to apply to this feature. */
2022-01-14 04:25:47 +00:00
style? : Computable < StyleValue > ;
2023-02-15 16:17:07 +00:00
/** Shows a marker on the corner of the feature. */
2022-01-14 04:25:47 +00:00
mark? : Computable < boolean | string > ;
2023-02-15 16:17:07 +00:00
/** Toggles a smaller design for the feature. */
2022-01-14 04:25:47 +00:00
small? : Computable < boolean > ;
2023-02-15 16:17:07 +00:00
/** Whether or not clicking this repeatable should attempt to maximize amount based on the requirements met. Requires {@link requirements} to be a requirement or array of requirements with {@link Requirement.canMaximize} true. */
2023-02-15 03:47:18 +00:00
maximize? : Computable < boolean > ;
2023-02-15 16:17:07 +00:00
/** The display to use for this repeatable. */
2023-02-14 19:23:59 +00:00
display? : Computable < RepeatableDisplay > ;
2022-01-14 04:25:47 +00:00
}
2023-02-15 16:17:07 +00:00
/ * *
* The properties that are added onto a processed { @link RepeatableOptions } to create a { @link Repeatable } .
* /
2023-02-14 19:23:59 +00:00
export interface BaseRepeatable {
2023-02-15 16:17:07 +00:00
/** An auto-generated ID for identifying features that appear in the DOM. Will not persistent between refreshes or updates. */
2022-01-14 04:25:47 +00:00
id : string ;
2023-02-15 16:17:07 +00:00
/** The current amount this repeatable has. */
2022-04-23 23:20:15 +00:00
amount : Persistent < DecimalSource > ;
2023-02-15 16:17:07 +00:00
/** Whether or not this repeatable's amount is at it's limit. */
2022-02-27 19:49:34 +00:00
maxed : Ref < boolean > ;
2023-02-15 16:17:07 +00:00
/** Whether or not this repeatable can be clicked. */
2022-01-14 04:25:47 +00:00
canClick : ProcessedComputable < boolean > ;
2023-02-15 16:17:07 +00:00
/** A function that gets called when this repeatable is clicked. */
2023-02-15 20:57:22 +00:00
onClick : ( event? : MouseEvent | TouchEvent ) = > void ;
2023-02-15 16:17:07 +00:00
/** A symbol that helps identify features of the same type. */
2023-02-14 19:23:59 +00:00
type : typeof RepeatableType ;
2023-02-15 16:17:07 +00:00
/** The Vue component used to render this feature. */
2022-01-14 04:25:47 +00:00
[ Component ] : typeof ClickableComponent ;
2023-02-15 16:17:07 +00:00
/** A function to gather the props the vue component requires for this feature. */
2022-02-27 19:49:34 +00:00
[ GatherProps ] : ( ) = > Record < string , unknown > ;
2022-01-14 04:25:47 +00:00
}
2023-02-15 16:17:07 +00:00
/** An object that represents a feature with multiple "levels" with scaling requirements. */
2023-02-14 19:23:59 +00:00
export type Repeatable < T extends RepeatableOptions > = Replace <
T & BaseRepeatable ,
2022-01-14 04:25:47 +00:00
{
visibility : GetComputableTypeWithDefault < T [ " visibility " ] , Visibility.Visible > ;
2022-12-31 20:57:09 +00:00
requirements : GetComputableType < T [ " requirements " ] > ;
2023-02-15 03:35:05 +00:00
limit : GetComputableTypeWithDefault < T [ " limit " ] , Decimal > ;
2022-01-14 04:25:47 +00:00
classes : GetComputableType < T [ " classes " ] > ;
style : GetComputableType < T [ " style " ] > ;
mark : GetComputableType < T [ " mark " ] > ;
small : GetComputableType < T [ " small " ] > ;
2023-02-15 03:47:18 +00:00
maximize : GetComputableType < T [ " maximize " ] > ;
2022-01-14 04:25:47 +00:00
display : Ref < CoercableComponent > ;
}
> ;
2023-02-15 16:17:07 +00:00
/** A type that matches any valid {@link Repeatable} object. */
2023-02-14 19:23:59 +00:00
export type GenericRepeatable = Replace <
Repeatable < RepeatableOptions > ,
2022-01-14 04:25:47 +00:00
{
2023-02-16 02:00:36 +00:00
visibility : ProcessedComputable < Visibility | boolean > ;
2023-02-15 03:35:05 +00:00
limit : ProcessedComputable < DecimalSource > ;
2022-01-14 04:25:47 +00:00
}
> ;
2023-02-15 16:17:07 +00:00
/ * *
* Lazily creates a repeatable with the given options .
* @param optionsFunc Repeatable options .
* /
2023-02-14 19:23:59 +00:00
export function createRepeatable < T extends RepeatableOptions > (
optionsFunc : OptionsFunc < T , BaseRepeatable , GenericRepeatable >
) : Repeatable < T > {
2022-04-23 23:20:15 +00:00
const amount = persistent < DecimalSource > ( 0 ) ;
return createLazyProxy ( ( ) = > {
2023-02-14 19:23:59 +00:00
const repeatable = optionsFunc ( ) ;
2022-01-14 04:25:47 +00:00
2023-02-14 19:23:59 +00:00
repeatable . id = getUniqueID ( "repeatable-" ) ;
repeatable . type = RepeatableType ;
repeatable [ Component ] = ClickableComponent ;
2022-02-27 19:49:34 +00:00
2023-02-14 19:23:59 +00:00
repeatable . amount = amount ;
2023-02-15 03:35:05 +00:00
repeatable . amount [ DefaultValue ] = repeatable . initialAmount ? ? 0 ;
2022-12-31 20:57:09 +00:00
const limitRequirement = {
requirementMet : computed ( ( ) = >
2023-02-05 08:51:07 +00:00
Decimal . sub (
2023-02-15 03:35:05 +00:00
unref ( ( repeatable as GenericRepeatable ) . limit ) ,
2023-02-14 19:23:59 +00:00
( repeatable as GenericRepeatable ) . amount . value
2022-12-31 20:57:09 +00:00
)
) ,
requiresPay : false ,
visibility : Visibility.None
} as const ;
2023-02-14 19:23:59 +00:00
const visibilityRequirement = createVisibilityRequirement ( repeatable as GenericRepeatable ) ;
if ( isArray ( repeatable . requirements ) ) {
repeatable . requirements . unshift ( visibilityRequirement ) ;
repeatable . requirements . push ( limitRequirement ) ;
2022-12-31 20:57:09 +00:00
} else {
2023-02-14 19:23:59 +00:00
repeatable . requirements = [
visibilityRequirement ,
repeatable . requirements ,
limitRequirement
] ;
2022-01-14 04:25:47 +00:00
}
2022-12-31 20:57:09 +00:00
2023-02-14 19:23:59 +00:00
repeatable . maxed = computed ( ( ) = >
2022-02-27 19:49:34 +00:00
Decimal . gte (
2023-02-14 19:23:59 +00:00
( repeatable as GenericRepeatable ) . amount . value ,
2023-02-15 03:35:05 +00:00
unref ( ( repeatable as GenericRepeatable ) . limit )
2022-02-27 19:49:34 +00:00
)
) ;
2023-02-14 19:23:59 +00:00
processComputable ( repeatable as T , "classes" ) ;
const classes = repeatable . classes as
| ProcessedComputable < Record < string , boolean > >
| undefined ;
repeatable . classes = computed ( ( ) = > {
2022-02-27 19:49:34 +00:00
const currClasses = unref ( classes ) || { } ;
2023-02-14 19:23:59 +00:00
if ( ( repeatable as GenericRepeatable ) . maxed . value ) {
2022-02-27 19:49:34 +00:00
currClasses . bought = true ;
}
return currClasses ;
} ) ;
2023-02-14 19:23:59 +00:00
repeatable . canClick = computed ( ( ) = > requirementsMet ( repeatable . requirements ) ) ;
2023-02-15 06:22:24 +00:00
const onClick = repeatable . onClick ;
2023-02-15 20:57:22 +00:00
repeatable . onClick = function ( this : GenericRepeatable , event? : MouseEvent | TouchEvent ) {
2023-02-15 06:22:24 +00:00
const genericRepeatable = repeatable as GenericRepeatable ;
if ( ! unref ( genericRepeatable . canClick ) ) {
return ;
}
payRequirements (
repeatable . requirements ,
unref ( genericRepeatable . maximize )
? maxRequirementsMet ( genericRepeatable . requirements )
: 1
) ;
genericRepeatable . amount . value = Decimal . add ( genericRepeatable . amount . value , 1 ) ;
2023-02-15 20:57:22 +00:00
onClick ? . ( event ) ;
2023-02-15 06:22:24 +00:00
} ;
2023-02-14 19:23:59 +00:00
processComputable ( repeatable as T , "display" ) ;
const display = repeatable . display ;
repeatable . display = jsx ( ( ) = > {
2022-02-27 19:49:34 +00:00
// TODO once processComputable types correctly, remove this "as X"
2023-02-14 19:23:59 +00:00
const currDisplay = unref ( display ) as RepeatableDisplay ;
2022-03-16 16:20:53 +00:00
if ( isCoercableComponent ( currDisplay ) ) {
2022-03-16 16:24:54 +00:00
const CurrDisplay = coerceComponent ( currDisplay ) ;
return < CurrDisplay / > ;
2022-03-16 16:20:53 +00:00
}
2022-12-31 20:57:09 +00:00
if ( currDisplay != null ) {
2023-02-14 19:23:59 +00:00
const genericRepeatable = repeatable as GenericRepeatable ;
2022-12-21 03:26:25 +00:00
const Title = coerceComponent ( currDisplay . title ? ? "" , "h3" ) ;
const Description = coerceComponent ( currDisplay . description ? ? "" ) ;
const EffectDisplay = coerceComponent ( currDisplay . effectDisplay ? ? "" ) ;
2022-03-11 15:16:19 +00:00
2022-02-27 19:49:34 +00:00
return (
< span >
2022-12-21 03:26:25 +00:00
{ currDisplay . title == null ? null : (
2022-02-27 19:49:34 +00:00
< div >
< Title / >
< / div >
2022-12-21 03:26:25 +00:00
) }
{ currDisplay . description == null ? null : < Description / > }
2022-07-06 15:09:37 +00:00
{ currDisplay . showAmount === false ? null : (
< div >
< br / >
2023-02-15 03:35:05 +00:00
{ unref ( genericRepeatable . limit ) === Decimal . dInf ? (
2023-02-14 19:23:59 +00:00
< > Amount : { formatWhole ( genericRepeatable . amount . value ) } < / >
2022-07-06 15:09:37 +00:00
) : (
< >
2023-02-14 19:23:59 +00:00
Amount : { formatWhole ( genericRepeatable . amount . value ) } / { " " }
2023-02-15 03:35:05 +00:00
{ formatWhole ( unref ( genericRepeatable . limit ) ) }
2022-07-06 15:09:37 +00:00
< / >
) }
< / div >
) }
2022-12-21 03:26:25 +00:00
{ currDisplay . effectDisplay == null ? null : (
2022-02-27 19:49:34 +00:00
< div >
< br / >
Currently : < EffectDisplay / >
< / div >
2022-12-21 03:26:25 +00:00
) }
2023-02-14 19:23:59 +00:00
{ genericRepeatable . maxed . value ? null : (
2022-02-27 19:49:34 +00:00
< div >
< br / >
2023-02-05 08:51:07 +00:00
{ displayRequirements (
2023-02-14 19:23:59 +00:00
genericRepeatable . requirements ,
2023-02-15 03:47:18 +00:00
unref ( genericRepeatable . maximize )
2023-02-14 19:23:59 +00:00
? maxRequirementsMet ( genericRepeatable . requirements )
2023-02-05 08:51:07 +00:00
: 1
) }
2022-02-27 19:49:34 +00:00
< / div >
2022-12-31 20:57:09 +00:00
) }
2022-02-27 19:49:34 +00:00
< / span >
) ;
}
return "" ;
} ) ;
2023-02-14 19:23:59 +00:00
processComputable ( repeatable as T , "visibility" ) ;
setDefault ( repeatable , "visibility" , Visibility . Visible ) ;
2023-02-15 03:35:05 +00:00
processComputable ( repeatable as T , "limit" ) ;
setDefault ( repeatable , "limit" , Decimal . dInf ) ;
2023-02-14 19:23:59 +00:00
processComputable ( repeatable as T , "style" ) ;
processComputable ( repeatable as T , "mark" ) ;
processComputable ( repeatable as T , "small" ) ;
2023-02-15 03:47:18 +00:00
processComputable ( repeatable as T , "maximize" ) ;
2022-01-14 04:25:47 +00:00
2023-02-14 19:23:59 +00:00
repeatable [ GatherProps ] = function ( this : GenericRepeatable ) {
2022-02-27 19:49:34 +00:00
const { display , visibility , style , classes , onClick , canClick , small , mark , id } =
this ;
2022-03-13 22:09:09 +00:00
return {
display ,
visibility ,
style : unref ( style ) ,
classes ,
onClick ,
canClick ,
small ,
mark ,
id
} ;
2022-02-27 19:49:34 +00:00
} ;
2022-01-14 04:25:47 +00:00
2023-02-14 19:23:59 +00:00
return repeatable as unknown as Repeatable < T > ;
2022-04-23 23:20:15 +00:00
} ) ;
2022-01-14 04:25:47 +00:00
}