-
+
discord
The Modding Tree
@@ -67,7 +80,7 @@ const isOpen = ref(false);
const timePlayed = computed(() => formatTime(player.timePlayed));
const infoComponent = computed(() => {
- return coerceComponent(jsx(() => <>{infoComponents.map(render)}>));
+ return coerceComponent(jsx(() => (<>{infoComponents.map(render)}>)));
});
defineExpose({
diff --git a/src/components/Layer.vue b/src/components/Layer.vue
index a64a255..a3fffda 100644
--- a/src/components/Layer.vue
+++ b/src/components/Layer.vue
@@ -1,15 +1,22 @@
-
-
@@ -19,11 +26,10 @@
import projInfo from "data/projInfo.json";
import type { CoercableComponent } from "features/feature";
import type { FeatureNode } from "game/layers";
-import type { Persistent } from "game/persistence";
import player from "game/player";
-import { computeComponent, processedPropType, wrapRef } from "util/vue";
+import { computeComponent, computeOptionalComponent, processedPropType, unwrapRef } from "util/vue";
import type { PropType, Ref } from "vue";
-import { computed, defineComponent, nextTick, toRefs, unref, watch } from "vue";
+import { computed, defineComponent, toRefs, unref } from "vue";
import Context from "./Context.vue";
export default defineComponent({
@@ -33,20 +39,13 @@ export default defineComponent({
type: Number,
required: true
},
- tab: {
- type: Function as PropType<() => HTMLElement | undefined>,
- required: true
- },
display: {
type: processedPropType(Object, String, Function),
required: true
},
+ minimizedDisplay: processedPropType(Object, String, Function),
minimized: {
- type: Object as PropType>,
- required: true
- },
- minWidth: {
- type: processedPropType(Number, String),
+ type: Object as PropType[>,
required: true
},
name: {
@@ -60,52 +59,31 @@ export default defineComponent({
required: true
}
},
+ emits: ["setMinimized"],
setup(props) {
- const { display, index, minimized, minWidth, tab } = toRefs(props);
+ const { display, index, minimized, minimizedDisplay } = toRefs(props);
const component = computeComponent(display);
+ const minimizedComponent = computeOptionalComponent(minimizedDisplay);
const showGoBack = computed(
- () => projInfo.allowGoBack && index.value > 0 && !minimized.value
+ () => projInfo.allowGoBack && index.value > 0 && !unwrapRef(minimized)
);
function goBack() {
player.tabs.splice(unref(props.index), Infinity);
}
- nextTick(() => updateTab(minimized.value, unref(minWidth.value)));
- watch([minimized, wrapRef(minWidth)], ([minimized, minWidth]) =>
- updateTab(minimized, minWidth)
- );
+ function setMinimized(min: boolean) {
+ minimized.value = min;
+ }
function updateNodes(nodes: Record) {
props.nodes.value = nodes;
}
- function updateTab(minimized: boolean, minWidth: number | string) {
- const width =
- typeof minWidth === "number" || Number.isNaN(parseInt(minWidth))
- ? minWidth + "px"
- : minWidth;
- const tabValue = tab.value();
- if (tabValue != undefined) {
- if (minimized) {
- tabValue.style.flexGrow = "0";
- tabValue.style.flexShrink = "0";
- tabValue.style.width = "60px";
- tabValue.style.minWidth = tabValue.style.flexBasis = "";
- tabValue.style.margin = "0";
- } else {
- tabValue.style.flexGrow = "";
- tabValue.style.flexShrink = "";
- tabValue.style.width = "";
- tabValue.style.minWidth = tabValue.style.flexBasis = width;
- tabValue.style.margin = "";
- }
- }
- }
-
return {
component,
+ minimizedComponent,
showGoBack,
updateNodes,
unref,
@@ -155,9 +133,10 @@ export default defineComponent({
background-color: transparent;
}
-.layer-tab.minimized div {
+.layer-tab.minimized > * {
margin: 0;
writing-mode: vertical-rl;
+ text-align: left;
padding-left: 10px;
width: 50px;
}
@@ -201,8 +180,8 @@ export default defineComponent({
.goBack {
position: sticky;
- top: 6px;
- left: 20px;
+ top: 10px;
+ left: 10px;
line-height: 30px;
margin-top: -50px;
margin-left: -35px;
@@ -211,7 +190,7 @@ export default defineComponent({
box-shadow: var(--background) 0 2px 3px 5px;
border-radius: 50%;
color: var(--foreground);
- font-size: 40px;
+ font-size: 30px;
cursor: pointer;
z-index: 7;
}
@@ -221,3 +200,10 @@ export default defineComponent({
text-shadow: 0 0 7px var(--foreground);
}
+
+
diff --git a/src/components/Modal.vue b/src/components/Modal.vue
index 7a3ee19..9fd5f06 100644
--- a/src/components/Modal.vue
+++ b/src/components/Modal.vue
@@ -40,7 +40,7 @@
-
diff --git a/src/components/Save.vue b/src/components/Save.vue
index c151666..1ff2496 100644
--- a/src/components/Save.vue
+++ b/src/components/Save.vue
@@ -104,11 +104,11 @@ const isEditing = ref(false);
const isConfirming = ref(false);
const newName = ref("");
-watch(isEditing, () => (newName.value = save.value.name || ""));
+watch(isEditing, () => (newName.value = save.value.name ?? ""));
-const isActive = computed(() => save.value && save.value.id === player.id);
+const isActive = computed(() => save.value != null && save.value.id === player.id);
const currentTime = computed(() =>
- isActive.value ? player.time : (save.value && save.value.time) || 0
+ isActive.value ? player.time : (save.value != null && save.value.time) ?? 0
);
function changeName() {
diff --git a/src/components/SavesManager.vue b/src/components/SavesManager.vue
index e05ab75..91edd40 100644
--- a/src/components/SavesManager.vue
+++ b/src/components/SavesManager.vue
@@ -59,11 +59,10 @@
diff --git a/src/features/resources/resource.ts b/src/features/resources/resource.ts
index 5cc9cdd..be42e7e 100644
--- a/src/features/resources/resource.ts
+++ b/src/features/resources/resource.ts
@@ -1,10 +1,14 @@
import { globalBus } from "game/events";
-import type { State } from "game/persistence";
+import { NonPersistent, Persistent, State } from "game/persistence";
import { persistent } from "game/persistence";
+import player from "game/player";
+import settings from "game/settings";
import type { DecimalSource } from "util/bignum";
import Decimal, { format, formatWhole } from "util/bignum";
+import type { ProcessedComputable } from "util/computed";
+import { loadingSave } from "util/save";
import type { ComputedRef, Ref } from "vue";
-import { computed, isRef, ref, watch } from "vue";
+import { computed, isRef, ref, unref, watch } from "vue";
export interface Resource extends Ref {
displayName: string;
@@ -12,24 +16,47 @@ export interface Resource extends Ref {
small?: boolean;
}
+export function createResource(
+ defaultValue: T,
+ displayName?: string,
+ precision?: number,
+ small?: boolean | undefined
+): Resource & Persistent & { [NonPersistent]: Resource };
+export function createResource(
+ defaultValue: Ref,
+ displayName?: string,
+ precision?: number,
+ small?: boolean | undefined
+): Resource;
export function createResource(
defaultValue: T | Ref,
displayName = "points",
precision = 0,
- small = undefined
-): Resource {
+ small: boolean | undefined = undefined
+) {
const resource: Partial> = isRef(defaultValue)
? defaultValue
: persistent(defaultValue);
resource.displayName = displayName;
resource.precision = precision;
resource.small = small;
+ if (!isRef(defaultValue)) {
+ const nonPersistentResource = (resource as Persistent)[
+ NonPersistent
+ ] as unknown as Resource;
+ nonPersistentResource.displayName = displayName;
+ nonPersistentResource.precision = precision;
+ nonPersistentResource.small = small;
+ }
return resource as Resource;
}
export function trackBest(resource: Resource): Ref {
const best = persistent(resource.value);
watch(resource, amount => {
+ if (loadingSave.value) {
+ return;
+ }
if (Decimal.gt(amount, best.value)) {
best.value = amount;
}
@@ -40,6 +67,9 @@ export function trackBest(resource: Resource): Ref {
export function trackTotal(resource: Resource): Ref {
const total = persistent(resource.value);
watch(resource, (amount, prevAmount) => {
+ if (loadingSave.value) {
+ return;
+ }
if (Decimal.gt(amount, prevAmount)) {
total.value = Decimal.add(total.value, Decimal.sub(amount, prevAmount));
}
@@ -110,7 +140,14 @@ export function trackOOMPS(
export function displayResource(resource: Resource, overrideAmount?: DecimalSource): string {
const amount = overrideAmount ?? resource.value;
if (Decimal.eq(resource.precision, 0)) {
- return formatWhole(amount);
+ return formatWhole(resource.small ? amount : Decimal.floor(amount));
}
return format(amount, resource.precision, resource.small);
}
+
+export function unwrapResource(resource: ProcessedComputable): Resource {
+ if ("displayName" in resource) {
+ return resource;
+ }
+ return unref(resource);
+}
diff --git a/src/features/tabs/TabButton.vue b/src/features/tabs/TabButton.vue
index c9347d1..2b4991f 100644
--- a/src/features/tabs/TabButton.vue
+++ b/src/features/tabs/TabButton.vue
@@ -1,11 +1,11 @@
import type { CoercableComponent, StyleValue } from "features/feature";
-import { Visibility } from "features/feature";
+import { isHidden, isVisible, Visibility } from "features/feature";
import { getNotifyStyle } from "game/notifications";
import { computeComponent, processedPropType, unwrapRef } from "util/vue";
import { computed, defineComponent, toRefs, unref } from "vue";
@@ -29,7 +29,7 @@ import { computed, defineComponent, toRefs, unref } from "vue";
export default defineComponent({
props: {
visibility: {
- type: processedPropType(Number),
+ type: processedPropType(Number, Boolean),
required: true
},
display: {
@@ -50,7 +50,7 @@ export default defineComponent({
const glowColorStyle = computed(() => {
const color = unwrapRef(glowColor);
- if (!color) {
+ if (color == null || color === "") {
return {};
}
if (unref(floating)) {
@@ -68,7 +68,9 @@ export default defineComponent({
component,
glowColorStyle,
unref,
- Visibility
+ Visibility,
+ isVisible,
+ isHidden
};
}
});
diff --git a/src/features/tabs/TabFamily.vue b/src/features/tabs/TabFamily.vue
index 1ae9007..436f81a 100644
--- a/src/features/tabs/TabFamily.vue
+++ b/src/features/tabs/TabFamily.vue
@@ -1,11 +1,11 @@
](Number),
+ type: processedPropType
(Number, Boolean),
required: true
},
activeTab: {
@@ -123,7 +123,9 @@ export default defineComponent({
Visibility,
component,
gatherButtonProps,
- unref
+ unref,
+ isVisible,
+ isHidden
};
}
});
@@ -220,8 +222,8 @@ export default defineComponent({
}
.showGoBack
- > .tab-family-container
- > .tab-buttons-container:not(.floating):first-child
+ > .tab-family-container:first-child
+ > .tab-buttons-container:not(.floating)
.tab-buttons {
padding-left: 70px;
}
diff --git a/src/features/tabs/tabFamily.ts b/src/features/tabs/tabFamily.ts
index a1281e0..9652f3d 100644
--- a/src/features/tabs/tabFamily.ts
+++ b/src/features/tabs/tabFamily.ts
@@ -1,5 +1,12 @@
import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
-import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature";
+import {
+ Component,
+ GatherProps,
+ getUniqueID,
+ isVisible,
+ setDefault,
+ Visibility
+} from "features/feature";
import TabButtonComponent from "features/tabs/TabButton.vue";
import TabFamilyComponent from "features/tabs/TabFamily.vue";
import type { Persistent } from "game/persistence";
@@ -20,7 +27,7 @@ export const TabButtonType = Symbol("TabButton");
export const TabFamilyType = Symbol("TabFamily");
export interface TabButtonOptions {
- visibility?: Computable;
+ visibility?: Computable;
tab: Computable;
display: Computable;
classes?: Computable>;
@@ -48,12 +55,12 @@ export type TabButton = Replace<
export type GenericTabButton = Replace<
TabButton,
{
- visibility: ProcessedComputable;
+ visibility: ProcessedComputable;
}
>;
export interface TabFamilyOptions {
- visibility?: Computable;
+ visibility?: Computable;
classes?: Computable>;
style?: Computable;
buttonContainerClasses?: Computable>;
@@ -81,7 +88,7 @@ export type TabFamily = Replace<
export type GenericTabFamily = Replace<
TabFamily,
{
- visibility: ProcessedComputable;
+ visibility: ProcessedComputable;
}
>;
@@ -91,10 +98,10 @@ export function createTabFamily(
): TabFamily {
if (Object.keys(tabs).length === 0) {
console.warn("Cannot create tab family with 0 tabs");
- throw "Cannot create tab family with 0 tabs";
+ throw new Error("Cannot create tab family with 0 tabs");
}
- const selected = persistent(Object.keys(tabs)[0]);
+ const selected = persistent(Object.keys(tabs)[0], false);
return createLazyProxy(() => {
const tabFamily = optionsFunc?.() ?? ({} as ReturnType>);
@@ -123,15 +130,10 @@ export function createTabFamily(
tabFamily.selected = selected;
tabFamily.activeTab = computed(() => {
const tabs = unref(processedTabFamily.tabs);
- if (
- selected.value in tabs &&
- unref(tabs[selected.value].visibility) === Visibility.Visible
- ) {
+ if (selected.value in tabs && isVisible(tabs[selected.value].visibility)) {
return unref(tabs[selected.value].tab);
}
- const firstTab = Object.values(tabs).find(
- tab => unref(tab.visibility) === Visibility.Visible
- );
+ const firstTab = Object.values(tabs).find(tab => isVisible(tab.visibility));
if (firstTab) {
return unref(firstTab.tab);
}
diff --git a/src/features/tooltips/tooltip.ts b/src/features/tooltips/tooltip.ts
index dcc618f..13cb931 100644
--- a/src/features/tooltips/tooltip.ts
+++ b/src/features/tooltips/tooltip.ts
@@ -78,7 +78,7 @@ export function addTooltip(
options.pinnable = false;
} else {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- (element as any).pinned = options.pinned = persistent(false);
+ (element as any).pinned = options.pinned = persistent(false, false);
}
}
diff --git a/src/features/trees/TreeNode.vue b/src/features/trees/TreeNode.vue
index 6c6cb09..ea48eeb 100644
--- a/src/features/trees/TreeNode.vue
+++ b/src/features/trees/TreeNode.vue
@@ -1,7 +1,7 @@
(Object, String, Function),
visibility: {
- type: processedPropType
(Number),
+ type: processedPropType(Number, Boolean),
required: true
},
style: processedPropType(String, Object, Array),
@@ -87,7 +87,9 @@ export default defineComponent({
comp,
unref,
Visibility,
- isCoercableComponent
+ isCoercableComponent,
+ isVisible,
+ isHidden
};
}
});
diff --git a/src/features/trees/tree.ts b/src/features/trees/tree.ts
index 3fbf935..bcbf333 100644
--- a/src/features/trees/tree.ts
+++ b/src/features/trees/tree.ts
@@ -23,7 +23,7 @@ export const TreeNodeType = Symbol("TreeNode");
export const TreeType = Symbol("Tree");
export interface TreeNodeOptions {
- visibility?: Computable;
+ visibility?: Computable;
canClick?: Computable;
color?: Computable;
display?: Computable;
@@ -60,7 +60,7 @@ export type TreeNode = Replace<
export type GenericTreeNode = Replace<
TreeNode,
{
- visibility: ProcessedComputable;
+ visibility: ProcessedComputable;
canClick: ProcessedComputable;
}
>;
@@ -87,16 +87,16 @@ export function createTreeNode(
if (treeNode.onClick) {
const onClick = treeNode.onClick.bind(treeNode);
- treeNode.onClick = function () {
- if (unref(treeNode.canClick)) {
- onClick();
+ treeNode.onClick = function (e) {
+ if (unref(treeNode.canClick) !== false) {
+ onClick(e);
}
};
}
if (treeNode.onHold) {
const onHold = treeNode.onHold.bind(treeNode);
treeNode.onHold = function () {
- if (unref(treeNode.canClick)) {
+ if (unref(treeNode.canClick) !== false) {
onHold();
}
};
@@ -141,7 +141,7 @@ export interface TreeBranch extends Omit {
}
export interface TreeOptions {
- visibility?: Computable;
+ visibility?: Computable;
nodes: Computable;
leftSideNodes?: Computable;
rightSideNodes?: Computable;
@@ -175,7 +175,7 @@ export type Tree = Replace<
export type GenericTree = Replace<
Tree,
{
- visibility: ProcessedComputable;
+ visibility: ProcessedComputable;
}
>;
diff --git a/src/features/upgrades/Upgrade.vue b/src/features/upgrades/Upgrade.vue
index f06ab41..d1fb6c3 100644
--- a/src/features/upgrades/Upgrade.vue
+++ b/src/features/upgrades/Upgrade.vue
@@ -1,9 +1,9 @@
(Number),
+ type: processedPropType(Number, Boolean),
required: true
},
style: processedPropType(String, Object, Array),
classes: processedPropType>(Object),
- resource: Object as PropType,
- cost: processedPropType(String, Object, Number),
+ requirements: {
+ type: Object as PropType,
+ required: true
+ },
canPurchase: {
type: processedPropType(Boolean),
required: true
@@ -75,7 +75,7 @@ export default defineComponent({
MarkNode
},
setup(props) {
- const { display, cost } = toRefs(props);
+ const { display, requirements, bought } = toRefs(props);
const component = shallowRef("");
@@ -89,32 +89,24 @@ export default defineComponent({
component.value = coerceComponent(currDisplay);
return;
}
- const currCost = unwrapRef(cost);
const Title = coerceComponent(currDisplay.title || "", "h3");
const Description = coerceComponent(currDisplay.description, "div");
const EffectDisplay = coerceComponent(currDisplay.effectDisplay || "");
component.value = coerceComponent(
jsx(() => (
- {currDisplay.title ? (
+ {currDisplay.title != null ? (
) : null}
- {currDisplay.effectDisplay ? (
+ {currDisplay.effectDisplay != null ? (
Currently:
) : null}
- {props.resource != null ? (
- <>
-
- Cost: {props.resource &&
- displayResource(props.resource, currCost)}{" "}
- {props.resource?.displayName}
- >
- ) : null}
+ {bought.value ? null : <>
{displayRequirements(requirements.value)}>}
))
);
@@ -123,7 +115,9 @@ export default defineComponent({
return {
component,
unref,
- Visibility
+ Visibility,
+ isVisible,
+ isHidden
};
}
});
diff --git a/src/features/upgrades/upgrade.ts b/src/features/upgrades/upgrade.ts
index 1039632..924e28f 100644
--- a/src/features/upgrades/upgrade.ts
+++ b/src/features/upgrades/upgrade.ts
@@ -1,4 +1,11 @@
-import type { CoercableComponent, OptionsFunc, Replace, StyleValue } from "features/feature";
+import { isArray } from "@vue/shared";
+import type {
+ CoercableComponent,
+ GenericComponent,
+ OptionsFunc,
+ Replace,
+ StyleValue
+} from "features/feature";
import {
Component,
findFeatures,
@@ -7,13 +14,16 @@ import {
setDefault,
Visibility
} from "features/feature";
-import type { Resource } from "features/resources/resource";
import UpgradeComponent from "features/upgrades/Upgrade.vue";
import type { GenericLayer } from "game/layers";
import type { Persistent } from "game/persistence";
import { persistent } from "game/persistence";
-import type { DecimalSource } from "util/bignum";
-import Decimal from "util/bignum";
+import {
+ createVisibilityRequirement,
+ payRequirements,
+ Requirements,
+ requirementsMet
+} from "game/requirements";
import { isFunction } from "util/common";
import type {
Computable,
@@ -29,7 +39,7 @@ import { computed, unref } from "vue";
export const UpgradeType = Symbol("Upgrade");
export interface UpgradeOptions {
- visibility?: Computable;
+ visibility?: Computable;
classes?: Computable>;
style?: Computable;
display?: Computable<
@@ -40,10 +50,8 @@ export interface UpgradeOptions {
effectDisplay?: CoercableComponent;
}
>;
+ requirements: Requirements;
mark?: Computable;
- cost?: Computable;
- resource?: Resource;
- canAfford?: Computable;
onPurchase?: VoidFunction;
}
@@ -64,79 +72,53 @@ export type Upgrade = Replace<
classes: GetComputableType;
style: GetComputableType;
display: GetComputableType;
+ requirements: GetComputableType;
mark: GetComputableType;
- cost: GetComputableType;
- canAfford: GetComputableTypeWithDefault>;
}
>;
export type GenericUpgrade = Replace<
Upgrade,
{
- visibility: ProcessedComputable;
- canPurchase: ProcessedComputable;
+ visibility: ProcessedComputable;
}
>;
export function createUpgrade(
optionsFunc: OptionsFunc
): Upgrade {
- const bought = persistent(false);
+ const bought = persistent(false, false);
return createLazyProxy(() => {
const upgrade = optionsFunc();
upgrade.id = getUniqueID("upgrade-");
upgrade.type = UpgradeType;
upgrade[Component] = UpgradeComponent;
- if (upgrade.canAfford == null && (upgrade.resource == null || upgrade.cost == null)) {
- console.warn(
- "Error: can't create upgrade without a canAfford property or a resource and cost property",
- upgrade
- );
- }
-
upgrade.bought = bought;
- if (upgrade.canAfford == null) {
- upgrade.canAfford = computed(() => {
- const genericUpgrade = upgrade as GenericUpgrade;
- return (
- genericUpgrade.resource != null &&
- genericUpgrade.cost != null &&
- Decimal.gte(genericUpgrade.resource.value, unref(genericUpgrade.cost))
- );
- });
- } else {
- processComputable(upgrade as T, "canAfford");
- }
- upgrade.canPurchase = computed(
- () =>
- unref((upgrade as GenericUpgrade).visibility) === Visibility.Visible &&
- unref((upgrade as GenericUpgrade).canAfford) &&
- !unref(upgrade.bought)
- );
+ upgrade.canPurchase = computed(() => requirementsMet(upgrade.requirements));
upgrade.purchase = function () {
const genericUpgrade = upgrade as GenericUpgrade;
if (!unref(genericUpgrade.canPurchase)) {
return;
}
- if (genericUpgrade.resource != null && genericUpgrade.cost != null) {
- genericUpgrade.resource.value = Decimal.sub(
- genericUpgrade.resource.value,
- unref(genericUpgrade.cost)
- );
- }
+ payRequirements(upgrade.requirements);
bought.value = true;
genericUpgrade.onPurchase?.();
};
+ const visibilityRequirement = createVisibilityRequirement(upgrade as GenericUpgrade);
+ if (isArray(upgrade.requirements)) {
+ upgrade.requirements.unshift(visibilityRequirement);
+ } else {
+ upgrade.requirements = [visibilityRequirement, upgrade.requirements];
+ }
+
processComputable(upgrade as T, "visibility");
setDefault(upgrade, "visibility", Visibility.Visible);
processComputable(upgrade as T, "classes");
processComputable(upgrade as T, "style");
processComputable(upgrade as T, "display");
processComputable(upgrade as T, "mark");
- processComputable(upgrade as T, "cost");
- processComputable(upgrade as T, "resource");
upgrade[GatherProps] = function (this: GenericUpgrade) {
const {
@@ -144,8 +126,7 @@ export function createUpgrade(
visibility,
style,
classes,
- resource,
- cost,
+ requirements,
canPurchase,
bought,
mark,
@@ -157,8 +138,7 @@ export function createUpgrade(
visibility,
style: unref(style),
classes,
- resource,
- cost,
+ requirements,
canPurchase,
bought,
mark,
@@ -176,8 +156,11 @@ export function setupAutoPurchase(
autoActive: Computable,
upgrades: GenericUpgrade[] = []
): void {
- upgrades = upgrades || findFeatures(layer, UpgradeType);
- const isAutoActive = isFunction(autoActive) ? computed(autoActive) : autoActive;
+ upgrades =
+ upgrades.length === 0 ? (findFeatures(layer, UpgradeType) as GenericUpgrade[]) : upgrades;
+ const isAutoActive: ProcessedComputable = isFunction(autoActive)
+ ? computed(autoActive)
+ : autoActive;
layer.on("update", () => {
if (unref(isAutoActive)) {
upgrades.forEach(upgrade => upgrade.purchase());
diff --git a/src/game/events.ts b/src/game/events.ts
index 3be00b7..a167042 100644
--- a/src/game/events.ts
+++ b/src/game/events.ts
@@ -1,12 +1,6 @@
-import projInfo from "data/projInfo.json";
-import player from "game/player";
import type { Settings } from "game/settings";
-import settings from "game/settings";
-import state from "game/state";
import { createNanoEvents } from "nanoevents";
-import Decimal from "util/bignum";
-import type { App, Ref } from "vue";
-import { watch } from "vue";
+import type { App } from "vue";
import type { GenericLayer } from "./layers";
/** All types of events able to be sent or emitted from the global event bus. */
@@ -50,105 +44,18 @@ export interface GlobalEvents {
* Happens when the page is opened and upon switching saves in the saves manager.
*/
onLoad: VoidFunction;
+ /**
+ * Using document.fonts.ready returns too early on firefox, so we use document.fonts.onloadingdone instead, which doesn't accept multiple listeners.
+ * This event fires when that callback is called.
+ */
+ fontsLoaded: VoidFunction;
}
/** A global event bus for hooking into {@link GlobalEvents}. */
export const globalBus = createNanoEvents();
-let intervalID: NodeJS.Timer | null = null;
-
-// Not imported immediately due to dependency cycles
-// This gets set during startGameLoop(), and will only be used in the update function
-let hasWon: null | Ref = null;
-
-function update() {
- const now = Date.now();
- let diff = (now - player.time) / 1e3;
- player.time = now;
- const trueDiff = diff;
-
- state.lastTenTicks.push(trueDiff);
- if (state.lastTenTicks.length > 10) {
- state.lastTenTicks = state.lastTenTicks.slice(1);
- }
-
- // Stop here if the game is paused on the win screen
- if (hasWon?.value && !player.keepGoing) {
- return;
- }
- // Stop here if the player had a NaN value
- if (state.hasNaN) {
- return;
- }
-
- diff = Math.max(diff, 0);
-
- if (player.devSpeed === 0) {
- return;
- }
-
- // Add offline time if any
- if (player.offlineTime != undefined) {
- if (Decimal.gt(player.offlineTime, projInfo.offlineLimit * 3600)) {
- player.offlineTime = projInfo.offlineLimit * 3600;
- }
- if (Decimal.gt(player.offlineTime, 0) && player.devSpeed !== 0) {
- const offlineDiff = Math.max(player.offlineTime / 10, diff);
- player.offlineTime = player.offlineTime - offlineDiff;
- diff += offlineDiff;
- } else if (player.devSpeed === 0) {
- player.offlineTime += diff;
- }
- if (!player.offlineProd || Decimal.lt(player.offlineTime, 0)) {
- player.offlineTime = null;
- }
- }
-
- // Cap at max tick length
- diff = Math.min(diff, projInfo.maxTickLength);
-
- // Apply dev speed
- if (player.devSpeed != undefined) {
- diff *= player.devSpeed;
- }
-
- if (!Number.isFinite(diff)) {
- diff = 1e308;
- }
-
- // Update
- if (Decimal.eq(diff, 0)) {
- return;
- }
-
- player.timePlayed += diff;
- if (!Number.isFinite(player.timePlayed)) {
- player.timePlayed = 1e308;
- }
- globalBus.emit("update", diff, trueDiff);
-
- if (settings.unthrottled) {
- requestAnimationFrame(update);
- if (intervalID != null) {
- clearInterval(intervalID);
- intervalID = null;
- }
- } else if (intervalID == null) {
- intervalID = setInterval(update, 50);
- }
-}
-
-/** Starts the game loop for the project, which updates the game in ticks. */
-export async function startGameLoop() {
- hasWon = (await import("data/projEntry")).hasWon;
- watch(hasWon, hasWon => {
- if (hasWon) {
- globalBus.emit("gameWon");
- }
- });
- if (settings.unthrottled) {
- requestAnimationFrame(update);
- } else {
- intervalID = setInterval(update, 50);
- }
+if ("fonts" in document) {
+ // This line breaks tests
+ // JSDom doesn't add document.fonts, and Object.defineProperty doesn't seem to work on document
+ document.fonts.onloadingdone = () => globalBus.emit("fontsLoaded");
}
diff --git a/src/game/formulas/formulas.ts b/src/game/formulas/formulas.ts
new file mode 100644
index 0000000..9806580
--- /dev/null
+++ b/src/game/formulas/formulas.ts
@@ -0,0 +1,1483 @@
+import { Resource } from "features/resources/resource";
+import Decimal, { DecimalSource, format } from "util/bignum";
+import { Computable, convertComputable, ProcessedComputable } from "util/computed";
+import { computed, ComputedRef, ref, unref } from "vue";
+import * as ops from "./operations";
+import type {
+ EvaluateFunction,
+ FormulaOptions,
+ FormulaSource,
+ GeneralFormulaOptions,
+ GenericFormula,
+ GuardedFormulasToDecimals,
+ IntegrableFormula,
+ IntegrateFunction,
+ InternalFormulaProperties,
+ InvertFunction,
+ InvertibleFormula,
+ InvertibleIntegralFormula,
+ SubstitutionFunction,
+ SubstitutionStack
+} from "./types";
+
+export function hasVariable(value: FormulaSource): value is InvertibleFormula {
+ return value instanceof Formula && value.hasVariable();
+}
+
+export function unrefFormulaSource(value: FormulaSource, variable?: DecimalSource) {
+ return value instanceof Formula ? value.evaluate(variable) : unref(value);
+}
+
+function integrateVariable(this: GenericFormula) {
+ return Formula.pow(this, 2).div(2);
+}
+
+function integrateVariableInner(this: GenericFormula) {
+ return this;
+}
+
+/**
+ * A class that can be used for cost/goal functions. It can be evaluated similar to a cost function, but also provides extra features for supported formulas. For example, a lot of math functions can be inverted.
+ * Typically, the use of these extra features is to support cost/goal functions that have multiple levels purchased/completed at once efficiently.
+ * @see {@link calculateMaxAffordable}
+ * @see {@link game/requirements.createCostRequirement}
+ */
+export default class Formula {
+ readonly inputs: T;
+
+ private readonly internalEvaluate: EvaluateFunction | undefined;
+ private readonly internalInvert: InvertFunction | undefined;
+ private readonly internalIntegrate: IntegrateFunction | undefined;
+ private readonly internalIntegrateInner: IntegrateFunction | undefined;
+ private readonly applySubstitution: SubstitutionFunction | undefined;
+ private readonly internalVariables: number;
+
+ public readonly innermostVariable: ProcessedComputable | undefined;
+
+ private integralFormula: GenericFormula | undefined;
+
+ constructor(options: FormulaOptions) {
+ let readonlyProperties;
+ if ("variable" in options) {
+ readonlyProperties = this.setupVariable(options);
+ } else if (!("evaluate" in options)) {
+ readonlyProperties = this.setupConstant(options);
+ } else {
+ readonlyProperties = this.setupFormula(options);
+ }
+ this.inputs = readonlyProperties.inputs;
+ this.internalVariables = readonlyProperties.internalVariables;
+ this.innermostVariable = readonlyProperties.innermostVariable;
+ this.internalEvaluate = readonlyProperties.internalEvaluate;
+ this.internalInvert = readonlyProperties.internalInvert;
+ this.internalIntegrate = readonlyProperties.internalIntegrate;
+ this.internalIntegrateInner = readonlyProperties.internalIntegrateInner;
+ this.applySubstitution = readonlyProperties.applySubstitution;
+ }
+
+ private setupVariable({
+ variable
+ }: {
+ variable: ProcessedComputable;
+ }): InternalFormulaProperties {
+ return {
+ inputs: [variable] as T,
+ internalVariables: 1,
+ innermostVariable: variable,
+ internalIntegrate: integrateVariable,
+ internalIntegrateInner: integrateVariableInner,
+ applySubstitution: ops.passthrough as unknown as SubstitutionFunction
+ };
+ }
+
+ private setupConstant({ inputs }: { inputs: [FormulaSource] }): InternalFormulaProperties {
+ if (inputs.length !== 1) {
+ throw new Error("Evaluate function is required if inputs is not length 1");
+ }
+ return {
+ inputs: inputs as T,
+ internalVariables: 0
+ };
+ }
+
+ private setupFormula(options: GeneralFormulaOptions): InternalFormulaProperties {
+ const { inputs, evaluate, invert, integrate, integrateInner, applySubstitution } = options;
+ const numVariables = inputs.reduce(
+ (acc, input) => acc + (input instanceof Formula ? input.internalVariables : 0),
+ 0
+ );
+ const variable = inputs.find(input => input instanceof Formula && input.hasVariable()) as
+ | GenericFormula
+ | undefined;
+
+ const innermostVariable = numVariables === 1 ? variable?.innermostVariable : undefined;
+
+ return {
+ inputs,
+ internalEvaluate: evaluate,
+ internalInvert: invert,
+ internalIntegrate: integrate,
+ internalIntegrateInner: integrateInner,
+ applySubstitution,
+ innermostVariable,
+ internalVariables: numVariables
+ };
+ }
+
+ /** Calculates C for the implementation of the integral formula for this formula. */
+ calculateConstantOfIntegration() {
+ // Calculate C based on the knowledge that at x=1, the integral should be the average between f(0) and f(1)
+ const integral = this.getIntegralFormula().evaluate(1);
+ const actualCost = Decimal.add(this.evaluate(0), this.evaluate(1)).div(2);
+ return Decimal.sub(actualCost, integral);
+ }
+
+ /** Type predicate that this formula can be inverted. */
+ isInvertible(): this is InvertibleFormula {
+ return this.hasVariable() && (this.internalInvert != null || this.internalEvaluate == null);
+ }
+
+ /** Type predicate that this formula can be integrated. */
+ isIntegrable(): this is IntegrableFormula {
+ return this.hasVariable() && this.internalIntegrate != null;
+ }
+
+ /** Type predicate that this formula has an integral function that can be inverted. */
+ isIntegralInvertible(): this is InvertibleIntegralFormula {
+ if (!this.isIntegrable()) {
+ return false;
+ }
+ return this.getIntegralFormula().isInvertible();
+ }
+
+ /** Whether or not this formula has a singular variable inside it, which can be accessed via {@link innermostVariable}. */
+ hasVariable(): boolean {
+ return this.internalVariables === 1;
+ }
+
+ /**
+ * Evaluate the current result of the formula
+ * @param variable Optionally override the value of the variable while evaluating. Ignored if there is not variable
+ */
+ evaluate(variable?: DecimalSource): DecimalSource {
+ return (
+ this.internalEvaluate?.call(
+ this,
+ ...(this.inputs.map(input =>
+ unrefFormulaSource(input, variable)
+ ) as GuardedFormulasToDecimals)
+ ) ??
+ (this.hasVariable() ? variable : null) ??
+ unrefFormulaSource(this.inputs[0])
+ );
+ }
+
+ /**
+ * Takes a potential result of the formula, and calculates what value the variable inside the formula would have to be for that result to occur. Only works if there's a single variable and if the formula is invertible.
+ * @param value The result of the formula
+ * @see {@link isInvertible}
+ */
+ invert(value: DecimalSource): DecimalSource {
+ if (this.internalInvert && this.hasVariable()) {
+ return this.internalInvert.call(this, value, ...this.inputs);
+ } else if (this.inputs.length === 1 && this.hasVariable()) {
+ return value;
+ }
+ throw new Error("Cannot invert non-invertible formula");
+ }
+
+ /**
+ * Evaluate the result of the indefinite integral (sans the constant of integration). Only works if there's a single variable and the formula is integrable. The formula can only have one "complex" operation (anything besides +,-,*,/).
+ * @param variable Optionally override the value of the variable while evaluating
+ * @see {@link isIntegrable}
+ */
+ evaluateIntegral(variable?: DecimalSource): DecimalSource {
+ if (!this.isIntegrable()) {
+ throw new Error("Cannot evaluate integral of formula without integral");
+ }
+ return this.getIntegralFormula().evaluate(variable);
+ }
+
+ /**
+ * Given the potential result of the formula's integral (and the constant of integration), calculate what value the variable inside the formula would have to be for that result to occur. Only works if there's a single variable and if the formula's integral is invertible.
+ * @param value The result of the integral.
+ * @see {@link isIntegralInvertible}
+ */
+ invertIntegral(value: DecimalSource): DecimalSource {
+ if (!this.isIntegrable() || !this.getIntegralFormula().isInvertible()) {
+ throw new Error("Cannot invert integral of formula without invertible integral");
+ }
+ return this.getIntegralFormula().invert(value);
+ }
+
+ /**
+ * Get a formula that will evaluate to the integral of this formula. May also be invertible.
+ * @param stack For nested formulas, a stack of operations that occur outside the complex operation.
+ */
+ getIntegralFormula(stack?: SubstitutionStack): GenericFormula {
+ if (this.integralFormula != null && stack == null) {
+ return this.integralFormula;
+ }
+ if (stack == null) {
+ // "Outer" part of the formula
+ if (this.applySubstitution == null) {
+ // We're the complex operation of this formula
+ stack = [];
+ if (this.internalIntegrate == null) {
+ throw new Error("Cannot integrate formula with non-integrable operation");
+ }
+ let value = this.internalIntegrate.call(this, stack, ...this.inputs);
+ stack.forEach(func => (value = func(value)));
+ this.integralFormula = value;
+ } else {
+ // Continue digging into the formula
+ if (this.internalIntegrate) {
+ this.integralFormula = this.internalIntegrate.call(
+ this,
+ undefined,
+ ...this.inputs
+ );
+ } else if (
+ this.inputs.length === 1 &&
+ this.internalEvaluate == null &&
+ this.hasVariable()
+ ) {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ this.integralFormula = this;
+ } else {
+ throw new Error("Cannot integrate formula without variable");
+ }
+ }
+ return this.integralFormula;
+ } else {
+ // "Inner" part of the formula
+ if (this.applySubstitution == null) {
+ throw new Error("Cannot have two complex operations in an integrable formula");
+ }
+ stack.push((variable: GenericFormula) =>
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ this.applySubstitution!.call(this, variable, ...this.inputs)
+ );
+ if (this.internalIntegrateInner) {
+ return this.internalIntegrateInner.call(this, stack, ...this.inputs);
+ } else if (this.internalIntegrate) {
+ return this.internalIntegrate.call(this, stack, ...this.inputs);
+ } else if (
+ this.inputs.length === 1 &&
+ this.internalEvaluate == null &&
+ this.hasVariable()
+ ) {
+ return this;
+ } else {
+ throw new Error("Cannot integrate formula without variable");
+ }
+ }
+ }
+
+ /**
+ * Compares if two formulas are equivalent to each other. Note that function contexts can lead to false negatives.
+ * @param other The formula to compare to this one.
+ */
+ equals(other: GenericFormula): boolean {
+ return (
+ this.inputs.length === other.inputs.length &&
+ this.inputs.every((input, i) =>
+ input instanceof Formula && other.inputs[i] instanceof Formula
+ ? input.equals(other.inputs[i])
+ : !(input instanceof Formula) &&
+ !(other.inputs[i] instanceof Formula) &&
+ Decimal.eq(unref(input), unref(other.inputs[i]))
+ ) &&
+ this.internalEvaluate === other.internalEvaluate &&
+ this.internalInvert === other.internalInvert &&
+ this.internalIntegrate === other.internalIntegrate &&
+ this.internalVariables === other.internalVariables
+ );
+ }
+
+ /**
+ * Creates a formula that evaluates to a constant value.
+ * @param value The constant value for this formula.
+ */
+ public static constant(
+ value: ProcessedComputable
+ ): InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula {
+ return new Formula({ inputs: [value] }) as InvertibleFormula;
+ }
+
+ /**
+ * Creates a formula that is marked as the variable for an outer formula. Typically used for inverting and integrating.
+ * @param value The variable for this formula.
+ */
+ public static variable(
+ value: ProcessedComputable
+ ): InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula {
+ return new Formula({ variable: value }) as InvertibleFormula;
+ }
+
+ // TODO add integration support to step-wise functions
+ /**
+ * Creates a step-wise formula. After {@ref start} the formula will have an additional modifier.
+ * This function assumes the incoming {@ref value} will be continuous and monotonically increasing.
+ * @param value The value before applying the step
+ * @param start The value at which to start applying the step
+ * @param formulaModifier How this step should modify the formula. The incoming value will be the unmodified formula value _minus the start value_. So for example if an incoming formula evaluates to 200 and has a step that starts at 150, the formulaModifier would be given 50 as the parameter
+ */
+ public static step(
+ value: FormulaSource,
+ start: Computable,
+ formulaModifier: (
+ value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
+ ) => GenericFormula
+ ): GenericFormula {
+ const lhsRef = ref(0);
+ const formula = formulaModifier(Formula.variable(lhsRef));
+ const processedStart = convertComputable(start);
+ function evalStep(lhs: DecimalSource) {
+ if (Decimal.lt(lhs, unref(processedStart))) {
+ return lhs;
+ }
+ lhsRef.value = Decimal.sub(lhs, unref(processedStart));
+ return Decimal.add(formula.evaluate(), unref(processedStart));
+ }
+ function invertStep(value: DecimalSource, lhs: FormulaSource) {
+ if (hasVariable(lhs)) {
+ if (Decimal.gt(value, unref(processedStart))) {
+ value = Decimal.add(
+ formula.invert(Decimal.sub(value, unref(processedStart))),
+ unref(processedStart)
+ );
+ }
+ return lhs.invert(value);
+ }
+ throw new Error("Could not invert due to no input being a variable");
+ }
+ return new Formula({
+ inputs: [value],
+ evaluate: evalStep,
+ invert: formula.isInvertible() && formula.hasVariable() ? invertStep : undefined
+ });
+ }
+
+ /**
+ * Applies a modifier to a formula under a given condition.
+ * @param value The incoming formula value
+ * @param condition Whether or not to apply the modifier
+ * @param formulaModifier The modifier to apply to the incoming formula if the condition is true
+ */
+ public static if(
+ value: FormulaSource,
+ condition: Computable,
+ formulaModifier: (
+ value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
+ ) => GenericFormula
+ ): GenericFormula {
+ const lhsRef = ref(0);
+ const formula = formulaModifier(Formula.variable(lhsRef));
+ const processedCondition = convertComputable(condition);
+ function evalStep(lhs: DecimalSource) {
+ if (unref(processedCondition)) {
+ lhsRef.value = lhs;
+ return formula.evaluate();
+ } else {
+ return lhs;
+ }
+ }
+ function invertStep(value: DecimalSource, lhs: FormulaSource) {
+ if (!hasVariable(lhs)) {
+ throw new Error("Could not invert due to no input being a variable");
+ }
+ if (unref(processedCondition)) {
+ return lhs.invert(formula.invert(value));
+ } else {
+ return lhs.invert(value);
+ }
+ }
+ return new Formula({
+ inputs: [value],
+ evaluate: evalStep,
+ invert: formula.isInvertible() && formula.hasVariable() ? invertStep : undefined
+ });
+ }
+ /** @see {@link if} */
+ public static conditional(
+ value: FormulaSource,
+ condition: Computable,
+ formulaModifier: (
+ value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
+ ) => GenericFormula
+ ) {
+ return Formula.if(value, condition, formulaModifier);
+ }
+
+ public static abs(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.abs });
+ }
+
+ public static neg(value: T): T;
+ public static neg(value: FormulaSource): GenericFormula;
+ public static neg(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.neg,
+ invert: ops.invertNeg,
+ applySubstitution: ops.applySubstitutionNeg,
+ integrate: ops.integrateNeg
+ });
+ }
+ public static negate = Formula.neg;
+ public static negated = Formula.neg;
+
+ public static sign(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.sign });
+ }
+ public static sgn = Formula.sign;
+
+ public static round(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.round });
+ }
+
+ public static floor(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.floor });
+ }
+
+ public static ceil(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.ceil });
+ }
+
+ public static trunc(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.trunc });
+ }
+
+ public static add(value: T, other: FormulaSource): T;
+ public static add(value: FormulaSource, other: T): T;
+ public static add(value: FormulaSource, other: FormulaSource): GenericFormula;
+ public static add(value: FormulaSource, other: FormulaSource) {
+ return new Formula({
+ inputs: [value, other],
+ evaluate: Decimal.add,
+ invert: ops.invertAdd,
+ integrate: ops.integrateAdd,
+ integrateInner: ops.integrateInnerAdd,
+ applySubstitution: ops.passthrough
+ });
+ }
+ public static plus = Formula.add;
+
+ public static sub(value: T, other: FormulaSource): T;
+ public static sub(value: FormulaSource, other: T): T;
+ public static sub(value: FormulaSource, other: FormulaSource): GenericFormula;
+ public static sub(value: FormulaSource, other: FormulaSource) {
+ return new Formula({
+ inputs: [value, other],
+ evaluate: Decimal.sub,
+ invert: ops.invertSub,
+ integrate: ops.integrateSub,
+ integrateInner: ops.integrateInnerSub,
+ applySubstitution: ops.passthrough
+ });
+ }
+ public static subtract = Formula.sub;
+ public static minus = Formula.sub;
+
+ public static mul(value: T, other: FormulaSource): T;
+ public static mul(value: FormulaSource, other: T): T;
+ public static mul(value: FormulaSource, other: FormulaSource): GenericFormula;
+ public static mul(value: FormulaSource, other: FormulaSource) {
+ return new Formula({
+ inputs: [value, other],
+ evaluate: Decimal.mul,
+ invert: ops.invertMul,
+ integrate: ops.integrateMul,
+ applySubstitution: ops.applySubstitutionMul
+ });
+ }
+ public static multiply = Formula.mul;
+ public static times = Formula.mul;
+
+ public static div(value: T, other: FormulaSource): T;
+ public static div(value: FormulaSource, other: T): T;
+ public static div(value: FormulaSource, other: FormulaSource): GenericFormula;
+ public static div(value: FormulaSource, other: FormulaSource) {
+ return new Formula({
+ inputs: [value, other],
+ evaluate: Decimal.div,
+ invert: ops.invertDiv,
+ integrate: ops.integrateDiv,
+ applySubstitution: ops.applySubstitutionDiv
+ });
+ }
+ public static divide = Formula.div;
+ public static divideBy = Formula.div;
+ public static dividedBy = Formula.div;
+
+ public static recip(value: T): T;
+ public static recip(value: FormulaSource): GenericFormula;
+ public static recip(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.recip,
+ invert: ops.invertRecip,
+ integrate: ops.integrateRecip
+ });
+ }
+ public static reciprocal = Formula.recip;
+ public static reciprocate = Formula.recip;
+
+ // TODO these functions should ostensibly be integrable, and the integrals should be invertible
+ public static max = ops.createPassthroughBinaryFormula(Decimal.max);
+ public static min = ops.createPassthroughBinaryFormula(Decimal.min);
+ public static minabs = ops.createPassthroughBinaryFormula(Decimal.minabs);
+ public static maxabs = ops.createPassthroughBinaryFormula(Decimal.maxabs);
+ public static clampMin = ops.createPassthroughBinaryFormula(Decimal.clampMin);
+ public static clampMax = ops.createPassthroughBinaryFormula(Decimal.clampMax);
+
+ public static clamp(
+ value: FormulaSource,
+ min: FormulaSource,
+ max: FormulaSource
+ ): GenericFormula {
+ return new Formula({
+ inputs: [value, min, max],
+ evaluate: Decimal.clamp,
+ invert: ops.passthrough as InvertFunction<[FormulaSource, FormulaSource, FormulaSource]>
+ });
+ }
+
+ public static pLog10(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.pLog10 });
+ }
+
+ public static absLog10(value: FormulaSource): GenericFormula {
+ return new Formula({ inputs: [value], evaluate: Decimal.absLog10 });
+ }
+
+ public static log10(value: T): T;
+ public static log10(value: FormulaSource): GenericFormula;
+ public static log10(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.log10,
+ invert: ops.invertLog10,
+ integrate: ops.integrateLog10
+ });
+ }
+
+ public static log(value: T, base: FormulaSource): T;
+ public static log(value: FormulaSource, base: T): T;
+ public static log(value: FormulaSource, base: FormulaSource): GenericFormula;
+ public static log(value: FormulaSource, base: FormulaSource) {
+ return new Formula({
+ inputs: [value, base],
+ evaluate: Decimal.log,
+ invert: ops.invertLog,
+ integrate: ops.integrateLog
+ });
+ }
+ public static logarithm = Formula.log;
+
+ public static log2(value: T): T;
+ public static log2(value: FormulaSource): GenericFormula;
+ public static log2(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.log2,
+ invert: ops.invertLog2,
+ integrate: ops.integrateLog2
+ });
+ }
+
+ public static ln(value: T): T;
+ public static ln(value: FormulaSource): GenericFormula;
+ public static ln(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.ln,
+ invert: ops.invertLn,
+ integrate: ops.integrateLn
+ });
+ }
+
+ public static pow(value: T, other: FormulaSource): T;
+ public static pow(value: FormulaSource, other: T): T;
+ public static pow(value: FormulaSource, other: FormulaSource): GenericFormula;
+ public static pow(value: FormulaSource, other: FormulaSource) {
+ return new Formula({
+ inputs: [value, other],
+ evaluate: Decimal.pow,
+ invert: ops.invertPow,
+ integrate: ops.integratePow
+ });
+ }
+
+ public static pow10(value: T): T;
+ public static pow10(value: FormulaSource): GenericFormula;
+ public static pow10(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.pow10,
+ invert: ops.invertPow10,
+ integrate: ops.integratePow10
+ });
+ }
+
+ public static pow_base(value: T, other: FormulaSource): T;
+ public static pow_base(value: FormulaSource, other: T): T;
+ public static pow_base(value: FormulaSource, other: FormulaSource): GenericFormula;
+ public static pow_base(value: FormulaSource, other: FormulaSource) {
+ return new Formula({
+ inputs: [value, other],
+ evaluate: Decimal.pow_base,
+ invert: ops.invertPowBase,
+ integrate: ops.integratePowBase
+ });
+ }
+
+ public static root(value: T, other: FormulaSource): T;
+ public static root(value: FormulaSource, other: T): T;
+ public static root(value: FormulaSource, other: FormulaSource): GenericFormula;
+ public static root(value: FormulaSource, other: FormulaSource) {
+ return new Formula({
+ inputs: [value, other],
+ evaluate: Decimal.root,
+ invert: ops.invertRoot,
+ integrate: ops.integrateRoot
+ });
+ }
+
+ public static factorial(value: FormulaSource) {
+ return new Formula({ inputs: [value], evaluate: Decimal.factorial });
+ }
+
+ public static gamma(value: FormulaSource) {
+ return new Formula({ inputs: [value], evaluate: Decimal.gamma });
+ }
+
+ public static lngamma(value: FormulaSource) {
+ return new Formula({ inputs: [value], evaluate: Decimal.lngamma });
+ }
+
+ public static exp(value: T): T;
+ public static exp(value: FormulaSource): GenericFormula;
+ public static exp(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.exp,
+ invert: ops.invertExp,
+ integrate: ops.integrateExp
+ });
+ }
+
+ public static sqr(value: T): T;
+ public static sqr(value: FormulaSource): GenericFormula;
+ public static sqr(value: FormulaSource) {
+ return Formula.pow(value, 2);
+ }
+
+ public static sqrt(value: T): T;
+ public static sqrt(value: FormulaSource): GenericFormula;
+ public static sqrt(value: FormulaSource) {
+ return Formula.root(value, 2);
+ }
+
+ public static cube(value: T): T;
+ public static cube(value: FormulaSource): GenericFormula;
+ public static cube(value: FormulaSource) {
+ return Formula.pow(value, 3);
+ }
+
+ public static cbrt(value: T): T;
+ public static cbrt(value: FormulaSource): GenericFormula;
+ public static cbrt(value: FormulaSource) {
+ return Formula.root(value, 3);
+ }
+
+ public static tetrate(
+ value: T,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): Omit;
+ public static tetrate(
+ value: FormulaSource,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): GenericFormula;
+ public static tetrate(
+ value: FormulaSource,
+ height: FormulaSource = 2,
+ payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1)
+ ) {
+ return new Formula({
+ inputs: [value, height, payload],
+ evaluate: ops.tetrate,
+ invert: ops.invertTetrate
+ });
+ }
+
+ public static iteratedexp(
+ value: T,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): Omit;
+ public static iteratedexp(
+ value: FormulaSource,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): GenericFormula;
+ public static iteratedexp(
+ value: FormulaSource,
+ height: FormulaSource = 2,
+ payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1)
+ ) {
+ return new Formula({
+ inputs: [value, height, payload],
+ evaluate: ops.iteratedexp,
+ invert: ops.invertIteratedExp
+ });
+ }
+
+ public static iteratedlog(
+ value: FormulaSource,
+ base: FormulaSource = 10,
+ times: FormulaSource = 1
+ ): GenericFormula {
+ return new Formula({ inputs: [value, base, times], evaluate: ops.iteratedLog });
+ }
+
+ public static slog(
+ value: T,
+ base?: FormulaSource
+ ): Omit;
+ public static slog(value: FormulaSource, base?: FormulaSource): GenericFormula;
+ public static slog(value: FormulaSource, base: FormulaSource = 10) {
+ return new Formula({ inputs: [value, base], evaluate: ops.slog, invert: ops.invertSlog });
+ }
+
+ public static layeradd10(value: FormulaSource, diff: FormulaSource) {
+ return new Formula({ inputs: [value, diff], evaluate: Decimal.layeradd10 });
+ }
+
+ public static layeradd(
+ value: T,
+ diff: FormulaSource,
+ base?: FormulaSource
+ ): Omit;
+ public static layeradd(
+ value: FormulaSource,
+ diff: FormulaSource,
+ base?: FormulaSource
+ ): GenericFormula;
+ public static layeradd(value: FormulaSource, diff: FormulaSource, base: FormulaSource = 10) {
+ return new Formula({
+ inputs: [value, diff, base],
+ evaluate: ops.layeradd,
+ invert: ops.invertLayeradd
+ });
+ }
+
+ public static lambertw(value: T): Omit;
+ public static lambertw(value: FormulaSource): GenericFormula;
+ public static lambertw(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.lambertw,
+ invert: ops.invertLambertw
+ });
+ }
+
+ public static ssqrt(value: T): Omit;
+ public static ssqrt(value: FormulaSource): GenericFormula;
+ public static ssqrt(value: FormulaSource) {
+ return new Formula({ inputs: [value], evaluate: Decimal.ssqrt, invert: ops.invertSsqrt });
+ }
+
+ public static pentate(
+ value: FormulaSource,
+ height: FormulaSource = 2,
+ payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1)
+ ): GenericFormula {
+ return new Formula({ inputs: [value, height, payload], evaluate: ops.pentate });
+ }
+
+ public static sin(value: T): T;
+ public static sin(value: FormulaSource): GenericFormula;
+ public static sin(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.sin,
+ invert: ops.invertAsin,
+ integrate: ops.integrateSin
+ });
+ }
+
+ public static cos(value: T): T;
+ public static cos(value: FormulaSource): GenericFormula;
+ public static cos(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.cos,
+ invert: ops.invertAcos,
+ integrate: ops.integrateCos
+ });
+ }
+
+ public static tan(value: T): T;
+ public static tan(value: FormulaSource): GenericFormula;
+ public static tan(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.tan,
+ invert: ops.invertAtan,
+ integrate: ops.integrateTan
+ });
+ }
+
+ public static asin(value: T): T;
+ public static asin(value: FormulaSource): GenericFormula;
+ public static asin(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.asin,
+ invert: ops.invertSin,
+ integrate: ops.integrateAsin
+ });
+ }
+
+ public static acos(value: T): T;
+ public static acos(value: FormulaSource): GenericFormula;
+ public static acos(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.acos,
+ invert: ops.invertCos,
+ integrate: ops.integrateAcos
+ });
+ }
+
+ public static atan(value: T): T;
+ public static atan(value: FormulaSource): GenericFormula;
+ public static atan(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.atan,
+ invert: ops.invertTan,
+ integrate: ops.integrateAtan
+ });
+ }
+
+ public static sinh(value: T): T;
+ public static sinh(value: FormulaSource): GenericFormula;
+ public static sinh(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.sinh,
+ invert: ops.invertAsinh,
+ integrate: ops.integrateSinh
+ });
+ }
+
+ public static cosh(value: T): T;
+ public static cosh(value: FormulaSource): GenericFormula;
+ public static cosh(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.cosh,
+ invert: ops.invertAcosh,
+ integrate: ops.integrateCosh
+ });
+ }
+
+ public static tanh(value: T): T;
+ public static tanh(value: FormulaSource): GenericFormula;
+ public static tanh(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.tanh,
+ invert: ops.invertAtanh,
+ integrate: ops.integrateTanh
+ });
+ }
+
+ public static asinh(value: T): T;
+ public static asinh(value: FormulaSource): GenericFormula;
+ public static asinh(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.asinh,
+ invert: ops.invertSinh,
+ integrate: ops.integrateAsinh
+ });
+ }
+
+ public static acosh(value: T): T;
+ public static acosh(value: FormulaSource): GenericFormula;
+ public static acosh(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.acosh,
+ invert: ops.invertCosh,
+ integrate: ops.integrateAcosh
+ });
+ }
+
+ public static atanh(value: T): T;
+ public static atanh(value: FormulaSource): GenericFormula;
+ public static atanh(value: FormulaSource) {
+ return new Formula({
+ inputs: [value],
+ evaluate: Decimal.atanh,
+ invert: ops.invertTanh,
+ integrate: ops.integrateAtanh
+ });
+ }
+
+ public step(
+ start: Computable,
+ formulaModifier: (
+ value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
+ ) => GenericFormula
+ ) {
+ return Formula.step(this, start, formulaModifier);
+ }
+
+ public if(
+ condition: Computable,
+ formulaModifier: (
+ value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
+ ) => GenericFormula
+ ) {
+ return Formula.if(this, condition, formulaModifier);
+ }
+ public conditional(
+ condition: Computable,
+ formulaModifier: (
+ value: InvertibleFormula & IntegrableFormula & InvertibleIntegralFormula
+ ) => GenericFormula
+ ) {
+ return Formula.if(this, condition, formulaModifier);
+ }
+
+ public abs() {
+ return Formula.abs(this);
+ }
+
+ public neg(this: T): T;
+ public neg(this: GenericFormula): GenericFormula;
+ public neg(this: GenericFormula) {
+ return Formula.neg(this);
+ }
+ public negate = this.neg;
+ public negated = this.neg;
+
+ public sign() {
+ return Formula.sign(this);
+ }
+ public sgn = this.sign;
+
+ public round() {
+ return Formula.round(this);
+ }
+
+ public floor() {
+ return Formula.floor(this);
+ }
+
+ public ceil() {
+ return Formula.ceil(this);
+ }
+
+ public trunc() {
+ return Formula.trunc(this);
+ }
+
+ public add(this: T, value: FormulaSource): T;
+ public add(this: GenericFormula, value: T): T;
+ public add(this: GenericFormula, value: FormulaSource): GenericFormula;
+ public add(this: GenericFormula, value: FormulaSource) {
+ return Formula.add(this, value);
+ }
+ public plus = this.add;
+
+ public sub(this: T, value: FormulaSource): T;
+ public sub(this: GenericFormula, value: T): T;
+ public sub(this: GenericFormula, value: FormulaSource): GenericFormula;
+ public sub(value: FormulaSource) {
+ return Formula.sub(this, value);
+ }
+ public subtract = this.sub;
+ public minus = this.sub;
+
+ public mul(this: T, value: FormulaSource): T;
+ public mul(this: GenericFormula, value: T): T;
+ public mul(this: GenericFormula, value: FormulaSource): GenericFormula;
+ public mul(value: FormulaSource) {
+ return Formula.mul(this, value);
+ }
+ public multiply = this.mul;
+ public times = this.mul;
+
+ public div(this: T, value: FormulaSource): T;
+ public div(this: GenericFormula, value: T): T;
+ public div(this: GenericFormula, value: FormulaSource): GenericFormula;
+ public div(value: FormulaSource) {
+ return Formula.div(this, value);
+ }
+ public divide = this.div;
+ public divideBy = this.div;
+ public dividedBy = this.div;
+
+ public recip(this: T): T;
+ public recip(this: FormulaSource): GenericFormula;
+ public recip() {
+ return Formula.recip(this);
+ }
+ public reciprocal = this.recip;
+ public reciprocate = this.recip;
+
+ public max(value: FormulaSource) {
+ return Formula.max(this, value);
+ }
+
+ public min(value: FormulaSource) {
+ return Formula.min(this, value);
+ }
+
+ public maxabs(value: FormulaSource) {
+ return Formula.maxabs(this, value);
+ }
+
+ public minabs(value: FormulaSource) {
+ return Formula.minabs(this, value);
+ }
+
+ public clamp(min: FormulaSource, max: FormulaSource) {
+ return Formula.clamp(this, min, max);
+ }
+
+ public clampMin(value: FormulaSource) {
+ return Formula.clampMin(this, value);
+ }
+
+ public clampMax(value: FormulaSource) {
+ return Formula.clampMax(this, value);
+ }
+
+ public pLog10() {
+ return Formula.pLog10(this);
+ }
+
+ public absLog10() {
+ return Formula.absLog10(this);
+ }
+
+ public log10(this: T): T;
+ public log10(this: FormulaSource): GenericFormula;
+ public log10() {
+ return Formula.log10(this);
+ }
+
+ public log(this: T, value: FormulaSource): T;
+ public log(this: FormulaSource, value: T): T;
+ public log(this: FormulaSource, value: FormulaSource): GenericFormula;
+ public log(value: FormulaSource) {
+ return Formula.log(this, value);
+ }
+ public logarithm = this.log;
+
+ public log2(this: T): T;
+ public log2(this: FormulaSource): GenericFormula;
+ public log2() {
+ return Formula.log2(this);
+ }
+
+ public ln(this: T): T;
+ public ln(this: FormulaSource): GenericFormula;
+ public ln() {
+ return Formula.ln(this);
+ }
+
+ public pow(this: T, value: FormulaSource): T;
+ public pow(this: FormulaSource, value: T): T;
+ public pow(this: FormulaSource, value: FormulaSource): GenericFormula;
+ public pow(value: FormulaSource) {
+ return Formula.pow(this, value);
+ }
+
+ public pow10(this: T): T;
+ public pow10(this: FormulaSource): GenericFormula;
+ public pow10() {
+ return Formula.pow10(this);
+ }
+
+ public pow_base(this: T, value: FormulaSource): T;
+ public pow_base(this: FormulaSource, value: T): T;
+ public pow_base(this: FormulaSource, value: FormulaSource): GenericFormula;
+ public pow_base(value: FormulaSource) {
+ return Formula.pow_base(this, value);
+ }
+
+ public root(this: T, value: FormulaSource): T;
+ public root(this: FormulaSource, value: T): T;
+ public root(this: FormulaSource, value: FormulaSource): GenericFormula;
+ public root(value: FormulaSource) {
+ return Formula.root(this, value);
+ }
+
+ public factorial() {
+ return Formula.factorial(this);
+ }
+
+ public gamma() {
+ return Formula.gamma(this);
+ }
+ public lngamma() {
+ return Formula.lngamma(this);
+ }
+
+ public exp(this: T): T;
+ public exp(this: FormulaSource): GenericFormula;
+ public exp(this: FormulaSource) {
+ return Formula.exp(this);
+ }
+
+ public sqr(this: T): T;
+ public sqr(this: FormulaSource): GenericFormula;
+ public sqr() {
+ return Formula.pow(this, 2);
+ }
+
+ public sqrt(this: T): T;
+ public sqrt(this: FormulaSource): GenericFormula;
+ public sqrt() {
+ return Formula.root(this, 2);
+ }
+ public cube(this: T): T;
+ public cube(this: FormulaSource): GenericFormula;
+ public cube() {
+ return Formula.pow(this, 3);
+ }
+
+ public cbrt(this: T): T;
+ public cbrt(this: FormulaSource): GenericFormula;
+ public cbrt() {
+ return Formula.root(this, 3);
+ }
+
+ public tetrate(
+ this: T,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): Omit;
+ public tetrate(
+ this: FormulaSource,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): GenericFormula;
+ public tetrate(
+ this: FormulaSource,
+ height: FormulaSource = 2,
+ payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1)
+ ) {
+ return Formula.tetrate(this, height, payload);
+ }
+
+ public iteratedexp(
+ this: T,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): Omit;
+ public iteratedexp(
+ this: FormulaSource,
+ height?: FormulaSource,
+ payload?: FormulaSource
+ ): GenericFormula;
+ public iteratedexp(
+ this: FormulaSource,
+ height: FormulaSource = 2,
+ payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1)
+ ) {
+ return Formula.iteratedexp(this, height, payload);
+ }
+
+ public iteratedlog(base: FormulaSource = 10, times: FormulaSource = 1) {
+ return Formula.iteratedlog(this, base, times);
+ }
+
+ public slog(this: T, base?: FormulaSource): Omit;
+ public slog(this: FormulaSource, base?: FormulaSource): GenericFormula;
+ public slog(this: FormulaSource, base: FormulaSource = 10) {
+ return Formula.slog(this, base);
+ }
+
+ public layeradd10(diff: FormulaSource) {
+ return Formula.layeradd10(this, diff);
+ }
+
+ public layeradd(
+ this: T,
+ diff: FormulaSource,
+ base?: FormulaSource
+ ): Omit;
+ public layeradd(this: FormulaSource, diff: FormulaSource, base?: FormulaSource): GenericFormula;
+ public layeradd(this: FormulaSource, diff: FormulaSource, base: FormulaSource) {
+ return Formula.layeradd(this, diff, base);
+ }
+
+ public lambertw(this: T): Omit;
+ public lambertw(this: FormulaSource): GenericFormula;
+ public lambertw(this: FormulaSource) {
+ return Formula.lambertw(this);
+ }
+
+ public ssqrt(this: T): Omit;
+ public ssqrt(this: FormulaSource): GenericFormula;
+ public ssqrt(this: FormulaSource) {
+ return Formula.ssqrt(this);
+ }
+
+ public pentate(
+ height: FormulaSource = 2,
+ payload: FormulaSource = Decimal.fromComponents_noNormalize(1, 0, 1)
+ ) {
+ return Formula.pentate(this, height, payload);
+ }
+
+ public sin(this: T): T;
+ public sin(this: FormulaSource): GenericFormula;
+ public sin(this: FormulaSource) {
+ return Formula.sin(this);
+ }
+
+ public cos(this: T): T;
+ public cos(this: FormulaSource): GenericFormula;
+ public cos(this: FormulaSource) {
+ return Formula.cos(this);
+ }
+
+ public tan(this: T): T;
+ public tan(this: FormulaSource): GenericFormula;
+ public tan(this: FormulaSource) {
+ return Formula.tan(this);
+ }
+
+ public asin(this: T): T;
+ public asin(this: FormulaSource): GenericFormula;
+ public asin(this: FormulaSource) {
+ return Formula.asin(this);
+ }
+
+ public acos(this: T): T;
+ public acos(this: FormulaSource): GenericFormula;
+ public acos(this: FormulaSource) {
+ return Formula.acos(this);
+ }
+
+ public atan(this: T): T;
+ public atan(this: FormulaSource): GenericFormula;
+ public atan(this: FormulaSource) {
+ return Formula.atan(this);
+ }
+
+ public sinh(this: T): T;
+ public sinh(this: FormulaSource): GenericFormula;
+ public sinh(this: FormulaSource) {
+ return Formula.sinh(this);
+ }
+
+ public cosh(this: T): T;
+ public cosh(this: FormulaSource): GenericFormula;
+ public cosh(this: FormulaSource) {
+ return Formula.cosh(this);
+ }
+
+ public tanh(this: T): T;
+ public tanh(this: FormulaSource): GenericFormula;
+ public tanh(this: FormulaSource) {
+ return Formula.tanh(this);
+ }
+
+ public asinh(this: T): T;
+ public asinh(this: FormulaSource): GenericFormula;
+ public asinh(this: FormulaSource) {
+ return Formula.asinh(this);
+ }
+
+ public acosh