forked from profectus/Profectus
Compare commits
1 commit
main
...
ducdat0507
Author | SHA1 | Date | |
---|---|---|---|
|
46306ee0e7 |
27 changed files with 122 additions and 398 deletions
|
@ -1,31 +0,0 @@
|
||||||
name: Build and Deploy
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- 'main'
|
|
||||||
workflow_dispatch:
|
|
||||||
jobs:
|
|
||||||
build-and-deploy:
|
|
||||||
if: github.repository != 'profectus-engine/Profectus' # Don't build placeholder mod on main repo
|
|
||||||
runs-on: docker
|
|
||||||
steps:
|
|
||||||
- name: Setup RSync
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y rsync
|
|
||||||
|
|
||||||
- name: Checkout 🛎️
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
|
|
||||||
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
|
|
||||||
run: |
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
- name: Deploy 🚀
|
|
||||||
uses: https://github.com/JamesIves/github-pages-deploy-action@v4.2.5
|
|
||||||
with:
|
|
||||||
branch: pages # The branch the action should deploy to.
|
|
||||||
folder: dist # The folder the action should deploy.
|
|
|
@ -1,21 +0,0 @@
|
||||||
name: Run Tests
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [ main ]
|
|
||||||
pull_request:
|
|
||||||
branches: [ main ]
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: docker
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- name: Use Node.js 16.x
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: 16.x
|
|
||||||
- run: npm ci
|
|
||||||
- run: npm run build --if-present
|
|
||||||
- run: npm test
|
|
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
|
@ -3,7 +3,6 @@ on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- 'main'
|
- 'main'
|
||||||
workflow_dispatch:
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
if: github.repository != 'profectus-engine/Profectus' # Don't build placeholder mod on main repo
|
if: github.repository != 'profectus-engine/Profectus' # Don't build placeholder mod on main repo
|
||||||
|
|
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -1,11 +1,11 @@
|
||||||
name: Run Tests
|
name: Build and Deploy
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
|
|
@ -38,7 +38,7 @@
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<span class="material-icons info-modal-discord">discord</span>
|
<span class="material-icons info-modal-discord">discord</span>
|
||||||
Profectus & Friends
|
The Paper Pilot Community
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
class="nan-modal-discord-link"
|
class="nan-modal-discord-link"
|
||||||
>
|
>
|
||||||
<span class="material-icons nan-modal-discord">discord</span>
|
<span class="material-icons nan-modal-discord">discord</span>
|
||||||
{{ discordName || "Profectus & Friends" }}
|
{{ discordName || "The Paper Pilot Community" }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
<a :href="discordLink" target="_blank">{{ discordName }}</a>
|
<a :href="discordLink" target="_blank">{{ discordName }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://discord.gg/yJ4fjnjU54" target="_blank">Profectus & Friends</a>
|
<a href="https://discord.gg/yJ4fjnjU54" target="_blank"
|
||||||
|
>The Paper Pilot Community</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://discord.gg/F3xveHV" target="_blank">The Modding Tree</a>
|
<a href="https://discord.gg/F3xveHV" target="_blank">The Modding Tree</a>
|
||||||
|
@ -80,7 +82,9 @@
|
||||||
<a :href="discordLink" target="_blank">{{ discordName }}</a>
|
<a :href="discordLink" target="_blank">{{ discordName }}</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://discord.gg/yJ4fjnjU54" target="_blank">Profectus & Friends</a>
|
<a href="https://discord.gg/yJ4fjnjU54" target="_blank"
|
||||||
|
>The Paper Pilot Community</a
|
||||||
|
>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="https://discord.gg/F3xveHV" target="_blank">The Modding Tree</a>
|
<a href="https://discord.gg/F3xveHV" target="_blank">The Modding Tree</a>
|
||||||
|
|
|
@ -9,7 +9,6 @@ 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 type { GenericFormula } from "game/formulas/types";
|
import type { GenericFormula } from "game/formulas/types";
|
||||||
import { BaseLayer } from "game/layers";
|
|
||||||
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";
|
||||||
|
@ -134,8 +133,8 @@ export function createResetButton<T extends ClickableOptions & ResetButtonOption
|
||||||
{unref(resetButton.conversion.buyMax) ? "Next:" : "Req:"}{" "}
|
{unref(resetButton.conversion.buyMax) ? "Next:" : "Req:"}{" "}
|
||||||
{displayResource(
|
{displayResource(
|
||||||
resetButton.conversion.baseResource,
|
resetButton.conversion.baseResource,
|
||||||
!unref(resetButton.conversion.buyMax) &&
|
!unref(resetButton.conversion.buyMax) ||
|
||||||
Decimal.gte(unref(resetButton.conversion.actualGain), 1)
|
Decimal.lt(unref(resetButton.conversion.actualGain), 1)
|
||||||
? unref(resetButton.conversion.currentAt)
|
? unref(resetButton.conversion.currentAt)
|
||||||
: unref(resetButton.conversion.nextAt)
|
: unref(resetButton.conversion.nextAt)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
|
@ -486,22 +485,3 @@ export function createFormulaPreview(
|
||||||
return <>{formatSmall(formula.evaluate())}</>;
|
return <>{formatSmall(formula.evaluate())}</>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function for getting a computed boolean for whether or not a given feature is currently rendered in the DOM.
|
|
||||||
* Note it will have a true value even if the feature is off screen.
|
|
||||||
* @param layer The layer the feature appears within
|
|
||||||
* @param id The ID of the feature
|
|
||||||
*/
|
|
||||||
export function isRendered(layer: BaseLayer, id: string): ComputedRef<boolean>;
|
|
||||||
/**
|
|
||||||
* Utility function for getting a computed boolean for whether or not a given feature is currently rendered in the DOM.
|
|
||||||
* Note it will have a true value even if the feature is off screen.
|
|
||||||
* @param layer The layer the feature appears within
|
|
||||||
* @param feature The feature that may be rendered
|
|
||||||
*/
|
|
||||||
export function isRendered(layer: BaseLayer, feature: { id: string }): ComputedRef<boolean>;
|
|
||||||
export function isRendered(layer: BaseLayer, idOrFeature: string | { id: string }) {
|
|
||||||
const id = typeof idOrFeature === "string" ? idOrFeature : idOrFeature.id;
|
|
||||||
return computed(() => id in layer.nodes.value);
|
|
||||||
}
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
|
||||||
color,
|
color,
|
||||||
reset
|
reset
|
||||||
}));
|
}));
|
||||||
const tooltip = addTooltip(treeNode, {
|
addTooltip(treeNode, {
|
||||||
display: createResourceTooltip(points),
|
display: createResourceTooltip(points),
|
||||||
pinnable: true
|
pinnable: true
|
||||||
});
|
});
|
||||||
|
@ -58,7 +58,6 @@ const layer = createLayer(id, function (this: BaseLayer) {
|
||||||
name,
|
name,
|
||||||
color,
|
color,
|
||||||
points,
|
points,
|
||||||
tooltip,
|
|
||||||
display: jsx(() => (
|
display: jsx(() => (
|
||||||
<>
|
<>
|
||||||
<MainDisplay resource={points} color={color} />
|
<MainDisplay resource={points} color={color} />
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import Node from "components/Node.vue";
|
|
||||||
import Spacer from "components/layout/Spacer.vue";
|
import Spacer from "components/layout/Spacer.vue";
|
||||||
import { jsx } from "features/feature";
|
import { jsx } from "features/feature";
|
||||||
import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource";
|
import { createResource, trackBest, trackOOMPS, trackTotal } from "features/resources/resource";
|
||||||
|
@ -49,35 +48,19 @@ export const main = createLayer("main", function (this: BaseLayer) {
|
||||||
links: tree.links,
|
links: tree.links,
|
||||||
display: jsx(() => (
|
display: jsx(() => (
|
||||||
<>
|
<>
|
||||||
{player.devSpeed === 0 ? (
|
{player.devSpeed === 0 ? <div>Game Paused</div> : null}
|
||||||
<div>
|
|
||||||
Game Paused
|
|
||||||
<Node id="paused" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{player.devSpeed != null && player.devSpeed !== 0 && player.devSpeed !== 1 ? (
|
{player.devSpeed != null && player.devSpeed !== 0 && player.devSpeed !== 1 ? (
|
||||||
<div>
|
<div>Dev Speed: {format(player.devSpeed)}x</div>
|
||||||
Dev Speed: {format(player.devSpeed)}x
|
|
||||||
<Node id="devspeed" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
{player.offlineTime != null && player.offlineTime !== 0 ? (
|
{player.offlineTime != null && player.offlineTime !== 0 ? (
|
||||||
<div>
|
<div>Offline Time: {formatTime(player.offlineTime)}</div>
|
||||||
Offline Time: {formatTime(player.offlineTime)}
|
|
||||||
<Node id="offline" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
<div>
|
<div>
|
||||||
{Decimal.lt(points.value, "1e1000") ? <span>You have </span> : null}
|
{Decimal.lt(points.value, "1e1000") ? <span>You have </span> : null}
|
||||||
<h2>{format(points.value)}</h2>
|
<h2>{format(points.value)}</h2>
|
||||||
{Decimal.lt(points.value, "1e1e6") ? <span> points</span> : null}
|
{Decimal.lt(points.value, "1e1e6") ? <span> points</span> : null}
|
||||||
</div>
|
</div>
|
||||||
{Decimal.gt(pointGain.value, 0) ? (
|
{Decimal.gt(pointGain.value, 0) ? <div>({oomps.value})</div> : null}
|
||||||
<div>
|
|
||||||
({oomps.value})
|
|
||||||
<Node id="oomps" />
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{render(tree)}
|
{render(tree)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -120,7 +120,7 @@ export default defineComponent({
|
||||||
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
|
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
|
||||||
break;
|
break;
|
||||||
case Direction.Left:
|
case Direction.Left:
|
||||||
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value}%)`;
|
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value} + '%)`;
|
||||||
break;
|
break;
|
||||||
case Direction.Default:
|
case Direction.Default:
|
||||||
barStyle.clipPath = "inset(0% 50% 0% 0%)";
|
barStyle.clipPath = "inset(0% 50% 0% 0%)";
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<line
|
<line
|
||||||
class="link"
|
class="link"
|
||||||
v-bind="linkProps"
|
v-bind="link"
|
||||||
:class="{ pulsing: link.pulsing }"
|
:class="{ pulsing: link.pulsing }"
|
||||||
:x1="startPosition.x"
|
:x1="startPosition.x"
|
||||||
:y1="startPosition.y"
|
:y1="startPosition.y"
|
||||||
|
@ -12,7 +12,6 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BoardNode, BoardNodeLink } from "features/boards/board";
|
import type { BoardNode, BoardNodeLink } from "features/boards/board";
|
||||||
import { kebabifyObject } from "util/vue";
|
|
||||||
import { computed, toRefs, unref } from "vue";
|
import { computed, toRefs, unref } from "vue";
|
||||||
|
|
||||||
const _props = defineProps<{
|
const _props = defineProps<{
|
||||||
|
@ -50,14 +49,11 @@ const endPosition = computed(() => {
|
||||||
}
|
}
|
||||||
return position;
|
return position;
|
||||||
});
|
});
|
||||||
|
|
||||||
const linkProps = computed(() => kebabifyObject(_props.link as unknown as Record<string, unknown>));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.link {
|
.link {
|
||||||
transition-duration: 0s;
|
transition-duration: 0s;
|
||||||
pointer-events: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.link.pulsing {
|
.link.pulsing {
|
||||||
|
|
|
@ -108,7 +108,7 @@ document.onkeydown = function (e) {
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
key = "ctrl+" + key;
|
key = "ctrl+" + key;
|
||||||
}
|
}
|
||||||
const hotkey = hotkeys[key] ?? hotkeys[key.toLowerCase()];
|
const hotkey = hotkeys[key];
|
||||||
if (hotkey && unref(hotkey.enabled)) {
|
if (hotkey && unref(hotkey.enabled)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
hotkey.onPress();
|
hotkey.onPress();
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<line
|
<line
|
||||||
stroke-width="15px"
|
stroke-width="15px"
|
||||||
stroke="white"
|
stroke="white"
|
||||||
v-bind="linkProps"
|
v-bind="link"
|
||||||
:x1="startPosition.x"
|
:x1="startPosition.x"
|
||||||
:y1="startPosition.y"
|
:y1="startPosition.y"
|
||||||
:x2="endPosition.x"
|
:x2="endPosition.x"
|
||||||
|
@ -13,7 +13,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Link } from "features/links/links";
|
import type { Link } from "features/links/links";
|
||||||
import type { FeatureNode } from "game/layers";
|
import type { FeatureNode } from "game/layers";
|
||||||
import { kebabifyObject } from "util/vue";
|
|
||||||
import { computed, toRefs } from "vue";
|
import { computed, toRefs } from "vue";
|
||||||
|
|
||||||
const _props = defineProps<{
|
const _props = defineProps<{
|
||||||
|
@ -55,6 +54,4 @@ const endPosition = computed(() => {
|
||||||
}
|
}
|
||||||
return position;
|
return position;
|
||||||
});
|
});
|
||||||
|
|
||||||
const linkProps = computed(() => kebabifyObject(_props.link as unknown as Record<string, unknown>));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -95,6 +95,18 @@ export function addTooltip<T extends TooltipOptions>(
|
||||||
}
|
}
|
||||||
|
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
if (options.pinnable) {
|
||||||
|
if ("pinned" in element) {
|
||||||
|
console.error(
|
||||||
|
"Cannot add pinnable tooltip to element that already has a property called 'pinned'"
|
||||||
|
);
|
||||||
|
options.pinnable = false;
|
||||||
|
deletePersistent(options.pinned as Persistent<boolean>);
|
||||||
|
} else {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(element as any).pinned = options.pinned;
|
||||||
|
}
|
||||||
|
}
|
||||||
const elementComponent = element[Component];
|
const elementComponent = element[Component];
|
||||||
element[Component] = TooltipComponent as GenericComponent;
|
element[Component] = TooltipComponent as GenericComponent;
|
||||||
const elementGatherProps = element[GatherProps].bind(element);
|
const elementGatherProps = element[GatherProps].bind(element);
|
||||||
|
|
|
@ -224,7 +224,7 @@ export interface BaseTree {
|
||||||
id: string;
|
id: string;
|
||||||
/** The link objects for each of the branches of the tree. */
|
/** The link objects for each of the branches of the tree. */
|
||||||
links: Ref<Link[]>;
|
links: Ref<Link[]>;
|
||||||
/** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */
|
/** Cause a reset on this node and propagate it through the tree according to {@link resetPropagation}. */
|
||||||
reset: (node: GenericTreeNode) => void;
|
reset: (node: GenericTreeNode) => void;
|
||||||
/** A flag that is true while the reset is still propagating through the tree. */
|
/** A flag that is true while the reset is still propagating through the tree. */
|
||||||
isResetting: Ref<boolean>;
|
isResetting: Ref<boolean>;
|
||||||
|
@ -338,21 +338,34 @@ export const branchedResetPropagation = function (
|
||||||
tree: GenericTree,
|
tree: GenericTree,
|
||||||
resettingNode: GenericTreeNode
|
resettingNode: GenericTreeNode
|
||||||
): void {
|
): void {
|
||||||
const links = unref(tree.branches);
|
const visitedNodes = [resettingNode];
|
||||||
if (links == null) return;
|
let currentNodes = [resettingNode];
|
||||||
const reset: GenericTreeNode[] = [];
|
if (tree.branches != null) {
|
||||||
let current = [resettingNode];
|
const branches = unref(tree.branches);
|
||||||
while (current.length != 0) {
|
while (currentNodes.length > 0) {
|
||||||
const next: GenericTreeNode[] = [];
|
const nextNodes: GenericTreeNode[] = [];
|
||||||
for (const node of current) {
|
currentNodes.forEach(node => {
|
||||||
for (const link of links.filter(link => link.startNode === node)) {
|
branches
|
||||||
if ([...reset, ...current].includes(link.endNode)) continue
|
.filter(branch => branch.startNode === node || branch.endNode === node)
|
||||||
next.push(link.endNode);
|
.map(branch => {
|
||||||
link.endNode.reset?.reset();
|
if (branch.startNode === node) {
|
||||||
|
return branch.endNode;
|
||||||
|
}
|
||||||
|
return branch.startNode;
|
||||||
|
})
|
||||||
|
.filter(node => !visitedNodes.includes(node))
|
||||||
|
.forEach(node => {
|
||||||
|
// Check here instead of in the filter because this check's results may
|
||||||
|
// change as we go through each node
|
||||||
|
if (!nextNodes.includes(node)) {
|
||||||
|
nextNodes.push(node);
|
||||||
|
node.reset?.reset();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
currentNodes = nextNodes;
|
||||||
|
visitedNodes.push(...currentNodes);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
reset.push(...current);
|
|
||||||
current = next;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -345,35 +345,19 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
public static sgn = InternalFormula.sign;
|
public static sgn = InternalFormula.sign;
|
||||||
|
|
||||||
public static round(value: FormulaSource) {
|
public static round(value: FormulaSource) {
|
||||||
return new Formula({
|
return new Formula({ inputs: [value], evaluate: Decimal.round });
|
||||||
inputs: [value],
|
|
||||||
evaluate: Decimal.round,
|
|
||||||
invert: ops.invertPassthrough
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static floor(value: FormulaSource) {
|
public static floor(value: FormulaSource) {
|
||||||
return new Formula({
|
return new Formula({ inputs: [value], evaluate: Decimal.floor });
|
||||||
inputs: [value],
|
|
||||||
evaluate: Decimal.floor,
|
|
||||||
invert: ops.invertPassthrough
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ceil(value: FormulaSource) {
|
public static ceil(value: FormulaSource) {
|
||||||
return new Formula({
|
return new Formula({ inputs: [value], evaluate: Decimal.ceil });
|
||||||
inputs: [value],
|
|
||||||
evaluate: Decimal.ceil,
|
|
||||||
invert: ops.invertPassthrough
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static trunc(value: FormulaSource) {
|
public static trunc(value: FormulaSource) {
|
||||||
return new Formula({
|
return new Formula({ inputs: [value], evaluate: Decimal.trunc });
|
||||||
inputs: [value],
|
|
||||||
evaluate: Decimal.trunc,
|
|
||||||
invert: ops.invertPassthrough
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static add<T extends GenericFormula>(value: T, other: FormulaSource): T;
|
public static add<T extends GenericFormula>(value: T, other: FormulaSource): T;
|
||||||
|
@ -475,7 +459,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [value, min, max],
|
inputs: [value, min, max],
|
||||||
evaluate: Decimal.clamp,
|
evaluate: Decimal.clamp,
|
||||||
invert: ops.invertPassthrough
|
invert: ops.passthrough as InvertFunction<[FormulaSource, FormulaSource, FormulaSource]>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1469,7 +1453,9 @@ export function calculateMaxAffordable(
|
||||||
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
|
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
|
||||||
).sub(unref(formula.innermostVariable) ?? 0);
|
).sub(unref(formula.innermostVariable) ?? 0);
|
||||||
} else {
|
} else {
|
||||||
affordable = Decimal.floor(formula.invert(resource.value));
|
affordable = Decimal.floor(
|
||||||
|
formula.invert(resource.value)
|
||||||
|
).add(1).sub(unref(formula.innermostVariable) ?? 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
affordable = Decimal.clampMax(affordable, maxBulkAmount);
|
affordable = Decimal.clampMax(affordable, maxBulkAmount);
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
import Decimal, { DecimalSource } from "util/bignum";
|
import Decimal, { DecimalSource } from "util/bignum";
|
||||||
import Formula, { hasVariable, unrefFormulaSource } from "./formulas";
|
import Formula, { hasVariable, unrefFormulaSource } from "./formulas";
|
||||||
import {
|
import { FormulaSource, GenericFormula, InvertFunction, SubstitutionStack } from "./types";
|
||||||
FormulaSource,
|
|
||||||
GenericFormula,
|
|
||||||
InvertFunction,
|
|
||||||
InvertibleFormula,
|
|
||||||
SubstitutionStack
|
|
||||||
} from "./types";
|
|
||||||
|
|
||||||
const ln10 = Decimal.ln(10);
|
const ln10 = Decimal.ln(10);
|
||||||
|
|
||||||
|
@ -14,15 +8,6 @@ export function passthrough<T extends GenericFormula | DecimalSource>(value: T):
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertPassthrough(value: DecimalSource, ...inputs: FormulaSource[]) {
|
|
||||||
const variable = inputs.find(input => hasVariable(input)) as InvertibleFormula | undefined;
|
|
||||||
if (variable == null) {
|
|
||||||
console.error("Could not invert due to no input being a variable");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return variable.invert(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
|
export function invertNeg(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.neg(value));
|
return lhs.invert(Decimal.neg(value));
|
||||||
|
|
|
@ -4,7 +4,7 @@ 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";
|
||||||
import Decimal, { formatSmall } from "util/bignum";
|
import Decimal, { formatSmall } from "util/bignum";
|
||||||
import type { RequiredKeys, WithRequired } from "util/common";
|
import type { WithRequired } from "util/common";
|
||||||
import type { Computable, ProcessedComputable } from "util/computed";
|
import type { Computable, ProcessedComputable } from "util/computed";
|
||||||
import { convertComputable } from "util/computed";
|
import { convertComputable } from "util/computed";
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
|
@ -38,11 +38,16 @@ export interface Modifier {
|
||||||
description?: ProcessedComputable<CoercableComponent>;
|
description?: ProcessedComputable<CoercableComponent>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Utility type that represents the output of all modifiers that represent a single operation. */
|
/**
|
||||||
export type OperationModifier<T> = WithRequired<
|
* 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).
|
||||||
Modifier,
|
*/
|
||||||
"invert" | "getFormula" | Extract<RequiredKeys<T>, keyof Modifier>
|
export type ModifierFromOptionalParams<T, S> = undefined extends T
|
||||||
>;
|
? undefined extends S
|
||||||
|
? Omit<WithRequired<Modifier, "invert" | "getFormula">, "description" | "enabled">
|
||||||
|
: Omit<WithRequired<Modifier, "invert" | "enabled" | "getFormula">, "description">
|
||||||
|
: undefined extends S
|
||||||
|
? Omit<WithRequired<Modifier, "invert" | "description" | "getFormula">, "enabled">
|
||||||
|
: WithRequired<Modifier, "invert" | "enabled" | "description" | "getFormula">;
|
||||||
|
|
||||||
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
|
/** An object that configures an additive modifier via {@link createAdditiveModifier}. */
|
||||||
export interface AdditiveModifierOptions {
|
export interface AdditiveModifierOptions {
|
||||||
|
@ -60,9 +65,9 @@ export interface AdditiveModifierOptions {
|
||||||
* Create a modifier that adds some value to the input value.
|
* Create a modifier that adds some value to the input value.
|
||||||
* @param optionsFunc Additive modifier options.
|
* @param optionsFunc Additive modifier options.
|
||||||
*/
|
*/
|
||||||
export function createAdditiveModifier<T extends AdditiveModifierOptions, S = OperationModifier<T>>(
|
export function createAdditiveModifier<T extends AdditiveModifierOptions>(
|
||||||
optionsFunc: OptionsFunc<T>
|
optionsFunc: OptionsFunc<T>
|
||||||
) {
|
): ModifierFromOptionalParams<T["description"], T["enabled"]> {
|
||||||
return createLazyProxy(feature => {
|
return createLazyProxy(feature => {
|
||||||
const { addend, description, enabled, smallerIsBetter } = optionsFunc.call(
|
const { addend, description, enabled, smallerIsBetter } = optionsFunc.call(
|
||||||
feature,
|
feature,
|
||||||
|
@ -106,7 +111,7 @@ export function createAdditiveModifier<T extends AdditiveModifierOptions, S = Op
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as unknown as ModifierFromOptionalParams<T["description"], T["enabled"]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that configures an multiplicative modifier via {@link createMultiplicativeModifier}. */
|
/** An object that configures an multiplicative modifier via {@link createMultiplicativeModifier}. */
|
||||||
|
@ -125,10 +130,9 @@ export interface MultiplicativeModifierOptions {
|
||||||
* Create a modifier that multiplies the input value by some value.
|
* Create a modifier that multiplies the input value by some value.
|
||||||
* @param optionsFunc Multiplicative modifier options.
|
* @param optionsFunc Multiplicative modifier options.
|
||||||
*/
|
*/
|
||||||
export function createMultiplicativeModifier<
|
export function createMultiplicativeModifier<T extends MultiplicativeModifierOptions>(
|
||||||
T extends MultiplicativeModifierOptions,
|
optionsFunc: OptionsFunc<T>
|
||||||
S = OperationModifier<T>
|
): ModifierFromOptionalParams<T["description"], T["enabled"]> {
|
||||||
>(optionsFunc: OptionsFunc<T>) {
|
|
||||||
return createLazyProxy(feature => {
|
return createLazyProxy(feature => {
|
||||||
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc.call(
|
const { multiplier, description, enabled, smallerIsBetter } = optionsFunc.call(
|
||||||
feature,
|
feature,
|
||||||
|
@ -171,7 +175,7 @@ export function createMultiplicativeModifier<
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as unknown as ModifierFromOptionalParams<T["description"], T["enabled"]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that configures an exponential modifier via {@link createExponentialModifier}. */
|
/** An object that configures an exponential modifier via {@link createExponentialModifier}. */
|
||||||
|
@ -192,10 +196,9 @@ export interface ExponentialModifierOptions {
|
||||||
* Create a modifier that raises the input value to the power of some value.
|
* Create a modifier that raises the input value to the power of some value.
|
||||||
* @param optionsFunc Exponential modifier options.
|
* @param optionsFunc Exponential modifier options.
|
||||||
*/
|
*/
|
||||||
export function createExponentialModifier<
|
export function createExponentialModifier<T extends ExponentialModifierOptions>(
|
||||||
T extends ExponentialModifierOptions,
|
optionsFunc: OptionsFunc<T>
|
||||||
S = OperationModifier<T>
|
): ModifierFromOptionalParams<T["description"], T["enabled"]> {
|
||||||
>(optionsFunc: OptionsFunc<T>) {
|
|
||||||
return createLazyProxy(feature => {
|
return createLazyProxy(feature => {
|
||||||
const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } =
|
const { exponent, description, enabled, supportLowNumbers, smallerIsBetter } =
|
||||||
optionsFunc.call(feature, feature);
|
optionsFunc.call(feature, feature);
|
||||||
|
@ -260,7 +263,7 @@ export function createExponentialModifier<
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as unknown as ModifierFromOptionalParams<T["description"], T["enabled"]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -271,9 +274,11 @@ export function createExponentialModifier<
|
||||||
* @see {@link createModifierSection}.
|
* @see {@link createModifierSection}.
|
||||||
*/
|
*/
|
||||||
export function createSequentialModifier<
|
export function createSequentialModifier<
|
||||||
T extends Modifier,
|
T extends Modifier[],
|
||||||
S = WithRequired<Modifier, Extract<RequiredKeys<T>, keyof Modifier>>
|
S = T extends WithRequired<Modifier, "invert">[]
|
||||||
>(modifiersFunc: () => T[]) {
|
? WithRequired<Modifier, "description" | "invert">
|
||||||
|
: Omit<WithRequired<Modifier, "description">, "invert">
|
||||||
|
>(modifiersFunc: () => T): S {
|
||||||
return createLazyProxy(() => {
|
return createLazyProxy(() => {
|
||||||
const modifiers = modifiersFunc();
|
const modifiers = modifiersFunc();
|
||||||
|
|
||||||
|
@ -291,14 +296,10 @@ export function createSequentialModifier<
|
||||||
: undefined,
|
: undefined,
|
||||||
getFormula: modifiers.every(m => m.getFormula != null)
|
getFormula: modifiers.every(m => m.getFormula != null)
|
||||||
? (gain: FormulaSource) =>
|
? (gain: FormulaSource) =>
|
||||||
modifiers.reduce((acc, curr) => {
|
modifiers
|
||||||
if (curr.enabled == null || curr.enabled === true) {
|
.filter(m => unref(m.enabled) !== false)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
return curr.getFormula!(acc);
|
.reduce((acc, curr) => curr.getFormula!(acc), gain)
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
||||||
return Formula.if(acc, curr.enabled, acc => curr.getFormula!(acc));
|
|
||||||
}, gain)
|
|
||||||
: undefined,
|
: undefined,
|
||||||
enabled: modifiers.some(m => m.enabled != null)
|
enabled: modifiers.some(m => m.enabled != null)
|
||||||
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)
|
||||||
|
@ -316,7 +317,7 @@ export function createSequentialModifier<
|
||||||
))
|
))
|
||||||
: undefined
|
: undefined
|
||||||
};
|
};
|
||||||
}) as S;
|
}) as unknown as S;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that configures a modifier section via {@link createModifierSection}. */
|
/** An object that configures a modifier section via {@link createModifierSection}. */
|
||||||
|
|
|
@ -222,7 +222,7 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
||||||
Decimal.gte(
|
Decimal.gte(
|
||||||
req.resource.value,
|
req.resource.value,
|
||||||
unref(req.cost as ProcessedComputable<DecimalSource>)
|
unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
) ? 1 : 0
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,3 @@
|
||||||
export type RequiredKeys<T> = {
|
|
||||||
[K in keyof T]-?: NonNullable<unknown> extends Pick<T, K> ? never : K;
|
|
||||||
}[keyof T];
|
|
||||||
export type OptionalKeys<T> = {
|
|
||||||
[K in keyof T]-?: NonNullable<unknown> extends Pick<T, K> ? K : never;
|
|
||||||
}[keyof T];
|
|
||||||
|
|
||||||
export type OmitOptional<T> = Pick<T, RequiredKeys<T>>;
|
|
||||||
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
|
||||||
|
|
||||||
export type ArrayElements<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer S>
|
export type ArrayElements<T extends ReadonlyArray<unknown>> = T extends ReadonlyArray<infer S>
|
||||||
|
@ -20,11 +12,6 @@ export function camelToTitle(camel: string): string {
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function camelToKebab(camel: string) {
|
|
||||||
// Split off first character so function works on upper camel (pascal) case
|
|
||||||
return (camel[0] + camel.slice(1).replace(/[A-Z]/g, c => `-${c}`)).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isFunction<T, S extends ReadonlyArray<unknown>, R>(
|
export function isFunction<T, S extends ReadonlyArray<unknown>, R>(
|
||||||
functionOrValue: ((...args: S) => T) | R
|
functionOrValue: ((...args: S) => T) | R
|
||||||
): functionOrValue is (...args: S) => T {
|
): functionOrValue is (...args: S) => T {
|
||||||
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
unref,
|
unref,
|
||||||
watchEffect
|
watchEffect
|
||||||
} from "vue";
|
} from "vue";
|
||||||
import { camelToKebab } from "./common";
|
|
||||||
|
|
||||||
export function coerceComponent(
|
export function coerceComponent(
|
||||||
component: CoercableComponent,
|
component: CoercableComponent,
|
||||||
|
@ -242,10 +241,3 @@ export function trackHover(element: VueFeature): Ref<boolean> {
|
||||||
|
|
||||||
return isHovered;
|
return isHovered;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function kebabifyObject(object: Record<string, unknown>) {
|
|
||||||
return Object.keys(object).reduce((acc, curr) => {
|
|
||||||
acc[camelToKebab(curr)] = object[curr];
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, unknown>);
|
|
||||||
}
|
|
||||||
|
|
|
@ -47,10 +47,6 @@ describe("Creating conversion", () => {
|
||||||
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
||||||
expect(unref(conversion.currentGain)).compare_tolerance(100);
|
expect(unref(conversion.currentGain)).compare_tolerance(100);
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.currentGain)).compare_tolerance(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("Calculates actualGain correctly", () => {
|
describe("Calculates actualGain correctly", () => {
|
||||||
let conversion: GenericConversion;
|
let conversion: GenericConversion;
|
||||||
|
@ -73,10 +69,6 @@ describe("Creating conversion", () => {
|
||||||
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
||||||
expect(unref(conversion.actualGain)).compare_tolerance(100);
|
expect(unref(conversion.actualGain)).compare_tolerance(100);
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.actualGain)).compare_tolerance(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("Calculates currentAt correctly", () => {
|
describe("Calculates currentAt correctly", () => {
|
||||||
let conversion: GenericConversion;
|
let conversion: GenericConversion;
|
||||||
|
@ -103,10 +95,6 @@ describe("Creating conversion", () => {
|
||||||
Decimal.pow(100, 2).times(10)
|
Decimal.pow(100, 2).times(10)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.currentAt)).compare_tolerance(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("Calculates nextAt correctly", () => {
|
describe("Calculates nextAt correctly", () => {
|
||||||
let conversion: GenericConversion;
|
let conversion: GenericConversion;
|
||||||
|
@ -129,10 +117,6 @@ describe("Creating conversion", () => {
|
||||||
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
||||||
expect(unref(conversion.nextAt)).compare_tolerance(Decimal.pow(101, 2).times(10));
|
expect(unref(conversion.nextAt)).compare_tolerance(Decimal.pow(101, 2).times(10));
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.nextAt)).compare_tolerance(Decimal.dTen);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
test("Converts correctly", () => {
|
test("Converts correctly", () => {
|
||||||
const conversion = createCumulativeConversion(() => ({
|
const conversion = createCumulativeConversion(() => ({
|
||||||
|
@ -209,10 +193,6 @@ describe("Creating conversion", () => {
|
||||||
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
||||||
expect(unref(conversion.currentGain)).compare_tolerance(100);
|
expect(unref(conversion.currentGain)).compare_tolerance(100);
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.currentGain)).compare_tolerance(1);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("Calculates actualGain correctly", () => {
|
describe("Calculates actualGain correctly", () => {
|
||||||
let conversion: GenericConversion;
|
let conversion: GenericConversion;
|
||||||
|
@ -236,10 +216,6 @@ describe("Creating conversion", () => {
|
||||||
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
||||||
expect(unref(conversion.actualGain)).compare_tolerance(99);
|
expect(unref(conversion.actualGain)).compare_tolerance(99);
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.actualGain)).compare_tolerance(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("Calculates currentAt correctly", () => {
|
describe("Calculates currentAt correctly", () => {
|
||||||
let conversion: GenericConversion;
|
let conversion: GenericConversion;
|
||||||
|
@ -267,10 +243,6 @@ describe("Creating conversion", () => {
|
||||||
Decimal.pow(100, 2).times(10)
|
Decimal.pow(100, 2).times(10)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.currentAt)).compare_tolerance(Decimal.pow(1, 2).times(10));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("Calculates nextAt correctly", () => {
|
describe("Calculates nextAt correctly", () => {
|
||||||
let conversion: GenericConversion;
|
let conversion: GenericConversion;
|
||||||
|
@ -294,10 +266,6 @@ describe("Creating conversion", () => {
|
||||||
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
|
||||||
expect(unref(conversion.nextAt)).compare_tolerance(Decimal.pow(101, 2).times(10));
|
expect(unref(conversion.nextAt)).compare_tolerance(Decimal.pow(101, 2).times(10));
|
||||||
});
|
});
|
||||||
test("Zero", () => {
|
|
||||||
baseResource.value = Decimal.dZero;
|
|
||||||
expect(unref(conversion.nextAt)).compare_tolerance(Decimal.pow(2, 2).times(10));
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
test("Converts correctly", () => {
|
test("Converts correctly", () => {
|
||||||
const conversion = createIndependentConversion(() => ({
|
const conversion = createIndependentConversion(() => ({
|
||||||
|
|
|
@ -1,111 +0,0 @@
|
||||||
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<boolean>, shouldNotReset: Ref<boolean>;
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -13,13 +13,9 @@ import { InvertibleIntegralFormula } from "game/formulas/types";
|
||||||
|
|
||||||
type FormulaFunctions = keyof GenericFormula & keyof typeof Formula & keyof typeof Decimal;
|
type FormulaFunctions = keyof GenericFormula & keyof typeof Formula & keyof typeof Decimal;
|
||||||
|
|
||||||
const testValues = [-2, "0", new Decimal(10.5)] as const;
|
const testValues = [-1, "0", Decimal.dOne] as const;
|
||||||
|
|
||||||
const invertibleZeroParamFunctionNames = [
|
const invertibleZeroParamFunctionNames = [
|
||||||
"round",
|
|
||||||
"floor",
|
|
||||||
"ceil",
|
|
||||||
"trunc",
|
|
||||||
"neg",
|
"neg",
|
||||||
"recip",
|
"recip",
|
||||||
"log10",
|
"log10",
|
||||||
|
@ -52,6 +48,10 @@ const invertibleZeroParamFunctionNames = [
|
||||||
const nonInvertibleZeroParamFunctionNames = [
|
const nonInvertibleZeroParamFunctionNames = [
|
||||||
"abs",
|
"abs",
|
||||||
"sign",
|
"sign",
|
||||||
|
"round",
|
||||||
|
"floor",
|
||||||
|
"ceil",
|
||||||
|
"trunc",
|
||||||
"pLog10",
|
"pLog10",
|
||||||
"absLog10",
|
"absLog10",
|
||||||
"factorial",
|
"factorial",
|
||||||
|
@ -85,10 +85,6 @@ const integrableZeroParamFunctionNames = [
|
||||||
] as const;
|
] as const;
|
||||||
const nonIntegrableZeroParamFunctionNames = [
|
const nonIntegrableZeroParamFunctionNames = [
|
||||||
...nonInvertibleZeroParamFunctionNames,
|
...nonInvertibleZeroParamFunctionNames,
|
||||||
"round",
|
|
||||||
"floor",
|
|
||||||
"ceil",
|
|
||||||
"trunc",
|
|
||||||
"lambertw",
|
"lambertw",
|
||||||
"ssqrt"
|
"ssqrt"
|
||||||
] as const;
|
] as const;
|
||||||
|
@ -155,7 +151,7 @@ describe("Formula Equality Checking", () => {
|
||||||
describe("Formula aliases", () => {
|
describe("Formula aliases", () => {
|
||||||
function testAliases<T extends FormulaFunctions>(
|
function testAliases<T extends FormulaFunctions>(
|
||||||
aliases: T[],
|
aliases: T[],
|
||||||
args: Parameters<typeof Formula[T]>
|
args: Parameters<(typeof Formula)[T]>
|
||||||
) {
|
) {
|
||||||
describe(aliases[0], () => {
|
describe(aliases[0], () => {
|
||||||
let formula: GenericFormula;
|
let formula: GenericFormula;
|
||||||
|
@ -250,7 +246,7 @@ describe("Creating Formulas", () => {
|
||||||
|
|
||||||
function checkFormula<T extends FormulaFunctions>(
|
function checkFormula<T extends FormulaFunctions>(
|
||||||
functionName: T,
|
functionName: T,
|
||||||
args: Readonly<Parameters<typeof Formula[T]>>
|
args: Readonly<Parameters<(typeof Formula)[T]>>
|
||||||
) {
|
) {
|
||||||
let formula: GenericFormula;
|
let formula: GenericFormula;
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
|
@ -274,7 +270,7 @@ describe("Creating Formulas", () => {
|
||||||
// It's a lot of tests, but I'd rather be exhaustive
|
// It's a lot of tests, but I'd rather be exhaustive
|
||||||
function testFormulaCall<T extends FormulaFunctions>(
|
function testFormulaCall<T extends FormulaFunctions>(
|
||||||
functionName: T,
|
functionName: T,
|
||||||
args: Readonly<Parameters<typeof Formula[T]>>
|
args: Readonly<Parameters<(typeof Formula)[T]>>
|
||||||
) {
|
) {
|
||||||
if ((functionName === "slog" || functionName === "layeradd") && args[0] === -1) {
|
if ((functionName === "slog" || functionName === "layeradd") && args[0] === -1) {
|
||||||
// These cases in particular take a long time, so skip them
|
// These cases in particular take a long time, so skip them
|
||||||
|
@ -492,18 +488,18 @@ describe("Inverting", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Inverting nested formulas", () => {
|
test("Inverting nested formulas", () => {
|
||||||
const formula = Formula.add(variable, constant).times(constant).floor();
|
const formula = Formula.add(variable, constant).times(constant);
|
||||||
expect(formula.invert(100)).compare_tolerance(0);
|
expect(formula.invert(100)).compare_tolerance(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Inverting with non-invertible sections", () => {
|
describe("Inverting with non-invertible sections", () => {
|
||||||
test("Non-invertible constant", () => {
|
test("Non-invertible constant", () => {
|
||||||
const formula = Formula.add(variable, constant.sign());
|
const formula = Formula.add(variable, constant.ceil());
|
||||||
expect(formula.isInvertible()).toBe(true);
|
expect(formula.isInvertible()).toBe(true);
|
||||||
expect(() => formula.invert(10)).not.toLogError();
|
expect(() => formula.invert(10)).not.toLogError();
|
||||||
});
|
});
|
||||||
test("Non-invertible variable", () => {
|
test("Non-invertible variable", () => {
|
||||||
const formula = Formula.add(variable.sign(), constant);
|
const formula = Formula.add(variable.ceil(), constant);
|
||||||
expect(formula.isInvertible()).toBe(false);
|
expect(formula.isInvertible()).toBe(false);
|
||||||
expect(() => formula.invert(10)).toLogError();
|
expect(() => formula.invert(10)).toLogError();
|
||||||
});
|
});
|
||||||
|
|
|
@ -133,14 +133,14 @@ describe("Exponential Modifiers", () =>
|
||||||
testModifiers(createExponentialModifier, "exponent", Decimal.pow));
|
testModifiers(createExponentialModifier, "exponent", Decimal.pow));
|
||||||
|
|
||||||
describe("Sequential Modifiers", () => {
|
describe("Sequential Modifiers", () => {
|
||||||
function createModifier<T extends Partial<ModifierConstructorOptions>>(
|
function createModifier(
|
||||||
value: Computable<DecimalSource>,
|
value: Computable<DecimalSource>,
|
||||||
options?: T
|
options: Partial<ModifierConstructorOptions> = {}
|
||||||
) {
|
): WithRequired<Modifier, "invert" | "getFormula"> {
|
||||||
return createSequentialModifier(() => [
|
return createSequentialModifier(() => [
|
||||||
createAdditiveModifier(() => ({ ...(options ?? {}), addend: value })),
|
createAdditiveModifier(() => ({ ...options, addend: value })),
|
||||||
createMultiplicativeModifier(() => ({ ...(options ?? {}), multiplier: value })),
|
createMultiplicativeModifier(() => ({ ...options, multiplier: value })),
|
||||||
createExponentialModifier(() => ({ ...(options ?? {}), exponent: value }))
|
createExponentialModifier(() => ({ ...options, exponent: value }))
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,17 +199,6 @@ describe("Sequential Modifiers", () => {
|
||||||
// So long as one is true or undefined, enable should be true
|
// So long as one is true or undefined, enable should be true
|
||||||
expect(unref(modifier.enabled)).toBe(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", () => {
|
describe("applies smallerIsBetter correctly", () => {
|
||||||
|
|
|
@ -83,7 +83,7 @@ describe("Creating cost requirement", () => {
|
||||||
cost: 10,
|
cost: 10,
|
||||||
cumulativeCost: false
|
cumulativeCost: false
|
||||||
}));
|
}));
|
||||||
expect(unref(requirement.requirementMet)).toBe(1);
|
expect(unref(requirement.requirementMet)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Requirement not met when not meeting the cost", () => {
|
test("Requirement not met when not meeting the cost", () => {
|
||||||
|
@ -92,7 +92,7 @@ describe("Creating cost requirement", () => {
|
||||||
cost: 100,
|
cost: 100,
|
||||||
cumulativeCost: false
|
cumulativeCost: false
|
||||||
}));
|
}));
|
||||||
expect(unref(requirement.requirementMet)).toBe(0);
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("canMaximize works correctly", () => {
|
describe("canMaximize works correctly", () => {
|
||||||
|
|
Loading…
Reference in a new issue