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:
branches:
- 'main'
workflow_dispatch:
jobs:
build-and-deploy:
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:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
test:
runs-on: ubuntu-latest
steps:

View file

@ -38,7 +38,7 @@
target="_blank"
>
<span class="material-icons info-modal-discord">discord</span>
The Paper Pilot Community
Profectus & Friends
</a>
</div>
<div>

View file

@ -19,7 +19,7 @@
class="nan-modal-discord-link"
>
<span class="material-icons nan-modal-discord">discord</span>
{{ discordName || "The Paper Pilot Community" }}
{{ discordName || "Profectus & Friends" }}
</a>
</div>
<br />

View file

@ -15,9 +15,7 @@
<a :href="discordLink" target="_blank">{{ discordName }}</a>
</li>
<li>
<a href="https://discord.gg/yJ4fjnjU54" target="_blank"
>The Paper Pilot Community</a
>
<a href="https://discord.gg/yJ4fjnjU54" target="_blank">Profectus & Friends</a>
</li>
<li>
<a href="https://discord.gg/F3xveHV" target="_blank">The Modding Tree</a>
@ -82,9 +80,7 @@
<a :href="discordLink" target="_blank">{{ discordName }}</a>
</li>
<li>
<a href="https://discord.gg/yJ4fjnjU54" target="_blank"
>The Paper Pilot Community</a
>
<a href="https://discord.gg/yJ4fjnjU54" target="_blank">Profectus & Friends</a>
</li>
<li>
<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 { createTreeNode } from "features/trees/tree";
import type { GenericFormula } from "game/formulas/types";
import { BaseLayer } from "game/layers";
import type { Modifier } from "game/modifiers";
import type { 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:"}{" "}
{displayResource(
resetButton.conversion.baseResource,
!unref(resetButton.conversion.buyMax) ||
Decimal.lt(unref(resetButton.conversion.actualGain), 1)
!unref(resetButton.conversion.buyMax) &&
Decimal.gte(unref(resetButton.conversion.actualGain), 1)
? unref(resetButton.conversion.currentAt)
: unref(resetButton.conversion.nextAt)
)}{" "}
@ -485,3 +486,22 @@ export function createFormulaPreview(
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,
reset
}));
addTooltip(treeNode, {
const tooltip = addTooltip(treeNode, {
display: createResourceTooltip(points),
pinnable: true
});
@ -58,6 +58,7 @@ const layer = createLayer(id, function (this: BaseLayer) {
name,
color,
points,
tooltip,
display: jsx(() => (
<>
<MainDisplay resource={points} color={color} />

View file

@ -120,7 +120,7 @@ export default defineComponent({
barStyle.clipPath = `inset(0% ${normalizedProgress.value}% 0% 0%)`;
break;
case Direction.Left:
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value} + '%)`;
barStyle.clipPath = `inset(0% 0% 0% ${normalizedProgress.value}%)`;
break;
case Direction.Default:
barStyle.clipPath = "inset(0% 50% 0% 0%)";

View file

@ -1,7 +1,7 @@
<template>
<line
class="link"
v-bind="link"
v-bind="linkProps"
:class="{ pulsing: link.pulsing }"
:x1="startPosition.x"
:y1="startPosition.y"
@ -12,6 +12,7 @@
<script setup lang="ts">
import type { BoardNode, BoardNodeLink } from "features/boards/board";
import { kebabifyObject } from "util/vue";
import { computed, toRefs, unref } from "vue";
const _props = defineProps<{
@ -49,11 +50,14 @@ const endPosition = computed(() => {
}
return position;
});
const linkProps = computed(() => kebabifyObject(_props.link as unknown as Record<string, unknown>));
</script>
<style scoped>
.link {
transition-duration: 0s;
pointer-events: none;
}
.link.pulsing {

View file

@ -2,7 +2,7 @@
<line
stroke-width="15px"
stroke="white"
v-bind="link"
v-bind="linkProps"
:x1="startPosition.x"
:y1="startPosition.y"
:x2="endPosition.x"
@ -13,6 +13,7 @@
<script setup lang="ts">
import type { Link } from "features/links/links";
import type { FeatureNode } from "game/layers";
import { kebabifyObject } from "util/vue";
import { computed, toRefs } from "vue";
const _props = defineProps<{
@ -54,4 +55,6 @@ const endPosition = computed(() => {
}
return position;
});
const linkProps = computed(() => kebabifyObject(_props.link as unknown as Record<string, unknown>));
</script>

View file

@ -95,18 +95,6 @@ export function addTooltip<T extends TooltipOptions>(
}
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];
element[Component] = TooltipComponent as GenericComponent;
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 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) {
return new Formula({ inputs: [value], evaluate: Decimal.floor });
return new Formula({
inputs: [value],
evaluate: Decimal.floor,
invert: ops.invertPassthrough
});
}
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) {
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;
@ -459,7 +475,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
return new Formula({
inputs: [value, min, max],
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 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);
@ -8,6 +14,15 @@ export function passthrough<T extends GenericFormula | DecimalSource>(value: T):
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) {
if (hasVariable(lhs)) {
return lhs.invert(Decimal.neg(value));

View file

@ -297,9 +297,9 @@ export function createSequentialModifier<
getFormula: modifiers.every(m => m.getFormula != null)
? (gain: FormulaSource) =>
modifiers
.filter(m => unref(m.enabled) !== false)
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
.reduce((acc, curr) => curr.getFormula!(acc), gain)
.reduce((acc, curr) => Formula.if(acc, curr.enabled ?? true,
acc => curr.getFormula!(acc), acc => acc), gain)
: undefined,
enabled: modifiers.some(m => m.enabled != null)
? computed(() => modifiers.filter(m => unref(m.enabled) !== false).length > 0)

View file

@ -222,7 +222,7 @@ export function createCostRequirement<T extends CostRequirementOptions>(
Decimal.gte(
req.resource.value,
unref(req.cost as ProcessedComputable<DecimalSource>)
)
) ? 1 : 0
);
}

View file

@ -12,6 +12,11 @@ export function camelToTitle(camel: string): string {
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>(
functionOrValue: ((...args: S) => T) | R
): functionOrValue is (...args: S) => T {

View file

@ -21,6 +21,7 @@ import {
unref,
watchEffect
} from "vue";
import { camelToKebab } from "./common";
export function coerceComponent(
component: CoercableComponent,
@ -241,3 +242,10 @@ export function trackHover(element: VueFeature): Ref<boolean> {
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);
expect(unref(conversion.currentGain)).compare_tolerance(100);
});
test("Zero", () => {
baseResource.value = Decimal.dZero;
expect(unref(conversion.currentGain)).compare_tolerance(0);
});
});
describe("Calculates actualGain correctly", () => {
let conversion: GenericConversion;
@ -69,6 +73,10 @@ describe("Creating conversion", () => {
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
expect(unref(conversion.actualGain)).compare_tolerance(100);
});
test("Zero", () => {
baseResource.value = Decimal.dZero;
expect(unref(conversion.actualGain)).compare_tolerance(0);
});
});
describe("Calculates currentAt correctly", () => {
let conversion: GenericConversion;
@ -95,6 +103,10 @@ describe("Creating conversion", () => {
Decimal.pow(100, 2).times(10)
);
});
test("Zero", () => {
baseResource.value = Decimal.dZero;
expect(unref(conversion.currentAt)).compare_tolerance(0);
});
});
describe("Calculates nextAt correctly", () => {
let conversion: GenericConversion;
@ -117,6 +129,10 @@ describe("Creating conversion", () => {
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
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", () => {
const conversion = createCumulativeConversion(() => ({
@ -193,6 +209,10 @@ describe("Creating conversion", () => {
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
expect(unref(conversion.currentGain)).compare_tolerance(100);
});
test("Zero", () => {
baseResource.value = Decimal.dZero;
expect(unref(conversion.currentGain)).compare_tolerance(1);
});
});
describe("Calculates actualGain correctly", () => {
let conversion: GenericConversion;
@ -216,6 +236,10 @@ describe("Creating conversion", () => {
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
expect(unref(conversion.actualGain)).compare_tolerance(99);
});
test("Zero", () => {
baseResource.value = Decimal.dZero;
expect(unref(conversion.actualGain)).compare_tolerance(0);
});
});
describe("Calculates currentAt correctly", () => {
let conversion: GenericConversion;
@ -243,6 +267,10 @@ describe("Creating conversion", () => {
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", () => {
let conversion: GenericConversion;
@ -266,6 +294,10 @@ describe("Creating conversion", () => {
baseResource.value = Decimal.pow(100, 2).times(10).add(1);
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", () => {
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;
const testValues = [-1, "0", Decimal.dOne] as const;
const testValues = [-2, "0", new Decimal(10.5)] as const;
const invertibleZeroParamFunctionNames = [
"round",
"floor",
"ceil",
"trunc",
"neg",
"recip",
"log10",
@ -48,10 +52,6 @@ const invertibleZeroParamFunctionNames = [
const nonInvertibleZeroParamFunctionNames = [
"abs",
"sign",
"round",
"floor",
"ceil",
"trunc",
"pLog10",
"absLog10",
"factorial",
@ -85,6 +85,10 @@ const integrableZeroParamFunctionNames = [
] as const;
const nonIntegrableZeroParamFunctionNames = [
...nonInvertibleZeroParamFunctionNames,
"round",
"floor",
"ceil",
"trunc",
"lambertw",
"ssqrt"
] as const;
@ -151,7 +155,7 @@ describe("Formula Equality Checking", () => {
describe("Formula aliases", () => {
function testAliases<T extends FormulaFunctions>(
aliases: T[],
args: Parameters<(typeof Formula)[T]>
args: Parameters<typeof Formula[T]>
) {
describe(aliases[0], () => {
let formula: GenericFormula;
@ -246,7 +250,7 @@ describe("Creating Formulas", () => {
function checkFormula<T extends FormulaFunctions>(
functionName: T,
args: Readonly<Parameters<(typeof Formula)[T]>>
args: Readonly<Parameters<typeof Formula[T]>>
) {
let formula: GenericFormula;
beforeAll(() => {
@ -270,7 +274,7 @@ describe("Creating Formulas", () => {
// It's a lot of tests, but I'd rather be exhaustive
function testFormulaCall<T extends FormulaFunctions>(
functionName: T,
args: Readonly<Parameters<(typeof Formula)[T]>>
args: Readonly<Parameters<typeof Formula[T]>>
) {
if ((functionName === "slog" || functionName === "layeradd") && args[0] === -1) {
// These cases in particular take a long time, so skip them
@ -488,18 +492,18 @@ describe("Inverting", () => {
});
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);
});
describe("Inverting with non-invertible sections", () => {
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.invert(10)).not.toLogError();
});
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.invert(10)).toLogError();
});

View file

@ -83,7 +83,7 @@ describe("Creating cost requirement", () => {
cost: 10,
cumulativeCost: false
}));
expect(unref(requirement.requirementMet)).toBe(true);
expect(unref(requirement.requirementMet)).toBe(1);
});
test("Requirement not met when not meeting the cost", () => {
@ -92,7 +92,7 @@ describe("Creating cost requirement", () => {
cost: 100,
cumulativeCost: false
}));
expect(unref(requirement.requirementMet)).toBe(false);
expect(unref(requirement.requirementMet)).toBe(0);
});
describe("canMaximize works correctly", () => {