From 9b54188970c51070d86f4c87e909ea7836c86f07 Mon Sep 17 00:00:00 2001
From: historia <[not public]>
Date: Sun, 5 Jul 2026 16:42:01 -0400
Subject: fix: restructure steal yaml, update admin web gui
---
data/.admin_history.json | 1 +
data/objects/adamantite_rock.yaml | 48 +++++----
data/objects/market_stall.yaml | 11 ++-
internal/admin/static/cardeditor.js | 78 +++++++++++----
internal/admin/static/dropeditor.js | 1 +
internal/admin/static/hazardeditor.js | 1 +
internal/admin/static/itemeditor.js | 54 ++++++-----
internal/admin/static/mobeditor.js | 15 +--
internal/admin/static/moduleeditor.js | 1 +
internal/admin/static/objecteditor.js | 177 ++++++++++++++++++++++++++--------
internal/admin/static/techeditor.js | 1 +
internal/game/act_steal.go | 30 +++---
internal/game/cmd_sneak.go | 4 +-
internal/game/cmd_verbs.go | 2 +-
internal/object/object.go | 14 ++-
internal/validate/checks.go | 37 ++++---
internal/world/room.go | 12 +--
17 files changed, 325 insertions(+), 162 deletions(-)
create mode 100644 data/.admin_history.json
diff --git a/data/.admin_history.json b/data/.admin_history.json
new file mode 100644
index 0000000..d4e355e
--- /dev/null
+++ b/data/.admin_history.json
@@ -0,0 +1 @@
+{"history":[{"time":"2026-07-05T15:49:30-04:00","description":"Update object adamantite_rock","file_path":"data/objects/adamantite_rock.yaml","old_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock\nname: adamantite rock\n","new_content":"color: \"78\"\ndescription: \"\"\ngather:\n bait: \"\"\n depleted_message: \"\"\n drops:\n - depletes: true\n item_id: adamantite_ore\n level: 70\n message: \"\"\n quantity: 0\n table: \"\"\n weight: 100\n xp: 95\n exhausted_message: \"\"\n fail_message: You chip away but get nothing useful.\n gather_message: You swing your pickaxe at the adamantite rock...\n respawn_broadcast: A green glint of adamantite catches your eye from some {name}.\n skill: mining\n success:\n base: 0.15\n cap: 0.85\n per_level: 0.01\n tools:\n - pickaxe\nhidden: false\nid: adamantite_rock\nname: adamantite rock\nsteal_level: 1\nsteal_speed: 1\nsteal_table: gem_table\nsteal_xp: 1\n","is_delete":false,"is_create":false}],"redo":null}
\ No newline at end of file
diff --git a/data/objects/adamantite_rock.yaml b/data/objects/adamantite_rock.yaml
index 06ce7bd..4145efe 100644
--- a/data/objects/adamantite_rock.yaml
+++ b/data/objects/adamantite_rock.yaml
@@ -1,21 +1,33 @@
color: "78"
+description: ""
gather:
- drops:
- - depletes: true
- item_id: adamantite_ore
- level: 70
- success_message: You manage to mine some {78}adamantite ore{/}.
- weight: 100
- xp: 95
- fail_message: You chip away but get nothing useful.
- gather_message: You swing your pickaxe at the adamantite rock...
- respawn_broadcast: A green glint of adamantite catches your eye from some {name}.
- respawn_timer: 140
- skill: mining
- success:
- base: 0.15
- cap: 0.85
- per_level: 0.01
- tools:
- - pickaxe
+ bait: ""
+ depleted_message: ""
+ drops:
+ - depletes: true
+ item_id: adamantite_ore
+ level: 70
+ message: ""
+ quantity: 0
+ table: ""
+ weight: 100
+ xp: 95
+ exhausted_message: ""
+ fail_message: You chip away but get nothing useful.
+ gather_message: You swing your pickaxe at the adamantite rock...
+ respawn_broadcast: A green glint of adamantite catches your eye from some {name}.
+ skill: mining
+ success:
+ base: 0.15
+ cap: 0.85
+ per_level: 0.01
+ tools:
+ - pickaxe
+hidden: false
+id: adamantite_rock
name: adamantite rock
+steal:
+ level: 1
+ speed: 1
+ table: gem_table
+ xp: 1
diff --git a/data/objects/market_stall.yaml b/data/objects/market_stall.yaml
index 7ab4570..6fb30d1 100644
--- a/data/objects/market_stall.yaml
+++ b/data/objects/market_stall.yaml
@@ -3,8 +3,9 @@ description: "A wooden stall piled with food and sundries. The vendor doesn't se
color: "B3"
inroom_description: "A bustling {B3}market stall{/} is set up here."
hidden: false
-steal_table: market_stall_steal
-steal_level: 5
-steal_xp: 12
-steal_speed: 5
-guard_mob: guard
\ No newline at end of file
+steal:
+ table: market_stall_steal
+ level: 5
+ xp: 12
+ speed: 5
+ guard_mob: guard
\ No newline at end of file
diff --git a/internal/admin/static/cardeditor.js b/internal/admin/static/cardeditor.js
index f2316e1..8ff5eaf 100644
--- a/internal/admin/static/cardeditor.js
+++ b/internal/admin/static/cardeditor.js
@@ -175,7 +175,7 @@ function ced_selectSearchResult(el) {
function ced_persistSearchToData(input, value) {
var ed = window._editorData;
- var dataPath = input.getAttribute('data-data-path');
+ var dataPath = input.getAttribute('data-field-path');
if (!ed || !dataPath) return;
var m = dataPath.match(/^(.+)\[(\d+)\]\.(.+)$/);
if (m) {
@@ -195,6 +195,48 @@ function ced_persistSearchToData(input, value) {
}
}
+function ced_setPathInData(ed, dataPath, value) {
+ if (!ed || !dataPath) return;
+ var m = dataPath.match(/^(.+)\[(\d+)\]\.(.+)$/);
+ if (m) {
+ if (!ed[m[1]]) ed[m[1]] = [];
+ if (!ed[m[1]][m[2]]) ed[m[1]][m[2]] = {};
+ ed[m[1]][m[2]][m[3]] = value;
+ } else {
+ var m2 = dataPath.match(/^(.+)\.(.+)$/);
+ if (m2) {
+ if (!ed[m2[1]]) ed[m2[1]] = {};
+ ed[m2[1]][m2[2]] = value;
+ } else {
+ ed[dataPath] = value;
+ }
+ }
+}
+
+function ced_syncDOMToData(ed) {
+ if (!ed) return;
+ var inputs = document.querySelectorAll('[data-field-path]');
+ for (var i = 0; i < inputs.length; i++) {
+ var el = inputs[i];
+ var dataPath = el.getAttribute('data-field-path');
+ if (!dataPath) continue;
+ var val;
+ if (el.tagName === 'TEXTAREA') {
+ val = el.value;
+ } else if (el.type === 'checkbox') {
+ val = el.checked;
+ } else if (el.type === 'number') {
+ var raw = el.value.trim();
+ val = raw === '' ? '' : (parseFloat(raw) || 0);
+ } else if (el.tagName === 'SELECT') {
+ val = el.value;
+ } else {
+ val = el.value;
+ }
+ ced_setPathInData(ed, dataPath, val);
+ }
+}
+
function ced_resolveDataPath(obj, path) {
return path.split('.').reduce(function(o, k) {
if (o === undefined || o === null) return undefined;
@@ -236,6 +278,17 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field
if (!showIf(sectionData)) hiddenStyle = ' style="display:none"';
}
+ var pfx = cardPathFn(sec);
+ var dp;
+ if (idx >= 0) {
+ dp = pfx + '[' + idx + '].' + key;
+ } else if (subPath) {
+ dp = (pfx ? pfx + '.' : '') + subPath + '.' + key;
+ } else {
+ dp = (pfx ? pfx + '.' : '') + key;
+ }
+ var dpAttr = ' data-field-path="' + escAttr(dp) + '"';
+
function wrap(cls, inner) {
var w = (wide === 'wide') ? ' obj-wide' : '';
if (wide === 'narrow') w = ' obj-narrow';
@@ -244,19 +297,19 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field
}
if (type === 'checkbox') {
- return wrap('obj-field obj-check', ' ' + esc(label) + '');
+ return wrap('obj-field obj-check', ' ' + esc(label) + '');
}
if (type === 'json') {
var jstr = typeof val === 'object' ? JSON.stringify(val) : String(val);
- return wrap('obj-field', '' + esc(label) + '');
+ return wrap('obj-field', '' + esc(label) + '');
}
if (type === 'textarea') {
var dstr = typeof val === 'object' ? (Array.isArray(val) ? val.map(function(v){return v.text||'';}).join('\n') : JSON.stringify(val)) : String(val);
- return wrap('obj-field', '' + esc(label) + '');
+ return wrap('obj-field', '' + esc(label) + '');
}
if (type === 'select') {
var opts = hint ? hint.split('|') : [];
- var inner = '' + esc(label) + '