Compare commits

...

36 commits

Author SHA1 Message Date
1b809a9550 Merge pull request 'Keep disabled modifiers when making formulas' (#56) from escapee/profectus-template:main into main
Reviewed-on: profectus/Profectus#56
2024-02-13 05:18:01 +00:00
Seth Posner
cf6265d8ce Keep disabled modifiers when making formulas 2024-02-12 07:58:39 -08:00
4f807aaf96 Merge pull request 'Add forgejo workflows support' (#53) from thepaperpilot/Profectus:main into main
Reviewed-on: profectus/Profectus#53
2024-02-11 19:43:37 +00:00
ffc42a5745 Merge pull request 'Fix reset button using currentAt when not yet gaining' (#25) from sethposner/profectus-template:main into main
Reviewed-on: profectus/Profectus#25
2023-11-13 22:29:49 +00:00
Seth Posner
8811996f64 Add tests confirming low-input conversion values 2023-11-13 14:10:00 -08:00
Seth Posner
7750a3368d Swap logic for nextAt display 2023-11-13 14:09:48 -08:00
2495dc9783 Implement forgejo actions workflows (#24)
Reviewed-on: profectus/Profectus#24
Co-authored-by: thepaperpilot <thepaperpilot@gmail.com>
Co-committed-by: thepaperpilot <thepaperpilot@gmail.com>
2023-11-05 17:00:28 +00:00
e66daad7a2 Merge pull request 'Implement isRednered' (#23) from thepaperpilot/Profectus:feature/is-rendered into main
Reviewed-on: profectus/Profectus#23
2023-11-05 16:59:37 +00:00
8065f8efa4 Approve install 2023-11-05 10:17:23 -06:00
e9283b5cca Update repos first 2023-11-05 10:16:38 -06:00
2c615ea524 Typo 2023-11-05 10:07:06 -06:00
953cd8047e Install rsync the normal way 2023-11-05 10:06:26 -06:00
766c600a70 Were the rsync logs wrong 2023-11-05 09:57:05 -06:00
c1d0b7eec6 Fully qualify >.< 2023-11-05 09:55:52 -06:00
52b500c9d8 setup rsync 2023-11-05 09:49:02 -06:00
aabb0a1bba Setup node 2023-11-05 09:13:56 -06:00
005bf5da9a Try alpine instead 2023-11-05 09:11:10 -06:00
7330a6bda4 Switch image to ubuntu:latest 2023-11-05 09:09:47 -06:00
65ff440e25 Fully qualify pages deploy action 2023-11-05 08:42:39 -06:00
acf1d24c15 Changed one of the wrong runs-on 2023-11-05 08:27:35 -06:00
d16bb55c3c Update runs-on 2023-11-05 07:46:30 -06:00
312cab1347 Rename workflow files 2023-11-05 07:42:28 -06:00
6d148da260 Add forgejo workflows 2023-11-05 07:28:43 -06:00
a5204106aa Forgot to comment the other signature 2023-10-11 21:44:02 -05:00
0cccf7aecc Add isRendered utility 2023-10-11 21:39:01 -05:00
d0fffd3b89 Update test.yml 2023-06-09 15:52:26 -05:00
3fe0311331 Try to allow actions to be run manually 2023-06-09 15:52:22 -05:00
eee5ac3e2d Fix passthroughs for inversions and make more operations invertible 2023-05-30 22:54:46 -05:00
9edda4d957 Make links ignore pointer events 2023-05-22 21:41:53 -05:00
6ad08c4052 Fix camelCase props not working on links 2023-05-21 17:27:29 -05:00
e0f1296b35 Rename "The Paper Pilot Community" to "Profectus & Friends" 2023-05-20 21:56:46 -05:00
c8ba77b89b Fix Direction.Left bars 2023-05-20 21:24:59 -05:00
63dcad4c12 Fix requirements tests 2023-05-20 08:30:07 -05:00
d6c9f95851 Fix error about pinnable tooltips 2023-05-20 08:28:27 -05:00
210c2290f0
Fix #9 2023-05-19 10:12:24 -05:00
e896fd84cf Change formula testing values to hopefully catch any other miscalculations 2023-05-19 08:04:20 -05:00
22 changed files with 195 additions and 50 deletions

View file

@ -0,0 +1,31 @@
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.

View file

@ -0,0 +1,21 @@
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

View file

@ -3,6 +3,7 @@ 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

View file

@ -1,11 +1,11 @@
name: Build and Deploy name: Run Tests
on: on:
push: push:
branches: [ main ] branches: [ main ]
pull_request: pull_request:
branches: [ main ] branches: [ main ]
jobs: jobs:
build: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View file

@ -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>
The Paper Pilot Community Profectus & Friends
</a> </a>
</div> </div>
<div> <div>

View file

@ -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 || "The Paper Pilot Community" }} {{ discordName || "Profectus & Friends" }}
</a> </a>
</div> </div>
<br /> <br />

View file

@ -15,9 +15,7 @@
<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" <a href="https://discord.gg/yJ4fjnjU54" target="_blank">Profectus & Friends</a>
>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>
@ -82,9 +80,7 @@
<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" <a href="https://discord.gg/yJ4fjnjU54" target="_blank">Profectus & Friends</a>
>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>

View file

@ -9,6 +9,7 @@ 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";
@ -133,8 +134,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.lt(unref(resetButton.conversion.actualGain), 1) Decimal.gte(unref(resetButton.conversion.actualGain), 1)
? unref(resetButton.conversion.currentAt) ? unref(resetButton.conversion.currentAt)
: unref(resetButton.conversion.nextAt) : unref(resetButton.conversion.nextAt)
)}{" "} )}{" "}
@ -485,3 +486,22 @@ 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);
}

