diff --git a/package-lock.json b/package-lock.json
index 82536ac..b496392 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,6 +13,7 @@
"vue": "^3.2.2",
"vue-class-component": "^8.0.0-rc.1",
"vue-next-select": "^2.9.0",
+ "vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.1",
"vue-transition-expand": "^0.1.0"
@@ -14925,6 +14926,14 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true
},
+ "node_modules/amator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz",
+ "integrity": "sha1-CMa2C8k67Cthu/wMTWd9MDI8wPE=",
+ "dependencies": {
+ "bezier-easing": "^2.0.3"
+ }
+ },
"node_modules/ansi-colors": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
@@ -15495,6 +15504,11 @@
"tweetnacl": "^0.14.3"
}
},
+ "node_modules/bezier-easing": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
+ "integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
+ },
"node_modules/bfj": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz",
@@ -22071,6 +22085,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "node_modules/ngraph.events": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.1.tgz",
+ "integrity": "sha512-D4C+nXH/RFxioGXQdHu8ELDtC6EaCiNsZtih0IvyGN81OZSUby4jXoJ5+RNWasfsd0FnKxxpAROyUMzw64QNsw=="
+ },
"node_modules/nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -22736,6 +22755,16 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
},
+ "node_modules/panzoom": {
+ "version": "9.4.2",
+ "resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.2.tgz",
+ "integrity": "sha512-sQLr0t6EmNFXpZHag0HQVtOKqF9xjF7iZdgWg3Ss1o7uh2QZLvcrz7S0Cl8M0d2TkPZ69JfPJdknXN3I0e/2aQ==",
+ "dependencies": {
+ "amator": "^1.1.0",
+ "ngraph.events": "^1.2.1",
+ "wheel": "^1.0.0"
+ }
+ },
"node_modules/parallel-transform": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
@@ -27245,6 +27274,14 @@
"vue": "^3.0.0"
}
},
+ "node_modules/vue-panzoom": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/vue-panzoom/-/vue-panzoom-1.1.6.tgz",
+ "integrity": "sha512-yEE60C/gnc5NGL6YBD++CErD820va7fkBJE5dCWZZzXX2aMGklj/UKmtqu1u5xDkuOIjnGUr412LNHwOOE711w==",
+ "dependencies": {
+ "panzoom": "^9.4.1"
+ }
+ },
"node_modules/vue-sortable": {
"version": "0.1.3",
"resolved": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
@@ -28083,6 +28120,11 @@
"node": ">=0.8.0"
}
},
+ "node_modules/wheel": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
+ "integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA=="
+ },
"node_modules/which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -31140,6 +31182,14 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true
},
+ "amator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/amator/-/amator-1.1.0.tgz",
+ "integrity": "sha1-CMa2C8k67Cthu/wMTWd9MDI8wPE=",
+ "requires": {
+ "bezier-easing": "^2.0.3"
+ }
+ },
"ansi-colors": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.4.tgz",
@@ -31576,6 +31626,11 @@
"tweetnacl": "^0.14.3"
}
},
+ "bezier-easing": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
+ "integrity": "sha1-wE3+i5JtbsrKGBPWn/F5t8ICXYY="
+ },
"bfj": {
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/bfj/-/bfj-6.1.2.tgz",
@@ -36803,6 +36858,11 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
+ "ngraph.events": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ngraph.events/-/ngraph.events-1.2.1.tgz",
+ "integrity": "sha512-D4C+nXH/RFxioGXQdHu8ELDtC6EaCiNsZtih0IvyGN81OZSUby4jXoJ5+RNWasfsd0FnKxxpAROyUMzw64QNsw=="
+ },
"nice-try": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
@@ -37323,6 +37383,16 @@
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true
},
+ "panzoom": {
+ "version": "9.4.2",
+ "resolved": "https://registry.npmjs.org/panzoom/-/panzoom-9.4.2.tgz",
+ "integrity": "sha512-sQLr0t6EmNFXpZHag0HQVtOKqF9xjF7iZdgWg3Ss1o7uh2QZLvcrz7S0Cl8M0d2TkPZ69JfPJdknXN3I0e/2aQ==",
+ "requires": {
+ "amator": "^1.1.0",
+ "ngraph.events": "^1.2.1",
+ "wheel": "^1.0.0"
+ }
+ },
"parallel-transform": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz",
@@ -41002,6 +41072,14 @@
"integrity": "sha512-GjX4pHqZXXitquDeSAtLaf85jXdMUOKyCNzo+EF3xRr4DebGwbST4CtmRvL0TX3EhwLHQjUlAc3JcJX+azpLHg==",
"requires": {}
},
+ "vue-panzoom": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/vue-panzoom/-/vue-panzoom-1.1.6.tgz",
+ "integrity": "sha512-yEE60C/gnc5NGL6YBD++CErD820va7fkBJE5dCWZZzXX2aMGklj/UKmtqu1u5xDkuOIjnGUr412LNHwOOE711w==",
+ "requires": {
+ "panzoom": "^9.4.1"
+ }
+ },
"vue-sortable": {
"version": "git+ssh://git@github.com/Netbel/vue-sortable.git#f4d4870ace71ea59bd79252eb2ec1cf6bfb02fe7",
"from": "vue-sortable@github:Netbel/vue-sortable#master-fix",
@@ -41687,6 +41765,11 @@
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
"dev": true
},
+ "wheel": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/wheel/-/wheel-1.0.0.tgz",
+ "integrity": "sha512-XiCMHibOiqalCQ+BaNSwRoZ9FDTAvOsXxGHXChBugewDj7HC8VBIER71dEOiRH1fSdLbRCQzngKTSiZ06ZQzeA=="
+ },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
diff --git a/package.json b/package.json
index e65641f..d09e8ea 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"vue": "^3.2.2",
"vue-class-component": "^8.0.0-rc.1",
"vue-next-select": "^2.9.0",
+ "vue-panzoom": "^1.1.6",
"vue-sortable": "github:Netbel/vue-sortable#master-fix",
"vue-textarea-autosize": "^1.1.1",
"vue-transition-expand": "^0.1.0"
diff --git a/src/components/board/Board.vue b/src/components/board/Board.vue
new file mode 100644
index 0000000..f490b60
--- /dev/null
+++ b/src/components/board/Board.vue
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/components/board/BoardNode.vue b/src/components/board/BoardNode.vue
new file mode 100644
index 0000000..bec3081
--- /dev/null
+++ b/src/components/board/BoardNode.vue
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
diff --git a/src/components/index.ts b/src/components/index.ts
index 9619597..408338f 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -6,6 +6,7 @@ import VueTextareaAutosize from "vue-textarea-autosize";
import Sortable from "vue-sortable";
import VueNextSelect from "vue-next-select";
import "vue-next-select/dist/index.css";
+import panZoom from "vue-panzoom";
import { App } from "vue";
export function registerComponents(vue: App): void {
@@ -23,4 +24,5 @@ export function registerComponents(vue: App): void {
vue.use(VueTextareaAutosize);
vue.use(Sortable);
vue.component("vue-select", VueNextSelect);
+ vue.use(panZoom);
}
diff --git a/src/game/enums.ts b/src/game/enums.ts
index f175079..a9047d3 100644
--- a/src/game/enums.ts
+++ b/src/game/enums.ts
@@ -28,3 +28,8 @@ export enum ImportingStatus {
WrongID = "WRONG_ID",
Force = "FORCE"
}
+
+export enum ProgressDisplay {
+ Outline = "Outline",
+ Fill = "Fill"
+}
diff --git a/src/game/layers.ts b/src/game/layers.ts
index 41c688e..d20d094 100644
--- a/src/game/layers.ts
+++ b/src/game/layers.ts
@@ -1,5 +1,6 @@
import { CacheableFunction } from "@/typings/cacheableFunction";
import { Achievement } from "@/typings/features/achievement";
+import { Board } from "@/typings/features/board";
import { Buyable } from "@/typings/features/buyable";
import { Challenge } from "@/typings/features/challenge";
import { Clickable } from "@/typings/features/clickable";
@@ -23,6 +24,7 @@ import Decimal, { DecimalSource } from "@/util/bignum";
import { isFunction } from "@/util/common";
import {
defaultLayerProperties,
+ getStartingBoards,
getStartingBuyables,
getStartingChallenges,
getStartingClickables,
@@ -32,6 +34,7 @@ import { createGridProxy, createLayerProxy } from "@/util/proxies";
import { applyPlayerData } from "@/util/save";
import clone from "lodash.clonedeep";
import { isRef } from "vue";
+import { ProgressDisplay } from "./enums";
import { default as playerProxy } from "./player";
export const layers: Record> = {};
@@ -70,6 +73,7 @@ export function addLayer(layer: RawLayer, player?: Partial): void {
buyables: getStartingBuyables(layer.buyables?.data),
clickables: getStartingClickables(layer.clickables?.data),
challenges: getStartingChallenges(layer.challenges?.data),
+ boards: getStartingBoards(layer.boards?.data),
grids: {},
confirmRespecBuyables: false,
...(layer.startData?.() || {})
@@ -423,6 +427,31 @@ export function addLayer(layer: RawLayer, player?: Partial): void {
layer.grids.data[id] = createGridProxy(layer.grids.data[id]) as Grid;
}
}
+ if (layer.boards) {
+ setupFeatures, Board>(layer.id, layer.boards);
+ for (const id in layer.boards.data) {
+ setDefault(layer.boards.data[id], "width", "100%");
+ setDefault(layer.boards.data[id], "height", "400px");
+ 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;
+ layer.boards.data[id].types[nodeType].type = nodeType;
+ setDefault(layer.boards.data[id].types[nodeType], "size", 50);
+ setDefault(layer.boards.data[id].types[nodeType], "draggable", false);
+ setDefault(layer.boards.data[id].types[nodeType], "canAccept", false);
+ 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(
+ node => node.type === this.type
+ );
+ });
+ }
+ }
+ }
if (layer.subtabs) {
layer.activeSubtab = function() {
if (
diff --git a/src/typings/features/board.d.ts b/src/typings/features/board.d.ts
new file mode 100644
index 0000000..520411e
--- /dev/null
+++ b/src/typings/features/board.d.ts
@@ -0,0 +1,45 @@
+import { State } from "../state";
+import { Feature, RawFeature } from "./feature";
+
+export interface BoardNode {
+ position: {
+ x: number;
+ y: number;
+ };
+ type: string;
+ data?: State;
+}
+
+export interface CardOption {
+ text: string;
+ selected: (node: BoardNode) => void;
+}
+
+export interface Board extends Feature {
+ startNodes: () => BoardNode[];
+ style?: Partial;
+ height: string;
+ width: string;
+ types: Record;
+}
+
+export type RawBoard = Omit, "types"> & {
+ startNodes: () => BoardNode[];
+ types: Record>;
+};
+
+export interface NodeType extends Feature {
+ tooltip?: string | ((node: BoardNode) => string);
+ title: string | ((node: BoardNode) => string);
+ size: number | ((node: BoardNode) => number);
+ draggable: boolean | ((node: BoardNode) => boolean);
+ canAccept: boolean | ((node: BoardNode, otherNode: BoardNode) => boolean);
+ progress?: number | ((node: BoardNode) => number);
+ progressDisplay: ProgressDisplay | ((node: BoardNode) => ProgressDisplay);
+ progressColor: string | ((node: BoardNode) => string);
+ fillColor?: string | ((node: BoardNode) => string);
+ outlineColor?: string | ((node: BoardNode) => string);
+ titleColor?: string | ((node: BoardNode) => string);
+ onClick: (node: BoardNode) => void;
+ nodes: BoardNode[];
+}
diff --git a/src/typings/layer.d.ts b/src/typings/layer.d.ts
index 9059174..9dfd56a 100644
--- a/src/typings/layer.d.ts
+++ b/src/typings/layer.d.ts
@@ -3,6 +3,7 @@ import Decimal, { DecimalSource } from "@/util/bignum";
import { CoercableComponent } from "./component";
import { Achievement } from "./features/achievement";
import { Bar } from "./features/bar";
+import { Board, RawBoard } from "./features/board";
import { Buyable } from "./features/buyable";
import { Challenge } from "./features/challenge";
import { Clickable } from "./features/clickable";
@@ -31,6 +32,7 @@ export interface RawLayer extends RawFeature {
challenges?: RawGridFeatures, Challenge>;
clickables?: RawGridFeatures, Clickable>;
grids?: RawFeatures, Grid>;
+ boards?: RawFeatures, Board, RawBoard>;
hotkeys?: RawFeature[];
infoboxes?: RawFeatures, Infoboxe>;
milestones?: RawFeatures, Milestone>;
@@ -108,6 +110,7 @@ export interface Layer extends Feature {
showMasterButton?: boolean;
};
grids?: Features;
+ boards?: Features;
hotkeys?: Hotkey[];
infoboxes?: Features;
milestones?: Features;
@@ -142,5 +145,6 @@ export interface ComponentStyles {
"prestige-button"?: Partial;
"respec-button"?: Partial;
upgrade?: Partial;
+ board?: Partial;
"tab-button"?: Partial;
}
diff --git a/src/typings/player.d.ts b/src/typings/player.d.ts
index f45c2cc..b75d2f3 100644
--- a/src/typings/player.d.ts
+++ b/src/typings/player.d.ts
@@ -1,6 +1,7 @@
import { Themes } from "@/data/themes";
import { DecimalSource } from "@/lib/break_eternity";
import Decimal from "@/util/bignum";
+import { BoardNode } from "./features/board";
import { MilestoneDisplay } from "./features/milestone";
import { State } from "./state";
@@ -53,14 +54,15 @@ export interface LayerSaveData {
unlockOrder?: number;
forceTooltip?: boolean;
resetTime: Decimal;
- upgrades: Array;
- achievements: Array;
- milestones: Array;
- infoboxes: Record;
- buyables: Record;
- clickables: Record;
- challenges: Record;
- grids: Record>;
+ upgrades: Array;
+ achievements: Array;
+ milestones: Array;
+ infoboxes: Record;
+ buyables: Record;
+ clickables: Record;
+ challenges: Record;
+ grids: Record>;
+ boards: Record>;
confirmRespecBuyables: boolean;
[index: string]: unknown;
}
diff --git a/src/typings/theme.d.ts b/src/typings/theme.d.ts
index d05ddbb..b3236e4 100644
--- a/src/typings/theme.d.ts
+++ b/src/typings/theme.d.ts
@@ -1,6 +1,18 @@
export interface Theme {
variables: {
- [index: string]: string;
+ "--background": string;
+ "--background-tooltip": string;
+ "--secondary-background": string;
+ "--color": string;
+ "--points": string;
+ "--locked": string;
+ "--bought": string;
+ "--link": string;
+ "--separator": string;
+ "--border-radius": string;
+ "--danger": string;
+ "--modal-border": string;
+ "--feature-margin": string;
};
stackedInfoboxes: boolean;
floatingTabs: boolean;
diff --git a/src/util/layers.ts b/src/util/layers.ts
index 5bbb2e2..519ca43 100644
--- a/src/util/layers.ts
+++ b/src/util/layers.ts
@@ -1,6 +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 { Buyable } from "@/typings/features/buyable";
import { Challenge } from "@/typings/features/challenge";
import { Clickable } from "@/typings/features/clickable";
@@ -70,6 +71,20 @@ export function getStartingChallenges(
: {};
}
+export function getStartingBoards(
+ boards?: Record | Record | undefined
+): Record> {
+ return boards
+ ? Object.keys(boards).reduce((acc: Record>, curr: string): Record<
+ string,
+ Array
+ > => {
+ acc[curr] = boards[curr].startNodes?.() || [];
+ return acc;
+ }, {})
+ : {};
+}
+
export function resetLayerData(layer: string, keep: Array = []): void {
keep.push("unlocked", "forceTooltip", "noRespecConfirm");
const keptData = keep.reduce((acc: Record, curr: string): Record => {
diff --git a/src/util/proxies.ts b/src/util/proxies.ts
index 57fa533..83c1c58 100644
--- a/src/util/proxies.ts
+++ b/src/util/proxies.ts
@@ -13,7 +13,6 @@ export function createLayerProxy(object: Record): Record): Record {
if (object.isProxy) {
console.warn(