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
|
||||
:style="style"
|
||||
selector="#g1"
|
||||
:options="{ initialZoom: 1, minZoom: 0.1, maxZoom: 10 }"
|
||||
:options="{ initialZoom: 1, minZoom: 0.1, maxZoom: 10, zoomDoubleClickSpeed: 1 }"
|
||||
ref="stage"
|
||||
@init="onInit"
|
||||
@mousemove="drag"
|
||||
@mousedown="deselect"
|
||||
@touchmove="drag"
|
||||
@mousedown="mouseDown"
|
||||
@touchstart="mouseDown"
|
||||
@mouseup="() => endDragging(dragging)"
|
||||
@touchend="() => endDragging(dragging)"
|
||||
@mouseleave="() => endDragging(dragging)"
|
||||
>
|
||||
<svg class="stage" width="100%" height="100%">
|
||||
<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
|
||||
v-for="node in nodes"
|
||||
:key="node.id"
|
||||
|
@ -127,11 +135,7 @@ export default defineComponent({
|
|||
onInit(panzoomInstance: any) {
|
||||
panzoomInstance.setTransformOrigin(null);
|
||||
},
|
||||
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) {
|
||||
mouseDown(e: MouseEvent, nodeID: string | null = null, draggable = false) {
|
||||
if (this.dragging == null) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -147,8 +151,10 @@ export default defineComponent({
|
|||
this.dragging = nodeID;
|
||||
}
|
||||
}
|
||||
player.layers[this.layer].boards[this.id].selectedNode = null;
|
||||
player.layers[this.layer].boards[this.id].selectedAction = null;
|
||||
if (nodeID != null) {
|
||||
player.layers[this.layer].boards[this.id].selectedNode = null;
|
||||
player.layers[this.layer].boards[this.id].selectedAction = null;
|
||||
}
|
||||
},
|
||||
drag(e: MouseEvent) {
|
||||
const zoom = (this.getZoomLevel as () => number)();
|
||||
|
@ -186,6 +192,9 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
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 {
|
||||
transition-duration: 0s;
|
||||
}
|
||||
|
||||
.link-enter-from,
|
||||
.link-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</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>
|
||||
<g v-if="selected && actions">
|
||||
<!-- TODO move to separate file -->
|
||||
<g
|
||||
v-for="(action, index) in actions"
|
||||
:key="action.id"
|
||||
class="action"
|
||||
:class="{ selected: selectedAction === action }"
|
||||
:transform="
|
||||
`translate(
|
||||
${(-size - 30) *
|
||||
|
@ -18,10 +20,23 @@
|
|||
Math.cos(((actions.length - 1) / 2 - index) * actionDistance)}
|
||||
)`
|
||||
"
|
||||
@click="performAction(action)"
|
||||
@mousedown="e => performAction(e, action)"
|
||||
>
|
||||
<circle :fill="fillColor" r="20" />
|
||||
<text :fill="titleColor" class="material-icons">{{ action.icon }}</text>
|
||||
<circle
|
||||
: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>
|
||||
</transition>
|
||||
|
@ -31,7 +46,9 @@
|
|||
@mouseenter="mouseEnter"
|
||||
@mouseleave="mouseLeave"
|
||||
@mousedown="mouseDown"
|
||||
@touchstart="mouseDown"
|
||||
@mouseup="mouseUp"
|
||||
@touchend="mouseUp"
|
||||
>
|
||||
<circle
|
||||
v-if="canAccept"
|
||||
|
@ -67,7 +84,9 @@
|
|||
@mouseenter="mouseEnter"
|
||||
@mouseleave="mouseLeave"
|
||||
@mousedown="mouseDown"
|
||||
@touchstart="mouseDown"
|
||||
@mouseup="mouseUp"
|
||||
@touchend="mouseUp"
|
||||
>
|
||||
<rect
|
||||
v-if="canAccept"
|
||||
|
@ -116,6 +135,27 @@
|
|||
</g>
|
||||
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
@ -124,14 +164,12 @@ import themes from "@/data/themes";
|
|||
import { ProgressDisplay, Shape } from "@/game/enums";
|
||||
import { layers } from "@/game/layers";
|
||||
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 { InjectLayerMixin } from "@/util/vue";
|
||||
import { defineComponent, PropType } from "vue";
|
||||
|
||||
export default defineComponent({
|
||||
name: "BoardNode",
|
||||
mixins: [InjectLayerMixin],
|
||||
data() {
|
||||
return {
|
||||
ProgressDisplay,
|
||||
|
@ -174,6 +212,9 @@ export default defineComponent({
|
|||
selected() {
|
||||
return this.board.selectedNode?.id === this.node.id;
|
||||
},
|
||||
selectedAction() {
|
||||
return this.board.selectedAction;
|
||||
},
|
||||
actions(): BoardNodeAction[] | null | undefined {
|
||||
return getNodeTypeProperty(this.nodeType, this.node, "actions");
|
||||
},
|
||||
|
@ -203,6 +244,9 @@ export default defineComponent({
|
|||
title(): string {
|
||||
return getNodeTypeProperty(this.nodeType, this.node, "title");
|
||||
},
|
||||
label(): NodeLabel | null | undefined {
|
||||
return getNodeTypeProperty(this.nodeType, this.node, "label");
|
||||
},
|
||||
progress(): number {
|
||||
return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
|
||||
},
|
||||
|
@ -263,8 +307,14 @@ export default defineComponent({
|
|||
mouseLeave() {
|
||||
this.hovering = false;
|
||||
},
|
||||
performAction(action: BoardNodeAction) {
|
||||
performAction(e: MouseEvent, action: BoardNodeAction) {
|
||||
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: {
|
||||
|
@ -295,11 +345,13 @@ export default defineComponent({
|
|||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.action:hover circle {
|
||||
.action:hover circle,
|
||||
.action.selected circle {
|
||||
r: 25;
|
||||
}
|
||||
|
||||
.action:hover text {
|
||||
.action:hover text,
|
||||
.action.selected text {
|
||||
font-size: 187.5%; /* 150% * 1.25 */
|
||||
}
|
||||
|
||||
|
@ -307,6 +359,29 @@ export default defineComponent({
|
|||
text-anchor: middle;
|
||||
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>
|
||||
|
|
|
@ -37,11 +37,11 @@ export default defineComponent({
|
|||
}
|
||||
return this.options.stroke!;
|
||||
},
|
||||
strokeWidth(): string {
|
||||
if (typeof this.options === "string" || !("stroke-width" in this.options)) {
|
||||
return "15px";
|
||||
strokeWidth(): number | string {
|
||||
if (typeof this.options === "string" || !("strokeWidth" in this.options)) {
|
||||
return "15";
|
||||
}
|
||||
return this.options["stroke-width"]!;
|
||||
return this.options["strokeWidth"]!;
|
||||
},
|
||||
startPosition(): Position {
|
||||
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)
|
||||
);
|
||||
body.value = coerceComponent(
|
||||
"<div><div>" +
|
||||
(node.data as ActionNodeData).log.join("</div><div>") +
|
||||
"<div><div class='entry'>" +
|
||||
(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>"
|
||||
);
|
||||
break;
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import { ProgressDisplay, Shape } from "@/game/enums";
|
||||
import { layers } from "@/game/layers";
|
||||
import player from "@/game/player";
|
||||
import Decimal, { DecimalSource } from "@/lib/break_eternity";
|
||||
import { RawLayer } from "@/typings/layer";
|
||||
import { camelToTitle } from "@/util/common";
|
||||
import { getUniqueNodeID } from "@/util/features";
|
||||
import themes from "../themes";
|
||||
import Main from "./Main.vue";
|
||||
|
||||
|
@ -19,9 +21,64 @@ export type ItemNodeData = {
|
|||
|
||||
export type ActionNodeData = {
|
||||
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 {
|
||||
id: "main",
|
||||
display: Main,
|
||||
|
@ -80,6 +137,21 @@ export default {
|
|||
title(node) {
|
||||
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,
|
||||
progress(node) {
|
||||
const data = node.data as ResourceNodeData;
|
||||
|
@ -109,6 +181,10 @@ export default {
|
|||
otherNode
|
||||
);
|
||||
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: {
|
||||
|
@ -134,6 +210,9 @@ export default {
|
|||
{
|
||||
id: "info",
|
||||
icon: "history_edu",
|
||||
fillColor() {
|
||||
return themes[player.theme].variables["--separator"];
|
||||
},
|
||||
tooltip: "Log",
|
||||
onClick(node) {
|
||||
player.layers.main.openNode = node.id;
|
||||
|
@ -145,7 +224,44 @@ export default {
|
|||
icon: "reddit",
|
||||
tooltip: "Browse Reddit",
|
||||
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 { PlayerData } from "@/typings/player";
|
||||
import Decimal from "@/util/bignum";
|
||||
import { hardReset } from "@/util/save";
|
||||
import { computed } from "vue";
|
||||
import main from "./layers/main";
|
||||
|
||||
|
|
|
@ -450,10 +450,26 @@ export function addLayer(layer: RawLayer, player?: Partial<PlayerData>): void {
|
|||
if (nodeType.actions === null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof nodeType.actions === "function") {
|
||||
return nodeType.actions(this.selectedNode);
|
||||
const actions =
|
||||
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) {
|
||||
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;
|
||||
featureType?: string;
|
||||
stroke?: string;
|
||||
"stroke-width"?: string;
|
||||
strokeWidth?: number | string;
|
||||
startOffset?: Position;
|
||||
endOffset?: Position;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
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";
|
||||
|
||||
export interface BoardNode {
|
||||
id: string;
|
||||
id: number;
|
||||
position: {
|
||||
x: number;
|
||||
y: number;
|
||||
|
@ -28,6 +28,7 @@ export interface Board extends Feature {
|
|||
nodes: BoardNode[];
|
||||
selectedNode: BoardNode | null;
|
||||
selectedAction: BoardNodeAction | null;
|
||||
links: BoardNodeLink[] | null;
|
||||
}
|
||||
|
||||
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 {
|
||||
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);
|
||||
shape: Shape | ((node: BoardNode) => Shape);
|
||||
canAccept: boolean | ((node: BoardNode, otherNode: BoardNode) => boolean);
|
||||
|
@ -57,7 +59,24 @@ export interface NodeType extends Feature {
|
|||
|
||||
export interface BoardNodeAction {
|
||||
id: string;
|
||||
icon: string;
|
||||
tooltip: string;
|
||||
icon: string | ((node: BoardNode) => string);
|
||||
fillColor?: string | ((node: BoardNode) => string);
|
||||
tooltip: string | ((node: BoardNode) => string);
|
||||
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 { NodeType, BoardNode } from "@/typings/features/board";
|
||||
import { NodeType, BoardNode, Board } from "@/typings/features/board";
|
||||
import { GridCell } from "@/typings/features/grid";
|
||||
import { State } from "@/typings/state";
|
||||
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 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.hardReset = async () => {
|
||||
export const hardReset = window.hardReset = async () => {
|
||||
await loadSave(newSave());
|
||||
const modData = JSON.parse(decodeURIComponent(escape(atob(localStorage.getItem(modInfo.id)!))));
|
||||
modData.active = player.id;
|
||||
|
|
Loading…
Reference in a new issue