Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions _datafiles/html/admin/items-api.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,26 @@ <h1>Items API Reference</h1>

<div class="api-list">

<details class="api-entry">
<summary>
<span class="method method-get">GET</span>
<span class="api-path">/admin/api/v1/items/equip-slots</span>
<span class="api-desc">Return ordered equipment slot names</span>
<span class="perm-badge">items.read</span>
</summary>
<div class="api-body">
<p>Returns the ordered list of equipment slot names as strings, sourced from the server's canonical slot definition. Use this to build slot UIs dynamically instead of hard-coding slot names.</p>
<div class="curl-block"><span class="kw">curl</span> <span class="flag">-s</span> <span class="flag">-u</span> <span class="str">admin:password</span> \
<span class="str">http://{{.CONFIG.FilePaths.WebDomain}}/admin/api/v1/items/equip-slots</span></div>
<div class="response-examples">
<details class="resp-entry">
<summary><span class="method resp-label status-ok">200</span> <span class="resp-label">Success</span></summary>
<div class="resp-body"><div class="curl-block">{"success":true,"data":["weapon","offhand","head","neck","body","belt","gloves","ring","legs","feet"]}</div></div>
</details>
</div>
</div>
</details>

<details class="api-entry">
<summary>
<span class="method method-get">GET</span>
Expand Down
105 changes: 41 additions & 64 deletions _datafiles/html/admin/mobs.html
Original file line number Diff line number Diff line change
Expand Up @@ -421,46 +421,7 @@ <h2 id="editor-title">Mob Editor</h2>

<div class="section-title" style="margin-top:0.5rem;">Equipment</div>

<div class="field">
<label>Weapon</label>
<div class="shop-picker-wrap" id="f-eq-weapon" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-weapon')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-weapon')">&times;</button></div>
</div>
<div class="field">
<label>Offhand</label>
<div class="shop-picker-wrap" id="f-eq-offhand" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-offhand')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-offhand')">&times;</button></div>
</div>
<div class="field">
<label>Head</label>
<div class="shop-picker-wrap" id="f-eq-head" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-head')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-head')">&times;</button></div>
</div>
<div class="field">
<label>Neck</label>
<div class="shop-picker-wrap" id="f-eq-neck" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-neck')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-neck')">&times;</button></div>
</div>
<div class="field">
<label>Body</label>
<div class="shop-picker-wrap" id="f-eq-body" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-body')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-body')">&times;</button></div>
</div>
<div class="field">
<label>Belt</label>
<div class="shop-picker-wrap" id="f-eq-belt" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-belt')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-belt')">&times;</button></div>
</div>
<div class="field">
<label>Gloves</label>
<div class="shop-picker-wrap" id="f-eq-gloves" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-gloves')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-gloves')">&times;</button></div>
</div>
<div class="field">
<label>Ring</label>
<div class="shop-picker-wrap" id="f-eq-ring" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-ring')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-ring')">&times;</button></div>
</div>
<div class="field">
<label>Legs</label>
<div class="shop-picker-wrap" id="f-eq-legs" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-legs')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-legs')">&times;</button></div>
</div>
<div class="field">
<label>Feet</label>
<div class="shop-picker-wrap" id="f-eq-feet" data-value="0"><span class="shop-picker-display empty">nothing</span><button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip('f-eq-feet')">Pick&hellip;</button><button type="button" class="btn-icon" title="Clear" onclick="clearEquip('f-eq-feet')">&times;</button></div>
</div>
<div id="equip-slots-grid"></div>

<div class="section-title" style="margin-top:0.5rem;">Inventory</div>

Expand Down Expand Up @@ -530,11 +491,12 @@ <h2 id="editor-title">Mob Editor</h2>
let scriptSync = null;

