diff --git a/server/frontend/client.js b/server/frontend/client.js
index d219812..a618676 100755
--- a/server/frontend/client.js
+++ b/server/frontend/client.js
@@ -2,7 +2,6 @@ import { crackdown } from "../utils/crackdown.js";
import { parseArgs } from "../utils/parseArgs.js";
import { MessageType } from "../utils/messages.js";
import { sprintf } from "sprintf-js";
-import { Config } from "../config.js";
/** Regex to validate if a :help [topic] command i entered correctly */
const helpRegex = /^:help(?:\s+(.*))?$/;
@@ -75,9 +74,10 @@ class MUDClient {
connect() {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
- const wsUrl = `${protocol}//${window.location.host}`.replace(/:\d+$/, Config.port);
+ // TODO Fix. Port should not be hardcoded
+ const wsUrl = `${protocol}//${window.location.host}`.replace(/:\d+$/, ":3000");
- console.log(window.location);
+ console.log(wsUrl);
this.updateStatus("Connecting...", "connecting");
diff --git a/server/frontend/manifest.json b/server/frontend/manifest.json
index 9e32466..bb83ec2 100755
--- a/server/frontend/manifest.json
+++ b/server/frontend/manifest.json
@@ -2,19 +2,10 @@
"name": "",
"short_name": "",
"icons": [
- {
- "src": "./img/android-chrome-192x192.png",
- "sizes": "192x192",
- "type": "image/png"
- },
- {
- "src": "./img/android-chrome-512x512.png",
- "sizes": "512x512",
- "type": "image/png"
- }
+ { "src": "./img/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" },
+ { "src": "./img/android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" }
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
-
diff --git a/server/frontend/progen.html b/server/frontend/progen.html
new file mode 100755
index 0000000..0c2cdbe
--- /dev/null
+++ b/server/frontend/progen.html
@@ -0,0 +1,61 @@
+
+
+
+
+
+ WebSocket MUD
+
+
+
+
+
9×9 Pixel Art Editor
+
+
+
+
Click a pixel to start drawing
+
+
+
+
+
Current Color
+
+
+
+
Color Palette
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/server/frontend/progen.js b/server/frontend/progen.js
new file mode 100755
index 0000000..5256ba2
--- /dev/null
+++ b/server/frontend/progen.js
@@ -0,0 +1,393 @@
+class PaintProg {
+ /** @type {string} */
+ currentColor = "#000";
+ /** @type {string} */
+ currentTool = "draw";
+ /** @type {boolean} */
+ isDrawing = false;
+ /** @type {boolean} */
+ drawingMode = false;
+
+ /**@param {string[]} pal */
+ constructor(dim, pal, gridElement, paletteElement, currentColorElement, colorPickerElement, previewElement) {
+ /** @type {number} */
+ this.dim = dim;
+ /** @type {string[]} Default color palette */
+ this.palette = pal;
+ /** @type {string[]} */
+ this.samplePixels = new Array(dim ** 2).fill(pal[pal.length - 1]);
+ /** @type {HTMLElement} */
+ this.gridElement = gridElement;
+ /** @type {HTMLElement} */
+ this.previewElement = previewElement;
+ /** @type {HTMLElement} */
+ this.paletteElement = paletteElement;
+ /** @type {HTMLElement} */
+ this.currentColorElement = currentColorElement;
+ /** @type {HTMLInputElement} */
+ this.colorPickerElement = colorPickerElement;
+
+ this.subImages = Array.from({ length: this.samplePixels.length }, () => [...this.samplePixels]);
+
+ this.createGrid();
+ this.createColorPalette();
+ this.updatePreview();
+ this.setCurrentColor(pal[0]);
+ this.updateAllSubimages();
+ }
+
+ createGrid() {
+ this.gridElement.innerHTML = "";
+
+ for (let i = 0; i < this.dim ** 2; i++) {
+ const pixel = document.createElement("div");
+ pixel.className = "pixel";
+ pixel.setAttribute("data-index", i);
+ pixel.style.backgroundColor = this.samplePixels[i];
+
+ pixel.addEventListener("mousedown", (e) => this.startDrawing(e, i));
+ pixel.addEventListener("mouseenter", (e) => this.continueDrawing(e, i));
+ pixel.addEventListener("mouseup", () => this.stopDrawing());
+
+ this.gridElement.appendChild(pixel);
+ }
+
+ // Prevent context menu and handle mouse events
+ this.gridElement.addEventListener("contextmenu", (e) => e.preventDefault());
+ document.addEventListener("mouseup", () => this.stopDrawing());
+ }
+
+ createColorPalette() {
+ this.paletteElement.innerHTML = "";
+
+ this.palette.forEach((color) => {
+ const swatch = document.createElement("div");
+ swatch.className = "color-swatch";
+ swatch.style.backgroundColor = color;
+ swatch.onclick = () => this.setCurrentColor(color);
+ this.paletteElement.appendChild(swatch);
+ });
+ }
+
+ setCurrentColor(color) {
+ this.currentColor = color;
+ this.currentColorElement.style.backgroundColor = color;
+ this.colorPickerElement.value = color;
+
+ // Update active swatch
+ // NOTE: this was "document.querySelectorAll "
+ this.paletteElement.querySelectorAll(".color-swatch").forEach((swatch) => {
+ swatch.classList.toggle("active", swatch.style.backgroundColor === this.colorToRgb(color));
+ });
+ }
+
+ colorToRgb(hex) {
+ const r = parseInt(hex.substr(1, 2), 16);
+ const g = parseInt(hex.substr(3, 2), 16);
+ const b = parseInt(hex.substr(5, 2), 16);
+ return `rgb(${r}, ${g}, ${b})`;
+ }
+
+ openColorPicker() {
+ this.colorPickerElement.click();
+ }
+
+ setTool(tool) {
+ this.currentTool = tool;
+ document.querySelectorAll(".tools button").forEach((btn) => btn.classList.remove("active"));
+ document.getElementById(tool + "Btn").classList.add("active");
+
+ const status = document.getElementById("status");
+ switch (tool) {
+ case "draw":
+ status.textContent = "Drawing mode - Click to paint pixels";
+ break;
+ case "fill":
+ status.textContent = "Fill mode - Click to fill area";
+ break;
+ }
+ }
+
+ toggleDrawingMode() {
+ this.drawingMode = !this.drawingMode;
+ const btn = document.getElementById("drawModeBtn");
+ if (this.drawingMode) {
+ btn.textContent = "Drawing Mode: ON";
+ btn.classList.add("drawing-mode");
+ document.getElementById("status").textContent = "Drawing mode ON - Click and drag to paint";
+ } else {
+ btn.textContent = "Drawing Mode: OFF";
+ btn.classList.remove("drawing-mode");
+ document.getElementById("status").textContent = "Drawing mode OFF - Click individual pixels";
+ }
+ }
+
+ startDrawing(e, index) {
+ e.preventDefault();
+ this.isDrawing = true;
+ this.applyTool(index);
+ }
+
+ /** @param {MouseEvent} e */
+ continueDrawing(e, index) {
+ if (this.isDrawing && this.drawingMode) {
+ this.applyTool(index);
+ return;
+ }
+
+ this.updateSingleSubImage(index);
+ this.updatePreview(index);
+ }
+
+ stopDrawing() {
+ this.isDrawing = false;
+ }
+
+ applyTool(index) {
+ switch (this.currentTool) {
+ case "draw":
+ this.setPixel(index, this.currentColor);
+ break;
+ case "fill":
+ this.floodFill(index, this.samplePixels[index], this.currentColor);
+ break;
+ }
+ }
+
+ setPixel(index, color) {
+ this.samplePixels[index] = color;
+ const pixel = document.querySelector(`[data-index="${index}"]`);
+ pixel.style.backgroundColor = color;
+ this.updatePreview();
+ }
+
+ floodFill(startIndex, targetColor, fillColor) {
+ if (targetColor === fillColor) return;
+
+ const stack = [startIndex];
+ const visited = new Set();
+
+ while (stack.length > 0) {
+ const index = stack.pop();
+ if (visited.has(index) || this.samplePixels[index] !== targetColor) continue;
+
+ visited.add(index);
+ this.setPixel(index, fillColor);
+
+ // Add neighbors
+ const row = Math.floor(index / this.dim);
+ const col = index % this.dim;
+
+ if (row > 0) stack.push(index - this.dim); // up
+ if (row < 8) stack.push(index + this.dim); // down
+ if (col > 0) stack.push(index - 1); // left
+ if (col < 8) stack.push(index + 1); // right
+ }
+ }
+
+ clearCanvas() {
+ this.samplePixels.fill("#fff");
+ this.createGrid();
+ this.updatePreview();
+ }
+
+ randomFill() {
+ for (let i = 0; i < this.dim ** 2; i++) {
+ const randomColor = this.palette[Math.floor(Math.random() * this.palette.length)];
+ this.setPixel(i, randomColor);
+ }
+ }
+
+ invertColors() {
+ for (let i = 0; i < this.dim ** 2; i++) {
+ const color = this.samplePixels[i];
+ const r = 15 - parseInt(color.substr(1, 1), 16);
+ const g = 15 - parseInt(color.substr(2, 1), 16);
+ const b = 15 - parseInt(color.substr(3, 1), 16);
+ const inverted =
+ "#" +
+ r.toString(16) + // red
+ g.toString(16) + // green
+ b.toString(16); // blue
+
+ this.setPixel(i, inverted);
+
+ if (i % 10 === 0) {
+ console.log("invertion", {
+ color,
+ r,
+ g,
+ b,
+ inverted,
+ });
+ }
+ }
+ }
+
+ updatePreview(subImageIdx = undefined) {
+ const canvas = document.createElement("canvas");
+ if (subImageIdx === undefined) {
+ canvas.width = this.dim;
+ canvas.height = this.dim;
+ const ctx = canvas.getContext("2d");
+
+ for (let i = 0; i < this.dim ** 2; i++) {
+ const x = i % this.dim;
+ const y = Math.floor(i / this.dim);
+ ctx.fillStyle = this.samplePixels[i];
+ ctx.fillRect(x, y, 1, 1);
+ }
+ } else {
+ canvas.width = 3;
+ canvas.height = 3;
+ const ctx = canvas.getContext("2d");
+
+ for (let i = 0; i < 3 * 3; i++) {
+ //
+ const x = i % 3;
+ const y = Math.floor(i / 3);
+ ctx.fillStyle = this.subImages[subImageIdx][i];
+ ctx.fillRect(x, y, 1, 1);
+ }
+ }
+
+ this.previewElement.style.backgroundImage = `url(${canvas.toDataURL()})`;
+ this.previewElement.style.backgroundSize = "100%";
+ }
+
+ updateAllSubimages() {
+ for (let i = 0; i < this.samplePixels.length; i++) {
+ this.updateSingleSubImage(i);
+ }
+ }
+
+ updateSingleSubImage(i) {
+ const dim = this.dim;
+ const len = dim ** 2;
+ const x = i % dim;
+ const y = Math.floor(i / dim);
+
+ const colorAt = (dX, dY) => {
+ const _x = (x + dim + dX) % dim; // add dim before modulo because JS modulo allows negative results
+ const _y = (y + dim + dY) % dim;
+ if (y == 0 && dY < 0) {
+ console.log(_x, _y);
+ }
+ return this.samplePixels[_y * dim + _x];
+ };
+
+ this.subImages[i] = [
+ // | neighbour
+ // ---------------------|-----------
+ colorAt(-1, -1), // | northwest
+ colorAt(0, -1), // | north
+ colorAt(1, -1), // | northeast
+
+ colorAt(-1, 0), // | east
+ this.samplePixels[i], //| -- self --
+ colorAt(1, 0), // | west
+
+ colorAt(-1, 1), // | southwest
+ colorAt(0, 1), // | south
+ colorAt(1, 1), // | southeast
+ ];
+ }
+
+ exportAsImage() {
+ const canvas = document.createElement("canvas");
+ canvas.width = 90; // 9x upscale
+ canvas.height = 90;
+ const ctx = canvas.getContext("2d");
+ ctx.imageSmoothingEnabled = false;
+
+ for (let i = 0; i < this.dim ** 2; i++) {
+ const x = (i % this.dim) * 10;
+ const y = Math.floor(i / this.dim) * 10;
+ ctx.fillStyle = this.samplePixels[i];
+ ctx.fillRect(x, y, 10, 10);
+ }
+
+ const link = document.createElement("a");
+ link.download = `pixel-art-${this.dim}x${this.dim}.png`;
+ link.href = canvas.toDataURL();
+ link.click();
+ }
+
+ exportAsData() {
+ const data = JSON.stringify(this.samplePixels);
+ const blob = new Blob([data], { type: "application/json" });
+ const link = document.createElement("a");
+ link.download = "pixel-art-data.json";
+ link.href = URL.createObjectURL(blob);
+ link.click();
+ }
+
+ importData() {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".json";
+ input.onchange = (e) => {
+ const file = e.target.files[0];
+ if (file) {
+ const reader = new FileReader();
+ reader.onload = (event) => {
+ try {
+ const data = JSON.parse(event.target.result);
+ if (Array.isArray(data) && data.length === this.dim ** 2) {
+ this.samplePixels = data;
+ this.createGrid();
+ this.updatePreview();
+ } else {
+ alert("Invalid data format!");
+ }
+ } catch (error) {
+ alert("Error reading file!");
+ }
+ };
+ reader.readAsText(file);
+ }
+ };
+ input.click();
+ }
+
+ // Initialize the editor
+}
+const palette = [
+ "#000",
+ "#008",
+ "#00f",
+ "#080",
+ "#088",
+ "#0f0",
+ "#0ff",
+ "#800",
+ "#808",
+ "#80f",
+ "#880",
+ "#888",
+ "#88f",
+ "#8f8",
+ "#8ff",
+ "#ccc",
+ "#f00",
+ "#f0f",
+ "#f80",
+ "#f88",
+ "#f8f",
+ "#ff0",
+ "#ff8",
+ "#fff",
+];
+
+window.painter = new PaintProg(
+ 9,
+ palette,
+ document.getElementById("gridContainer"), //
+ document.getElementById("colorPalette"), //
+ document.getElementById("currentColor"), //
+ document.getElementById("colorPicker"), //
+ document.getElementById("preview"), //
+);
+
+// share window.dim with the HTML and CSS
+document.getElementsByTagName("body")[0].style.setProperty("--dim", window.painter.dim);
diff --git a/server/frontend/progen.scss b/server/frontend/progen.scss
new file mode 100644
index 0000000..acc4618
--- /dev/null
+++ b/server/frontend/progen.scss
@@ -0,0 +1,184 @@
+// number number of cells per side in the source picture
+$dim: 9;
+$pixSize: 80px;
+$gridSize: calc($dim * $pixSize);
+
+body {
+ margin: 0;
+ padding: 20px;
+ font-family: Arial, sans-serif;
+ background-color: #2c3e50;
+ color: white;
+ display: flex;
+ gap: 30px;
+ min-height: 100vh;
+}
+
+.editor-container {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+}
+
+.grid-container {
+ display: grid;
+ grid-template-columns: repeat(var(--dim), 1fr);
+ grid-template-rows: repeat(var(--dim), 1fr);
+ gap: 1px;
+ width: $gridSize;
+ height: $gridSize;
+ background-color: #34495e;
+ padding: 10px;
+ border-radius: 8px;
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.3);
+}
+
+.pixel {
+ background-color: #ffffff;
+ cursor: pointer;
+ border: 1px solid #7f8c8d;
+ transition: transform 0.1s ease;
+ position: relative;
+ aspect-ratio: 1;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ grid-template-rows: repeat(3, 1fr);
+
+ .subpixel {
+ width: 100%;
+ height: 100%;
+ background-color: transparent;
+ }
+}
+
+.pixel:hover {
+ transform: scale(1.1);
+ z-index: 1;
+ border-color: #3498db;
+}
+
+.controls-panel {
+ background: #34495e;
+ padding: 20px;
+ border-radius: 8px;
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
+ width: 300px;
+}
+
+.color-picker-section {
+ margin-bottom: 20px;
+}
+
+.current-color {
+ width: 60px;
+ height: 60px;
+ border: 3px solid white;
+ border-radius: 8px;
+ margin: 10px 0;
+ cursor: pointer;
+ background-color: #000000;
+}
+
+.color-palette {
+ display: grid;
+ grid-template-columns: repeat(8, 1fr);
+ gap: 4px;
+ margin: 10px 0;
+}
+
+.color-swatch {
+ width: 25px;
+ height: 25px;
+ cursor: pointer;
+ border: 2px solid transparent;
+ border-radius: 4px;
+ transition: transform 0.1s ease;
+}
+
+.color-swatch:hover {
+ transform: scale(1.2);
+ border-color: white;
+}
+
+.color-swatch.active {
+ border-color: #3498db;
+ transform: scale(1.1);
+}
+
+.tools {
+ margin: 20px 0;
+}
+
+.tool-group {
+ margin: 15px 0;
+}
+
+.tool-group h3 {
+ margin: 0 0 10px 0;
+ color: #ecf0f1;
+ font-size: 14px;
+}
+
+button {
+ background: #3498db;
+ color: white;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ margin: 2px;
+ font-size: 12px;
+ transition: background 0.2s ease;
+}
+
+button:hover {
+ background: #2980b9;
+}
+
+button.active {
+ background: #e74c3c;
+}
+
+input[type="color"] {
+ width: 40px;
+ height: 30px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+}
+
+.export-section {
+ margin-top: 5px;
+ padding-top: 5px;
+ border-top: 1px solid #7f8c8d;
+}
+
+.preview {
+ width: 100%;
+ aspect-ratio: 1 / 1;
+ /* height: 90px; */
+ background: white;
+ border: 2px solid #bdc3c7;
+ border-radius: 4px;
+ margin: 10px 0;
+ image-rendering: pixelated;
+ image-rendering: -moz-crisp-edges;
+ image-rendering: crisp-edges;
+}
+
+.grid-lines {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ pointer-events: none;
+ opacity: 0.3;
+}
+
+.drawing-mode {
+ font-weight: bold;
+ /* color: #e74c3c; */
+ background: #383;
+}
diff --git a/server/frontend/subpixel.js b/server/frontend/subpixel.js
new file mode 100755
index 0000000..4fe067c
--- /dev/null
+++ b/server/frontend/subpixel.js
@@ -0,0 +1,137 @@
+const NW = 0;
+const N = 1;
+const NE = 2;
+const E = 3;
+const C = 4;
+const W = 5;
+const SW = 6;
+const S = 7;
+const SE = 8;
+
+export class TrainingImage {
+ /** @param {number} w Width (in pixels) of the training image */
+ /** @param {number} h Height (in pixels) of the training image */
+ /** @param {Array} pixels
+ *
+ */
+ constructor(w, h, pixels) {
+ this.pixels = pixels;
+
+ this.w = w;
+
+ this.h = h;
+ }
+
+ establishFriendships() {
+ //
+ // This can be optimized a helluvalot much
+ this.pixels.forEach((pix1) => {
+ this.pixels.forEach((pix2) => {
+ pix1.addFriend(pix2);
+ });
+ });
+ }
+}
+
+/**
+ * Represents a 3x3 grid of trianing-pixels, that are used as building blocks for procedurally generated
+ * images. In reality, only the center color will be included in the output image. The 8 surrounding
+ * colors are there to establish restrictions/options for the WFC algorithm.
+ */
+export class TrainingPixel {
+ /** @type {string[9]} The 9 sub pixels that make up this TrainingPixel */
+ subPixels;
+
+ /** @type {TrainingPixel[]} The other TrainingPixels that we can share eastern borders with */
+ friendsEast = new Set();
+
+ /** @type {TrainingPixel[]} The other TrainingPixels that we can share western borders with */
+ friendsWest = new Set();
+
+ /** @type {TrainingPixel[]} The other TrainingPixels that we can share northern borders with */
+ friendsNorth = new Set();
+
+ /** @type {TrainingPixel[]} The other TrainingPixels that we can share southern borders with */
+ friendsSouth = new Set();
+
+ /** @type {TrainingPixel[]} Superset of all the friends this TrainingPixel has */
+ friendsTotal = new Set();
+
+ constructor(subPixels) {
+ this.subPixels = subPixels;
+ }
+
+ /**
+ * @param {TrainingPixel} other
+ *
+ * @returns {N,S,E,W,false}
+ */
+ addFriend(other) {
+ // sadly, we're not allowed to be friends with ourselves.
+ if (this === other) {
+ return false;
+ }
+
+ // Check if other can be placed to the east of me
+ if (
+ // My center col must match their west col
+ this.subPixels[N] === other.subPixels[NW] &&
+ this.subPixels[C] === other.subPixels[W] &&
+ this.subPixels[S] === other.subPixels[SW] &&
+ // My east col must match their center col
+ this.subPixels[NE] === other.subPixels[N] &&
+ this.subPixels[E] === other.subPixels[C] &&
+ this.subPixels[SE] === other.subPixels[S]
+ ) {
+ this.friendsEast.add(other);
+ other.friendsWest.add(this);
+ }
+
+ // check if other can be placed west of me
+ if (
+ // My center col must match their east col
+ this.subPixels[N] === other.subPixels[NE] &&
+ this.subPixels[C] === other.subPixels[E] &&
+ this.subPixels[S] === other.subPixels[SE] &&
+ // My west col must match their center col
+ this.subPixels[NW] === other.subPixels[N] &&
+ this.subPixels[W] === other.subPixels[C] &&
+ this.subPixels[SW] === other.subPixels[S]
+ ) {
+ this.friendsWest.add(other);
+ other.friendsEast.add(this);
+ }
+
+ // check if other can be placed to my north
+ if (
+ // my middle row must match their south row
+ this.subPixels[W] === other.subPixels[SW] &&
+ this.subPixels[C] === other.subPixels[S] &&
+ this.subPixels[E] === other.subPixels[SE] &&
+ // my north row must match their middle row
+ this.subPixels[NW] === other.subPixels[W] &&
+ this.subPixels[NC] === other.subPixels[C] &&
+ this.subPixels[NE] === other.subPixels[E]
+ ) {
+ this.friendsNorth.add(other);
+ other.friendsSouth.add(this);
+ }
+
+ // check if other can be placed to my south
+ if (
+ // my middle row must match their north row
+ this.subPixels[W] === other.subPixels[NW] &&
+ this.subPixels[C] === other.subPixels[N] &&
+ this.subPixels[E] === other.subPixels[SE] &&
+ // my south row must match their middle row
+ this.subPixels[SW] === other.subPixels[W] &&
+ this.subPixels[SC] === other.subPixels[c] &&
+ this.subPixels[SE] === other.subPixels[E]
+ ) {
+ this.friendsSouth.add(other);
+ other.friendsNorth.add(this);
+ }
+
+ return false;
+ }
+}
diff --git a/server/ideas.md b/server/ideas.md
index 905686c..0fa289b 100755
--- a/server/ideas.md
+++ b/server/ideas.md
@@ -83,12 +83,19 @@ Each group chat has a name.
- `Donjons`
- GameMode = _Crawling_: Lady Gurthie Firefoot is Crawling the Donjons of Speematoforr.
- Played like `Knights of Pen and Paper`
+ - WebGL: Actual rendered 3d, but black and white.
+ - Texture pack is just ascii text (TEXTures).
+ - Possibly Procedurally generated
+ - Most likely like this: https://www.youtube.com/watch?v=D1jLK4TG6O4&list=LL
+ - Animations like `Stonekeep` - only showing animation when the player moves.
+ - Monsters are just outlines of monster shapes, shaded with variably-sized letters
+ that spell out the monster's name.
- Procedurally (pre-)generated dungeons
- - Very simple square dungeon layout (like KoPaP).
- Every time you enter a non-explored space, you roll a die, and see what happens.
- - Combat is like `Dark Queen of Krynn`
+ - Combat is like `Dark Queen of Krynn` (i.e. third person semi-iso)
- 1 Location == 1 donjon room/area
- BSP (binary space partition) https://www.youtube.com/watch?v=TlLIOgWYVpI&t=374s
+
- `Overland`
- GameMode = _Traveling_: Swift Dangledonk the Slow is Traveling the Marshes of Moohfaahsaah
- Travel is like `Rogue`
diff --git a/server/package.json b/server/package.json
index 796b3a4..d609081 100755
--- a/server/package.json
+++ b/server/package.json
@@ -10,7 +10,7 @@
"frontend:build": "vite build frontend",
"frontend:dev": "vite frontend",
"dev": "concurrently 'npm run server:dev' 'npm run frontend:dev'",
- "dev:color": "concurrently -n 'API,UI' -c 'blue,green' 'npm run server:dev' 'npm run frontend:dev'"
+ "dev:color": "concurrently -n 'Server,Client' -c 'blue,green' 'npm run server:dev' 'npm run frontend:dev'"
},
"keywords": [
"mud",