diff --git a/src/data/common.tsx b/src/data/common.tsx index f392741..b421460 100644 --- a/src/data/common.tsx +++ b/src/data/common.tsx @@ -20,6 +20,7 @@ import type { ProcessedComputable } from "util/computed"; import { convertComputable, processComputable } from "util/computed"; +import { createLazyProxy } from "util/proxies"; import { renderJSX } from "util/vue"; import type { Ref } from "vue"; import { computed, unref } from "vue"; @@ -33,7 +34,7 @@ export interface ResetButtonOptions extends ClickableOptions { tree: GenericTree; /** The specific tree node associated with this reset button */ treeNode: GenericTreeNode; - /** + /** * Text to display on low conversion amounts, describing what "resetting" is in this context. * Defaults to "Reset for ". */ @@ -253,62 +254,67 @@ export interface Section { /** * Takes an array of modifier "sections", and creates a JSXFunction that can render all those sections, and allow each section to be collapsed. * Also returns a list of persistent refs that are used to control which sections are currently collapsed. + * @param sectionsFunc A function that returns the sections to display. */ export function createCollapsibleModifierSections( - sections: Section[] + sectionsFunc: () => Section[] ): [JSXFunction, Persistent<boolean>[]] { - const processedBase = sections.map(s => convertComputable(s.base)); - const processedBaseText = sections.map(s => convertComputable(s.baseText)); - const processedVisible = sections.map(s => convertComputable(s.visible)); - const collapsed = sections.map(() => persistent<boolean>(false)); - const jsxFunc = jsx(() => { - const sectionJSX = sections.map((s, i) => { - if (unref(processedVisible[i]) === false) return null; - const header = ( - <h3 - onClick={() => (collapsed[i].value = !collapsed[i].value)} - style="cursor: pointer" - > - <span class={"modifier-toggle" + (unref(collapsed[i]) ? " collapsed" : "")}> - ▼ - </span> - {s.title} - {s.subtitle ? <span class="subtitle"> ({s.subtitle})</span> : null} - </h3> - ); + return createLazyProxy(() => { + const sections = sectionsFunc(); - const modifiers = unref(collapsed[i]) ? null : ( - <> - <div class="modifier-container"> - <span class="modifier-amount"> - {format(unref(processedBase[i]) ?? 1)} + const processedBase = sections.map(s => convertComputable(s.base)); + const processedBaseText = sections.map(s => convertComputable(s.baseText)); + const processedVisible = sections.map(s => convertComputable(s.visible)); + const collapsed = sections.map(() => persistent<boolean>(false)); + const jsxFunc = jsx(() => { + const sectionJSX = sections.map((s, i) => { + if (unref(processedVisible[i]) === false) return null; + const header = ( + <h3 + onClick={() => (collapsed[i].value = !collapsed[i].value)} + style="cursor: pointer" + > + <span class={"modifier-toggle" + (unref(collapsed[i]) ? " collapsed" : "")}> + ▼ + </span> + {s.title} + {s.subtitle ? <span class="subtitle"> ({s.subtitle})</span> : null} + </h3> + ); + + const modifiers = unref(collapsed[i]) ? null : ( + <> + <div class="modifier-container"> + <span class="modifier-amount"> + {format(unref(processedBase[i]) ?? 1)} + {s.unit} + </span> + <span class="modifier-description"> + {renderJSX(unref(processedBaseText[i]) ?? "Base")} + </span> + </div> + {renderJSX(unref(s.modifier.description))} + </> + ); + + return ( + <> + {i === 0 ? null : <br />} + <div> + {header} + <br /> + {modifiers} + <hr /> + Total: {format(s.modifier.apply(unref(processedBase[i]) ?? 1))} {s.unit} - </span> - <span class="modifier-description"> - {renderJSX(unref(processedBaseText[i]) ?? "Base")} - </span> - </div> - {renderJSX(unref(s.modifier.description))} - </> - ); - - return ( - <> - {i === 0 ? null : <br />} - <div> - {header} - <br /> - {modifiers} - <hr /> - Total: {format(s.modifier.apply(unref(processedBase[i]) ?? 1))} - {s.unit} - </div> - </> - ); + </div> + </> + ); + }); + return <>{sectionJSX}</>; }); - return <>{sectionJSX}</>; + return [jsxFunc, collapsed]; }); - return [jsxFunc, collapsed]; } /** diff --git a/src/game/modifiers.tsx b/src/game/modifiers.tsx index 6f090c7..b476027 100644 --- a/src/game/modifiers.tsx +++ b/src/game/modifiers.tsx @@ -6,6 +6,7 @@ import Decimal, { format } from "util/bignum"; import type { WithRequired } from "util/common"; import type { Computable, ProcessedComputable } from "util/computed"; import { convertComputable } from "util/computed"; +import { createLazyProxy } from "util/proxies"; import { renderJSX } from "util/vue"; import { computed, unref } from "vue"; @@ -44,112 +45,161 @@ export type ModifierFromOptionalParams<T, S> = T extends undefined ? Omit<WithRequired<Modifier, "revert" | "description">, "enabled"> : WithRequired<Modifier, "revert" | "enabled" | "description">; +/** An object that configures an additive modifier via {@link createAdditiveModifier}. */ +export interface AdditiveModifierOptions< + T extends Computable<CoercableComponent> | undefined, + S extends Computable<boolean> | undefined +> { + /** The amount to add to the input value. */ + addend: Computable<DecimalSource>; + /** Description of what this modifier is doing. */ + description?: T; + /** A computable that will be processed and passed directly into the returned modifier. */ + enabled?: S; +} + /** * Create a modifier that adds some value to the input value. - * @param addend The amount to add to the input value. - * @param description Description of what this modifier is doing. - * @param enabled A computable that will be processed and passed directly into the returned modifier. + * @param optionsFunc Additive modifier options. */ export function createAdditiveModifier< T extends Computable<CoercableComponent> | undefined, S extends Computable<boolean> | undefined, R = ModifierFromOptionalParams<T, S> ->(addend: Computable<DecimalSource>, description?: T, enabled?: S): R { - const processedAddend = convertComputable(addend); - const processedDescription = convertComputable(description); - const processedEnabled = enabled == null ? undefined : convertComputable(enabled); - return { - apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)), - revert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)), - enabled: processedEnabled, - description: - description == null - ? undefined - : jsx(() => ( - <div class="modifier-container"> - <span class="modifier-amount"> - {Decimal.gte(unref(processedAddend), 0) ? "+" : ""} - {format(unref(processedAddend))} - </span> - {unref(processedDescription) ? ( - <span class="modifier-description"> - {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} - {renderJSX(unref(processedDescription)!)} +>(optionsFunc: () => AdditiveModifierOptions<T, S>): R { + return createLazyProxy(() => { + const { addend, description, enabled } = optionsFunc(); + + const processedAddend = convertComputable(addend); + const processedDescription = convertComputable(description); + const processedEnabled = enabled == null ? undefined : convertComputable(enabled); + return { + apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)), + revert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)), + enabled: processedEnabled, + description: + description == null + ? undefined + : jsx(() => ( + <div class="modifier-container"> + <span class="modifier-amount"> + {Decimal.gte(unref(processedAddend), 0) ? "+" : ""} + {format(unref(processedAddend))} </span> - ) : null} - </div> - )) - } as unknown as R; + {unref(processedDescription) ? ( + <span class="modifier-description"> + {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} + {renderJSX(unref(processedDescription)!)} + </span> + ) : null} + </div> + )) + }; + }) as unknown as R; +} + +/** An object that configures an multiplicative modifier via {@link createMultiplicativeModifier}. */ +export interface MultiplicativeModifierOptions< + T extends Computable<CoercableComponent> | undefined, + S extends Computable<boolean> | undefined +> { + /** The amount to multiply the input value by. */ + multiplier: Computable<DecimalSource>; + /** Description of what this modifier is doing. */ + description?: T; + /** A computable that will be processed and passed directly into the returned modifier. */ + enabled?: S; } /** * Create a modifier that multiplies the input value by some value. - * @param multiplier The value to multiply the input value by. - * @param description Description of what this modifier is doing. - * @param enabled A computable that will be processed and passed directly into the returned modifier. + * @param optionsFunc Multiplicative modifier options. */ export function createMultiplicativeModifier< T extends Computable<CoercableComponent> | undefined, S extends Computable<boolean> | undefined, R = ModifierFromOptionalParams<T, S> ->(multiplier: Computable<DecimalSource>, description?: T, enabled?: S): R { - const processedMultiplier = convertComputable(multiplier); - const processedDescription = convertComputable(description); - const processedEnabled = enabled == null ? undefined : convertComputable(enabled); - return { - apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)), - revert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)), - enabled: processedEnabled, - description: - description == null - ? undefined - : jsx(() => ( - <div class="modifier-container"> - <span class="modifier-amount">x{format(unref(processedMultiplier))}</span> - {unref(processedDescription) ? ( - <span class="modifier-description"> - {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} - {renderJSX(unref(processedDescription)!)} +>(optionsFunc: () => MultiplicativeModifierOptions<T, S>): R { + return createLazyProxy(() => { + const { multiplier, description, enabled } = optionsFunc(); + + const processedMultiplier = convertComputable(multiplier); + const processedDescription = convertComputable(description); + const processedEnabled = enabled == null ? undefined : convertComputable(enabled); + return { + apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)), + revert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)), + enabled: processedEnabled, + description: + description == null + ? undefined + : jsx(() => ( + <div class="modifier-container"> + <span class="modifier-amount"> + x{format(unref(processedMultiplier))} </span> - ) : null} - </div> - )) - } as unknown as R; + {unref(processedDescription) ? ( + <span class="modifier-description"> + {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} + {renderJSX(unref(processedDescription)!)} + </span> + ) : null} + </div> + )) + }; + }) as unknown as R; +} + +/** An object that configures an exponential modifier via {@link createExponentialModifier}. */ +export interface ExponentialModifierOptions< + T extends Computable<CoercableComponent> | undefined, + S extends Computable<boolean> | undefined +> { + /** The amount to raise the input value to the power of. */ + exponent: Computable<DecimalSource>; + /** Description of what this modifier is doing. */ + description?: T; + /** A computable that will be processed and passed directly into the returned modifier. */ + enabled?: S; } /** * Create a modifier that raises the input value to the power of some value. - * @param exponent The value to raise the input value to the power of. - * @param description Description of what this modifier is doing. - * @param enabled A computable that will be processed and passed directly into the returned modifier. + * @param optionsFunc Exponential modifier options. */ export function createExponentialModifier< T extends Computable<CoercableComponent> | undefined, S extends Computable<boolean> | undefined, R = ModifierFromOptionalParams<T, S> ->(exponent: Computable<DecimalSource>, description?: T, enabled?: S): R { - const processedExponent = convertComputable(exponent); - const processedDescription = convertComputable(description); - const processedEnabled = enabled == null ? undefined : convertComputable(enabled); - return { - apply: (gain: DecimalSource) => Decimal.pow(gain, unref(processedExponent)), - revert: (gain: DecimalSource) => Decimal.root(gain, unref(processedExponent)), - enabled: processedEnabled, - description: - description == null - ? undefined - : jsx(() => ( - <div class="modifier-container"> - <span class="modifier-amount">^{format(unref(processedExponent))}</span> - {unref(processedDescription) ? ( - <span class="modifier-description"> - {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} - {renderJSX(unref(processedDescription)!)} +>(optionsFunc: () => ExponentialModifierOptions<T, S>): R { + return createLazyProxy(() => { + const { exponent, description, enabled } = optionsFunc(); + + const processedExponent = convertComputable(exponent); + const processedDescription = convertComputable(description); + const processedEnabled = enabled == null ? undefined : convertComputable(enabled); + return { + apply: (gain: DecimalSource) => Decimal.pow(gain, unref(processedExponent)), + revert: (gain: DecimalSource) => Decimal.root(gain, unref(processedExponent)), + enabled: processedEnabled, + description: + description == null + ? undefined + : jsx(() => ( + <div class="modifier-container"> + <span class="modifier-amount"> + ^{format(unref(processedExponent))} </span> - ) : null} - </div> - )) - } as unknown as R; + {unref(processedDescription) ? ( + <span class="modifier-description"> + {/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */} + {renderJSX(unref(processedDescription)!)} + </span> + ) : null} + </div> + )) + }; + }) as unknown as R; } /**