var hazardData = null; var hazardID = null; var expandedCards = {}; var addedSections = {}; var cardWidths = {}; var HAZARD_SECTIONS = [ { id: 'core', label: 'Core', always: true, fields: [ ['name', 'Name', 'text', 'toxic gas'], ['description', 'Description', 'textarea', 'A cloud of toxic gas fills the room.', 'wide'], ['warn_message', 'Warn Message', 'text', 'The air grows thick with toxic fumes...', 'wide'], ['hit_message', 'Hit Message', 'text', 'You cough and choke on the toxic gas!'], ['miss_message', 'Miss Message', 'text', 'You hold your breath and avoid the fumes.'], ['required_item', 'Required Item', 'search', 'gas_mask', '', null, 'items'], ] }, { id: 'combat', label: 'Combat', path: 'combat', detect: function(d) { return d.combat; }, fields: [ ['stats.attack', 'Attack', 'number', '5', 'narrow'], ['stats.strength', 'Strength', 'number', '5', 'narrow'], ['stats.ranged', 'Ranged', 'number', '1', 'narrow'], ['stats.science', 'Science', 'number', '1', 'narrow'], ['stats.speed', 'Speed', 'number', '5', 'narrow'], ['stats.max_melee_hit', 'Max Melee Hit', 'number', '2', 'narrow'], ['stats.max_ranged_hit', 'Max Ranged Hit', 'number', '0', 'narrow'], ['stats.max_science_hit', 'Max Science Hit', 'number', '0', 'narrow'], ['stats.bonuses.attack_bonus', 'Atk Bonus', 'number', '0', 'narrow'], ['stats.bonuses.strength_bonus', 'Str Bonus', 'number', '0', 'narrow'], ['stats.bonuses.ranged_bonus', 'Rng Bonus', 'number', '0', 'narrow'], ['stats.bonuses.ranged_strength_bonus', 'Rng Str Bonus', 'number', '0', 'narrow'], ['stats.bonuses.science_bonus', 'Sci Bonus', 'number', '0', 'narrow'], ['stats.bonuses.science_percent_bonus', 'Sci % Bonus', 'number', '0', 'narrow'], ] } ]; function initHazardEditor() { editorType = 'hazards'; editorFields = []; loadList(); if (window.location.hash) loadHazardEditor(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 loadHazardEditor(id) { hazardID = id; currentID = id; renderList(''); window.location.hash = id; API.get('/api/hazards/' + encodeURIComponent(id)).then(function(data) { hazardData = data; renderHazardEditor(id, data); }).catch(function(e) { $('#editorMain').innerHTML = '

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

'; }); } function loadItem(id) { loadHazardEditor(id); } function renderHazardEditor(id, data) { var html = renderRenameableHeader('Hazard', id); html += '
'; html += '
'; html += '
'; html += '
'; HAZARD_SECTIONS.forEach(function(sec) { if (!sec.always && sec.detect && !sec.detect(data) && !addedSections[sec.id]) return; html += renderCard(sec, data); }); html += '
'; html += '
'; html += ''; html += ''; html += renderMoveBar(); html += '
'; 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(hazardData); if (!hazardData || !hazardID) return; renderHazardEditor(hazardID, hazardData); } 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 += ''; if (!sec.always) { h += ''; } h += ''; h += '
'; if (sec.id === 'combat') { h += renderHazardCombatCard(data); } else { h += '
'; sec.fields.forEach(function(f) { h += renderField(sec, f, data, -1); }); h += '
'; if (sec.id === 'core') { h += ced_renderTagHelp('color'); } } h += '
'; h += '
'; return h; } function renderField(sec, f, data, idx) { return ced_renderField(sec, f, data, idx, null, null, cardPath, fieldID, getVal); } function toggleCardWidth(id) { cardWidths[id] = !cardWidths[id]; renderCurrent(); } function removeSection(id) { var sec = HAZARD_SECTIONS.find(function(s) { return s.id === id; }); if (!sec) return; ced_syncDOMToData(hazardData); if (sec.path) { delete hazardData[sec.path]; } delete addedSections[id]; renderHazardEditor(hazardID, hazardData); } function addSection() { var sel = $('#addSectionSelect'); var id = sel.value; if (!id) return; var sec = HAZARD_SECTIONS.find(function(s) { return s.id === id; }); if (!sec) return; if (id === 'combat') { hazardData.combat = { attack_types: ['crush'], stats: { attack: 1, strength: 0, ranged: 0, science: 0, speed: 5, max_melee_hit: 0, max_ranged_hit: 0, max_science_hit: 0, bonuses: {attack_bonus:0, strength_bonus:0, ranged_bonus:0, ranged_strength_bonus:0, science_bonus:0, science_percent_bonus:0} } }; } addedSections[id] = true; expandedCards[id] = true; renderCurrent(); } function saveHazard() { if (!ced_validateFields(HAZARD_SECTIONS, fieldID, null)) return; var out = {}; HAZARD_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.id === 'combat' && document.getElementById('hz_atktype')) { sectionObj['attack_types'] = ced_collectAtkType('hz_atktype'); hasValues = true; } if (sec.always) { Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; }); } else if (hasValues && sec.path) { out[sec.path] = ced_unflattenObj(sectionObj); } else if (hasValues && !sec.path) { Object.keys(sectionObj).forEach(function(k) { out[k] = sectionObj[k]; }); } }); ced_cleanOutput(out); out.id = hazardID; API.put('/api/hazards/' + encodeURIComponent(hazardID), out).then(function(resp) { notify('Hazard ' + hazardID + ' saved', 'success'); updateUndoBar(); if (resp && resp._raw) hazardData._raw = resp._raw; }).catch(function(e) { notify('Save failed: ' + e.message, 'error'); }); } function deleteHazard() { if (!confirm('Delete hazard "' + hazardID + '"?')) return; API.del('/api/hazards/' + encodeURIComponent(hazardID)).then(function() { notify('Hazard ' + hazardID + ' deleted', 'success'); updateUndoBar(); hazardID = null; currentID = null; hazardData = null; $('#editorMain').innerHTML = '

Deleted

'; loadList(); }).catch(function(e) { notify('Delete failed: ' + e.message, 'error'); }); } function duplicateHazard() { var baseID = hazardID + '_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(hazardData)); delete copy._raw; delete copy._path; copy.id = newID; var dir = ''; if (hazardData._path) { var parts = hazardData._path.split('/'); var parentDir = parts[parts.length - 2]; if (parentDir !== 'hazards') dir = parentDir; } API.post('/api/hazards', {id: newID}).then(function() { var putOp = API.put('/api/hazards/' + encodeURIComponent(newID), copy); if (dir) { return putOp.then(function() { return API.post('/api/hazards/move', {id: newID, dir: dir}); }); } return putOp; }).then(function() { notify('Duplicated as ' + newID, 'success'); updateUndoBar(); loadList(); loadHazardEditor(newID); }).catch(function(e) { notify('Duplicate failed: ' + e.message, 'error'); }); } function renderHazardCombatCard(data) { var sec = HAZARD_SECTIONS.find(function(s) { return s.id === 'combat'; }); if (!sec) return ''; var byKey = {}; sec.fields.forEach(function(f) { byKey[f[0]] = f; }); var h = ced_renderAtkTypeWidget(data, 'hz_atktype', 'ced_atkPillSelect'); h += '
'; h += 'Stats'; h += '
'; var statKeys = ['stats.attack','stats.strength','stats.ranged','stats.science']; statKeys.forEach(function(k) { h += renderField(sec, byKey[k], data, -1); }); h += '
'; h += '
'; h += renderField(sec, byKey['stats.speed'], data, -1); h += '
'; h += '
'; var maxKeys = ['stats.max_melee_hit','stats.max_ranged_hit','stats.max_science_hit']; maxKeys.forEach(function(k) { h += renderField(sec, byKey[k], data, -1); }); h += '
'; h += '
'; h += '
'; h += 'Bonuses'; h += '
'; var bonusKeys = ['stats.bonuses.attack_bonus','stats.bonuses.strength_bonus','stats.bonuses.ranged_bonus', 'stats.bonuses.ranged_strength_bonus','stats.bonuses.science_bonus','stats.bonuses.science_percent_bonus']; bonusKeys.forEach(function(k) { h += renderField(sec, byKey[k], data, -1); }); h += '
'; h += '
'; return h; }