2022-12-31 20:57:09 +00:00
import { isArray } from "@vue/shared" ;
import { CoercableComponent , jsx , JSXFunction , setDefault , Visibility } from "features/feature" ;
import { displayResource , Resource } from "features/resources/resource" ;
import Decimal , { DecimalSource } from "lib/break_eternity" ;
import {
Computable ,
convertComputable ,
processComputable ,
ProcessedComputable
} from "util/computed" ;
import { createLazyProxy } from "util/proxies" ;
import { joinJSX , renderJSX } from "util/vue" ;
import { computed , unref } from "vue" ;
2023-02-05 08:44:03 +00:00
import Formula , { calculateCost , calculateMaxAffordable , GenericFormula } from "./formulas" ;
2022-12-31 20:57:09 +00:00
/ * *
* An object that can be used to describe a requirement to perform some purchase or other action .
* @see { @link createCostRequirement }
* /
export interface Requirement {
/** The display for this specific requirement. This is used for displays multiple requirements condensed. Required if {@link visibility} can be {@link Visibility.Visible}. */
2023-02-05 08:44:03 +00:00
partialDisplay ? : ( amount? : DecimalSource ) = > JSX . Element ;
2022-12-31 20:57:09 +00:00
/** The display for this specific requirement. Required if {@link visibility} can be {@link Visibility.Visible}. */
2023-02-05 08:44:03 +00:00
display ? : ( amount? : DecimalSource ) = > JSX . Element ;
2022-12-31 20:57:09 +00:00
visibility : ProcessedComputable < Visibility.Visible | Visibility.None > ;
2023-02-05 08:44:03 +00:00
requirementMet : ProcessedComputable < DecimalSource | boolean > ;
2022-12-31 20:57:09 +00:00
requiresPay : ProcessedComputable < boolean > ;
2023-02-05 08:44:03 +00:00
buyMax? : ProcessedComputable < boolean > ;
pay ? : ( amount? : DecimalSource ) = > void ;
2022-12-31 20:57:09 +00:00
}
export type Requirements = Requirement | Requirement [ ] ;
export interface CostRequirementOptions {
resource : Resource ;
2023-02-05 08:44:03 +00:00
cost : Computable < DecimalSource > | GenericFormula ;
2022-12-31 20:57:09 +00:00
visibility? : Computable < Visibility.Visible | Visibility.None > ;
2023-02-05 08:44:03 +00:00
requiresPay? : Computable < boolean > ;
buyMax? : Computable < boolean > ;
spendResources? : Computable < boolean > ;
pay ? : ( amount? : DecimalSource ) = > void ;
2022-12-31 20:57:09 +00:00
}
2023-02-05 08:44:03 +00:00
export function createCostRequirement < T extends CostRequirementOptions > ( optionsFunc : ( ) = > T ) {
2022-12-31 20:57:09 +00:00
return createLazyProxy ( ( ) = > {
const req = optionsFunc ( ) as T & Partial < Requirement > ;
2023-02-05 08:44:03 +00:00
req . partialDisplay = amount = > (
2022-12-31 20:57:09 +00:00
< span
style = {
unref ( req . requirementMet as ProcessedComputable < boolean > )
? ""
: "color: var(--danger)"
}
>
{ displayResource (
req . resource ,
2023-02-05 08:44:03 +00:00
req . cost instanceof Formula
? calculateCost (
req . cost ,
amount ? ? 1 ,
unref (
req . spendResources as ProcessedComputable < boolean > | undefined
) ? ? true
)
: unref ( req . cost as ProcessedComputable < DecimalSource > )
2022-12-31 20:57:09 +00:00
) } { " " }
{ req . resource . displayName }
< / span >
2023-02-05 08:44:03 +00:00
) ;
req . display = amount = > (
2022-12-31 20:57:09 +00:00
< div >
{ unref ( req . requiresPay as ProcessedComputable < boolean > ) ? "Costs: " : "Requires: " }
{ displayResource (
req . resource ,
2023-02-05 08:44:03 +00:00
req . cost instanceof Formula
? calculateCost (
req . cost ,
amount ? ? 1 ,
unref (
req . spendResources as ProcessedComputable < boolean > | undefined
) ? ? true
)
: unref ( req . cost as ProcessedComputable < DecimalSource > )
2022-12-31 20:57:09 +00:00
) } { " " }
{ req . resource . displayName }
< / div >
2023-02-05 08:44:03 +00:00
) ;
2022-12-31 20:57:09 +00:00
processComputable ( req as T , "visibility" ) ;
setDefault ( req , "visibility" , Visibility . Visible ) ;
processComputable ( req as T , "cost" ) ;
processComputable ( req as T , "requiresPay" ) ;
2023-02-05 08:44:03 +00:00
processComputable ( req as T , "spendResources" ) ;
2022-12-31 20:57:09 +00:00
setDefault ( req , "requiresPay" , true ) ;
2023-02-05 08:44:03 +00:00
setDefault ( req , "pay" , function ( amount? : DecimalSource ) {
const cost =
req . cost instanceof Formula
? calculateCost (
req . cost ,
amount ? ? 1 ,
unref ( req . spendResources as ProcessedComputable < boolean > | undefined ) ? ?
true
)
: unref ( req . cost as ProcessedComputable < DecimalSource > ) ;
req . resource . value = Decimal . sub ( req . resource . value , cost ) . max ( 0 ) ;
2022-12-31 20:57:09 +00:00
} ) ;
2023-02-05 08:44:03 +00:00
processComputable ( req as T , "buyMax" ) ;
if (
"buyMax" in req &&
req . buyMax !== false &&
req . cost instanceof Formula &&
req . cost . isInvertible ( )
) {
req . requirementMet = calculateMaxAffordable (
req . cost ,
req . resource ,
unref ( req . spendResources as ProcessedComputable < boolean > | undefined ) ? ? true
) ;
} else {
req . requirementMet = computed ( ( ) = > {
if ( req . cost instanceof Formula ) {
return Decimal . gte ( req . resource . value , req . cost . evaluate ( ) ) ;
} else {
return Decimal . gte (
req . resource . value ,
unref ( req . cost as ProcessedComputable < DecimalSource > )
) ;
}
} ) ;
}
2022-12-31 20:57:09 +00:00
return req as Requirement ;
} ) ;
}
export function createVisibilityRequirement ( feature : {
visibility : ProcessedComputable < Visibility > ;
} ) : Requirement {
return createLazyProxy ( ( ) = > ( {
requirementMet : computed ( ( ) = > unref ( feature . visibility ) === Visibility . Visible ) ,
visibility : Visibility.None ,
requiresPay : false
} ) ) ;
}
export function createBooleanRequirement (
requirement : Computable < boolean > ,
display? : CoercableComponent
) : Requirement {
return createLazyProxy ( ( ) = > ( {
requirementMet : convertComputable ( requirement ) ,
partialDisplay : display == null ? undefined : jsx ( ( ) = > renderJSX ( display ) ) ,
display : display == null ? undefined : jsx ( ( ) = > < > Req : { renderJSX ( display ) } < / > ) ,
visibility : display == null ? Visibility.None : Visibility.Visible ,
requiresPay : false
} ) ) ;
}
2023-02-05 08:44:03 +00:00
export function requirementsMet ( requirements : Requirements ) : boolean {
if ( isArray ( requirements ) ) {
return requirements . every ( requirementsMet ) ;
}
const reqsMet = unref ( requirements . requirementMet ) ;
return typeof reqsMet === "boolean" ? reqsMet : Decimal.gt ( reqsMet , 0 ) ;
}
export function maxRequirementsMet ( requirements : Requirements ) : DecimalSource {
2022-12-31 20:57:09 +00:00
if ( isArray ( requirements ) ) {
2023-02-05 08:44:03 +00:00
return requirements . map ( maxRequirementsMet ) . reduce ( Decimal . min ) ;
}
const reqsMet = unref ( requirements . requirementMet ) ;
if ( typeof reqsMet === "boolean" ) {
return reqsMet ? Infinity : 0 ;
2022-12-31 20:57:09 +00:00
}
2023-02-05 08:44:03 +00:00
return reqsMet ;
2022-12-31 20:57:09 +00:00
}
2023-02-05 08:44:03 +00:00
export function displayRequirements ( requirements : Requirements , amount : DecimalSource = 1 ) {
2022-12-31 20:57:09 +00:00
if ( isArray ( requirements ) ) {
requirements = requirements . filter ( r = > unref ( r . visibility ) === Visibility . Visible ) ;
if ( requirements . length === 1 ) {
requirements = requirements [ 0 ] ;
}
}
if ( isArray ( requirements ) ) {
requirements = requirements . filter ( r = > "partialDisplay" in r ) ;
const withCosts = requirements . filter ( r = > unref ( r . requiresPay ) ) ;
const withoutCosts = requirements . filter ( r = > ! unref ( r . requiresPay ) ) ;
return (
< >
{ withCosts . length > 0 ? (
< div >
Costs : { " " }
{ joinJSX (
2023-02-05 08:44:03 +00:00
withCosts . map ( r = > r . partialDisplay ! ( amount ) ) ,
2022-12-31 20:57:09 +00:00
< > , < / >
) }
< / div >
) : null }
{ withoutCosts . length > 0 ? (
< div >
Requires : { " " }
{ joinJSX (
2023-02-05 08:44:03 +00:00
withoutCosts . map ( r = > r . partialDisplay ! ( amount ) ) ,
2022-12-31 20:57:09 +00:00
< > , < / >
) }
< / div >
) : null }
< / >
) ;
}
return requirements . display ? . ( ) ? ? < > < / > ;
}
2023-02-05 08:44:03 +00:00
export function payRequirements ( requirements : Requirements , buyMax = false ) {
const amount = buyMax ? maxRequirementsMet ( requirements ) : 1 ;
2022-12-31 20:57:09 +00:00
if ( isArray ( requirements ) ) {
2023-02-05 08:44:03 +00:00
requirements . filter ( r = > unref ( r . requiresPay ) ) . forEach ( r = > r . pay ? . ( amount ) ) ;
2022-12-31 20:57:09 +00:00
} else if ( unref ( requirements . requiresPay ) ) {
2023-02-05 08:44:03 +00:00
requirements . pay ? . ( amount ) ;
2022-12-31 20:57:09 +00:00
}
}