forked from profectus/Profectus
Implemented dropping nodes into each other
Also improved node z-ordering and other related things
This commit is contained in:
parent
f3b934337f
commit
02443bbb0c
8 changed files with 249 additions and 100 deletions
|
@ -2,18 +2,35 @@
|
||||||
<panZoom
|
<panZoom
|
||||||
:style="style"
|
:style="style"
|
||||||
selector="#g1"
|
selector="#g1"
|
||||||
@init="onInit"
|
|
||||||
:options="{ initialZoom: 1, minZoom: 0.1, maxZoom: 10 }"
|
:options="{ initialZoom: 1, minZoom: 0.1, maxZoom: 10 }"
|
||||||
ref="stage"
|
ref="stage"
|
||||||
|
@init="onInit"
|
||||||
|
@mousemove="drag"
|
||||||
|
@mouseup="() => endDragging(dragging)"
|
||||||
|
@mouseleave="() => endDragging(dragging)"
|
||||||
>
|
>
|
||||||
<svg class="stage" width="100%" height="100%">
|
<svg class="stage" width="100%" height="100%">
|
||||||
<g id="g1">
|
<g id="g1">
|
||||||
<BoardNode
|
<BoardNode
|
||||||
v-for="(node, nodeIndex) in nodes"
|
v-for="node in nodes"
|
||||||
:key="nodeIndex"
|
:key="node.id"
|
||||||
:index="nodeIndex"
|
|
||||||
:node="node"
|
:node="node"
|
||||||
:nodeType="board.types[node.type]"
|
:nodeType="board.types[node.type]"
|
||||||
|
:dragging="draggingNode"
|
||||||
|
:dragged="dragged"
|
||||||
|
:receivingNode="receivingNode?.id === node.id"
|
||||||
|
@startDragging="startDragging"
|
||||||
|
@endDragging="endDragging"
|
||||||
|
/>
|
||||||
|
<BoardNode
|
||||||
|
v-if="draggingNode"
|
||||||
|
:node="draggingNode"
|
||||||
|
:nodeType="board.types[draggingNode.type]"
|
||||||
|
:dragging="draggingNode"
|
||||||
|
:dragged="dragged"
|
||||||
|
:receivingNode="receivingNode?.id === draggingNode.id"
|
||||||
|
@startDragging="startDragging"
|
||||||
|
@endDragging="endDragging"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -23,24 +40,30 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { layers } from "@/game/layers";
|
import { layers } from "@/game/layers";
|
||||||
import player from "@/game/player";
|
import player from "@/game/player";
|
||||||
import { Board } from "@/typings/features/board";
|
import { Board, BoardNode } from "@/typings/features/board";
|
||||||
import { InjectLayerMixin } from "@/util/vue";
|
import { InjectLayerMixin } from "@/util/vue";
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "Board",
|
name: "Board",
|
||||||
mixins: [InjectLayerMixin],
|
mixins: [InjectLayerMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
lastMousePosition: { x: 0, y: 0 },
|
||||||
|
dragged: { x: 0, y: 0 },
|
||||||
|
dragging: null
|
||||||
|
} as {
|
||||||
|
lastMousePosition: { x: number; y: number };
|
||||||
|
dragged: { x: number; y: number };
|
||||||
|
dragging: string | null;
|
||||||
|
};
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
id: {
|
id: {
|
||||||
type: [Number, String],
|
type: [Number, String],
|
||||||
required: true
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
provide() {
|
|
||||||
return {
|
|
||||||
getZoomLevel: () => (this.$refs.stage as any).$panZoomInstance.getTransform().scale
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
board(): Board {
|
board(): Board {
|
||||||
return layers[this.layer].boards!.data[this.id];
|
return layers[this.layer].boards!.data[this.id];
|
||||||
|
@ -55,13 +78,101 @@ export default defineComponent({
|
||||||
this.board.style
|
this.board.style
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
draggingNode() {
|
||||||
|
return this.dragging
|
||||||
|
? player.layers[this.layer].boards[this.id].find(node => node.id === this.dragging)
|
||||||
|
: null;
|
||||||
|
},
|
||||||
nodes() {
|
nodes() {
|
||||||
return player.layers[this.layer].boards[this.id];
|
return player.layers[this.layer].boards[this.id].filter(
|
||||||
|
node => node !== this.draggingNode
|
||||||
|
);
|
||||||
|
},
|
||||||
|
receivingNode(): BoardNode | null {
|
||||||
|
if (this.draggingNode == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = {
|
||||||
|
x: this.draggingNode.position.x + this.dragged.x,
|
||||||
|
y: this.draggingNode.position.y + this.dragged.y
|
||||||
|
};
|
||||||
|
let smallestDistance = Number.MAX_VALUE;
|
||||||
|
return this.nodes.reduce((smallest: BoardNode | null, curr: BoardNode) => {
|
||||||
|
const nodeType = this.board.types[curr.type];
|
||||||
|
const canAccept =
|
||||||
|
typeof nodeType.canAccept === "boolean"
|
||||||
|
? nodeType.canAccept
|
||||||
|
: nodeType.canAccept(curr, this.draggingNode!);
|
||||||
|
if (!canAccept) {
|
||||||
|
return smallest;
|
||||||
|
}
|
||||||
|
|
||||||
|
const distanceSquared =
|
||||||
|
Math.pow(position.x - curr.position.x, 2) +
|
||||||
|
Math.pow(position.y - curr.position.y, 2);
|
||||||
|
const size =
|
||||||
|
typeof nodeType.size === "number" ? nodeType.size : nodeType.size(curr);
|
||||||
|
if (distanceSquared > smallestDistance || distanceSquared > size * size) {
|
||||||
|
return smallest;
|
||||||
|
}
|
||||||
|
|
||||||
|
smallestDistance = distanceSquared;
|
||||||
|
return curr;
|
||||||
|
}, null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
getZoomLevel(): number {
|
||||||
|
return (this.$refs.stage as any).$panZoomInstance.getTransform().scale;
|
||||||
|
},
|
||||||
onInit: function(panzoomInstance) {
|
onInit: function(panzoomInstance) {
|
||||||
panzoomInstance.setTransformOrigin(null);
|
panzoomInstance.setTransformOrigin(null);
|
||||||
|
},
|
||||||
|
startDragging(e: MouseEvent, nodeID: string) {
|
||||||
|
if (this.dragging == null) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
this.lastMousePosition = {
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY
|
||||||
|
};
|
||||||
|
this.dragged = { x: 0, y: 0 };
|
||||||
|
|
||||||
|
this.dragging = nodeID;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
drag(e: MouseEvent) {
|
||||||
|
if (this.dragging) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const zoom = (this.getZoomLevel as () => number)();
|
||||||
|
this.dragged.x += (e.clientX - this.lastMousePosition.x) / zoom;
|
||||||
|
this.dragged.y += (e.clientY - this.lastMousePosition.y) / zoom;
|
||||||
|
this.lastMousePosition = {
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
endDragging(nodeID: string | null) {
|
||||||
|
if (this.dragging != null && this.dragging === nodeID) {
|
||||||
|
const nodes = player.layers[this.layer].boards[this.id];
|
||||||
|
const draggingNode = this.draggingNode!;
|
||||||
|
const receivingNode = this.receivingNode;
|
||||||
|
draggingNode.position.x += Math.round(this.dragged.x / 25) * 25;
|
||||||
|
draggingNode.position.y += Math.round(this.dragged.y / 25) * 25;
|
||||||
|
nodes.splice(nodes.indexOf(draggingNode), 1);
|
||||||
|
nodes.push(draggingNode);
|
||||||
|
|
||||||
|
if (receivingNode) {
|
||||||
|
this.board.types[receivingNode.type].onDrop(receivingNode, draggingNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dragging = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
<template>
|
<template>
|
||||||
<g
|
<g
|
||||||
class="boardnode"
|
class="boardnode"
|
||||||
:style="{ opacity: dragging ? 0.5 : 1 }"
|
:style="{ opacity: dragging?.id === node.id ? 0.5 : 1 }"
|
||||||
:transform="`translate(${position.x},${position.y})`"
|
:transform="`translate(${position.x},${position.y})`"
|
||||||
@mousedown="mouseDown"
|
@mouseenter="mouseEnter"
|
||||||
|
@mouseleave="mouseLeave"
|
||||||
|
@mousedown="e => $emit('startDragging', e, node.id)"
|
||||||
>
|
>
|
||||||
<circle :r="size + 8" :fill="backgroundColor" stroke="#0F03" :stroke-width="2" />
|
<circle
|
||||||
|
v-if="canAccept"
|
||||||
|
:r="size + 8"
|
||||||
|
:fill="backgroundColor"
|
||||||
|
:stroke="receivingNode ? '#0F0' : '#0F03'"
|
||||||
|
:stroke-width="2"
|
||||||
|
/>
|
||||||
|
|
||||||
<circle :r="size" :fill="fillColor" :stroke="outlineColor" :stroke-width="4" />
|
<circle :r="size" :fill="fillColor" :stroke="outlineColor" :stroke-width="4" />
|
||||||
|
|
||||||
|
@ -34,45 +42,22 @@ import themes from "@/data/themes";
|
||||||
import { ProgressDisplay } from "@/game/enums";
|
import { ProgressDisplay } from "@/game/enums";
|
||||||
import player from "@/game/player";
|
import player from "@/game/player";
|
||||||
import { BoardNode, NodeType } from "@/typings/features/board";
|
import { BoardNode, NodeType } from "@/typings/features/board";
|
||||||
|
import { getNodeTypeProperty } from "@/util/features";
|
||||||
import { InjectLayerMixin } from "@/util/vue";
|
import { InjectLayerMixin } from "@/util/vue";
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
// TODO will blindly use any T given (can't restrict it to S[R] because I can't figure out how
|
|
||||||
// to make it support narrowing the return type)
|
|
||||||
function getTypeProperty<T, S extends NodeType, R extends keyof S>(
|
|
||||||
nodeType: S,
|
|
||||||
node: BoardNode,
|
|
||||||
property: R
|
|
||||||
): S[R] extends Pick<
|
|
||||||
S,
|
|
||||||
{
|
|
||||||
[K in keyof S]-?: undefined extends S[K] ? never : K;
|
|
||||||
}[keyof S]
|
|
||||||
>
|
|
||||||
? T
|
|
||||||
: T | undefined {
|
|
||||||
return typeof nodeType[property] === "function"
|
|
||||||
? (nodeType[property] as (node: BoardNode) => T)(node)
|
|
||||||
: (nodeType[property] as T);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "BoardNode",
|
name: "BoardNode",
|
||||||
mixins: [InjectLayerMixin],
|
mixins: [InjectLayerMixin],
|
||||||
inject: ["getZoomLevel"],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ProgressDisplay,
|
ProgressDisplay,
|
||||||
lastMousePosition: { x: 0, y: 0 },
|
lastMousePosition: { x: 0, y: 0 },
|
||||||
dragged: { x: 0, y: 0 },
|
hovering: false
|
||||||
dragging: false
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
emits: ["startDragging", "endDragging"],
|
||||||
props: {
|
props: {
|
||||||
index: {
|
|
||||||
type: Number,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
node: {
|
node: {
|
||||||
type: Object as PropType<BoardNode>,
|
type: Object as PropType<BoardNode>,
|
||||||
required: true
|
required: true
|
||||||
|
@ -80,14 +65,25 @@ export default defineComponent({
|
||||||
nodeType: {
|
nodeType: {
|
||||||
type: Object as PropType<NodeType>,
|
type: Object as PropType<NodeType>,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
dragging: {
|
||||||
|
type: Object as PropType<BoardNode>
|
||||||
|
},
|
||||||
|
dragged: {
|
||||||
|
type: Object as PropType<{ x: number; y: number }>,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
receivingNode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
draggable(): boolean {
|
draggable(): boolean {
|
||||||
return getTypeProperty(this.nodeType, this.node, "draggable");
|
return getNodeTypeProperty(this.nodeType, this.node, "draggable");
|
||||||
},
|
},
|
||||||
position(): { x: number; y: number } {
|
position(): { x: number; y: number } {
|
||||||
return this.draggable && this.dragging
|
return this.draggable && this.dragging?.id === this.node.id
|
||||||
? {
|
? {
|
||||||
x: this.node.position.x + Math.round(this.dragged.x / 25) * 25,
|
x: this.node.position.x + Math.round(this.dragged.x / 25) * 25,
|
||||||
y: this.node.position.y + Math.round(this.dragged.y / 25) * 25
|
y: this.node.position.y + Math.round(this.dragged.y / 25) * 25
|
||||||
|
@ -95,89 +91,71 @@ export default defineComponent({
|
||||||
: this.node.position;
|
: this.node.position;
|
||||||
},
|
},
|
||||||
size(): number {
|
size(): number {
|
||||||
return getTypeProperty(this.nodeType, this.node, "size");
|
let size: number = getNodeTypeProperty(this.nodeType, this.node, "size");
|
||||||
|
if (this.receivingNode) {
|
||||||
|
size *= 1.25;
|
||||||
|
} else if (this.hovering) {
|
||||||
|
size *= 1.15;
|
||||||
|
}
|
||||||
|
return size;
|
||||||
},
|
},
|
||||||
title(): string {
|
title(): string {
|
||||||
return getTypeProperty(this.nodeType, this.node, "title");
|
return getNodeTypeProperty(this.nodeType, this.node, "title");
|
||||||
},
|
},
|
||||||
progress(): number {
|
progress(): number {
|
||||||
return getTypeProperty(this.nodeType, this.node, "progress") || 0;
|
return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
|
||||||
},
|
},
|
||||||
backgroundColor(): string {
|
backgroundColor(): string {
|
||||||
return themes[player.theme].variables["--background"];
|
return themes[player.theme].variables["--background"];
|
||||||
},
|
},
|
||||||
outlineColor(): string {
|
outlineColor(): string {
|
||||||
return (
|
return (
|
||||||
getTypeProperty(this.nodeType, this.node, "outlineColor") ||
|
getNodeTypeProperty(this.nodeType, this.node, "outlineColor") ||
|
||||||
themes[player.theme].variables["--separator"]
|
themes[player.theme].variables["--separator"]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
fillColor(): string {
|
fillColor(): string {
|
||||||
return (
|
return (
|
||||||
getTypeProperty(this.nodeType, this.node, "fillColor") ||
|
getNodeTypeProperty(this.nodeType, this.node, "fillColor") ||
|
||||||
themes[player.theme].variables["--secondary-background"]
|
themes[player.theme].variables["--secondary-background"]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
progressColor(): string {
|
progressColor(): string {
|
||||||
return getTypeProperty(this.nodeType, this.node, "progressColor") || "none";
|
return getNodeTypeProperty(this.nodeType, this.node, "progressColor") || "none";
|
||||||
},
|
},
|
||||||
titleColor(): string {
|
titleColor(): string {
|
||||||
return (
|
return (
|
||||||
getTypeProperty(this.nodeType, this.node, "titleColor") ||
|
getNodeTypeProperty(this.nodeType, this.node, "titleColor") ||
|
||||||
themes[player.theme].variables["--color"]
|
themes[player.theme].variables["--color"]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
progressDisplay(): ProgressDisplay {
|
progressDisplay(): ProgressDisplay {
|
||||||
return (
|
return (
|
||||||
getTypeProperty(this.nodeType, this.node, "progressDisplay") ||
|
getNodeTypeProperty(this.nodeType, this.node, "progressDisplay") ||
|
||||||
ProgressDisplay.Outline
|
ProgressDisplay.Outline
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
canAccept(): boolean {
|
||||||
|
if (this.dragging == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return typeof this.nodeType.canAccept === "boolean"
|
||||||
|
? this.nodeType.canAccept
|
||||||
|
: this.nodeType.canAccept(this.node, this.dragging);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
mouseDown(e: MouseEvent) {
|
mouseEnter() {
|
||||||
if (this.draggable) {
|
this.hovering = true;
|
||||||
e.preventDefault();
|
},
|
||||||
e.stopPropagation();
|
mouseLeave() {
|
||||||
|
this.hovering = false;
|
||||||
this.lastMousePosition = {
|
|
||||||
x: e.clientX,
|
|
||||||
y: e.clientY
|
|
||||||
};
|
|
||||||
this.dragged = { x: 0, y: 0 };
|
|
||||||
|
|
||||||
this.dragging = true;
|
|
||||||
document.onmouseup = this.mouseUp;
|
|
||||||
document.onmousemove = this.mouseMove;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mouseMove(e: MouseEvent) {
|
watch: {
|
||||||
if (this.draggable && this.dragging) {
|
onDraggableChanged() {
|
||||||
e.preventDefault();
|
if (this.dragging && !this.draggable) {
|
||||||
e.stopPropagation();
|
this.$emit("endDragging", this.node.id);
|
||||||
|
|
||||||
const zoom = (this.getZoomLevel as () => number)();
|
|
||||||
console.log(zoom);
|
|
||||||
this.dragged.x += (e.clientX - this.lastMousePosition.x) / zoom;
|
|
||||||
this.dragged.y += (e.clientY - this.lastMousePosition.y) / zoom;
|
|
||||||
this.lastMousePosition = {
|
|
||||||
x: e.clientX,
|
|
||||||
y: e.clientY
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mouseUp(e: MouseEvent) {
|
|
||||||
if (this.draggable && this.dragging) {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
let node = player.layers[this.nodeType.layer].boards[this.nodeType.id][this.index];
|
|
||||||
node.position.x += Math.round(this.dragged.x / 25) * 25;
|
|
||||||
node.position.y += Math.round(this.dragged.y / 25) * 25;
|
|
||||||
|
|
||||||
this.dragging = false;
|
|
||||||
document.onmouseup = null;
|
|
||||||
document.onmousemove = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,11 @@ type ResourceNodeData = {
|
||||||
maxAmount: DecimalSource;
|
maxAmount: DecimalSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ItemNodeData = {
|
||||||
|
itemType: string;
|
||||||
|
amount: DecimalSource;
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: "main",
|
id: "main",
|
||||||
display: `
|
display: `
|
||||||
|
@ -39,6 +44,14 @@ export default {
|
||||||
amount: new Decimal(24 * 60 * 60),
|
amount: new Decimal(24 * 60 * 60),
|
||||||
maxAmount: new Decimal(24 * 60 * 60)
|
maxAmount: new Decimal(24 * 60 * 60)
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
position: { x: 0, y: 150 },
|
||||||
|
type: "item",
|
||||||
|
data: {
|
||||||
|
itemType: "speed",
|
||||||
|
amount: new Decimal(5 * 60 * 60)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
@ -63,7 +76,22 @@ export default {
|
||||||
default:
|
default:
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
canAccept(node, otherNode) {
|
||||||
|
return otherNode.type === "item";
|
||||||
|
},
|
||||||
|
onDrop(node, otherNode) {
|
||||||
|
const index = player.layers[this.layer].boards[this.id].indexOf(
|
||||||
|
otherNode
|
||||||
|
);
|
||||||
|
player.layers[this.layer].boards[this.id].splice(index, 1);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
title(node) {
|
||||||
|
return (node.data as ItemNodeData).itemType;
|
||||||
|
},
|
||||||
|
draggable: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,6 +104,12 @@ const playerHandler: ProxyHandler<Record<string, any>> = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
ownKeys(target: Record<string, any>) {
|
||||||
|
return Reflect.ownKeys(target.__state);
|
||||||
|
},
|
||||||
|
has(target: Record<string, any>, key: string) {
|
||||||
|
return Reflect.has(target.__state, key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export default window.player = new Proxy(
|
export default window.player = new Proxy(
|
||||||
|
|
8
src/typings/features/board.d.ts
vendored
8
src/typings/features/board.d.ts
vendored
|
@ -2,6 +2,7 @@ import { State } from "../state";
|
||||||
import { Feature, RawFeature } from "./feature";
|
import { Feature, RawFeature } from "./feature";
|
||||||
|
|
||||||
export interface BoardNode {
|
export interface BoardNode {
|
||||||
|
id: string;
|
||||||
position: {
|
position: {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
@ -16,15 +17,15 @@ export interface CardOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Board extends Feature {
|
export interface Board extends Feature {
|
||||||
startNodes: () => BoardNode[];
|
startNodes: () => Omit<BoardNode, "id">[];
|
||||||
style?: Partial<CSSStyleDeclaration>;
|
style?: Partial<CSSStyleDeclaration>;
|
||||||
height: string;
|
height: string;
|
||||||
width: string;
|
width: string;
|
||||||
types: Record<string, NodeType>;
|
types: Record<string, NodeType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RawBoard = Omit<RawFeature<Board>, "types"> & {
|
export type RawBoard = Omit<RawFeature<Board>, "types" | "startNodes"> & {
|
||||||
startNodes: () => BoardNode[];
|
startNodes: () => Omit<BoardNode, "id">[];
|
||||||
types: Record<string, RawFeature<NodeType>>;
|
types: Record<string, RawFeature<NodeType>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,5 +42,6 @@ export interface NodeType extends Feature {
|
||||||
outlineColor?: string | ((node: BoardNode) => string);
|
outlineColor?: string | ((node: BoardNode) => string);
|
||||||
titleColor?: string | ((node: BoardNode) => string);
|
titleColor?: string | ((node: BoardNode) => string);
|
||||||
onClick: (node: BoardNode) => void;
|
onClick: (node: BoardNode) => void;
|
||||||
|
onDrop: (node: BoardNode, otherNode: BoardNode) => void;
|
||||||
nodes: BoardNode[];
|
nodes: BoardNode[];
|
||||||
}
|
}
|
||||||
|
|
2
src/typings/player.d.ts
vendored
2
src/typings/player.d.ts
vendored
|
@ -62,7 +62,7 @@ export interface LayerSaveData {
|
||||||
clickables: Record<string, State>;
|
clickables: Record<string, State>;
|
||||||
challenges: Record<string, Decimal>;
|
challenges: Record<string, Decimal>;
|
||||||
grids: Record<string, Record<string, State>>;
|
grids: Record<string, Record<string, State>>;
|
||||||
boards: Record<string, Array<BoardNode>>;
|
boards: Record<string, BoardNode[]>;
|
||||||
confirmRespecBuyables: boolean;
|
confirmRespecBuyables: boolean;
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { layers } from "@/game/layers";
|
import { layers } from "@/game/layers";
|
||||||
|
import { NodeType, BoardNode } from "@/typings/features/board";
|
||||||
import { GridCell } from "@/typings/features/grid";
|
import { GridCell } from "@/typings/features/grid";
|
||||||
import { State } from "@/typings/state";
|
import { State } from "@/typings/state";
|
||||||
import Decimal, { DecimalSource } from "@/util/bignum";
|
import Decimal, { DecimalSource } from "@/util/bignum";
|
||||||
|
@ -87,3 +88,22 @@ export function achievementEffect(layer: string, id: string | number): State | u
|
||||||
export function gridEffect(layer: string, id: string, cell: string | number): State | undefined {
|
export function gridEffect(layer: string, id: string, cell: string | number): State | undefined {
|
||||||
return (layers[layer].grids?.data[id][cell] as GridCell).effect;
|
return (layers[layer].grids?.data[id][cell] as GridCell).effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO will blindly use any T given (can't restrict it to S[R] because I can't figure out how
|
||||||
|
// to make it support narrowing the return type)
|
||||||
|
export function getNodeTypeProperty<T, S extends NodeType, R extends keyof S>(
|
||||||
|
nodeType: S,
|
||||||
|
node: BoardNode,
|
||||||
|
property: R
|
||||||
|
): S[R] extends Pick<
|
||||||
|
S,
|
||||||
|
{
|
||||||
|
[K in keyof S]-?: undefined extends S[K] ? never : K;
|
||||||
|
}[keyof S]
|
||||||
|
>
|
||||||
|
? T
|
||||||
|
: T | undefined {
|
||||||
|
return typeof nodeType[property] === "function"
|
||||||
|
? (nodeType[property] as (node: BoardNode) => T)(node)
|
||||||
|
: (nodeType[property] as T);
|
||||||
|
}
|
||||||
|
|
|
@ -73,13 +73,17 @@ export function getStartingChallenges(
|
||||||
|
|
||||||
export function getStartingBoards(
|
export function getStartingBoards(
|
||||||
boards?: Record<string, Board> | Record<string, RawBoard> | undefined
|
boards?: Record<string, Board> | Record<string, RawBoard> | undefined
|
||||||
): Record<string, Array<BoardNode>> {
|
): Record<string, BoardNode[]> {
|
||||||
return boards
|
return boards
|
||||||
? Object.keys(boards).reduce((acc: Record<string, Array<BoardNode>>, curr: string): Record<
|
? Object.keys(boards).reduce((acc: Record<string, BoardNode[]>, curr: string): Record<
|
||||||
string,
|
string,
|
||||||
Array<BoardNode>
|
BoardNode[]
|
||||||
> => {
|
> => {
|
||||||
acc[curr] = boards[curr].startNodes?.() || [];
|
const nodes = boards[curr].startNodes?.() || [];
|
||||||
|
acc[curr] = nodes.map((node, index) => ({
|
||||||
|
id: index.toString(),
|
||||||
|
...node
|
||||||
|
})) as BoardNode[];
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
: {};
|
: {};
|
||||||
|
|
Loading…
Reference in a new issue