forked from profectus/Profectus
Implemented selecting nodes and running immediate actions
This commit is contained in:
parent
35b3226995
commit
11df9853d0
12 changed files with 346 additions and 103 deletions
|
@ -6,6 +6,7 @@
|
|||
ref="stage"
|
||||
@init="onInit"
|
||||
@mousemove="drag"
|
||||
@mousedown="deselect"
|
||||
@mouseup="() => endDragging(dragging)"
|
||||
@mouseleave="() => endDragging(dragging)"
|
||||
>
|
||||
|
@ -18,18 +19,9 @@
|
|||
:nodeType="board.types[node.type]"
|
||||
:dragging="draggingNode"
|
||||
:dragged="dragged"
|
||||
:hasDragged="hasDragged"
|
||||
: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"
|
||||
@mouseDown="mouseDown"
|
||||
@endDragging="endDragging"
|
||||
/>
|
||||
</g>
|
||||
|
@ -51,11 +43,13 @@ export default defineComponent({
|
|||
return {
|
||||
lastMousePosition: { x: 0, y: 0 },
|
||||
dragged: { x: 0, y: 0 },
|
||||
dragging: null
|
||||
dragging: null,
|
||||
hasDragged: false
|
||||
} as {
|
||||
lastMousePosition: { x: number; y: number };
|
||||
dragged: { x: number; y: number };
|
||||
dragging: string | null;
|
||||
hasDragged: boolean;
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
@ -79,14 +73,15 @@ export default defineComponent({
|
|||
];
|
||||
},
|
||||
draggingNode() {
|
||||
return this.dragging
|
||||
? player.layers[this.layer].boards[this.id].find(node => node.id === this.dragging)
|
||||
: null;
|
||||
return this.dragging ? this.board.nodes.find(node => node.id === this.dragging) : null;
|
||||
},
|
||||
nodes() {
|
||||
return player.layers[this.layer].boards[this.id].filter(
|
||||
node => node !== this.draggingNode
|
||||
);
|
||||
const nodes = this.board.nodes.slice();
|
||||
if (this.draggingNode) {
|
||||
const draggingNode = nodes.splice(nodes.indexOf(this.draggingNode), 1)[0];
|
||||
nodes.push(draggingNode);
|
||||
}
|
||||
return nodes;
|
||||
},
|
||||
receivingNode(): BoardNode | null {
|
||||
if (this.draggingNode == null) {
|
||||
|
@ -99,6 +94,9 @@ export default defineComponent({
|
|||
};
|
||||
let smallestDistance = Number.MAX_VALUE;
|
||||
return this.nodes.reduce((smallest: BoardNode | null, curr: BoardNode) => {
|
||||
if (curr.id === this.draggingNode!.id) {
|
||||
return smallest;
|
||||
}
|
||||
const nodeType = this.board.types[curr.type];
|
||||
const canAccept =
|
||||
typeof nodeType.canAccept === "boolean"
|
||||
|
@ -126,10 +124,14 @@ export default defineComponent({
|
|||
getZoomLevel(): number {
|
||||
return (this.$refs.stage as any).$panZoomInstance.getTransform().scale;
|
||||
},
|
||||
onInit: function(panzoomInstance: any) {
|
||||
onInit(panzoomInstance: any) {
|
||||
panzoomInstance.setTransformOrigin(null);
|
||||
},
|
||||
startDragging(e: MouseEvent, nodeID: string) {
|
||||
deselect() {
|
||||
player.layers[this.layer].boards[this.id].selectedNode = null;
|
||||
player.layers[this.layer].boards[this.id].selectedAction = null;
|
||||
},
|
||||
mouseDown(e: MouseEvent, nodeID: string, draggable: boolean) {
|
||||
if (this.dragging == null) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -139,33 +141,43 @@ export default defineComponent({
|
|||
y: e.clientY
|
||||
};
|
||||
this.dragged = { x: 0, y: 0 };
|
||||
this.hasDragged = false;
|
||||
|
||||
if (draggable) {
|
||||
this.dragging = nodeID;
|
||||
}
|
||||
}
|
||||
player.layers[this.layer].boards[this.id].selectedNode = null;
|
||||
player.layers[this.layer].boards[this.id].selectedAction = null;
|
||||
},
|
||||
drag(e: MouseEvent) {
|
||||
if (this.dragging) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const zoom = (this.getZoomLevel as () => number)();
|
||||
this.dragged = {
|
||||
x: this.dragged.x + (e.clientX - this.lastMousePosition.x) / zoom,
|
||||
y: this.dragged.y + (e.clientY - this.lastMousePosition.y) / zoom
|
||||
}
|
||||
};
|
||||
this.lastMousePosition = {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
};
|
||||
|
||||
if (Math.abs(this.dragged.x) > 10 || Math.abs(this.dragged.y) > 10) {
|
||||
this.hasDragged = true;
|
||||
}
|
||||
|
||||
if (this.dragging) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
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;
|
||||
|
||||
const nodes = this.board.nodes;
|
||||
nodes.splice(nodes.indexOf(draggingNode), 1);
|
||||
nodes.push(draggingNode);
|
||||
|
||||
|
|
|
@ -1,13 +1,38 @@
|
|||
<template>
|
||||
<g
|
||||
class="boardnode"
|
||||
:style="{ opacity: dragging?.id === node.id ? 0.5 : 1 }"
|
||||
:style="{ opacity: dragging?.id === node.id && hasDragged ? 0.5 : 1 }"
|
||||
:transform="`translate(${position.x},${position.y})`"
|
||||
>
|
||||
<transition name="actions" appear>
|
||||
<g v-if="selected && actions">
|
||||
<g
|
||||
v-for="(action, index) in actions"
|
||||
:key="action.id"
|
||||
class="action"
|
||||
:transform="
|
||||
`translate(
|
||||
${(-size - 30) *
|
||||
Math.sin(((actions.length - 1) / 2 - index) * actionDistance)},
|
||||
${(size + 30) *
|
||||
Math.cos(((actions.length - 1) / 2 - index) * actionDistance)}
|
||||
)`
|
||||
"
|
||||
@click="performAction(action)"
|
||||
>
|
||||
<circle :fill="fillColor" r="20" />
|
||||
<text :fill="titleColor" class="material-icons">{{ action.icon }}</text>
|
||||
</g>
|
||||
</g>
|
||||
</transition>
|
||||
|
||||
<g
|
||||
v-if="shape === Shape.Circle"
|
||||
@mouseenter="mouseEnter"
|
||||
@mouseleave="mouseLeave"
|
||||
@mousedown="mouseDown"
|
||||
@mouseup="mouseUp"
|
||||
>
|
||||
<g v-if="shape === Shape.Circle">
|
||||
<circle
|
||||
v-if="canAccept"
|
||||
:r="size + 8"
|
||||
|
@ -36,21 +61,30 @@
|
|||
:stroke="progressColor"
|
||||
/>
|
||||
</g>
|
||||
<g v-else-if="shape === Shape.Diamond" transform="rotate(45, 0, 0)">
|
||||
<g
|
||||
v-else-if="shape === Shape.Diamond"
|
||||
transform="rotate(45, 0, 0)"
|
||||
@mouseenter="mouseEnter"
|
||||
@mouseleave="mouseLeave"
|
||||
@mousedown="mouseDown"
|
||||
@mouseup="mouseUp"
|
||||
>
|
||||
<rect
|
||||
v-if="canAccept"
|
||||
:width="size + 16"
|
||||
:height="size + 16"
|
||||
:transform="`translate(${-(size + 16) / 2}, ${-(size + 16) / 2})`"
|
||||
:width="size * sqrtTwo + 16"
|
||||
:height="size * sqrtTwo + 16"
|
||||
:transform="
|
||||
`translate(${-(size * sqrtTwo + 16) / 2}, ${-(size * sqrtTwo + 16) / 2})`
|
||||
"
|
||||
:fill="backgroundColor"
|
||||
:stroke="receivingNode ? '#0F0' : '#0F03'"
|
||||
:stroke-width="2"
|
||||
/>
|
||||
|
||||
<rect
|
||||
:width="size"
|
||||
:height="size"
|
||||
:transform="`translate(${-size / 2}, ${-size / 2})`"
|
||||
:width="size * sqrtTwo"
|
||||
:height="size * sqrtTwo"
|
||||
:transform="`translate(${(-size * sqrtTwo) / 2}, ${(-size * sqrtTwo) / 2})`"
|
||||
:fill="fillColor"
|
||||
:stroke="outlineColor"
|
||||
:stroke-width="4"
|
||||
|
@ -58,11 +92,11 @@
|
|||
|
||||
<rect
|
||||
v-if="progressDisplay === ProgressDisplay.Fill"
|
||||
:width="Math.max(size * progress - 2, 0)"
|
||||
:height="Math.max(size * progress - 2, 0)"
|
||||
:width="Math.max(size * sqrtTwo * progress - 2, 0)"
|
||||
:height="Math.max(size * sqrtTwo * progress - 2, 0)"
|
||||
:transform="
|
||||
`translate(${-Math.max(size * progress - 2, 0) / 2}, ${-Math.max(
|
||||
size * progress - 2,
|
||||
`translate(${-Math.max(size * sqrtTwo * progress - 2, 0) / 2}, ${-Math.max(
|
||||
size * sqrtTwo * progress - 2,
|
||||
0
|
||||
) / 2})`
|
||||
"
|
||||
|
@ -70,13 +104,13 @@
|
|||
/>
|
||||
<rect
|
||||
v-else
|
||||
:width="size + 9"
|
||||
:height="size + 9"
|
||||
:transform="`translate(${-(size + 9) / 2}, ${-(size + 9) / 2})`"
|
||||
:width="size * sqrtTwo + 9"
|
||||
:height="size * sqrtTwo + 9"
|
||||
:transform="`translate(${-(size * sqrtTwo + 9) / 2}, ${-(size * sqrtTwo + 9) / 2})`"
|
||||
fill="transparent"
|
||||
:stroke-dasharray="(size + 9) * 4"
|
||||
:stroke-dasharray="(size * sqrtTwo + 9) * 4"
|
||||
:stroke-width="5"
|
||||
:stroke-dashoffset="(size + 9) * 4 - progress * (size + 9) * 4"
|
||||
:stroke-dashoffset="(size * sqrtTwo + 9) * 4 - progress * (size * sqrtTwo + 9) * 4"
|
||||
:stroke="progressColor"
|
||||
/>
|
||||
</g>
|
||||
|
@ -88,8 +122,9 @@
|
|||
<script lang="ts">
|
||||
import themes from "@/data/themes";
|
||||
import { ProgressDisplay, Shape } from "@/game/enums";
|
||||
import { layers } from "@/game/layers";
|
||||
import player from "@/game/player";
|
||||
import { BoardNode, NodeType } from "@/typings/features/board";
|
||||
import { BoardNode, BoardNodeAction, NodeType } from "@/typings/features/board";
|
||||
import { getNodeTypeProperty } from "@/util/features";
|
||||
import { InjectLayerMixin } from "@/util/vue";
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
@ -102,10 +137,11 @@ export default defineComponent({
|
|||
ProgressDisplay,
|
||||
Shape,
|
||||
lastMousePosition: { x: 0, y: 0 },
|
||||
hovering: false
|
||||
hovering: false,
|
||||
sqrtTwo: Math.sqrt(2)
|
||||
};
|
||||
},
|
||||
emits: ["startDragging", "endDragging"],
|
||||
emits: ["mouseDown", "endDragging"],
|
||||
props: {
|
||||
node: {
|
||||
type: Object as PropType<BoardNode>,
|
||||
|
@ -122,12 +158,25 @@ export default defineComponent({
|
|||
type: Object as PropType<{ x: number; y: number }>,
|
||||
required: true
|
||||
},
|
||||
hasDragged: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
receivingNode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
board() {
|
||||
return layers[this.nodeType.layer].boards!.data[this.nodeType.id];
|
||||
},
|
||||
selected() {
|
||||
return this.board.selectedNode?.id === this.node.id;
|
||||
},
|
||||
actions(): BoardNodeAction[] | null | undefined {
|
||||
return getNodeTypeProperty(this.nodeType, this.node, "actions");
|
||||
},
|
||||
draggable(): boolean {
|
||||
return getNodeTypeProperty(this.nodeType, this.node, "draggable");
|
||||
},
|
||||
|
@ -146,7 +195,7 @@ export default defineComponent({
|
|||
let size: number = getNodeTypeProperty(this.nodeType, this.node, "size");
|
||||
if (this.receivingNode) {
|
||||
size *= 1.25;
|
||||
} else if (this.hovering) {
|
||||
} else if (this.hovering || this.selected) {
|
||||
size *= 1.15;
|
||||
}
|
||||
return size;
|
||||
|
@ -188,18 +237,24 @@ export default defineComponent({
|
|||
);
|
||||
},
|
||||
canAccept(): boolean {
|
||||
if (this.dragging == null) {
|
||||
if (this.dragging == null || !this.hasDragged) {
|
||||
return false;
|
||||
}
|
||||
return typeof this.nodeType.canAccept === "boolean"
|
||||
? this.nodeType.canAccept
|
||||
: this.nodeType.canAccept(this.node, this.dragging);
|
||||
},
|
||||
actionDistance(): number {
|
||||
return getNodeTypeProperty(this.nodeType, this.node, "actionDistance");
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
mouseDown(e: MouseEvent) {
|
||||
if (this.draggable) {
|
||||
this.$emit('startDragging', e, this.node.id);
|
||||
this.$emit("mouseDown", e, this.node.id, this.draggable);
|
||||
},
|
||||
mouseUp() {
|
||||
if (!this.hasDragged) {
|
||||
this.nodeType.onClick?.(this.node);
|
||||
}
|
||||
},
|
||||
mouseEnter() {
|
||||
|
@ -207,11 +262,14 @@ export default defineComponent({
|
|||
},
|
||||
mouseLeave() {
|
||||
this.hovering = false;
|
||||
},
|
||||
performAction(action: BoardNodeAction) {
|
||||
action.onClick(this.node);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
onDraggableChanged() {
|
||||
if (this.dragging && !this.draggable) {
|
||||
if (this.dragging?.id === this.node.id && !this.draggable) {
|
||||
this.$emit("endDragging", this.node.id);
|
||||
}
|
||||
}
|
||||
|
@ -230,9 +288,30 @@ export default defineComponent({
|
|||
dominant-baseline: middle;
|
||||
font-family: monospace;
|
||||
font-size: 200%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.progressRing {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.action:hover circle {
|
||||
r: 25;
|
||||
}
|
||||
|
||||
.action:hover text {
|
||||
font-size: 187.5%; /* 150% * 1.25 */
|
||||
}
|
||||
|
||||
.action text {
|
||||
text-anchor: middle;
|
||||
dominant-baseline: central;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.actions-enter-from .action,
|
||||
.actions-leave-to .action {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
</style>
|
||||
|
|
80
src/data/layers/Main.vue
Normal file
80
src/data/layers/Main.vue
Normal file
|
@ -0,0 +1,80 @@
|
|||
<template>
|
||||
<div v-if="devSpeed === 0">Game Paused</div>
|
||||
<div v-else-if="devSpeed && devSpeed !== 1">Dev Speed: {{ formattedDevSpeed }}x</div>
|
||||
<Board id="main" />
|
||||
<Modal :show="showModal" @close="closeModal">
|
||||
<template v-slot:header v-if="title">
|
||||
<component :is="title" />
|
||||
</template>
|
||||
<template v-slot:body v-if="body">
|
||||
<component :is="body" />
|
||||
</template>
|
||||
<template v-slot:footer v-if="footer">
|
||||
<component :is="footer" />
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import player from "@/game/player";
|
||||
import { CoercableComponent } from "@/typings/component";
|
||||
import { format } from "@/util/break_eternity";
|
||||
import { camelToTitle } from "@/util/common";
|
||||
import { coerceComponent } from "@/util/vue";
|
||||
import { computed, defineComponent, shallowRef, watchEffect } from "vue";
|
||||
import { ActionNodeData, ResourceNodeData } from "./main";
|
||||
|
||||
export default defineComponent(function Main() {
|
||||
const title = shallowRef<CoercableComponent | null>(null);
|
||||
const body = shallowRef<CoercableComponent | null>(null);
|
||||
const footer = shallowRef<CoercableComponent | null>(null);
|
||||
|
||||
watchEffect(() => {
|
||||
const node = player.layers.main.boards.main.nodes.find(
|
||||
node => node.id === player.layers.main.openNode
|
||||
);
|
||||
if (node == null) {
|
||||
player.layers.main.showModal = false;
|
||||
return;
|
||||
}
|
||||
switch (node.type) {
|
||||
default:
|
||||
player.layers.main.showModal = false;
|
||||
break;
|
||||
case "resource":
|
||||
switch ((node.data as ResourceNodeData).resourceType) {
|
||||
default:
|
||||
player.layers.main.showModal = false;
|
||||
break;
|
||||
case "time":
|
||||
title.value = coerceComponent("<h2>Time</h2>");
|
||||
body.value = coerceComponent(
|
||||
"The ultimate resource, that you'll never have enough of."
|
||||
);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "action":
|
||||
title.value = coerceComponent(
|
||||
camelToTitle((node.data as ActionNodeData).actionType)
|
||||
);
|
||||
body.value = coerceComponent(
|
||||
"<div><div>" +
|
||||
(node.data as ActionNodeData).log.join("</div><div>") +
|
||||
"</div></div>"
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
const showModal = computed(() => player.layers.main.showModal);
|
||||
const closeModal = () => {
|
||||
player.layers.main.showModal = false;
|
||||
};
|
||||
|
||||
const devSpeed = computed(() => player.devSpeed);
|
||||
const formattedDevSpeed = computed(() => player.devSpeed && format(player.devSpeed));
|
||||
|
||||
return { title, body, footer, showModal, closeModal, devSpeed, formattedDevSpeed };
|
||||
});
|
||||
</script>
|
|
@ -4,36 +4,34 @@ import Decimal, { DecimalSource } from "@/lib/break_eternity";
|
|||
import { RawLayer } from "@/typings/layer";
|
||||
import { camelToTitle } from "@/util/common";
|
||||
import themes from "../themes";
|
||||
import Main from "./Main.vue";
|
||||
|
||||
type ResourceNodeData = {
|
||||
export type ResourceNodeData = {
|
||||
resourceType: string;
|
||||
amount: DecimalSource;
|
||||
maxAmount: DecimalSource;
|
||||
};
|
||||
|
||||
type ItemNodeData = {
|
||||
export type ItemNodeData = {
|
||||
itemType: string;
|
||||
amount: DecimalSource;
|
||||
};
|
||||
|
||||
type ActionNodeData = {
|
||||
export type ActionNodeData = {
|
||||
actionType: string;
|
||||
log: string[];
|
||||
};
|
||||
|
||||
export default {
|
||||
id: "main",
|
||||
display: `
|
||||
<div v-if="player.devSpeed === 0">Game Paused</div>
|
||||
<div v-else-if="player.devSpeed && player.devSpeed !== 1">Dev Speed: {{ format(player.devSpeed) }}x</div>
|
||||
<div>TODO: Board</div>
|
||||
<Board id="main" />
|
||||
|
||||
`,
|
||||
display: Main,
|
||||
startData() {
|
||||
return {
|
||||
openNode: null
|
||||
openNode: null,
|
||||
showModal: false
|
||||
} as {
|
||||
openNode: string | null;
|
||||
showModal: boolean;
|
||||
};
|
||||
},
|
||||
minimizable: false,
|
||||
|
@ -71,7 +69,8 @@ export default {
|
|||
position: { x: -150, y: 150 },
|
||||
type: "action",
|
||||
data: {
|
||||
actionType: "browse"
|
||||
actionType: "web",
|
||||
log: []
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@ -101,31 +100,55 @@ export default {
|
|||
canAccept(node, otherNode) {
|
||||
return otherNode.type === "item";
|
||||
},
|
||||
onClick(node) {
|
||||
player.layers.main.openNode = node.id;
|
||||
player.layers.main.showModal = true;
|
||||
},
|
||||
onDrop(node, otherNode) {
|
||||
const index = player.layers[this.layer].boards[this.id].indexOf(
|
||||
const index = player.layers[this.layer].boards[this.id].nodes.indexOf(
|
||||
otherNode
|
||||
);
|
||||
player.layers[this.layer].boards[this.id].splice(index, 1);
|
||||
player.layers[this.layer].boards[this.id].nodes.splice(index, 1);
|
||||
}
|
||||
},
|
||||
item: {
|
||||
title(node) {
|
||||
return (node.data as ItemNodeData).itemType;
|
||||
},
|
||||
onClick(node) {
|
||||
player.layers.main.openNode = node.id;
|
||||
player.layers.main.showModal = true;
|
||||
},
|
||||
draggable: true
|
||||
},
|
||||
action: {
|
||||
title(node) {
|
||||
return camelToTitle((node.data as ActionNodeData).actionType);
|
||||
},
|
||||
fillColor() {
|
||||
return themes[player.theme].variables["--background-tooltip"];
|
||||
},
|
||||
fillColor: "#000",
|
||||
draggable: true,
|
||||
shape: Shape.Diamond,
|
||||
size: 100,
|
||||
progressColor: "#0FF3",
|
||||
progressDisplay: ProgressDisplay.Outline
|
||||
progressDisplay: ProgressDisplay.Outline,
|
||||
actions: [
|
||||
{
|
||||
id: "info",
|
||||
icon: "history_edu",
|
||||
tooltip: "Log",
|
||||
onClick(node) {
|
||||
player.layers.main.openNode = node.id;
|
||||
player.layers.main.showModal = true;
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "reddit",
|
||||
icon: "reddit",
|
||||
tooltip: "Browse Reddit",
|
||||
onClick(node) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,10 +68,10 @@ function updateLayers(diff: DecimalSource) {
|
|||
);
|
||||
}
|
||||
layers[layer].update?.(diff);
|
||||
if (layers[layer].boards) {
|
||||
Reflect.ownKeys(player.layers[layer].boards).forEach(board => {
|
||||
player.layers[layer].boards[board.toString()].forEach(node => {
|
||||
const nodeType = layers[layer].boards!.data[board.toString()].types[node.type];
|
||||
if (layers[layer].boards && layers[layer].boards?.data) {
|
||||
Object.values(layers[layer].boards!.data!).forEach(board => {
|
||||
board.nodes.forEach(node => {
|
||||
const nodeType = board.types[node.type];
|
||||
nodeType.update?.(node, diff);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -432,6 +432,29 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
|
|||
for (const id in layer.boards.data) {
|
||||
setDefault(layer.boards.data[id], "width", "100%");
|
||||
setDefault(layer.boards.data[id], "height", "400px");
|
||||
setDefault(layer.boards.data[id], "nodes", function() {
|
||||
return playerProxy.layers[this.layer].boards[this.id].nodes;
|
||||
});
|
||||
setDefault(layer.boards.data[id], "selectedNode", function() {
|
||||
return playerProxy.layers[this.layer].boards[this.id].nodes.find(
|
||||
node => node.id === playerProxy.layers[this.layer].boards[this.id].selectedNode
|
||||
);
|
||||
});
|
||||
setDefault(layer.boards.data[id], "selectedAction", function() {
|
||||
if (this.selectedNode == null) {
|
||||
return null;
|
||||
}
|
||||
const nodeType = layers[this.layer].boards!.data[this.id].types[
|
||||
this.selectedNode.type
|
||||
];
|
||||
if (nodeType.actions === null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof nodeType.actions === "function") {
|
||||
return nodeType.actions(this.selectedNode);
|
||||
}
|
||||
return nodeType.actions;
|
||||
});
|
||||
for (const nodeType in layer.boards.data[id].types) {
|
||||
layer.boards.data[id].types[nodeType].layer = layer.id;
|
||||
layer.boards.data[id].types[nodeType].id = id;
|
||||
|
@ -440,16 +463,20 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
|
|||
setDefault(layer.boards.data[id].types[nodeType], "draggable", false);
|
||||
setDefault(layer.boards.data[id].types[nodeType], "shape", Shape.Circle);
|
||||
setDefault(layer.boards.data[id].types[nodeType], "canAccept", false);
|
||||
setDefault(layer.boards.data[id].types[nodeType], "actionDistance", Math.PI / 6);
|
||||
setDefault(
|
||||
layer.boards.data[id].types[nodeType],
|
||||
"progressDisplay",
|
||||
ProgressDisplay.Fill
|
||||
);
|
||||
setDefault(layer.boards.data[id].types[nodeType], "nodes", function() {
|
||||
return playerProxy.layers[this.layer].boards[this.id].filter(
|
||||
return playerProxy.layers[this.layer].boards[this.id].nodes.filter(
|
||||
node => node.type === this.type
|
||||
);
|
||||
});
|
||||
setDefault(layer.boards.data[id].types[nodeType], "onClick", function(node) {
|
||||
playerProxy.layers[this.layer].boards[this.id].selectedNode = node.id;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4
src/typings/component.d.ts
vendored
4
src/typings/component.d.ts
vendored
|
@ -1,3 +1,3 @@
|
|||
import { ComponentOptions } from "vue";
|
||||
import { Component, ComponentOptions } from "vue";
|
||||
|
||||
export type CoercableComponent = string | ComponentOptions;
|
||||
export type CoercableComponent = string | ComponentOptions | Component;
|
||||
|
|
19
src/typings/features/board.d.ts
vendored
19
src/typings/features/board.d.ts
vendored
|
@ -13,9 +13,10 @@ export interface BoardNode {
|
|||
data?: State;
|
||||
}
|
||||
|
||||
export interface CardOption {
|
||||
text: string;
|
||||
selected: (node: BoardNode) => void;
|
||||
export interface BoardData {
|
||||
nodes: BoardNode[];
|
||||
selectedNode: string | null;
|
||||
selectedAction: string | null;
|
||||
}
|
||||
|
||||
export interface Board extends Feature {
|
||||
|
@ -24,6 +25,9 @@ export interface Board extends Feature {
|
|||
height: string;
|
||||
width: string;
|
||||
types: Record<string, NodeType>;
|
||||
nodes: BoardNode[];
|
||||
selectedNode: BoardNode | null;
|
||||
selectedAction: BoardNodeAction | null;
|
||||
}
|
||||
|
||||
export type RawBoard = Omit<RawFeature<Board>, "types" | "startNodes"> & {
|
||||
|
@ -43,8 +47,17 @@ export interface NodeType extends Feature {
|
|||
fillColor?: string | ((node: BoardNode) => string);
|
||||
outlineColor?: string | ((node: BoardNode) => string);
|
||||
titleColor?: string | ((node: BoardNode) => string);
|
||||
actions?: BoardNodeAction[] | ((node: BoardNode) => BoardNodeAction[]);
|
||||
actionDistance: number | ((node: BoardNode) => number);
|
||||
onClick?: (node: BoardNode) => void;
|
||||
onDrop?: (node: BoardNode, otherNode: BoardNode) => void;
|
||||
update?: (node: BoardNode, diff: DecimalSource) => void;
|
||||
nodes: BoardNode[];
|
||||
}
|
||||
|
||||
export interface BoardNodeAction {
|
||||
id: string;
|
||||
icon: string;
|
||||
tooltip: string;
|
||||
onClick: (node: BoardNode) => void;
|
||||
}
|
||||
|
|
4
src/typings/player.d.ts
vendored
4
src/typings/player.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
import { Themes } from "@/data/themes";
|
||||
import { DecimalSource } from "@/lib/break_eternity";
|
||||
import Decimal from "@/util/bignum";
|
||||
import { BoardNode } from "./features/board";
|
||||
import { BoardData, BoardNode } from "./features/board";
|
||||
import { MilestoneDisplay } from "./features/milestone";
|
||||
import { State } from "./state";
|
||||
|
||||
|
@ -62,7 +62,7 @@ export interface LayerSaveData {
|
|||
clickables: Record<string, State>;
|
||||
challenges: Record<string, Decimal>;
|
||||
grids: Record<string, Record<string, State>>;
|
||||
boards: Record<string, BoardNode[]>;
|
||||
boards: Record<string, BoardData>;
|
||||
confirmRespecBuyables: boolean;
|
||||
[index: string]: unknown;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { hotkeys, layers } from "@/game/layers";
|
||||
import player from "@/game/player";
|
||||
import { CacheableFunction } from "@/typings/cacheableFunction";
|
||||
import { Board, BoardNode, RawBoard } from "@/typings/features/board";
|
||||
import { Board, BoardData, BoardNode, RawBoard } from "@/typings/features/board";
|
||||
import { Buyable } from "@/typings/features/buyable";
|
||||
import { Challenge } from "@/typings/features/challenge";
|
||||
import { Clickable } from "@/typings/features/clickable";
|
||||
|
@ -73,17 +73,21 @@ export function getStartingChallenges(
|
|||
|
||||
export function getStartingBoards(
|
||||
boards?: Record<string, Board> | Record<string, RawBoard> | undefined
|
||||
): Record<string, BoardNode[]> {
|
||||
): Record<string, BoardData> {
|
||||
return boards
|
||||
? Object.keys(boards).reduce((acc: Record<string, BoardNode[]>, curr: string): Record<
|
||||
? Object.keys(boards).reduce((acc: Record<string, BoardData>, curr: string): Record<
|
||||
string,
|
||||
BoardNode[]
|
||||
BoardData
|
||||
> => {
|
||||
const nodes = boards[curr].startNodes?.() || [];
|
||||
acc[curr] = nodes.map((node, index) => ({
|
||||
acc[curr] = {
|
||||
nodes: nodes.map((node, index) => ({
|
||||
id: index.toString(),
|
||||
...node
|
||||
})) as BoardNode[];
|
||||
})),
|
||||
selectedNode: null,
|
||||
selectedAction: null
|
||||
} as BoardData;
|
||||
return acc;
|
||||
}, {})
|
||||
: {};
|
||||
|
|
|
@ -43,7 +43,8 @@ function travel(
|
|||
object[key] = computed(object[key].bind(objectProxy));
|
||||
} else if (
|
||||
(isPlainObject(object[key]) || Array.isArray(object[key])) &&
|
||||
!(object[key] instanceof Decimal)
|
||||
!(object[key] instanceof Decimal) &&
|
||||
typeof object[key].render !== "function"
|
||||
) {
|
||||
object[key] = callback(object[key]);
|
||||
}
|
||||
|
@ -62,7 +63,11 @@ const layerHandler: ProxyHandler<Record<string, any>> = {
|
|||
|
||||
if (isRef(target[key])) {
|
||||
return target[key].value;
|
||||
} else if (target[key].isProxy || target[key] instanceof Decimal) {
|
||||
} else if (
|
||||
target[key].isProxy ||
|
||||
target[key] instanceof Decimal ||
|
||||
typeof target[key].render === "function"
|
||||
) {
|
||||
return target[key];
|
||||
} else if (
|
||||
(isPlainObject(target[key]) || Array.isArray(target[key])) &&
|
||||
|
|
|
@ -35,7 +35,7 @@ const data = function(): Record<string, unknown> {
|
|||
return { Decimal, player, layers, hasWon, pointGain, ...numberUtils };
|
||||
};
|
||||
export function coerceComponent(
|
||||
component: string | ComponentOptions,
|
||||
component: string | ComponentOptions | Component,
|
||||
defaultWrapper = "span"
|
||||
): Component | string {
|
||||
if (typeof component === "string") {
|
||||
|
|
Loading…
Reference in a new issue