';
@@ -198,6 +194,7 @@ function renderMobEditor(id, data) {
$('#editorMain').innerHTML = html;
ced_setupSearchFields();
setupMobKindChange();
+ setupMobProtectedToggle();
MOB_SECTIONS.forEach(function(sec) {
if (sec.id === 'talk' && data.talk && data.talk.nodes) {
@@ -291,16 +288,52 @@ function renderMobCard(sec, data) {
function renderMobCoreCard(data) {
var sec = MOB_SECTIONS[0];
+ var byKey = {};
+ sec.fields.forEach(function(f) { byKey[f[0]] = f; });
var h = '
';
- h += renderMobField(sec, sec.fields[0], data, -1, null, 'flex:1');
+ h += renderMobField(sec, byKey['name'], data, -1, null, 'flex:1');
h += '
';
- h += renderMobField(sec, sec.fields[1], data, -1);
+ h += renderMobField(sec, byKey['description'], data, -1);
sec.inline.forEach(function(il) {
h += ced_renderTags(sec, il, data);
});
+ // Behavior fields (merged into Core).
+ h += '
';
+ h += renderMobField(sec, byKey['unique'], data, -1);
+ h += renderMobField(sec, byKey['protected'], data, -1);
+ h += renderMobField(sec, byKey['aggressive'], data, -1);
+ h += '
';
+ h += '
';
+ h += renderMobField(sec, byKey['kind'], data, -1);
+ h += renderMobField(sec, byKey['respawn_ticks'], data, -1);
+ h += '
';
return h;
}
+// setupMobProtectedToggle hides Aggressive, Respawn Ticks and Kind while
+// Protected is checked (protected mobs never attack, respawn, or work). Toggles
+// CSS only — no re-render — so other unsaved field edits are preserved.
+function setupMobProtectedToggle() {
+ var sec = MOB_SECTIONS.find(function(s) { return s.id === 'core'; });
+ if (!sec) return;
+ var protEl = document.getElementById(mobFieldID(sec, 'protected', -1));
+ if (!protEl) return;
+ function apply() {
+ var hide = protEl.checked;
+ ['aggressive', 'respawn_ticks', 'kind'].forEach(function(key) {
+ var el = document.getElementById(mobFieldID(sec, key, -1));
+ if (!el) return;
+ var field = el.closest('.obj-field');
+ if (field) field.style.display = hide ? 'none' : '';
+ });
+ }
+ if (!protEl._hasProtectedToggle) {
+ protEl._hasProtectedToggle = true;
+ protEl.addEventListener('change', apply);
+ }
+ apply();
+}
+
function renderMobField(sec, f, data, idx, subPath, optStyle) {
return ced_renderField(sec, f, data, idx, subPath, optStyle, mobCardPath, mobFieldID, mobGetVal);
}
@@ -340,6 +373,12 @@ function renderMobSubtable(sec, st, data) {
} else {
arr = [];
}
+ if (st.table) {
+ return renderMobTableSubtable(sec, st, arr);
+ }
+ if (sec.id === 'drops' && st.key === 'loot') {
+ return renderMobLootSubtable(sec, st, arr);
+ }
var h = '
';
h += '
' + esc(st.label) + '
';
arr.forEach(function(item, idx) {
@@ -360,6 +399,94 @@ function renderMobSubtable(sec, st, data) {
return h;
}
+// renderMobLootSubtable renders the Drops loot list where each row is either an
+// item drop (Item + Weight + Quantity) or a table drop (Table + Weight + Rolls).
+// A table entry's "Rolls" is stored in the `quantity` field and means the number
+// of independent draws from that table (see behavior.ResolveDropList).
+function renderMobLootSubtable(sec, st, arr) {
+ var byKey = {};
+ st.fields.forEach(function(f) { byKey[f[0]] = f; });
+ var rollsField = ['quantity', 'Rolls', 'number', '', 'narrow'];
+
+ var h = '
';
+ if (kind === 'table') {
+ h += renderMobSubField(sec, st.key, idx, byKey['table'], item.table);
+ h += renderMobSubField(sec, st.key, idx, byKey['weight'], item.weight);
+ h += renderMobSubField(sec, st.key, idx, rollsField, item.quantity);
+ } else {
+ h += renderMobSubField(sec, st.key, idx, byKey['item_id'], item.item_id);
+ h += renderMobSubField(sec, st.key, idx, byKey['weight'], item.weight);
+ h += renderMobSubField(sec, st.key, idx, byKey['quantity'], item.quantity);
+ }
+ h += '';
+ h += '
';
+ });
+ h += '
';
+ h += '';
+ h += '';
+ h += '
';
+ h += '
';
+ return h;
+}
+
+// Column width (px) for a non-search field, sized to fit its header label.
+function mobTableColWidth(f) {
+ return Math.max(60, esc(f[1]).length * 8 + 20);
+}
+
+// renderMobTableSubtable renders a subtable in the ingredients-style table
+// layout: a single column-header row (no card header) and one row per entry.
+function renderMobTableSubtable(sec, st, arr) {
+ var h = '
';
+ h += '
';
+ st.fields.forEach(function(f) {
+ if (f[2] === 'search') {
+ h += '
' + esc(f[1]) + '
';
+ } else {
+ h += '
' + esc(f[1]) + '
';
+ }
+ });
+ h += '';
+ h += '
';
+
+ arr.forEach(function(item, idx) {
+ h += '
';
+ st.fields.forEach(function(f) {
+ var fid = mobFieldIDSub(sec, st.key, idx, f[0]);
+ var val = item[f[0]];
+ if (f[2] === 'search') {
+ var entity = f[6] || 'items';
+ var sval = (val === undefined || val === null) ? '' : String(val);
+ h += '
' +
+ '' +
+ '
';
+ } else {
+ // Numeric column. Blank a 0/unset value only when the field has a
+ // placeholder, so the placeholder (e.g. "1000") shows through.
+ var hasPh = !!f[3];
+ var nval;
+ if (val === undefined || val === null || val === '') nval = '';
+ else if (val === 0 && hasPh) nval = '';
+ else nval = String(val);
+ var ph = hasPh ? ' placeholder="' + escAttr(f[3]) + '"' : '';
+ h += '
' +
+ '' +
+ '
';
+ }
+ });
+ h += '';
+ h += '
';
+ });
+
+ h += '
';
+ h += '';
+ return h;
+}
+
// ---- Card toggle / section add-remove ----
function toggleMobCard(id) {
@@ -390,7 +517,7 @@ function mobAddSection() {
if (sec.path) {
if (sec.id === 'drops') {
- mobData[sec.path] = {remains:'', loot:[]};
+ mobData[sec.path] = {remains:'bones', loot:[]};
} else if (sec.id === 'talk') {
mobData[sec.path] = {nodes:{start:{messages:[],options:[]}}};
} else if (sec.id === 'shop') {
@@ -420,10 +547,25 @@ function removeMobSubRow(cardID, subKey, idx) {
renderCurrent();
}
+// addMobLootRow appends an item- or table-kind loot entry. The ephemeral _kind
+// marker is only used for rendering fresh empty rows; it's never saved (save
+// rebuilds rows from the visible inputs) and is re-inferred from item_id/table
+// on reload.
+function addMobLootRow(kind) {
+ if (!mobData.drops) mobData.drops = {};
+ if (!Array.isArray(mobData.drops.loot)) mobData.drops.loot = [];
+ if (kind === 'table') {
+ mobData.drops.loot.push({table:'', weight:0, quantity:1, _kind:'table'});
+ } else {
+ mobData.drops.loot.push({item_id:'', weight:0, quantity:1, _kind:'item'});
+ }
+ renderCurrent();
+}
+
// ---- Kind change handler (show/hide task fields) ----
function setupMobKindChange() {
- var kindSel = document.getElementById(mobFieldID(MOB_SECTIONS.find(function(s){return s.id==='behavior';}), 'kind', -1));
+ var kindSel = document.getElementById(mobFieldID(MOB_SECTIONS.find(function(s){return s.id==='core';}), 'kind', -1));
if (!kindSel) return;
kindSel.addEventListener('change', function() {
if (kindSel.value === 'task') {
--
cgit v1.2.3