aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-07-05 16:42:01 -0400
committerhistoria <[not public]>2026-07-05 16:42:01 -0400
commit9b54188970c51070d86f4c87e909ea7836c86f07 (patch)
tree9dea17fee340bb92dee8bb4632001dbf219d2462 /internal
parent21c1997a3fd0f5ec5dcff1add45e4dfaf7a338ca (diff)
downloadthehouseoficarus-9b54188970c51070d86f4c87e909ea7836c86f07.tar.gz
fix: restructure steal yaml, update admin web gui
Diffstat (limited to 'internal')
-rw-r--r--internal/admin/static/cardeditor.js78
-rw-r--r--internal/admin/static/dropeditor.js1
-rw-r--r--internal/admin/static/hazardeditor.js1
-rw-r--r--internal/admin/static/itemeditor.js54
-rw-r--r--internal/admin/static/mobeditor.js15
-rw-r--r--internal/admin/static/moduleeditor.js1
-rw-r--r--internal/admin/static/objecteditor.js177
-rw-r--r--internal/admin/static/techeditor.js1
-rw-r--r--internal/game/act_steal.go30
-rw-r--r--internal/game/cmd_sneak.go4
-rw-r--r--internal/game/cmd_verbs.go2
-rw-r--r--internal/object/object.go14
-rw-r--r--internal/validate/checks.go37
-rw-r--r--internal/world/room.go12
14 files changed, 288 insertions, 139 deletions
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', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(label) + '</span>');
+ return wrap('obj-field obj-check', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + dpAttr + '> <span>' + esc(label) + '</span>');
}
if (type === 'json') {
var jstr = typeof val === 'object' ? JSON.stringify(val) : String(val);
- return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="2" placeholder="{}">' + esc(jstr) + '</textarea>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="2" placeholder="{}"' + dpAttr + '>' + esc(jstr) + '</textarea>');
}
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', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="3">' + esc(dstr) + '</textarea>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="3"' + dpAttr + '>' + esc(dstr) + '</textarea>');
}
if (type === 'select') {
var opts = hint ? hint.split('|') : [];
- var inner = '<span>' + esc(label) + '</span><select id="' + fid + '">';
+ var inner = '<span>' + esc(label) + '</span><select id="' + fid + '"' + dpAttr + '>';
opts.forEach(function(o) {
inner += '<option value="' + escAttr(o) + '"' + (String(val) === o ? ' selected' : '') + '>' + esc(o) + '</option>';
});
@@ -265,24 +318,15 @@ function ced_renderField(sec, f, data, idx, subPath, optStyle, cardPathFn, field
}
if (type === 'color') {
var swatch = '<span class="color-swatch" style="background:#' + escAttr(String(val) || '808080') + '"></span>';
- return wrap('obj-field color-field', '<span>' + esc(label) + '</span><div class="obj-color-row">' + swatch + '<input id="' + fid + '" value="' + escAttr(String(val)) + '" class="color-input"></div>');
+ return wrap('obj-field color-field', '<span>' + esc(label) + '</span><div class="obj-color-row">' + swatch + '<input id="' + fid + '" value="' + escAttr(String(val)) + '" class="color-input"' + dpAttr + '></div>');
}
if (type === 'search') {
var entity = searchEntity || 'items';
var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."';
- 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;
- }
- return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
+ return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
}
var ph = hint ? ' placeholder="' + esc(hint) + '"' : '';
- return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + '>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + dpAttr + '>');
}
// ---- Subtable row add/remove ----
diff --git a/internal/admin/static/dropeditor.js b/internal/admin/static/dropeditor.js
index cbd51c7..8e463ec 100644
--- a/internal/admin/static/dropeditor.js
+++ b/internal/admin/static/dropeditor.js
@@ -84,6 +84,7 @@ function renderDropEditor(id, data) {
}
function renderCurrent() {
+ ced_syncDOMToData(dropData);
if (!dropData || !dropID) return;
renderDropEditor(dropID, dropData);
}
diff --git a/internal/admin/static/hazardeditor.js b/internal/admin/static/hazardeditor.js
index ac35e78..a0610dc 100644
--- a/internal/admin/static/hazardeditor.js
+++ b/internal/admin/static/hazardeditor.js
@@ -108,6 +108,7 @@ function renderHazardEditor(id, data) {
}
function renderCurrent() {
+ ced_syncDOMToData(hazardData);
if (!hazardData || !hazardID) return;
renderHazardEditor(hazardID, hazardData);
}
diff --git a/internal/admin/static/itemeditor.js b/internal/admin/static/itemeditor.js
index 5a2b371..5ffb0cb 100644
--- a/internal/admin/static/itemeditor.js
+++ b/internal/admin/static/itemeditor.js
@@ -224,6 +224,7 @@ function renderItemEditor(id, data) {
}
function renderCurrent() {
+ ced_syncDOMToData(itemData);
if (!itemData || !itemID) return;
renderItemEditor(itemID, itemData);
}
@@ -349,6 +350,8 @@ function equipFieldID(key) {
function renderEquipField(key, label, type, hint, val, wide, searchEntity) {
var fid = equipFieldID(key);
val = val === undefined || val === null ? '' : val;
+ var dp = 'equipment.' + key;
+ var dpAttr = ' data-field-path="' + escAttr(dp) + '"';
function wrap(cls, inner) {
var w = (wide === 'wide') ? ' obj-wide' : '';
@@ -358,7 +361,7 @@ function renderEquipField(key, label, type, hint, val, wide, searchEntity) {
if (type === 'select') {
var opts = hint ? hint.split('|') : [];
- var inner = '<span>' + esc(label) + '</span><select id="' + fid + '">';
+ var inner = '<span>' + esc(label) + '</span><select id="' + fid + '"' + dpAttr + '>';
opts.forEach(function(o) {
inner += '<option value="' + escAttr(o) + '"' + (String(val) === o ? ' selected' : '') + '>' + esc(o) + '</option>';
});
@@ -368,10 +371,10 @@ function renderEquipField(key, label, type, hint, val, wide, searchEntity) {
if (type === 'search') {
var entity = searchEntity || 'items';
var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."';
- return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
+ return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
}
var ph = hint ? ' placeholder="' + esc(hint) + '"' : '';
- return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + '>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + dpAttr + '>');
}
function renderEquipmentCard(data) {
@@ -491,6 +494,17 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
if (!showIf(sectionData)) hiddenStyle = ' style="display:none"';
}
+ var pfx = cardPath(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';
@@ -499,19 +513,19 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
}
if (type === 'checkbox') {
- return wrap('obj-field obj-check', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(label) + '</span>');
+ return wrap('obj-field obj-check', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + dpAttr + '> <span>' + esc(label) + '</span>');
}
if (type === 'json') {
var jstr = typeof val === 'object' ? JSON.stringify(val) : String(val);
- return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="2" placeholder="{}">' + esc(jstr) + '</textarea>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="2" placeholder="{}"' + dpAttr + '>' + esc(jstr) + '</textarea>');
}
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', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="3">' + esc(dstr) + '</textarea>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="3"' + dpAttr + '>' + esc(dstr) + '</textarea>');
}
if (type === 'select') {
var opts = hint ? hint.split('|') : [];
- var inner = '<span>' + esc(label) + '</span><select id="' + fid + '">';
+ var inner = '<span>' + esc(label) + '</span><select id="' + fid + '"' + dpAttr + '>';
opts.forEach(function(o) {
inner += '<option value="' + escAttr(o) + '"' + (String(val) === o ? ' selected' : '') + '>' + esc(o) + '</option>';
});
@@ -520,24 +534,15 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
}
if (type === 'color') {
var swatch = '<span class="color-swatch" style="background:#' + escAttr(String(val) || '808080') + '"></span>';
- return wrap('obj-field color-field', '<span>' + esc(label) + '</span><div class="obj-color-row">' + swatch + '<input id="' + fid + '" value="' + escAttr(String(val)) + '" class="color-input"></div>');
+ return wrap('obj-field color-field', '<span>' + esc(label) + '</span><div class="obj-color-row">' + swatch + '<input id="' + fid + '" value="' + escAttr(String(val)) + '" class="color-input"' + dpAttr + '></div>');
}
if (type === 'search') {
var entity = searchEntity || 'items';
var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."';
- var pfx = cardPath(sec);
- var dp;
- if (idx >= 0) {
- dp = pfx + '[' + idx + '].' + key;
- } else if (subPath) {
- dp = (pfx ? pfx + '.' : '') + subPath + '.' + key;
- } else {
- dp = (pfx ? pfx + '.' : '') + key;
- }
- return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
+ return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
}
var ph = hint ? ' placeholder="' + esc(hint) + '"' : '';
- return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + '>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + dpAttr + '>');
}
// =========================================================================
@@ -575,25 +580,26 @@ function renderSubFieldHTML(sec, stKey, idx, f, val) {
if (f[4] === 'wide') extraStyle = ' style="flex:1 1 100%"';
var defaultPh = '';
if (f[3]) defaultPh = ' placeholder="' + escAttr(f[3]) + '"';
+ var dp = cardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0];
+ var dpAttr = ' data-field-path="' + escAttr(dp) + '"';
if (f[2] === 'search') {
var entity = f[6] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
- var dp = cardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0];
- return '<label class="obj-field obj-search' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ return '<label class="obj-field obj-search' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
}
if (f[2] === 'checkbox') {
- return '<label class="obj-field obj-check' + extraCls + '"' + extraStyle + '><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
+ return '<label class="obj-field obj-check' + extraCls + '"' + extraStyle + '><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + dpAttr + '> <span>' + esc(f[1]) + '</span></label>';
}
if (f[2] === 'select') {
var opts = f[3] ? f[3].split('|') : [];
- var sel = '<label class="obj-field' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><select id="' + fid + '">';
+ var sel = '<label class="obj-field' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><select id="' + fid + '"' + dpAttr + '>';
opts.forEach(function(o) {
sel += '<option value="' + escAttr(o) + '"' + (String(val) === o ? ' selected' : '') + '>' + esc(o || '(none)') + '</option>';
});
sel += '</select></label>';
return sel;
}
- return '<label class="obj-field' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + defaultPh + '></label>';
+ return '<label class="obj-field' + extraCls + '"' + extraStyle + '><span>' + esc(f[1]) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + defaultPh + dpAttr + '></label>';
}
function renderSubtable(sec, st, data) {
diff --git a/internal/admin/static/mobeditor.js b/internal/admin/static/mobeditor.js
index c564088..73dd16b 100644
--- a/internal/admin/static/mobeditor.js
+++ b/internal/admin/static/mobeditor.js
@@ -202,6 +202,7 @@ function renderMobEditor(id, data) {
}
function renderCurrent() {
+ ced_syncDOMToData(mobData);
if (!mobData || !mobID) return;
renderMobEditor(mobID, mobData);
}
@@ -425,16 +426,17 @@ function renderMobSubField(sec, stKey, idx, f, val) {
else if (f[4] === 'grow') extraCls = ' obj-grow';
var defaultPh = '';
if (f[3]) defaultPh = ' placeholder="' + escAttr(f[3]) + '"';
+ var dp = mobCardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0];
+ var dpAttr = ' data-field-path="' + escAttr(dp) + '"';
if (f[2] === 'search') {
var entity = f[6] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
- var dp = mobCardPath(sec) + '.' + stKey + '[' + idx + '].' + f[0];
- return '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ return '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
}
if (f[2] === 'checkbox') {
- return '<label class="obj-field obj-check' + extraCls + '"><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
+ return '<label class="obj-field obj-check' + extraCls + '"><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + dpAttr + '> <span>' + esc(f[1]) + '</span></label>';
}
- return '<label class="obj-field' + extraCls + '"><span>' + esc(f[1]) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + defaultPh + '></label>';
+ return '<label class="obj-field' + extraCls + '"><span>' + esc(f[1]) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + defaultPh + dpAttr + '></label>';
}
function renderMobSubtable(sec, st, data) {
@@ -529,11 +531,12 @@ function renderMobTableSubtable(sec, st, arr) {
st.fields.forEach(function(f) {
var fid = mobFieldIDSub(sec, st.key, idx, f[0]);
var val = item[f[0]];
+ var dp = mobCardPath(sec) + '.' + st.key + '[' + idx + '].' + f[0];
if (f[2] === 'search') {
var entity = f[6] || 'items';
var sval = (val === undefined || val === null) ? '' : String(val);
h += '<div class="ingredients-col ingredients-col-items"><div class="obj-field obj-search" style="width:100%"><div style="position:relative;width:100%">' +
- '<input id="' + fid + '" value="' + escAttr(sval) + '" class="search-input" data-search-type="' + entity + '" placeholder="Search ' + entity + '...">' +
+ '<input id="' + fid + '" value="' + escAttr(sval) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '" placeholder="Search ' + entity + '...">' +
'<div class="search-results" style="display:none"></div></div></div></div>';
} else {
var hasPh = !!f[3];
@@ -543,7 +546,7 @@ function renderMobTableSubtable(sec, st, arr) {
else nval = String(val);
var ph = hasPh ? ' placeholder="' + escAttr(f[3]) + '"' : '';
h += '<div class="ingredients-col" style="width:' + mobTableColWidth(f) + 'px">' +
- '<label class="obj-field"><input id="' + fid + '" value="' + escAttr(nval) + '"' + ph + '></label>' +
+ '<label class="obj-field"><input id="' + fid + '" value="' + escAttr(nval) + '"' + ph + ' data-field-path="' + escAttr(dp) + '"></label>' +
'</div>';
}
});
diff --git a/internal/admin/static/moduleeditor.js b/internal/admin/static/moduleeditor.js
index 6d6a3e0..f1fb0fd 100644
--- a/internal/admin/static/moduleeditor.js
+++ b/internal/admin/static/moduleeditor.js
@@ -109,6 +109,7 @@ function renderModuleEditor(id, data) {
}
function renderCurrent() {
+ ced_syncDOMToData(moduleData);
if (!moduleData || !moduleID) return;
renderModuleEditor(moduleID, moduleData);
}
diff --git a/internal/admin/static/objecteditor.js b/internal/admin/static/objecteditor.js
index c80cb57..f8ef18a 100644
--- a/internal/admin/static/objecteditor.js
+++ b/internal/admin/static/objecteditor.js
@@ -18,14 +18,14 @@ var SECTIONS = [
]
},
{
- id: 'steal', label: 'Steal',
- detect: function(d) { return d.steal_table !== undefined || d.steal_level !== undefined || d.steal_speed !== undefined || d.steal_xp !== undefined || d.guard_mob !== undefined; },
+ id: 'steal', label: 'Steal', path: 'steal',
+ detect: function(d) { return d.steal !== undefined; },
fields: [
- ['steal_table', 'Table ID', 'text'],
- ['steal_level', 'Level', 'number', '', 'narrow'],
- ['steal_xp', 'XP', 'number', '', 'narrow'],
- ['steal_speed', 'Speed', 'number', '', 'narrow'],
- ['guard_mob', 'Guard Mob', 'text'],
+ ['table', 'Table ID', 'search', '', '', null, 'drops'],
+ ['level', 'Level', 'number', '1', 'narrow'],
+ ['xp', 'XP', 'number', '5', 'narrow'],
+ ['speed', 'Speed', 'number', '4', 'narrow'],
+ ['guard_mob', 'Guard Mob', 'search', '', '', null, 'mobs'],
]
},
{
@@ -148,6 +148,8 @@ function loadObject(id) {
window._editorData = objectData;
window._useInterVis = {};
window._useInterHelpCollapsed = undefined;
+ window._coreVis = {};
+ window._stealVis = {};
renderObjectEditor(id, data);
}).catch(function(e) {
$('#editorMain').innerHTML = '<p style="color:var(--danger)">Failed to load: ' + esc(e.message) + '</p>';
@@ -250,6 +252,8 @@ function renderCard(sec, data) {
h += renderArraySection(sec, data);
} else if (sec.id === 'core') {
h += renderCoreCard(data);
+ } else if (sec.id === 'steal') {
+ h += renderStealCard(data);
} else {
h += '<div class="obj-card-grid">';
sec.fields.forEach(function(f) {
@@ -351,6 +355,17 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
if (!showIf(sectionData)) hiddenStyle = ' style="display:none"';
}
+ var pfx = cardPath(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';
@@ -359,19 +374,19 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
}
if (type === 'checkbox') {
- return wrap('obj-field obj-check', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(label) + '</span>');
+ return wrap('obj-field obj-check', '<input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + dpAttr + '> <span>' + esc(label) + '</span>');
}
if (type === 'json') {
var jstr = typeof val === 'object' ? JSON.stringify(val) : String(val);
- return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="2" placeholder="{}">' + esc(jstr) + '</textarea>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="2" placeholder="{}"' + dpAttr + '>' + esc(jstr) + '</textarea>');
}
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', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="3">' + esc(dstr) + '</textarea>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><textarea id="' + fid + '" rows="3"' + dpAttr + '>' + esc(dstr) + '</textarea>');
}
if (type === 'select') {
var opts = hint ? hint.split('|') : [];
- var inner = '<span>' + esc(label) + '</span><select id="' + fid + '" onchange="toggleGatherConditionals()">';
+ var inner = '<span>' + esc(label) + '</span><select id="' + fid + '" onchange="toggleGatherConditionals()"' + dpAttr + '>';
opts.forEach(function(o) {
inner += '<option value="' + escAttr(o) + '"' + (String(val) === o ? ' selected' : '') + '>' + (o === '' ? 'All' : esc(o)) + '</option>';
});
@@ -380,39 +395,93 @@ function renderField(sec, f, data, idx, subPath, optStyle) {
}
if (type === 'color') {
var swatch = '<span class="color-swatch" style="background:#' + escAttr(String(val) || '808080') + '"></span>';
- return wrap('obj-field color-field', '<span>' + esc(label) + '</span><div class="obj-color-row">' + swatch + '<input id="' + fid + '" value="' + escAttr(String(val)) + '" class="color-input"></div>');
+ return wrap('obj-field color-field', '<span>' + esc(label) + '</span><div class="obj-color-row">' + swatch + '<input id="' + fid + '" value="' + escAttr(String(val)) + '" class="color-input"' + dpAttr + '></div>');
}
if (type === 'search') {
var entity = searchEntity || 'items';
var ph = hint ? ' placeholder="' + esc(hint) + '"' : ' placeholder="Search ' + entity + '..."';
- var pfx = cardPath(sec);
- var dp;
- if (idx >= 0) {
- dp = pfx + '[' + idx + '].' + key;
- } else if (subPath) {
- dp = (pfx ? pfx + '.' : '') + subPath + '.' + key;
- } else {
- dp = (pfx ? pfx + '.' : '') + key;
- }
- return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
+ return wrap('obj-field obj-search', '<span>' + esc(label) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div>');
}
var ph = hint ? ' placeholder="' + esc(hint) + '"' : '';
- return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + '>');
+ return wrap('obj-field', '<span>' + esc(label) + '</span><input id="' + fid + '" value="' + escAttr(String(val)) + '"' + ph + dpAttr + '>');
}
function renderCoreCard(data) {
var sec = SECTIONS[0];
+ window._coreVis = window._coreVis || {};
+ var vis = window._coreVis;
+ var showAliases = vis.aliases || (data.aliases && data.aliases.length > 0);
+ var showInRoom = vis.inroom_description || (data.inroom_description && data.inroom_description !== '');
+ var showRemoval = vis.removal_item || (data.removal_item && data.removal_item !== '');
+
var h = '<div class="obj-core-row">';
h += renderField(sec, sec.fields[0], data, -1, null, 'flex:1'); // name
- h += renderField(sec, sec.fields[2], data, -1, null, 'flex:2'); // aliases
h += renderField(sec, sec.fields[1], data, -1); // color
h += renderField(sec, sec.fields[3], data, -1); // hidden
h += '</div>';
h += '<div style="display:flex;flex-direction:column;gap:8px">';
h += renderField(sec, sec.fields[5], data, -1); // description
- h += renderField(sec, sec.fields[4], data, -1); // in-room description
- h += renderField(sec, sec.fields[6], data, -1); // removal item
+
+ if (showAliases) {
+ h += '<div style="display:flex;gap:6px;align-items:flex-start">';
+ h += '<div style="flex:1">' + renderField(sec, sec.fields[2], data, -1, null, 'flex:2') + '</div>';
+ h += '<button class="btn btn-sm btn-danger" onclick="hideCoreField(\'aliases\')" style="margin-top:2px">X</button>';
+ h += '</div>';
+ }
+ if (showInRoom) {
+ h += '<div style="display:flex;gap:6px;align-items:flex-start">';
+ h += '<div style="flex:1">' + renderField(sec, sec.fields[4], data, -1) + '</div>';
+ h += '<button class="btn btn-sm btn-danger" onclick="hideCoreField(\'inroom_description\')" style="margin-top:2px">X</button>';
+ h += '</div>';
+ }
+ if (showRemoval) {
+ h += '<div style="display:flex;gap:6px;align-items:flex-start">';
+ h += '<div style="flex:1">' + renderField(sec, sec.fields[6], data, -1) + '</div>';
+ h += '<button class="btn btn-sm btn-danger" onclick="hideCoreField(\'removal_item\')" style="margin-top:2px">X</button>';
+ h += '</div>';
+ }
h += '</div>';
+
+ h += '<div style="display:flex;gap:8px;margin-top:4px">';
+ if (!showAliases) {
+ h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;white-space:nowrap" onclick="showCoreField(\'aliases\')">+ Add Aliases</button>';
+ }
+ if (!showInRoom) {
+ h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;white-space:nowrap" onclick="showCoreField(\'inroom_description\')">+ Add In-Room Desc</button>';
+ }
+ if (!showRemoval) {
+ h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;white-space:nowrap" onclick="showCoreField(\'removal_item\')">+ Add Removal Item</button>';
+ }
+ h += '</div>';
+
+ return h;
+}
+
+function renderStealCard(data) {
+ var sec = SECTIONS[1];
+ window._stealVis = window._stealVis || {};
+ var vis = window._stealVis;
+ var stealData = data.steal || {};
+ var showGuard = vis.guard_mob || (stealData.guard_mob && stealData.guard_mob !== '');
+
+ var h = '<div style="display:flex;gap:10px">';
+ h += '<div style="flex:1">' + renderField(sec, sec.fields[0], data, -1) + '</div>';
+ h += renderField(sec, sec.fields[1], data, -1);
+ h += renderField(sec, sec.fields[2], data, -1);
+ h += renderField(sec, sec.fields[3], data, -1);
+ h += '</div>';
+
+ if (showGuard) {
+ h += '<div style="display:flex;gap:6px;align-items:flex-start;margin-top:8px">';
+ h += '<div style="flex:1">' + renderField(sec, sec.fields[4], data, -1) + '</div>';
+ h += '<button class="btn btn-sm btn-danger" onclick="hideGuardMob()" style="margin-top:2px">X</button>';
+ h += '</div>';
+ } else {
+ h += '<div style="margin-top:8px">';
+ h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;white-space:nowrap;width:100%" onclick="showGuardMob()">+ Guard Mob</button>';
+ h += '</div>';
+ }
+
return h;
}
@@ -433,7 +502,9 @@ function renderArraySection(sec, data) {
h += '<div class="obj-sub-row" data-idx="' + idx + '" style="flex-direction:column;align-items:stretch;position:relative;gap:6px">';
- h += '<div style="display:flex;gap:6px;align-items:flex-end;flex-wrap:wrap">';
+ h += '<button class="btn btn-sm btn-danger" style="position:absolute;top:0;right:0;white-space:nowrap;z-index:1" onclick="removeArrayItem(\'' + sec.id + '\',' + idx + ')">Remove Interaction</button>';
+
+ h += '<div style="display:flex;gap:4px;align-items:flex-end;flex-wrap:wrap;padding-right:125px">';
h += renderField(sec, sec.fields[0], data, idx, null, 'max-width:260px');
h += '<div style="width:1px;background:var(--border);align-self:stretch;margin:2px 0;flex-shrink:0"></div>';
if (!showCond) {
@@ -445,7 +516,6 @@ function renderArraySection(sec, data) {
if (!showMsg) {
h += '<button class="btn btn-sm" style="background:var(--accent);color:var(--text);border:1px solid #1a5a8e;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="showUseInterField(' + idx + ',\'message\')">+ Add Message</button>';
}
- h += '<button class="btn btn-sm btn-danger" style="margin-left:auto;align-self:flex-end;margin-bottom:3px;white-space:nowrap" onclick="removeArrayItem(\'' + sec.id + '\',' + idx + ')">Remove Interaction</button>';
h += '</div>';
h += '<div style="display:' + (showCond ? 'flex' : 'none') + ';gap:6px;align-items:flex-start">';
@@ -541,7 +611,7 @@ function renderSubtable(sec, st, data) {
var entity = f[5] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
var dp = cardPath(sec) + '.' + st.key + '[' + idx + '].' + f[0];
- h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
} else if (f[2] === 'checkbox') {
h += '<label class="obj-field obj-check' + extraCls + '"><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
} else {
@@ -567,7 +637,7 @@ function renderSubtable(sec, st, data) {
var entity = f[5] || 'items';
var ph = f[3] ? ' placeholder="' + escAttr(f[3]) + '"' : ' placeholder="Search ' + entity + '..."';
var dp = cardPath(sec) + '.' + st.key + '[' + idx + '].' + f[0];
- h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-data-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
+ h += '<label class="obj-field obj-search' + extraCls + '"><span>' + esc(f[1]) + '</span><div style="position:relative;width:100%"><input id="' + fid + '" value="' + escAttr(String(val)) + '" class="search-input" data-search-type="' + entity + '" data-field-path="' + escAttr(dp) + '"' + ph + '><div class="search-results" style="display:none"></div></div></label>';
} else if (f[2] === 'checkbox') {
h += '<label class="obj-field obj-check' + extraCls + '"><input type="checkbox" id="' + fid + '"' + (val ? ' checked' : '') + '> <span>' + esc(f[1]) + '</span></label>';
} else {
@@ -693,11 +763,7 @@ function removeSection(id) {
if (sec.path) {
delete objectData[sec.path];
} else if (id === 'steal') {
- delete objectData.steal_table;
- delete objectData.steal_level;
- delete objectData.steal_xp;
- delete objectData.steal_speed;
- delete objectData.guard_mob;
+ delete objectData.steal;
}
renderCurrent();
}
@@ -724,11 +790,7 @@ function addSection() {
objectData[sec.path] = {};
}
} else if (id === 'steal') {
- objectData.steal_table = '';
- objectData.steal_level = 0;
- objectData.steal_xp = 0;
- objectData.steal_speed = 0;
- objectData.guard_mob = '';
+ objectData.steal = {table: ''};
}
expandedCards[id] = true;
renderCurrent();
@@ -806,6 +868,7 @@ function validateDropRow(idx) {
}
function renderCurrent() {
+ ced_syncDOMToData(objectData);
if (!objectData || !objectID) return;
renderObjectEditor(objectID, objectData);
}
@@ -827,6 +890,38 @@ function hideUseInterField(idx, key) {
renderCurrent();
}
+function showCoreField(key) {
+ window._coreVis = window._coreVis || {};
+ window._coreVis[key] = true;
+ renderCurrent();
+}
+
+function hideCoreField(key) {
+ window._coreVis = window._coreVis || {};
+ window._coreVis[key] = false;
+ if (key === 'aliases') {
+ objectData.aliases = [];
+ } else if (key === 'inroom_description') {
+ objectData.inroom_description = '';
+ } else if (key === 'removal_item') {
+ objectData.removal_item = '';
+ }
+ renderCurrent();
+}
+
+function showGuardMob() {
+ window._stealVis = window._stealVis || {};
+ window._stealVis.guard_mob = true;
+ renderCurrent();
+}
+
+function hideGuardMob() {
+ window._stealVis = window._stealVis || {};
+ window._stealVis.guard_mob = false;
+ if (objectData.steal) objectData.steal.guard_mob = '';
+ renderCurrent();
+}
+
function toggleUseInterHelp() {
if (window._useInterHelpCollapsed === undefined) {
window._useInterHelpCollapsed = false;
@@ -967,6 +1062,8 @@ function saveObject() {
}
} else if (hasValues && sec.path) {
out[sec.path] = sectionObj;
+ } else if (hasValues) {
+ Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; });
}
});
diff --git a/internal/admin/static/techeditor.js b/internal/admin/static/techeditor.js
index cd4f50f..70f881e 100644
--- a/internal/admin/static/techeditor.js
+++ b/internal/admin/static/techeditor.js
@@ -98,6 +98,7 @@ function renderTechEditor(id, data) {
}
function renderCurrent() {
+ ced_syncDOMToData(techData);
if (!techData || !techID) return;
renderTechEditor(techID, techData);
}
diff --git a/internal/game/act_steal.go b/internal/game/act_steal.go
index a64bc96..b5c1210 100644
--- a/internal/game/act_steal.go
+++ b/internal/game/act_steal.go
@@ -102,7 +102,7 @@ func (g *Game) resolveStealTarget(sess *net.Session, p *player.Player, input str
var stealObjs []*object.ObjectDef
for _, st := range roomObjs {
def, err := g.ObjectStore.Load(st.DefID)
- if err != nil || def.StealTable == "" {
+ if err != nil || def.Steal == nil || def.Steal.Table == "" {
continue
}
stealObjs = append(stealObjs, def)
@@ -224,14 +224,14 @@ func (g *Game) startSteal(sess *net.Session, p *player.Player, targetType string
return
}
} else {
- stealTable = objDef.StealTable
- stealLevel = objDef.StealLevel
- stealXP = objDef.StealXP
- stealSpeed = objDef.StealSpeed
+ stealTable = objDef.Steal.Table
+ stealLevel = objDef.Steal.Level
+ stealXP = objDef.Steal.XP
+ stealSpeed = objDef.Steal.Speed
targetName = objDef.Name
targetID = objDef.ID
objDefID = objDef.ID
- guardMob = objDef.GuardMob
+ guardMob = objDef.Steal.GuardMob
if stealTable == "" {
sess.WriteLine(fmt.Sprintf("You can't steal from the %s.", targetName))
@@ -328,15 +328,15 @@ func (g *Game) advanceSteal(sess *net.Session, p *player.Player) {
return
}
objDef, err := g.ObjectStore.Load(objDefID)
- if err == nil {
- d.StealLevel = objDef.StealLevel
- d.StealXP = objDef.StealXP
- d.StealSpeed = objDef.StealSpeed
- stealLevel = objDef.StealLevel
- stealXP = objDef.StealXP
- stealSpeed = objDef.StealSpeed
- d.GuardMob = objDef.GuardMob
- guardMob = objDef.GuardMob
+ if err == nil && objDef.Steal != nil {
+ d.StealLevel = objDef.Steal.Level
+ d.StealXP = objDef.Steal.XP
+ d.StealSpeed = objDef.Steal.Speed
+ stealLevel = objDef.Steal.Level
+ stealXP = objDef.Steal.XP
+ stealSpeed = objDef.Steal.Speed
+ d.GuardMob = objDef.Steal.GuardMob
+ guardMob = objDef.Steal.GuardMob
if guardMob != "" {
guard := g.findGuardInRoom(p.RoomID, guardMob)
diff --git a/internal/game/cmd_sneak.go b/internal/game/cmd_sneak.go
index 325a36b..5ae60e7 100644
--- a/internal/game/cmd_sneak.go
+++ b/internal/game/cmd_sneak.go
@@ -40,11 +40,11 @@ func (g *Game) SneakTick() {
objs := g.World.AllObjInstances(p.RoomID)
for _, st := range objs {
objDef, err := g.ObjectStore.Load(st.DefID)
- if err != nil || objDef.GuardMob == "" {
+ if err != nil || objDef.Steal == nil || objDef.Steal.GuardMob == "" {
continue
}
- guard := g.findGuardInRoom(p.RoomID, objDef.GuardMob)
+ guard := g.findGuardInRoom(p.RoomID, objDef.Steal.GuardMob)
if guard == nil {
continue
}
diff --git a/internal/game/cmd_verbs.go b/internal/game/cmd_verbs.go
index cad90c5..57db0a6 100644
--- a/internal/game/cmd_verbs.go
+++ b/internal/game/cmd_verbs.go
@@ -118,7 +118,7 @@ func (g *Game) executeVerbs(sess *net.Session, args []string, rawInput string) {
}
}
- if def.StealTable != "" {
+ if def.Steal != nil && def.Steal.Table != "" {
addUnique(&sectionObjs, &seen, "steal "+name)
}
diff --git a/internal/object/object.go b/internal/object/object.go
index 2dcc4bf..5b45a91 100644
--- a/internal/object/object.go
+++ b/internal/object/object.go
@@ -9,6 +9,14 @@ type UseInteraction struct {
Action *behavior.NodeAction `yaml:"action"`
}
+type ObjectSteal struct {
+ Table string `yaml:"table"`
+ Level int `yaml:"level"`
+ XP int `yaml:"xp"`
+ Speed float64 `yaml:"speed"`
+ GuardMob string `yaml:"guard_mob,omitempty"`
+}
+
type ObjectDef struct {
ID string `yaml:"id"`
Name string `yaml:"name"`
@@ -19,11 +27,7 @@ type ObjectDef struct {
RemovalItem string `yaml:"removal_item"`
Description behavior.DescList `yaml:"description"`
UseInteractions []UseInteraction `yaml:"use_interactions"`
- StealTable string `yaml:"steal_table"`
- StealLevel int `yaml:"steal_level"`
- StealXP int `yaml:"steal_xp"`
- StealSpeed float64 `yaml:"steal_speed"`
- GuardMob string `yaml:"guard_mob"`
+ Steal *ObjectSteal `yaml:"steal,omitempty"`
Gather *behavior.GatherConfig `yaml:"gather,omitempty"`
Talk *behavior.TalkConfig `yaml:"talk,omitempty"`
diff --git a/internal/validate/checks.go b/internal/validate/checks.go
index 974998a..0fc5b8f 100644
--- a/internal/validate/checks.go
+++ b/internal/validate/checks.go
@@ -198,12 +198,9 @@ func validateLocalObject(roomID int, robj world.RoomObject, itemIDs map[string]b
if len(def.UseInteractions) > 0 {
bad = append(bad, "use_interactions")
}
- if def.StealTable != "" || def.StealLevel != 0 || def.StealXP != 0 || def.StealSpeed != 0 {
+ if def.Steal != nil {
bad = append(bad, "steal")
}
- if def.GuardMob != "" {
- bad = append(bad, "guard_mob")
- }
if def.RemovalItem != "" {
bad = append(bad, "removal_item")
}
@@ -548,22 +545,24 @@ func validateObjects(s Source) []Issue {
})
}
- if obj.StealTable != "" && !dropIDs[obj.StealTable] {
- issues = append(issues, Issue{
- Level: "ERROR",
- Type: "reference",
- Message: fmt.Sprintf("Object %q: steal_table %q does not exist",
- id, obj.StealTable),
- })
- }
+ if obj.Steal != nil {
+ if obj.Steal.Table != "" && !dropIDs[obj.Steal.Table] {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: steal.table %q does not exist",
+ id, obj.Steal.Table),
+ })
+ }
- if obj.GuardMob != "" && !mobIDs[obj.GuardMob] {
- issues = append(issues, Issue{
- Level: "ERROR",
- Type: "reference",
- Message: fmt.Sprintf("Object %q: guard_mob %q does not exist",
- id, obj.GuardMob),
- })
+ if obj.Steal.GuardMob != "" && !mobIDs[obj.Steal.GuardMob] {
+ issues = append(issues, Issue{
+ Level: "ERROR",
+ Type: "reference",
+ Message: fmt.Sprintf("Object %q: steal.guard_mob %q does not exist",
+ id, obj.Steal.GuardMob),
+ })
+ }
}
for _, ui := range obj.UseInteractions {
diff --git a/internal/world/room.go b/internal/world/room.go
index 9c2e52f..f66ce1d 100644
--- a/internal/world/room.go
+++ b/internal/world/room.go
@@ -210,11 +210,7 @@ func (ro RoomObject) MarshalYAML() (interface{}, error) {
RemovalItem string `yaml:"removal_item,omitempty"`
Description behavior.DescList `yaml:"description,omitempty"`
UseInteractions []object.UseInteraction `yaml:"use_interactions,omitempty"`
- StealTable string `yaml:"steal_table,omitempty"`
- StealLevel int `yaml:"steal_level,omitempty"`
- StealXP int `yaml:"steal_xp,omitempty"`
- StealSpeed float64 `yaml:"steal_speed,omitempty"`
- GuardMob string `yaml:"guard_mob,omitempty"`
+ Steal *object.ObjectSteal `yaml:"steal,omitempty"`
Gather *behavior.GatherConfig `yaml:"gather,omitempty"`
Talk *behavior.TalkConfig `yaml:"talk,omitempty"`
Use *behavior.UseConfig `yaml:"use,omitempty"`
@@ -231,11 +227,7 @@ func (ro RoomObject) MarshalYAML() (interface{}, error) {
RemovalItem: def.RemovalItem,
Description: def.Description,
UseInteractions: def.UseInteractions,
- StealTable: def.StealTable,
- StealLevel: def.StealLevel,
- StealXP: def.StealXP,
- StealSpeed: def.StealSpeed,
- GuardMob: def.GuardMob,
+ Steal: def.Steal,
Gather: def.Gather,
Talk: def.Talk,
Use: def.Use,