function esc(s) { return String(s || '').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 += '
';
html += sectionHeader('Identity');
html += '
';
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 += '
';
if (data.description) {
html += sectionHeader('Description');
html += '
' + esc(data.description) + '
';
}
if (data.skill_levels && Object.keys(data.skill_levels).length > 0) {
html += sectionHeader('Skills');
html += '
';
var skillNames = Object.keys(data.skill_levels).sort();
skillNames.forEach(function(sk) {
html += fieldDisplay(capitalize(sk), esc(data.skill_levels[sk]));
});
html += '
';
}
if (data.equipment && Object.keys(data.equipment).length > 0) {
html += sectionHeader('Equipment');
html += '
';
var eqSlots = Object.keys(data.equipment).sort();
eqSlots.forEach(function(slot) {
html += fieldDisplay(formatSlot(slot), esc(data.equipment[slot]));
});
html += '
';
}
if (data.inventory && Object.keys(data.inventory).length > 0) {
html += sectionHeader('Inventory');
html += '
';
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 += '
';
}
if (data.bank && Object.keys(data.bank).length > 0) {
html += sectionHeader('Bank');
html += '
';
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 += '
';
}
if (data.stats) {
html += sectionHeader('Stats');
html += '
';
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 += '
';
if (data.stats.mob_kills && Object.keys(data.stats.mob_kills).length > 0) {
html += sectionHeader('Mob Kills');
html += '
';
var mobs = Object.keys(data.stats.mob_kills).sort();
mobs.forEach(function(mob) {
html += fieldDisplay(capitalize(mob), esc(data.stats.mob_kills[mob]));
});
html += '
';
}
}
if (data.flags && Object.keys(data.flags).length > 0) {
html += sectionHeader('Flags');
html += '
';
var flagKeys = Object.keys(data.flags).sort();
flagKeys.forEach(function(k) {
html += fieldDisplay(esc(k), esc(data.flags[k]));
});
html += '
';
}
html += '
';
html += '';
html += '
' + esc(data._raw || JSON.stringify(data, null, 2)) + '
';
html += '
';
$('#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 = '';
html += sectionHeader('Identity');
html += '
';
html += fieldDisplay('Name', esc(data.name));
html += fieldDisplay('Admin', data.admin ? 'Yes' : 'No');
html += '
';
if (data.characters && data.characters.length > 0) {
html += sectionHeader('Characters');
html += '
';
data.characters.forEach(function(charName) {
html += fieldDisplay('', buildCharacterLink(charName));
});
html += '
';
}
if (data.aliases && Object.keys(data.aliases).length > 0) {
html += sectionHeader('Aliases');
html += '
';
var aliasKeys = Object.keys(data.aliases).sort();
aliasKeys.forEach(function(k) {
html += fieldDisplay(esc(k), esc(data.aliases[k]));
});
html += '
';
}
if (data.colors && Object.keys(data.colors).length > 0) {
html += sectionHeader('Colors');
html += '
';
var colorKeys = Object.keys(data.colors).sort();
colorKeys.forEach(function(k) {
html += fieldDisplay(esc(k), esc(data.colors[k]));
});
html += '
';
}
if (data.options && Object.keys(data.options).length > 0) {
html += sectionHeader('Options');
html += '
';
var optKeys = Object.keys(data.options).sort();
optKeys.forEach(function(k) {
html += fieldDisplay(esc(k), esc(data.options[k]));
});
html += '
';
}
html += '
';
html += '';
html += '
' + esc(data._raw || JSON.stringify(data, null, 2)) + '
';
html += '
';
$('#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 '