async function init() {
const [mobsRes, racesRes, buffsRes, itemsRes] = await AdminAPI.all([
const [mobsRes, racesRes, buffsRes, itemsRes, slotsRes] = await AdminAPI.all([
AdminAPI.get('/admin/api/v1/mobs'),
AdminAPI.get('/admin/api/v1/races'),
AdminAPI.get('/admin/api/v1/buffs'),
AdminAPI.get('/admin/api/v1/items'),
AdminAPI.get('/admin/api/v1/items/equip-slots'),
]);

if (!mobsRes.ok) { showStatus('Failed to load mobs: ' + mobsRes.error, false); return; }
Expand All @@ -552,6 +514,10 @@ <h2 id="editor-title">Mob Editor</h2>
allItems = (itemsRes.data.data || []).sort((a, b) => a.ItemId - b.ItemId);
populateEquipSelects(allItems);
}
if (slotsRes.ok) {
EQ_SLOTS = slotsRes.data.data || [];
}
buildEquipSlotsGrid();

populateZoneFilter();
renderList(allMobs);
Expand All @@ -576,7 +542,28 @@ <h2 id="editor-title">Mob Editor</h2>
// Equipment slots now use the Picker - no pre-population needed.
}

const EQ_SLOTS = ['weapon','offhand','head','neck','body','belt','gloves','ring','legs','feet'];
let EQ_SLOTS = [];

function buildEquipSlotsGrid() {
const grid = document.getElementById('equip-slots-grid');
if (!grid) return;
grid.innerHTML = '';
grid.style.display = 'contents';
for (const slot of EQ_SLOTS) {
const label = slot.charAt(0).toUpperCase() + slot.slice(1);
const id = 'f-eq-' + slot;
const field = document.createElement('div');
field.className = 'field';
field.innerHTML =
'<label>' + label + '</label>' +
'<div class="shop-picker-wrap" id="' + id + '" data-value="0">' +
'<span class="shop-picker-display empty">nothing</span>' +
'<button type="button" class="btn-add-sm" style="margin-top:0" onclick="pickEquip(\'' + id + '\')">Pick&hellip;</button>' +
'<button type="button" class="btn-icon" title="Clear" onclick="clearEquip(\'' + id + '\')">\u00d7</button>' +
'</div>';
grid.appendChild(field);
}
}

function applyRaceEquipDisabled(raceId) {
const race = allRaces[String(raceId)];
Expand Down Expand Up @@ -794,16 +781,10 @@ <h2 id="editor-title">Mob Editor</h2>

// Equipment
const eq = ch.Equipment || {};
setEquipSlot('f-eq-weapon', eqItemId(eq.Weapon));
setEquipSlot('f-eq-offhand', eqItemId(eq.Offhand));
setEquipSlot('f-eq-head', eqItemId(eq.Head));
setEquipSlot('f-eq-neck', eqItemId(eq.Neck));
setEquipSlot('f-eq-body', eqItemId(eq.Body));
setEquipSlot('f-eq-belt', eqItemId(eq.Belt));
setEquipSlot('f-eq-gloves', eqItemId(eq.Gloves));
setEquipSlot('f-eq-ring', eqItemId(eq.Ring));
setEquipSlot('f-eq-legs', eqItemId(eq.Legs));
setEquipSlot('f-eq-feet', eqItemId(eq.Feet));
for (const slot of EQ_SLOTS) {
const key = slot.charAt(0).toUpperCase() + slot.slice(1);
setEquipSlot('f-eq-' + slot, eqItemId(eq[key]));
}

// Inventory items
renderItemRows(ch.Items || []);
Expand Down Expand Up @@ -1341,18 +1322,14 @@ <h2 id="editor-title">Mob Editor</h2>
Mysticism: { Training: parseInt(document.getElementById('f-stat-mysticism').value, 10) || 0 },
Perception: { Training: parseInt(document.getElementById('f-stat-perception').value, 10) || 0 },
},
Equipment: {
Weapon: eqSlot('f-eq-weapon'),
Offhand: eqSlot('f-eq-offhand'),
Head: eqSlot('f-eq-head'),
Neck: eqSlot('f-eq-neck'),
Body: eqSlot('f-eq-body'),
Belt: eqSlot('f-eq-belt'),
Gloves: eqSlot('f-eq-gloves'),
Ring: eqSlot('f-eq-ring'),
Legs: eqSlot('f-eq-legs'),
Feet: eqSlot('f-eq-feet'),
},
Equipment: (function () {
const eq = {};
for (const slot of EQ_SLOTS) {
const key = slot.charAt(0).toUpperCase() + slot.slice(1);
eq[key] = eqSlot('f-eq-' + slot);
}
return eq;
}()),
Items: collectItems(),
Shop: collectShop(),
SpellBook: collectSpellBook(),
Expand Down
32 changes: 21 additions & 11 deletions _datafiles/html/admin/races.html
Original file line number Diff line number Diff line change
Expand Up @@ -187,16 +187,6 @@ <h1>Races</h1>
<div class="section-title">Disabled Equipment Slots</div>
<div class="field-hint span2" style="grid-column:1/-1; margin-bottom:0.5rem;">Click a slot to toggle it. Dark slots are <strong>disabled</strong> for this race.</div>
<div class="slots-grid" id="slotsGrid">
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="weapon" /> Weapon</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="offhand" /> Off-hand</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="head" /> Head</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="neck" /> Neck</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="body" /> Body</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="belt" /> Belt</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="gloves" /> Gloves</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="ring" /> Ring</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="legs" /> Legs</label>
<label class="slot-item" onclick="toggleSlot(this)"><input type="checkbox" class="slot-cb" value="feet" /> Feet</label>
</div>

<div class="btn-row">
Expand All @@ -215,9 +205,15 @@ <h1>Races</h1>

// ---- Load ----
async function loadRaces() {
const res = await AdminAPI.get('/admin/api/v1/races');
const [res, slotsRes] = await AdminAPI.all([
AdminAPI.get('/admin/api/v1/races'),
AdminAPI.get('/admin/api/v1/items/equip-slots'),
]);
if (!res.ok) { console.error('Failed to load races:', res.error); return; }
racesData = (res.data && res.data.data) || {};
if (slotsRes.ok) {
buildSlotsGrid(slotsRes.data.data || []);
}
renderList();
DiceRollHelper.attach(document.getElementById('fDiceRoll'));
const hashId = location.hash.slice(1);
Expand Down Expand Up @@ -410,6 +406,20 @@ <h1>Races</h1>
if (type === 'success') setTimeout(() => { bar.className = 'status-bar'; }, 3000);
}

function buildSlotsGrid(slots) {
const grid = document.getElementById('slotsGrid');
if (!grid) return;
grid.innerHTML = '';
for (const slot of slots) {
const label = slot.charAt(0).toUpperCase() + slot.slice(1);
const lbl = document.createElement('label');
lbl.className = 'slot-item';
lbl.setAttribute('onclick', 'toggleSlot(this)');
lbl.innerHTML = '<input type="checkbox" class="slot-cb" value="' + escAttr(slot) + '" /> ' + escHtml(label);
grid.appendChild(lbl);
}
}

function toggleSlot(label) {
const cb = label.querySelector('.slot-cb');
cb.checked = !cb.checked;
Expand Down
Loading
Loading