';
html += '';
html += '';
html += '';
html += '';
html += '
';
$('#editorMain').innerHTML = html;
ced_setupSearchFields();
setupMobProtectedToggle();
MOB_SECTIONS.forEach(function(sec) {
if (sec.id === 'talk' && data.talk && data.talk.nodes) {
renderTalkTree(data);
}
});
}
function renderCurrent() {
ced_syncDOMToData(mobData);
ie_syncAllInteractions(mobData, MOB_SECTIONS);
if (!mobData || !mobID) return;
renderMobEditor(mobID, mobData);
}
// ---- Card rendering ----
function renderMobCard(sec, data) {
var collapsed = expandedCards[sec.id] === false;
var isFull = cardWidths[sec.id] !== undefined ? cardWidths[sec.id] : (sec.fullWidth || false);
var fullClass = isFull ? ' obj-card-full' : '';
var h = '
';
h += '
';
h += '' + (collapsed ? '▶' : '▼') + '';
h += '' + sec.label + '';
h += '';
if (!sec.always) {
h += '';
}
h += '
';
if (!collapsed) {
h += '
';
if (sec.id === 'core') {
h += renderMobCoreCard(data);
} else if (sec.id === 'combat') {
h += renderMobCombatCard(data);
} else if (sec.isArray) {
h += renderMobArraySection(sec, data);
} else if (sec.subtables) {
h += '
';
sec.fields.forEach(function(f) {
h += renderMobField(sec, f, data, -1);
});
h += '
';
sec.subtables.forEach(function(st) {
h += renderMobSubtable(sec, st, data);
});
} else {
h += '
';
sec.fields.forEach(function(f) {
h += renderMobField(sec, f, data, -1);
});
h += '
';
if (sec.inline) {
sec.inline.forEach(function(il) {
if (il.type === 'tags') {
h += ced_renderTags(sec, il, data);
}
});
}
if (sec.id === 'talk') {
h += '';
}
}
h += '
';
}
h += '
';
return h;
}
function renderMobCoreCard(data) {
var sec = MOB_SECTIONS[0];
var byKey = {};
sec.fields.forEach(function(f) { byKey[f[0]] = f; });
var h = '
';
h += renderMobField(sec, byKey['name'], data, -1, null, 'flex:1');
h += '
';
h += renderMobField(sec, byKey['description'], data, -1);
sec.inline.forEach(function(il) {
h += ced_renderTags(sec, il, data);
});
h += '
';
h += renderMobField(sec, byKey['unique'], data, -1);
h += renderMobField(sec, byKey['protected'], data, -1);
h += '
';
return h;
}
// normalizeAttackTypes coerces a mob attack_types value (array) into an array of
// strings, tolerating a legacy scalar string for display only.
function normalizeAttackTypes(v) {
if (Array.isArray(v)) return v.slice();
if (typeof v === 'string' && v) return [v];
return [];
}
// renderMobAttackTypeWidget renders the Atk Type selector: one required melee
// type (radio: stab/slash/crush) plus optional ranged/science (checkboxes).
function renderMobAttackTypeWidget(data) {
var combat = data.combat || {};
var types = normalizeAttackTypes(combat.attack_types);
var melee = types.filter(function(t) { return t === 'stab' || t === 'slash' || t === 'crush'; })[0] || 'crush';
var hasRanged = types.indexOf('ranged') >= 0;
var hasScience = types.indexOf('science') >= 0;
var h = '
';
h += 'Atk Type';
h += '
';
['stab','slash','crush'].forEach(function(m) {
h += '';
});
h += '
';
h += '
';
h += '';
h += '';
h += '
';
h += '
';
return h;
}
// collectMobAttackType reads the Atk Type widget into an array: the selected
// melee type first, then any optional ranged/science types.
function collectMobAttackType() {
var melee = 'crush';
var r = document.querySelector('input[name="mob_atktype_melee"]:checked');
if (r) melee = r.value;
var out = [melee];
var rng = document.getElementById('mob_atktype_ranged');
var sci = document.getElementById('mob_atktype_science');
if (rng && rng.checked) out.push('ranged');
if (sci && sci.checked) out.push('science');
return out;
}
function renderMobCombatCard(data) {
var sec = MOB_SECTIONS.find(function(s) { return s.id === 'combat'; });
if (!sec) return '';
var byKey = {};
sec.fields.forEach(function(f) { byKey[f[0]] = f; });
var h = '
';
h += renderMobField(sec, byKey['kind'], data, -1);
h += renderMobField(sec, byKey['aggressive'], data, -1);
h += renderMobField(sec, byKey['respawn_ticks'], data, -1);
h += '
';
h += renderMobAttackTypeWidget(data);
h += '
';
h += 'Stats';
h += '
';
var statKeys = ['stats.hp','stats.attack','stats.strength','stats.defense','stats.ranged','stats.science'];
statKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); });
h += '
';
h += '
';
h += renderMobField(sec, byKey['stats.speed'], data, -1);
h += '
';
h += '
';
var statKeys3 = ['stats.max_melee_hit','stats.max_ranged_hit','stats.max_science_hit'];
statKeys3.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); });
h += '
';
h += '
';
h += '
';
h += 'Bonuses';
h += '
';
var bonusKeys = ['stats.bonuses.attack_bonus','stats.bonuses.strength_bonus','stats.bonuses.science_bonus',
'stats.bonuses.science_percent_bonus','stats.bonuses.ranged_bonus','stats.bonuses.ranged_strength_bonus'];
bonusKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); });
h += '
';
h += '
';
h += '
';
h += 'Defenses';
h += '
';
var defKeys = ['stats.defenses.stab_defense','stats.defenses.slash_defense','stats.defenses.crush_defense',
'stats.defenses.science_defense','stats.defenses.ranged_defense'];
defKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); });
h += '
';
h += '
';
h += renderMobField(sec, byKey['stats.defenses.weakness'], data, -1);
h += renderMobField(sec, byKey['stats.defenses.weakness_percent'], data, -1);
h += '
';
h += '
';
h += '
';
h += renderMobField(sec, byKey['assassin_level'], data, -1);
h += renderMobField(sec, byKey['size'], data, -1);
h += '
';
h += '
';
h += renderMobField(sec, byKey['finishing_blow'], data, -1);
h += renderMobField(sec, byKey['damage_without'], data, -1);
h += '
';
sec.inline.forEach(function(il) {
h += ced_renderTags(sec, il, data);
});
return h;
}
// setupMobProtectedToggle hides the entire Combat card when Protected is checked.
function setupMobProtectedToggle() {
var sec = MOB_SECTIONS.find(function(s) { return s.id === 'core'; });
if (!sec) return;
var protEl = document.getElementById(fieldID(sec, 'protected', -1));
if (!protEl) return;
function apply() {
var combatCard = document.querySelector('.obj-card[data-card="combat"]');
if (combatCard) {
combatCard.style.display = protEl.checked ? '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, cardPath, fieldID, getVal);
}
// ---- Interaction-style array rendering (On Kill) ----
function renderMobArraySection(sec, data) {
if (sec.interactionStyle) {
window._mobInterVis = window._mobInterVis || {};
if (!window._mobInterVis[sec.id]) window._mobInterVis[sec.id] = {};
return ie_renderArraySection({
sec: sec,
data: data,
visMap: window._mobInterVis[sec.id],
showRemove: function(idx) { return 'mobRemoveArrayItem(\'' + sec.id + '\',' + idx + ')'; },
showField: function(secId, idx, key) { return 'mobShowInterField(\'' + secId + '\',' + idx + ',\'' + key + '\')'; },
hideField: function(secId, idx, key) { return 'mobHideInterField(\'' + secId + '\',' + idx + ',\'' + key + '\')'; },
addCondition: function(secId, idx) { return 'mobAddInterRootCondition(\'' + secId + '\',' + idx + ')'; },
addEffect: function(secId, idx) { return 'mobAddInterAction(\'' + secId + '\',' + idx + ')'; },
renderField: function(sec, f, data, idx, optStyle) { return renderMobField(sec, f, data, idx, null, optStyle); },
onAdd: function() { return 'mobAddArrayItem(\'' + sec.id + '\')'; },
});
}
return '';
}
function mobAddArrayItem(cardID) {
var sec = MOB_SECTIONS.find(function(s) { return s.id === cardID; });
if (!sec || !sec.isArray) return;
var arr = mobData[sec.path] || [];
var empty = {};
sec.fields.forEach(function(f) {
empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : '');
});
arr.push(empty);
mobData[sec.path] = arr;
renderCurrent();
}
function mobRemoveArrayItem(cardID, idx) {
var sec = MOB_SECTIONS.find(function(s) { return s.id === cardID; });
if (!sec || !sec.isArray) return;
ced_syncDOMToData(mobData);
var arr = mobData[sec.path] || [];
arr.splice(idx, 1);
renderMobEditor(mobID, mobData);
}
function mobShowInterField(secId, idx, key) {
window._mobInterVis = window._mobInterVis || {};
ie_showField(window._mobInterVis, secId, idx, key);
renderCurrent();
}
function mobAddInterRootCondition(secId, idx) {
var sec = MOB_SECTIONS.find(function(s) { return s.id === secId; });
if (!sec) return;
ie_addRootConditionGroup(sec.path, idx);
}
function mobAddInterAction(secId, idx) {
var sec = MOB_SECTIONS.find(function(s) { return s.id === secId; });
if (!sec) return;
ie_addEffectToInteraction(sec.path, idx);
}
function mobHideInterField(secId, idx, key) {
window._mobInterVis = window._mobInterVis || {};
ie_hideField(window._mobInterVis, secId, idx, key);
var sec = MOB_SECTIONS.find(function(s) { return s.id === secId; });
var secPath = sec ? sec.path : secId;
ced_syncDOMToData(mobData);
ie_syncAllInteractions(mobData, MOB_SECTIONS);
if (mobData[secPath] && mobData[secPath][idx]) {
if (key === 'condition' || key === 'action') {
delete mobData[secPath][idx][key];
} else {
mobData[secPath][idx][key] = '';
}
}
renderMobEditor(mobID, mobData);
}
// ---- Subtable rendering ----
function renderMobSubField(sec, stKey, idx, f, val) {
var fid = fieldIDSub(sec, stKey, idx, f[0]);
val = val === undefined || val === null ? '' : val;
var extraCls = '';
if (f[4] === 'narrow') extraCls = ' obj-narrow';
else if (f[4] === 'grow') extraCls = ' obj-grow';
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 + '..."';
return '';
}
if (f[2] === 'checkbox') {
return '';
}
return '';
}
function renderMobSubtable(sec, st, data) {
var p = cardPath(sec);
var sectionRoot = p ? (mobData[p] || {}) : mobData;
var raw = sectionRoot[st.key];
var arr;
if (st.single) {
arr = raw ? [raw] : [];
} else if (Array.isArray(raw)) {
arr = raw;
} else if (raw && typeof raw === 'object') {
arr = [raw];
} else {
arr = [];
}
if (st.table) {
return renderMobTableSubtable(sec, st, arr);
}
if ((sec.id === 'drops' && st.key === 'loot') || (sec.id === 'steal' && st.key === 'drops')) {
return renderMobLootSubtable(sec, st, arr);
}
var h = '
';
h += '
' + esc(st.label) + '
';
arr.forEach(function(item, idx) {
h += '
';
st.fields.forEach(function(f) {
var val = item[f[0]];
h += renderMobSubField(sec, st.key, idx, f, val);
});
if (!st.single) {
h += '';
}
h += '
';
});
if (!st.single || arr.length === 0) {
h += '';
}
h += '
';
return h;
}
function renderMobLootSubtable(sec, st, arr) {
var byKey = {};
st.fields.forEach(function(f) { byKey[f[0]] = f; });
var isSteal = sec.id === 'steal';
var rollsField = ['quantity', 'Rolls', 'number', '', 'narrow'];
var h = '