forked from profectus/Profectus
Compare commits
4 commits
main
...
fix/lazy-p
Author | SHA1 | Date | |
---|---|---|---|
2f8c37d730 | |||
a8c550c551 | |||
cf069cee61 | |||
c39982b1bc |
56 changed files with 736 additions and 1511 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:
|
||||||
|
|
36
CHANGELOG.md
36
CHANGELOG.md
|
@ -6,42 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [0.6.1] - 2023-05-17
|
|
||||||
### Added
|
|
||||||
- Error boundaries around each layer, and errors now display on the page when in development
|
|
||||||
- Utility for creating requirement based on whether a conversion has met a requirement
|
|
||||||
### Changed
|
|
||||||
- **BREAKING** Formulas/requirements refactor
|
|
||||||
- spendResources renamed to cumulativeCost
|
|
||||||
- summedPurchases renamed to directSum
|
|
||||||
- calculateMaxAffordable now takes optional 'maxBulkAmount' parameter
|
|
||||||
- cost requirements now pass cumulativeCost, maxBulkAmount, and directSum to calculateMaxAffordable
|
|
||||||
- Non-integrable and non-invertible formulas will now work in more situations
|
|
||||||
- Repeatable.maximize is removed
|
|
||||||
- Challenge.maximize is removed
|
|
||||||
- Formulas have better typing information now
|
|
||||||
- Integrate functions now log errors if the variable input is not integrable
|
|
||||||
- Cyclical proxies now throw errors
|
|
||||||
- createFormulaPreview is now a JSX function
|
|
||||||
- Tree nodes are not automatically capitalized anymore
|
|
||||||
- upgrade.canPurchase now returns false if the upgrade is already bought
|
|
||||||
- TPS display is simplified and more performant now
|
|
||||||
### Fixed
|
|
||||||
- Actions could not be constructed
|
|
||||||
- Progress bar on actions was misaligned
|
|
||||||
- Many different issues the Board features (and many changes/improvements)
|
|
||||||
- Calculating max affordable could sometimes infinite loop
|
|
||||||
- Non-integrable formulas could cause errors in cost requirements
|
|
||||||
- estimateTime would not show "never" when production is 0
|
|
||||||
- isInvertible and isIntegrable now properly handle nested formulas
|
|
||||||
- Repeatables' amount display would show the literal text "joinJSX"
|
|
||||||
- Repeatables would not buy max properly
|
|
||||||
- Reset buttons were showing wrong "currentAt" vs "nextAt"
|
|
||||||
- Step-wise formulas not updating their value correctly
|
|
||||||
- Bonus amount decorator now checks for `amount` property in the post construct callback
|
|
||||||
### Documentation
|
|
||||||
- Various typos fixed and a few sections made more thorough
|
|
||||||
|
|
||||||
## [0.6.0] - 2023-04-20
|
## [0.6.0] - 2023-04-20
|
||||||
### Added
|
### Added
|
||||||
- **BREAKING** New requirements system
|
- **BREAKING** New requirements system
|
||||||
|
|
|
@ -26,6 +26,11 @@ npm run build
|
||||||
npm run preview
|
npm run preview
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
### Runs the tests using vite-jest
|
### Runs the tests using vite-jest
|
||||||
```
|
```
|
||||||
npm run test
|
npm run test
|
||||||
|
|
4
package-lock.json
generated
4
package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.6.1",
|
"version": "0.6.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.6.1",
|
"version": "0.6.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/material-icons": "^4.5.4",
|
"@fontsource/material-icons": "^4.5.4",
|
||||||
"@fontsource/roboto-mono": "^4.5.8",
|
"@fontsource/roboto-mono": "^4.5.8",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "profectus",
|
"name": "profectus",
|
||||||
"version": "0.6.1",
|
"version": "0.6.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
|
39
src/App.vue
39
src/App.vue
|
@ -1,25 +1,18 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="appErrors.length > 0" class="error-container" :style="theme"><Error :errors="appErrors" /></div>
|
<div id="modal-root" :style="theme" />
|
||||||
<template v-else>
|
<div class="app" :style="theme" :class="{ useHeader }">
|
||||||
<div id="modal-root" :style="theme" />
|
<Nav v-if="useHeader" />
|
||||||
<div class="app" :style="theme" :class="{ useHeader }">
|
<Game />
|
||||||
<Nav v-if="useHeader" />
|
<TPS v-if="unref(showTPS)" />
|
||||||
<Game />
|
<GameOverScreen />
|
||||||
<TPS v-if="unref(showTPS)" />
|
<NaNScreen />
|
||||||
<GameOverScreen />
|
<component :is="gameComponent" />
|
||||||
<NaNScreen />
|
</div>
|
||||||
<component :is="gameComponent" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="tsx">
|
<script setup lang="tsx">
|
||||||
import "@fontsource/roboto-mono";
|
|
||||||
import Error from "components/Error.vue";
|
|
||||||
import { jsx } from "features/feature";
|
import { jsx } from "features/feature";
|
||||||
import state from "game/state";
|
|
||||||
import { coerceComponent, render } from "util/vue";
|
import { coerceComponent, render } from "util/vue";
|
||||||
import { CSSProperties, watch } from "vue";
|
|
||||||
import { computed, toRef, unref } from "vue";
|
import { computed, toRef, unref } from "vue";
|
||||||
import Game from "./components/Game.vue";
|
import Game from "./components/Game.vue";
|
||||||
import GameOverScreen from "./components/GameOverScreen.vue";
|
import GameOverScreen from "./components/GameOverScreen.vue";
|
||||||
|
@ -30,11 +23,12 @@ import projInfo from "./data/projInfo.json";
|
||||||
import themes from "./data/themes";
|
import themes from "./data/themes";
|
||||||
import settings, { gameComponents } from "./game/settings";
|
import settings, { gameComponents } from "./game/settings";
|
||||||
import "./main.css";
|
import "./main.css";
|
||||||
|
import "@fontsource/roboto-mono";
|
||||||
|
import type { CSSProperties } from "vue";
|
||||||
|
|
||||||
const useHeader = projInfo.useHeader;
|
const useHeader = projInfo.useHeader;
|
||||||
const theme = computed(() => themes[settings.theme].variables as CSSProperties);
|
const theme = computed(() => themes[settings.theme].variables as CSSProperties);
|
||||||
const showTPS = toRef(settings, "showTPS");
|
const showTPS = toRef(settings, "showTPS");
|
||||||
const appErrors = toRef(state, "errors");
|
|
||||||
|
|
||||||
const gameComponent = computed(() => {
|
const gameComponent = computed(() => {
|
||||||
return coerceComponent(jsx(() => (<>{gameComponents.map(render)}</>)));
|
return coerceComponent(jsx(() => (<>{gameComponents.map(render)}</>)));
|
||||||
|
@ -57,15 +51,4 @@ const gameComponent = computed(() => {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
color: var(--foreground);
|
color: var(--foreground);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-container {
|
|
||||||
background: var(--background);
|
|
||||||
overflow: auto;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-container > .error {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -1,135 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="error">
|
|
||||||
<h1 class="error-title">{{ firstError.name }}: {{ firstError.message }}</h1>
|
|
||||||
<div class="error-details" style="margin-top: -10px">
|
|
||||||
<div v-if="firstError.cause">
|
|
||||||
<div v-for="row in causes[0]" :key="row">{{ row }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="firstError.stack" :style="firstError.cause ? 'margin-top: 10px' : ''">
|
|
||||||
<div v-for="row in stacks[0]" :key="row">{{ row }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="instructions">
|
|
||||||
Check the console for more details, and consider sharing it with the developers on
|
|
||||||
<a :href="projInfo.discordLink || 'https://discord.gg/yJ4fjnjU54'" class="discord-link"
|
|
||||||
>discord</a
|
|
||||||
>!
|
|
||||||
<FeedbackButton @click="exportSave" class="button" style="display: inline-flex"
|
|
||||||
><span class="material-icons" style="font-size: 16px">content_paste</span
|
|
||||||
><span style="margin-left: 8px; font-size: medium">Copy Save</span></FeedbackButton
|
|
||||||
><br />
|
|
||||||
<div v-if="errors.length > 1" style="margin-top: 20px"><h3>Other errors</h3></div>
|
|
||||||
<div v-for="(error, i) in errors.slice(1)" :key="i" style="margin-top: 20px">
|
|
||||||
<details class="error-details">
|
|
||||||
<summary>{{ error.name }}: {{ error.message }}</summary>
|
|
||||||
<div v-if="error.cause" style="margin-top: 10px">
|
|
||||||
<div v-for="row in causes[i + 1]" :key="row">{{ row }}</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="error.stack" style="margin-top: 10px">
|
|
||||||
<div v-for="row in stacks[i + 1]" :key="row">{{ row }}</div>
|
|
||||||
</div>
|
|
||||||
</details>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import projInfo from "data/projInfo.json";
|
|
||||||
import player, { stringifySave } from "game/player";
|
|
||||||
import LZString from "lz-string";
|
|
||||||
import { computed, onMounted } from "vue";
|
|
||||||
import FeedbackButton from "./fields/FeedbackButton.vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
errors: Error[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const firstError = computed(() => props.errors[0]);
|
|
||||||
const stacks = computed(() =>
|
|
||||||
props.errors.map(error => (error.stack == null ? [] : error.stack.split("\n")))
|
|
||||||
);
|
|
||||||
const causes = computed(() =>
|
|
||||||
props.errors.map(error =>
|
|
||||||
error.cause == null
|
|
||||||
? []
|
|
||||||
: (typeof error.cause === "string" ? error.cause : JSON.stringify(error.cause)).split(
|
|
||||||
"\n"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
function exportSave() {
|
|
||||||
let saveToExport = stringifySave(player);
|
|
||||||
switch (projInfo.exportEncoding) {
|
|
||||||
default:
|
|
||||||
console.warn(`Unknown save encoding: ${projInfo.exportEncoding}. Defaulting to lz`);
|
|
||||||
case "lz":
|
|
||||||
saveToExport = LZString.compressToUTF16(saveToExport);
|
|
||||||
break;
|
|
||||||
case "base64":
|
|
||||||
saveToExport = btoa(unescape(encodeURIComponent(saveToExport)));
|
|
||||||
break;
|
|
||||||
case "plain":
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log(saveToExport);
|
|
||||||
|
|
||||||
// Put on clipboard. Using the clipboard API asks for permissions and stuff
|
|
||||||
const el = document.createElement("textarea");
|
|
||||||
el.value = saveToExport;
|
|
||||||
document.body.appendChild(el);
|
|
||||||
el.select();
|
|
||||||
el.setSelectionRange(0, 99999);
|
|
||||||
document.execCommand("copy");
|
|
||||||
document.body.removeChild(el);
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
player.autosave = false;
|
|
||||||
player.devSpeed = 0;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.error {
|
|
||||||
border: solid 10px var(--danger);
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
text-align: left;
|
|
||||||
min-height: calc(100% - 20px);
|
|
||||||
text-align: left;
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-title {
|
|
||||||
background: var(--danger);
|
|
||||||
color: var(--feature-foreground);
|
|
||||||
display: block;
|
|
||||||
margin: -10px 0 10px 0;
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-details {
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: auto;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: var(--raised-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.instructions {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discord-link {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
summary {
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<ErrorVue v-if="errors.length > 0" :errors="errors" />
|
<div class="layer-container" :style="{ '--layer-color': unref(color) }">
|
||||||
<div class="layer-container" :style="{ '--layer-color': unref(color) }" v-bind="$attrs" v-else>
|
|
||||||
<button v-if="showGoBack" class="goBack" @click="goBack">❌</button>
|
<button v-if="showGoBack" class="goBack" @click="goBack">❌</button>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
@ -29,12 +28,12 @@ import type { CoercableComponent } from "features/feature";
|
||||||
import type { FeatureNode } from "game/layers";
|
import type { FeatureNode } from "game/layers";
|
||||||
import player from "game/player";
|
import player from "game/player";
|
||||||
import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
|
import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
|
||||||
import { PropType, Ref, computed, defineComponent, onErrorCaptured, ref, toRefs, unref } from "vue";
|
import type { PropType, Ref } from "vue";
|
||||||
|
import { computed, defineComponent, toRefs, unref } from "vue";
|
||||||
import Context from "./Context.vue";
|
import Context from "./Context.vue";
|
||||||
import ErrorVue from "./Error.vue";
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: { Context, ErrorVue },
|
components: { Context },
|
||||||
props: {
|
props: {
|
||||||
index: {
|
index: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
@ -78,23 +77,13 @@ export default defineComponent({
|
||||||
props.nodes.value = nodes;
|
props.nodes.value = nodes;
|
||||||
}
|
}
|
||||||
|
|
||||||
const errors = ref<Error[]>([]);
|
|
||||||
onErrorCaptured((err, instance, info) => {
|
|
||||||
console.warn(`Error caught in "${props.name}" layer`, err, instance, info);
|
|
||||||
errors.value.push(
|
|
||||||
err instanceof Error ? (err as Error) : new Error(JSON.stringify(err))
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
component,
|
component,
|
||||||
minimizedComponent,
|
minimizedComponent,
|
||||||
showGoBack,
|
showGoBack,
|
||||||
updateNodes,
|
updateNodes,
|
||||||
unref,
|
unref,
|
||||||
goBack,
|
goBack
|
||||||
errors
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tpsDisplay" v-if="!tps.isNan()">TPS: {{ formatWhole(tps) }}</div>
|
<div class="tpsDisplay" v-if="!tps.isNan()">
|
||||||
|
TPS: {{ formatWhole(tps) }}
|
||||||
|
<transition name="fade"
|
||||||
|
><span v-if="showLow" class="low">{{ formatWhole(low) }}</span></transition
|
||||||
|
>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import state from "game/state";
|
import state from "game/state";
|
||||||
|
import type { DecimalSource } from "util/bignum";
|
||||||
import Decimal, { formatWhole } from "util/bignum";
|
import Decimal, { formatWhole } from "util/bignum";
|
||||||
import { computed } from "vue";
|
import { computed, ref, watchEffect } from "vue";
|
||||||
|
|
||||||
const tps = computed(() =>
|
const tps = computed(() =>
|
||||||
Decimal.div(
|
Decimal.div(
|
||||||
|
@ -13,6 +19,20 @@ const tps = computed(() =>
|
||||||
state.lastTenTicks.reduce((acc, curr) => acc + curr, 0)
|
state.lastTenTicks.reduce((acc, curr) => acc + curr, 0)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const lastTenFPS = ref<number[]>([]);
|
||||||
|
watchEffect(() => {
|
||||||
|
lastTenFPS.value.push(Math.round(tps.value.toNumber()));
|
||||||
|
if (lastTenFPS.value.length > 10) {
|
||||||
|
lastTenFPS.value = lastTenFPS.value.slice(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const low = computed(() =>
|
||||||
|
lastTenFPS.value.reduce<DecimalSource>((acc, curr) => Decimal.max(acc, curr), 0)
|
||||||
|
);
|
||||||
|
|
||||||
|
const showLow = computed(() => Decimal.sub(tps.value, low.value).gt(1));
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -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,10 +133,10 @@ 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.floor(unref(resetButton.conversion.actualGain)).neq(1)
|
||||||
? unref(resetButton.conversion.currentAt)
|
? unref(resetButton.conversion.nextAt)
|
||||||
: unref(resetButton.conversion.nextAt)
|
: unref(resetButton.conversion.currentAt)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{resetButton.conversion.baseResource.displayName}
|
{resetButton.conversion.baseResource.displayName}
|
||||||
</div>
|
</div>
|
||||||
|
@ -460,7 +459,7 @@ export function createFormulaPreview(
|
||||||
const processedShowPreview = convertComputable(showPreview);
|
const processedShowPreview = convertComputable(showPreview);
|
||||||
const processedPreviewAmount = convertComputable(previewAmount);
|
const processedPreviewAmount = convertComputable(previewAmount);
|
||||||
if (!formula.hasVariable()) {
|
if (!formula.hasVariable()) {
|
||||||
console.error("Cannot create formula preview if the formula does not have a variable");
|
throw new Error("Cannot create formula preview if the formula does not have a variable");
|
||||||
}
|
}
|
||||||
return jsx(() => {
|
return jsx(() => {
|
||||||
if (unref(processedShowPreview)) {
|
if (unref(processedShowPreview)) {
|
||||||
|
@ -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)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
GatherProps,
|
GatherProps,
|
||||||
GenericComponent,
|
GenericComponent,
|
||||||
OptionsFunc,
|
OptionsFunc,
|
||||||
Replace,
|
|
||||||
StyleValue,
|
StyleValue,
|
||||||
Visibility,
|
Visibility,
|
||||||
getUniqueID,
|
getUniqueID,
|
||||||
|
@ -30,16 +29,10 @@ import {
|
||||||
} from "game/requirements";
|
} from "game/requirements";
|
||||||
import settings, { registerSettingField } from "game/settings";
|
import settings, { registerSettingField } from "game/settings";
|
||||||
import { camelToTitle } from "util/common";
|
import { camelToTitle } from "util/common";
|
||||||
import type {
|
import { Computable, Defaults, ProcessedFeature, convertComputable } from "util/computed";
|
||||||
Computable,
|
|
||||||
GetComputableType,
|
|
||||||
GetComputableTypeWithDefault,
|
|
||||||
ProcessedComputable
|
|
||||||
} from "util/computed";
|
|
||||||
import { processComputable } from "util/computed";
|
|
||||||
import { createLazyProxy } from "util/proxies";
|
import { createLazyProxy } from "util/proxies";
|
||||||
import { coerceComponent, isCoercableComponent } from "util/vue";
|
import { coerceComponent, isCoercableComponent } from "util/vue";
|
||||||
import { unref, watchEffect } from "vue";
|
import { ComputedRef, unref, watchEffect } from "vue";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
|
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
@ -98,6 +91,8 @@ export interface AchievementOptions {
|
||||||
export interface BaseAchievement {
|
export interface BaseAchievement {
|
||||||
/** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */
|
/** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** Whether this achievement should be visible. */
|
||||||
|
visibility: ComputedRef<Visibility | boolean>;
|
||||||
/** Whether or not this achievement has been earned. */
|
/** Whether or not this achievement has been earned. */
|
||||||
earned: Persistent<boolean>;
|
earned: Persistent<boolean>;
|
||||||
/** A function to complete this achievement. */
|
/** A function to complete this achievement. */
|
||||||
|
@ -110,35 +105,20 @@ export interface BaseAchievement {
|
||||||
[GatherProps]: () => Record<string, unknown>;
|
[GatherProps]: () => Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An object that represents a feature with requirements that is passively earned upon meeting certain requirements. */
|
export type Achievement<T extends AchievementOptions> = BaseAchievement &
|
||||||
export type Achievement<T extends AchievementOptions> = Replace<
|
ProcessedFeature<AchievementOptions, Exclude<T, BaseAchievement>> &
|
||||||
T & BaseAchievement,
|
Defaults<
|
||||||
{
|
Exclude<T, BaseAchievement>,
|
||||||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
{
|
||||||
display: GetComputableType<T["display"]>;
|
showPopups: true;
|
||||||
mark: GetComputableType<T["mark"]>;
|
}
|
||||||
image: GetComputableType<T["image"]>;
|
>;
|
||||||
style: GetComputableType<T["style"]>;
|
|
||||||
classes: GetComputableType<T["classes"]>;
|
|
||||||
showPopups: GetComputableTypeWithDefault<T["showPopups"], true>;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
/** A type that matches any valid {@link Achievement} object. */
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
export type GenericAchievement = Replace<
|
export type GenericAchievement = Achievement<any>;
|
||||||
Achievement<AchievementOptions>,
|
|
||||||
{
|
|
||||||
visibility: ProcessedComputable<Visibility | boolean>;
|
|
||||||
showPopups: ProcessedComputable<boolean>;
|
|
||||||
}
|
|
||||||
>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lazily creates an achievement with the given options.
|
|
||||||
* @param optionsFunc Achievement options.
|
|
||||||
*/
|
|
||||||
export function createAchievement<T extends AchievementOptions>(
|
export function createAchievement<T extends AchievementOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseAchievement, GenericAchievement>,
|
optionsFunc?: OptionsFunc<T, GenericAchievement>,
|
||||||
...decorators: GenericDecorator[]
|
...decorators: GenericDecorator[]
|
||||||
): Achievement<T> {
|
): Achievement<T> {
|
||||||
const earned = persistent<boolean>(false, false);
|
const earned = persistent<boolean>(false, false);
|
||||||
|
@ -147,37 +127,81 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
return createLazyProxy(feature => {
|
return createLazyProxy(feature => {
|
||||||
const achievement =
|
const { visibility, display, mark, small, image, style, classes, showPopups, ...options } =
|
||||||
optionsFunc?.call(feature, feature) ??
|
optionsFunc?.call(feature, feature) ??
|
||||||
({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
({} as ReturnType<NonNullable<typeof optionsFunc>>);
|
||||||
achievement.id = getUniqueID("achievement-");
|
|
||||||
achievement.type = AchievementType;
|
const optionsVisibility = convertComputable(visibility, feature) ?? Visibility.Visible;
|
||||||
achievement[Component] = AchievementComponent as GenericComponent;
|
const processedVisibility = computed(() => {
|
||||||
|
const display = unref(achievement.display);
|
||||||
|
switch (settings.msDisplay) {
|
||||||
|
default:
|
||||||
|
case AchievementDisplay.All:
|
||||||
|
return unref(optionsVisibility);
|
||||||
|
case AchievementDisplay.Configurable:
|
||||||
|
if (
|
||||||
|
unref(achievement.earned) &&
|
||||||
|
!(
|
||||||
|
display != null &&
|
||||||
|
typeof display == "object" &&
|
||||||
|
"optionsDisplay" in (display as Record<string, unknown>)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return Visibility.None;
|
||||||
|
}
|
||||||
|
return unref(optionsVisibility);
|
||||||
|
case AchievementDisplay.Incomplete:
|
||||||
|
if (unref(achievement.earned)) {
|
||||||
|
return Visibility.None;
|
||||||
|
}
|
||||||
|
return unref(optionsVisibility);
|
||||||
|
case AchievementDisplay.None:
|
||||||
|
return Visibility.None;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const achievement = {
|
||||||
|
id: getUniqueID("achievement-"),
|
||||||
|
visibility: processedVisibility,
|
||||||
|
earned,
|
||||||
|
complete,
|
||||||
|
type: AchievementType,
|
||||||
|
[Component]: AchievementComponent as GenericComponent,
|
||||||
|
[GatherProps]: gatherProps,
|
||||||
|
display: convertComputable(display, feature),
|
||||||
|
mark: convertComputable(mark, feature),
|
||||||
|
small: convertComputable(small, feature),
|
||||||
|
image: convertComputable(image, feature),
|
||||||
|
style: convertComputable(style, feature),
|
||||||
|
classes: convertComputable(classes, feature),
|
||||||
|
showPopups: convertComputable(showPopups, feature) ?? true,
|
||||||
|
...options
|
||||||
|
} satisfies Partial<Achievement<T>>;
|
||||||
|
|
||||||
for (const decorator of decorators) {
|
for (const decorator of decorators) {
|
||||||
decorator.preConstruct?.(achievement);
|
decorator.preConstruct?.(achievement);
|
||||||
}
|
}
|
||||||
|
Object.assign(achievement, decoratedData);
|
||||||
|
for (const decorator of decorators) {
|
||||||
|
decorator.postConstruct?.(achievement);
|
||||||
|
}
|
||||||
|
const decoratedProps = decorators.reduce(
|
||||||
|
(current, next) => Object.assign(current, next.getGatheredProps?.(achievement)),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
achievement.earned = earned;
|
function complete() {
|
||||||
achievement.complete = function () {
|
|
||||||
if (earned.value) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
earned.value = true;
|
earned.value = true;
|
||||||
const genericAchievement = achievement as GenericAchievement;
|
achievement.onComplete?.();
|
||||||
genericAchievement.onComplete?.();
|
if (achievement.display != null && unref(achievement.showPopups) === true) {
|
||||||
if (
|
const display = unref(achievement.display);
|
||||||
genericAchievement.display != null &&
|
|
||||||
unref(genericAchievement.showPopups) === true
|
|
||||||
) {
|
|
||||||
const display = unref(genericAchievement.display);
|
|
||||||
let Display;
|
let Display;
|
||||||
if (isCoercableComponent(display)) {
|
if (isCoercableComponent(display)) {
|
||||||
Display = coerceComponent(display);
|
Display = coerceComponent(display);
|
||||||
} else if (display.requirement != null) {
|
} else if (display.requirement != null) {
|
||||||
Display = coerceComponent(display.requirement);
|
Display = coerceComponent(display.requirement);
|
||||||
} else {
|
} else {
|
||||||
Display = displayRequirements(genericAchievement.requirements ?? []);
|
Display = displayRequirements(achievement.requirements ?? []);
|
||||||
}
|
}
|
||||||
toast.info(
|
toast.info(
|
||||||
<div>
|
<div>
|
||||||
|
@ -190,59 +214,9 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
Object.assign(achievement, decoratedData);
|
|
||||||
|
|
||||||
processComputable(achievement as T, "visibility");
|
|
||||||
setDefault(achievement, "visibility", Visibility.Visible);
|
|
||||||
const visibility = achievement.visibility as ProcessedComputable<Visibility | boolean>;
|
|
||||||
achievement.visibility = computed(() => {
|
|
||||||
const display = unref((achievement as GenericAchievement).display);
|
|
||||||
switch (settings.msDisplay) {
|
|
||||||
default:
|
|
||||||
case AchievementDisplay.All:
|
|
||||||
return unref(visibility);
|
|
||||||
case AchievementDisplay.Configurable:
|
|
||||||
if (
|
|
||||||
unref(achievement.earned) &&
|
|
||||||
!(
|
|
||||||
display != null &&
|
|
||||||
typeof display == "object" &&
|
|
||||||
"optionsDisplay" in (display as Record<string, unknown>)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return Visibility.None;
|
|
||||||
}
|
|
||||||
return unref(visibility);
|
|
||||||
case AchievementDisplay.Incomplete:
|
|
||||||
if (unref(achievement.earned)) {
|
|
||||||
return Visibility.None;
|
|
||||||
}
|
|
||||||
return unref(visibility);
|
|
||||||
case AchievementDisplay.None:
|
|
||||||
return Visibility.None;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
processComputable(achievement as T, "display");
|
|
||||||
processComputable(achievement as T, "mark");
|
|
||||||
processComputable(achievement as T, "small");
|
|
||||||
processComputable(achievement as T, "image");
|
|
||||||
processComputable(achievement as T, "style");
|
|
||||||
processComputable(achievement as T, "classes");
|
|
||||||
processComputable(achievement as T, "showPopups");
|
|
||||||
setDefault(achievement, "showPopups", true);
|
|
||||||
|
|
||||||
for (const decorator of decorators) {
|
|
||||||
decorator.postConstruct?.(achievement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoratedProps = decorators.reduce(
|
function gatherProps(this: GenericAchievement): Record<string, unknown> {
|
||||||
(current, next) => Object.assign(current, next.getGatheredProps?.(achievement)),
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
achievement[GatherProps] = function (this: GenericAchievement) {
|
|
||||||
const {
|
const {
|
||||||
visibility,
|
visibility,
|
||||||
display,
|
display,
|
||||||
|
@ -268,13 +242,12 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
id,
|
id,
|
||||||
...decoratedProps
|
...decoratedProps
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
if (achievement.requirements) {
|
if (achievement.requirements != null) {
|
||||||
const genericAchievement = achievement as GenericAchievement;
|
|
||||||
const requirements = [
|
const requirements = [
|
||||||
createVisibilityRequirement(genericAchievement),
|
createVisibilityRequirement(achievement),
|
||||||
createBooleanRequirement(() => !genericAchievement.earned.value),
|
createBooleanRequirement(() => !earned.value),
|
||||||
...(isArray(achievement.requirements)
|
...(isArray(achievement.requirements)
|
||||||
? achievement.requirements
|
? achievement.requirements
|
||||||
: [achievement.requirements])
|
: [achievement.requirements])
|
||||||
|
@ -282,15 +255,35 @@ export function createAchievement<T extends AchievementOptions>(
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (settings.active !== player.id) return;
|
if (settings.active !== player.id) return;
|
||||||
if (requirementsMet(requirements)) {
|
if (requirementsMet(requirements)) {
|
||||||
genericAchievement.complete();
|
achievement.complete();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return achievement as unknown as Achievement<T>;
|
return achievement;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ach = createAchievement(ach => ({
|
||||||
|
image: "",
|
||||||
|
showPopups: computed(() => false),
|
||||||
|
small: () => true,
|
||||||
|
foo: "bar",
|
||||||
|
bar: () => "foo"
|
||||||
|
}));
|
||||||
|
ach;
|
||||||
|
ach.image; // string
|
||||||
|
ach.showPopups; // ComputedRef<false>
|
||||||
|
ach.small; // ComputedRef<true>
|
||||||
|
ach.foo; // "bar"
|
||||||
|
ach.bar; // () => "foo"
|
||||||
|
ach.mark; // TS should yell about this not existing (or at least mark it undefined)
|
||||||
|
ach.visibility; // ComputedRef<Visibility | boolean>
|
||||||
|
|
||||||
|
const badAch = createAchievement(() => ({
|
||||||
|
requirements: "foo"
|
||||||
|
}));
|
||||||
|
|
||||||
declare module "game/settings" {
|
declare module "game/settings" {
|
||||||
interface Settings {
|
interface Settings {
|
||||||
msDisplay: AchievementDisplay;
|
msDisplay: AchievementDisplay;
|
||||||
|
|
|
@ -169,6 +169,7 @@ export function createAction<T extends ActionOptions>(
|
||||||
direction: Direction.Right,
|
direction: Direction.Right,
|
||||||
width: 100,
|
width: 100,
|
||||||
height: 10,
|
height: 10,
|
||||||
|
style: "margin-top: 8px",
|
||||||
borderStyle: "border-color: black",
|
borderStyle: "border-color: black",
|
||||||
baseStyle: "margin-top: -1px",
|
baseStyle: "margin-top: -1px",
|
||||||
progress: () => Decimal.div(progress.value, unref(genericAction.duration)),
|
progress: () => Decimal.div(progress.value, unref(genericAction.duration)),
|
||||||
|
@ -243,9 +244,8 @@ export function createAction<T extends ActionOptions>(
|
||||||
decorator.postConstruct?.(action);
|
decorator.postConstruct?.(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
const decoratedProps = decorators.reduce(
|
const decoratedProps = decorators.reduce((current, next) =>
|
||||||
(current, next) => Object.assign(current, next.getGatheredProps?.(action)),
|
Object.assign(current, next.getGatheredProps?.(action))
|
||||||
{}
|
|
||||||
);
|
);
|
||||||
action[GatherProps] = function (this: GenericAction) {
|
action[GatherProps] = function (this: GenericAction) {
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -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%)";
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
@touchstart="(e: TouchEvent) => mouseDown(e)"
|
@touchstart="(e: TouchEvent) => mouseDown(e)"
|
||||||
@mouseup="() => endDragging(unref(draggingNode))"
|
@mouseup="() => endDragging(unref(draggingNode))"
|
||||||
@touchend.passive="() => endDragging(unref(draggingNode))"
|
@touchend.passive="() => endDragging(unref(draggingNode))"
|
||||||
@mouseleave="() => endDragging(unref(draggingNode), true)"
|
@mouseleave="() => endDragging(unref(draggingNode))"
|
||||||
>
|
>
|
||||||
<svg class="stage" width="100%" height="100%">
|
<svg class="stage" width="100%" height="100%">
|
||||||
<g class="g1">
|
<g class="g1">
|
||||||
|
@ -28,16 +28,7 @@
|
||||||
v-for="link in unref(links) || []"
|
v-for="link in unref(links) || []"
|
||||||
:key="`${link.startNode.id}-${link.endNode.id}`"
|
:key="`${link.startNode.id}-${link.endNode.id}`"
|
||||||
>
|
>
|
||||||
<BoardLinkVue
|
<BoardLinkVue :link="link" />
|
||||||
:link="link"
|
|
||||||
:dragging="unref(draggingNode)"
|
|
||||||
:dragged="
|
|
||||||
link.startNode === unref(draggingNode) ||
|
|
||||||
link.endNode === unref(draggingNode)
|
|
||||||
? dragged
|
|
||||||
: undefined
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</g>
|
</g>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
<transition-group name="grow" :duration="500" appear>
|
<transition-group name="grow" :duration="500" appear>
|
||||||
|
@ -47,12 +38,10 @@
|
||||||
:nodeType="types[node.type]"
|
:nodeType="types[node.type]"
|
||||||
:dragging="unref(draggingNode)"
|
:dragging="unref(draggingNode)"
|
||||||
:dragged="unref(draggingNode) === node ? dragged : undefined"
|
:dragged="unref(draggingNode) === node ? dragged : undefined"
|
||||||
:hasDragged="unref(draggingNode) == null ? false : hasDragged"
|
:hasDragged="hasDragged"
|
||||||
:receivingNode="unref(receivingNode) === node"
|
:receivingNode="unref(receivingNode)?.id === node.id"
|
||||||
:isSelected="unref(selectedNode) === node"
|
:selectedNode="unref(selectedNode)"
|
||||||
:selectedAction="
|
:selectedAction="unref(selectedAction)"
|
||||||
unref(selectedNode) === node ? unref(selectedAction) : null
|
|
||||||
"
|
|
||||||
@mouseDown="mouseDown"
|
@mouseDown="mouseDown"
|
||||||
@endDragging="endDragging"
|
@endDragging="endDragging"
|
||||||
@clickAction="(actionId: string) => clickAction(node, actionId)"
|
@clickAction="(actionId: string) => clickAction(node, actionId)"
|
||||||
|
@ -108,10 +97,6 @@ const stage = ref<any>(null);
|
||||||
|
|
||||||
const sortedNodes = computed(() => {
|
const sortedNodes = computed(() => {
|
||||||
const nodes = props.nodes.value.slice();
|
const nodes = props.nodes.value.slice();
|
||||||
if (props.selectedNode.value) {
|
|
||||||
const node = nodes.splice(nodes.indexOf(props.selectedNode.value), 1)[0];
|
|
||||||
nodes.push(node);
|
|
||||||
}
|
|
||||||
if (props.draggingNode.value) {
|
if (props.draggingNode.value) {
|
||||||
const node = nodes.splice(nodes.indexOf(props.draggingNode.value), 1)[0];
|
const node = nodes.splice(nodes.indexOf(props.draggingNode.value), 1)[0];
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
|
@ -238,7 +223,7 @@ function drag(e: MouseEvent | TouchEvent) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function endDragging(node: BoardNode | null, mouseLeave = false) {
|
function endDragging(node: BoardNode | null) {
|
||||||
if (props.draggingNode.value != null && props.draggingNode.value === node) {
|
if (props.draggingNode.value != null && props.draggingNode.value === node) {
|
||||||
if (props.receivingNode.value == null) {
|
if (props.receivingNode.value == null) {
|
||||||
props.draggingNode.value.position.x += Math.round(dragged.value.x / 25) * 25;
|
props.draggingNode.value.position.x += Math.round(dragged.value.x / 25) * 25;
|
||||||
|
@ -256,7 +241,7 @@ function endDragging(node: BoardNode | null, mouseLeave = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
props.setDraggingNode.value(null);
|
props.setDraggingNode.value(null);
|
||||||
} else if (!hasDragged.value && !mouseLeave) {
|
} else if (!hasDragged.value) {
|
||||||
props.state.value.selectedNode = null;
|
props.state.value.selectedNode = null;
|
||||||
props.state.value.selectedAction = null;
|
props.state.value.selectedAction = null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -11,53 +11,36 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BoardNode, BoardNodeLink } from "features/boards/board";
|
import type { 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<{
|
||||||
link: BoardNodeLink;
|
link: BoardNodeLink;
|
||||||
dragging: BoardNode | null;
|
|
||||||
dragged?: {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
};
|
|
||||||
}>();
|
}>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
|
|
||||||
const startPosition = computed(() => {
|
const startPosition = computed(() => {
|
||||||
const position = { ...props.link.value.startNode.position };
|
const position = props.link.value.startNode.position;
|
||||||
if (props.link.value.offsetStart) {
|
if (props.link.value.offsetStart) {
|
||||||
position.x += unref(props.link.value.offsetStart).x;
|
position.x += unref(props.link.value.offsetStart).x;
|
||||||
position.y += unref(props.link.value.offsetStart).y;
|
position.y += unref(props.link.value.offsetStart).y;
|
||||||
}
|
}
|
||||||
if (props.dragging?.value === props.link.value.startNode) {
|
|
||||||
position.x += props.dragged?.value?.x ?? 0;
|
|
||||||
position.y += props.dragged?.value?.y ?? 0;
|
|
||||||
}
|
|
||||||
return position;
|
return position;
|
||||||
});
|
});
|
||||||
|
|
||||||
const endPosition = computed(() => {
|
const endPosition = computed(() => {
|
||||||
const position = { ...props.link.value.endNode.position };
|
const position = props.link.value.endNode.position;
|
||||||
if (props.link.value.offsetEnd) {
|
if (props.link.value.offsetEnd) {
|
||||||
position.x += unref(props.link.value.offsetEnd).x;
|
position.x += unref(props.link.value.offsetEnd).x;
|
||||||
position.y += unref(props.link.value.offsetEnd).y;
|
position.y += unref(props.link.value.offsetEnd).y;
|
||||||
}
|
}
|
||||||
if (props.dragging?.value === props.link.value.endNode) {
|
|
||||||
position.x += props.dragged?.value?.x ?? 0;
|
|
||||||
position.y += props.dragged?.value?.y ?? 0;
|
|
||||||
}
|
|
||||||
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 {
|
||||||
|
|
|
@ -160,7 +160,7 @@ const _props = defineProps<{
|
||||||
};
|
};
|
||||||
hasDragged?: boolean;
|
hasDragged?: boolean;
|
||||||
receivingNode?: boolean;
|
receivingNode?: boolean;
|
||||||
isSelected: boolean;
|
selectedNode: BoardNode | null;
|
||||||
selectedAction: GenericBoardNodeAction | null;
|
selectedAction: GenericBoardNodeAction | null;
|
||||||
}>();
|
}>();
|
||||||
const props = toRefs(_props);
|
const props = toRefs(_props);
|
||||||
|
@ -170,6 +170,7 @@ const emit = defineEmits<{
|
||||||
(e: "clickAction", actionId: string): void;
|
(e: "clickAction", actionId: string): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const isSelected = computed(() => unref(props.selectedNode) === unref(props.node));
|
||||||
const isDraggable = computed(() =>
|
const isDraggable = computed(() =>
|
||||||
getNodeProperty(props.nodeType.value.draggable, unref(props.node))
|
getNodeProperty(props.nodeType.value.draggable, unref(props.node))
|
||||||
);
|
);
|
||||||
|
@ -210,7 +211,7 @@ const shape = computed(() => getNodeProperty(props.nodeType.value.shape, unref(p
|
||||||
const title = computed(() => getNodeProperty(props.nodeType.value.title, unref(props.node)));
|
const title = computed(() => getNodeProperty(props.nodeType.value.title, unref(props.node)));
|
||||||
const label = computed(
|
const label = computed(
|
||||||
() =>
|
() =>
|
||||||
(props.isSelected.value
|
(isSelected.value
|
||||||
? unref(props.selectedAction) &&
|
? unref(props.selectedAction) &&
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
getNodeProperty(unref(props.selectedAction)!.tooltip, unref(props.node))
|
getNodeProperty(unref(props.selectedAction)!.tooltip, unref(props.node))
|
||||||
|
|
|
@ -104,7 +104,7 @@ export interface NodeTypeOptions {
|
||||||
shape: NodeComputable<Shape>;
|
shape: NodeComputable<Shape>;
|
||||||
/** Whether the node can accept another node being dropped upon it. */
|
/** Whether the node can accept another node being dropped upon it. */
|
||||||
canAccept?: NodeComputable<boolean, [BoardNode]>;
|
canAccept?: NodeComputable<boolean, [BoardNode]>;
|
||||||
/** The progress value of the node, from 0 to 1. */
|
/** The progress value of the node. */
|
||||||
progress?: NodeComputable<number>;
|
progress?: NodeComputable<number>;
|
||||||
/** How the progress should be displayed on the node. */
|
/** How the progress should be displayed on the node. */
|
||||||
progressDisplay?: NodeComputable<ProgressDisplay>;
|
progressDisplay?: NodeComputable<ProgressDisplay>;
|
||||||
|
|
|
@ -52,6 +52,8 @@ export interface ChallengeOptions {
|
||||||
reset?: GenericReset;
|
reset?: GenericReset;
|
||||||
/** The requirement(s) to complete this challenge. */
|
/** The requirement(s) to complete this challenge. */
|
||||||
requirements: Requirements;
|
requirements: Requirements;
|
||||||
|
/** Whether or not completing this challenge should grant multiple completions if requirements met. Requires {@link requirements} to be a requirement or array of requirements with {@link Requirement.canMaximize} true. */
|
||||||
|
maximize?: Computable<boolean>;
|
||||||
/** The maximum number of times the challenge can be completed. */
|
/** The maximum number of times the challenge can be completed. */
|
||||||
completionLimit?: Computable<DecimalSource>;
|
completionLimit?: Computable<DecimalSource>;
|
||||||
/** Shows a marker on the corner of the feature. */
|
/** Shows a marker on the corner of the feature. */
|
||||||
|
@ -122,6 +124,7 @@ export type Challenge<T extends ChallengeOptions> = Replace<
|
||||||
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
visibility: GetComputableTypeWithDefault<T["visibility"], Visibility.Visible>;
|
||||||
canStart: GetComputableTypeWithDefault<T["canStart"], true>;
|
canStart: GetComputableTypeWithDefault<T["canStart"], true>;
|
||||||
requirements: GetComputableType<T["requirements"]>;
|
requirements: GetComputableType<T["requirements"]>;
|
||||||
|
maximize: GetComputableType<T["maximize"]>;
|
||||||
completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>;
|
completionLimit: GetComputableTypeWithDefault<T["completionLimit"], 1>;
|
||||||
mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>;
|
mark: GetComputableTypeWithDefault<T["mark"], Ref<boolean>>;
|
||||||
classes: GetComputableType<T["classes"]>;
|
classes: GetComputableType<T["classes"]>;
|
||||||
|
@ -207,7 +210,10 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
challenge.canComplete = computed(() =>
|
challenge.canComplete = computed(() =>
|
||||||
maxRequirementsMet((challenge as GenericChallenge).requirements)
|
Decimal.max(
|
||||||
|
maxRequirementsMet((challenge as GenericChallenge).requirements),
|
||||||
|
unref((challenge as GenericChallenge).maximize) ? Decimal.dInf : 1
|
||||||
|
)
|
||||||
);
|
);
|
||||||
challenge.complete = function (remainInChallenge?: boolean) {
|
challenge.complete = function (remainInChallenge?: boolean) {
|
||||||
const genericChallenge = challenge as GenericChallenge;
|
const genericChallenge = challenge as GenericChallenge;
|
||||||
|
@ -248,6 +254,7 @@ export function createChallenge<T extends ChallengeOptions>(
|
||||||
|
|
||||||
processComputable(challenge as T, "canStart");
|
processComputable(challenge as T, "canStart");
|
||||||
setDefault(challenge, "canStart", true);
|
setDefault(challenge, "canStart", true);
|
||||||
|
processComputable(challenge as T, "maximize");
|
||||||
processComputable(challenge as T, "completionLimit");
|
processComputable(challenge as T, "completionLimit");
|
||||||
setDefault(challenge, "completionLimit", 1);
|
setDefault(challenge, "completionLimit", 1);
|
||||||
processComputable(challenge as T, "mark");
|
processComputable(challenge as T, "mark");
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CoercableComponent, OptionsFunc, Replace } from "features/feature";
|
import type { OptionsFunc, Replace } from "features/feature";
|
||||||
import { setDefault } from "features/feature";
|
import { setDefault } from "features/feature";
|
||||||
import type { Resource } from "features/resources/resource";
|
import type { Resource } from "features/resources/resource";
|
||||||
import Formula from "game/formulas/formulas";
|
import Formula from "game/formulas/formulas";
|
||||||
|
@ -12,7 +12,6 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
import { GenericDecorator } from "./decorators/common";
|
import { GenericDecorator } from "./decorators/common";
|
||||||
import { createBooleanRequirement } from "game/requirements";
|
|
||||||
|
|
||||||
/** An object that configures a {@link Conversion}. */
|
/** An object that configures a {@link Conversion}. */
|
||||||
export interface ConversionOptions {
|
export interface ConversionOptions {
|
||||||
|
@ -293,20 +292,3 @@ export function setupPassiveGeneration(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates requirement that is met when the conversion hits a specified gain amount
|
|
||||||
* @param conversion The conversion to check the gain amount of
|
|
||||||
* @param minGainAmount The minimum gain amount that must be met for the requirement to be met
|
|
||||||
*/
|
|
||||||
export function createCanConvertRequirement(
|
|
||||||
conversion: GenericConversion,
|
|
||||||
minGainAmount: Computable<DecimalSource> = 1,
|
|
||||||
display?: CoercableComponent
|
|
||||||
) {
|
|
||||||
const computedMinGainAmount = convertComputable(minGainAmount);
|
|
||||||
return createBooleanRequirement(
|
|
||||||
() => Decimal.gte(unref(conversion.actualGain), unref(computedMinGainAmount)),
|
|
||||||
display
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
|
@ -69,12 +69,14 @@ export const bonusAmountDecorator: Decorator<
|
||||||
BaseBonusAmountFeature,
|
BaseBonusAmountFeature,
|
||||||
GenericBonusAmountFeature
|
GenericBonusAmountFeature
|
||||||
> = {
|
> = {
|
||||||
postConstruct(feature) {
|
preConstruct(feature) {
|
||||||
if (feature.amount === undefined) {
|
if (feature.amount === undefined) {
|
||||||
console.error(
|
console.error(
|
||||||
`Decorated feature ${feature.id} does not contain the required 'amount' property"`
|
`Decorated feature ${feature.id} does not contain the required 'amount' property"`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
postConstruct(feature) {
|
||||||
processComputable(feature, "bonusAmount");
|
processComputable(feature, "bonusAmount");
|
||||||
if (feature.totalAmount === undefined) {
|
if (feature.totalAmount === undefined) {
|
||||||
feature.totalAmount = computed(() =>
|
feature.totalAmount = computed(() =>
|
||||||
|
|
|
@ -27,8 +27,8 @@ export type Decorator<
|
||||||
|
|
||||||
export type GenericDecorator = Decorator<unknown>;
|
export type GenericDecorator = Decorator<unknown>;
|
||||||
|
|
||||||
export interface EffectFeatureOptions<T = unknown> {
|
export interface EffectFeatureOptions {
|
||||||
effect: Computable<T>;
|
effect: Computable<unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EffectFeature<T extends EffectFeatureOptions> = Replace<
|
export type EffectFeature<T extends EffectFeatureOptions> = Replace<
|
||||||
|
@ -36,9 +36,9 @@ export type EffectFeature<T extends EffectFeatureOptions> = Replace<
|
||||||
{ effect: GetComputableType<T["effect"]> }
|
{ effect: GetComputableType<T["effect"]> }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type GenericEffectFeature<T = unknown> = Replace<
|
export type GenericEffectFeature = Replace<
|
||||||
EffectFeature<EffectFeatureOptions>,
|
EffectFeature<EffectFeatureOptions>,
|
||||||
{ effect: ProcessedComputable<T> }
|
{ effect: ProcessedComputable<unknown> }
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,9 +42,9 @@ export type Replace<T, S> = S & Omit<T, keyof S>;
|
||||||
* with "this" bound to what the type will eventually be processed into.
|
* with "this" bound to what the type will eventually be processed into.
|
||||||
* Intended for making lazily evaluated objects.
|
* Intended for making lazily evaluated objects.
|
||||||
*/
|
*/
|
||||||
export type OptionsFunc<T, R = unknown, S = R> = (obj: R) => OptionsObject<T, R, S>;
|
export type OptionsFunc<T, R = unknown> = (this: R, obj: R) => OptionsObject<T, R>;
|
||||||
|
|
||||||
export type OptionsObject<T, R = unknown, S = R> = T & Partial<R> & ThisType<T & S>;
|
export type OptionsObject<T, R = unknown> = Exclude<T, R> & Partial<R>;
|
||||||
|
|
||||||
let id = 0;
|
let id = 0;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -67,6 +67,8 @@ export interface RepeatableOptions {
|
||||||
mark?: Computable<boolean | string>;
|
mark?: Computable<boolean | string>;
|
||||||
/** Toggles a smaller design for the feature. */
|
/** Toggles a smaller design for the feature. */
|
||||||
small?: Computable<boolean>;
|
small?: Computable<boolean>;
|
||||||
|
/** Whether or not clicking this repeatable should attempt to maximize amount based on the requirements met. Requires {@link requirements} to be a requirement or array of requirements with {@link Requirement.canMaximize} true. */
|
||||||
|
maximize?: Computable<boolean>;
|
||||||
/** The display to use for this repeatable. */
|
/** The display to use for this repeatable. */
|
||||||
display?: Computable<RepeatableDisplay>;
|
display?: Computable<RepeatableDisplay>;
|
||||||
}
|
}
|
||||||
|
@ -85,6 +87,7 @@ export interface BaseRepeatable {
|
||||||
canClick: ProcessedComputable<boolean>;
|
canClick: ProcessedComputable<boolean>;
|
||||||
/**
|
/**
|
||||||
* How much amount can be increased by, or 1 if unclickable.
|
* How much amount can be increased by, or 1 if unclickable.
|
||||||
|
* Capped at 1 if {@link RepeatableOptions.maximize} is false.
|
||||||
**/
|
**/
|
||||||
amountToIncrease: Ref<DecimalSource>;
|
amountToIncrease: Ref<DecimalSource>;
|
||||||
/** A function that gets called when this repeatable is clicked. */
|
/** A function that gets called when this repeatable is clicked. */
|
||||||
|
@ -108,6 +111,7 @@ export type Repeatable<T extends RepeatableOptions> = Replace<
|
||||||
style: GetComputableType<T["style"]>;
|
style: GetComputableType<T["style"]>;
|
||||||
mark: GetComputableType<T["mark"]>;
|
mark: GetComputableType<T["mark"]>;
|
||||||
small: GetComputableType<T["small"]>;
|
small: GetComputableType<T["small"]>;
|
||||||
|
maximize: GetComputableType<T["maximize"]>;
|
||||||
display: Ref<CoercableComponent>;
|
display: Ref<CoercableComponent>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
@ -158,8 +162,7 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
requiresPay: false,
|
requiresPay: false,
|
||||||
visibility: Visibility.None,
|
visibility: Visibility.None
|
||||||
canMaximize: true
|
|
||||||
} as const;
|
} as const;
|
||||||
const visibilityRequirement = createVisibilityRequirement(repeatable as GenericRepeatable);
|
const visibilityRequirement = createVisibilityRequirement(repeatable as GenericRepeatable);
|
||||||
if (isArray(repeatable.requirements)) {
|
if (isArray(repeatable.requirements)) {
|
||||||
|
@ -191,7 +194,9 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
return currClasses;
|
return currClasses;
|
||||||
});
|
});
|
||||||
repeatable.amountToIncrease = computed(() =>
|
repeatable.amountToIncrease = computed(() =>
|
||||||
Decimal.clampMin(maxRequirementsMet(repeatable.requirements), 1)
|
unref((repeatable as GenericRepeatable).maximize)
|
||||||
|
? maxRequirementsMet(repeatable.requirements)
|
||||||
|
: 1
|
||||||
);
|
);
|
||||||
repeatable.canClick = computed(() => requirementsMet(repeatable.requirements));
|
repeatable.canClick = computed(() => requirementsMet(repeatable.requirements));
|
||||||
const onClick = repeatable.onClick;
|
const onClick = repeatable.onClick;
|
||||||
|
@ -200,12 +205,8 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
if (!unref(genericRepeatable.canClick)) {
|
if (!unref(genericRepeatable.canClick)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const amountToIncrease = unref(repeatable.amountToIncrease) ?? 1;
|
payRequirements(repeatable.requirements, unref(repeatable.amountToIncrease));
|
||||||
payRequirements(repeatable.requirements, amountToIncrease);
|
genericRepeatable.amount.value = Decimal.add(genericRepeatable.amount.value, 1);
|
||||||
genericRepeatable.amount.value = Decimal.add(
|
|
||||||
genericRepeatable.amount.value,
|
|
||||||
amountToIncrease
|
|
||||||
);
|
|
||||||
onClick?.(event);
|
onClick?.(event);
|
||||||
};
|
};
|
||||||
processComputable(repeatable as T, "display");
|
processComputable(repeatable as T, "display");
|
||||||
|
@ -234,10 +235,12 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
{currDisplay.showAmount === false ? null : (
|
{currDisplay.showAmount === false ? null : (
|
||||||
<div>
|
<div>
|
||||||
<br />
|
<br />
|
||||||
<>Amount: {formatWhole(genericRepeatable.amount.value)}</>
|
joinJSX(
|
||||||
{Decimal.isFinite(unref(genericRepeatable.limit)) ? (
|
<>Amount: {formatWhole(genericRepeatable.amount.value)}</>,
|
||||||
|
{unref(genericRepeatable.limit) !== Decimal.dInf ? (
|
||||||
<> / {formatWhole(unref(genericRepeatable.limit))}</>
|
<> / {formatWhole(unref(genericRepeatable.limit))}</>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
|
)
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{currDisplay.effectDisplay == null ? null : (
|
{currDisplay.effectDisplay == null ? null : (
|
||||||
|
@ -268,6 +271,7 @@ export function createRepeatable<T extends RepeatableOptions>(
|
||||||
processComputable(repeatable as T, "style");
|
processComputable(repeatable as T, "style");
|
||||||
processComputable(repeatable as T, "mark");
|
processComputable(repeatable as T, "mark");
|
||||||
processComputable(repeatable as T, "small");
|
processComputable(repeatable as T, "small");
|
||||||
|
processComputable(repeatable as T, "maximize");
|
||||||
|
|
||||||
for (const decorator of decorators) {
|
for (const decorator of decorators) {
|
||||||
decorator.postConstruct?.(repeatable);
|
decorator.postConstruct?.(repeatable);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import type { OptionsFunc, Replace } from "features/feature";
|
import type { OptionsFunc, Replace } from "features/feature";
|
||||||
import { getUniqueID } from "features/feature";
|
import { getUniqueID } from "features/feature";
|
||||||
import { globalBus } from "game/events";
|
import { globalBus } from "game/events";
|
||||||
import Formula from "game/formulas/formulas";
|
|
||||||
import type { BaseLayer } from "game/layers";
|
import type { BaseLayer } from "game/layers";
|
||||||
import { NonPersistent, Persistent, SkipPersistence } from "game/persistence";
|
import type { NonPersistent, Persistent } from "game/persistence";
|
||||||
import { DefaultValue, persistent } from "game/persistence";
|
import { DefaultValue, persistent } from "game/persistence";
|
||||||
import type { Unsubscribe } from "nanoevents";
|
import type { Unsubscribe } from "nanoevents";
|
||||||
import Decimal from "util/bignum";
|
import Decimal from "util/bignum";
|
||||||
|
@ -20,7 +19,7 @@ export const ResetType = Symbol("Reset");
|
||||||
*/
|
*/
|
||||||
export interface ResetOptions {
|
export interface ResetOptions {
|
||||||
/** List of things to reset. Can include objects which will be recursed over for persistent values. */
|
/** List of things to reset. Can include objects which will be recursed over for persistent values. */
|
||||||
thingsToReset: Computable<unknown[]>;
|
thingsToReset: Computable<Record<string, unknown>[]>;
|
||||||
/** A function that is called when the reset is performed. */
|
/** A function that is called when the reset is performed. */
|
||||||
onReset?: VoidFunction;
|
onReset?: VoidFunction;
|
||||||
}
|
}
|
||||||
|
@ -62,15 +61,7 @@ export function createReset<T extends ResetOptions>(
|
||||||
|
|
||||||
reset.reset = function () {
|
reset.reset = function () {
|
||||||
const handleObject = (obj: unknown) => {
|
const handleObject = (obj: unknown) => {
|
||||||
if (
|
if (obj != null && typeof obj === "object") {
|
||||||
obj != null &&
|
|
||||||
typeof obj === "object" &&
|
|
||||||
!(obj instanceof Decimal) &&
|
|
||||||
!(obj instanceof Formula)
|
|
||||||
) {
|
|
||||||
if (SkipPersistence in obj && obj[SkipPersistence] === true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (DefaultValue in obj) {
|
if (DefaultValue in obj) {
|
||||||
const persistent = obj as NonPersistent;
|
const persistent = obj as NonPersistent;
|
||||||
persistent.value = persistent[DefaultValue];
|
persistent.value = persistent[DefaultValue];
|
||||||
|
|
|
@ -151,7 +151,8 @@ export function createTabFamily<T extends TabFamilyOptions>(
|
||||||
optionsFunc?: OptionsFunc<T, BaseTabFamily, GenericTabFamily>
|
optionsFunc?: OptionsFunc<T, BaseTabFamily, GenericTabFamily>
|
||||||
): TabFamily<T> {
|
): TabFamily<T> {
|
||||||
if (Object.keys(tabs).length === 0) {
|
if (Object.keys(tabs).length === 0) {
|
||||||
console.error("Cannot create tab family with 0 tabs");
|
console.warn("Cannot create tab family with 0 tabs");
|
||||||
|
throw new Error("Cannot create tab family with 0 tabs");
|
||||||
}
|
}
|
||||||
|
|
||||||
const selected = persistent(Object.keys(tabs)[0], false);
|
const selected = persistent(Object.keys(tabs)[0], false);
|
||||||
|
|
|
@ -52,7 +52,7 @@ export interface BaseTooltip {
|
||||||
export type Tooltip<T extends TooltipOptions> = Replace<
|
export type Tooltip<T extends TooltipOptions> = Replace<
|
||||||
T & BaseTooltip,
|
T & BaseTooltip,
|
||||||
{
|
{
|
||||||
pinnable: undefined extends T["pinnable"] ? false : T["pinnable"];
|
pinnable: T["pinnable"] extends undefined ? false : T["pinnable"];
|
||||||
pinned: T["pinnable"] extends true ? Ref<boolean> : undefined;
|
pinned: T["pinnable"] extends true ? Ref<boolean> : undefined;
|
||||||
display: GetComputableType<T["display"]>;
|
display: GetComputableType<T["display"]>;
|
||||||
classes: GetComputableType<T["classes"]>;
|
classes: GetComputableType<T["classes"]>;
|
||||||
|
@ -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;
|
||||||
};
|
}
|
||||||
reset.push(...current);
|
return branch.startNode;
|
||||||
current = next;
|
})
|
||||||
|
.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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -137,9 +137,7 @@ export function createUpgrade<T extends UpgradeOptions>(
|
||||||
upgrade.bought = bought;
|
upgrade.bought = bought;
|
||||||
Object.assign(upgrade, decoratedData);
|
Object.assign(upgrade, decoratedData);
|
||||||
|
|
||||||
upgrade.canPurchase = computed(
|
upgrade.canPurchase = computed(() => requirementsMet(upgrade.requirements));
|
||||||
() => !bought.value && requirementsMet(upgrade.requirements)
|
|
||||||
);
|
|
||||||
upgrade.purchase = function () {
|
upgrade.purchase = function () {
|
||||||
const genericUpgrade = upgrade as GenericUpgrade;
|
const genericUpgrade = upgrade as GenericUpgrade;
|
||||||
if (!unref(genericUpgrade.canPurchase)) {
|
if (!unref(genericUpgrade.canPurchase)) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Resource } from "features/resources/resource";
|
||||||
import { NonPersistent } from "game/persistence";
|
import { NonPersistent } from "game/persistence";
|
||||||
import Decimal, { DecimalSource, format } from "util/bignum";
|
import Decimal, { DecimalSource, format } from "util/bignum";
|
||||||
import { Computable, ProcessedComputable, convertComputable } from "util/computed";
|
import { Computable, ProcessedComputable, convertComputable } from "util/computed";
|
||||||
import { Ref, computed, ref, unref } from "vue";
|
import { ComputedRef, Ref, computed, ref, unref } from "vue";
|
||||||
import * as ops from "./operations";
|
import * as ops from "./operations";
|
||||||
import type {
|
import type {
|
||||||
EvaluateFunction,
|
EvaluateFunction,
|
||||||
|
@ -104,7 +104,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
|
|
||||||
private setupConstant({ inputs }: { inputs: [FormulaSource] }): InternalFormulaProperties<T> {
|
private setupConstant({ inputs }: { inputs: [FormulaSource] }): InternalFormulaProperties<T> {
|
||||||
if (inputs.length !== 1) {
|
if (inputs.length !== 1) {
|
||||||
console.error("Evaluate function is required if inputs is not length 1");
|
throw new Error("Evaluate function is required if inputs is not length 1");
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
inputs: inputs as T,
|
inputs: inputs as T,
|
||||||
|
@ -124,14 +124,11 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
|
|
||||||
const innermostVariable = numVariables === 1 ? variable?.innermostVariable : undefined;
|
const innermostVariable = numVariables === 1 ? variable?.innermostVariable : undefined;
|
||||||
|
|
||||||
const invertible = variable?.isInvertible() ?? false;
|
|
||||||
const integrable = variable?.isIntegrable() ?? false;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputs,
|
inputs,
|
||||||
internalEvaluate: evaluate,
|
internalEvaluate: evaluate,
|
||||||
internalInvert: invertible ? invert : undefined,
|
internalInvert: invert,
|
||||||
internalIntegrate: integrable ? integrate : undefined,
|
internalIntegrate: integrate,
|
||||||
internalIntegrateInner: integrateInner,
|
internalIntegrateInner: integrateInner,
|
||||||
applySubstitution,
|
applySubstitution,
|
||||||
innermostVariable,
|
innermostVariable,
|
||||||
|
@ -229,16 +226,15 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
start: Computable<DecimalSource>,
|
start: Computable<DecimalSource>,
|
||||||
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula
|
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula
|
||||||
) {
|
) {
|
||||||
const formula = formulaModifier(Formula.variable(0));
|
const lhsRef = ref<DecimalSource>(0);
|
||||||
|
const formula = formulaModifier(Formula.variable(lhsRef));
|
||||||
const processedStart = convertComputable(start);
|
const processedStart = convertComputable(start);
|
||||||
function evalStep(lhs: DecimalSource) {
|
function evalStep(lhs: DecimalSource) {
|
||||||
if (Decimal.lt(lhs, unref(processedStart))) {
|
if (Decimal.lt(lhs, unref(processedStart))) {
|
||||||
return lhs;
|
return lhs;
|
||||||
}
|
}
|
||||||
return Decimal.add(
|
lhsRef.value = Decimal.sub(lhs, unref(processedStart));
|
||||||
formula.evaluate(Decimal.sub(lhs, unref(processedStart))),
|
return Decimal.add(formula.evaluate(), unref(processedStart));
|
||||||
unref(processedStart)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
function invertStep(value: DecimalSource, lhs: FormulaSource) {
|
function invertStep(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs) && formula.isInvertible()) {
|
if (hasVariable(lhs) && formula.isInvertible()) {
|
||||||
|
@ -250,8 +246,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
}
|
}
|
||||||
return lhs.invert(value);
|
return lhs.invert(value);
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [value],
|
inputs: [value],
|
||||||
|
@ -295,8 +290,7 @@ export abstract class InternalFormula<T extends [FormulaSource] | FormulaSource[
|
||||||
!formula.isInvertible() ||
|
!formula.isInvertible() ||
|
||||||
(elseFormula != null && !elseFormula.isInvertible())
|
(elseFormula != null && !elseFormula.isInvertible())
|
||||||
) {
|
) {
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (unref(processedCondition)) {
|
if (unref(processedCondition)) {
|
||||||
return lhs.invert(formula.invert(value));
|
return lhs.invert(formula.invert(value));
|
||||||
|
@ -345,35 +339,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 +453,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]>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1278,8 +1256,7 @@ export default class Formula<
|
||||||
} else if (this.inputs.length === 1 && this.hasVariable()) {
|
} else if (this.inputs.length === 1 && this.hasVariable()) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
console.error("Cannot invert non-invertible formula");
|
throw new Error("Cannot invert non-invertible formula");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1289,8 +1266,7 @@ export default class Formula<
|
||||||
*/
|
*/
|
||||||
evaluateIntegral(variable?: DecimalSource): DecimalSource {
|
evaluateIntegral(variable?: DecimalSource): DecimalSource {
|
||||||
if (!this.isIntegrable()) {
|
if (!this.isIntegrable()) {
|
||||||
console.error("Cannot evaluate integral of formula without integral");
|
throw new Error("Cannot evaluate integral of formula without integral");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
return this.getIntegralFormula().evaluate(variable);
|
return this.getIntegralFormula().evaluate(variable);
|
||||||
}
|
}
|
||||||
|
@ -1302,8 +1278,7 @@ export default class Formula<
|
||||||
*/
|
*/
|
||||||
invertIntegral(value: DecimalSource): DecimalSource {
|
invertIntegral(value: DecimalSource): DecimalSource {
|
||||||
if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) {
|
if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) {
|
||||||
console.error("Cannot invert integral of formula without invertible integral");
|
throw new Error("Cannot invert integral of formula without invertible integral");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
return (this.getIntegralFormula() as InvertibleFormula).invert(value);
|
return (this.getIntegralFormula() as InvertibleFormula).invert(value);
|
||||||
}
|
}
|
||||||
|
@ -1330,8 +1305,7 @@ export default class Formula<
|
||||||
// We're the complex operation of this formula
|
// We're the complex operation of this formula
|
||||||
stack = [];
|
stack = [];
|
||||||
if (this.internalIntegrate == null) {
|
if (this.internalIntegrate == null) {
|
||||||
console.error("Cannot integrate formula with non-integrable operation");
|
throw new Error("Cannot integrate formula with non-integrable operation");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
let value = this.internalIntegrate.call(this, stack, ...this.inputs);
|
let value = this.internalIntegrate.call(this, stack, ...this.inputs);
|
||||||
stack.forEach(func => (value = func(value)));
|
stack.forEach(func => (value = func(value)));
|
||||||
|
@ -1351,16 +1325,14 @@ export default class Formula<
|
||||||
) {
|
) {
|
||||||
this.integralFormula = this;
|
this.integralFormula = this;
|
||||||
} else {
|
} else {
|
||||||
console.error("Cannot integrate formula without variable");
|
throw new Error("Cannot integrate formula without variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return this.integralFormula;
|
return this.integralFormula;
|
||||||
} else {
|
} else {
|
||||||
// "Inner" part of the formula
|
// "Inner" part of the formula
|
||||||
if (this.applySubstitution == null) {
|
if (this.applySubstitution == null) {
|
||||||
console.error("Cannot have two complex operations in an integrable formula");
|
throw new Error("Cannot have two complex operations in an integrable formula");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
stack.push((variable: GenericFormula) =>
|
stack.push((variable: GenericFormula) =>
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
@ -1377,8 +1349,7 @@ export default class Formula<
|
||||||
) {
|
) {
|
||||||
return this;
|
return this;
|
||||||
} else {
|
} else {
|
||||||
console.error("Cannot integrate formula without variable");
|
throw new Error("Cannot integrate formula without variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1425,70 +1396,58 @@ export function printFormula(formula: FormulaSource): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility for calculating the maximum amount of purchases possible with a given formula and resource. If {@link cumulativeCost} is changed to false, the calculation will be much faster with higher numbers.
|
* Utility for calculating the maximum amount of purchases possible with a given formula and resource. If {@link spendResources} is changed to false, the calculation will be much faster with higher numbers.
|
||||||
* @param formula The formula to use for calculating buy max from
|
* @param formula The formula to use for calculating buy max from
|
||||||
* @param resource The resource used when purchasing (is only read from)
|
* @param resource The resource used when purchasing (is only read from)
|
||||||
* @param cumulativeCost Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards fewer purchases
|
* @param spendResources Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards fewer purchases
|
||||||
* @param directSum How many of the most expensive purchases should be manually summed for better accuracy. If unspecified uses 10 when spending resources and 0 when not
|
* @param summedPurchases How many of the most expensive purchases should be manually summed for better accuracy. If unspecified uses 10 when spending resources and 0 when not
|
||||||
* @param maxBulkAmount Cap on how many can be purchased at once. If equal to 1 or lte to {@link directSum} then the formula does not need to be invertible. Defaults to Infinity.
|
|
||||||
*/
|
*/
|
||||||
export function calculateMaxAffordable(
|
export function calculateMaxAffordable(
|
||||||
formula: GenericFormula,
|
formula: InvertibleFormula,
|
||||||
resource: Resource,
|
resource: Resource,
|
||||||
cumulativeCost: Computable<boolean> = true,
|
spendResources?: true,
|
||||||
directSum?: Computable<number>,
|
summedPurchases?: number
|
||||||
maxBulkAmount: Computable<DecimalSource> = Decimal.dInf
|
): ComputedRef<DecimalSource>;
|
||||||
|
export function calculateMaxAffordable(
|
||||||
|
formula: InvertibleIntegralFormula,
|
||||||
|
resource: Resource,
|
||||||
|
spendResources: Computable<boolean>,
|
||||||
|
summedPurchases?: number
|
||||||
|
): ComputedRef<DecimalSource>;
|
||||||
|
export function calculateMaxAffordable(
|
||||||
|
formula: InvertibleFormula,
|
||||||
|
resource: Resource,
|
||||||
|
spendResources: Computable<boolean> = true,
|
||||||
|
summedPurchases?: number
|
||||||
) {
|
) {
|
||||||
const computedCumulativeCost = convertComputable(cumulativeCost);
|
const computedSpendResources = convertComputable(spendResources);
|
||||||
const computedDirectSum = convertComputable(directSum);
|
|
||||||
const computedmaxBulkAmount = convertComputable(maxBulkAmount);
|
|
||||||
return computed(() => {
|
return computed(() => {
|
||||||
const maxBulkAmount = unref(computedmaxBulkAmount);
|
let affordable;
|
||||||
if (Decimal.eq(maxBulkAmount, 1)) {
|
if (unref(computedSpendResources)) {
|
||||||
return Decimal.gte(resource.value, formula.evaluate()) ? Decimal.dOne : Decimal.dZero;
|
if (!formula.isIntegrable() || !formula.isIntegralInvertible()) {
|
||||||
}
|
throw new Error(
|
||||||
|
"Cannot calculate max affordable of formula with non-invertible integral"
|
||||||
const cumulativeCost = unref(computedCumulativeCost);
|
);
|
||||||
const directSum = unref(computedDirectSum) ?? (cumulativeCost ? 10 : 0);
|
}
|
||||||
let affordable: DecimalSource = 0;
|
affordable = Decimal.floor(
|
||||||
if (Decimal.gt(maxBulkAmount, directSum)) {
|
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
|
||||||
|
).sub(unref(formula.innermostVariable) ?? 0);
|
||||||
|
if (summedPurchases == null) {
|
||||||
|
summedPurchases = 10;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (!formula.isInvertible()) {
|
if (!formula.isInvertible()) {
|
||||||
console.error(
|
throw new Error("Cannot calculate max affordable of non-invertible formula");
|
||||||
"Cannot calculate max affordable of non-invertible formula with more maxBulkAmount than directSum"
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (cumulativeCost) {
|
affordable = Decimal.floor(formula.invert(resource.value));
|
||||||
if (!formula.isIntegralInvertible()) {
|
if (summedPurchases == null) {
|
||||||
console.error(
|
summedPurchases = 0;
|
||||||
"Cannot calculate max affordable of formula with non-invertible integral"
|
|
||||||
);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
affordable = Decimal.floor(
|
|
||||||
formula.invertIntegral(Decimal.add(resource.value, formula.evaluateIntegral()))
|
|
||||||
).sub(unref(formula.innermostVariable) ?? 0);
|
|
||||||
} else {
|
|
||||||
affordable = Decimal.floor(formula.invert(resource.value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
affordable = Decimal.clampMax(affordable, maxBulkAmount);
|
if (summedPurchases > 0 && Decimal.lt(calculateCost(formula, affordable, true, 0), 1e308)) {
|
||||||
if (directSum > 0) {
|
affordable = affordable.sub(summedPurchases).clampMin(0);
|
||||||
const preSumAffordable = affordable;
|
let summedCost = calculateCost(formula, affordable, true, 0);
|
||||||
affordable = Decimal.sub(affordable, directSum).clampMin(0);
|
while (true) {
|
||||||
let summedCost;
|
|
||||||
if (cumulativeCost) {
|
|
||||||
summedCost = calculateCost(formula as InvertibleFormula, affordable, true, 0);
|
|
||||||
} else {
|
|
||||||
summedCost = formula.evaluate(
|
|
||||||
Decimal.add(unref(formula.innermostVariable) ?? 0, affordable)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
while (
|
|
||||||
Decimal.lt(affordable, maxBulkAmount) &&
|
|
||||||
Decimal.lt(affordable, Number.MAX_SAFE_INTEGER) &&
|
|
||||||
Decimal.add(preSumAffordable, 1).gte(affordable)
|
|
||||||
) {
|
|
||||||
const nextCost = formula.evaluate(
|
const nextCost = formula.evaluate(
|
||||||
affordable.add(unref(formula.innermostVariable) ?? 0)
|
affordable.add(unref(formula.innermostVariable) ?? 0)
|
||||||
);
|
);
|
||||||
|
@ -1505,78 +1464,65 @@ export function calculateMaxAffordable(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility for calculating the cost of a formula for a given amount of purchases. If {@link cumulativeCost} is changed to false, the calculation will be much faster with higher numbers.
|
* Utility for calculating the cost of a formula for a given amount of purchases. If {@link spendResources} is changed to false, the calculation will be much faster with higher numbers.
|
||||||
* @param formula The formula to use for calculating buy max from
|
* @param formula The formula to use for calculating buy max from
|
||||||
* @param amountToBuy The amount of purchases to calculate the cost for
|
* @param amountToBuy The amount of purchases to calculate the cost for
|
||||||
* @param cumulativeCost Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards higher cost
|
* @param spendResources Whether or not to count spent resources on each purchase or not. If true, costs will be approximated for performance, skewing towards higher cost
|
||||||
* @param directSum How many purchases to manually sum for improved accuracy. If not specified, defaults to 10 when cost is cumulative and 0 when not
|
* @param summedPurchases How many purchases to manually sum for improved accuracy. If not specified, defaults to 10 when spending resources and 0 when not
|
||||||
*/
|
*/
|
||||||
export function calculateCost(
|
export function calculateCost(
|
||||||
formula: InvertibleFormula,
|
formula: InvertibleFormula,
|
||||||
amountToBuy: DecimalSource,
|
amountToBuy: DecimalSource,
|
||||||
cumulativeCost?: true,
|
spendResources?: true,
|
||||||
directSum?: number
|
summedPurchases?: number
|
||||||
): DecimalSource;
|
): DecimalSource;
|
||||||
export function calculateCost(
|
export function calculateCost(
|
||||||
formula: InvertibleIntegralFormula,
|
formula: InvertibleIntegralFormula,
|
||||||
amountToBuy: DecimalSource,
|
amountToBuy: DecimalSource,
|
||||||
cumulativeCost: boolean,
|
spendResources: boolean,
|
||||||
directSum?: number
|
summedPurchases?: number
|
||||||
): DecimalSource;
|
): DecimalSource;
|
||||||
export function calculateCost(
|
export function calculateCost(
|
||||||
formula: InvertibleFormula,
|
formula: InvertibleFormula,
|
||||||
amountToBuy: DecimalSource,
|
amountToBuy: DecimalSource,
|
||||||
cumulativeCost = true,
|
spendResources = true,
|
||||||
directSum?: number
|
summedPurchases?: number
|
||||||
) {
|
) {
|
||||||
// Single purchase
|
let newValue = Decimal.add(amountToBuy, unref(formula.innermostVariable) ?? 0);
|
||||||
if (Decimal.eq(amountToBuy, 1)) {
|
if (spendResources) {
|
||||||
return formula.evaluate();
|
if (!formula.isIntegrable()) {
|
||||||
}
|
throw new Error(
|
||||||
|
"Cannot calculate cost with spending resources of non-integrable formula"
|
||||||
const origValue = unref(formula.innermostVariable) ?? 0;
|
);
|
||||||
let newValue = Decimal.add(amountToBuy, origValue);
|
|
||||||
const targetValue = newValue;
|
|
||||||
directSum ??= cumulativeCost ? 10 : 0;
|
|
||||||
newValue = newValue.sub(directSum).clampMin(origValue);
|
|
||||||
let cost: DecimalSource = 0;
|
|
||||||
|
|
||||||
// Indirect sum
|
|
||||||
if (Decimal.gt(amountToBuy, directSum)) {
|
|
||||||
if (!formula.isInvertible()) {
|
|
||||||
console.error("Cannot calculate cost with indirect sum of non-invertible formula");
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
if (cumulativeCost) {
|
const targetValue = newValue;
|
||||||
if (!formula.isIntegrable()) {
|
newValue = newValue
|
||||||
console.error(
|
.sub(summedPurchases ?? 10)
|
||||||
"Cannot calculate cost with cumulative cost of non-integrable formula"
|
.clampMin(unref(formula.innermostVariable) ?? 0);
|
||||||
);
|
let cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral());
|
||||||
return 0;
|
if (targetValue.gt(1e308)) {
|
||||||
}
|
// Too large of a number for summedPurchases to make a difference,
|
||||||
cost = Decimal.sub(formula.evaluateIntegral(newValue), formula.evaluateIntegral());
|
// just get the cost and multiply by summed purchases
|
||||||
if (targetValue.gt(1e308)) {
|
return cost.add(Decimal.sub(targetValue, newValue).times(formula.evaluate(newValue)));
|
||||||
// Too large of a number for directSum to make a difference,
|
|
||||||
// just get the cost and multiply by summed purchases
|
|
||||||
return Decimal.add(
|
|
||||||
cost,
|
|
||||||
Decimal.sub(targetValue, newValue).times(formula.evaluate(newValue))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cost = formula.evaluate(newValue);
|
|
||||||
newValue = newValue.add(1);
|
|
||||||
if (targetValue.gt(1e308)) {
|
|
||||||
// Too large of a number for directSum to make a difference,
|
|
||||||
// just get the cost and multiply by summed purchases
|
|
||||||
return Decimal.sub(targetValue, newValue).add(1).times(cost);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) {
|
||||||
|
cost = cost.add(formula.evaluate(i));
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
|
} else {
|
||||||
|
const targetValue = newValue;
|
||||||
|
newValue = newValue
|
||||||
|
.sub(summedPurchases ?? 0)
|
||||||
|
.clampMin(unref(formula.innermostVariable) ?? 0);
|
||||||
|
let cost = formula.evaluate(newValue);
|
||||||
|
if (targetValue.gt(1e308)) {
|
||||||
|
// Too large of a number for summedPurchases to make a difference,
|
||||||
|
// just get the cost and multiply by summed purchases
|
||||||
|
return Decimal.sub(targetValue, newValue).add(1).times(cost);
|
||||||
|
}
|
||||||
|
for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) {
|
||||||
|
cost = Decimal.add(cost, formula.evaluate(i));
|
||||||
|
}
|
||||||
|
return cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Direct sum
|
|
||||||
for (let i = newValue.toNumber(); i < targetValue.toNumber(); i++) {
|
|
||||||
cost = Decimal.add(cost, formula.evaluate(i));
|
|
||||||
}
|
|
||||||
return cost;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,33 +8,21 @@ 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));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateNeg(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
return Formula.neg(lhs.getIntegralFormula(stack));
|
return Formula.neg(lhs.getIntegralFormula(stack));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applySubstitutionNeg(value: GenericFormula) {
|
export function applySubstitutionNeg(value: GenericFormula) {
|
||||||
|
@ -53,28 +35,24 @@ export function invertAdd(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.sub(value, unrefFormulaSource(lhs)));
|
return rhs.invert(Decimal.sub(value, unrefFormulaSource(lhs)));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateAdd(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x);
|
return Formula.times(rhs, lhs.innermostVariable ?? 0).add(x);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x);
|
return Formula.times(lhs, rhs.innermostVariable ?? 0).add(x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateInnerAdd(
|
export function integrateInnerAdd(
|
||||||
|
@ -84,21 +62,18 @@ export function integrateInnerAdd(
|
||||||
) {
|
) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.add(x, rhs);
|
return Formula.add(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.add(x, lhs);
|
return Formula.add(x, lhs);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -107,28 +82,24 @@ export function invertSub(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.sub(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.sub(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateSub(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0));
|
return Formula.sub(x, Formula.times(rhs, lhs.innermostVariable ?? 0));
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x);
|
return Formula.times(lhs, rhs.innermostVariable ?? 0).sub(x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateInnerSub(
|
export function integrateInnerSub(
|
||||||
|
@ -138,21 +109,18 @@ export function integrateInnerSub(
|
||||||
) {
|
) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sub(x, rhs);
|
return Formula.sub(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.sub(x, lhs);
|
return Formula.sub(x, lhs);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -161,28 +129,24 @@ export function invertMul(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.div(value, unrefFormulaSource(lhs)));
|
return rhs.invert(Decimal.div(value, unrefFormulaSource(lhs)));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateMul(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.times(x, rhs);
|
return Formula.times(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.times(x, lhs);
|
return Formula.times(x, lhs);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applySubstitutionMul(
|
export function applySubstitutionMul(
|
||||||
|
@ -195,8 +159,7 @@ export function applySubstitutionMul(
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return Formula.div(value, lhs);
|
return Formula.div(value, lhs);
|
||||||
}
|
}
|
||||||
console.error("Could not apply substitution due to no input being a variable");
|
throw new Error("Could not apply substitution due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -205,28 +168,24 @@ export function invertDiv(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.div(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.div(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateDiv(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.div(x, rhs);
|
return Formula.div(x, rhs);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.div(lhs, x);
|
return Formula.div(lhs, x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applySubstitutionDiv(
|
export function applySubstitutionDiv(
|
||||||
|
@ -239,37 +198,32 @@ export function applySubstitutionDiv(
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return Formula.mul(value, lhs);
|
return Formula.mul(value, lhs);
|
||||||
}
|
}
|
||||||
console.error("Could not apply substitution due to no input being a variable");
|
throw new Error("Could not apply substitution due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertRecip(value: DecimalSource, lhs: FormulaSource) {
|
export function invertRecip(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.recip(value));
|
return lhs.invert(Decimal.recip(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateRecip(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.ln(x);
|
return Formula.ln(x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLog10(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLog10(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.pow10(value));
|
return lhs.invert(Decimal.pow10(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLog10(lhs: DecimalSource) {
|
function internalIntegrateLog10(lhs: DecimalSource) {
|
||||||
|
@ -281,15 +235,13 @@ function internalInvertIntegralLog10(value: DecimalSource, lhs: FormulaSource) {
|
||||||
const numerator = ln10.times(value);
|
const numerator = ln10.times(value);
|
||||||
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack)],
|
inputs: [lhs.getIntegralFormula(stack)],
|
||||||
|
@ -297,8 +249,7 @@ export function integrateLog10(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
invert: internalInvertIntegralLog10
|
invert: internalInvertIntegralLog10
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -307,8 +258,7 @@ export function invertLog(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) {
|
function internalIntegrateLog(lhs: DecimalSource, rhs: DecimalSource) {
|
||||||
|
@ -320,15 +270,13 @@ function internalInvertIntegralLog(value: DecimalSource, lhs: FormulaSource, rhs
|
||||||
const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value);
|
const numerator = Decimal.ln(unrefFormulaSource(rhs)).times(value);
|
||||||
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack), rhs],
|
inputs: [lhs.getIntegralFormula(stack), rhs],
|
||||||
|
@ -336,16 +284,14 @@ export function integrateLog(stack: SubstitutionStack, lhs: FormulaSource, rhs:
|
||||||
invert: internalInvertIntegralLog
|
invert: internalInvertIntegralLog
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLog2(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLog2(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.pow(2, value));
|
return lhs.invert(Decimal.pow(2, value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLog2(lhs: DecimalSource) {
|
function internalIntegrateLog2(lhs: DecimalSource) {
|
||||||
|
@ -357,15 +303,13 @@ function internalInvertIntegralLog2(value: DecimalSource, lhs: FormulaSource) {
|
||||||
const numerator = Decimal.ln(2).times(value);
|
const numerator = Decimal.ln(2).times(value);
|
||||||
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
return lhs.invert(numerator.div(numerator.div(Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack)],
|
inputs: [lhs.getIntegralFormula(stack)],
|
||||||
|
@ -373,16 +317,14 @@ export function integrateLog2(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
invert: internalInvertIntegralLog2
|
invert: internalInvertIntegralLog2
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLn(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLn(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.exp(value));
|
return lhs.invert(Decimal.exp(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function internalIntegrateLn(lhs: DecimalSource) {
|
function internalIntegrateLn(lhs: DecimalSource) {
|
||||||
|
@ -393,15 +335,13 @@ function internalInvertIntegralLn(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw()));
|
return lhs.invert(Decimal.div(value, Decimal.div(value, Math.E).lambertw()));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
return new Formula({
|
return new Formula({
|
||||||
inputs: [lhs.getIntegralFormula(stack)],
|
inputs: [lhs.getIntegralFormula(stack)],
|
||||||
|
@ -409,8 +349,7 @@ export function integrateLn(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
invert: internalInvertIntegralLn
|
invert: internalInvertIntegralLn
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -419,81 +358,70 @@ export function invertPow(value: DecimalSource, lhs: FormulaSource, rhs: Formula
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(lhs))));
|
return rhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(lhs))));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integratePow(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
const pow = Formula.add(rhs, 1);
|
const pow = Formula.add(rhs, 1);
|
||||||
return Formula.pow(x, pow).div(pow);
|
return Formula.pow(x, pow).div(pow);
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
return Formula.pow(lhs, x).div(Formula.ln(lhs));
|
return Formula.pow(lhs, x).div(Formula.ln(lhs));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertPow10(value: DecimalSource, lhs: FormulaSource) {
|
export function invertPow10(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.root(value, 10));
|
return lhs.invert(Decimal.root(value, 10));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integratePow10(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integratePow10(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.pow10(x).div(Formula.ln(10));
|
return Formula.pow10(x).div(Formula.ln(10));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertPowBase(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.ln(value).div(Decimal.ln(unrefFormulaSource(rhs))));
|
return lhs.invert(Decimal.ln(value).div(unrefFormulaSource(rhs)));
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
return rhs.invert(Decimal.root(unrefFormulaSource(lhs), value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integratePowBase(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.pow(rhs, x).div(Formula.ln(rhs));
|
return Formula.pow(rhs, x).div(Formula.ln(rhs));
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
if (!rhs.isIntegrable()) {
|
if (!rhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = rhs.getIntegralFormula(stack);
|
const x = rhs.getIntegralFormula(stack);
|
||||||
const denominator = Formula.add(lhs, 1);
|
const denominator = Formula.add(lhs, 1);
|
||||||
return Formula.pow(x, denominator).div(denominator);
|
return Formula.pow(x, denominator).div(denominator);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
|
@ -502,42 +430,36 @@ export function invertRoot(value: DecimalSource, lhs: FormulaSource, rhs: Formul
|
||||||
} else if (hasVariable(rhs)) {
|
} else if (hasVariable(rhs)) {
|
||||||
return rhs.invert(Decimal.ln(unrefFormulaSource(lhs)).div(Decimal.ln(value)));
|
return rhs.invert(Decimal.ln(unrefFormulaSource(lhs)).div(Decimal.ln(value)));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
export function integrateRoot(stack: SubstitutionStack, lhs: FormulaSource, rhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1));
|
return Formula.pow(x, Formula.recip(rhs).add(1)).times(rhs).div(Formula.add(rhs, 1));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertExp(value: DecimalSource, lhs: FormulaSource) {
|
export function invertExp(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.ln(value));
|
return lhs.invert(Decimal.ln(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateExp(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.exp(x);
|
return Formula.exp(x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tetrate(
|
export function tetrate(
|
||||||
|
@ -559,8 +481,7 @@ export function invertTetrate(
|
||||||
return base.invert(Decimal.ssqrt(value));
|
return base.invert(Decimal.ssqrt(value));
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function iteratedexp(
|
export function iteratedexp(
|
||||||
|
@ -588,8 +509,7 @@ export function invertIteratedExp(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function iteratedLog(
|
export function iteratedLog(
|
||||||
|
@ -613,8 +533,7 @@ export function invertSlog(value: DecimalSource, lhs: FormulaSource, rhs: Formul
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function layeradd(value: DecimalSource, diff: DecimalSource, base: DecimalSource) {
|
export function layeradd(value: DecimalSource, diff: DecimalSource, base: DecimalSource) {
|
||||||
|
@ -637,24 +556,21 @@ export function invertLayeradd(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Other params can't be inverted ATM
|
// Other params can't be inverted ATM
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertLambertw(value: DecimalSource, lhs: FormulaSource) {
|
export function invertLambertw(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.pow(Math.E, value).times(value));
|
return lhs.invert(Decimal.pow(Math.E, value).times(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertSsqrt(value: DecimalSource, lhs: FormulaSource) {
|
export function invertSsqrt(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.tetrate(value, 2));
|
return lhs.invert(Decimal.tetrate(value, 2));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function pentate(value: DecimalSource, height: DecimalSource, payload: DecimalSource) {
|
export function pentate(value: DecimalSource, height: DecimalSource, payload: DecimalSource) {
|
||||||
|
@ -666,262 +582,226 @@ export function invertSin(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.asin(value));
|
return lhs.invert(Decimal.asin(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateSin(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cos(x).neg();
|
return Formula.cos(x).neg();
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertCos(value: DecimalSource, lhs: FormulaSource) {
|
export function invertCos(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.acos(value));
|
return lhs.invert(Decimal.acos(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateCos(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sin(x);
|
return Formula.sin(x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertTan(value: DecimalSource, lhs: FormulaSource) {
|
export function invertTan(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.atan(value));
|
return lhs.invert(Decimal.atan(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateTan(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cos(x).ln().neg();
|
return Formula.cos(x).ln().neg();
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAsin(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAsin(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.sin(value));
|
return lhs.invert(Decimal.sin(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAsin(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.asin(x)
|
return Formula.asin(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
.add(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAcos(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAcos(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.cos(value));
|
return lhs.invert(Decimal.cos(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAcos(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.acos(x)
|
return Formula.acos(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
.sub(Formula.sqrt(Formula.sub(1, Formula.pow(x, 2))));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAtan(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAtan(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.tan(value));
|
return lhs.invert(Decimal.tan(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAtan(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.atan(x)
|
return Formula.atan(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2));
|
.sub(Formula.ln(Formula.pow(x, 2).add(1)).div(2));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertSinh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertSinh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.asinh(value));
|
return lhs.invert(Decimal.asinh(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateSinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cosh(x);
|
return Formula.cosh(x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertCosh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertCosh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.acosh(value));
|
return lhs.invert(Decimal.acosh(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateCosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.sinh(x);
|
return Formula.sinh(x);
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertTanh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertTanh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.atanh(value));
|
return lhs.invert(Decimal.atanh(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateTanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.cosh(x).ln();
|
return Formula.cosh(x).ln();
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAsinh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAsinh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.sinh(value));
|
return lhs.invert(Decimal.sinh(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAsinh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt());
|
return Formula.asinh(x).times(x).sub(Formula.pow(x, 2).add(1).sqrt());
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAcosh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAcosh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.cosh(value));
|
return lhs.invert(Decimal.cosh(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAcosh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.acosh(x)
|
return Formula.acosh(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt()));
|
.sub(Formula.add(x, 1).sqrt().times(Formula.sub(x, 1).sqrt()));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invertAtanh(value: DecimalSource, lhs: FormulaSource) {
|
export function invertAtanh(value: DecimalSource, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
return lhs.invert(Decimal.tanh(value));
|
return lhs.invert(Decimal.tanh(value));
|
||||||
}
|
}
|
||||||
console.error("Could not invert due to no input being a variable");
|
throw new Error("Could not invert due to no input being a variable");
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
export function integrateAtanh(stack: SubstitutionStack, lhs: FormulaSource) {
|
||||||
if (hasVariable(lhs)) {
|
if (hasVariable(lhs)) {
|
||||||
if (!lhs.isIntegrable()) {
|
if (!lhs.isIntegrable()) {
|
||||||
console.error("Could not integrate due to variable not being integrable");
|
throw new Error("Could not integrate due to variable not being integrable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
const x = lhs.getIntegralFormula(stack);
|
const x = lhs.getIntegralFormula(stack);
|
||||||
return Formula.atanh(x)
|
return Formula.atanh(x)
|
||||||
.times(x)
|
.times(x)
|
||||||
.add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2));
|
.add(Formula.sub(1, Formula.pow(x, 2)).ln().div(2));
|
||||||
}
|
}
|
||||||
console.error("Could not integrate due to no input being a variable");
|
throw new Error("Could not integrate due to no input being a variable");
|
||||||
return Formula.constant(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createPassthroughBinaryFormula(
|
export function createPassthroughBinaryFormula(
|
||||||
|
|
|
@ -225,9 +225,7 @@ export function createLayer<T extends LayerOptions>(
|
||||||
addingLayers[addingLayers.length - 1] == null ||
|
addingLayers[addingLayers.length - 1] == null ||
|
||||||
addingLayers[addingLayers.length - 1] !== id
|
addingLayers[addingLayers.length - 1] !== id
|
||||||
) {
|
) {
|
||||||
throw new Error(
|
throw `Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`;
|
||||||
`Adding layers stack in invalid state. This should not happen\nStack: ${addingLayers}\nTrying to pop ${layer.id}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
addingLayers.pop();
|
addingLayers.pop();
|
||||||
|
|
||||||
|
|
|
@ -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> = T extends undefined
|
||||||
>;
|
? S extends undefined
|
||||||
|
? Omit<WithRequired<Modifier, "invert" | "getFormula">, "description" | "enabled">
|
||||||
|
: Omit<WithRequired<Modifier, "invert" | "enabled" | "getFormula">, "description">
|
||||||
|
: S extends undefined
|
||||||
|
? 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
|
|
||||||
return curr.getFormula!(acc);
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
return Formula.if(acc, curr.enabled, acc => curr.getFormula!(acc));
|
.reduce((acc, curr) => curr.getFormula!(acc), gain)
|
||||||
}, 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}. */
|
||||||
|
|
|
@ -9,7 +9,6 @@ import type { Ref, WritableComputedRef } from "vue";
|
||||||
import { computed, isReactive, isRef, ref } from "vue";
|
import { computed, isReactive, isRef, ref } from "vue";
|
||||||
import player from "./player";
|
import player from "./player";
|
||||||
import state from "./state";
|
import state from "./state";
|
||||||
import Formula from "./formulas/formulas";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A symbol used in {@link Persistent} objects.
|
* A symbol used in {@link Persistent} objects.
|
||||||
|
@ -116,7 +115,12 @@ function checkNaNAndWrite<T extends State>(persistent: Persistent<T>, value: T)
|
||||||
state.NaNPath = persistent[SaveDataPath];
|
state.NaNPath = persistent[SaveDataPath];
|
||||||
state.NaNPersistent = persistent as Persistent<DecimalSource>;
|
state.NaNPersistent = persistent as Persistent<DecimalSource>;
|
||||||
}
|
}
|
||||||
console.error(`Attempted to save NaN value to ${persistent[SaveDataPath]?.join(".")}`);
|
console.error(
|
||||||
|
`Attempted to save NaN value to`,
|
||||||
|
persistent[SaveDataPath]?.join("."),
|
||||||
|
persistent
|
||||||
|
);
|
||||||
|
throw new Error("Attempted to set NaN value. See above for details");
|
||||||
}
|
}
|
||||||
persistent[PersistentState].value = value;
|
persistent[PersistentState].value = value;
|
||||||
}
|
}
|
||||||
|
@ -287,8 +291,8 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
"."
|
"."
|
||||||
)}\` when it's already present at \`${value[SaveDataPath].join(
|
)}\` when it's already present at \`${value[SaveDataPath].join(
|
||||||
"."
|
"."
|
||||||
)}\`.`,
|
)}\`. This can cause unexpected behavior when loading saves between updates.`,
|
||||||
"This can cause unexpected behavior when loading saves between updates."
|
value
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
value[SaveDataPath] = newPath;
|
value[SaveDataPath] = newPath;
|
||||||
|
@ -321,7 +325,6 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
!(value instanceof Decimal) &&
|
!(value instanceof Decimal) &&
|
||||||
!(value instanceof Formula) &&
|
|
||||||
!isRef(value) &&
|
!isRef(value) &&
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
!features.includes(value as { type: typeof Symbol })
|
!features.includes(value as { type: typeof Symbol })
|
||||||
|
@ -363,9 +366,9 @@ globalBus.on("addLayer", (layer: GenericLayer, saveData: Record<string, unknown>
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error(
|
console.error(
|
||||||
`Created persistent ref in ${layer.id} without registering it to the layer!`,
|
`Created persistent ref in ${layer.id} without registering it to the layer! Make sure to include everything persistent in the returned object`,
|
||||||
"Make sure to include everything persistent in the returned object.\n\nCreated at:\n" +
|
persistent,
|
||||||
persistent[StackTrace]
|
"\nCreated at:\n" + persistent[StackTrace]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
persistentRefs[layer.id].clear();
|
persistentRefs[layer.id].clear();
|
||||||
|
|
|
@ -20,7 +20,7 @@ import { createLazyProxy } from "util/proxies";
|
||||||
import { joinJSX, renderJSX } from "util/vue";
|
import { joinJSX, renderJSX } from "util/vue";
|
||||||
import { computed, unref } from "vue";
|
import { computed, unref } from "vue";
|
||||||
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
import Formula, { calculateCost, calculateMaxAffordable } from "./formulas/formulas";
|
||||||
import type { GenericFormula } from "./formulas/types";
|
import type { GenericFormula, InvertibleFormula } from "./formulas/types";
|
||||||
import { DefaultValue, Persistent } from "./persistence";
|
import { DefaultValue, Persistent } from "./persistence";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -86,15 +86,7 @@ export interface CostRequirementOptions {
|
||||||
* When calculating multiple levels to be handled at once, whether it should consider resources used for each level as spent. Setting this to false causes calculations to be faster with larger numbers and supports more math functions.
|
* When calculating multiple levels to be handled at once, whether it should consider resources used for each level as spent. Setting this to false causes calculations to be faster with larger numbers and supports more math functions.
|
||||||
* @see {Formula}
|
* @see {Formula}
|
||||||
*/
|
*/
|
||||||
cumulativeCost?: Computable<boolean>;
|
spendResources?: Computable<boolean>;
|
||||||
/**
|
|
||||||
* Upper limit on levels that can be performed at once. Defaults to 1.
|
|
||||||
*/
|
|
||||||
maxBulkAmount?: Computable<DecimalSource>;
|
|
||||||
/**
|
|
||||||
* When calculating requirement for multiple levels, how many should be directly summed for increase accuracy. High numbers can cause lag. Defaults to 10 if cumulative cost, 0 otherwise.
|
|
||||||
*/
|
|
||||||
directSum?: Computable<number>;
|
|
||||||
/**
|
/**
|
||||||
* Pass-through to {@link Requirement.pay}. May be required for maximizing support.
|
* Pass-through to {@link Requirement.pay}. May be required for maximizing support.
|
||||||
* @see {@link cost} for restrictions on maximizing support.
|
* @see {@link cost} for restrictions on maximizing support.
|
||||||
|
@ -108,7 +100,7 @@ export type CostRequirement = Replace<
|
||||||
cost: ProcessedComputable<DecimalSource> | GenericFormula;
|
cost: ProcessedComputable<DecimalSource> | GenericFormula;
|
||||||
visibility: ProcessedComputable<Visibility.Visible | Visibility.None | boolean>;
|
visibility: ProcessedComputable<Visibility.Visible | Visibility.None | boolean>;
|
||||||
requiresPay: ProcessedComputable<boolean>;
|
requiresPay: ProcessedComputable<boolean>;
|
||||||
cumulativeCost: ProcessedComputable<boolean>;
|
spendResources: ProcessedComputable<boolean>;
|
||||||
canMaximize: ProcessedComputable<boolean>;
|
canMaximize: ProcessedComputable<boolean>;
|
||||||
}
|
}
|
||||||
>;
|
>;
|
||||||
|
@ -134,12 +126,7 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
||||||
{displayResource(
|
{displayResource(
|
||||||
req.resource,
|
req.resource,
|
||||||
req.cost instanceof Formula
|
req.cost instanceof Formula
|
||||||
? calculateCost(
|
? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean)
|
||||||
req.cost,
|
|
||||||
amount ?? 1,
|
|
||||||
unref(req.cumulativeCost) as boolean,
|
|
||||||
unref(req.directSum) as number
|
|
||||||
)
|
|
||||||
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{req.resource.displayName}
|
{req.resource.displayName}
|
||||||
|
@ -151,12 +138,7 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
||||||
{displayResource(
|
{displayResource(
|
||||||
req.resource,
|
req.resource,
|
||||||
req.cost instanceof Formula
|
req.cost instanceof Formula
|
||||||
? calculateCost(
|
? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean)
|
||||||
req.cost,
|
|
||||||
amount ?? 1,
|
|
||||||
unref(req.cumulativeCost) as boolean,
|
|
||||||
unref(req.directSum) as number
|
|
||||||
)
|
|
||||||
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
: unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{req.resource.displayName}
|
{req.resource.displayName}
|
||||||
|
@ -168,62 +150,54 @@ export function createCostRequirement<T extends CostRequirementOptions>(
|
||||||
processComputable(req as T, "cost");
|
processComputable(req as T, "cost");
|
||||||
processComputable(req as T, "requiresPay");
|
processComputable(req as T, "requiresPay");
|
||||||
setDefault(req, "requiresPay", true);
|
setDefault(req, "requiresPay", true);
|
||||||
processComputable(req as T, "cumulativeCost");
|
processComputable(req as T, "spendResources");
|
||||||
setDefault(req, "cumulativeCost", true);
|
setDefault(req, "spendResources", true);
|
||||||
processComputable(req as T, "maxBulkAmount");
|
|
||||||
setDefault(req, "maxBulkAmount", 1);
|
|
||||||
processComputable(req as T, "directSum");
|
|
||||||
setDefault(req, "pay", function (amount?: DecimalSource) {
|
setDefault(req, "pay", function (amount?: DecimalSource) {
|
||||||
const cost =
|
const cost =
|
||||||
req.cost instanceof Formula
|
req.cost instanceof Formula
|
||||||
? calculateCost(
|
? calculateCost(req.cost, amount ?? 1, unref(req.spendResources) as boolean)
|
||||||
req.cost,
|
|
||||||
amount ?? 1,
|
|
||||||
unref(req.cumulativeCost) as boolean,
|
|
||||||
unref(req.directSum) as number
|
|
||||||
)
|
|
||||||
: unref(req.cost as ProcessedComputable<DecimalSource>);
|
: unref(req.cost as ProcessedComputable<DecimalSource>);
|
||||||
req.resource.value = Decimal.sub(req.resource.value, cost).max(0);
|
req.resource.value = Decimal.sub(req.resource.value, cost).max(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
req.canMaximize = computed(() => {
|
req.canMaximize = computed(
|
||||||
if (!(req.cost instanceof Formula)) {
|
() =>
|
||||||
return false;
|
req.cost instanceof Formula &&
|
||||||
}
|
req.cost.isInvertible() &&
|
||||||
const maxBulkAmount = unref(req.maxBulkAmount as ProcessedComputable<DecimalSource>);
|
(unref(req.spendResources) === false || req.cost.isIntegrable())
|
||||||
if (Decimal.lte(maxBulkAmount, 1)) {
|
);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const cumulativeCost = unref(req.cumulativeCost as ProcessedComputable<boolean>);
|
|
||||||
const directSum =
|
|
||||||
unref(req.directSum as ProcessedComputable<number>) ?? (cumulativeCost ? 10 : 0);
|
|
||||||
if (Decimal.lte(maxBulkAmount, directSum)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!req.cost.isInvertible()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (cumulativeCost === true && !req.cost.isIntegrable()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (req.cost instanceof Formula) {
|
if (req.cost instanceof Formula && req.cost.isInvertible()) {
|
||||||
req.requirementMet = calculateMaxAffordable(
|
const maxAffordable = calculateMaxAffordable(
|
||||||
req.cost,
|
req.cost,
|
||||||
req.resource,
|
req.resource,
|
||||||
req.cumulativeCost ?? true,
|
unref(req.spendResources) as boolean
|
||||||
req.directSum,
|
|
||||||
req.maxBulkAmount
|
|
||||||
);
|
);
|
||||||
|
req.requirementMet = computed(() => {
|
||||||
|
if (unref(req.canMaximize)) {
|
||||||
|
return maxAffordable.value;
|
||||||
|
} else {
|
||||||
|
if (req.cost instanceof Formula) {
|
||||||
|
return Decimal.gte(req.resource.value, req.cost.evaluate());
|
||||||
|
} else {
|
||||||
|
return Decimal.gte(
|
||||||
|
req.resource.value,
|
||||||
|
unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
req.requirementMet = computed(() =>
|
req.requirementMet = computed(() => {
|
||||||
Decimal.gte(
|
if (req.cost instanceof Formula) {
|
||||||
req.resource.value,
|
return Decimal.gte(req.resource.value, req.cost.evaluate());
|
||||||
unref(req.cost as ProcessedComputable<DecimalSource>)
|
} else {
|
||||||
) ? 1 : 0
|
return Decimal.gte(
|
||||||
);
|
req.resource.value,
|
||||||
|
unref(req.cost as ProcessedComputable<DecimalSource>)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return req as CostRequirement;
|
return req as CostRequirement;
|
||||||
|
@ -354,7 +328,7 @@ export function payByDivision(this: CostRequirement, amount?: DecimalSource) {
|
||||||
? calculateCost(
|
? calculateCost(
|
||||||
this.cost,
|
this.cost,
|
||||||
amount ?? 1,
|
amount ?? 1,
|
||||||
unref(this.cumulativeCost as ProcessedComputable<boolean> | undefined) ?? true
|
unref(this.spendResources as ProcessedComputable<boolean> | undefined) ?? true
|
||||||
)
|
)
|
||||||
: unref(this.cost as ProcessedComputable<DecimalSource>);
|
: unref(this.cost as ProcessedComputable<DecimalSource>);
|
||||||
this.resource.value = Decimal.div(this.resource.value, cost);
|
this.resource.value = Decimal.div(this.resource.value, cost);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type { DecimalSource } from "util/bignum";
|
import type { DecimalSource } from "util/bignum";
|
||||||
import { reactive, shallowReactive } from "vue";
|
import { shallowReactive } from "vue";
|
||||||
import type { Persistent } from "./persistence";
|
import type { Persistent } from "./persistence";
|
||||||
|
|
||||||
/** An object of global data that is not persistent. */
|
/** An object of global data that is not persistent. */
|
||||||
|
@ -12,8 +12,6 @@ export interface Transient {
|
||||||
NaNPath?: string[];
|
NaNPath?: string[];
|
||||||
/** The ref that was being set to NaN. */
|
/** The ref that was being set to NaN. */
|
||||||
NaNPersistent?: Persistent<DecimalSource>;
|
NaNPersistent?: Persistent<DecimalSource>;
|
||||||
/** List of errors that have occurred, to show the user. */
|
|
||||||
errors: Error[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -26,6 +24,5 @@ declare global {
|
||||||
export default window.state = shallowReactive<Transient>({
|
export default window.state = shallowReactive<Transient>({
|
||||||
lastTenTicks: [],
|
lastTenTicks: [],
|
||||||
hasNaN: false,
|
hasNaN: false,
|
||||||
NaNPath: [],
|
NaNPath: []
|
||||||
errors: reactive([])
|
|
||||||
});
|
});
|
||||||
|
|
29
src/main.ts
29
src/main.ts
|
@ -2,7 +2,6 @@ import "@fontsource/material-icons";
|
||||||
import App from "App.vue";
|
import App from "App.vue";
|
||||||
import projInfo from "data/projInfo.json";
|
import projInfo from "data/projInfo.json";
|
||||||
import "game/notifications";
|
import "game/notifications";
|
||||||
import state from "game/state";
|
|
||||||
import { load } from "util/save";
|
import { load } from "util/save";
|
||||||
import { useRegisterSW } from "virtual:pwa-register/vue";
|
import { useRegisterSW } from "virtual:pwa-register/vue";
|
||||||
import type { App as VueApp } from "vue";
|
import type { App as VueApp } from "vue";
|
||||||
|
@ -24,32 +23,11 @@ declare global {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const error = console.error;
|
|
||||||
console.error = function (...args) {
|
|
||||||
if (import.meta.env.DEV) {
|
|
||||||
state.errors.push(new Error(args[0], { cause: args[1] }));
|
|
||||||
}
|
|
||||||
error(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.onerror = function (event, source, lineno, colno, err) {
|
|
||||||
state.errors.push(err instanceof Error ? err : new Error(JSON.stringify(err)));
|
|
||||||
error(err);
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
window.onunhandledrejection = function (event) {
|
|
||||||
state.errors.push(
|
|
||||||
event.reason instanceof Error ? event.reason : new Error(JSON.stringify(event.reason))
|
|
||||||
);
|
|
||||||
error(event.reason);
|
|
||||||
};
|
|
||||||
|
|
||||||
document.title = projInfo.title;
|
document.title = projInfo.title;
|
||||||
window.projInfo = projInfo;
|
window.projInfo = projInfo;
|
||||||
if (projInfo.id === "") {
|
if (projInfo.id === "") {
|
||||||
console.error(
|
throw new Error(
|
||||||
"Project ID is empty!",
|
"Project ID is empty! Please select a unique ID for this project in /src/data/projInfo.json"
|
||||||
"Please select a unique ID for this project in /src/data/projInfo.json"
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,9 +43,6 @@ requestAnimationFrame(async () => {
|
||||||
|
|
||||||
// Create Vue
|
// Create Vue
|
||||||
const vue = (window.vue = createApp(App));
|
const vue = (window.vue = createApp(App));
|
||||||
vue.config.errorHandler = function (err, instance, info) {
|
|
||||||
console.error(err, info, instance);
|
|
||||||
};
|
|
||||||
globalBus.emit("setupVue", vue);
|
globalBus.emit("setupVue", vue);
|
||||||
vue.mount("#app");
|
vue.mount("#app");
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { JSXFunction } from "features/feature";
|
import type { JSXFunction } from "features/feature";
|
||||||
import { isFunction } from "util/common";
|
import { isFunction } from "util/common";
|
||||||
import type { Ref } from "vue";
|
import type { ComputedRef, Ref } from "vue";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
|
|
||||||
export const DoNotCache = Symbol("DoNotCache");
|
export const DoNotCache = Symbol("DoNotCache");
|
||||||
|
@ -26,32 +26,71 @@ export type ComputableKeysOf<T> = Pick<
|
||||||
}[keyof T]
|
}[keyof T]
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type ProcessedFeature<T, S extends T> = {
|
||||||
|
[K in keyof S]: K extends keyof T
|
||||||
|
? T[K] extends Computable<infer R>
|
||||||
|
? S[K] extends () => infer Q
|
||||||
|
? ComputedRef<Q>
|
||||||
|
: S[K]
|
||||||
|
: S[K]
|
||||||
|
: S[K];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Defaults<T, S> = {
|
||||||
|
[K in keyof S]: K extends keyof T ? (T[K] extends undefined ? S[K] : T[K]) : S[K];
|
||||||
|
};
|
||||||
|
|
||||||
// TODO fix the typing of this function, such that casting isn't necessary and can be used to
|
// TODO fix the typing of this function, such that casting isn't necessary and can be used to
|
||||||
// detect if a createX function is validly written
|
// detect if a createX function is validly written
|
||||||
export function processComputable<T, S extends keyof ComputableKeysOf<T>>(
|
// export function processComputable<
|
||||||
obj: T,
|
// T extends object,
|
||||||
key: S
|
// S extends keyof {
|
||||||
): asserts obj is T & { [K in S]: ProcessedComputable<UnwrapComputableType<T[S]>> } {
|
// [K in keyof T]: T[K] extends Computable<unknown> ? K : never;
|
||||||
const computable = obj[key];
|
// },
|
||||||
if (
|
// R = T[S]
|
||||||
isFunction(computable) &&
|
// >(
|
||||||
computable.length === 0 &&
|
// obj: T,
|
||||||
!(computable as unknown as JSXFunction)[DoNotCache]
|
// key: S
|
||||||
) {
|
// ): asserts obj is {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// [K in keyof T]: K extends keyof S ? S[K] extends ProcessedComputable<UnwrapComputableType<R>> : T[K];
|
||||||
// @ts-ignore
|
// } {
|
||||||
obj[key] = computed(computable.bind(obj));
|
// const computable = obj[key];
|
||||||
} else if (isFunction(computable)) {
|
// if (
|
||||||
obj[key] = computable.bind(obj) as unknown as T[S];
|
// isFunction(computable) &&
|
||||||
(obj[key] as unknown as JSXFunction)[DoNotCache] = true;
|
// computable.length === 0 &&
|
||||||
}
|
// !(computable as unknown as JSXFunction)[DoNotCache]
|
||||||
|
// ) {
|
||||||
|
// // eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// // @ts-ignore
|
||||||
|
// obj[key] = computed(computable.bind(obj));
|
||||||
|
// } else if (isFunction(computable)) {
|
||||||
|
// obj[key] = computable.bind(obj) as unknown as T[S];
|
||||||
|
// (obj[key] as unknown as JSXFunction)[DoNotCache] = true;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
function isJSXFunction(value: unknown): value is JSXFunction {
|
||||||
|
return typeof value === "function" && DoNotCache in value && value[DoNotCache] === true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function convertComputable<T>(obj: Computable<T>): ProcessedComputable<T> {
|
export function convertComputable<T>(
|
||||||
if (isFunction(obj) && !(obj as unknown as JSXFunction)[DoNotCache]) {
|
obj: Computable<NonNullable<T>>,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
thisArg?: object
|
||||||
// @ts-ignore
|
): typeof obj extends JSXFunction ? typeof obj : ProcessedComputable<T>;
|
||||||
|
export function convertComputable(obj: undefined, thisArg?: object): undefined;
|
||||||
|
export function convertComputable<T>(
|
||||||
|
obj: Computable<T> | undefined,
|
||||||
|
thisArg?: object
|
||||||
|
): (typeof obj extends JSXFunction ? typeof obj : ProcessedComputable<T>) | undefined;
|
||||||
|
export function convertComputable<T>(
|
||||||
|
obj: Computable<T> | undefined,
|
||||||
|
thisArg?: object
|
||||||
|
) /*: (typeof obj extends JSXFunction ? typeof obj : ProcessedComputable<T>) | undefined*/ {
|
||||||
|
if (isFunction(obj) && !isJSXFunction(obj)) {
|
||||||
|
if (thisArg != null) {
|
||||||
|
obj = obj.bind(thisArg);
|
||||||
|
}
|
||||||
obj = computed(obj);
|
obj = computed(obj);
|
||||||
}
|
}
|
||||||
return obj as ProcessedComputable<T>;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,13 +36,8 @@ export function createLazyProxy<T extends object, S extends T>(
|
||||||
): T {
|
): T {
|
||||||
const obj: S & Partial<T> = baseObject;
|
const obj: S & Partial<T> = baseObject;
|
||||||
let calculated = false;
|
let calculated = false;
|
||||||
let calculating = false;
|
|
||||||
function calculateObj(): T {
|
function calculateObj(): T {
|
||||||
if (!calculated) {
|
if (!calculated) {
|
||||||
if (calculating) {
|
|
||||||
console.error("Cyclical dependency detected. Cannot evaluate lazy proxy.");
|
|
||||||
}
|
|
||||||
calculating = true;
|
|
||||||
Object.assign(obj, objectFunc.call(obj, obj));
|
Object.assign(obj, objectFunc.call(obj, obj));
|
||||||
calculated = true;
|
calculated = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -231,15 +227,14 @@ describe("Creating Formulas", () => {
|
||||||
expect(formula.evaluate()).compare_tolerance(expectedValue));
|
expect(formula.evaluate()).compare_tolerance(expectedValue));
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
test("Invert errors", () => expect(() => formula.invert(25)).toLogError());
|
test("Invert throws", () => expect(() => formula.invert(25)).toThrow());
|
||||||
test("Integrate errors", () =>
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
/* @ts-ignore */
|
||||||
|
test("Integrate throws", () => expect(() => formula.evaluateIntegral()).toThrow());
|
||||||
|
test("Invert integral throws", () =>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
expect(() => formula.evaluateIntegral()).toLogError());
|
expect(() => formula.invertIntegral(25)).toThrow());
|
||||||
test("Invert integral errors", () =>
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
||||||
/* @ts-ignore */
|
|
||||||
expect(() => formula.invertIntegral(25)).toLogError());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
testConstant("number", () => Formula.constant(10));
|
testConstant("number", () => Formula.constant(10));
|
||||||
|
@ -250,7 +245,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(() => {
|
||||||
|
@ -261,10 +256,10 @@ describe("Creating Formulas", () => {
|
||||||
// None of these formulas have variables, so they should all behave the same
|
// None of these formulas have variables, so they should all behave the same
|
||||||
test("Is not marked as having a variable", () => expect(formula.hasVariable()).toBe(false));
|
test("Is not marked as having a variable", () => expect(formula.hasVariable()).toBe(false));
|
||||||
test("Is not invertible", () => expect(formula.isInvertible()).toBe(false));
|
test("Is not invertible", () => expect(formula.isInvertible()).toBe(false));
|
||||||
test(`Formula errors if trying to invert`, () =>
|
test(`Formula throws if trying to invert`, () =>
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
/* @ts-ignore */
|
/* @ts-ignore */
|
||||||
expect(() => formula.invert(10)).toLogError());
|
expect(() => formula.invert(10)).toThrow());
|
||||||
test("Is not integrable", () => expect(formula.isIntegrable()).toBe(false));
|
test("Is not integrable", () => expect(formula.isIntegrable()).toBe(false));
|
||||||
test("Has a non-invertible integral", () =>
|
test("Has a non-invertible integral", () =>
|
||||||
expect(formula.isIntegralInvertible()).toBe(false));
|
expect(formula.isIntegralInvertible()).toBe(false));
|
||||||
|
@ -274,7 +269,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,21 +487,14 @@ 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", () => {
|
test("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.isInvertible()).toBe(true);
|
expect(formula.invert(10)).compare_tolerance(0);
|
||||||
expect(() => formula.invert(10)).not.toLogError();
|
|
||||||
});
|
|
||||||
test("Non-invertible variable", () => {
|
|
||||||
const formula = Formula.add(variable.sign(), constant);
|
|
||||||
expect(formula.isInvertible()).toBe(false);
|
|
||||||
expect(() => formula.invert(10)).toLogError();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -629,20 +617,7 @@ describe("Integrating", () => {
|
||||||
|
|
||||||
test("Integrating nested complex formulas", () => {
|
test("Integrating nested complex formulas", () => {
|
||||||
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
||||||
expect(() => formula.evaluateIntegral()).toLogError();
|
expect(() => formula.evaluateIntegral()).toThrow();
|
||||||
});
|
|
||||||
|
|
||||||
describe("Integrating with non-integrable sections", () => {
|
|
||||||
test("Non-integrable constant", () => {
|
|
||||||
const formula = Formula.add(variable, constant.ceil());
|
|
||||||
expect(formula.isIntegrable()).toBe(true);
|
|
||||||
expect(() => formula.evaluateIntegral()).not.toLogError();
|
|
||||||
});
|
|
||||||
test("Non-integrable variable", () => {
|
|
||||||
const formula = Formula.add(variable.ceil(), constant);
|
|
||||||
expect(formula.isIntegrable()).toBe(false);
|
|
||||||
expect(() => formula.evaluateIntegral()).toLogError();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -662,7 +637,7 @@ describe("Inverting integrals", () => {
|
||||||
describe("Invertible Integral functions marked as such", () => {
|
describe("Invertible Integral functions marked as such", () => {
|
||||||
function checkFormula(formula: InvertibleIntegralFormula) {
|
function checkFormula(formula: InvertibleIntegralFormula) {
|
||||||
expect(formula.isIntegralInvertible()).toBe(true);
|
expect(formula.isIntegralInvertible()).toBe(true);
|
||||||
expect(() => formula.invertIntegral(10)).not.toLogError();
|
expect(() => formula.invertIntegral(10)).to.not.throw();
|
||||||
}
|
}
|
||||||
invertibleIntegralZeroPramFunctionNames.forEach(name => {
|
invertibleIntegralZeroPramFunctionNames.forEach(name => {
|
||||||
describe(name, () => {
|
describe(name, () => {
|
||||||
|
@ -681,7 +656,7 @@ describe("Inverting integrals", () => {
|
||||||
test(`${name}(var, var) is marked as not having an invertible integral`, () => {
|
test(`${name}(var, var) is marked as not having an invertible integral`, () => {
|
||||||
const formula = Formula[name](variable, variable);
|
const formula = Formula[name](variable, variable);
|
||||||
expect(formula.isIntegralInvertible()).toBe(false);
|
expect(formula.isIntegralInvertible()).toBe(false);
|
||||||
expect(() => formula.invertIntegral(10)).toLogError();
|
expect(() => formula.invertIntegral(10)).to.throw();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -737,7 +712,7 @@ describe("Inverting integrals", () => {
|
||||||
|
|
||||||
test("Inverting integral of nested complex formulas", () => {
|
test("Inverting integral of nested complex formulas", () => {
|
||||||
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
const formula = Formula.pow(1.05, variable).times(100).pow(0.5);
|
||||||
expect(() => formula.invertIntegral(100)).toLogError();
|
expect(() => formula.invertIntegral(100)).toThrow();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -770,7 +745,7 @@ describe("Step-wise", () => {
|
||||||
);
|
);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.step(constant, 10, value => Formula.add(value, 10)).evaluateIntegral()
|
Formula.step(constant, 10, value => Formula.add(value, 10)).evaluateIntegral()
|
||||||
).toLogError();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula never marked as having an invertible integral", () => {
|
test("Formula never marked as having an invertible integral", () => {
|
||||||
|
@ -779,7 +754,7 @@ describe("Step-wise", () => {
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.step(constant, 10, value => Formula.add(value, 10)).invertIntegral(10)
|
Formula.step(constant, 10, value => Formula.add(value, 10)).invertIntegral(10)
|
||||||
).toLogError();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
||||||
|
@ -871,7 +846,7 @@ describe("Conditionals", () => {
|
||||||
);
|
);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.if(constant, true, value => Formula.add(value, 10)).evaluateIntegral()
|
Formula.if(constant, true, value => Formula.add(value, 10)).evaluateIntegral()
|
||||||
).toLogError();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula never marked as having an invertible integral", () => {
|
test("Formula never marked as having an invertible integral", () => {
|
||||||
|
@ -880,7 +855,7 @@ describe("Conditionals", () => {
|
||||||
).toBe(false);
|
).toBe(false);
|
||||||
expect(() =>
|
expect(() =>
|
||||||
Formula.if(constant, true, value => Formula.add(value, 10)).invertIntegral(10)
|
Formula.if(constant, true, value => Formula.add(value, 10)).invertIntegral(10)
|
||||||
).toLogError();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
test("Formula modifiers with variables mark formula as non-invertible", () => {
|
||||||
|
@ -981,7 +956,7 @@ describe("Custom Formulas", () => {
|
||||||
evaluate: () => 6,
|
evaluate: () => 6,
|
||||||
invert: value => value
|
invert: value => value
|
||||||
}).invert(10)
|
}).invert(10)
|
||||||
).toLogError());
|
).toThrow());
|
||||||
test("One input inverts correctly", () =>
|
test("One input inverts correctly", () =>
|
||||||
expect(
|
expect(
|
||||||
new Formula({
|
new Formula({
|
||||||
|
@ -1008,7 +983,7 @@ describe("Custom Formulas", () => {
|
||||||
evaluate: () => 0,
|
evaluate: () => 0,
|
||||||
integrate: stack => variable
|
integrate: stack => variable
|
||||||
}).evaluateIntegral()
|
}).evaluateIntegral()
|
||||||
).toLogError());
|
).toThrow());
|
||||||
test("One input integrates correctly", () =>
|
test("One input integrates correctly", () =>
|
||||||
expect(
|
expect(
|
||||||
new Formula({
|
new Formula({
|
||||||
|
@ -1035,7 +1010,7 @@ describe("Custom Formulas", () => {
|
||||||
evaluate: () => 0,
|
evaluate: () => 0,
|
||||||
integrate: stack => variable
|
integrate: stack => variable
|
||||||
}).invertIntegral(20)
|
}).invertIntegral(20)
|
||||||
).toLogError());
|
).toThrow());
|
||||||
test("One input inverts integral correctly", () =>
|
test("One input inverts integral correctly", () =>
|
||||||
expect(
|
expect(
|
||||||
new Formula({
|
new Formula({
|
||||||
|
@ -1078,19 +1053,12 @@ describe("Buy Max", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
resource = createResource(ref(100000));
|
resource = createResource(ref(100000));
|
||||||
});
|
});
|
||||||
describe("Without cumulative cost", () => {
|
describe("Without spending", () => {
|
||||||
test("Errors on calculating max affordable of non-invertible formula", () => {
|
test("Throws on formula with non-invertible integral", () => {
|
||||||
const purchases = ref(1);
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
const variable = Formula.variable(purchases);
|
/* @ts-ignore */
|
||||||
const formula = Formula.abs(variable);
|
const maxAffordable = calculateMaxAffordable(Formula.neg(10), resource, false);
|
||||||
const maxAffordable = calculateMaxAffordable(formula, resource, false);
|
expect(() => maxAffordable.value).toThrow();
|
||||||
expect(() => maxAffordable.value).toLogError();
|
|
||||||
});
|
|
||||||
test("Errors on calculating cost of non-invertible formula", () => {
|
|
||||||
const purchases = ref(1);
|
|
||||||
const variable = Formula.variable(purchases);
|
|
||||||
const formula = Formula.abs(variable);
|
|
||||||
expect(() => calculateCost(formula, 5, false, 0)).toLogError();
|
|
||||||
});
|
});
|
||||||
test("Calculates max affordable and cost correctly", () => {
|
test("Calculates max affordable and cost correctly", () => {
|
||||||
const variable = Formula.variable(0);
|
const variable = Formula.variable(0);
|
||||||
|
@ -1101,32 +1069,11 @@ describe("Buy Max", () => {
|
||||||
Decimal.pow(1.05, 141).times(100)
|
Decimal.pow(1.05, 141).times(100)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test("Calculates max affordable and cost correctly with direct sum", () => {
|
|
||||||
const variable = Formula.variable(0);
|
|
||||||
const formula = Formula.pow(1.05, variable).times(100);
|
|
||||||
const maxAffordable = calculateMaxAffordable(formula, resource, false, 4);
|
|
||||||
expect(maxAffordable.value).compare_tolerance(141 - 4);
|
|
||||||
|
|
||||||
const actualCost = new Array(4)
|
|
||||||
.fill(null)
|
|
||||||
.reduce((acc, _, i) => acc.add(formula.evaluate(133 + i)), new Decimal(0));
|
|
||||||
const calculatedCost = calculateCost(formula, maxAffordable.value, false, 4);
|
|
||||||
expect(calculatedCost).compare_tolerance(actualCost);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
describe("With cumulative cost", () => {
|
describe("With spending", () => {
|
||||||
test("Errors on calculating max affordable of non-invertible formula", () => {
|
test("Throws on non-invertible formula", () => {
|
||||||
const purchases = ref(1);
|
const maxAffordable = calculateMaxAffordable(Formula.abs(10), resource);
|
||||||
const variable = Formula.variable(purchases);
|
expect(() => maxAffordable.value).toThrow();
|
||||||
const formula = Formula.abs(variable);
|
|
||||||
const maxAffordable = calculateMaxAffordable(formula, resource, true);
|
|
||||||
expect(() => maxAffordable.value).toLogError();
|
|
||||||
});
|
|
||||||
test("Errors on calculating cost of non-invertible formula", () => {
|
|
||||||
const purchases = ref(1);
|
|
||||||
const variable = Formula.variable(purchases);
|
|
||||||
const formula = Formula.abs(variable);
|
|
||||||
expect(() => calculateCost(formula, 5, true, 0)).toLogError();
|
|
||||||
});
|
});
|
||||||
test("Estimates max affordable and cost correctly with 0 purchases", () => {
|
test("Estimates max affordable and cost correctly with 0 purchases", () => {
|
||||||
const purchases = ref(0);
|
const purchases = ref(0);
|
||||||
|
@ -1184,7 +1131,7 @@ describe("Buy Max", () => {
|
||||||
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
||||||
).toBeLessThan(0.1);
|
).toBeLessThan(0.1);
|
||||||
});
|
});
|
||||||
test("Estimates max affordable and cost more accurately with direct sum", () => {
|
test("Estimates max affordable and cost more accurately with summing last purchases", () => {
|
||||||
const purchases = ref(1);
|
const purchases = ref(1);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
const formula = Formula.pow(1.05, variable).times(100);
|
const formula = Formula.pow(1.05, variable).times(100);
|
||||||
|
@ -1211,7 +1158,7 @@ describe("Buy Max", () => {
|
||||||
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
Decimal.sub(actualCost, calculatedCost).abs().div(actualCost).toNumber()
|
||||||
).toBeLessThan(0.02);
|
).toBeLessThan(0.02);
|
||||||
});
|
});
|
||||||
test("Handles direct sum when making few purchases", () => {
|
test("Handles summing purchases when making few purchases", () => {
|
||||||
const purchases = ref(90);
|
const purchases = ref(90);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
const formula = Formula.pow(1.05, variable).times(100);
|
const formula = Formula.pow(1.05, variable).times(100);
|
||||||
|
@ -1239,7 +1186,7 @@ describe("Buy Max", () => {
|
||||||
// Since we're summing all the purchases this should be equivalent
|
// Since we're summing all the purchases this should be equivalent
|
||||||
expect(calculatedCost).compare_tolerance(actualCost);
|
expect(calculatedCost).compare_tolerance(actualCost);
|
||||||
});
|
});
|
||||||
test("Handles direct sum when making very few purchases", () => {
|
test("Handles summing purchases when making very few purchases", () => {
|
||||||
const purchases = ref(0);
|
const purchases = ref(0);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
const formula = variable.add(1);
|
const formula = variable.add(1);
|
||||||
|
@ -1253,11 +1200,11 @@ describe("Buy Max", () => {
|
||||||
(acc, _, i) => acc.add(formula.evaluate(i + purchases.value)),
|
(acc, _, i) => acc.add(formula.evaluate(i + purchases.value)),
|
||||||
new Decimal(0)
|
new Decimal(0)
|
||||||
);
|
);
|
||||||
const calculatedCost = calculateCost(formula, maxAffordable.value);
|
const calculatedCost = calculateCost(formula, maxAffordable.value, true);
|
||||||
// Since we're summing all the purchases this should be equivalent
|
// Since we're summing all the purchases this should be equivalent
|
||||||
expect(calculatedCost).compare_tolerance(actualCost);
|
expect(calculatedCost).compare_tolerance(actualCost);
|
||||||
});
|
});
|
||||||
test("Handles direct sum when over e308 purchases", () => {
|
test("Handles summing purchases when over e308 purchases", () => {
|
||||||
resource.value = "1ee308";
|
resource.value = "1ee308";
|
||||||
const purchases = ref(0);
|
const purchases = ref(0);
|
||||||
const variable = Formula.variable(purchases);
|
const variable = Formula.variable(purchases);
|
||||||
|
@ -1268,10 +1215,5 @@ describe("Buy Max", () => {
|
||||||
expect(Decimal.isFinite(calculatedCost)).toBe(true);
|
expect(Decimal.isFinite(calculatedCost)).toBe(true);
|
||||||
resource.value = 100000;
|
resource.value = 100000;
|
||||||
});
|
});
|
||||||
test("Handles direct sum of non-integrable formula", () => {
|
|
||||||
const purchases = ref(0);
|
|
||||||
const formula = Formula.variable(purchases).abs();
|
|
||||||
expect(() => calculateCost(formula, 10)).not.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", () => {
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
Requirement,
|
Requirement,
|
||||||
requirementsMet
|
requirementsMet
|
||||||
} from "game/requirements";
|
} from "game/requirements";
|
||||||
import Decimal from "util/bignum";
|
|
||||||
import { beforeAll, describe, expect, test } from "vitest";
|
import { beforeAll, describe, expect, test } from "vitest";
|
||||||
import { isRef, ref, unref } from "vue";
|
import { isRef, ref, unref } from "vue";
|
||||||
import "../utils";
|
import "../utils";
|
||||||
|
@ -27,7 +26,8 @@ describe("Creating cost requirement", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
requirement = createCostRequirement(() => ({
|
requirement = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 10
|
cost: 10,
|
||||||
|
spendResources: false
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ describe("Creating cost requirement", () => {
|
||||||
});
|
});
|
||||||
test("is visible", () => expect(requirement.visibility).toBe(Visibility.Visible));
|
test("is visible", () => expect(requirement.visibility).toBe(Visibility.Visible));
|
||||||
test("requires pay", () => expect(requirement.requiresPay).toBe(true));
|
test("requires pay", () => expect(requirement.requiresPay).toBe(true));
|
||||||
test("does not spend resources", () => expect(requirement.cumulativeCost).toBe(true));
|
test("does not spend resources", () => expect(requirement.spendResources).toBe(false));
|
||||||
test("cannot maximize", () => expect(unref(requirement.canMaximize)).toBe(false));
|
test("cannot maximize", () => expect(unref(requirement.canMaximize)).toBe(false));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -56,9 +56,8 @@ describe("Creating cost requirement", () => {
|
||||||
cost: Formula.variable(resource).times(10),
|
cost: Formula.variable(resource).times(10),
|
||||||
visibility: Visibility.None,
|
visibility: Visibility.None,
|
||||||
requiresPay: false,
|
requiresPay: false,
|
||||||
cumulativeCost: false,
|
maximize: true,
|
||||||
maxBulkAmount: Decimal.dInf,
|
spendResources: true,
|
||||||
directSum: 5,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||||
pay() {}
|
pay() {}
|
||||||
}));
|
}));
|
||||||
|
@ -70,43 +69,30 @@ describe("Creating cost requirement", () => {
|
||||||
requirement.pay.length === 1);
|
requirement.pay.length === 1);
|
||||||
test("is not visible", () => expect(requirement.visibility).toBe(Visibility.None));
|
test("is not visible", () => expect(requirement.visibility).toBe(Visibility.None));
|
||||||
test("does not require pay", () => expect(requirement.requiresPay).toBe(false));
|
test("does not require pay", () => expect(requirement.requiresPay).toBe(false));
|
||||||
test("spends resources", () => expect(requirement.cumulativeCost).toBe(false));
|
test("spends resources", () => expect(requirement.spendResources).toBe(true));
|
||||||
test("can maximize", () => expect(unref(requirement.canMaximize)).toBe(true));
|
test("can maximize", () => expect(unref(requirement.canMaximize)).toBe(true));
|
||||||
test("maxBulkAmount is set", () =>
|
|
||||||
expect(unref(requirement.maxBulkAmount)).compare_tolerance(Decimal.dInf));
|
|
||||||
test("directSum is set", () => expect(unref(requirement.directSum)).toBe(5));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Requirement met when meeting the cost", () => {
|
test("Requirement met when meeting the cost", () => {
|
||||||
const requirement = createCostRequirement(() => ({
|
const requirement = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 10,
|
cost: 10,
|
||||||
cumulativeCost: false
|
spendResources: 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", () => {
|
||||||
const requirement = createCostRequirement(() => ({
|
const requirement = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 100,
|
cost: 100,
|
||||||
cumulativeCost: false
|
spendResources: false
|
||||||
}));
|
}));
|
||||||
expect(unref(requirement.requirementMet)).toBe(0);
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("canMaximize works correctly", () => {
|
describe("canMaximize works correctly", () => {
|
||||||
test("Cost function cannot maximize", () =>
|
test("Cost function cannot maximize", () =>
|
||||||
expect(
|
|
||||||
unref(
|
|
||||||
createCostRequirement(() => ({
|
|
||||||
resource,
|
|
||||||
cost: () => 10,
|
|
||||||
maxBulkAmount: Decimal.dInf
|
|
||||||
})).canMaximize
|
|
||||||
)
|
|
||||||
).toBe(false));
|
|
||||||
test("Integrable formula cannot maximize if maxBulkAmount is left at 1", () =>
|
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
|
@ -115,104 +101,82 @@ describe("Creating cost requirement", () => {
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(false));
|
).toBe(false));
|
||||||
test("Non-invertible formula cannot maximize when max bulk amount is above direct sum", () =>
|
test("Non-invertible formula cannot maximize", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).abs(),
|
cost: Formula.variable(resource).abs()
|
||||||
maxBulkAmount: Decimal.dInf
|
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(false));
|
).toBe(false));
|
||||||
test("Non-invertible formula can maximize when max bulk amount is lte direct sum", () =>
|
test("Invertible formula can maximize if spendResources is false", () =>
|
||||||
expect(
|
|
||||||
unref(
|
|
||||||
createCostRequirement(() => ({
|
|
||||||
resource,
|
|
||||||
cost: Formula.variable(resource).abs(),
|
|
||||||
maxBulkAmount: 20,
|
|
||||||
directSum: 20
|
|
||||||
})).canMaximize
|
|
||||||
)
|
|
||||||
).toBe(true));
|
|
||||||
test("Invertible formula can maximize if cumulativeCost is false", () =>
|
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).lambertw(),
|
cost: Formula.variable(resource).lambertw(),
|
||||||
cumulativeCost: false,
|
spendResources: false
|
||||||
maxBulkAmount: Decimal.dInf
|
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(true));
|
).toBe(true));
|
||||||
test("Invertible formula cannot maximize if cumulativeCost is true", () =>
|
test("Invertible formula cannot maximize if spendResources is true", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).lambertw(),
|
cost: Formula.variable(resource).lambertw(),
|
||||||
cumulativeCost: true,
|
spendResources: true
|
||||||
maxBulkAmount: Decimal.dInf
|
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(false));
|
).toBe(false));
|
||||||
test("Integrable formula can maximize if cumulativeCost is false", () =>
|
test("Integrable formula can maximize if spendResources is false", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).pow(2),
|
cost: Formula.variable(resource).pow(2),
|
||||||
cumulativeCost: false,
|
spendResources: false
|
||||||
maxBulkAmount: Decimal.dInf
|
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(true));
|
).toBe(true));
|
||||||
test("Integrable formula can maximize if cumulativeCost is true", () =>
|
test("Integrable formula can maximize if spendResources is true", () =>
|
||||||
expect(
|
expect(
|
||||||
unref(
|
unref(
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: Formula.variable(resource).pow(2),
|
cost: Formula.variable(resource).pow(2),
|
||||||
cumulativeCost: true,
|
spendResources: true
|
||||||
maxBulkAmount: Decimal.dInf
|
|
||||||
})).canMaximize
|
})).canMaximize
|
||||||
)
|
)
|
||||||
).toBe(true));
|
).toBe(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Requirements met capped by maxBulkAmount", () =>
|
|
||||||
expect(
|
|
||||||
unref(
|
|
||||||
createCostRequirement(() => ({
|
|
||||||
resource,
|
|
||||||
cost: Formula.variable(resource).times(0.0001),
|
|
||||||
maxBulkAmount: 10,
|
|
||||||
cumulativeCost: false
|
|
||||||
})).requirementMet
|
|
||||||
)
|
|
||||||
).compare_tolerance(10));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Creating visibility requirement", () => {
|
describe("Creating visibility requirement", () => {
|
||||||
const visibility = ref<Visibility.None | Visibility.Visible | boolean>(Visibility.Visible);
|
test("Requirement met when visible", () => {
|
||||||
const requirement = createVisibilityRequirement({ visibility });
|
const requirement = createVisibilityRequirement({ visibility: Visibility.Visible });
|
||||||
expect(unref(requirement.requirementMet)).toBe(true);
|
expect(unref(requirement.requirementMet)).toBe(true);
|
||||||
visibility.value = true;
|
});
|
||||||
expect(unref(requirement.requirementMet)).toBe(true);
|
|
||||||
visibility.value = Visibility.None;
|
test("Requirement not met when not visible", () => {
|
||||||
expect(unref(requirement.requirementMet)).toBe(false);
|
let requirement = createVisibilityRequirement({ visibility: Visibility.None });
|
||||||
visibility.value = false;
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
expect(unref(requirement.requirementMet)).toBe(false);
|
requirement = createVisibilityRequirement({ visibility: false });
|
||||||
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Creating boolean requirement", () => {
|
describe("Creating boolean requirement", () => {
|
||||||
const req = ref(true);
|
test("Requirement met when true", () => {
|
||||||
const requirement = createBooleanRequirement(req);
|
const requirement = createBooleanRequirement(ref(true));
|
||||||
expect(unref(requirement.requirementMet)).toBe(true);
|
expect(unref(requirement.requirementMet)).toBe(true);
|
||||||
req.value = false;
|
});
|
||||||
expect(unref(requirement.requirementMet)).toBe(false);
|
|
||||||
|
test("Requirement not met when false", () => {
|
||||||
|
const requirement = createBooleanRequirement(ref(false));
|
||||||
|
expect(unref(requirement.requirementMet)).toBe(false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Checking all requirements met", () => {
|
describe("Checking all requirements met", () => {
|
||||||
|
@ -244,7 +208,7 @@ describe("Checking maximum levels of requirements met", () => {
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource: createResource(ref(10)),
|
resource: createResource(ref(10)),
|
||||||
cost: Formula.variable(0),
|
cost: Formula.variable(0),
|
||||||
cumulativeCost: false
|
spendResources: false
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
expect(maxRequirementsMet(requirements)).compare_tolerance(0);
|
expect(maxRequirementsMet(requirements)).compare_tolerance(0);
|
||||||
|
@ -256,8 +220,7 @@ describe("Checking maximum levels of requirements met", () => {
|
||||||
createCostRequirement(() => ({
|
createCostRequirement(() => ({
|
||||||
resource: createResource(ref(10)),
|
resource: createResource(ref(10)),
|
||||||
cost: Formula.variable(0),
|
cost: Formula.variable(0),
|
||||||
cumulativeCost: false,
|
spendResources: false
|
||||||
maxBulkAmount: Decimal.dInf
|
|
||||||
}))
|
}))
|
||||||
];
|
];
|
||||||
expect(maxRequirementsMet(requirements)).compare_tolerance(10);
|
expect(maxRequirementsMet(requirements)).compare_tolerance(10);
|
||||||
|
@ -270,12 +233,12 @@ test("Paying requirements", () => {
|
||||||
resource,
|
resource,
|
||||||
cost: 10,
|
cost: 10,
|
||||||
requiresPay: false,
|
requiresPay: false,
|
||||||
cumulativeCost: false
|
spendResources: false
|
||||||
}));
|
}));
|
||||||
const payment = createCostRequirement(() => ({
|
const payment = createCostRequirement(() => ({
|
||||||
resource,
|
resource,
|
||||||
cost: 10,
|
cost: 10,
|
||||||
cumulativeCost: false
|
spendResources: false
|
||||||
}));
|
}));
|
||||||
payRequirements([noPayment, payment]);
|
payRequirements([noPayment, payment]);
|
||||||
expect(resource.value).compare_tolerance(90);
|
expect(resource.value).compare_tolerance(90);
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import Decimal, { DecimalSource, format } from "util/bignum";
|
import Decimal, { DecimalSource, format } from "util/bignum";
|
||||||
import { Mock, expect, vi } from "vitest";
|
import { expect } from "vitest";
|
||||||
|
|
||||||
interface CustomMatchers<R = unknown> {
|
interface CustomMatchers<R = unknown> {
|
||||||
compare_tolerance(expected: DecimalSource, tolerance?: number): R;
|
compare_tolerance(expected: DecimalSource, tolerance?: number): R;
|
||||||
toLogError(): R;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -37,25 +36,5 @@ expect.extend({
|
||||||
expected: format(expected),
|
expected: format(expected),
|
||||||
actual: format(received)
|
actual: format(received)
|
||||||
};
|
};
|
||||||
},
|
|
||||||
toLogError(received: () => unknown) {
|
|
||||||
const { isNot } = this;
|
|
||||||
console.error = vi.fn();
|
|
||||||
received();
|
|
||||||
const calls = (
|
|
||||||
console.error as unknown as Mock<
|
|
||||||
Parameters<typeof console.error>,
|
|
||||||
ReturnType<typeof console.error>
|
|
||||||
>
|
|
||||||
).mock.calls.length;
|
|
||||||
const pass = calls >= 1;
|
|
||||||
vi.restoreAllMocks();
|
|
||||||
return {
|
|
||||||
pass,
|
|
||||||
message: () =>
|
|
||||||
`Expected ${received} to ${(isNot as boolean) ? " not" : ""} log an error`,
|
|
||||||
expected: "1+",
|
|
||||||
actual: calls
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue