forked from profectus/Profectus
Implemented confirmable actions (plus logs and other related features)
This commit is contained in:
parent
c4db2a51c7
commit
7618ee291a
12 changed files with 351 additions and 36 deletions
|
@ -2,16 +2,24 @@
|
||||||
<panZoom
|
<panZoom
|
||||||
:style="style"
|
:style="style"
|
||||||
selector="#g1"
|
selector="#g1"
|
||||||
:options="{ initialZoom: 1, minZoom: 0.1, maxZoom: 10 }"
|
:options="{ initialZoom: 1, minZoom: 0.1, maxZoom: 10, zoomDoubleClickSpeed: 1 }"
|
||||||
ref="stage"
|
ref="stage"
|
||||||
@init="onInit"
|
@init="onInit"
|
||||||
@mousemove="drag"
|
@mousemove="drag"
|
||||||
@mousedown="deselect"
|
@touchmove="drag"
|
||||||
|
@mousedown="mouseDown"
|
||||||
|
@touchstart="mouseDown"
|
||||||
@mouseup="() => endDragging(dragging)"
|
@mouseup="() => endDragging(dragging)"
|
||||||
|
@touchend="() => endDragging(dragging)"
|
||||||
@mouseleave="() => 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">
|
||||||
|
<transition-group name="link" appear>
|
||||||
|
<g v-for="(link, index) in board.links || []" :key="index">
|
||||||
|
<BoardLink :link="link" />
|
||||||
|
</g>
|
||||||
|
</transition-group>
|
||||||
<BoardNode
|
<BoardNode
|
||||||
v-for="node in nodes"
|
v-for="node in nodes"
|
||||||
:key="node.id"
|
:key="node.id"
|
||||||
|
@ -127,11 +135,7 @@ export default defineComponent({
|
||||||
onInit(panzoomInstance: any) {
|
onInit(panzoomInstance: any) {
|
||||||
panzoomInstance.setTransformOrigin(null);
|
panzoomInstance.setTransformOrigin(null);
|
||||||
},
|
},
|
||||||
deselect() {
|
mouseDown(e: MouseEvent, nodeID: string | null = null, draggable = false) {
|
||||||
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();
|
||||||
|
@ -147,8 +151,10 @@ export default defineComponent({
|
||||||
this.dragging = nodeID;
|
this.dragging = nodeID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
player.layers[this.layer].boards[this.id].selectedNode = null;
|
if (nodeID != null) {
|
||||||
player.layers[this.layer].boards[this.id].selectedAction = null;
|
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)();
|
const zoom = (this.getZoomLevel as () => number)();
|
||||||
|
@ -186,6 +192,9 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
this.dragging = null;
|
this.dragging = null;
|
||||||
|
} else if (!this.hasDragged) {
|
||||||
|
player.layers[this.layer].boards[this.id].selectedNode = null;
|
||||||
|
player.layers[this.layer].boards[this.id].selectedAction = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,4 +211,9 @@ export default defineComponent({
|
||||||
#g1 {
|
#g1 {
|
||||||
transition-duration: 0s;
|
transition-duration: 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-enter-from,
|
||||||
|
.link-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
55
src/components/board/BoardLink.vue
Normal file
55
src/components/board/BoardLink.vue
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<template>
|
||||||
|
<line
|
||||||
|
v-bind="link"
|
||||||
|
class="link"
|
||||||
|
:class="{ pulsing: link.pulsing }"
|
||||||
|
:x1="startPosition.x"
|
||||||
|
:y1="startPosition.y"
|
||||||
|
:x2="endPosition.x"
|
||||||
|
:y2="endPosition.y"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Position } from "@/typings/branches";
|
||||||
|
import { BoardNodeLink } from "@/typings/features/board";
|
||||||
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: "BoardLink",
|
||||||
|
props: {
|
||||||
|
link: {
|
||||||
|
type: Object as PropType<BoardNodeLink>,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
startPosition(): Position {
|
||||||
|
return this.link.from.position;
|
||||||
|
},
|
||||||
|
endPosition(): Position {
|
||||||
|
return this.link.to.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.link.pulsing {
|
||||||
|
animation: pulsing 2s ease-in infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulsing {
|
||||||
|
0% {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -6,10 +6,12 @@
|
||||||
>
|
>
|
||||||
<transition name="actions" appear>
|
<transition name="actions" appear>
|
||||||
<g v-if="selected && actions">
|
<g v-if="selected && actions">
|
||||||
|
<!-- TODO move to separate file -->
|
||||||
<g
|
<g
|
||||||
v-for="(action, index) in actions"
|
v-for="(action, index) in actions"
|
||||||
:key="action.id"
|
:key="action.id"
|
||||||
class="action"
|
class="action"
|
||||||
|
:class="{ selected: selectedAction === action }"
|
||||||
:transform="
|
:transform="
|
||||||
`translate(
|
`translate(
|
||||||
${(-size - 30) *
|
${(-size - 30) *
|
||||||
|
@ -18,10 +20,23 @@
|
||||||
Math.cos(((actions.length - 1) / 2 - index) * actionDistance)}
|
Math.cos(((actions.length - 1) / 2 - index) * actionDistance)}
|
||||||
)`
|
)`
|
||||||
"
|
"
|
||||||
@click="performAction(action)"
|
@mousedown="e => performAction(e, action)"
|
||||||
>
|
>
|
||||||
<circle :fill="fillColor" r="20" />
|
<circle
|
||||||
<text :fill="titleColor" class="material-icons">{{ action.icon }}</text>
|
:fill="
|
||||||
|
action.fillColor
|
||||||
|
? typeof action.fillColor === 'function'
|
||||||
|
? action.fillColor(node)
|
||||||
|
: action.fillColor
|
||||||
|
: fillColor
|
||||||
|
"
|
||||||
|
r="20"
|
||||||
|
:stroke-width="selectedAction === action ? 4 : 0"
|
||||||
|
:stroke="outlineColor"
|
||||||
|
/>
|
||||||
|
<text :fill="titleColor" class="material-icons">{{
|
||||||
|
typeof action.icon === "function" ? action.icon(node) : action.icon
|
||||||
|
}}</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</transition>
|
</transition>
|
||||||
|
@ -31,7 +46,9 @@
|
||||||
@mouseenter="mouseEnter"
|
@mouseenter="mouseEnter"
|
||||||
@mouseleave="mouseLeave"
|
@mouseleave="mouseLeave"
|
||||||
@mousedown="mouseDown"
|
@mousedown="mouseDown"
|
||||||
|
@touchstart="mouseDown"
|
||||||
@mouseup="mouseUp"
|
@mouseup="mouseUp"
|
||||||
|
@touchend="mouseUp"
|
||||||
>
|
>
|
||||||
<circle
|
<circle
|
||||||
v-if="canAccept"
|
v-if="canAccept"
|
||||||
|
@ -67,7 +84,9 @@
|
||||||
@mouseenter="mouseEnter"
|
@mouseenter="mouseEnter"
|
||||||
@mouseleave="mouseLeave"
|
@mouseleave="mouseLeave"
|
||||||
@mousedown="mouseDown"
|
@mousedown="mouseDown"
|
||||||
|
@touchstart="mouseDown"
|
||||||
@mouseup="mouseUp"
|
@mouseup="mouseUp"
|
||||||
|
@touchend="mouseUp"
|
||||||
>
|
>
|
||||||
<rect
|
<rect
|
||||||
v-if="canAccept"
|
v-if="canAccept"
|
||||||
|
@ -116,6 +135,27 @@
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<text :fill="titleColor" class="node-title">{{ title }}</text>
|
<text :fill="titleColor" class="node-title">{{ title }}</text>
|
||||||
|
|
||||||
|
<transition name="fade" appear>
|
||||||
|
<text
|
||||||
|
v-if="label"
|
||||||
|
:fill="label.color || titleColor"
|
||||||
|
class="node-title"
|
||||||
|
:class="{ pulsing: label.pulsing }"
|
||||||
|
:y="-size - 20"
|
||||||
|
>{{ label.text }}</text
|
||||||
|
>
|
||||||
|
</transition>
|
||||||
|
|
||||||
|
<transition name="fade" appear>
|
||||||
|
<text
|
||||||
|
:fill="titleColor"
|
||||||
|
class="node-title"
|
||||||
|
:y="size + 75"
|
||||||
|
v-if="selected && selectedAction"
|
||||||
|
>Tap again to confirm</text
|
||||||
|
>
|
||||||
|
</transition>
|
||||||
</g>
|
</g>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -124,14 +164,12 @@ import themes from "@/data/themes";
|
||||||
import { ProgressDisplay, Shape } from "@/game/enums";
|
import { ProgressDisplay, Shape } from "@/game/enums";
|
||||||
import { layers } from "@/game/layers";
|
import { layers } from "@/game/layers";
|
||||||
import player from "@/game/player";
|
import player from "@/game/player";
|
||||||
import { BoardNode, BoardNodeAction, NodeType } from "@/typings/features/board";
|
import { BoardNode, BoardNodeAction, NodeLabel, NodeType } from "@/typings/features/board";
|
||||||
import { getNodeTypeProperty } from "@/util/features";
|
import { getNodeTypeProperty } from "@/util/features";
|
||||||
import { InjectLayerMixin } from "@/util/vue";
|
|
||||||
import { defineComponent, PropType } from "vue";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: "BoardNode",
|
name: "BoardNode",
|
||||||
mixins: [InjectLayerMixin],
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
ProgressDisplay,
|
ProgressDisplay,
|
||||||
|
@ -174,6 +212,9 @@ export default defineComponent({
|
||||||
selected() {
|
selected() {
|
||||||
return this.board.selectedNode?.id === this.node.id;
|
return this.board.selectedNode?.id === this.node.id;
|
||||||
},
|
},
|
||||||
|
selectedAction() {
|
||||||
|
return this.board.selectedAction;
|
||||||
|
},
|
||||||
actions(): BoardNodeAction[] | null | undefined {
|
actions(): BoardNodeAction[] | null | undefined {
|
||||||
return getNodeTypeProperty(this.nodeType, this.node, "actions");
|
return getNodeTypeProperty(this.nodeType, this.node, "actions");
|
||||||
},
|
},
|
||||||
|
@ -203,6 +244,9 @@ export default defineComponent({
|
||||||
title(): string {
|
title(): string {
|
||||||
return getNodeTypeProperty(this.nodeType, this.node, "title");
|
return getNodeTypeProperty(this.nodeType, this.node, "title");
|
||||||
},
|
},
|
||||||
|
label(): NodeLabel | null | undefined {
|
||||||
|
return getNodeTypeProperty(this.nodeType, this.node, "label");
|
||||||
|
},
|
||||||
progress(): number {
|
progress(): number {
|
||||||
return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
|
return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
|
||||||
},
|
},
|
||||||
|
@ -263,8 +307,14 @@ export default defineComponent({
|
||||||
mouseLeave() {
|
mouseLeave() {
|
||||||
this.hovering = false;
|
this.hovering = false;
|
||||||
},
|
},
|
||||||
performAction(action: BoardNodeAction) {
|
performAction(e: MouseEvent, action: BoardNodeAction) {
|
||||||
action.onClick(this.node);
|
action.onClick(this.node);
|
||||||
|
// If the onClick function made this action selected,
|
||||||
|
// don't propagate the event (which will deselect everything)
|
||||||
|
if (this.board.selectedAction === action) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -295,11 +345,13 @@ export default defineComponent({
|
||||||
transform: rotate(-90deg);
|
transform: rotate(-90deg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.action:hover circle {
|
.action:hover circle,
|
||||||
|
.action.selected circle {
|
||||||
r: 25;
|
r: 25;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action:hover text {
|
.action:hover text,
|
||||||
|
.action.selected text {
|
||||||
font-size: 187.5%; /* 150% * 1.25 */
|
font-size: 187.5%; /* 150% * 1.25 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,6 +359,29 @@ export default defineComponent({
|
||||||
text-anchor: middle;
|
text-anchor: middle;
|
||||||
dominant-baseline: central;
|
dominant-baseline: central;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulsing {
|
||||||
|
animation: pulsing 2s ease-in infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulsing {
|
||||||
|
0% {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -37,11 +37,11 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
return this.options.stroke!;
|
return this.options.stroke!;
|
||||||
},
|
},
|
||||||
strokeWidth(): string {
|
strokeWidth(): number | string {
|
||||||
if (typeof this.options === "string" || !("stroke-width" in this.options)) {
|
if (typeof this.options === "string" || !("strokeWidth" in this.options)) {
|
||||||
return "15px";
|
return "15";
|
||||||
}
|
}
|
||||||
return this.options["stroke-width"]!;
|
return this.options["strokeWidth"]!;
|
||||||
},
|
},
|
||||||
startPosition(): Position {
|
startPosition(): Position {
|
||||||
const position = { x: this.startNode.x || 0, y: this.startNode.y || 0 };
|
const position = { x: this.startNode.x || 0, y: this.startNode.y || 0 };
|
||||||
|
|
|
@ -59,8 +59,16 @@ export default defineComponent(function Main() {
|
||||||
camelToTitle((node.data as ActionNodeData).actionType)
|
camelToTitle((node.data as ActionNodeData).actionType)
|
||||||
);
|
);
|
||||||
body.value = coerceComponent(
|
body.value = coerceComponent(
|
||||||
"<div><div>" +
|
"<div><div class='entry'>" +
|
||||||
(node.data as ActionNodeData).log.join("</div><div>") +
|
(node.data as ActionNodeData).log
|
||||||
|
.map(log => {
|
||||||
|
let display = log.description;
|
||||||
|
if (log.effectDescription) {
|
||||||
|
display += `<div style="font-style: italic;">${log.effectDescription}</div>`;
|
||||||
|
}
|
||||||
|
return display;
|
||||||
|
})
|
||||||
|
.join("</div><div class='entry'>") +
|
||||||
"</div></div>"
|
"</div></div>"
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
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 Decimal, { DecimalSource } from "@/lib/break_eternity";
|
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 { getUniqueNodeID } from "@/util/features";
|
||||||
import themes from "../themes";
|
import themes from "../themes";
|
||||||
import Main from "./Main.vue";
|
import Main from "./Main.vue";
|
||||||
|
|
||||||
|
@ -19,9 +21,64 @@ export type ItemNodeData = {
|
||||||
|
|
||||||
export type ActionNodeData = {
|
export type ActionNodeData = {
|
||||||
actionType: string;
|
actionType: string;
|
||||||
log: string[];
|
log: LogEntry[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type LogEntry = {
|
||||||
|
description: string;
|
||||||
|
effectDescription?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WeightedEvent = {
|
||||||
|
event: () => LogEntry;
|
||||||
|
weight: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const redditEvents = [
|
||||||
|
{
|
||||||
|
event: () => ({ description: "You blink and half an hour has passed before you know it." }),
|
||||||
|
weight: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: () => {
|
||||||
|
const id = getUniqueNodeID(layers.main.boards!.data.main);
|
||||||
|
player.layers.main.boards.main.nodes.push({
|
||||||
|
id,
|
||||||
|
position: { x: 0, y: 150 }, // TODO function to get nearest unoccupied space
|
||||||
|
type: "item",
|
||||||
|
data: {
|
||||||
|
itemType: "speed",
|
||||||
|
amount: new Decimal(15 * 60)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
description: "You found some funny memes and actually feel a bit refreshed.",
|
||||||
|
effectDescription: `Added <span style="color: #0FF;">Speed</span> node`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
weight: 0.5
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
function getRandomEvent(events: WeightedEvent[]): LogEntry | null {
|
||||||
|
if (events.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const totalWeight = events.reduce((acc, curr) => acc + curr.weight, 0);
|
||||||
|
const random = Math.random() * totalWeight;
|
||||||
|
|
||||||
|
let weight = 0;
|
||||||
|
for (const outcome of events) {
|
||||||
|
weight += outcome.weight;
|
||||||
|
if (random <= weight) {
|
||||||
|
return outcome.event();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should never reach here
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
id: "main",
|
id: "main",
|
||||||
display: Main,
|
display: Main,
|
||||||
|
@ -80,6 +137,21 @@ export default {
|
||||||
title(node) {
|
title(node) {
|
||||||
return (node.data as ResourceNodeData).resourceType;
|
return (node.data as ResourceNodeData).resourceType;
|
||||||
},
|
},
|
||||||
|
label(node) {
|
||||||
|
if (player.layers[this.layer].boards[this.id].selectedAction == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const action = player.layers[this.layer].boards[this.id].selectedAction;
|
||||||
|
switch (action) {
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
case "reddit":
|
||||||
|
if ((node.data as ResourceNodeData).resourceType === "time") {
|
||||||
|
return { text: "30m", color: "red", pulsing: true };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
draggable: true,
|
draggable: true,
|
||||||
progress(node) {
|
progress(node) {
|
||||||
const data = node.data as ResourceNodeData;
|
const data = node.data as ResourceNodeData;
|
||||||
|
@ -109,6 +181,10 @@ export default {
|
||||||
otherNode
|
otherNode
|
||||||
);
|
);
|
||||||
player.layers[this.layer].boards[this.id].nodes.splice(index, 1);
|
player.layers[this.layer].boards[this.id].nodes.splice(index, 1);
|
||||||
|
(node.data as ResourceNodeData).amount = Decimal.add(
|
||||||
|
(node.data as ResourceNodeData).amount,
|
||||||
|
(otherNode.data as ItemNodeData).amount
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
item: {
|
item: {
|
||||||
|
@ -134,6 +210,9 @@ export default {
|
||||||
{
|
{
|
||||||
id: "info",
|
id: "info",
|
||||||
icon: "history_edu",
|
icon: "history_edu",
|
||||||
|
fillColor() {
|
||||||
|
return themes[player.theme].variables["--separator"];
|
||||||
|
},
|
||||||
tooltip: "Log",
|
tooltip: "Log",
|
||||||
onClick(node) {
|
onClick(node) {
|
||||||
player.layers.main.openNode = node.id;
|
player.layers.main.openNode = node.id;
|
||||||
|
@ -145,7 +224,44 @@ export default {
|
||||||
icon: "reddit",
|
icon: "reddit",
|
||||||
tooltip: "Browse Reddit",
|
tooltip: "Browse Reddit",
|
||||||
onClick(node) {
|
onClick(node) {
|
||||||
// TODO
|
if (player.layers.main.boards.main.selectedAction === this.id) {
|
||||||
|
const timeNode = player.layers.main.boards.main.nodes.find(
|
||||||
|
node =>
|
||||||
|
node.type === "resource" &&
|
||||||
|
(node.data as ResourceNodeData).resourceType ===
|
||||||
|
"time"
|
||||||
|
);
|
||||||
|
if (timeNode) {
|
||||||
|
(timeNode.data as ResourceNodeData).amount = Decimal.sub(
|
||||||
|
(timeNode.data as ResourceNodeData).amount,
|
||||||
|
30 * 60
|
||||||
|
);
|
||||||
|
player.layers.main.boards.main.selectedAction = null;
|
||||||
|
(node.data as ActionNodeData).log.push(
|
||||||
|
getRandomEvent(redditEvents)!
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
player.layers.main.boards.main.selectedAction = this.id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
links(node) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
// TODO this is ridiculous and needs some utility
|
||||||
|
// function to shrink it down
|
||||||
|
from: player.layers.main.boards.main.nodes.find(
|
||||||
|
node =>
|
||||||
|
node.type === "resource" &&
|
||||||
|
(node.data as ResourceNodeData).resourceType ===
|
||||||
|
"time"
|
||||||
|
),
|
||||||
|
to: node,
|
||||||
|
stroke: "red",
|
||||||
|
"stroke-width": 4,
|
||||||
|
pulsing: true
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { RawLayer } from "@/typings/layer";
|
import { RawLayer } from "@/typings/layer";
|
||||||
import { PlayerData } from "@/typings/player";
|
import { PlayerData } from "@/typings/player";
|
||||||
import Decimal from "@/util/bignum";
|
import Decimal from "@/util/bignum";
|
||||||
|
import { hardReset } from "@/util/save";
|
||||||
import { computed } from "vue";
|
import { computed } from "vue";
|
||||||
import main from "./layers/main";
|
import main from "./layers/main";
|
||||||
|
|
||||||
|
|
|
@ -450,10 +450,26 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
|
||||||
if (nodeType.actions === null) {
|
if (nodeType.actions === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (typeof nodeType.actions === "function") {
|
const actions =
|
||||||
return nodeType.actions(this.selectedNode);
|
typeof nodeType.actions === "function"
|
||||||
|
? nodeType.actions(this.selectedNode)
|
||||||
|
: nodeType.actions;
|
||||||
|
return actions?.find(
|
||||||
|
action =>
|
||||||
|
action.id === playerProxy.layers[this.layer].boards[this.id].selectedAction
|
||||||
|
);
|
||||||
|
});
|
||||||
|
setDefault(layer.boards.data[id], "links", function() {
|
||||||
|
if (this.selectedAction == null) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return nodeType.actions;
|
if (this.selectedAction.links) {
|
||||||
|
if (typeof this.selectedAction.links === "function") {
|
||||||
|
return this.selectedAction.links(this.selectedNode);
|
||||||
|
}
|
||||||
|
return this.selectedAction.links;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
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;
|
||||||
|
|
3
src/typings/branches.d.ts
vendored
3
src/typings/branches.d.ts
vendored
|
@ -17,9 +17,10 @@ export interface BranchOptions {
|
||||||
target?: string;
|
target?: string;
|
||||||
featureType?: string;
|
featureType?: string;
|
||||||
stroke?: string;
|
stroke?: string;
|
||||||
"stroke-width"?: string;
|
strokeWidth?: number | string;
|
||||||
startOffset?: Position;
|
startOffset?: Position;
|
||||||
endOffset?: Position;
|
endOffset?: Position;
|
||||||
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Position {
|
export interface Position {
|
||||||
|
|
27
src/typings/features/board.d.ts
vendored
27
src/typings/features/board.d.ts
vendored
|
@ -4,7 +4,7 @@ import { State } from "../state";
|
||||||
import { Feature, RawFeature } from "./feature";
|
import { Feature, RawFeature } from "./feature";
|
||||||
|
|
||||||
export interface BoardNode {
|
export interface BoardNode {
|
||||||
id: string;
|
id: number;
|
||||||
position: {
|
position: {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
@ -28,6 +28,7 @@ export interface Board extends Feature {
|
||||||
nodes: BoardNode[];
|
nodes: BoardNode[];
|
||||||
selectedNode: BoardNode | null;
|
selectedNode: BoardNode | null;
|
||||||
selectedAction: BoardNodeAction | null;
|
selectedAction: BoardNodeAction | null;
|
||||||
|
links: BoardNodeLink[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RawBoard = Omit<RawFeature<Board>, "types" | "startNodes"> & {
|
export type RawBoard = Omit<RawFeature<Board>, "types" | "startNodes"> & {
|
||||||
|
@ -37,7 +38,8 @@ export type RawBoard = Omit<RawFeature<Board>, "types" | "startNodes"> & {
|
||||||
|
|
||||||
export interface NodeType extends Feature {
|
export interface NodeType extends Feature {
|
||||||
title: string | ((node: BoardNode) => string);
|
title: string | ((node: BoardNode) => string);
|
||||||
size: number | ((node: BoardNode) => number);
|
label?: NodeLabel | null | ((node: BoardNode) => NodeLabel | null);
|
||||||
|
size: number | string | ((node: BoardNode) => number | string);
|
||||||
draggable: boolean | ((node: BoardNode) => boolean);
|
draggable: boolean | ((node: BoardNode) => boolean);
|
||||||
shape: Shape | ((node: BoardNode) => Shape);
|
shape: Shape | ((node: BoardNode) => Shape);
|
||||||
canAccept: boolean | ((node: BoardNode, otherNode: BoardNode) => boolean);
|
canAccept: boolean | ((node: BoardNode, otherNode: BoardNode) => boolean);
|
||||||
|
@ -57,7 +59,24 @@ export interface NodeType extends Feature {
|
||||||
|
|
||||||
export interface BoardNodeAction {
|
export interface BoardNodeAction {
|
||||||
id: string;
|
id: string;
|
||||||
icon: string;
|
icon: string | ((node: BoardNode) => string);
|
||||||
tooltip: string;
|
fillColor?: string | ((node: BoardNode) => string);
|
||||||
|
tooltip: string | ((node: BoardNode) => string);
|
||||||
onClick: (node: BoardNode) => void;
|
onClick: (node: BoardNode) => void;
|
||||||
|
links?: BoardNodeLink[] | ((node: BoardNode) => BoardNodeLink[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BoardNodeLink {
|
||||||
|
from: BoardNode;
|
||||||
|
to: BoardNode;
|
||||||
|
stroke: string;
|
||||||
|
strokeWidth: number | string;
|
||||||
|
pulsing?: boolean;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NodeLabel {
|
||||||
|
text: string;
|
||||||
|
color?: string;
|
||||||
|
pulsing?: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { layers } from "@/game/layers";
|
import { layers } from "@/game/layers";
|
||||||
import { NodeType, BoardNode } from "@/typings/features/board";
|
import { NodeType, BoardNode, Board } 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";
|
||||||
|
@ -107,3 +107,13 @@ export function getNodeTypeProperty<T, S extends NodeType, R extends keyof S>(
|
||||||
? (nodeType[property] as (node: BoardNode) => T)(node)
|
? (nodeType[property] as (node: BoardNode) => T)(node)
|
||||||
: (nodeType[property] as T);
|
: (nodeType[property] as T);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getUniqueNodeID(board: Board): number {
|
||||||
|
let id = 0;
|
||||||
|
board.nodes.forEach(node => {
|
||||||
|
if (node.id >= id) {
|
||||||
|
id = node.id + 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
|
@ -187,7 +187,7 @@ window.onbeforeunload = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.save = save;
|
window.save = save;
|
||||||
window.hardReset = async () => {
|
export const hardReset = window.hardReset = async () => {
|
||||||
await loadSave(newSave());
|
await loadSave(newSave());
|
||||||
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!))));
|
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!))));
|
||||||
modData.active = player.id;
|
modData.active = player.id;
|
||||||
|
|
Loading…
Reference in a new issue