diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx
index e4640b6..f69ac8b 100644
--- a/src/data/projEntry.tsx
+++ b/src/data/projEntry.tsx
@@ -1,3 +1,4 @@
+import Node from "components/Node.vue";
import Spacer from "components/layout/Spacer.vue";
import { jsx } from "features/feature";
import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource";
@@ -48,19 +49,35 @@ export const main = createLayer("main", function (this: BaseLayer) {
links: tree.links,
display: jsx(() => (
<>
- {player.devSpeed === 0 ?
Game Paused
: null}
+ {player.devSpeed === 0 ? (
+
+ Game Paused
+
+
+ ) : null}
{player.devSpeed != null && player.devSpeed !== 0 && player.devSpeed !== 1 ? (
- Dev Speed: {format(player.devSpeed)}x
+
+ Dev Speed: {format(player.devSpeed)}x
+
+
) : null}
{player.offlineTime != null && player.offlineTime !== 0 ? (
- Offline Time: {formatTime(player.offlineTime)}
+
+ Offline Time: {formatTime(player.offlineTime)}
+
+
) : null}
{Decimal.lt(points.value, "1e1000") ? You have : null}
{format(points.value)}
{Decimal.lt(points.value, "1e1e6") ? points : null}
- {Decimal.gt(pointGain.value, 0) ? ({oomps.value})
: null}
+ {Decimal.gt(pointGain.value, 0) ? (
+
+ ({oomps.value})
+
+
+ ) : null}
{render(tree)}
>
diff --git a/src/features/hotkey.tsx b/src/features/hotkey.tsx
index 51fafbb..80eebbd 100644
--- a/src/features/hotkey.tsx
+++ b/src/features/hotkey.tsx
@@ -108,7 +108,7 @@ document.onkeydown = function (e) {
if (e.ctrlKey) {
key = "ctrl+" + key;
}
- const hotkey = hotkeys[key];
+ const hotkey = hotkeys[key] ?? hotkeys[key.toLowerCase()];
if (hotkey && unref(hotkey.enabled)) {
e.preventDefault();
hotkey.onPress();
diff --git a/src/features/trees/tree.ts b/src/features/trees/tree.ts
index 8ec317f..4e43bbf 100644
--- a/src/features/trees/tree.ts
+++ b/src/features/trees/tree.ts
@@ -224,7 +224,7 @@ export interface BaseTree {
id: string;
/** The link objects for each of the branches of the tree. */
links: Ref;
- /** Cause a reset on this node and propagate it through the tree according to {@link resetPropagation}. */
+ /** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */
reset: (node: GenericTreeNode) => void;
/** A flag that is true while the reset is still propagating through the tree. */
isResetting: Ref;
diff --git a/src/game/modifiers.tsx b/src/game/modifiers.tsx
index 1ee3905..55efccb 100644
--- a/src/game/modifiers.tsx
+++ b/src/game/modifiers.tsx
@@ -4,7 +4,7 @@ import { jsx } from "features/feature";
import settings from "game/settings";
import type { DecimalSource } from "util/bignum";
import Decimal, { formatSmall } from "util/bignum";
-import type { WithRequired } from "util/common";
+import type { RequiredKeys, WithRequired } from "util/common";
import type { Computable, ProcessedComputable } from "util/computed";
import { convertComputable } from "util/computed";
import { createLazyProxy } from "util/proxies";
@@ -38,16 +38,11 @@ export interface Modifier {
description?: ProcessedComputable;
}
-/**
- * Utility type used to narrow down a modifier type that will have a description and/or enabled property based on optional parameters, T and S (respectively).
- */
-export type ModifierFromOptionalParams = undefined extends T
- ? undefined extends S
- ? Omit, "description" | "enabled">
- : Omit, "description">
- : undefined extends S
- ? Omit, "enabled">
- : WithRequired;
+/** Utility type that represents the output of all modifiers that represent a single operation. */
+export type OperationModifier = WithRequired<
+ Modifier,
+ "invert" | "getFormula" | Extract, keyof Modifier>
+>;
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
export interface AdditiveModifierOptions {
@@ -65,9 +60,9 @@ export interface AdditiveModifierOptions {
* Create a modifier that adds some value to the input value.
* @param optionsFunc Additive modifier options.
*/
-export function createAdditiveModifier(
+export function createAdditiveModifier>(
optionsFunc: OptionsFunc
-): ModifierFromOptionalParams {
+) {
return createLazyProxy(feature => {
const { addend, description, enabled, smallerIsBetter } = optionsFunc.call(
feature,
@@ -111,7 +106,7 @@ export function createAdditiveModifier(
))
};
- }) as unknown as ModifierFromOptionalParams;
+ }) as S;
}
/** An object that configures an multiplicative modifier via {@link createMultiplicativeModifier}. */
@@ -130,9 +125,10 @@ export interface MultiplicativeModifierOptions {
* Create a modifier that multiplies the input value by some value.
* @param optionsFunc Multiplicative modifier options.
*/
-export function createMultiplicativeModifier(
- optionsFunc: OptionsFunc
-): ModifierFromOptionalParams {
+export function createMultiplicativeModifier<
+ T extends MultiplicativeModifierOptions,
+ S = OperationModifier
+>(optionsFunc: OptionsFunc) {
return createLazyProxy(feature => {
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc.call(
feature,
@@ -175,7 +171,7 @@ export function createMultiplicativeModifier
))
};
- }) as unknown as ModifierFromOptionalParams;
+ }) as S;
}
/** An object that configures an exponential modifier via {@link createExponentialModifier}. */
@@ -196,9 +192,10 @@ export interface ExponentialModifierOptions {
* Create a modifier that raises the input value to the power of some value.
* @param optionsFunc Exponential modifier options.
*/
-export function createExponentialModifier(
- optionsFunc: OptionsFunc
-): ModifierFromOptionalParams {
+export function createExponentialModifier<
+ T extends ExponentialModifierOptions,
+ S = OperationModifier
+>(optionsFunc: OptionsFunc) {
return createLazyProxy(feature => {
const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } =
optionsFunc.call(feature, feature);
@@ -263,7 +260,7 @@ export function createExponentialModifier(
))
};
- }) as unknown as ModifierFromOptionalParams;
+ }) as S;
}
/**
@@ -274,11 +271,9 @@ export function createExponentialModifier(
* @see {@link createModifierSection}.
*/
export function createSequentialModifier<
- T extends Modifier[],
- S = T extends WithRequired[]
- ? WithRequired
- : Omit, "invert">
->(modifiersFunc: () => T): S {
+ T extends Modifier,
+ S = WithRequired, keyof Modifier>>
+>(modifiersFunc: () => T[]) {
return createLazyProxy(() => {
const modifiers = modifiersFunc();
@@ -296,10 +291,14 @@ export function createSequentialModifier<
: undefined,
getFormula: modifiers.every(m => m.getFormula != null)
? (gain: FormulaSource) =>
- modifiers
+ modifiers.reduce((acc, curr) => {
+ if (curr.enabled == null || curr.enabled === true) {
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return curr.getFormula!(acc);
+ }
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- .reduce((acc, curr) => Formula.if(acc, curr.enabled ?? true,
- acc => curr.getFormula!(acc), acc => acc), gain)
+ return Formula.if(acc, curr.enabled, acc => curr.getFormula!(acc));
+ }, gain)
: undefined,
enabled: modifiers.some(m => m.enabled != null)
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
@@ -317,7 +316,7 @@ export function createSequentialModifier<
))
: undefined
};
- }) as unknown as S;
+ }) as S;
}
/** An object that configures a modifier section via {@link createModifierSection}. */
diff --git a/src/util/common.ts b/src/util/common.ts
index dbbe233..00847e6 100644
--- a/src/util/common.ts
+++ b/src/util/common.ts
@@ -1,3 +1,11 @@
+export type RequiredKeys = {
+ [K in keyof T]-?: NonNullable extends Pick ? never : K;
+}[keyof T];
+export type OptionalKeys = {
+ [K in keyof T]-?: NonNullable extends Pick ? K : never;
+}[keyof T];
+
+export type OmitOptional = Pick>;
export type WithRequired = T & { [P in K]-?: T[P] };
export type ArrayElements> = T extends ReadonlyArray
diff --git a/tests/features/tree.test.ts b/tests/features/tree.test.ts
new file mode 100644
index 0000000..206988f
--- /dev/null
+++ b/tests/features/tree.test.ts
@@ -0,0 +1,111 @@
+import { beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
+import { Ref, ref } from "vue";
+import "../utils";
+import {
+ createTree,
+ createTreeNode,
+ defaultResetPropagation,
+ invertedResetPropagation,
+ branchedResetPropagation
+} from "features/trees/tree";
+import { createReset, GenericReset } from "features/reset";
+
+describe("Reset propagation", () => {
+ let shouldReset: Ref, shouldNotReset: Ref;
+ let goodReset: GenericReset, badReset: GenericReset;
+ beforeAll(() => {
+ shouldReset = ref(false);
+ shouldNotReset = ref(false);
+ goodReset = createReset(() => ({
+ thingsToReset: [],
+ onReset() {
+ shouldReset.value = true;
+ }
+ }));
+ badReset = createReset(() => ({
+ thingsToReset: [],
+ onReset() {
+ shouldNotReset.value = true;
+ }
+ }));
+ });
+ beforeEach(() => {
+ shouldReset.value = false;
+ shouldNotReset.value = false;
+ });
+ test("No resets", () => {
+ expect(() => {
+ const a = createTreeNode(() => ({}));
+ const b = createTreeNode(() => ({}));
+ const c = createTreeNode(() => ({}));
+ const tree = createTree(() => ({
+ nodes: [[a], [b], [c]]
+ }));
+ tree.reset(a);
+ }).not.toThrowError();
+ });
+
+ test("Do not propagate resets", () => {
+ const a = createTreeNode(() => ({ reset: badReset }));
+ const b = createTreeNode(() => ({ reset: badReset }));
+ const c = createTreeNode(() => ({ reset: badReset }));
+ const tree = createTree(() => ({
+ nodes: [[a], [b], [c]]
+ }));
+ tree.reset(b);
+ expect(shouldNotReset.value).toBe(false);
+ });
+
+ test("Default propagation", () => {
+ const a = createTreeNode(() => ({ reset: goodReset }));
+ const b = createTreeNode(() => ({}));
+ const c = createTreeNode(() => ({ reset: badReset }));
+ const tree = createTree(() => ({
+ nodes: [[a], [b], [c]],
+ resetPropagation: defaultResetPropagation
+ }));
+ tree.reset(b);
+ expect(shouldReset.value).toBe(true);
+ expect(shouldNotReset.value).toBe(false);
+ });
+
+ test("Inverted propagation", () => {
+ const a = createTreeNode(() => ({ reset: badReset }));
+ const b = createTreeNode(() => ({}));
+ const c = createTreeNode(() => ({ reset: goodReset }));
+ const tree = createTree(() => ({
+ nodes: [[a], [b], [c]],
+ resetPropagation: invertedResetPropagation
+ }));
+ tree.reset(b);
+ expect(shouldReset.value).toBe(true);
+ expect(shouldNotReset.value).toBe(false);
+ });
+
+ test("Branched propagation", () => {
+ const a = createTreeNode(() => ({ reset: badReset }));
+ const b = createTreeNode(() => ({}));
+ const c = createTreeNode(() => ({ reset: goodReset }));
+ const tree = createTree(() => ({
+ nodes: [[a, b, c]],
+ resetPropagation: branchedResetPropagation,
+ branches: [{ startNode: b, endNode: c }]
+ }));
+ tree.reset(b);
+ expect(shouldReset.value).toBe(true);
+ expect(shouldNotReset.value).toBe(false);
+ });
+
+ test("Branched propagation not bi-directional", () => {
+ const a = createTreeNode(() => ({ reset: badReset }));
+ const b = createTreeNode(() => ({}));
+ const c = createTreeNode(() => ({ reset: badReset }));
+ const tree = createTree(() => ({
+ nodes: [[a, b, c]],
+ resetPropagation: branchedResetPropagation,
+ branches: [{ startNode: c, endNode: b }]
+ }));
+ tree.reset(b);
+ expect(shouldNotReset.value).toBe(false);
+ });
+});
diff --git a/tests/game/modifiers.test.ts b/tests/game/modifiers.test.ts
index d5e186d..dd019e1 100644
--- a/tests/game/modifiers.test.ts
+++ b/tests/game/modifiers.test.ts
@@ -133,14 +133,14 @@ describe("Exponential Modifiers", () =>
testModifiers(createExponentialModifier, "exponent", Decimal.pow));
describe("Sequential Modifiers", () => {
- function createModifier(
+ function createModifier>(
value: Computable,
- options: Partial = {}
- ): WithRequired {
+ options?: T
+ ) {
return createSequentialModifier(() => [
- createAdditiveModifier(() => ({ ...options, addend: value })),
- createMultiplicativeModifier(() => ({ ...options, multiplier: value })),
- createExponentialModifier(() => ({ ...options, exponent: value }))
+ createAdditiveModifier(() => ({ ...(options ?? {}), addend: value })),
+ createMultiplicativeModifier(() => ({ ...(options ?? {}), multiplier: value })),
+ createExponentialModifier(() => ({ ...(options ?? {}), exponent: value }))
]);
}
@@ -199,6 +199,17 @@ describe("Sequential Modifiers", () => {
// So long as one is true or undefined, enable should be true
expect(unref(modifier.enabled)).toBe(true);
});
+ test("respects enabled", () => {
+ const value = ref(10);
+ const enabled = ref(false);
+ const modifier = createSequentialModifier(() => [
+ createMultiplicativeModifier(() => ({ multiplier: 5, enabled }))
+ ]);
+ const formula = modifier.getFormula(Formula.variable(value));
+ expect(formula.evaluate()).compare_tolerance(value.value);
+ enabled.value = true;
+ expect(formula.evaluate()).not.compare_tolerance(value.value);
+ });
});
describe("applies smallerIsBetter correctly", () => {