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"
|
ref="stage"
|
||||||
@init="onInit"
|
@init="onInit"
|
||||||
@mousemove="drag"
|
@mousemove="drag"
|
||||||
|
@mousedown="deselect"
|
||||||
@mouseup="() => endDragging(dragging)"
|
@mouseup="() => endDragging(dragging)"
|
||||||
@mouseleave="() => endDragging(dragging)"
|
@mouseleave="() => endDragging(dragging)"
|
||||||
>
|
>
|
||||||
|
@ -18,18 +19,9 @@
|
||||||
:nodeType="board.types[node.type]"
|
:nodeType="board.types[node.type]"
|
||||||
:dragging="draggingNode"
|
:dragging="draggingNode"
|
||||||
:dragged="dragged"
|
:dragged="dragged"
|
||||||
|
:hasDragged="hasDragged"
|
||||||
:receivingNode="receivingNode?.id === node.id"
|
:receivingNode="receivingNode?.id === node.id"
|
||||||
@startDragging="startDragging"
|
@mouseDown="mouseDown"
|
||||||
@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"
|
@endDragging="endDragging"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
@ -51,11 +43,13 @@ export default defineComponent({
|
||||||
return {
|
return {
|
||||||
lastMousePosition: { x: 0, y: 0 },
|
lastMousePosition: { x: 0, y: 0 },
|
||||||
dragged: { x: 0, y: 0 },
|
dragged: { x: 0, y: 0 },
|
||||||
dragging: null
|
dragging: null,
|
||||||
|
hasDragged: false
|
||||||
} as {
|
} as {
|
||||||
lastMousePosition: { x: number; y: number };
|
lastMousePosition: { x: number; y: number };
|
||||||
dragged: { x: number; y: number };
|
dragged: { x: number; y: number };
|
||||||
dragging: string | null;
|
dragging: string | null;
|
||||||
|
hasDragged: boolean;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -79,14 +73,15 @@ export default defineComponent({
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
draggingNode() {
|
draggingNode() {
|
||||||
return this.dragging
|
return this.dragging ? this.board.nodes.find(node => node.id === this.dragging) : null;
|
||||||
? player.layers[this.layer].boards[this.id].find(node => node.id === this.dragging)
|
|
||||||
: null;
|
|
||||||
},
|
},
|
||||||
nodes() {
|
nodes() {
|
||||||
return player.layers[this.layer].boards[this.id].filter(
|
const nodes = this.board.nodes.slice();
|
||||||
node => node !== this.draggingNode
|
if (this.draggingNode) {
|
||||||
);
|
const draggingNode = nodes.splice(nodes.indexOf(this.draggingNode), 1)[0];
|
||||||
|
nodes.push(draggingNode);
|
||||||
|
}
|
||||||
|
return nodes;
|
||||||
},
|
},
|
||||||
receivingNode(): BoardNode | null {
|
receivingNode(): BoardNode | null {
|
||||||
if (this.draggingNode == null) {
|
if (this.draggingNode == null) {
|
||||||
|
@ -99,6 +94,9 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
let smallestDistance = Number.MAX_VALUE;
|
let smallestDistance = Number.MAX_VALUE;
|
||||||
return this.nodes.reduce((smallest: BoardNode | null, curr: BoardNode) => {
|
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 nodeType = this.board.types[curr.type];
|
||||||
const canAccept =
|
const canAccept =
|
||||||
typeof nodeType.canAccept === "boolean"
|
typeof nodeType.canAccept === "boolean"
|
||||||
|
@ -126,10 +124,14 @@ export default defineComponent({
|
||||||
getZoomLevel(): number {
|
getZoomLevel(): number {
|
||||||
return (this.$refs.stage as any).$panZoomInstance.getTransform().scale;
|
return (this.$refs.stage as any).$panZoomInstance.getTransform().scale;
|
||||||
},
|
},
|
||||||
onInit: function(panzoomInstance: any) {
|
onInit(panzoomInstance: any) {
|
||||||
panzoomInstance.setTransformOrigin(null);
|
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) {
|
if (this.dragging == null) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
@ -139,33 +141,43 @@ export default defineComponent({
|
||||||
y: e.clientY
|
y: e.clientY
|
||||||
};
|
};
|
||||||
this.dragged = { x: 0, y: 0 };
|
this.dragged = { x: 0, y: 0 };
|
||||||
|
this.hasDragged = false;
|
||||||
|
|
||||||
this.dragging = nodeID;
|
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) {
|
drag(e: MouseEvent) {
|
||||||
|
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) {
|
if (this.dragging) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
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
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
endDragging(nodeID: string | null) {
|
endDragging(nodeID: string | null) {
|
||||||
if (this.dragging != null && this.dragging === nodeID) {
|
if (this.dragging != null && this.dragging === nodeID) {
|
||||||
const nodes = player.layers[this.layer].boards[this.id];
|
|
||||||
const draggingNode = this.draggingNode!;
|
const draggingNode = this.draggingNode!;
|
||||||
const receivingNode = this.receivingNode;
|
const receivingNode = this.receivingNode;
|
||||||
draggingNode.position.x += Math.round(this.dragged.x / 25) * 25;
|
draggingNode.position.x += Math.round(this.dragged.x / 25) * 25;
|
||||||
draggingNode.position.y += Math.round(this.dragged.y / 25) * 25;
|
draggingNode.position.y += Math.round(this.dragged.y / 25) * 25;
|
||||||
|
|
||||||
|
const nodes = this.board.nodes;
|
||||||
nodes.splice(nodes.indexOf(draggingNode), 1);
|
nodes.splice(nodes.indexOf(draggingNode), 1);
|
||||||
nodes.push(draggingNode);
|
nodes.push(draggingNode);
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,38 @@
|
||||||
<template>
|
<template>
|
||||||
<g
|
<g
|
||||||
class="boardnode"
|
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})`"
|
:transform="`translate(${position.x},${position.y})`"
|
||||||
@mouseenter="mouseEnter"
|
|
||||||
@mouseleave="mouseLeave"
|
|
||||||
@mousedown="mouseDown"
|
|
||||||
>
|
>
|
||||||
<g v-if="shape === Shape.Circle">
|
<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"
|
||||||
|
>
|
||||||
<circle
|
<circle
|
||||||
v-if="canAccept"
|
v-if="canAccept"
|
||||||
:r="size + 8"
|
:r="size + 8"
|
||||||
|
@ -36,21 +61,30 @@
|
||||||
:stroke="progressColor"
|
:stroke="progressColor"
|
||||||
/>
|
/>
|
||||||
</g>
|
</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
|
<rect
|
||||||
v-if="canAccept"
|
v-if="canAccept"
|
||||||
:width="size + 16"
|
:width="size * sqrtTwo + 16"
|
||||||
:height="size + 16"
|
:height="size * sqrtTwo + 16"
|
||||||
:transform="`translate(${-(size + 16) / 2}, ${-(size + 16) / 2})`"
|
:transform="
|
||||||
|
`translate(${-(size * sqrtTwo + 16) / 2}, ${-(size * sqrtTwo + 16) / 2})`
|
||||||
|
"
|
||||||
:fill="backgroundColor"
|
:fill="backgroundColor"
|
||||||
:stroke="receivingNode ? '#0F0' : '#0F03'"
|
:stroke="receivingNode ? '#0F0' : '#0F03'"
|
||||||
:stroke-width="2"
|
:stroke-width="2"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<rect
|
<rect
|
||||||
:width="size"
|
:width="size * sqrtTwo"
|
||||||
:height="size"
|
:height="size * sqrtTwo"
|
||||||
:transform="`translate(${-size / 2}, ${-size / 2})`"
|
:transform="`translate(${(-size * sqrtTwo) / 2}, ${(-size * sqrtTwo) / 2})`"
|
||||||
:fill="fillColor"
|
:fill="fillColor"
|
||||||
:stroke="outlineColor"
|
:stroke="outlineColor"
|
||||||
:stroke-width="4"
|
:stroke-width="4"
|
||||||
|
@ -58,11 +92,11 @@
|
||||||
|
|
||||||
<rect
|
<rect
|
||||||
v-if="progressDisplay === ProgressDisplay.Fill"
|
v-if="progressDisplay === ProgressDisplay.Fill"
|
||||||
:width="Math.max(size * progress - 2, 0)"
|
:width="Math.max(size * sqrtTwo * progress - 2, 0)"
|
||||||
:height="Math.max(size * progress - 2, 0)"
|
:height="Math.max(size * sqrtTwo * progress - 2, 0)"
|
||||||
:transform="
|
:transform="
|
||||||
`translate(${-Math.max(size * progress - 2, 0) / 2}, ${-Math.max(
|
`translate(${-Math.max(size * sqrtTwo * progress - 2, 0) / 2}, ${-Math.max(
|
||||||
size * progress - 2,
|
size * sqrtTwo * progress - 2,
|
||||||
0
|
0
|
||||||
) / 2})`
|
) / 2})`
|
||||||
"
|
"
|
||||||
|
@ -70,13 +104,13 @@
|
||||||
/>
|
/>
|
||||||
<rect
|
<rect
|
||||||
v-else
|
v-else
|
||||||
:width="size + 9"
|
:width="size * sqrtTwo + 9"
|
||||||
:height="size + 9"
|
:height="size * sqrtTwo + 9"
|
||||||
:transform="`translate(${-(size + 9) / 2}, ${-(size + 9) / 2})`"
|
:transform="`translate(${-(size * sqrtTwo + 9) / 2}, ${-(size * sqrtTwo + 9) / 2})`"
|
||||||
fill="transparent"
|
fill="transparent"
|
||||||
:stroke-dasharray="(size + 9) * 4"
|
:stroke-dasharray="(size * sqrtTwo + 9) * 4"
|
||||||
:stroke-width="5"
|
: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"
|
:stroke="progressColor"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
@ -88,8 +122,9 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import themes from "@/data/themes";
|
import themes from "@/data/themes";
|
||||||
import { ProgressDisplay, Shape } from "@/game/enums";
|
import { ProgressDisplay, Shape } from "@/game/enums";
|
||||||
|
import { layers } from "@/game/layers";
|
||||||
import player from "@/game/player";
|
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 { getNodeTypeProperty } from "@/util/features";
|
||||||
import { InjectLayerMixin } from "@/util/vue";
|
import { InjectLayerMixin } from "@/util/vue";
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
@ -102,10 +137,11 @@ export default defineComponent({
|
||||||
ProgressDisplay,
|
ProgressDisplay,
|
||||||
Shape,
|
Shape,
|
||||||
lastMousePosition: { x: 0, y: 0 },
|
lastMousePosition: { x: 0, y: 0 },
|
||||||
hovering: false
|
hovering: false,
|
||||||
|
sqrtTwo: Math.sqrt(2)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
emits: ["startDragging", "endDragging"],
|
emits: ["mouseDown", "endDragging"],
|
||||||
props: {
|
props: {
|
||||||
node: {
|
node: {
|
||||||
type: Object as PropType<BoardNode>,
|
type: Object as PropType<BoardNode>,
|
||||||
|
@ -122,12 +158,25 @@ export default defineComponent({
|
||||||
type: Object as PropType<{ x: number; y: number }>,
|
type: Object as PropType<{ x: number; y: number }>,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
hasDragged: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
receivingNode: {
|
receivingNode: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
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 {
|
draggable(): boolean {
|
||||||
return getNodeTypeProperty(this.nodeType, this.node, "draggable");
|
return getNodeTypeProperty(this.nodeType, this.node, "draggable");
|
||||||
},
|
},
|
||||||
|
@ -146,7 +195,7 @@ export default defineComponent({
|
||||||
let size: number = getNodeTypeProperty(this.nodeType, this.node, "size");
|
let size: number = getNodeTypeProperty(this.nodeType, this.node, "size");
|
||||||
if (this.receivingNode) {
|
if (this.receivingNode) {
|
||||||
size *= 1.25;
|
size *= 1.25;
|
||||||
} else if (this.hovering) {
|
} else if (this.hovering || this.selected) {
|
||||||
size *= 1.15;
|
size *= 1.15;
|
||||||
}
|
}
|
||||||
return size;
|
return size;
|
||||||
|
@ -188,18 +237,24 @@ export default defineComponent({
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
canAccept(): boolean {
|
canAccept(): boolean {
|
||||||
if (this.dragging == null) {
|
if (this.dragging == null || !this.hasDragged) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return typeof this.nodeType.canAccept === "boolean"
|
return typeof this.nodeType.canAccept === "boolean"
|
||||||
? this.nodeType.canAccept
|
? this.nodeType.canAccept
|
||||||
: this.nodeType.canAccept(this.node, this.dragging);
|
: this.nodeType.canAccept(this.node, this.dragging);
|
||||||
|
},
|
||||||
|
actionDistance(): number {
|
||||||
|
return getNodeTypeProperty(this.nodeType, this.node, "actionDistance");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
mouseDown(e: MouseEvent) {
|
mouseDown(e: MouseEvent) {
|
||||||
if (this.draggable) {
|
this.$emit("mouseDown", e, this.node.id, this.draggable);
|
||||||
this.$emit('startDragging', e, this.node.id);
|
},
|
||||||
|
mouseUp() {
|
||||||
|
if (!this.hasDragged) {
|
||||||
|
this.nodeType.onClick?.(this.node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mouseEnter() {
|
mouseEnter() {
|
||||||
|
@ -207,11 +262,14 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
mouseLeave() {
|
mouseLeave() {
|
||||||
this.hovering = false;
|
this.hovering = false;
|
||||||
|
},
|
||||||
|
performAction(action: BoardNodeAction) {
|
||||||
|
action.onClick(this.node);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
onDraggableChanged() {
|
onDraggableChanged() {
|
||||||
if (this.dragging && !this.draggable) {
|
if (this.dragging?.id === this.node.id && !this.draggable) {
|
||||||
this.$emit("endDragging", this.node.id);
|
this.$emit("endDragging", this.node.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,9 +288,30 @@ export default defineComponent({
|
||||||
dominant-baseline: middle;
|
dominant-baseline: middle;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 200%;
|
font-size: 200%;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.progressRing {
|
.progressRing {
|
||||||
transform: rotate(-90deg);
|
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>
|
</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 { RawLayer } from "@/typings/layer";
|
||||||
import { camelToTitle } from "@/util/common";
|
import { camelToTitle } from "@/util/common";
|
||||||
import themes from "../themes";
|
import themes from "../themes";
|
||||||
|
import Main from "./Main.vue";
|
||||||
|
|
||||||
type ResourceNodeData = {
|
export type ResourceNodeData = {
|
||||||
resourceType: string;
|
resourceType: string;
|
||||||
amount: DecimalSource;
|
amount: DecimalSource;
|
||||||
maxAmount: DecimalSource;
|
maxAmount: DecimalSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ItemNodeData = {
|
export type ItemNodeData = {
|
||||||
itemType: string;
|
itemType: string;
|
||||||
amount: DecimalSource;
|
amount: DecimalSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ActionNodeData = {
|
export type ActionNodeData = {
|
||||||
actionType: string;
|
actionType: string;
|
||||||
|
log: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: "main",
|
id: "main",
|
||||||
display: `
|
display: Main,
|
||||||
<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" />
|
|
||||||
|
|
||||||
`,
|
|
||||||
startData() {
|
startData() {
|
||||||
return {
|
return {
|
||||||
openNode: null
|
openNode: null,
|
||||||
|
showModal: false
|
||||||
} as {
|
} as {
|
||||||
openNode: string | null;
|
openNode: string | null;
|
||||||
|
showModal: boolean;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
minimizable: false,
|
minimizable: false,
|
||||||
|
@ -71,7 +69,8 @@ export default {
|
||||||
position: { x: -150, y: 150 },
|
position: { x: -150, y: 150 },
|
||||||
type: "action",
|
type: "action",
|
||||||
data: {
|
data: {
|
||||||
actionType: "browse"
|
actionType: "web",
|
||||||
|
log: []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -101,31 +100,55 @@ export default {
|
||||||
canAccept(node, otherNode) {
|
canAccept(node, otherNode) {
|
||||||
return otherNode.type === "item";
|
return otherNode.type === "item";
|
||||||
},
|
},
|
||||||
|
onClick(node) {
|
||||||
|
player.layers.main.openNode = node.id;
|
||||||
|
player.layers.main.showModal = true;
|
||||||
|
},
|
||||||
onDrop(node, otherNode) {
|
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
|
otherNode
|
||||||
);
|
);
|
||||||
player.layers[this.layer].boards[this.id].splice(index, 1);
|
player.layers[this.layer].boards[this.id].nodes.splice(index, 1);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
title(node) {
|
title(node) {
|
||||||
return (node.data as ItemNodeData).itemType;
|
return (node.data as ItemNodeData).itemType;
|
||||||
},
|
},
|
||||||
|
onClick(node) {
|
||||||
|
player.layers.main.openNode = node.id;
|
||||||
|
player.layers.main.showModal = true;
|
||||||
|
},
|
||||||
draggable: true
|
draggable: true
|
||||||
},
|
},
|
||||||
action: {
|
action: {
|
||||||
title(node) {
|
title(node) {
|
||||||
return camelToTitle((node.data as ActionNodeData).actionType);
|
return camelToTitle((node.data as ActionNodeData).actionType);
|
||||||
},
|
},
|
||||||
fillColor() {
|
fillColor: "#000",
|
||||||
return themes[player.theme].variables["--background-tooltip"];
|
|
||||||
},
|
|
||||||
draggable: true,
|
draggable: true,
|
||||||
shape: Shape.Diamond,
|
shape: Shape.Diamond,
|
||||||
size: 100,
|
|
||||||
progressColor: "#0FF3",
|
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);
|
layers[layer].update?.(diff);
|
||||||
if (layers[layer].boards) {
|
if (layers[layer].boards && layers[layer].boards?.data) {
|
||||||
Reflect.ownKeys(player.layers[layer].boards).forEach(board => {
|
Object.values(layers[layer].boards!.data!).forEach(board => {
|
||||||
player.layers[layer].boards[board.toString()].forEach(node => {
|
board.nodes.forEach(node => {
|
||||||
const nodeType = layers[layer].boards!.data[board.toString()].types[node.type];
|
const nodeType = board.types[node.type];
|
||||||
nodeType.update?.(node, diff);
|
nodeType.update?.(node, diff);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -432,6 +432,29 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
|
||||||
for (const id in layer.boards.data) {
|
for (const id in layer.boards.data) {
|
||||||
setDefault(layer.boards.data[id], "width", "100%");
|
setDefault(layer.boards.data[id], "width", "100%");
|
||||||
setDefault(layer.boards.data[id], "height", "400px");
|
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) {
|
for (const nodeType in layer.boards.data[id].types) {
|
||||||
layer.boards.data[id].types[nodeType].layer = layer.id;
|
layer.boards.data[id].types[nodeType].layer = layer.id;
|
||||||
layer.boards.data[id].types[nodeType].id = 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], "draggable", false);
|
||||||
setDefault(layer.boards.data[id].types[nodeType], "shape", Shape.Circle);
|
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], "canAccept", false);
|
||||||
|
setDefault(layer.boards.data[id].types[nodeType], "actionDistance", Math.PI / 6);
|
||||||
setDefault(
|
setDefault(
|
||||||
layer.boards.data[id].types[nodeType],
|
layer.boards.data[id].types[nodeType],
|
||||||
"progressDisplay",
|
"progressDisplay",
|
||||||
ProgressDisplay.Fill
|
ProgressDisplay.Fill
|
||||||
);
|
);
|
||||||
setDefault(layer.boards.data[id].types[nodeType], "nodes", function() {
|
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
|
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;
|
data?: State;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardOption {
|
export interface BoardData {
|
||||||
text: string;
|
nodes: BoardNode[];
|
||||||
selected: (node: BoardNode) => void;
|
selectedNode: string | null;
|
||||||
|
selectedAction: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Board extends Feature {
|
export interface Board extends Feature {
|
||||||
|
@ -24,6 +25,9 @@ export interface Board extends Feature {
|
||||||
height: string;
|
height: string;
|
||||||
width: string;
|
width: string;
|
||||||
types: Record<string, NodeType>;
|
types: Record<string, NodeType>;
|
||||||
|
nodes: BoardNode[];
|
||||||
|
selectedNode: BoardNode | null;
|
||||||
|
selectedAction: BoardNodeAction | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RawBoard = Omit<RawFeature<Board>, "types" | "startNodes"> & {
|
export type RawBoard = Omit<RawFeature<Board>, "types" | "startNodes"> & {
|
||||||
|
@ -43,8 +47,17 @@ export interface NodeType extends Feature {
|
||||||
fillColor?: string | ((node: BoardNode) => string);
|
fillColor?: string | ((node: BoardNode) => string);
|
||||||
outlineColor?: string | ((node: BoardNode) => string);
|
outlineColor?: string | ((node: BoardNode) => string);
|
||||||
titleColor?: string | ((node: BoardNode) => string);
|
titleColor?: string | ((node: BoardNode) => string);
|
||||||
|
actions?: BoardNodeAction[] | ((node: BoardNode) => BoardNodeAction[]);
|
||||||
|
actionDistance: number | ((node: BoardNode) => number);
|
||||||
onClick?: (node: BoardNode) => void;
|
onClick?: (node: BoardNode) => void;
|
||||||
onDrop?: (node: BoardNode, otherNode: BoardNode) => void;
|
onDrop?: (node: BoardNode, otherNode: BoardNode) => void;
|
||||||
update?: (node: BoardNode, diff: DecimalSource) => void;
|
update?: (node: BoardNode, diff: DecimalSource) => void;
|
||||||
nodes: BoardNode[];
|
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 { Themes } from "@/data/themes";
|
||||||
import { DecimalSource } from "@/lib/break_eternity";
|
import { DecimalSource } from "@/lib/break_eternity";
|
||||||
import Decimal from "@/util/bignum";
|
import Decimal from "@/util/bignum";
|
||||||
import { BoardNode } from "./features/board";
|
import { BoardData, BoardNode } from "./features/board";
|
||||||
import { MilestoneDisplay } from "./features/milestone";
|
import { MilestoneDisplay } from "./features/milestone";
|
||||||
import { State } from "./state";
|
import { State } from "./state";
|
||||||
|
|
||||||
|
@ -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, BoardNode[]>;
|
boards: Record<string, BoardData>;
|
||||||
confirmRespecBuyables: boolean;
|
confirmRespecBuyables: boolean;
|
||||||
[index: string]: unknown;
|
[index: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { hotkeys, layers } from "@/game/layers";
|
import { hotkeys, layers } from "@/game/layers";
|
||||||
import player from "@/game/player";
|
import player from "@/game/player";
|
||||||
import { CacheableFunction } from "@/typings/cacheableFunction";
|
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 { Buyable } from "@/typings/features/buyable";
|
||||||
import { Challenge } from "@/typings/features/challenge";
|
import { Challenge } from "@/typings/features/challenge";
|
||||||
import { Clickable } from "@/typings/features/clickable";
|
import { Clickable } from "@/typings/features/clickable";
|
||||||
|
@ -73,17 +73,21 @@ 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, BoardNode[]> {
|
): Record<string, BoardData> {
|
||||||
return boards
|
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,
|
string,
|
||||||
BoardNode[]
|
BoardData
|
||||||
> => {
|
> => {
|
||||||
const nodes = boards[curr].startNodes?.() || [];
|
const nodes = boards[curr].startNodes?.() || [];
|
||||||
acc[curr] = nodes.map((node, index) => ({
|
acc[curr] = {
|
||||||
id: index.toString(),
|
nodes: nodes.map((node, index) => ({
|
||||||
...node
|
id: index.toString(),
|
||||||
})) as BoardNode[];
|
...node
|
||||||
|
})),
|
||||||
|
selectedNode: null,
|
||||||
|
selectedAction: null
|
||||||
|
} as BoardData;
|
||||||
return acc;
|
return acc;
|
||||||
}, {})
|
}, {})
|
||||||
: {};
|
: {};
|
||||||
|
|
|
@ -43,7 +43,8 @@ function travel(
|
||||||
object[key] = computed(object[key].bind(objectProxy));
|
object[key] = computed(object[key].bind(objectProxy));
|
||||||
} else if (
|
} else if (
|
||||||
(isPlainObject(object[key]) || Array.isArray(object[key])) &&
|
(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]);
|
object[key] = callback(object[key]);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +63,11 @@ const layerHandler: ProxyHandler<Record<string, any>> = {
|
||||||
|
|
||||||
if (isRef(target[key])) {
|
if (isRef(target[key])) {
|
||||||
return target[key].value;
|
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];
|
return target[key];
|
||||||
} else if (
|
} else if (
|
||||||
(isPlainObject(target[key]) || Array.isArray(target[key])) &&
|
(isPlainObject(target[key]) || Array.isArray(target[key])) &&
|
||||||
|
|
|
@ -35,7 +35,7 @@ const data = function(): Record<string, unknown> {
|
||||||
return { Decimal, player, layers, hasWon, pointGain, ...numberUtils };
|
return { Decimal, player, layers, hasWon, pointGain, ...numberUtils };
|
||||||
};
|
};
|
||||||
export function coerceComponent(
|
export function coerceComponent(
|
||||||
component: string | ComponentOptions,
|
component: string | ComponentOptions | Component,
|
||||||
defaultWrapper = "span"
|
defaultWrapper = "span"
|
||||||
): Component | string {
|
): Component | string {
|
||||||
if (typeof component === "string") {
|
if (typeof component === "string") {
|
||||||
|
|
Loading…
Reference in a new issue