diff --git a/package-lock.json b/package-lock.json index f0135da..cfa7b70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -27,7 +27,7 @@ "vite": "^5.1.8", "vite-plugin-pwa": "^0.20.5", "vite-tsconfig-paths": "^4.3.0", - "vue": "^3.5.12", + "vue": "^3.5.13", "vue-next-select": "^2.10.5", "vue-panzoom": "https://github.com/thepaperpilot/vue-panzoom.git", "vue-textarea-autosize": "^1.1.1", @@ -45,7 +45,7 @@ "eslint": "^8.57.0", "jsdom": "^24.0.0", "prettier": "^3.2.5", - "typescript": "^5.4.2", + "typescript": "~5.5.4", "vitest": "^1.4.0", "vue-tsc": "^2.0.6" }, @@ -2497,49 +2497,49 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", - "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.12", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", - "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "dependencies": { - "@vue/compiler-core": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", - "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.12", - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", - "postcss": "^8.4.47", + "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", - "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-vue2": { @@ -2615,49 +2615,49 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", - "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "dependencies": { - "@vue/shared": "3.5.12" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", - "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", - "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/runtime-core": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", - "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "dependencies": { - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.5.12" + "vue": "3.5.13" } }, "node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==" + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==" }, "node_modules/acorn": { "version": "8.13.0", @@ -5693,9 +5693,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -5712,7 +5712,7 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -6853,9 +6853,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -7233,15 +7233,15 @@ "dev": true }, "node_modules/vue": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", - "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-sfc": "3.5.12", - "@vue/runtime-dom": "3.5.12", - "@vue/server-renderer": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" diff --git a/package.json b/package.json index 2f5e175..58ecb2b 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "vite": "^5.1.8", "vite-plugin-pwa": "^0.20.5", "vite-tsconfig-paths": "^4.3.0", - "vue": "^3.5.12", + "vue": "^3.5.13", "vue-next-select": "^2.10.5", "vue-panzoom": "https://github.com/thepaperpilot/vue-panzoom.git", "vue-textarea-autosize": "^1.1.1", @@ -52,7 +52,7 @@ "eslint": "^8.57.0", "jsdom": "^24.0.0", "prettier": "^3.2.5", - "typescript": "^5.4.2", + "typescript": "~5.5.4", "vitest": "^1.4.0", "vue-tsc": "^2.0.6" }, diff --git a/src/components/common/features.css b/src/components/common/features.css index 9ed07a7..196fa09 100644 --- a/src/components/common/features.css +++ b/src/components/common/features.css @@ -14,6 +14,10 @@ button.feature, transition: all 0.5s, z-index 0s 0.5s; } +.feature button { + position: relative; +} + button.can, .can button { background-color: var(--layer-color); diff --git a/src/components/common/table.css b/src/components/common/table.css index 54d043a..ac5518e 100644 --- a/src/components/common/table.css +++ b/src/components/common/table.css @@ -174,39 +174,3 @@ .row.mergeAdjacent > .table:last-child > .col.mergeAdjacent > :first-child .feature button { border-radius: 0 0 0 var(--border-radius); } - -.row-grid.mergeAdjacent > .feature:not(.dontMerge), -.row-grid.mergeAdjacent > .tooltip-container > .feature:not(.dontMerge) { - margin-left: 0; - margin-right: 0; - margin-bottom: 0; - margin-top: 0; - border-radius: 0; -} - -.row-grid.mergeAdjacent > .feature:not(.dontMerge):last-child, -.row-grid.mergeAdjacent > .tooltip-container:last-child > .feature:not(.dontMerge) { - border-radius: 0 0 0 0; -} - - -.row-grid.mergeAdjacent > .feature:not(.dontMerge):first-child, -.row-grid.mergeAdjacent > .tooltip-container:first-child > .feature:not(.dontMerge) { - border-radius: 0 0 0 0; -} - -.table-grid > .row-grid.mergeAdjacent:last-child > .feature:not(.dontMerge):first-child { - border-radius: 0 0 0 var(--border-radius); -} - -.table-grid > .row-grid.mergeAdjacent:first-child > .feature:not(.dontMerge):last-child { - border-radius: 0 var(--border-radius) 0 0; -} - -.table-grid > .row-grid.mergeAdjacent:first-child > .feature:not(.dontMerge):first-child { - border-radius: var(--border-radius) 0 0 0; -} - -.table-grid > .row-grid.mergeAdjacent:last-child > .feature:not(.dontMerge):last-child { - border-radius: 0 0 var(--border-radius) 0; -} diff --git a/src/components/modals/GameOverScreen.vue b/src/components/modals/GameOverScreen.vue index e7e375a..be86c8c 100644 --- a/src/components/modals/GameOverScreen.vue +++ b/src/components/modals/GameOverScreen.vue @@ -18,12 +18,18 @@ updates!
-
+
discord {{ discordName }}
+
+ + discord + Profectus & Friends + +
diff --git a/src/data/common.tsx b/src/data/common.tsx index 01d3662..289e111 100644 --- a/src/data/common.tsx +++ b/src/data/common.tsx @@ -3,7 +3,7 @@ import { Achievement } from "features/achievements/achievement"; import type { Clickable, ClickableOptions } from "features/clickables/clickable"; import { createClickable } from "features/clickables/clickable"; import { Conversion } from "features/conversion"; -import { getFirstFeature, type OptionsFunc, type Replace } from "features/feature"; +import { getFirstFeature } from "features/feature"; import { displayResource, Resource } from "features/resources/resource"; import type { Tree, TreeNode, TreeNodeOptions } from "features/trees/tree"; import { createTreeNode } from "features/trees/tree"; @@ -21,6 +21,7 @@ import { processGetter } from "util/computed"; import { render, Renderable, renderCol } from "util/vue"; import type { ComputedRef, MaybeRef, MaybeRefOrGetter } from "vue"; import { computed, ref, unref } from "vue"; +import { JSX } from "vue/jsx-runtime"; import "./common.css"; /** An object that configures a {@link ResetButton} */ @@ -61,24 +62,37 @@ export interface ResetButtonOptions extends ClickableOptions { * It will show how much can be converted currently, and can show when that amount will go up, as well as handle only being clickable when a sufficient amount of currency can be gained. * Assumes this button is associated with a specific node on a tree, and triggers that tree's reset propagation. */ -export type ResetButton = Replace< - Clickable, - { - resetDescription: MaybeRef; - showNextAt: MaybeRef; - minimumGain: MaybeRef; - } ->; +export interface ResetButton extends Clickable { + /** The conversion the button uses to calculate how much resources will be gained on click */ + conversion: Conversion; + /** The tree this reset button is apart of */ + tree: Tree; + /** The specific tree node associated with this reset button */ + treeNode: TreeNode; + /** + * Text to display on low conversion amounts, describing what "resetting" is in this context. + * Defaults to "Reset for ". + */ + resetDescription?: MaybeRef; + /** Whether or not to show how much currency would be required to make the gain amount increase. */ + showNextAt?: MaybeRef; + /** + * When {@link canClick} is left to its default, minimumGain is used to only enable the reset button when a sufficient amount of currency to gain is available. + */ + minimumGain?: MaybeRef; + /** A persistent ref to track how much time has passed since the last time this tree node was reset. */ + resetTime?: Persistent; +} /** * Lazily creates a reset button with the given options. * @param optionsFunc A function that returns the options object for this reset button. */ export function createResetButton( - optionsFunc: OptionsFunc + optionsFunc: () => T ) { - const resetButton = createClickable(feature => { - const options = optionsFunc.call(feature, feature); + const resetButton = createClickable(() => { + const options = optionsFunc(); const { conversion, tree, @@ -113,41 +127,43 @@ export function createResetButton ( - - {unref(resetButton.resetDescription)} - - {displayResource( - conversion.gainResource, - Decimal.max( - unref(conversion.actualGain), - unref(resetButton.minimumGain) - ) - )} - {" "} - {conversion.gainResource.displayName} - {unref(resetButton.showNextAt as MaybeRef) != null ? ( -
-
- {unref(conversion.buyMax) ? "Next:" : "Req:"}{" "} + computed( + (): JSX.Element => ( + + {unref(resetButton.resetDescription)} + {displayResource( - conversion.baseResource, - !unref(conversion.buyMax) && - Decimal.gte(unref(conversion.actualGain), 1) - ? unref(conversion.currentAt) - : unref(conversion.nextAt) - )}{" "} - {conversion.baseResource.displayName} -
- ) : null} -
- )), - onClick: function (e) { + conversion.gainResource, + Decimal.max( + unref(conversion.actualGain), + unref(resetButton.minimumGain) + ) + )} + {" "} + {conversion.gainResource.displayName} + {unref(resetButton.showNextAt) != null ? ( +
+
+ {unref(conversion.buyMax) ? "Next:" : "Req:"}{" "} + {displayResource( + conversion.baseResource, + !unref(conversion.buyMax) && + Decimal.gte(unref(conversion.actualGain), 1) + ? unref(conversion.currentAt) + : unref(conversion.nextAt) + )}{" "} + {conversion.baseResource.displayName} +
+ ) : null} + + ) + ), + onClick: function (e?: MouseEvent | TouchEvent) { if (unref(resetButton.canClick) === false) { return; } conversion.convert(); - tree.reset(resetButton.treeNode); + tree.reset(treeNode); if (resetTime) { resetTime.value = resetTime[DefaultValue]; } @@ -173,21 +189,23 @@ export interface LayerTreeNodeOptions extends TreeNodeOptions { } /** A tree node that is associated with a given layer, and which opens the layer when clicked. */ -export type LayerTreeNode = Replace< - TreeNode, - { - layerID: string; - append: MaybeRef; - } ->; +export interface LayerTreeNode extends TreeNode { + /** The ID of the layer this tree node is associated with */ + layerID: string; + /** Whether or not to append the layer to the tabs list. + * If set to false, then the tree node will instead always remove all tabs to its right and then add the layer tab. + * Defaults to true. + */ + append?: MaybeRef; +} /** * Lazily creates a tree node that's associated with a specific layer, with the given options. * @param optionsFunc A function that returns the options object for this tree node. */ -export function createLayerTreeNode(optionsFunc: OptionsFunc) { - const layerTreeNode = createTreeNode(feature => { - const options = optionsFunc.call(feature, feature); +export function createLayerTreeNode(optionsFunc: () => T) { + const layerTreeNode = createTreeNode(() => { + const options = optionsFunc(); const { display, append, layerID, ...props } = options; return { diff --git a/src/data/layers/board.tsx b/src/data/layers/board.tsx new file mode 100644 index 0000000..d663598 --- /dev/null +++ b/src/data/layers/board.tsx @@ -0,0 +1,424 @@ +import { createUpgrade } from "features/clickables/upgrade"; +import { createResource } from "features/resources/resource"; +import Board from "game/boards/Board.vue"; +import CircleProgress from "game/boards/CircleProgress.vue"; +import SVGNode from "game/boards/SVGNode.vue"; +import SquareProgress from "game/boards/SquareProgress.vue"; +import { + Draggable, + MakeDraggableOptions, + NodePosition, + makeDraggable, + placeInAvailableSpace, + setupActions, + setupDraggableNode, + setupUniqueIds +} from "game/boards/board"; +import type { BaseLayer } from "game/layers"; +import { createLayer } from "game/layers"; +import { persistent } from "game/persistence"; +import { createCostRequirement } from "game/requirements"; +import { render } from "util/vue"; +import { ComponentPublicInstance, computed, ref, watch } from "vue"; +import { setupSelectable } from "../common"; + +const board = createLayer("board", function (this: BaseLayer) { + type ANode = NodePosition & { id: number; links: number[]; type: "anode"; z: number }; + type BNode = NodePosition & { id: number; links: number[]; type: "bnode"; z: number }; + type CNode = typeof cNode & { draggable: Draggable }; + type NodeTypes = ANode | BNode; + + const board = ref>(); + + const { select, deselect, selected } = setupSelectable(); + const { + select: selectAction, + deselect: deselectAction, + selected: selectedAction + } = setupSelectable(); + + watch(selected, selected => { + if (selected == null) { + deselectAction(); + } + }); + + const { + startDrag, + endDrag, + drag, + nodeBeingDragged, + hasDragged, + receivingNodes, + receivingNode, + dragDelta + } = setupDraggableNode({ + board, + getPosition(id) { + return nodesById.value[id] ?? (cNode as CNode).draggable.position.value; + }, + setPosition(id, position) { + const node = nodesById.value[id] ?? (cNode as CNode).draggable.position.value; + node.x = position.x; + node.y = position.y; + } + }); + + // a nodes can be slotted into b nodes to draw a branch between them, with limited connections + // a nodes can be selected and have an action to spawn a b node, and vice versa + // Newly spawned nodes should find a safe spot to spawn, and display a link to their creator + // a nodes use all the stuff circles used to have, and b diamonds + // c node also exists but is a single Upgrade element that cannot be selected, but can be dragged + // d nodes are a performance test - 1000 simple nodes that have no interactions + // Make all nodes animate in (decorator? `fadeIn(feature)?) + const nodes = persistent<(ANode | BNode)[]>([ + { id: 0, x: 0, y: 0, z: 0, links: [], type: "anode" } + ]); + const nodesById = computed>(() => + nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {}) + ); + function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) { + const oldZ = node.z; + nodes.value.forEach(node => { + if (node.z > oldZ) { + node.z--; + } + }); + node.z = nextId.value; + if (nodeBeingDragged.value == null) { + startDrag(e, node.id); + } + deselect(); + } + function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) { + if (!hasDragged.value) { + endDrag(); + if (typeof node.id === "number") { + select(node.id); + } + e.stopPropagation(); + } + } + function translate(node: NodePosition, isDragging: boolean) { + let x = node.x; + let y = node.y; + if (isDragging) { + x += dragDelta.value.x; + y += dragDelta.value.y; + } + return ` translate(${x}px,${y}px)`; + } + function rotate(rotation: number) { + return ` rotate(${rotation}deg) `; + } + function scale(nodeOrBool: NodeTypes | boolean) { + const isSelected = + typeof nodeOrBool === "boolean" ? nodeOrBool : selected.value === nodeOrBool.id; + return isSelected ? " scale(1.2)" : ""; + } + function opacity(node: NodeTypes) { + const isDragging = selected.value !== node.id && nodeBeingDragged.value === node.id; + if (isDragging) { + return "; opacity: 0.5;"; + } + return ""; + } + function zIndex(node: NodeTypes) { + if (selected.value === node.id || nodeBeingDragged.value === node.id) { + return "; z-index: 100000000"; + } + return "; z-index: " + node.z; + } + + const renderANode = function (node: ANode) { + return ( + mouseDownNode(e, node)} + onMouseUp={e => mouseUpNode(e, node)} + > + + {receivingNodes.value.includes(node.id) && ( + + )} + + + + {selected.value === node.id && selectedAction.value === 0 && ( + + Spawn B Node + + )} + + A + + + ); + }; + const aActions = setupActions({ + node: () => nodesById.value[selected.value ?? ""], + shouldShowActions: node => node.type === "anode", + actions(node) { + return [ + p => ( + { + if (selectedAction.value === 0) { + spawnBNode(node as ANode); + } else { + selectAction(0); + } + }} + > + + + add + + + ) + ]; + }, + distance: 100 + }); + const sqrtTwo = Math.sqrt(2); + const renderBNode = function (node: BNode) { + return ( + mouseDownNode(e, node)} + onMouseUp={e => mouseUpNode(e, node)} + > + + {receivingNodes.value.includes(node.id) && ( + + )} + + + + {selected.value === node.id && selectedAction.value === 0 && ( + + Spawn A Node + + )} + + B + + + ); + }; + const bActions = setupActions({ + node: () => nodesById.value[selected.value ?? ""], + shouldShowActions: node => node.type === "bnode", + actions(node) { + return [ + p => ( + { + if (selectedAction.value === 0) { + spawnANode(node as BNode); + } else { + selectAction(0); + } + }} + > + + + add + + + ) + ]; + }, + distance: 100 + }); + function spawnANode(parent: ANode | BNode) { + const node: ANode = { + x: parent.x, + y: parent.y, + z: nextId.value, + type: "anode", + links: [parent.id], + id: nextId.value + }; + placeInAvailableSpace(node, nodes.value); + nodes.value.push(node); + } + function spawnBNode(parent: ANode | BNode) { + const node: BNode = { + x: parent.x, + y: parent.y, + z: nextId.value, + type: "bnode", + links: [parent.id], + id: nextId.value + }; + placeInAvailableSpace(node, nodes.value); + nodes.value.push(node); + } + + const points = createResource(10); + const cNode = createUpgrade(() => ({ + display:

C

, + // Purposefully not using noPersist + requirements: createCostRequirement(() => ({ cost: 10, resource: points })), + style: { + x: "100px", + y: "100px", + "--layer-color": "var(--accent1)" + }, + // no-op to prevent purchasing while dragging + onHold: () => {} + })); + makeDraggable>(cNode, () => ({ + id: "cnode", + endDrag, + startDrag, + hasDragged, + nodeBeingDragged, + dragDelta, + onMouseUp: cNode.purchase + })); + + const dNodesPerAxis = 50; + const dNodes = ( + <> + {new Array(dNodesPerAxis * dNodesPerAxis).fill(0).map((_, i) => { + const x = (Math.floor(i / dNodesPerAxis) - dNodesPerAxis / 2) * 100; + const y = ((i % dNodesPerAxis) - dNodesPerAxis / 2) * 100; + return ( + + ); + })} + + ); + + const links = computed(() => ( + <> + {nodes.value + .reduce( + (acc, curr) => [ + ...acc, + ...curr.links.map(l => ({ from: curr, to: nodesById.value[l] })) + ], + [] as { from: NodeTypes; to: NodeTypes }[] + ) + .map(link => ( + + ))} + + )); + + const nextId = setupUniqueIds(() => nodes.value); + + function renderNode(node: NodeTypes | typeof cNode) { + if (node.type === "anode") { + return renderANode(node); + } else if (node.type === "bnode") { + return renderBNode(node); + } else { + return render(node); + } + } + + return { + name: "Board", + color: "var(--accent1)", + display: () => ( + <> + + + {dNodes} + {links.value} + + {nodes.value.map(renderNode)} + {render(cNode)} + + {aActions.value} + {bActions.value} + + + + ), + boardNodes: nodes, + cNode, + selected: persistent(selected) + }; +}); + +export default board; diff --git a/src/data/projEntry.tsx b/src/data/projEntry.tsx index 13f0a1e..b129c2f 100644 --- a/src/data/projEntry.tsx +++ b/src/data/projEntry.tsx @@ -5,8 +5,7 @@ import { branchedResetPropagation, createTree, Tree } from "features/trees/tree" import { globalBus } from "game/events"; import type { BaseLayer, Layer } from "game/layers"; import { createLayer } from "game/layers"; -import type { Player } from "game/player"; -import player from "game/player"; +import player, { Player } from "game/player"; import type { DecimalSource } from "util/bignum"; import Decimal, { format, formatTime } from "util/bignum"; import { render } from "util/vue"; @@ -31,17 +30,21 @@ export const main = createLayer("main", function (this: BaseLayer) { }); const oomps = trackOOMPS(points, pointGain); + // Note: Casting as generic tree to avoid recursive type definitions const tree = createTree(() => ({ nodes: [[prestige.treeNode]], branches: [], onReset() { - points.value = toRaw(this.resettingNode.value) === toRaw(prestige.treeNode) ? 0 : 10; + points.value = toRaw(tree.resettingNode.value) === toRaw(prestige.treeNode) ? 0 : 10; best.value = points.value; total.value = points.value; }, resetPropagation: branchedResetPropagation })) as Tree; + // Note: layers don't _need_ a reference to everything, + // but I'd recommend it over trying to remember what does and doesn't need to be included. + // Officially all you need are anything with persistency or that you want to access elsewhere return { name: "Tree", links: tree.links, diff --git a/src/features/achievements/Achievement.vue b/src/features/achievements/Achievement.vue index f92635c..3184822 100644 --- a/src/features/achievements/Achievement.vue +++ b/src/features/achievements/Achievement.vue @@ -1,5 +1,5 @@ diff --git a/src/features/grids/GridCell.vue b/src/features/grids/GridCell.vue deleted file mode 100644 index bbd1513..0000000 --- a/src/features/grids/GridCell.vue +++ /dev/null @@ -1,51 +0,0 @@ -