var techData = null; var techID = null; var expandedCards = {}; var cardWidths = {}; var TECH_SECTIONS = [ { id: 'core', label: 'Core', always: true, fields: [ ['name', 'Name', 'text', 'Protect from Melee'], ['level', 'Level', 'number', '37', 'narrow'], ['drain_rate', 'Drain Rate', 'number', '0.5', 'narrow'], ['group', 'Group', 'text', 'accuracy_tier'], ] }, { id: 'effects', label: 'Effects', always: true, path: 'effects', fields: [ ['accuracy_percent', 'Accuracy %', 'number', '5', 'narrow'], ['strength_percent', 'Strength %', 'number', '5', 'narrow'], ['defense_percent', 'Defense %', 'number', '5', 'narrow'], ['ranged_percent', 'Ranged %', 'number', '5', 'narrow'], ['science_percent', 'Science %', 'number', '5', 'narrow'], ['damage_reduction', 'Damage Reduction', 'number', '40', 'narrow'], ['hp_regen_multi', 'HP Regen Multi', 'number', '2', 'narrow'], ['preserve_drain', 'Preserve Drain', 'number', '1', 'narrow'], ['retribution_pct', 'Retribution %', 'number', '25', 'narrow'], ] } ]; function initTechEditor() { editorType = 'techs'; editorFields = []; loadList(); if (window.location.hash) loadTechEditor(window.location.hash.substring(1)); } function cardPath(sec) { return sec.path || ''; } function getVal(data, sec, key) { return ced_getVal(data, sec, key); } function fieldID(sec, key, idx) { return ced_fieldID(sec, key, idx); } function setupSearchFields() { ced_setupSearchFields(); } function collectFieldValue(el, type) { return ced_collectFieldValue(el, type); } function loadTechEditor(id) { techID = id; currentID = id; renderList(''); window.location.hash = id; API.get('/api/techs/' + encodeURIComponent(id)).then(function(data) { techData = data; renderTechEditor(id, data); }).catch(function(e) { $('#editorMain').innerHTML = '

Failed to load: ' + esc(e.message) + '

'; }); } function loadItem(id) { loadTechEditor(id); } function renderTechEditor(id, data) { var html = renderRenameableHeader('Tech', id); html += '
'; html += '
'; html += '
'; html += '
'; TECH_SECTIONS.forEach(function(sec) { html += renderCard(sec, data); }); html += '
'; html += '
' + renderMoveBar() + '
'; html += '
'; html += ''; html += '
'; 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; }); }); setupSearchFields(); } function renderCurrent() { ced_syncDOMToData(techData); if (!techData || !techID) return; renderTechEditor(techID, techData); } function renderCard(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 += ''; h += ''; h += '
'; if (sec.id === 'core') { h += renderTechCoreCard(data); } else if (sec.id === 'effects') { h += renderTechEffectsCard(data); } else { h += '
'; sec.fields.forEach(function(f) { h += renderField(sec, f, data, -1); }); h += '
'; } h += '
'; h += '
'; return h; } function renderField(sec, f, data, idx, subPath, optStyle) { return ced_renderField(sec, f, data, idx, subPath, optStyle, cardPath, fieldID, getVal); } function toggleCardWidth(id) { cardWidths[id] = !cardWidths[id]; renderCurrent(); } // ---- Custom Core card ---- function renderTechCoreCard(data) { var sec = TECH_SECTIONS.find(function(s) { return s.id === 'core'; }); var fields = sec.fields; var h = ''; h += '
'; h += renderField(sec, fields[0], data, -1, null, 'flex:1'); h += renderField(sec, fields[1], data, -1); h += renderField(sec, fields[2], data, -1); h += '
'; h += '
'; var groupExists = data && data.group !== undefined; if (groupExists) { h += '
'; h += renderField(sec, fields[3], data, -1); h += ''; h += '
'; } else { h += '
'; h += ''; h += '
'; } h += '
'; h += '
Only one tech per Group can be active at a time.
'; h += '
Drain rate is applied per tick.
'; return h; } function addTechGroup() { ced_syncDOMToData(techData); techData.group = ''; renderTechEditor(techID, techData); } function removeTechGroup() { ced_syncDOMToData(techData); delete techData.group; renderTechEditor(techID, techData); } // ---- Custom Effects card ---- function renderTechEffectsCard(data) { var sec = TECH_SECTIONS.find(function(s) { return s.id === 'effects'; }); var eff = data.effects || {}; var h = ''; var hasActive = false; var protectType = null; if (eff.protect_melee) protectType = 'melee'; else if (eff.protect_ranged) protectType = 'ranged'; else if (eff.protect_science) protectType = 'science'; if (protectType) { hasActive = true; var protectIdx = protectType === 'melee' ? 0 : (protectType === 'ranged' ? 1 : 2); h += '
'; h += '
'; h += '
'; h += '
'; h += 'Protection'; h += '
'; ['melee', 'ranged', 'science'].forEach(function(t, i) { h += ''; }); h += '
'; h += '
'; h += '
'; h += ''; h += '
'; h += '
'; } sec.fields.forEach(function(f) { var key = f[0], label = f[1]; var val = eff[key]; if (val === undefined || val === null) return; if (f[2] === 'checkbox' && !val) return; if (val === '' || val === 0) return; hasActive = true; h += '
'; h += '
'; h += '
'; h += renderField(sec, f, data, -1); h += '
'; h += ''; h += '
'; h += '
'; }); var inactiveFields = []; sec.fields.forEach(function(f) { var key = f[0]; var val = eff[key]; var isSet = val !== undefined && val !== null; if (f[2] === 'checkbox' && !val) isSet = false; if (val === '' || val === 0) isSet = false; if (!isSet) inactiveFields.push(f); }); if (inactiveFields.length > 0 || !protectType) { if (hasActive) h += '
'; h += '
'; inactiveFields.forEach(function(f) { var key = f[0], label = f[1], type = f[2]; var dflt = type === 'checkbox' ? true : (parseFloat(f[3]) || 0); h += ''; }); if (!protectType) { h += ''; } h += '
'; } return h; } function addTechEffect(key, dflt) { ced_syncDOMToData(techData); if (!techData.effects) techData.effects = {}; techData.effects[key] = dflt; renderTechEditor(techID, techData); } function removeTechEffect(key) { ced_syncDOMToData(techData); if (techData.effects) delete techData.effects[key]; renderTechEditor(techID, techData); } function addTechProtection() { ced_syncDOMToData(techData); if (!techData.effects) techData.effects = {}; techData.effects.protect_melee = true; techData.effects.damage_reduction = 0.4; renderTechEditor(techID, techData); } function removeTechProtection() { ced_syncDOMToData(techData); if (techData.effects) { delete techData.effects.protect_melee; delete techData.effects.protect_ranged; delete techData.effects.protect_science; } renderTechEditor(techID, techData); } function techProtectPillSelect(val, btn) { ced_syncDOMToData(techData); if (!techData.effects) techData.effects = {}; delete techData.effects.protect_melee; delete techData.effects.protect_ranged; delete techData.effects.protect_science; techData.effects['protect_' + val] = true; var pill = document.getElementById('tech-protect-pill'); if (pill) { 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); } } // ---- Save / Delete / Duplicate ---- function saveTech() { if (!ced_validateFields(TECH_SECTIONS, fieldID, null)) return; ced_syncDOMToData(techData); var out = {}; TECH_SECTIONS.forEach(function(sec) { var sectionObj = {}; var hasValues = sec.always; if (sec.fields) { sec.fields.forEach(function(f) { var fid = fieldID(sec, f[0]); var el = document.getElementById(fid); if (!el) return; var val = collectFieldValue(el, f[2]); if (val !== '' && val !== false && val !== 0 && val !== null) hasValues = true; sectionObj[f[0]] = val; }); } if (sec.always && sec.path) { out[sec.path] = sectionObj; } else if (sec.always) { Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; }); } else if (hasValues && sec.path) { out[sec.path] = sectionObj; } else if (hasValues) { Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; }); } }); if (techData.effects) { if (techData.effects.protect_melee || techData.effects.protect_ranged || techData.effects.protect_science) { if (!out.effects) out.effects = {}; if (techData.effects.protect_melee) out.effects.protect_melee = true; if (techData.effects.protect_ranged) out.effects.protect_ranged = true; if (techData.effects.protect_science) out.effects.protect_science = true; } } ced_cleanOutput(out); out.id = techID; if (out.effects && Object.keys(out.effects).length === 0) delete out.effects; API.put('/api/techs/' + encodeURIComponent(techID), out).then(function(resp) { notify('Tech ' + techID + ' saved', 'success'); updateUndoBar(); if (resp && resp._raw) techData._raw = resp._raw; }).catch(function(e) { notify('Save failed: ' + e.message, 'error'); }); } function deleteTech() { if (!confirm('Delete tech "' + techID + '"?')) return; API.del('/api/techs/' + encodeURIComponent(techID)).then(function() { notify('Tech ' + techID + ' deleted', 'success'); updateUndoBar(); techID = null; currentID = null; techData = null; $('#editorMain').innerHTML = '

Deleted

'; loadList(); }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); }); } function duplicateTech() { var baseID = techID + '_copy'; var newID = baseID; var counter = 2; while (true) { var exists = false; for (var i = 0; i < allIDs.length; i++) { if (allIDs[i] === newID) { exists = true; break; } } if (!exists) break; newID = baseID + '_' + counter; counter++; } var copy = JSON.parse(JSON.stringify(techData)); delete copy._raw; delete copy._path; copy.id = newID; var dir = ''; if (techData._path) { var parts = techData._path.split('/'); var parentDir = parts[parts.length - 2]; if (parentDir !== 'techs') dir = parentDir; } API.post('/api/techs', {id: newID}).then(function() { var putOp = API.put('/api/techs/' + encodeURIComponent(newID), copy); if (dir) { return putOp.then(function() { return API.post('/api/techs/move', {id: newID, dir: dir}); }); } return putOp; }).then(function() { notify('Duplicated as ' + newID, 'success'); updateUndoBar(); loadList(); loadTechEditor(newID); }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); }