import Board from "features/boards/Board.vue"; import CircleProgress from "features/boards/CircleProgress.vue"; import SVGNode from "features/boards/SVGNode.vue"; import SquareProgress from "features/boards/SquareProgress.vue"; import { NodePosition, placeInAvailableSpace, setupActions, setupDraggableNode, setupSelectable, setupUniqueIds } from "features/boards/board"; import { jsx } from "features/feature"; import type { BaseLayer, GenericLayer } from "game/layers"; import { createLayer } from "game/layers"; import { persistent } from "game/persistence"; import type { Player } from "game/player"; import { ComponentPublicInstance, computed, ref, watch } from "vue"; import prestige from "./layers/prestige"; type ANode = NodePosition & { id: number; links: number[]; type: "anode" }; type BNode = NodePosition & { id: number; links: number[]; type: "bnode" }; type NodeTypes = ANode | BNode; /** * @hidden */ export const main = createLayer("main", function (this: BaseLayer) { 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, isDraggable: function (node) { return nodes.value.includes(node); } }); // 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([{ id: 0, x: 0, y: 0, links: [], type: "anode" }]); const nodesById = computed>(() => nodes.value.reduce((acc, curr) => ({ ...acc, [curr.id]: curr }), {}) ); function mouseDownNode(e: MouseEvent | TouchEvent, node: NodeTypes) { if (nodeBeingDragged.value == null) { startDrag(e, node); } deselect(); } function mouseUpNode(e: MouseEvent | TouchEvent, node: NodeTypes) { if (!hasDragged.value) { endDrag(); select(node); e.stopPropagation(); } } function getTranslateString(node: NodePosition, overrideSelected?: boolean) { const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; const isDragging = !isSelected && nodeBeingDragged.value === node; 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 getRotateString(rotation: number) { return ` rotate(${rotation}deg) `; } function getScaleString(node: NodePosition, overrideSelected?: boolean) { const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; return isSelected ? " scale(1.2)" : ""; } function getOpacityString(node: NodePosition, overrideSelected?: boolean) { const isSelected = overrideSelected == null ? selected.value === node : overrideSelected; const isDragging = !isSelected && nodeBeingDragged.value === node; if (isDragging) { return "; opacity: 0.5;"; } return ""; } const renderANode = function (node: ANode) { return ( mouseDownNode(e, node)} onMouseUp={e => mouseUpNode(e, node)} > {receivingNodes.value.includes(node) && ( )} {selected.value === node && selectedAction.value === 0 && ( Spawn B Node )} A ); }; const aActions = setupActions({ node: selected, shouldShowActions: () => selected.value?.type === "anode", actions(node) { return [ p => ( { if (selectedAction.value === 0) { spawnBNode(node); } 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) && ( )} {selected.value === node && selectedAction.value === 0 && ( Spawn A Node )} B ); }; const bActions = setupActions({ node: selected, shouldShowActions: () => selected.value?.type === "bnode", actions(node) { return [ p => ( { if (selectedAction.value === 0) { spawnANode(node); } else { selectAction(0); } }} > add ) ]; }, distance: 100 }); function spawnANode(parent: NodeTypes) { const node: ANode = { x: parent.x, y: parent.y, type: "anode", links: [parent.id], id: nextId.value }; placeInAvailableSpace(node, nodes.value); nodes.value.push(node); } function spawnBNode(parent: NodeTypes) { const node: BNode = { x: parent.x, y: parent.y, type: "bnode", links: [parent.id], id: nextId.value }; placeInAvailableSpace(node, nodes.value); nodes.value.push(node); } // const cNode = createUpgrade(() => ({ // requirements: createCostRequirement(() => ({ cost: 10, resource: points })), // style: { // x: "100px", // y: "100px" // } // })); // makeDraggable(cNode); // TODO make decorator // const dNodes; const links = jsx(() => ( <> {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 filterNodes(n: NodeTypes) { return n !== nodeBeingDragged.value && n !== selected.value; } function renderNode(node: NodeTypes | undefined) { if (node == undefined) { return undefined; } else if (node.type === "anode") { return renderANode(node); } else if (node.type === "bnode") { return renderBNode(node); } } return { name: "Tree", display: jsx(() => ( <> {links()} {nodes.value.filter(filterNodes).map(renderNode)} {aActions()} {bActions()} {renderNode(selected.value)} {renderNode(nodeBeingDragged.value)} )), boardNodes: nodes // cNode }; }); /** * Given a player save data object being loaded, return a list of layers that should currently be enabled. * If your project does not use dynamic layers, this should just return all layers. */ export const getInitialLayers = ( /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ player: Partial ): Array => [main, prestige]; /** * A computed ref whose value is true whenever the game is over. */ export const hasWon = computed(() => { return false; }); /** * Given a player save data object being loaded with a different version, update the save data object to match the structure of the current version. * @param oldVersion The version of the save being loaded in * @param player The save data being loaded in */ /* eslint-disable @typescript-eslint/no-unused-vars */ export function fixOldSave( oldVersion: string | undefined, player: Partial // eslint-disable-next-line @typescript-eslint/no-empty-function ): void {} /* eslint-enable @typescript-eslint/no-unused-vars */