diff options
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/admin/static/dropeditor.js | 66 | ||||
| -rw-r--r-- | internal/validate/checks.go | 72 |
2 files changed, 115 insertions, 23 deletions
diff --git a/internal/admin/static/dropeditor.js b/internal/admin/static/dropeditor.js index 566d696..ac1df97 100644 --- a/internal/admin/static/dropeditor.js +++ b/internal/admin/static/dropeditor.js @@ -11,10 +11,6 @@ var DROP_SECTIONS = [ ['table', 'Table', 'search', 'e.g. gem_table', '', null, 'drops'], ['weight', 'Weight', 'number', '10', 'narrow'], ['quantity', 'Quantity', 'number', '1', 'narrow'], - ['level', 'Level', 'number', '1', 'narrow'], - ['xp', 'XP', 'number', '5', 'narrow'], - ['depletes', 'Depletes', 'checkbox'], - ['success_message', 'Success Message', 'text', 'You get some %n.', 'wide'], ] } ]; @@ -113,16 +109,37 @@ function renderArraySection(sec, data) { var arr = dropData[cardPath(sec)] || []; var h = ''; arr.forEach(function(item, idx) { + var isItem = item._type !== undefined ? item._type !== 't' : !item.table; + var showKey = isItem ? 'item_id' : 'table'; + var hideKey = isItem ? 'table' : 'item_id'; + h += '<div class="obj-sub-row" data-idx="' + idx + '">'; + h += '<input type="hidden" id="' + fieldID(sec, hideKey, idx) + '" value="' + escAttr(item[hideKey] || '') + '">'; h += '<div class="obj-card-grid">'; - sec.fields.forEach(function(f) { - h += renderField(sec, f, data, idx); - }); + + h += '<div class="obj-field obj-narrow">' + + '<span style="visibility:hidden">.</span>' + + '<span class="obj-drop-kind-label">' + (isItem ? 'Item' : 'Table') + '</span>' + + '</div>'; + + var showField = sec.fields.find(function(f) { return f[0] === showKey; }); + if (showField) { + h += renderField(sec, showField, data, idx); + } + + var weightField = sec.fields.find(function(f) { return f[0] === 'weight'; }); + var qtyField = sec.fields.find(function(f) { return f[0] === 'quantity'; }); + if (weightField) h += renderField(sec, weightField, data, idx); + if (qtyField) h += renderField(sec, qtyField, data, idx); + h += '</div>'; h += '<button class="btn btn-sm btn-danger obj-sub-remove" onclick="removeArrayItem(\'' + sec.id + '\',' + idx + ')">X</button>'; h += '</div>'; }); - h += '<button class="btn btn-sm obj-sub-add" onclick="addArrayItem(\'' + sec.id + '\')">+ Add Entry</button>'; + h += '<div style="display:flex;gap:6px;padding:6px 10px">'; + h += '<button class="btn btn-sm obj-sub-add" style="flex:1;margin-top:0" onclick="addDropRow(\'' + sec.id + '\',\'item\')">+ Add Item Row</button>'; + h += '<button class="btn btn-sm obj-sub-add" style="flex:1;margin-top:0" onclick="addDropRow(\'' + sec.id + '\',\'table\')">+ Add Table Row</button>'; + h += '</div>'; return h; } @@ -130,6 +147,16 @@ function renderField(sec, f, data, idx) { return ced_renderField(sec, f, data, idx, null, null, cardPath, fieldID, getVal); } +function addDropRow(cardID, kind) { + var sec = DROP_SECTIONS.find(function(s) { return s.id === cardID; }); + if (!sec || !sec.isArray) return; + var arr = dropData[sec.path] || []; + var empty = { _type: (kind === 'table' ? 't' : 'i'), weight: 10, quantity: 0 }; + arr.push(empty); + dropData[sec.path] = arr; + renderCurrent(); +} + function toggleCard(id) { expandedCards[id] = expandedCards[id] === false ? true : false; renderCurrent(); @@ -140,19 +167,6 @@ function toggleCardWidth(id) { renderCurrent(); } -function addArrayItem(cardID) { - var sec = DROP_SECTIONS.find(function(s) { return s.id === cardID; }); - if (!sec || !sec.isArray) return; - var arr = dropData[sec.path] || []; - var empty = {}; - sec.fields.forEach(function(f) { - empty[f[0]] = f[2] === 'checkbox' ? false : (f[2] === 'number' ? 0 : ''); - }); - arr.push(empty); - dropData[sec.path] = arr; - renderCurrent(); -} - function removeArrayItem(cardID, idx) { var sec = DROP_SECTIONS.find(function(s) { return s.id === cardID; }); if (!sec || !sec.isArray) return; @@ -178,7 +192,12 @@ function saveDrop() { sec.fields.forEach(function(f) { var fid = fieldID(sec, f[0], idx); var el = document.getElementById(fid); - if (el) item[f[0]] = collectFieldValue(el, f[2]); + if (el) { + var val = collectFieldValue(el, f[2]); + if (val !== '' && val !== null && val !== undefined && val !== 0 && val !== false) { + item[f[0]] = val; + } + } }); if (Object.keys(item).length > 0) arr.push(item); }); @@ -246,6 +265,9 @@ function duplicateDrop() { delete copy._raw; delete copy._path; copy.id = newID; + if (copy.drops && Array.isArray(copy.drops)) { + copy.drops.forEach(function(e) { delete e._type; }); + } var dir = ''; if (dropData._path) { diff --git a/internal/validate/checks.go b/internal/validate/checks.go index f89e52d..bbcb820 100644 --- a/internal/validate/checks.go +++ b/internal/validate/checks.go @@ -2,6 +2,8 @@ package validate import ( "fmt" + "os" + "path/filepath" "sort" "strings" @@ -732,7 +734,24 @@ func validateDropTables(s Source) []Issue { }) continue } - for _, d := range dt.Drops { + + // Detect files that use "entries:" instead of "drops:" as the top-level list key. + // DropTableDef only reads "drops:", so "entries:" produces an empty list. + if len(dt.Drops) == 0 { + raw, rawErr := os.ReadFile(filepath.Join(s.DataDir, "drops", id+".yaml")) + if rawErr == nil { + content := string(raw) + if strings.Contains(content, "\nentries:") || strings.HasPrefix(content, "entries:") { + issues = append(issues, Issue{ + Level: "ERROR", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q: uses key 'entries:' instead of 'drops:' — entries are not loaded; rename to 'drops:'", id), + }) + } + } + } + + for i, d := range dt.Drops { if d.ItemID != "" && !itemIDs[d.ItemID] { issues = append(issues, Issue{ Level: "ERROR", @@ -749,6 +768,57 @@ func validateDropTables(s Source) []Issue { id, d.Table), }) } + + if d.ItemID != "" && d.Table != "" { + issues = append(issues, Issue{ + Level: "WARN", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q entry %d: both item_id and table set (only one is used per entry)", id, i+1), + }) + } + if d.ItemID == "" && d.Table == "" { + issues = append(issues, Issue{ + Level: "WARN", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q entry %d: neither item_id nor table set (roll produces nothing)", id, i+1), + }) + } + + if d.Depletes { + issues = append(issues, Issue{ + Level: "WARN", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q entry %d: 'depletes' is ignored on shared drop table entries; set it on the gather entry that references this table instead", id, i+1), + }) + } + if d.Level > 0 { + issues = append(issues, Issue{ + Level: "WARN", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q entry %d: 'level' is ignored on shared drop table entries; set it on the gather entry that references this table", id, i+1), + }) + } + if d.XP > 0 { + issues = append(issues, Issue{ + Level: "WARN", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q entry %d: 'xp' is ignored on shared drop table entries; set it on the gather entry that references this table", id, i+1), + }) + } + if d.Tool != "" { + issues = append(issues, Issue{ + Level: "WARN", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q entry %d: 'tool' is ignored on shared drop table entries; set it on the gather entry that references this table", id, i+1), + }) + } + if d.SuccessMessage != "" { + issues = append(issues, Issue{ + Level: "WARN", + Type: "integrity", + Message: fmt.Sprintf("Drop table %q entry %d: 'success_message' is ignored on shared drop table entries; set it on the gather entry that references this table", id, i+1), + }) + } } } |
