diff options
| author | historia <[not public]> | 2026-07-13 23:01:45 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-07-13 23:01:45 -0400 |
| commit | 5a6986a8c539364b7a166abdfb608b3cc00ecb3b (patch) | |
| tree | 0a8a199fa28173df7f97493a209b5642ef7b4817 /internal/admin/static/charactereditor.js | |
| parent | 5ea48c1f91298b42924f6865ffc30810cceec2d3 (diff) | |
| download | thehouseoficarus-5a6986a8c539364b7a166abdfb608b3cc00ecb3b.tar.gz | |
feat(admin): convert players page into characters page with basic stats/yaml
Diffstat (limited to 'internal/admin/static/charactereditor.js')
| -rw-r--r-- | internal/admin/static/charactereditor.js | 323 |
1 files changed, 323 insertions, 0 deletions
diff --git a/internal/admin/static/charactereditor.js b/internal/admin/static/charactereditor.js new file mode 100644 index 0000000..bf40f09 --- /dev/null +++ b/internal/admin/static/charactereditor.js @@ -0,0 +1,323 @@ +function esc(s) { return String(s || '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); } +function escAttr(s) { return String(s || '').replace(/&/g,'&').replace(/"/g,'"'); } + +var charList = []; +var currentView = null; +var currentChar = null; +var currentAcc = null; + +function initCharacterEditor() { + loadCharList(); + if (window.location.hash) { + var hash = window.location.hash.substring(1); + if (hash.indexOf('account:') === 0) { + loadAccount(hash.substring(8)); + } else { + loadCharacter(hash); + } + } +} + +function loadCharList() { + API.get('/api/players').then(function(data) { + charList = data || []; + renderCharList(''); + }).catch(function(e) { + notify('Failed to load characters: ' + e.message, 'error'); + }); +} + +function filterCharList(val) { + renderCharList(val); +} + +function renderCharList(filter) { + var el = $('#listEntries'); + var f = (filter || '').toLowerCase(); + var html = ''; + + var filtered = charList.filter(function(c) { + var name = (c.name || '').toLowerCase(); + var account = (c.account || '').toLowerCase(); + return !f || name.indexOf(f) >= 0 || account.indexOf(f) >= 0; + }); + + if (filtered.length === 0) { + html += '<div style="padding:12px;color:#888;font-size:12px">No characters found</div>'; + } else { + filtered.forEach(function(c) { + var active = currentChar === c.name ? ' active' : ''; + html += '<div class="item' + active + '" onclick="loadCharacter(\'' + escAttr(c.name) + '\')">'; + html += '<div style="font-weight:600">' + esc(c.name) + '</div>'; + html += '<div style="font-size:10px;color:#888;margin-top:2px">Total Lvl ' + esc(c.total_level) + ' · Room ' + esc(c.room) + '</div>'; + if (c.account) { + html += '<div style="font-size:10px;color:#aaa">' + esc(c.account) + '</div>'; + } + html += '</div>'; + }); + } + + el.innerHTML = html; +} + +function loadCharacter(name) { + currentChar = name; + currentView = 'character'; + currentAcc = null; + renderCharList(''); + window.location.hash = name; + + API.get('/api/players/' + encodeURIComponent(name)).then(function(data) { + renderCharacter(name, data); + }).catch(function(e) { + $('#editorMain').innerHTML = '<p style="color:var(--danger)">Failed to load: ' + esc(e.message) + '</p>'; + }); +} + +function renderCharacter(name, data) { + var html = '<h2 style="margin-top:0">Character: ' + esc(name) + '</h2>'; + + html += '<div class="tabs"><button class="tab active" onclick="switchTab(event,\'fields\')">Fields</button>'; + html += '<button class="tab" onclick="switchTab(event,\'yaml\')">Raw YAML</button></div>'; + + html += '<div class="tab-content" id="tabFields">'; + + html += sectionHeader('Identity'); + html += '<div class="form-grid">'; + html += fieldDisplay('Account', buildAccountLink(data.account || '-')); + html += fieldDisplay('Current Room', esc(data.room_id)); + html += fieldDisplay('HP / Max HP', esc(data.hp) + ' / ' + esc(data.max_hp)); + html += fieldDisplay('Credits', esc(data.credits)); + html += fieldDisplay('Banked Credits', esc(data.banked_credits)); + html += fieldDisplay('Combat Level', esc(data.combat_level)); + html += fieldDisplay('Total Level', esc(data.total_level)); + html += fieldDisplay('Attack Style', esc(data.attack_style)); + html += fieldDisplay('Battery', esc(data.battery)); + html += fieldDisplay('Prompt', esc(data.prompt)); + html += '</div>'; + + if (data.description) { + html += sectionHeader('Description'); + html += '<pre style="background:var(--input-bg);padding:12px;border-radius:4px;font-size:12px;border:1px solid var(--border);white-space:pre-wrap">' + esc(data.description) + '</pre>'; + } + + if (data.skill_levels && Object.keys(data.skill_levels).length > 0) { + html += sectionHeader('Skills'); + html += '<div class="form-grid">'; + var skillNames = Object.keys(data.skill_levels).sort(); + skillNames.forEach(function(sk) { + html += fieldDisplay(capitalize(sk), esc(data.skill_levels[sk])); + }); + html += '</div>'; + } + + if (data.equipment && Object.keys(data.equipment).length > 0) { + html += sectionHeader('Equipment'); + html += '<div class="form-grid">'; + var eqSlots = Object.keys(data.equipment).sort(); + eqSlots.forEach(function(slot) { + html += fieldDisplay(formatSlot(slot), esc(data.equipment[slot])); + }); + html += '</div>'; + } + + if (data.inventory && Object.keys(data.inventory).length > 0) { + html += sectionHeader('Inventory'); + html += '<div class="form-grid">'; + var invSlots = Object.keys(data.inventory).sort(byNum); + invSlots.forEach(function(sk) { + var slot = data.inventory[sk]; + var item = slot ? (slot.item_id || JSON.stringify(slot)) : '-'; + var qty = slot && slot.quantity ? ' x' + slot.quantity : ''; + html += fieldDisplay('Slot ' + esc(sk), esc(item) + qty); + }); + html += '</div>'; + } + + if (data.bank && Object.keys(data.bank).length > 0) { + html += sectionHeader('Bank'); + html += '<div class="form-grid">'; + var bankSlots = Object.keys(data.bank).sort(byNum); + bankSlots.forEach(function(sk) { + var slot = data.bank[sk]; + var item = slot ? (slot.item_id || JSON.stringify(slot)) : '-'; + var qty = slot && slot.quantity ? ' x' + slot.quantity : ''; + html += fieldDisplay('Slot ' + esc(sk), esc(item) + qty); + }); + html += '</div>'; + } + + if (data.stats) { + html += sectionHeader('Stats'); + html += '<div class="form-grid">'; + html += fieldDisplay('Total Kills', esc(data.stats.total_kills)); + html += fieldDisplay('Deaths', esc(data.stats.deaths)); + if (data.stats.rooms_visited) { + html += fieldDisplay('Unique Rooms', esc(roomVisitedCount(data.stats.rooms_visited))); + } + html += '</div>'; + + if (data.stats.mob_kills && Object.keys(data.stats.mob_kills).length > 0) { + html += sectionHeader('Mob Kills'); + html += '<div class="form-grid">'; + var mobs = Object.keys(data.stats.mob_kills).sort(); + mobs.forEach(function(mob) { + html += fieldDisplay(capitalize(mob), esc(data.stats.mob_kills[mob])); + }); + html += '</div>'; + } + } + + if (data.flags && Object.keys(data.flags).length > 0) { + html += sectionHeader('Flags'); + html += '<div class="form-grid">'; + var flagKeys = Object.keys(data.flags).sort(); + flagKeys.forEach(function(k) { + html += fieldDisplay(esc(k), esc(data.flags[k])); + }); + html += '</div>'; + } + + html += '</div>'; + + html += '<div class="tab-content" id="tabYaml" style="display:none">'; + html += '<pre>' + esc(data._raw || JSON.stringify(data, null, 2)) + '</pre>'; + html += '</div>'; + + $('#editorMain').innerHTML = html; +} + +function loadAccount(name) { + currentAcc = name; + currentView = 'account'; + currentChar = null; + renderCharList(''); + window.location.hash = 'account:' + name; + + API.get('/api/players/account/' + encodeURIComponent(name)).then(function(data) { + renderAccount(name, data); + }).catch(function(e) { + $('#editorMain').innerHTML = '<p style="color:var(--danger)">Failed to load account: ' + esc(e.message) + '</p>'; + }); +} + +function renderAccount(name, data) { + var html = '<h2 style="margin-top:0">Account: ' + esc(name) + '</h2>'; + + html += '<div class="tabs"><button class="tab active" onclick="switchTab(event,\'fields\')">Fields</button>'; + html += '<button class="tab" onclick="switchTab(event,\'yaml\')">Raw YAML</button></div>'; + + html += '<div class="tab-content" id="tabFields">'; + + html += sectionHeader('Identity'); + html += '<div class="form-grid">'; + html += fieldDisplay('Name', esc(data.name)); + html += fieldDisplay('Admin', data.admin ? '<span style="color:var(--accent)">Yes</span>' : 'No'); + html += '</div>'; + + if (data.characters && data.characters.length > 0) { + html += sectionHeader('Characters'); + html += '<div class="form-grid">'; + data.characters.forEach(function(charName) { + html += fieldDisplay('', buildCharacterLink(charName)); + }); + html += '</div>'; + } + + if (data.aliases && Object.keys(data.aliases).length > 0) { + html += sectionHeader('Aliases'); + html += '<div class="form-grid">'; + var aliasKeys = Object.keys(data.aliases).sort(); + aliasKeys.forEach(function(k) { + html += fieldDisplay(esc(k), esc(data.aliases[k])); + }); + html += '</div>'; + } + + if (data.colors && Object.keys(data.colors).length > 0) { + html += sectionHeader('Colors'); + html += '<div class="form-grid">'; + var colorKeys = Object.keys(data.colors).sort(); + colorKeys.forEach(function(k) { + html += fieldDisplay(esc(k), esc(data.colors[k])); + }); + html += '</div>'; + } + + if (data.options && Object.keys(data.options).length > 0) { + html += sectionHeader('Options'); + html += '<div class="form-grid">'; + var optKeys = Object.keys(data.options).sort(); + optKeys.forEach(function(k) { + html += fieldDisplay(esc(k), esc(data.options[k])); + }); + html += '</div>'; + } + + html += '</div>'; + + html += '<div class="tab-content" id="tabYaml" style="display:none">'; + html += '<pre>' + esc(data._raw || JSON.stringify(data, null, 2)) + '</pre>'; + html += '</div>'; + + $('#editorMain').innerHTML = html; +} + +function switchTab(e, tab) { + e.target.parentElement.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); }); + e.target.classList.add('active'); + var tabs = $('#editorMain').querySelectorAll('.tab-content'); + tabs.forEach(function(t) { t.style.display = 'none'; }); + var target = document.getElementById('tab' + tab.charAt(0).toUpperCase() + tab.slice(1)); + if (target) target.style.display = 'block'; +} + +function buildAccountLink(accountName) { + if (!accountName || accountName === '-') return esc(accountName); + return '<a href="javascript:void(0)" onclick="loadAccount(\'' + escAttr(accountName) + '\')" class="entity-link">' + esc(accountName) + '</a>'; +} + +function buildCharacterLink(charName) { + return '<a href="javascript:void(0)" onclick="loadCharacter(\'' + escAttr(charName) + '\')" class="entity-link">' + esc(charName) + '</a>'; +} + +function sectionHeader(label) { + return '<h3 style="margin:16px 0 8px 0;color:var(--accent);border-bottom:1px solid var(--border);padding-bottom:4px;font-size:13px;text-transform:uppercase;letter-spacing:0.5px">' + esc(label) + '</h3>'; +} + +function fieldDisplay(label, value) { + if (label) { + return '<div class="form-group"><label>' + label + '</label><span style="font-size:12px;word-break:break-all">' + value + '</span></div>'; + } + return '<div class="form-group"><span style="font-size:12px;word-break:break-all">' + value + '</span></div>'; +} + +function capitalize(s) { + var str = String(s); + return str.charAt(0).toUpperCase() + str.slice(1).replace(/_/g, ' '); +} + +function formatSlot(slot) { + return capitalize(String(slot)); +} + +function byNum(a, b) { + return parseInt(a) - parseInt(b); +} + +function roomVisitedCount(base64Str) { + if (typeof base64Str !== 'string' || base64Str.length === 0) return '0'; + try { + var binary = atob(base64Str); + var count = 0; + for (var i = 0; i < binary.length; i++) { + var byte = binary.charCodeAt(i); + for (var bit = 0; bit < 8; bit++) { + if (byte & (1 << bit)) count++; + } + } + return count; + } catch (e) { + return base64Str.length > 40 ? base64Str.substring(0, 40) + '...' : base64Str; + } +} |