View file

@ -37,7 +37,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
color, color,
reset reset
})); }));
addTooltip(treeNode, { const tooltip = addTooltip(treeNode, {
display: createResourceTooltip(points), display: createResourceTooltip(points),
pinnable: true pinnable: true
}); });
@ -58,6 +58,7 @@ 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} />

View file

@ -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%)";

View file

@ -1,7 +1,7 @@
<template> <template>
<line <line
class="link" class="link"
v-bind="link" v-bind="linkProps"
:class="{ pulsing: link.pulsing }" :class="{ pulsing: link.pulsing }"
:x1="startPosition.x" :x1="startPosition.x"
:y1="startPosition.y" :y1="startPosition.y"
@ -12,6 +12,7 @@
<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<{
@ -49,11 +50,14 @@ 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 {

View file

@ -2,7 +2,7 @@
<line <line
stroke-width="15px" stroke-width="15px"
stroke="white" stroke="white"
v-bind="link" v-bind="linkProps"
:x1="startPosition.x" :x1="startPosition.x"
:y1="startPosition.y" :y1="startPosition.y"
:x2="endPosition.x" :x2="endPosition.x"
@ -13,6 +13,7 @@
<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<{
@ -54,4 +55,6 @@ const endPosition = computed(() => {
} }
return position; return position;
}); });
const linkProps = computed(() => kebabifyObject(_props.link as unknown as Record<string, unknown>));
</script> </script>

View file

@ -95,18 +95,6 @@ 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);

View file

@ -345,19 +345,35 @@ 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({ inputs: [value], evaluate: Decimal.round }); return new Formula({
inputs: [value],
evaluate: Decimal.round,
invert: ops.invertPassthrough
});
} }
public static floor(value: FormulaSource) { public static floor(value: FormulaSource) {
return new Formula({ inputs: [value], evaluate: Decimal.floor }); return new Formula({
inputs: [value],
evaluate: Decimal.floor,
invert: ops.invertPassthrough
});
} }
public static ceil(value: FormulaSource) { public static ceil(value: FormulaSource) {
return new Formula({ inputs: [value], evaluate: Decimal.ceil }); return new Formula({
inputs: [value],
evaluate: Decimal.ceil,
invert: ops.invertPassthrough
});
} }
public static trunc(value: FormulaSource) { public static trunc(value: FormulaSource) {
return new Formula({ inputs: [value], evaluate: Decimal.trunc }); return new Formula({
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;
@ -459,7 +475,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.passthrough as InvertFunction<[FormulaSource, FormulaSource, FormulaSource]> invert: ops.invertPassthrough
}); });
} }

View file

