@@ -22,9 +24,8 @@ import AddictionWarning from "components/modals/AddictionWarning.vue";
import CloudSaveResolver from "components/modals/CloudSaveResolver.vue";
import GameOverScreen from "components/modals/GameOverScreen.vue";
import NaNScreen from "components/modals/NaNScreen.vue";
-import { jsx } from "features/feature";
import state from "game/state";
-import { coerceComponent, render } from "util/vue";
+import { render } from "util/vue";
import type { CSSProperties } from "vue";
import { computed, toRef, unref } from "vue";
import Game from "./components/Game.vue";
@@ -40,9 +41,7 @@ const theme = computed(() => themes[settings.theme].variables as CSSProperties);
const showTPS = toRef(settings, "showTPS");
const appErrors = toRef(state, "errors");
-const gameComponent = computed(() => {
- return coerceComponent(jsx(() => (<>{gameComponents.map(render)}>)));
-});
+const GameComponent = () => gameComponents.map(c => render(c));
diff --git a/src/features/trees/tree.ts b/src/features/trees/tree.ts
deleted file mode 100644
index 1148cf0..0000000
--- a/src/features/trees/tree.ts
+++ /dev/null
@@ -1,387 +0,0 @@
-import { GenericDecorator } from "features/decorators/common";
-import type {
- CoercableComponent,
- GenericComponent,
- OptionsFunc,
- Replace,
- StyleValue
-} from "features/feature";
-import { Component, GatherProps, getUniqueID, setDefault, Visibility } from "features/feature";
-import type { Link } from "features/links/links";
-import type { GenericReset } from "features/reset";
-import type { Resource } from "features/resources/resource";
-import { displayResource } from "features/resources/resource";
-import TreeComponent from "features/trees/Tree.vue";
-import TreeNodeComponent from "features/trees/TreeNode.vue";
-import type { DecimalSource } from "util/bignum";
-import Decimal, { format, formatWhole } from "util/bignum";
-import type {
- Computable,
- GetComputableType,
- GetComputableTypeWithDefault,
- ProcessedComputable
-} from "util/computed";
-import { convertComputable, processComputable } from "util/computed";
-import { createLazyProxy } from "util/proxies";
-import type { Ref } from "vue";
-import { computed, ref, shallowRef, unref } from "vue";
-
-/** A symbol used to identify {@link TreeNode} features. */
-export const TreeNodeType = Symbol("TreeNode");
-/** A symbol used to identify {@link Tree} features. */
-export const TreeType = Symbol("Tree");
-
-/**
- * An object that configures a {@link TreeNode}.
- */
-export interface TreeNodeOptions {
- /** Whether this tree node should be visible. */
- visibility?: Computable;
- /** Whether or not this tree node can be clicked. */
- canClick?: Computable;
- /** The background color for this node. */
- color?: Computable;
- /** The label to display on this tree node. */
- display?: Computable;
- /** The color of the glow effect shown to notify the user there's something to do with this node. */
- glowColor?: Computable;
- /** Dictionary of CSS classes to apply to this feature. */
- classes?: Computable>;
- /** CSS to apply to this feature. */
- style?: Computable;
- /** Shows a marker on the corner of the feature. */
- mark?: Computable;
- /** A reset object attached to this node, used for propagating resets through the tree. */
- reset?: GenericReset;
- /** A function that is called when the tree node is clicked. */
- onClick?: (e?: MouseEvent | TouchEvent) => void;
- /** A function that is called when the tree node is held down. */
- onHold?: VoidFunction;
-}
-
-/**
- * The properties that are added onto a processed {@link TreeNodeOptions} to create an {@link TreeNode}.
- */
-export interface BaseTreeNode {
- /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */
- id: string;
- /** A symbol that helps identify features of the same type. */
- type: typeof TreeNodeType;
- /** The Vue component used to render this feature. */
- [Component]: GenericComponent;
- /** A function to gather the props the vue component requires for this feature. */
- [GatherProps]: () => Record;
-}
-
-/** An object that represents a node on a tree. */
-export type TreeNode = Replace<
- T & BaseTreeNode,
- {
- visibility: GetComputableTypeWithDefault;
- canClick: GetComputableTypeWithDefault;
- color: GetComputableType;
- display: GetComputableType;
- glowColor: GetComputableType;
- classes: GetComputableType;
- style: GetComputableType;
- mark: GetComputableType;
- }
->;
-
-/** A type that matches any valid {@link TreeNode} object. */
-export type GenericTreeNode = Replace<
- TreeNode,
- {
- visibility: ProcessedComputable;
- canClick: ProcessedComputable;
- }
->;
-
-/**
- * Lazily creates a tree node with the given options.
- * @param optionsFunc Tree Node options.
- */
-export function createTreeNode(
- optionsFunc?: OptionsFunc,
- ...decorators: GenericDecorator[]
-): TreeNode {
- const decoratedData = decorators.reduce(
- (current, next) => Object.assign(current, next.getPersistentData?.()),
- {}
- );
- return createLazyProxy(feature => {
- const treeNode =
- optionsFunc?.call(feature, feature) ??
- ({} as ReturnType>);
- treeNode.id = getUniqueID("treeNode-");
- treeNode.type = TreeNodeType;
- treeNode[Component] = TreeNodeComponent as GenericComponent;
-
- for (const decorator of decorators) {
- decorator.preConstruct?.(treeNode);
- }
-
- Object.assign(decoratedData);
-
- processComputable(treeNode as T, "visibility");
- setDefault(treeNode, "visibility", Visibility.Visible);
- processComputable(treeNode as T, "canClick");
- setDefault(treeNode, "canClick", true);
- processComputable(treeNode as T, "color");
- processComputable(treeNode as T, "display");
- processComputable(treeNode as T, "glowColor");
- processComputable(treeNode as T, "classes");
- processComputable(treeNode as T, "style");
- processComputable(treeNode as T, "mark");
-
- for (const decorator of decorators) {
- decorator.postConstruct?.(treeNode);
- }
-
- if (treeNode.onClick) {
- const onClick = treeNode.onClick.bind(treeNode);
- treeNode.onClick = function (e) {
- if (
- unref(treeNode.canClick as ProcessedComputable) !== false
- ) {
- onClick(e);
- }
- };
- }
- if (treeNode.onHold) {
- const onHold = treeNode.onHold.bind(treeNode);
- treeNode.onHold = function () {
- if (
- unref(treeNode.canClick as ProcessedComputable) !== false
- ) {
- onHold();
- }
- };
- }
-
- const decoratedProps = decorators.reduce(
- (current, next) => Object.assign(current, next.getGatheredProps?.(treeNode)),
- {}
- );
- treeNode[GatherProps] = function (this: GenericTreeNode) {
- const {
- display,
- visibility,
- style,
- classes,
- onClick,
- onHold,
- color,
- glowColor,
- canClick,
- mark,
- id
- } = this;
- return {
- display,
- visibility,
- style,
- classes,
- onClick,
- onHold,
- color,
- glowColor,
- canClick,
- mark,
- id,
- ...decoratedProps
- };
- };
-
- return treeNode as unknown as TreeNode;
- });
-}
-
-/** Represents a branch between two nodes in a tree. */
-export interface TreeBranch extends Omit {
- startNode: GenericTreeNode;
- endNode: GenericTreeNode;
-}
-
-/**
- * An object that configures a {@link Tree}.
- */
-export interface TreeOptions {
- /** Whether this clickable should be visible. */
- visibility?: Computable;
- /** The nodes within the tree, in a 2D array. */
- nodes: Computable;
- /** Nodes to show on the left side of the tree. */
- leftSideNodes?: Computable;
- /** Nodes to show on the right side of the tree. */
- rightSideNodes?: Computable;
- /** The branches between nodes within this tree. */
- branches?: Computable;
- /** How to propagate resets through the tree. */
- resetPropagation?: ResetPropagation;
- /** A function that is called when a node within the tree is reset. */
- onReset?: (node: GenericTreeNode) => void;
-}
-
-export interface BaseTree {
- /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */
- id: string;
- /** The link objects for each of the branches of the tree. */
- links: Ref;
- /** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */
- reset: (node: GenericTreeNode) => void;
- /** A flag that is true while the reset is still propagating through the tree. */
- isResetting: Ref;
- /** A reference to the node that caused the currently propagating reset. */
- resettingNode: Ref;
- /** A symbol that helps identify features of the same type. */
- type: typeof TreeType;
- /** The Vue component used to render this feature. */
- [Component]: GenericComponent;
- /** A function to gather the props the vue component requires for this feature. */
- [GatherProps]: () => Record;
-}
-
-/** An object that represents a feature that is a tree of nodes with branches between them. Contains support for reset mechanics that can propagate through the tree. */
-export type Tree = Replace<
- T & BaseTree,
- {
- visibility: GetComputableTypeWithDefault;
- nodes: GetComputableType;
- leftSideNodes: GetComputableType;
- rightSideNodes: GetComputableType;
- branches: GetComputableType;
- }
->;
-
-/** A type that matches any valid {@link Tree} object. */
-export type GenericTree = Replace<
- Tree,
- {
- visibility: ProcessedComputable;
- }
->;
-
-/**
- * Lazily creates a tree with the given options.
- * @param optionsFunc Tree options.
- */
-export function createTree(
- optionsFunc: OptionsFunc
-): Tree {
- return createLazyProxy(feature => {
- const tree = optionsFunc.call(feature, feature);
- tree.id = getUniqueID("tree-");
- tree.type = TreeType;
- tree[Component] = TreeComponent as GenericComponent;
-
- tree.isResetting = ref(false);
- tree.resettingNode = shallowRef(null);
-
- tree.reset = function (node) {
- const genericTree = tree as GenericTree;
- genericTree.isResetting.value = true;
- genericTree.resettingNode.value = node;
- genericTree.resetPropagation?.(genericTree, node);
- genericTree.onReset?.(node);
- genericTree.isResetting.value = false;
- genericTree.resettingNode.value = null;
- };
- tree.links = computed(() => {
- const genericTree = tree as GenericTree;
- return unref(genericTree.branches) ?? [];
- });
-
- processComputable(tree as T, "visibility");
- setDefault(tree, "visibility", Visibility.Visible);
- processComputable(tree as T, "nodes");
- processComputable(tree as T, "leftSideNodes");
- processComputable(tree as T, "rightSideNodes");
- processComputable(tree as T, "branches");
-
- tree[GatherProps] = function (this: GenericTree) {
- const { nodes, leftSideNodes, rightSideNodes, branches } = this;
- return { nodes, leftSideNodes, rightSideNodes, branches };
- };
-
- return tree as unknown as Tree;
- });
-}
-
-/** A function that is used to propagate resets through a tree. */
-export type ResetPropagation = {
- (tree: GenericTree, resettingNode: GenericTreeNode): void;
-};
-
-/** Propagate resets down the tree by resetting every node in a lower row. */
-export const defaultResetPropagation = function (
- tree: GenericTree,
- resettingNode: GenericTreeNode
-): void {
- const nodes = unref(tree.nodes);
- const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) - 1;
- for (let x = row; x >= 0; x--) {
- nodes[x].forEach(node => node.reset?.reset());
- }
-};
-
-/** Propagate resets down the tree by resetting every node in a lower row. */
-export const invertedResetPropagation = function (
- tree: GenericTree,
- resettingNode: GenericTreeNode
-): void {
- const nodes = unref(tree.nodes);
- const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) + 1;
- for (let x = row; x < nodes.length; x++) {
- nodes[x].forEach(node => node.reset?.reset());
- }
-};
-
-/** Propagate resets down the branches of the tree. */
-export const branchedResetPropagation = function (
- tree: GenericTree,
- resettingNode: GenericTreeNode
-): void {
- const links = unref(tree.branches);
- if (links == null) return;
- const reset: GenericTreeNode[] = [];
- let current = [resettingNode];
- while (current.length !== 0) {
- const next: GenericTreeNode[] = [];
- for (const node of current) {
- for (const link of links.filter(link => link.startNode === node)) {
- if ([...reset, ...current].includes(link.endNode)) continue;
- next.push(link.endNode);
- link.endNode.reset?.reset();
- }
- }
- reset.push(...current);
- current = next;
- }
-};
-
-/**
- * Utility for creating a tooltip for a tree node that displays a resource-based unlock requirement, and after unlock shows the amount of another resource.
- * It sounds oddly specific, but comes up a lot.
- */
-export function createResourceTooltip(
- resource: Resource,
- requiredResource: Resource | null = null,
- requirement: Computable = 0
-): Ref {
- const req = convertComputable(requirement);
- return computed(() => {
- if (requiredResource == null || Decimal.gte(resource.value, unref(req))) {
- return displayResource(resource) + " " + resource.displayName;
- }
- return `Reach ${
- Decimal.eq(requiredResource.precision, 0)
- ? formatWhole(unref(req))
- : format(unref(req), requiredResource.precision)
- } ${requiredResource.displayName} to unlock (You have ${
- Decimal.eq(requiredResource.precision, 0)
- ? formatWhole(requiredResource.value)
- : format(requiredResource.value, requiredResource.precision)
- })`;
- });
-}
diff --git a/src/features/trees/tree.tsx b/src/features/trees/tree.tsx
new file mode 100644
index 0000000..7d2ded6
--- /dev/null
+++ b/src/features/trees/tree.tsx
@@ -0,0 +1,279 @@
+import type { OptionsFunc, Replace } from "features/feature";
+import { Link } from "features/links/links";
+import type { Reset } from "features/reset";
+import type { Resource } from "features/resources/resource";
+import { displayResource } from "features/resources/resource";
+import Tree from "features/trees/Tree.vue";
+import TreeNode from "features/trees/TreeNode.vue";
+import type { DecimalSource } from "util/bignum";
+import Decimal, { format, formatWhole } from "util/bignum";
+import { ProcessedRefOrGetter, processGetter } from "util/computed";
+import { createLazyProxy } from "util/proxies";
+import { Renderable, VueFeature, vueFeatureMixin, VueFeatureOptions } from "util/vue";
+import type { MaybeRef, MaybeRefOrGetter, Ref } from "vue";
+import { computed, ref, shallowRef, unref } from "vue";
+
+/** A symbol used to identify {@link TreeNode} features. */
+export const TreeNodeType = Symbol("TreeNode");
+/** A symbol used to identify {@link Tree} features. */
+export const TreeType = Symbol("Tree");
+
+/**
+ * An object that configures a {@link TreeNode}.
+ */
+export interface TreeNodeOptions extends VueFeatureOptions {
+ /** Whether or not this tree node can be clicked. */
+ canClick?: MaybeRefOrGetter;
+ /** The background color for this node. */
+ color?: MaybeRefOrGetter;
+ /** The label to display on this tree node. */
+ display?: MaybeRefOrGetter;
+ /** The color of the glow effect shown to notify the user there's something to do with this node. */
+ glowColor?: MaybeRefOrGetter;
+ /** A reset object attached to this node, used for propagating resets through the tree. */
+ reset?: Reset;
+ /** A function that is called when the tree node is clicked. */
+ onClick?: (e?: MouseEvent | TouchEvent) => void;
+ /** A function that is called when the tree node is held down. */
+ onHold?: VoidFunction;
+}
+
+/**
+ * The properties that are added onto a processed {@link TreeNodeOptions} to create an {@link TreeNode}.
+ */
+export interface BaseTreeNode extends VueFeature {
+ /** A symbol that helps identify features of the same type. */
+ type: typeof TreeNodeType;
+}
+
+/** An object that represents a node on a tree. */
+export type TreeNode = Replace<
+ TreeNodeOptions & BaseTreeNode,
+ {
+ canClick: MaybeRef;
+ color: ProcessedRefOrGetter;
+ display: ProcessedRefOrGetter;
+ glowColor: ProcessedRefOrGetter;
+ }
+>;
+
+/**
+ * Lazily creates a tree node with the given options.
+ * @param optionsFunc Tree Node options.
+ */
+export function createTreeNode(
+ optionsFunc?: OptionsFunc
+) {
+ return createLazyProxy(feature => {
+ const options = optionsFunc?.call(feature, feature as TreeNode) ?? ({} as T);
+ const { canClick, color, display, glowColor, onClick, onHold, ...props } = options;
+
+ const treeNode = {
+ type: TreeNodeType,
+ ...(props as Omit),
+ ...vueFeatureMixin("treeNode", options, () => (
+
+ )),
+ canClick: processGetter(canClick) ?? true,
+ color: processGetter(color),
+ display: processGetter(display),
+ glowColor: processGetter(glowColor),
+ onClick:
+ onClick == null
+ ? undefined
+ : function (e) {
+ if (unref(treeNode.canClick) !== false) {
+ onClick.call(treeNode, e);
+ }
+ },
+ onHold:
+ onHold == null
+ ? undefined
+ : function () {
+ if (unref(treeNode.canClick) !== false) {
+ onHold.call(treeNode);
+ }
+ }
+ } satisfies TreeNode;
+
+ return treeNode;
+ });
+}
+
+/** Represents a branch between two nodes in a tree. */
+export interface TreeBranch extends Omit {
+ startNode: TreeNode;
+ endNode: TreeNode;
+}
+
+/**
+ * An object that configures a {@link Tree}.
+ */
+export interface TreeOptions extends VueFeatureOptions {
+ /** The nodes within the tree, in a 2D array. */
+ nodes: MaybeRefOrGetter;
+ /** Nodes to show on the left side of the tree. */
+ leftSideNodes?: MaybeRefOrGetter;
+ /** Nodes to show on the right side of the tree. */
+ rightSideNodes?: MaybeRefOrGetter;
+ /** The branches between nodes within this tree. */
+ branches?: MaybeRefOrGetter;
+ /** How to propagate resets through the tree. */
+ resetPropagation?: ResetPropagation;
+ /** A function that is called when a node within the tree is reset. */
+ onReset?: (node: TreeNode) => void;
+}
+
+export interface BaseTree extends VueFeature {
+ /** The link objects for each of the branches of the tree. */
+ links: Ref;
+ /** Cause a reset on this node and propagate it through the tree according to {@link TreeOptions.resetPropagation}. */
+ reset: (node: TreeNode) => void;
+ /** A flag that is true while the reset is still propagating through the tree. */
+ isResetting: Ref;
+ /** A reference to the node that caused the currently propagating reset. */
+ resettingNode: Ref;
+ /** A symbol that helps identify features of the same type. */
+ type: typeof TreeType;
+}
+
+/** An object that represents a feature that is a tree of nodes with branches between them. Contains support for reset mechanics that can propagate through the tree. */
+export type Tree = Replace<
+ TreeOptions & BaseTree,
+ {
+ nodes: ProcessedRefOrGetter;
+ leftSideNodes: ProcessedRefOrGetter;
+ rightSideNodes: ProcessedRefOrGetter;
+ branches: ProcessedRefOrGetter;
+ }
+>;
+
+/**
+ * Lazily creates a tree with the given options.
+ * @param optionsFunc Tree options.
+ */
+export function createTree(optionsFunc: OptionsFunc) {
+ return createLazyProxy(feature => {
+ const options = optionsFunc.call(feature, feature as Tree);
+ const {
+ branches,
+ nodes,
+ leftSideNodes,
+ rightSideNodes,
+ reset,
+ resetPropagation,
+ onReset,
+ ...props
+ } = options;
+
+ const tree = {
+ type: TreeType,
+ ...(props as Omit),
+ ...vueFeatureMixin("tree", options, () => (
+
+ )),
+ branches: processGetter(branches),
+ isResetting: ref(false),
+ resettingNode: shallowRef(null),
+ nodes: processGetter(nodes),
+ leftSideNodes: processGetter(leftSideNodes),
+ rightSideNodes: processGetter(rightSideNodes),
+ links: processGetter(branches) ?? [],
+ resetPropagation,
+ onReset,
+ reset:
+ reset ??
+ function (node: TreeNode) {
+ tree.isResetting.value = true;
+ tree.resettingNode.value = node;
+ tree.resetPropagation?.(tree, node);
+ tree.onReset?.(node);
+ tree.isResetting.value = false;
+ tree.resettingNode.value = null;
+ }
+ } satisfies Tree;
+
+ return tree;
+ });
+}
+
+/** A function that is used to propagate resets through a tree. */
+export type ResetPropagation = {
+ (tree: Tree, resettingNode: TreeNode): void;
+};
+
+/** Propagate resets down the tree by resetting every node in a lower row. */
+export const defaultResetPropagation = function (tree: Tree, resettingNode: TreeNode): void {
+ const nodes = unref(tree.nodes);
+ const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) - 1;
+ for (let x = row; x >= 0; x--) {
+ nodes[x].forEach(node => node.reset?.reset());
+ }
+};
+
+/** Propagate resets down the tree by resetting every node in a lower row. */
+export const invertedResetPropagation = function (tree: Tree, resettingNode: TreeNode): void {
+ const nodes = unref(tree.nodes);
+ const row = nodes.findIndex(nodes => nodes.includes(resettingNode)) + 1;
+ for (let x = row; x < nodes.length; x++) {
+ nodes[x].forEach(node => node.reset?.reset());
+ }
+};
+
+/** Propagate resets down the branches of the tree. */
+export const branchedResetPropagation = function (tree: Tree, resettingNode: TreeNode): void {
+ const links = unref(tree.branches);
+ if (links == null) return;
+ const reset: TreeNode[] = [];
+ let current = [resettingNode];
+ while (current.length !== 0) {
+ const next: TreeNode[] = [];
+ for (const node of current) {
+ for (const link of links.filter(link => link.startNode === node)) {
+ if ([...reset, ...current].includes(link.endNode)) continue;
+ next.push(link.endNode);
+ link.endNode.reset?.reset();
+ }
+ }
+ reset.push(...current);
+ current = next;
+ }
+};
+
+/**
+ * Utility for creating a tooltip for a tree node that displays a resource-based unlock requirement, and after unlock shows the amount of another resource.
+ * It sounds oddly specific, but comes up a lot.
+ */
+export function createResourceTooltip(
+ resource: Resource,
+ requiredResource: Resource | null = null,
+ requirement: MaybeRefOrGetter = 0
+): Ref {
+ const req = processGetter(requirement);
+ return computed(() => {
+ if (requiredResource == null || Decimal.gte(resource.value, unref(req))) {
+ return displayResource(resource) + " " + resource.displayName;
+ }
+ return `Reach ${
+ Decimal.eq(requiredResource.precision, 0)
+ ? formatWhole(unref(req))
+ : format(unref(req), requiredResource.precision)
+ } ${requiredResource.displayName} to unlock (You have ${
+ Decimal.eq(requiredResource.precision, 0)
+ ? formatWhole(requiredResource.value)
+ : format(requiredResource.value, requiredResource.precision)
+ })`;
+ });
+}
diff --git a/src/features/upgrades/Upgrade.vue b/src/features/upgrades/Upgrade.vue
deleted file mode 100644
index b2e3b5e..0000000
--- a/src/features/upgrades/Upgrade.vue
+++ /dev/null
@@ -1,98 +0,0 @@
-
-
-
-
-
-
-
diff --git a/src/features/upgrades/upgrade.ts b/src/features/upgrades/upgrade.ts
deleted file mode 100644
index a3075e7..0000000
--- a/src/features/upgrades/upgrade.ts
+++ /dev/null
@@ -1,227 +0,0 @@
-import { GenericDecorator } from "features/decorators/common";
-import type {
- CoercableComponent,
- GenericComponent,
- OptionsFunc,
- Replace,
- StyleValue
-} from "features/feature";
-import {
- Component,
- GatherProps,
- Visibility,
- findFeatures,
- getUniqueID,
- setDefault
-} from "features/feature";
-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 {
- Requirements,
- createVisibilityRequirement,
- payRequirements,
- requirementsMet
-} from "game/requirements";
-import { isFunction } from "util/common";
-import type {
- Computable,
- GetComputableType,
- GetComputableTypeWithDefault,
- ProcessedComputable
-} from "util/computed";
-import { processComputable } from "util/computed";
-import { createLazyProxy } from "util/proxies";
-import type { Ref } from "vue";
-import { computed, unref } from "vue";
-
-/** A symbol used to identify {@link Upgrade} features. */
-export const UpgradeType = Symbol("Upgrade");
-
-/**
- * An object that configures a {@link Upgrade}.
- */
-export interface UpgradeOptions {
- /** Whether this clickable should be visible. */
- visibility?: Computable;
- /** Dictionary of CSS classes to apply to this feature. */
- classes?: Computable>;
- /** CSS to apply to this feature. */
- style?: Computable;
- /** Shows a marker on the corner of the feature. */
- mark?: Computable;
- /** The display to use for this clickable. */
- display?: Computable<
- | CoercableComponent
- | {
- /** A header to appear at the top of the display. */
- title?: CoercableComponent;
- /** The main text that appears in the display. */
- description: CoercableComponent;
- /** A description of the current effect of the achievement. Useful when the effect changes dynamically. */
- effectDisplay?: CoercableComponent;
- }
- >;
- /** The requirements to purchase this upgrade. */
- requirements: Requirements;
- /** A function that is called when the upgrade is purchased. */
- onPurchase?: VoidFunction;
-}
-
-/**
- * The properties that are added onto a processed {@link UpgradeOptions} to create an {@link Upgrade}.
- */
-export interface BaseUpgrade {
- /** An auto-generated ID for identifying features that appear in the DOM. Will not persist between refreshes or updates. */
- id: string;
- /** Whether or not this upgrade has been purchased. */
- bought: Persistent;
- /** Whether or not the upgrade can currently be purchased. */
- canPurchase: Ref;
- /** Purchase the upgrade */
- purchase: VoidFunction;
- /** A symbol that helps identify features of the same type. */
- type: typeof UpgradeType;
- /** The Vue component used to render this feature. */
- [Component]: GenericComponent;
- /** A function to gather the props the vue component requires for this feature. */
- [GatherProps]: () => Record;
-}
-
-/** An object that represents a feature that can be purchased a single time. */
-export type Upgrade = Replace<
- T & BaseUpgrade,
- {
- visibility: GetComputableTypeWithDefault;
- classes: GetComputableType;
- style: GetComputableType;
- display: GetComputableType;
- requirements: GetComputableType;
- mark: GetComputableType;
- }
->;
-
-/** A type that matches any valid {@link Upgrade} object. */
-export type GenericUpgrade = Replace<
- Upgrade,
- {
- visibility: ProcessedComputable;
- }
->;
-
-/**
- * Lazily creates an upgrade with the given options.
- * @param optionsFunc Upgrade options.
- */
-export function createUpgrade(
- optionsFunc: OptionsFunc,
- ...decorators: GenericDecorator[]
-): Upgrade {
- const bought = persistent(false, false);
- const decoratedData = decorators.reduce(
- (current, next) => Object.assign(current, next.getPersistentData?.()),
- {}
- );
- return createLazyProxy(feature => {
- const upgrade = optionsFunc.call(feature, feature);
- upgrade.id = getUniqueID("upgrade-");
- upgrade.type = UpgradeType;
- upgrade[Component] = UpgradeComponent as GenericComponent;
-
- for (const decorator of decorators) {
- decorator.preConstruct?.(upgrade);
- }
-
- upgrade.bought = bought;
- Object.assign(upgrade, decoratedData);
-
- upgrade.canPurchase = computed(
- () => !bought.value && requirementsMet(upgrade.requirements)
- );
- upgrade.purchase = function () {
- const genericUpgrade = upgrade as GenericUpgrade;
- if (!unref(genericUpgrade.canPurchase)) {
- return;
- }
- payRequirements(upgrade.requirements);
- bought.value = true;
- genericUpgrade.onPurchase?.();
- };
-
- const visibilityRequirement = createVisibilityRequirement(upgrade as GenericUpgrade);
- if (Array.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");
-
- for (const decorator of decorators) {
- decorator.postConstruct?.(upgrade);
- }
-
- const decoratedProps = decorators.reduce(
- (current, next) => Object.assign(current, next.getGatheredProps?.(upgrade)),
- {}
- );
- upgrade[GatherProps] = function (this: GenericUpgrade) {
- const {
- display,
- visibility,
- style,
- classes,
- requirements,
- canPurchase,
- bought,
- mark,
- id,
- purchase
- } = this;
- return {
- display,
- visibility,
- style: unref(style),
- classes,
- requirements,
- canPurchase,
- bought,
- mark,
- id,
- purchase,
- ...decoratedProps
- };
- };
-
- return upgrade as unknown as Upgrade;
- });
-}
-
-/**
- * Utility to auto purchase a list of upgrades whenever they're affordable.
- * @param layer The layer the upgrades are apart of
- * @param autoActive Whether or not the upgrades should currently be auto-purchasing
- * @param upgrades The specific upgrades to upgrade. If unspecified, uses all upgrades on the layer.
- */
-export function setupAutoPurchase(
- layer: GenericLayer,
- autoActive: Computable,
- upgrades: GenericUpgrade[] = []
-): void {
- 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/boards/Draggable.vue b/src/game/boards/Draggable.vue
index b181b8b..9b4a2fa 100644
--- a/src/game/boards/Draggable.vue
+++ b/src/game/boards/Draggable.vue
@@ -7,23 +7,17 @@
@mouseup="e => mouseUp(e)"
@touchend.passive="e => mouseUp(e)"
>
-
+
diff --git a/src/game/boards/board.tsx b/src/game/boards/board.tsx
index b877d92..a0b90ac 100644
--- a/src/game/boards/board.tsx
+++ b/src/game/boards/board.tsx
@@ -1,13 +1,11 @@
-import { Component, GatherProps, GenericComponent, jsx } from "features/feature";
import { globalBus } from "game/events";
import { Persistent, persistent } from "game/persistence";
import type { PanZoom } from "panzoom";
import { Direction, isFunction } from "util/common";
-import type { Computable, ProcessedComputable } from "util/computed";
-import { convertComputable } from "util/computed";
+import { processGetter } from "util/computed";
import { VueFeature } from "util/vue";
-import type { ComponentPublicInstance, Ref } from "vue";
-import { computed, nextTick, ref, unref, watchEffect } from "vue";
+import type { ComponentPublicInstance, MaybeRef, MaybeRefOrGetter, Ref } from "vue";
+import { computed, ref, unref, watchEffect } from "vue";
import panZoom from "vue-panzoom";
import { JSX } from "vue/jsx-runtime";
import Board from "./Board.vue";
@@ -20,10 +18,10 @@ globalBus.on("setupVue", app => panZoom.install(app));
export type NodePosition = { x: number; y: number };
/**
- * A type representing a computable value for a node on the board. Used for node types to return different values based on the given node and the state of the board.
+ * A type representing a MaybeRefOrGetter value for a node on the board. Used for node types to return different values based on the given node and the state of the board.
*/
-export type NodeComputable =
- | Computable
+export type NodeMaybeRefOrGetter =
+ | MaybeRefOrGetter
| ((node: T, ...args: S) => R);
/**
@@ -32,11 +30,11 @@ export type NodeComputable =
* @param node The node to get the property of
*/
export function unwrapNodeRef(
- property: NodeComputable,
+ property: NodeMaybeRefOrGetter,
node: T,
...args: S
): R {
- return isFunction>(property)
+ return isFunction>(property)
? property(node, ...args)
: unref(property);
}
@@ -46,8 +44,8 @@ export function unwrapNodeRef(
* @param nodes The list of current nodes with IDs as properties
* @returns A computed ref that will give the value of the next unique ID
*/
-export function setupUniqueIds(nodes: Computable<{ id: number }[]>) {
- const processedNodes = convertComputable(nodes);
+export function setupUniqueIds(nodes: MaybeRefOrGetter<{ id: number }[]>) {
+ const processedNodes = processGetter(nodes);
return computed(() => Math.max(-1, ...unref(processedNodes).map(node => node.id)) + 1);
}
@@ -60,9 +58,9 @@ export interface DraggableNodeOptions {
/** Setter function to update the position of a node. */
setPosition: (node: T, position: NodePosition) => void;
/** A list of nodes that the currently dragged node can be dropped upon. */
- receivingNodes?: NodeComputable;
+ receivingNodes?: NodeMaybeRefOrGetter;
/** The maximum distance (in pixels, before zoom) away a node can be and still drop onto a receiving node. */
- dropAreaRadius?: NodeComputable;
+ dropAreaRadius?: NodeMaybeRefOrGetter;
/** A callback for when a node gets dropped upon a receiving node. */
onDrop?: (acceptingNode: T, draggingNode: T) => void;
}
@@ -262,12 +260,12 @@ export interface MakeDraggableOptions {
* @param element The vue feature to make draggable.
* @param options The options to configure the dragging behavior.
*/
-export function makeDraggable(
- element: T,
- options: MakeDraggableOptions
-): asserts element is T & { position: Persistent } {
+export function makeDraggable(
+ element: VueFeature,
+ options: MakeDraggableOptions
+): asserts element is VueFeature & { position: Persistent } {
const position = persistent(options.initialPosition ?? { x: 0, y: 0 });
- (element as T & { position: Persistent }).position = position;
+ (element as VueFeature & { position: Persistent }).position = position;
const computedPosition = computed(() => {
if (options.nodeBeingDragged.value === options.id) {
return {
@@ -292,36 +290,25 @@ export function makeDraggable(
options.onMouseUp?.(e);
}
- nextTick(() => {
- const elementComponent = element[Component];
- const elementGatherProps = element[GatherProps].bind(element);
- element[Component] = Draggable as GenericComponent;
- element[GatherProps] = function gatherTooltipProps(this: typeof options) {
- return {
- element: {
- [Component]: elementComponent,
- [GatherProps]: elementGatherProps
- },
- mouseDown: handleMouseDown,
- mouseUp: handleMouseUp,
- position: computedPosition
- };
- }.bind(options);
- });
+ element.wrappers.push(el => (
+
+ {el}
+
+ ));
}
/** An object that configures how to setup a list of actions using {@link setupActions}. */
export interface SetupActionsOptions {
/** The node to display actions upon, or undefined when the actions should be hidden. */
- node: Computable;
+ node: MaybeRefOrGetter;
/** Whether or not to currently display the actions. */
- shouldShowActions?: NodeComputable;
+ shouldShowActions?: NodeMaybeRefOrGetter;
/** The list of actions to display. Actions are arbitrary JSX elements. */
- actions: NodeComputable JSX.Element)[]>;
+ actions: NodeMaybeRefOrGetter JSX.Element)[]>;
/** The distance from the node to place the actions. */
- distance: NodeComputable;
+ distance: NodeMaybeRefOrGetter;
/** The arc length to place between actions, in radians. */
- arcLength?: NodeComputable;
+ arcLength?: NodeMaybeRefOrGetter;
}
/**
@@ -330,8 +317,8 @@ export interface SetupActionsOptions {
* @returns A JSX function to render the actions.
*/
export function setupActions(options: SetupActionsOptions) {
- const node = convertComputable(options.node);
- return jsx(() => {
+ const node = processGetter(options.node) as MaybeRef;
+ return computed(() => {
const currNode = unref(node);
if (currNode == null) {
return "";
diff --git a/src/game/events.ts b/src/game/events.ts
index 9e74714..c89cca3 100644
--- a/src/game/events.ts
+++ b/src/game/events.ts
@@ -1,7 +1,7 @@
import type { Settings } from "game/settings";
import { createNanoEvents } from "nanoevents";
import type { App } from "vue";
-import type { GenericLayer } from "./layers";
+import type { Layer } from "./layers";
import state from "./state";
/** All types of events able to be sent or emitted from the global event bus. */
@@ -11,12 +11,12 @@ export interface GlobalEvents {
* @param layer The layer being added.
* @param saveData The layer's save data object within player.
*/
- addLayer: (layer: GenericLayer, saveData: Record) => void;
+ addLayer: (layer: Layer, saveData: Record) => void;
/**
* Sent whenever a layer is removed.
* @param layer The layer being removed.
*/
- removeLayer: (layer: GenericLayer) => void;
+ removeLayer: (layer: Layer) => void;
/**
* Sent every game tick. Runs the life cycle of the project.
* @param diff The delta time since last tick, in ms.
diff --git a/src/game/formulas/formulas.ts b/src/game/formulas/formulas.ts
index d9b0a78..03a53a7 100644
--- a/src/game/formulas/formulas.ts
+++ b/src/game/formulas/formulas.ts
@@ -1,7 +1,7 @@
import { Resource } from "features/resources/resource";
import { NonPersistent } from "game/persistence";
import Decimal, { DecimalSource, format } from "util/bignum";
-import { Computable, ProcessedComputable, convertComputable } from "util/computed";
+import { MaybeRefOrGetter, MaybeRef, processGetter } from "util/computed";
import { Ref, computed, ref, unref } from "vue";
import * as ops from "./operations";
import type {
@@ -60,7 +60,7 @@ export abstract class InternalFormula | undefined;
+ public readonly innermostVariable: MaybeRef | undefined;
constructor(options: FormulaOptions) {
let readonlyProperties;
@@ -93,7 +93,7 @@ export abstract class InternalFormula;
+ variable: MaybeRef;
}): InternalFormulaProperties {
return {
inputs: [variable] as T,
@@ -207,7 +207,7 @@ export abstract class InternalFormula): InvertibleIntegralFormula {
+ public static constant(value: MaybeRef): InvertibleIntegralFormula {
return new Formula({ inputs: [value] });
}
@@ -215,7 +215,7 @@ export abstract class InternalFormula): InvertibleIntegralFormula {
+ public static variable(value: MaybeRef): InvertibleIntegralFormula {
return new Formula({ variable: value });
}
@@ -248,11 +248,11 @@ export abstract class InternalFormula,
+ start: MaybeRefOrGetter,
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula
) {
const formula = formulaModifier(Formula.variable(0));
- const processedStart = convertComputable(start);
+ const processedStart = processGetter(start);
function evalStep(lhs: DecimalSource) {
if (Decimal.lt(lhs, unref(processedStart))) {
return lhs;
@@ -293,7 +293,7 @@ export abstract class InternalFormula,
+ condition: MaybeRefOrGetter,
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula,
elseFormulaModifier?: (value: InvertibleIntegralFormula) => GenericFormula
) {
@@ -301,7 +301,7 @@ export abstract class InternalFormula,
+ condition: MaybeRefOrGetter,
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula,
elseFormulaModifier?: (value: InvertibleIntegralFormula) => GenericFormula
) {
@@ -909,20 +909,20 @@ export abstract class InternalFormula,
+ start: MaybeRefOrGetter,
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula
) {
return Formula.step(this, start, formulaModifier);
}
public if(
- condition: Computable,
+ condition: MaybeRefOrGetter,
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula
) {
return Formula.if(this, condition, formulaModifier);
}
public conditional(
- condition: Computable,
+ condition: MaybeRefOrGetter,
formulaModifier: (value: InvertibleIntegralFormula) => GenericFormula
) {
return Formula.if(this, condition, formulaModifier);
@@ -1443,13 +1443,13 @@ export function findNonInvertible(formula: GenericFormula): GenericFormula | nul
export function calculateMaxAffordable(
formula: GenericFormula,
resource: Resource,
- cumulativeCost: Computable = true,
- directSum?: Computable,
- maxBulkAmount: Computable = Decimal.dInf
+ cumulativeCost: MaybeRefOrGetter = true,
+ directSum?: MaybeRefOrGetter,
+ maxBulkAmount: MaybeRefOrGetter = Decimal.dInf
) {
- const computedCumulativeCost = convertComputable(cumulativeCost);
- const computedDirectSum = convertComputable(directSum);
- const computedmaxBulkAmount = convertComputable(maxBulkAmount);
+ const computedCumulativeCost = processGetter(cumulativeCost);
+ const computedDirectSum = processGetter(directSum);
+ const computedmaxBulkAmount = processGetter(maxBulkAmount);
return computed(() => {
const maxBulkAmount = unref(computedmaxBulkAmount);
if (Decimal.eq(maxBulkAmount, 1)) {
diff --git a/src/game/formulas/types.d.ts b/src/game/formulas/types.d.ts
index cc185a9..3f98f55 100644
--- a/src/game/formulas/types.d.ts
+++ b/src/game/formulas/types.d.ts
@@ -1,10 +1,10 @@
import { InternalFormula } from "game/formulas/formulas";
import { DecimalSource } from "util/bignum";
-import { ProcessedComputable } from "util/computed";
+import { MaybeRef } from "util/computed";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type GenericFormula = InternalFormula;
-type FormulaSource = ProcessedComputable | GenericFormula;
+type FormulaSource = MaybeRef | GenericFormula;
type InvertibleFormula = GenericFormula & {
invert: NonNullable;
};
@@ -38,7 +38,7 @@ type SubstitutionFunction = (
) => GenericFormula;
type VariableFormulaOptions = {
- variable: ProcessedComputable;
+ variable: MaybeRef;
description?: string;
};
type ConstantFormulaOptions = {
@@ -67,7 +67,7 @@ type InternalFormulaProperties = {
internalIntegrate?: IntegrateFunction;
internalIntegrateInner?: IntegrateFunction;
applySubstitution?: SubstitutionFunction;
- innermostVariable?: ProcessedComputable;
+ innermostVariable?: MaybeRef;
description?: string;
};
diff --git a/src/game/layers.tsx b/src/game/layers.tsx
index 7e82ead..a5f3fe2 100644
--- a/src/game/layers.tsx
+++ b/src/game/layers.tsx
@@ -1,28 +1,26 @@
import Modal from "components/modals/Modal.vue";
-import type {
- CoercableComponent,
- JSXFunction,
- OptionsFunc,
- Replace,
- StyleValue
-} from "features/feature";
-import { jsx, setDefault } from "features/feature";
+import type { OptionsFunc, Replace } from "features/feature";
import { globalBus } from "game/events";
import type { Persistent } from "game/persistence";
import { persistent } from "game/persistence";
import player from "game/player";
import type { Emitter } from "nanoevents";
import { createNanoEvents } from "nanoevents";
-import type {
- Computable,
- GetComputableType,
- GetComputableTypeWithDefault,
- ProcessedComputable
-} from "util/computed";
-import { processComputable } from "util/computed";
+import { ProcessedRefOrGetter, processGetter } from "util/computed";
import { createLazyProxy } from "util/proxies";
-import { computed, InjectionKey, Ref } from "vue";
-import { ref, shallowReactive, unref } from "vue";
+import { Renderable } from "util/vue";
+import {
+ computed,
+ type CSSProperties,
+ InjectionKey,
+ MaybeRef,
+ MaybeRefOrGetter,
+ Ref,
+ ref,
+ shallowReactive,
+ unref
+} from "vue";
+import { JSX } from "vue/jsx-runtime";
/** A feature's node in the DOM that has its size tracked. */
export interface FeatureNode {
@@ -74,12 +72,12 @@ export interface LayerEvents {
* A reference to all the current layers.
* It is shallow reactive so it will update when layers are added or removed, but not interfere with the existing refs within each layer.
*/
-export const layers: Record | undefined> = shallowReactive({});
+export const layers: Record> = shallowReactive({});
declare global {
/** Augment the window object so the layers can be accessed from the console. */
interface Window {
- layers: Record | undefined>;
+ layers: Record | undefined>;
}
}
window.layers = layers;
@@ -106,42 +104,42 @@ export interface Position {
*/
export interface LayerOptions {
/** The color of the layer, used to theme the entire layer's display. */
- color?: Computable;
+ color?: MaybeRefOrGetter;
/**
* The layout of this layer's features.
* When the layer is open in {@link game/player.PlayerData.tabs}, this is the content that is displayed.
*/
- display: Computable;
+ display: MaybeRefOrGetter;
/** An object of classes that should be applied to the display. */
- classes?: Computable>;
+ classes?: MaybeRefOrGetter>;
/** Styles that should be applied to the display. */
- style?: Computable;
+ style?: MaybeRefOrGetter;
/**
* The name of the layer, used on minimized tabs.
* Defaults to {@link BaseLayer.id}.
*/
- name?: Computable;
+ name?: MaybeRefOrGetter;
/**
* Whether or not the layer can be minimized.
* Defaults to true.
*/
- minimizable?: Computable;
+ minimizable?: MaybeRefOrGetter;
/**
* The layout of this layer's features.
* When the layer is open in {@link game/player.PlayerData.tabs}, but the tab is {@link Layer.minimized} this is the content that is displayed.
*/
- minimizedDisplay?: Computable;
+ minimizedDisplay?: MaybeRefOrGetter;
/**
* Whether or not to force the go back button to be hidden.
* If true, go back will be hidden regardless of {@link data/projInfo.allowGoBack}.
*/
- forceHideGoBack?: Computable;
+ forceHideGoBack?: MaybeRefOrGetter;
/**
* A CSS min-width value that is applied to the layer.
* Can be a number, in which case the unit is assumed to be px.
* Defaults to 600px.
*/
- minWidth?: Computable;
+ minWidth?: MaybeRefOrGetter;
}
/** The properties that are added onto a processed {@link LayerOptions} to create a {@link Layer} */
@@ -165,28 +163,18 @@ export interface BaseLayer {
}
/** An unit of game content. Displayed to the user as a tab or modal. */
-export type Layer = Replace<
- T & BaseLayer,
+export type Layer = Replace<
+ Replace,
{
- color: GetComputableType;
- display: GetComputableType;
- classes: GetComputableType;
- style: GetComputableType;
- name: GetComputableTypeWithDefault;
- minWidth: GetComputableTypeWithDefault;
- minimizable: GetComputableTypeWithDefault;
- minimizedDisplay: GetComputableType;
- forceHideGoBack: GetComputableType;
- }
->;
-
-/** A type that matches any valid {@link Layer} object. */
-export type GenericLayer = Replace<
- Layer,
- {
- name: ProcessedComputable;
- minWidth: ProcessedComputable;
- minimizable: ProcessedComputable;
+ color?: ProcessedRefOrGetter;
+ display: ProcessedRefOrGetter;
+ classes?: ProcessedRefOrGetter;
+ style?: ProcessedRefOrGetter;
+ name: MaybeRef;
+ minWidth: MaybeRef;
+ minimizable: MaybeRef;
+ minimizedDisplay?: ProcessedRefOrGetter;
+ forceHideGoBack?: ProcessedRefOrGetter;
}
>;
@@ -206,72 +194,85 @@ export const addingLayers: string[] = [];
export function createLayer(
id: string,
optionsFunc: OptionsFunc
-): Layer {
+) {
return createLazyProxy(() => {
- const layer = {} as T & Partial;
- const emitter = (layer.emitter = createNanoEvents());
- layer.on = emitter.on.bind(emitter);
- layer.emit = emitter.emit.bind(emitter) as (
- ...args: [K, ...Parameters]
- ) => void;
- layer.nodes = ref({});
- layer.id = id;
-
+ const emitter = createNanoEvents();
addingLayers.push(id);
persistentRefs[id] = new Set();
- layer.minimized = persistent(false, false);
- Object.assign(layer, optionsFunc.call(layer, layer as BaseLayer));
+
+ const baseLayer = {
+ id,
+ emitter,
+ ...emitter,
+ nodes: ref({}),
+ minimized: persistent(false, false)
+ } satisfies BaseLayer;
+
+ const options = optionsFunc.call(baseLayer, baseLayer);
+ const {
+ color,
+ display,
+ classes,
+ style: _style,
+ name,
+ forceHideGoBack,
+ minWidth,
+ minimizable,
+ minimizedDisplay,
+ ...props
+ } = options;
if (
addingLayers[addingLayers.length - 1] == null ||
addingLayers[addingLayers.length - 1] !== id
) {
throw new Error(
- `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 ${id}`
);
}
addingLayers.pop();
- processComputable(layer as T, "color");
- processComputable(layer as T, "display");
- processComputable(layer as T, "classes");
- processComputable(layer as T, "style");
- processComputable(layer as T, "name");
- setDefault(layer, "name", layer.id);
- processComputable(layer as T, "minWidth");
- setDefault(layer, "minWidth", 600);
- processComputable(layer as T, "minimizable");
- setDefault(layer, "minimizable", true);
- processComputable(layer as T, "minimizedDisplay");
+ const style = processGetter(_style);
- const style = layer.style as ProcessedComputable | undefined;
- layer.style = computed(() => {
- let width = unref(layer.minWidth as ProcessedComputable);
- if (typeof width === "number" || !Number.isNaN(parseInt(width))) {
- width = width + "px";
- }
- return [
- unref(style) ?? "",
- layer.minimized?.value
- ? {
- flexGrow: "0",
- flexShrink: "0",
- width: "60px",
- minWidth: "",
- flexBasis: "",
- margin: "0"
- }
- : {
- flexGrow: "",
- flexShrink: "",
- width: "",
- minWidth: width,
- flexBasis: width,
- margin: ""
- }
- ];
- }) as Ref;
+ const layer = {
+ ...baseLayer,
+ ...(props as Omit),
+ color: processGetter(color),
+ display: processGetter(display),
+ classes: processGetter(classes),
+ style: computed((): CSSProperties => {
+ let width = unref(layer.minWidth);
+ if (typeof width === "number" || !Number.isNaN(parseInt(width))) {
+ width = width + "px";
+ }
+ return {
+ ...unref(style),
+ ...(baseLayer.minimized.value
+ ? {
+ flexGrow: "0",
+ flexShrink: "0",
+ width: "60px",
+ minWidth: "",
+ flexBasis: "",
+ margin: "0"
+ }
+ : {
+ flexGrow: "",
+ flexShrink: "",
+ width: "",
+ minWidth: width,
+ flexBasis: width,
+ margin: ""
+ })
+ };
+ }),
+ name: processGetter(name) ?? id,
+ forceHideGoBack: processGetter(forceHideGoBack),
+ minWidth: processGetter(minWidth) ?? 600,
+ minimizable: processGetter(minimizable) ?? true,
+ minimizedDisplay: processGetter(minimizedDisplay)
+ } satisfies Layer;
- return layer as unknown as Layer;
+ return layer;
});
}
@@ -284,11 +285,11 @@ export function createLayer(
* @param player The player data object, which will have a data object for this layer.
*/
export function addLayer(
- layer: GenericLayer,
+ layer: Layer,
player: { layers?: Record> }
): void {
console.info("Adding layer", layer.id);
- if (layers[layer.id]) {
+ if (layers[layer.id] != null) {
console.error(
"Attempted to add layer with same ID as existing layer",
layer.id,
@@ -297,7 +298,7 @@ export function addLayer(
return;
}
- setDefault(player, "layers", {});
+ player.layers ??= {};
if (player.layers[layer.id] == null) {
player.layers[layer.id] = {};
}
@@ -310,7 +311,7 @@ export function addLayer(
* Convenience method for getting a layer by its ID with correct typing.
* @param layerID The ID of the layer to get.
*/
-export function getLayer(layerID: string): T {
+export function getLayer(layerID: string): T {
return layers[layerID] as T;
}
@@ -319,11 +320,11 @@ export function getLayer(layerID: string): T {
* Note that accessing a layer/its properties does NOT require it to be enabled.
* @param layer The layer to remove.
*/
-export function removeLayer(layer: GenericLayer): void {
+export function removeLayer(layer: Layer): void {
console.info("Removing layer", layer.id);
globalBus.emit("removeLayer", layer);
- layers[layer.id] = undefined;
+ delete layers[layer.id];
}
/**
@@ -331,7 +332,7 @@ export function removeLayer(layer: GenericLayer): void {
* This is useful for layers with dynamic content, to ensure persistent refs are correctly configured.
* @param layer Layer to remove and then re-add
*/
-export function reloadLayer(layer: GenericLayer): void {
+export function reloadLayer(layer: Layer): void {
removeLayer(layer);
// Re-create layer
@@ -343,14 +344,14 @@ export function reloadLayer(layer: GenericLayer): void {
* Returns the modal itself, which can be rendered anywhere you need, as well as a function to open the modal.
* @param layer The layer to display in the modal.
*/
-export function setupLayerModal(layer: GenericLayer): {
+export function setupLayerModal(layer: Layer): {
openModal: VoidFunction;
- modal: JSXFunction;
+ modal: Ref;
} {
const showModal = ref(false);
return {
openModal: () => (showModal.value = true),
- modal: jsx(() => (
+ modal: computed(() => (
(showModal.value = value)}
diff --git a/src/game/modifiers.tsx b/src/game/modifiers.tsx
index 55efccb..995b300 100644
--- a/src/game/modifiers.tsx
+++ b/src/game/modifiers.tsx
@@ -1,15 +1,13 @@
import "components/common/modifiers.css";
-import type { CoercableComponent, OptionsFunc } from "features/feature";
-import { jsx } from "features/feature";
+import type { OptionsFunc } from "features/feature";
import settings from "game/settings";
import type { DecimalSource } from "util/bignum";
import Decimal, { formatSmall } from "util/bignum";
import type { RequiredKeys, WithRequired } from "util/common";
-import type { Computable, ProcessedComputable } from "util/computed";
-import { convertComputable } from "util/computed";
+import { processGetter } from "util/computed";
import { createLazyProxy } from "util/proxies";
-import { renderJSX } from "util/vue";
-import { computed, unref } from "vue";
+import { render, Renderable } from "util/vue";
+import { computed, MaybeRef, MaybeRefOrGetter, unref } from "vue";
import Formula from "./formulas/formulas";
import { FormulaSource, GenericFormula } from "./formulas/types";
@@ -30,12 +28,12 @@ export interface Modifier {
* Whether or not this modifier should be considered enabled.
* Typically for use with modifiers passed into {@link createSequentialModifier}.
*/
- enabled?: ProcessedComputable;
+ enabled?: MaybeRef;
/**
* A description of this modifier.
* @see {@link createModifierSection}.
*/
- description?: ProcessedComputable;
+ description?: MaybeRef;
}
/** Utility type that represents the output of all modifiers that represent a single operation. */
@@ -47,11 +45,11 @@ export type OperationModifier