';
html += '';
html += '';
html += '';
html += '';
html += '
';
$('#editorMain').innerHTML = html;
document.querySelectorAll('details.obj-card').forEach(function(d) {
d.addEventListener('toggle', function() { expandedCards[d.getAttribute('data-card')] = d.open; });
});
ced_setupSearchFields();
setupMobProtectedToggle();
applyTaskVisibility();
MOB_SECTIONS.forEach(function(sec) {
if (sec.id === 'talk' && data.talk && data.talk.nodes) {
renderTalkTree(data);
}
});
setupMobDropViews();
}
function renderCurrent() {
ced_syncDOMToData(mobData);
ie_syncAllInteractions(mobData, MOB_SECTIONS);
if (!mobData || !mobID) return;
renderMobEditor(mobID, mobData);
}
// ---- Card rendering ----
function renderMobCard(sec, data) {
var openAttr = expandedCards[sec.id] === false ? '' : ' open';
var isFull = cardWidths[sec.id] !== undefined ? cardWidths[sec.id] : (sec.fullWidth || false);
var fullClass = isFull ? ' obj-card-full' : '';
var h = '';
h += '';
h += '';
h += '' + sec.label + '';
h += '';
if (!sec.always) {
h += '';
}
h += '';
h += '
';
if (sec.id === 'core') {
h += renderMobCoreCard(data);
} else if (sec.id === 'combat') {
h += renderMobCombatCard(data);
} else if (sec.id === 'task') {
h += renderMobTaskCard(data);
} else if (sec.id === 'drops') {
h += renderMobDropsCard(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 += ced_renderTagHelp('talk');
}
}
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 += '
';
h += renderMobField(sec, byKey['unique'], data, -1);
h += '
';
h += '
';
h += renderMobField(sec, byKey['protected'], data, -1);
h += '
';
h += '
';
h += ced_renderTagHelp('color');
return h;
}
function renderMobKindToggle(data) {
var combat = data.combat || {};
var kind = combat.kind === 'task' ? 'task' : 'combat';
var activeC = kind === 'combat';
var h = '
';
h += 'Kind';
h += '
';
h += '';
h += '';
h += '
';
return h;
}
function mobKindPillSelect(val, btn) {
var pill = document.getElementById('mob_kind_pill');
if (!pill) return;
var opts = pill.querySelectorAll('.seg-opt');
var idx = -1;
opts.forEach(function(o, i) {
if (o.getAttribute('data-v') === val) {
o.classList.add('seg-active');
idx = i;
} else {
o.classList.remove('seg-active');
}
});
pill.className = pill.className.replace(/seg-idx-\d/g, 'seg-idx-' + idx);
if (val === 'task') {
if (!mobData.combat) mobData.combat = {};
mobData.combat.kind = 'task';
if (!mobData.task) mobData.task = {verb:'work', progress_noun:'construction', complete_message:'The work is finished.'};
addedSections.task = true;
expandedCards.task = true;
} else {
if (mobData.combat) mobData.combat.kind = 'combat';
delete mobData.task;
delete addedSections.task;
}
renderCurrent();
}
function collectMobKind() {
var pill = document.getElementById('mob_kind_pill');
if (!pill) return 'combat';
var active = pill.querySelector('.seg-opt.seg-active');
return active ? active.getAttribute('data-v') : 'combat';
}
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 += renderMobKindToggle(data);
h += renderMobField(sec, byKey['respawn_ticks'], data, -1);
h += '
';
h += renderMobField(sec, byKey['aggressive'], data, -1);
h += '
';
h += '
';
h += '
';
h += ced_renderAtkTypeWidget(data, 'mob_atktype', 'ced_atkPillSelect');
h += '
';
h += '
';
h += 'Stats';
h += '
';
h += renderMobField(sec, byKey['stats.hp'], data, -1);
h += renderMobField(sec, byKey['stats.defense'], data, -1);
h += '
';
h += '
';
h += '
';
var atkStatKeys = ['stats.attack','stats.strength','stats.ranged','stats.science'];
atkStatKeys.forEach(function(k) { h += renderMobField(sec, byKey[k], data, -1); });
h += '
';
h += '
';
h += '
';
h += '
';
h += renderMobField(sec, byKey['stats.speed'], data, -1);
h += '
';
h += '
';
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 += '
';
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 += '
';
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 += '
';
var combat = data.combat || {};
var combatDescs = Array.isArray(combat.combat_descriptions) ? combat.combat_descriptions : [];
var cdgid = 'cdg_combat';
var hasCD = combatDescs.length > 0;
h += '
';
h += '
';
h += 'Combat Descriptions';
h += '';
h += '
';
h += '
';
combatDescs.forEach(function(desc) {
h += '';
});
h += '
';
h += '';
h += '
';
h += '';
return h;
}
function renderMobTaskCard(data) {
var sec = MOB_SECTIONS.find(function(s) { return s.id === 'task'; });
if (!sec) return '';
var byKey = {};
sec.fields.forEach(function(f) { byKey[f[0]] = f; });
var h = '
Task mobs can be attacked but do not fight back. The Verb and Progress Noun appear in combat messages. Complete Message prints when the task is finished.
';
h += '
';
h += renderMobField(sec, byKey['verb'], data, -1);
h += renderMobField(sec, byKey['progress_noun'], data, -1);
h += '
';
h += renderMobField(sec, byKey['complete_message'], data, -1, null, 'width:100%');
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 applyTaskVisibility() {
var isTask = mobData && mobData.combat && mobData.combat.kind === 'task';
var hidden = document.querySelectorAll('.mob-task-hide');
hidden.forEach(function(el) { el.style.display = isTask ? 'none' : ''; });
}
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 : '';
});
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 renderMobDropsCard(data) {
var h = '';
var drops = data.drops || {};
var hasRemains = drops.remains !== undefined && drops.remains !== null;
if (hasRemains) {
h += '
';
h += '';
h += '';
h += '
';
} else {
h += '';
}
var lootSec = MOB_SECTIONS.find(function(s) { return s.id === 'drops'; });
var lootSt = lootSec.subtables[0];
h += renderMobLootSubtable(lootSec, lootSt, drops.loot || []);
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 = '