@ -1,6 +1,12 @@
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 { FormulaSource, GenericFormula, InvertFunction, SubstitutionStack } from "./types"; import {
FormulaSource,
GenericFormula,
InvertFunction,
InvertibleFormula,
SubstitutionStack
} from "./types";
const ln10 = Decimal.ln(10); const ln10 = Decimal.ln(10);
@ -8,6 +14,15 @@ 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));

View file

@ -297,9 +297,9 @@ export function createSequentialModifier<
getFormula: modifiers.every(m => m.getFormula != null) getFormula: modifiers.every(m => m.getFormula != null)
? (gain: FormulaSource) => ? (gain: FormulaSource) =>
modifiers modifiers
.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
.reduce((acc, curr) => curr.getFormula!(acc), gain) .reduce((acc, curr) => Formula.if(acc, curr.enabled ?? true,
acc => curr.getFormula!(acc), acc => 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)

View file

@ -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
); );
} }

View file

@ -12,6 +12,11 @@ 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 {

View file

@ -21,6 +21,7 @@ import {
unref, unref,
watchEffect watchEffect
} from "vue"; } from "vue";
import { camelToKebab } from "./common";
export function coerceComponent( export function coerceComponent(
component: CoercableComponent, component: CoercableComponent,
@ -241,3 +242,10 @@ 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>);
}

View file

@ -47,6 +47,10 @@ 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;
@ -69,6 +73,10 @@ 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;
@ -95,6 +103,10 @@ 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;
@ -117,6 +129,10 @@ 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(() => ({
@ -193,6 +209,10 @@ 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;
@ -216,6 +236,10 @@ 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;
@ -243,6 +267,10 @@ 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;
@ -266,6 +294,10 @@ 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(() => ({

View file

@ -13,9 +13,13 @@ 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 = [-1, "0", Decimal.dOne] as const; const testValues = [-2, "0", new Decimal(10.5)] as const;
const invertibleZeroParamFunctionNames = [ const invertibleZeroParamFunctionNames = [
"round",
"floor",
"ceil",
"trunc",
"neg", "neg",
"recip", "recip",
"log10", "log10",
@ -48,10 +52,6 @@ const invertibleZeroParamFunctionNames = [
const nonInvertibleZeroParamFunctionNames = [ const nonInvertibleZeroParamFunctionNames = [
"abs", "abs",
"sign", "sign",
"round",
"floor",
"ceil",
"trunc",
"pLog10", "pLog10",
"absLog10", "absLog10",
"factorial", "factorial",
@ -85,6 +85,10 @@ const integrableZeroParamFunctionNames = [
] as const; ] as const;
const nonIntegrableZeroParamFunctionNames = [ const nonIntegrableZeroParamFunctionNames = [
...nonInvertibleZeroParamFunctionNames, ...nonInvertibleZeroParamFunctionNames,
"round",
"floor",
"ceil",
"trunc",
"lambertw", "lambertw",
"ssqrt" "ssqrt"
] as const; ] as const;
@ -151,7 +155,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;
@ -246,7 +250,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(() => {
@ -270,7 +274,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
@ -488,18 +492,18 @@ describe("Inverting", () => {
}); });
test("Inverting nested formulas", () => { test("Inverting nested formulas", () => {
const formula = Formula.add(variable, constant).times(constant); const formula = Formula.add(variable, constant).times(constant).floor();
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.ceil()); const formula = Formula.add(variable, constant.sign());
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.ceil(), constant); const formula = Formula.add(variable.sign(), constant);
expect(formula.isInvertible()).toBe(false); expect(formula.isInvertible()).toBe(false);
expect(() => formula.invert(10)).toLogError(); expect(() => formula.invert(10)).toLogError();
}); });

View file

@ -83,7 +83,7 @@ describe("Creating cost requirement", () => {
cost: 10, cost: 10,
cumulativeCost: false cumulativeCost: false
})); }));
expect(unref(requirement.requirementMet)).toBe(true); expect(unref(requirement.requirementMet)).toBe(1);
}); });
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(false); expect(unref(requirement.requirementMet)).toBe(0);
}); });
describe("canMaximize works correctly", () => { describe("canMaximize works correctly", () => {