Reworked flow for converting modifiers to formulas, and renamed revert to invert

This commit is contained in:
thepaperpilot 2023-04-15 09:39:16 -05:00
parent 8c0a0c4410
commit c65dc777cc
4 changed files with 33 additions and 42 deletions

View file

@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **BREAKING** Formulas, which can be used to calculate buy max for you
- Requirements can use them so repeatables and challenges can be "buy max" without any extra effort
- Conversions now use formulas instead of the old scaling functions system, allowing for arbitrary functions that are much easier to follow
- There's a utility for converting modifiers to formulas, thus replacing things like the gain modifier on conversions
- Modifiers have a new getFormula property
- Action feature, which is a clickable with a cooldown
- ETA util (calculates time until a specific amount of a resource, based on its current gain rate)
- createCollapsibleAchievements util

View file

@ -491,29 +491,3 @@ export function createFormulaPreview(
return formatSmall(formula.evaluate());
});
}
/**
* Utility for converting a modifier into a formula. Takes the input for this formula as the base parameter.
* @param modifier The modifier to convert to the formula
* @param base An existing formula or processed DecimalSource that will be the input to the formula
*/
export function modifierToFormula<T extends GenericFormula>(
modifier: WithRequired<Modifier, "revert">,
base: T
): T;
export function modifierToFormula(modifier: Modifier, base: FormulaSource): GenericFormula;
export function modifierToFormula(modifier: Modifier, base: FormulaSource) {
return new Formula({
inputs: [base],
evaluate: val => modifier.apply(val),
invert:
"revert" in modifier && modifier.revert != null
? (val, lhs) => {
if (lhs instanceof Formula && lhs.hasVariable()) {
return lhs.invert(modifier.revert!(val));
}
throw new Error("Could not invert due to no input being a variable");
}
: undefined
});
}

View file

@ -36,7 +36,7 @@ import { ClickableOptions } from "./clickables/clickable";
export const ActionType = Symbol("Action");
/**
* An object that configures a {@link Action}.
* An object that configures an {@link Action}.
*/
export interface ActionOptions extends Omit<ClickableOptions, "onClick" | "onHold"> {
/** The cooldown during which the action cannot be performed again, in seconds. */
@ -71,7 +71,7 @@ export interface BaseAction {
[GatherProps]: () => Record<string, unknown>;
}
/** An object that represens a feature that can be clicked upon, and then have a cooldown before they can be clicked again. */
/** An object that represents a feature that can be clicked upon, and then has a cooldown before it can be clicked again. */
export type Action<T extends ActionOptions> = Replace<
T & BaseAction,
{

View file

@ -10,6 +10,8 @@ import { convertComputable } from "util/computed";
import { createLazyProxy } from "util/proxies";
import { renderJSX } from "util/vue";
import { computed, unref } from "vue";
import Formula from "./formulas/formulas";
import { FormulaSource, GenericFormula } from "./formulas/types";
/**
* An object that can be used to apply or unapply some modification to a number.
@ -21,7 +23,9 @@ export interface Modifier {
/** Applies some operation on the input and returns the result. */
apply: (gain: DecimalSource) => DecimalSource;
/** Reverses the operation applied by the apply property. Required by some features. */
revert?: (gain: DecimalSource) => DecimalSource;
invert?: (gain: DecimalSource) => DecimalSource;
/** Get a formula for this modifier. Required by some features. */
getFormula?: (gain: FormulaSource) => GenericFormula;
/**
* Whether or not this modifier should be considered enabled.
* Typically for use with modifiers passed into {@link createSequentialModifier}.
@ -39,11 +43,11 @@ export interface Modifier {
*/
export type ModifierFromOptionalParams<T, S> = T extends undefined
? S extends undefined
? Omit<WithRequired<Modifier, "revert">, "description" | "enabled">
: Omit<WithRequired<Modifier, "revert" | "enabled">, "description">
? Omit<WithRequired<Modifier, "invert">, "description" | "enabled">
: Omit<WithRequired<Modifier, "invert" | "enabled">, "description">
: S extends undefined
? Omit<WithRequired<Modifier, "revert" | "description">, "enabled">
: WithRequired<Modifier, "revert" | "enabled" | "description">;
? Omit<WithRequired<Modifier, "invert" | "description">, "enabled">
: WithRequired<Modifier, "invert" | "enabled" | "description">;
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
export interface AdditiveModifierOptions {
@ -72,7 +76,8 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions>(
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
return {
apply: (gain: DecimalSource) => Decimal.add(gain, unref(processedAddend)),
revert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)),
invert: (gain: DecimalSource) => Decimal.sub(gain, unref(processedAddend)),
getFormula: (gain: FormulaSource) => Formula.add(gain, processedAddend),
enabled: processedEnabled,
description:
description == null
@ -133,7 +138,8 @@ export function createMultiplicativeModifier<T extends MultiplicativeModifierOpt
const processedEnabled = enabled == null ? undefined : convertComputable(enabled);
return {
apply: (gain: DecimalSource) => Decimal.times(gain, unref(processedMultiplier)),
revert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)),
invert: (gain: DecimalSource) => Decimal.div(gain, unref(processedMultiplier)),
getFormula: (gain: FormulaSource) => Formula.times(gain, processedMultiplier),
enabled: processedEnabled,
description:
description == null
@ -206,7 +212,7 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
}
return result;
},
revert: (gain: DecimalSource) => {
invert: (gain: DecimalSource) => {
let result = gain;
if (supportLowNumbers) {
result = Decimal.add(result, 1);
@ -217,6 +223,10 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
}
return result;
},
getFormula: (gain: FormulaSource) =>
supportLowNumbers
? Formula.add(gain, 1).pow(processedExponent).sub(1)
: Formula.pow(gain, processedExponent),
enabled: processedEnabled,
description:
description == null
@ -259,9 +269,9 @@ export function createExponentialModifier<T extends ExponentialModifierOptions>(
*/
export function createSequentialModifier<
T extends Modifier[],
S = T extends WithRequired<Modifier, "revert">[]
? WithRequired<Modifier, "description" | "revert">
: Omit<WithRequired<Modifier, "description">, "revert">
S = T extends WithRequired<Modifier, "invert">[]
? WithRequired<Modifier, "description" | "invert">
: Omit<WithRequired<Modifier, "description">, "invert">
>(modifiersFunc: () => T): S {
return createLazyProxy(() => {
const modifiers = modifiersFunc();
@ -271,12 +281,19 @@ export function createSequentialModifier<
modifiers
.filter(m => unref(m.enabled) !== false)
.reduce((gain, modifier) => modifier.apply(gain), gain),
revert: modifiers.every(m => m.revert != null)
invert: modifiers.every(m => m.invert != null)
? (gain: DecimalSource) =>
modifiers
.filter(m => unref(m.enabled) !== false)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.reduceRight((gain, modifier) => modifier.revert!(gain), gain)
.reduceRight((gain, modifier) => modifier.invert!(gain), gain)
: undefined,
getFormula: modifiers.every(m => m.getFormula != null)
? (gain: FormulaSource) =>
modifiers
.filter(m => unref(m.enabled) !== false)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.reduce((acc, curr) => curr.getFormula!(acc), gain)
: undefined,
enabled: computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0),
description: jsx(() => (