Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Seth Posner 2023-04-19 16:56:44 -07:00
commit f79359b18a
27 changed files with 132 additions and 71 deletions

View file

@ -33,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Requires referencing persistent refs either through a proxy or by wrapping in `noPersist()` - Requires referencing persistent refs either through a proxy or by wrapping in `noPersist()`
- **BREAKING** Visibility properties can now take booleans - **BREAKING** Visibility properties can now take booleans
- Removed showIf util - Removed showIf util
- **BREAKING** Lazy proxies and options functions now pass the base object in as `this` as well as the first parameter.
- Tweaked settings display - Tweaked settings display
- setupPassiveGeneration will no longer lower the resource - setupPassiveGeneration will no longer lower the resource
- displayResource now floors resource amounts - displayResource now floors resource amounts

View file

@ -5,11 +5,10 @@ import { createClickable } from "features/clickables/clickable";
import type { GenericConversion } from "features/conversion"; import type { GenericConversion } from "features/conversion";
import type { CoercableComponent, JSXFunction, OptionsFunc, Replace } from "features/feature"; import type { CoercableComponent, JSXFunction, OptionsFunc, Replace } from "features/feature";
import { jsx, setDefault } from "features/feature"; import { jsx, setDefault } from "features/feature";
import { displayResource, Resource } from "features/resources/resource"; import { Resource, displayResource } from "features/resources/resource";
import type { GenericTree, GenericTreeNode, TreeNode, TreeNodeOptions } from "features/trees/tree"; import type { GenericTree, GenericTreeNode, TreeNode, TreeNodeOptions } from "features/trees/tree";
import { createTreeNode } from "features/trees/tree"; import { createTreeNode } from "features/trees/tree";
import Formula from "game/formulas/formulas"; import type { GenericFormula } from "game/formulas/types";
import type { FormulaSource, GenericFormula } from "game/formulas/types";
import type { Modifier } from "game/modifiers"; import type { Modifier } from "game/modifiers";
import type { Persistent } from "game/persistence"; import type { Persistent } from "game/persistence";
import { DefaultValue, persistent } from "game/persistence"; import { DefaultValue, persistent } from "game/persistence";
@ -99,8 +98,8 @@ export type GenericResetButton = Replace<
export function createResetButton<T extends ClickableOptions & ResetButtonOptions>( export function createResetButton<T extends ClickableOptions & ResetButtonOptions>(
optionsFunc: OptionsFunc<T> optionsFunc: OptionsFunc<T>
): ResetButton<T> { ): ResetButton<T> {
return createClickable(() => { return createClickable(feature => {
const resetButton = optionsFunc(); const resetButton = optionsFunc.call(feature, feature);
processComputable(resetButton as T, "showNextAt"); processComputable(resetButton as T, "showNextAt");
setDefault(resetButton, "showNextAt", true); setDefault(resetButton, "showNextAt", true);
@ -213,8 +212,8 @@ export type GenericLayerTreeNode = Replace<
export function createLayerTreeNode<T extends LayerTreeNodeOptions>( export function createLayerTreeNode<T extends LayerTreeNodeOptions>(
optionsFunc: OptionsFunc<T> optionsFunc: OptionsFunc<T>
): LayerTreeNode<T> { ): LayerTreeNode<T> {
return createTreeNode(() => { return createTreeNode(feature => {
const options = optionsFunc(); const options = optionsFunc.call(feature, feature);
processComputable(options as T, "display"); processComputable(options as T, "display");
setDefault(options, "display", options.layerID); setDefault(options, "display", options.layerID);
processComputable(options as T, "append"); processComputable(options as T, "append");

View file

@ -143,8 +143,10 @@ export function createAchievement<T extends AchievementOptions>(
): Achievement<T> { ): Achievement<T> {
const earned = persistent<boolean>(false, false); const earned = persistent<boolean>(false, false);
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const achievement = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>); const achievement =
optionsFunc?.call(feature, feature) ??
({} as ReturnType<NonNullable<typeof optionsFunc>>);
achievement.id = getUniqueID("achievement-"); achievement.id = getUniqueID("achievement-");
achievement.type = AchievementType; achievement.type = AchievementType;
achievement[Component] = AchievementComponent as GenericComponent; achievement[Component] = AchievementComponent as GenericComponent;

View file

@ -108,8 +108,10 @@ export function createAction<T extends ActionOptions>(
): Action<T> { ): Action<T> {
const progress = persistent<DecimalSource>(0); const progress = persistent<DecimalSource>(0);
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const action = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>); const action =
optionsFunc?.call(feature, feature) ??
({} as ReturnType<NonNullable<typeof optionsFunc>>);
action.id = getUniqueID("action-"); action.id = getUniqueID("action-");
action.type = ActionType; action.type = ActionType;
action[Component] = ClickableComponent as GenericComponent; action[Component] = ClickableComponent as GenericComponent;

View file

@ -106,8 +106,8 @@ export function createBar<T extends BarOptions>(
...decorators: Decorator<T, BaseBar, GenericBar>[] ...decorators: Decorator<T, BaseBar, GenericBar>[]
): Bar<T> { ): Bar<T> {
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const bar = optionsFunc(); const bar = optionsFunc.call(feature, feature);
bar.id = getUniqueID("bar-"); bar.id = getUniqueID("bar-");
bar.type = BarType; bar.type = BarType;
bar[Component] = BarComponent as GenericComponent; bar[Component] = BarComponent as GenericComponent;

View file

@ -297,8 +297,8 @@ export function createBoard<T extends BoardOptions>(
false false
); );
return createLazyProxy(() => { return createLazyProxy(feature => {
const board = optionsFunc(); const board = optionsFunc.call(feature, feature);
board.id = getUniqueID("board-"); board.id = getUniqueID("board-");
board.type = BoardType; board.type = BoardType;
board[Component] = BoardComponent as GenericComponent; board[Component] = BoardComponent as GenericComponent;

View file

@ -155,8 +155,8 @@ export function createChallenge<T extends ChallengeOptions>(
const completions = persistent(0); const completions = persistent(0);
const active = persistent(false, false); const active = persistent(false, false);
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const challenge = optionsFunc(); const challenge = optionsFunc.call(feature, feature);
challenge.id = getUniqueID("challenge-"); challenge.id = getUniqueID("challenge-");
challenge.type = ChallengeType; challenge.type = ChallengeType;

View file

@ -100,8 +100,10 @@ export function createClickable<T extends ClickableOptions>(
...decorators: Decorator<T, BaseClickable, GenericClickable>[] ...decorators: Decorator<T, BaseClickable, GenericClickable>[]
): Clickable<T> { ): Clickable<T> {
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const clickable = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>); const clickable =
optionsFunc?.call(feature, feature) ??
({} as ReturnType<NonNullable<typeof optionsFunc>>);
clickable.id = getUniqueID("clickable-"); clickable.id = getUniqueID("clickable-");
clickable.type = ClickableType; clickable.type = ClickableType;
clickable[Component] = ClickableComponent as GenericComponent; clickable[Component] = ClickableComponent as GenericComponent;

View file

@ -127,8 +127,8 @@ export function createConversion<T extends ConversionOptions>(
optionsFunc: OptionsFunc<T, BaseConversion, GenericConversion>, optionsFunc: OptionsFunc<T, BaseConversion, GenericConversion>,
...decorators: Decorator<T, BaseConversion, GenericConversion>[] ...decorators: Decorator<T, BaseConversion, GenericConversion>[]
): Conversion<T> { ): Conversion<T> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const conversion = optionsFunc(); const conversion = optionsFunc.call(feature, feature);
for (const decorator of decorators) { for (const decorator of decorators) {
decorator.preConstruct?.(conversion); decorator.preConstruct?.(conversion);
@ -221,8 +221,8 @@ export function createCumulativeConversion<S extends ConversionOptions>(
export function createIndependentConversion<S extends ConversionOptions>( export function createIndependentConversion<S extends ConversionOptions>(
optionsFunc: OptionsFunc<S, BaseConversion, GenericConversion> optionsFunc: OptionsFunc<S, BaseConversion, GenericConversion>
): Conversion<S> { ): Conversion<S> {
return createConversion(() => { return createConversion(feature => {
const conversion: S = optionsFunc(); const conversion: S = optionsFunc.call(feature, feature);
setDefault(conversion, "buyMax", false); setDefault(conversion, "buyMax", false);

View file

@ -42,9 +42,9 @@ export type Replace<T, S> = S & Omit<T, keyof S>;
* with "this" bound to what the type will eventually be processed into. * with "this" bound to what the type will eventually be processed into.
* Intended for making lazily evaluated objects. * Intended for making lazily evaluated objects.
*/ */
export type OptionsFunc<T, R = Record<string, unknown>, S = R> = () => OptionsObject<T,R,S>; export type OptionsFunc<T, R = unknown, S = R> = (obj: R) => OptionsObject<T,R,S>;
export type OptionsObject<T, R = Record<string, unknown>, S = R> = T & Partial<R> & ThisType<T & S>; export type OptionsObject<T, R = unknown, S = R> = T & Partial<R> & ThisType<T & S>;
let id = 0; let id = 0;
/** /**

View file

@ -307,8 +307,8 @@ export function createGrid<T extends GridOptions>(
optionsFunc: OptionsFunc<T, BaseGrid, GenericGrid> optionsFunc: OptionsFunc<T, BaseGrid, GenericGrid>
): Grid<T> { ): Grid<T> {
const cellState = persistent<Record<string | number, State>>({}, false); const cellState = persistent<Record<string | number, State>>({}, false);
return createLazyProxy(() => { return createLazyProxy(feature => {
const grid = optionsFunc(); const grid = optionsFunc.call(feature, feature);
grid.id = getUniqueID("grid-"); grid.id = getUniqueID("grid-");
grid[Component] = GridComponent as GenericComponent; grid[Component] = GridComponent as GenericComponent;

View file

@ -68,8 +68,8 @@ const uppercaseNumbers = [")", "!", "@", "#", "$", "%", "^", "&", "*", "("];
export function createHotkey<T extends HotkeyOptions>( export function createHotkey<T extends HotkeyOptions>(
optionsFunc: OptionsFunc<T, BaseHotkey, GenericHotkey> optionsFunc: OptionsFunc<T, BaseHotkey, GenericHotkey>
): Hotkey<T> { ): Hotkey<T> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const hotkey = optionsFunc(); const hotkey = optionsFunc.call(feature, feature);
hotkey.type = HotkeyType; hotkey.type = HotkeyType;
processComputable(hotkey as T, "enabled"); processComputable(hotkey as T, "enabled");

View file

@ -91,8 +91,8 @@ export function createInfobox<T extends InfoboxOptions>(
optionsFunc: OptionsFunc<T, BaseInfobox, GenericInfobox> optionsFunc: OptionsFunc<T, BaseInfobox, GenericInfobox>
): Infobox<T> { ): Infobox<T> {
const collapsed = persistent<boolean>(false, false); const collapsed = persistent<boolean>(false, false);
return createLazyProxy(() => { return createLazyProxy(feature => {
const infobox = optionsFunc(); const infobox = optionsFunc.call(feature, feature);
infobox.id = getUniqueID("infobox-"); infobox.id = getUniqueID("infobox-");
infobox.type = InfoboxType; infobox.type = InfoboxType;
infobox[Component] = InfoboxComponent as GenericComponent; infobox[Component] = InfoboxComponent as GenericComponent;

View file

@ -59,8 +59,8 @@ export type GenericLinks = Replace<
export function createLinks<T extends LinksOptions>( export function createLinks<T extends LinksOptions>(
optionsFunc: OptionsFunc<T, BaseLinks, GenericLinks> optionsFunc: OptionsFunc<T, BaseLinks, GenericLinks>
): Links<T> { ): Links<T> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const links = optionsFunc(); const links = optionsFunc.call(feature, feature);
links.type = LinksType; links.type = LinksType;
links[Component] = LinksComponent as GenericComponent; links[Component] = LinksComponent as GenericComponent;

View file

@ -69,8 +69,10 @@ export type GenericParticles = Particles<ParticlesOptions>;
export function createParticles<T extends ParticlesOptions>( export function createParticles<T extends ParticlesOptions>(
optionsFunc?: OptionsFunc<T, BaseParticles, GenericParticles> optionsFunc?: OptionsFunc<T, BaseParticles, GenericParticles>
): Particles<T> { ): Particles<T> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const particles = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>); const particles =
optionsFunc?.call(feature, feature) ??
({} as ReturnType<NonNullable<typeof optionsFunc>>);
particles.id = getUniqueID("particles-"); particles.id = getUniqueID("particles-");
particles.type = ParticlesType; particles.type = ParticlesType;
particles[Component] = ParticlesComponent as GenericComponent; particles[Component] = ParticlesComponent as GenericComponent;

View file

@ -135,8 +135,8 @@ export function createRepeatable<T extends RepeatableOptions>(
): Repeatable<T> { ): Repeatable<T> {
const amount = persistent<DecimalSource>(0); const amount = persistent<DecimalSource>(0);
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const repeatable = optionsFunc(); const repeatable = optionsFunc.call(feature, feature);
repeatable.id = getUniqueID("repeatable-"); repeatable.id = getUniqueID("repeatable-");
repeatable.type = RepeatableType; repeatable.type = RepeatableType;

View file

@ -54,8 +54,8 @@ export type GenericReset = Reset<ResetOptions>;
export function createReset<T extends ResetOptions>( export function createReset<T extends ResetOptions>(
optionsFunc: OptionsFunc<T, BaseReset, GenericReset> optionsFunc: OptionsFunc<T, BaseReset, GenericReset>
): Reset<T> { ): Reset<T> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const reset = optionsFunc(); const reset = optionsFunc.call(feature, feature);
reset.id = getUniqueID("reset-"); reset.id = getUniqueID("reset-");
reset.type = ResetType; reset.type = ResetType;

View file

@ -62,8 +62,8 @@ export type GenericTab = Tab<TabOptions>;
export function createTab<T extends TabOptions>( export function createTab<T extends TabOptions>(
optionsFunc: OptionsFunc<T, BaseTab, GenericTab> optionsFunc: OptionsFunc<T, BaseTab, GenericTab>
): Tab<T> { ): Tab<T> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const tab = optionsFunc(); const tab = optionsFunc.call(feature, feature);
tab.id = getUniqueID("tab-"); tab.id = getUniqueID("tab-");
tab.type = TabType; tab.type = TabType;
tab[Component] = TabComponent as GenericComponent; tab[Component] = TabComponent as GenericComponent;

View file

@ -156,8 +156,10 @@ export function createTabFamily<T extends TabFamilyOptions>(
} }
const selected = persistent(Object.keys(tabs)[0], false); const selected = persistent(Object.keys(tabs)[0], false);
return createLazyProxy(() => { return createLazyProxy(feature => {
const tabFamily = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>); const tabFamily =
optionsFunc?.call(feature, feature) ??
({} as ReturnType<NonNullable<typeof optionsFunc>>);
tabFamily.id = getUniqueID("tabFamily-"); tabFamily.id = getUniqueID("tabFamily-");
tabFamily.type = TabFamilyType; tabFamily.type = TabFamilyType;

View file

@ -106,8 +106,10 @@ export function createTreeNode<T extends TreeNodeOptions>(
...decorators: Decorator<T, BaseTreeNode, GenericTreeNode>[] ...decorators: Decorator<T, BaseTreeNode, GenericTreeNode>[]
): TreeNode<T> { ): TreeNode<T> {
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const treeNode = optionsFunc?.() ?? ({} as ReturnType<NonNullable<typeof optionsFunc>>); const treeNode =
optionsFunc?.call(feature, feature) ??
({} as ReturnType<NonNullable<typeof optionsFunc>>);
treeNode.id = getUniqueID("treeNode-"); treeNode.id = getUniqueID("treeNode-");
treeNode.type = TreeNodeType; treeNode.type = TreeNodeType;
treeNode[Component] = TreeNodeComponent as GenericComponent; treeNode[Component] = TreeNodeComponent as GenericComponent;
@ -257,8 +259,8 @@ export type GenericTree = Replace<
export function createTree<T extends TreeOptions>( export function createTree<T extends TreeOptions>(
optionsFunc: OptionsFunc<T, BaseTree, GenericTree> optionsFunc: OptionsFunc<T, BaseTree, GenericTree>
): Tree<T> { ): Tree<T> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const tree = optionsFunc(); const tree = optionsFunc.call(feature, feature);
tree.id = getUniqueID("tree-"); tree.id = getUniqueID("tree-");
tree.type = TreeType; tree.type = TreeType;
tree[Component] = TreeComponent as GenericComponent; tree[Component] = TreeComponent as GenericComponent;

View file

@ -123,8 +123,8 @@ export function createUpgrade<T extends UpgradeOptions>(
): Upgrade<T> { ): Upgrade<T> {
const bought = persistent<boolean>(false, false); const bought = persistent<boolean>(false, false);
const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {}); const decoratedData = decorators.reduce((current, next) => Object.assign(current, next.getPersistentData?.()), {});
return createLazyProxy(() => { return createLazyProxy(feature => {
const upgrade = optionsFunc(); const upgrade = optionsFunc.call(feature, feature);
upgrade.id = getUniqueID("upgrade-"); upgrade.id = getUniqueID("upgrade-");
upgrade.type = UpgradeType; upgrade.type = UpgradeType;
upgrade[Component] = UpgradeComponent as GenericComponent; upgrade[Component] = UpgradeComponent as GenericComponent;

View file

@ -1,7 +1,8 @@
import { Resource } from "features/resources/resource"; import { Resource } from "features/resources/resource";
import { NonPersistent } from "game/persistence";
import Decimal, { DecimalSource, format } from "util/bignum"; import Decimal, { DecimalSource, format } from "util/bignum";
import { Computable, convertComputable, ProcessedComputable } from "util/computed"; import { Computable, ProcessedComputable, convertComputable } from "util/computed";
import { computed, ComputedRef, ref, unref } from "vue"; import { ComputedRef, Ref, computed, ref, unref } from "vue";
import * as ops from "./operations"; import * as ops from "./operations";
import type { import type {
EvaluateFunction, EvaluateFunction,
@ -58,7 +59,15 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
constructor(options: FormulaOptions<T>) { constructor(options: FormulaOptions<T>) {
let readonlyProperties; let readonlyProperties;
if ("inputs" in options) {
options.inputs = options.inputs.map(input =>
typeof input === "object" && NonPersistent in input ? input[NonPersistent] : input
) as T | [FormulaSource];
}
if ("variable" in options) { if ("variable" in options) {
if (typeof options.variable === "object" && NonPersistent in options.variable) {
options.variable = options.variable[NonPersistent] as Ref<DecimalSource>;
}
readonlyProperties = this.setupVariable(options); readonlyProperties = this.setupVariable(options);
} else if (!("evaluate" in options)) { } else if (!("evaluate" in options)) {
readonlyProperties = this.setupConstant(options); readonlyProperties = this.setupConstant(options);
@ -364,21 +373,30 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
* @param value The incoming formula value * @param value The incoming formula value
* @param condition Whether or not to apply the modifier * @param condition Whether or not to apply the modifier
* @param formulaModifier The modifier to apply to the incoming formula if the condition is true * @param formulaModifier The modifier to apply to the incoming formula if the condition is true
* @param elseFormulaModifier An optional modifier to apply to the incoming formula if the condition is false
*/ */
public static if( public static if(
value: FormulaSource, value: FormulaSource,
condition: Computable<boolean>, condition: Computable<boolean>,
formulaModifier: ( formulaModifier: (
value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
) => GenericFormula,
elseFormulaModifier?: (
value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
) => GenericFormula ) => GenericFormula
): GenericFormula { ): GenericFormula {
const lhsRef = ref<DecimalSource>(0); const lhsRef = ref<DecimalSource>(0);
const formula = formulaModifier(Formula.variable(lhsRef)); const variable = Formula.variable(lhsRef);
const formula = formulaModifier(variable);
const elseFormula = elseFormulaModifier?.(variable);
const processedCondition = convertComputable(condition); const processedCondition = convertComputable(condition);
function evalStep(lhs: DecimalSource) { function evalStep(lhs: DecimalSource) {
if (unref(processedCondition)) { if (unref(processedCondition)) {
lhsRef.value = lhs; lhsRef.value = lhs;
return formula.evaluate(); return formula.evaluate();
} else if (elseFormula) {
lhsRef.value = lhs;
return elseFormula.evaluate();
} else { } else {
return lhs; return lhs;
} }
@ -389,6 +407,8 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
} }
if (unref(processedCondition)) { if (unref(processedCondition)) {
return lhs.invert(formula.invert(value)); return lhs.invert(formula.invert(value));
} else if (elseFormula) {
return lhs.invert(elseFormula.invert(value));
} else { } else {
return lhs.invert(value); return lhs.invert(value);
} }
@ -399,15 +419,17 @@ export default class Formula<T extends [FormulaSource] | FormulaSource[]> {
invert: formula.isInvertible() && formula.hasVariable() ? invertStep : undefined invert: formula.isInvertible() && formula.hasVariable() ? invertStep : undefined
}); });
} }
/** @see {@link if} */
public static conditional( public static conditional(
value: FormulaSource, value: FormulaSource,
condition: Computable<boolean>, condition: Computable<boolean>,
formulaModifier: ( formulaModifier: (
value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
) => GenericFormula,
elseFormulaModifier?: (
value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
) => GenericFormula ) => GenericFormula
) { ) {
return Formula.if(value, condition, formulaModifier); return Formula.if(value, condition, formulaModifier, elseFormulaModifier);
} }
public static abs(value: FormulaSource): GenericFormula { public static abs(value: FormulaSource): GenericFormula {

View file

@ -220,7 +220,7 @@ export function createLayer<T extends LayerOptions>(
addingLayers.push(id); addingLayers.push(id);
persistentRefs[id] = new Set(); persistentRefs[id] = new Set();
layer.minimized = persistent(false, false); layer.minimized = persistent(false, false);
Object.assign(layer, optionsFunc.call(layer as BaseLayer)); Object.assign(layer, optionsFunc.call(layer, layer as BaseLayer));
if ( if (
addingLayers[addingLayers.length - 1] == null || addingLayers[addingLayers.length - 1] == null ||
addingLayers[addingLayers.length - 1] !== id addingLayers[addingLayers.length - 1] !== id

View file

@ -1,5 +1,5 @@
import "components/common/modifiers.css"; import "components/common/modifiers.css";
import type { CoercableComponent } from "features/feature"; import type { CoercableComponent, OptionsFunc } from "features/feature";
import { jsx } from "features/feature"; import { jsx } from "features/feature";
import settings from "game/settings"; import settings from "game/settings";
import type { DecimalSource } from "util/bignum"; import type { DecimalSource } from "util/bignum";
@ -66,10 +66,13 @@ export interface AdditiveModifierOptions {
* @param optionsFunc Additive modifier options. * @param optionsFunc Additive modifier options.
*/ */
export function createAdditiveModifier<T extends AdditiveModifierOptions>( export function createAdditiveModifier<T extends AdditiveModifierOptions>(
optionsFunc: () => T optionsFunc: OptionsFunc<T>
): ModifierFromOptionalParams<T["description"], T["enabled"]> { ): ModifierFromOptionalParams<T["description"], T["enabled"]> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const { addend, description, enabled, smallerIsBetter } = optionsFunc(); const { addend, description, enabled, smallerIsBetter } = optionsFunc.call(
feature,
feature
);
const processedAddend = convertComputable(addend); const processedAddend = convertComputable(addend);
const processedDescription = convertComputable(description); const processedDescription = convertComputable(description);
@ -128,10 +131,13 @@ export interface MultiplicativeModifierOptions {
* @param optionsFunc Multiplicative modifier options. * @param optionsFunc Multiplicative modifier options.
*/ */
export function createMultiplicativeModifier<T extends MultiplicativeModifierOptions>( export function createMultiplicativeModifier<T extends MultiplicativeModifierOptions>(
optionsFunc: () => T optionsFunc: OptionsFunc<T>
): ModifierFromOptionalParams<T["description"], T["enabled"]> { ): ModifierFromOptionalParams<T["description"], T["enabled"]> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc(); const { multiplier, description, enabled, smallerIsBetter } = optionsFunc.call(
feature,
feature
);
const processedMultiplier = convertComputable(multiplier); const processedMultiplier = convertComputable(multiplier);
const processedDescription = convertComputable(description); const processedDescription = convertComputable(description);
@ -191,11 +197,11 @@ export interface ExponentialModifierOptions {
* @param optionsFunc Exponential modifier options. * @param optionsFunc Exponential modifier options.
*/ */
export function createExponentialModifier<T extends ExponentialModifierOptions>( export function createExponentialModifier<T extends ExponentialModifierOptions>(
optionsFunc: () => T optionsFunc: OptionsFunc<T>
): ModifierFromOptionalParams<T["description"], T["enabled"]> { ): ModifierFromOptionalParams<T["description"], T["enabled"]> {
return createLazyProxy(() => { return createLazyProxy(feature => {
const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } = const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } =
optionsFunc(); optionsFunc.call(feature, feature);
const processedExponent = convertComputable(exponent); const processedExponent = convertComputable(exponent);
const processedDescription = convertComputable(description); const processedDescription = convertComputable(description);

View file

@ -3,6 +3,7 @@ import {
CoercableComponent, CoercableComponent,
isVisible, isVisible,
jsx, jsx,
OptionsFunc,
Replace, Replace,
setDefault, setDefault,
Visibility Visibility
@ -108,10 +109,10 @@ export type CostRequirement = Replace<
* @param optionsFunc Cost requirement options. * @param optionsFunc Cost requirement options.
*/ */
export function createCostRequirement<T extends CostRequirementOptions>( export function createCostRequirement<T extends CostRequirementOptions>(
optionsFunc: () => T optionsFunc: OptionsFunc<T>
): CostRequirement { ): CostRequirement {
return createLazyProxy(() => { return createLazyProxy(feature => {
const req = optionsFunc() as T & Partial<Requirement>; const req = optionsFunc.call(feature, feature) as T & Partial<Requirement>;
req.partialDisplay = amount => ( req.partialDisplay = amount => (
<span <span

View file

@ -31,14 +31,14 @@ export type Proxied<T> = NonNullable<T> extends Record<PropertyKey, unknown>
// Takes a function that returns an object and pretends to be that object // Takes a function that returns an object and pretends to be that object
// Note that the object is lazily calculated // Note that the object is lazily calculated
export function createLazyProxy<T extends object, S extends T>( export function createLazyProxy<T extends object, S extends T>(
objectFunc: (baseObject: S) => T & S, objectFunc: (this: S, baseObject: S) => T & S,
baseObject: S = {} as S baseObject: S = {} as S
): T { ): T {
const obj: S & Partial<T> = baseObject; const obj: S & Partial<T> = baseObject;
let calculated = false; let calculated = false;
function calculateObj(): T { function calculateObj(): T {
if (!calculated) { if (!calculated) {
Object.assign(obj, objectFunc(obj)); Object.assign(obj, objectFunc.call(obj, obj));
calculated = true; calculated = true;
} }
return obj as S & T; return obj as S & T;
@ -73,7 +73,7 @@ export function createLazyProxy<T extends object, S extends T>(
}, },
getOwnPropertyDescriptor(target, key) { getOwnPropertyDescriptor(target, key) {
if (!calculated) { if (!calculated) {
Object.assign(obj, objectFunc(obj)); Object.assign(obj, objectFunc.call(obj, obj));
calculated = true; calculated = true;
} }
return Object.getOwnPropertyDescriptor(target, key); return Object.getOwnPropertyDescriptor(target, key);

View file

@ -868,6 +868,26 @@ describe("Conditionals", () => {
Formula.if(variable, false, value => Formula.sqrt(value)).invert(10) Formula.if(variable, false, value => Formula.sqrt(value)).invert(10)
).compare_tolerance(10)); ).compare_tolerance(10));
}); });
describe("Evaluates correctly with condition false and else statement", () => {
test("Evaluates correctly", () =>
expect(
Formula.if(
constant,
false,
value => Formula.sqrt(value),
value => value.times(2)
).evaluate()
).compare_tolerance(20));
test("Inverts correctly with variable in input", () =>
expect(
Formula.if(
variable,
false,
value => Formula.sqrt(value),
value => value.times(2)
).invert(20)
).compare_tolerance(10));
});
describe("Evaluates correctly with condition true", () => { describe("Evaluates correctly with condition true", () => {
test("Evaluates correctly", () => test("Evaluates correctly", () =>