fix(dashboard): coins, universe score, imagination, reputation and life from the locale

Pages label the game's currencies and stats with the client's words
(UI_COINS, UI_UNIVERSE_SCORE, UI_IMAGINATION, UI_REPUTATION, UI_HEALTH)
through game.terms and GameText.term instead of writing them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-29 22:06:37 -05:00
parent ade116c985
commit 597269b4c5
12 changed files with 41 additions and 41 deletions

View File

@@ -27,9 +27,9 @@
}).join(''); }).join('');
var r = m.rewards, rewards = []; var r = m.rewards, rewards = [];
if (r) { if (r) {
if (r.coins) rewards.push(nf.format(r.coins) + ' coins'); if (r.coins) rewards.push(nf.format(r.coins) + ' ' + GameText.term('coins'));
if (r.uscore) rewards.push(nf.format(r.uscore) + ' U-score'); if (r.uscore) rewards.push(nf.format(r.uscore) + ' ' + GameText.term('uscore'));
if (r.reputation) rewards.push(nf.format(r.reputation) + ' reputation'); if (r.reputation) rewards.push(nf.format(r.reputation) + ' ' + GameText.term('reputation'));
r.items.forEach(function (i) { rewards.push(nf.format(i.count) + 'x ' + (i.name || 'LOT ' + i.lot)); }); r.items.forEach(function (i) { rewards.push(nf.format(i.count) + 'x ' + (i.name || 'LOT ' + i.lot)); });
} }
return (tasks ? '<ul class="small mb-1">' + tasks + '</ul>' : '') + return (tasks ? '<ul class="small mb-1">' + tasks + '</ul>' : '') +

View File

@@ -62,7 +62,7 @@
function describeChanges() { function describeChanges() {
var body = buildEdit(), parts = []; var body = buildEdit(), parts = [];
['coins', 'uscore', 'level'].forEach(function (k) { if (body[k] !== undefined) parts.push(k === 'uscore' ? 'U-score' : k); }); ['coins', 'uscore', 'level'].forEach(function (k) { if (body[k] !== undefined) parts.push(k === 'level' ? k : GameText.term(k)); });
if (body.counts) parts.push(Object.keys(body.counts).length + ' count(s)'); if (body.counts) parts.push(Object.keys(body.counts).length + ' count(s)');
if (body.remove) parts.push(body.remove.length + ' removed'); if (body.remove) parts.push(body.remove.length + ' removed');
if (body.add) parts.push(body.add.length + ' added'); if (body.add) parts.push(body.add.length + ' added');
@@ -148,8 +148,8 @@
function compare(before, after) { function compare(before, after) {
var rows = []; var rows = [];
[['coins', 'Coins'], ['uscore', 'U-score'], ['level', 'Level'], ['missions_done', 'Missions done']].forEach(function (f) { [['coins', GameText.term('coins')], ['uscore', GameText.term('uscore')], ['level', 'Level'], ['missions_done', 'Missions done']].forEach(function (f) {
if (before[f[0]] !== after[f[0]]) rows.push('<tr><td>' + f[1] + '</td><td>' + nf.format(before[f[0]]) + '</td><td>' + nf.format(after[f[0]]) + '</td></tr>'); if (before[f[0]] !== after[f[0]]) rows.push('<tr><td>' + esc(f[1]) + '</td><td>' + nf.format(before[f[0]]) + '</td><td>' + nf.format(after[f[0]]) + '</td></tr>');
}); });
var a = itemsById(before), b = itemsById(after); var a = itemsById(before), b = itemsById(after);
Object.keys(a).forEach(function (id) { Object.keys(a).forEach(function (id) {

View File

@@ -495,14 +495,14 @@
switch (type) { switch (type) {
case 'treasure_hunt': case 'treasure_hunt':
return '<div class="mb-2"><label class="form-label small" for="evp-' + i + '-lot">Treasure</label><input class="form-control form-control-sm" id="evp-' + i + '-lot" data-search="treasure" data-p="' + i + '" data-f="config.lot" data-value="' + esc(c.lot || '') + '" value="' + esc(c.lot ? 'LOT ' + c.lot : '') + '" placeholder="Search objects"></div>' + return '<div class="mb-2"><label class="form-label small" for="evp-' + i + '-lot">Treasure</label><input class="form-control form-control-sm" id="evp-' + i + '-lot" data-search="treasure" data-p="' + i + '" data-f="config.lot" data-value="' + esc(c.lot || '') + '" value="' + esc(c.lot ? 'LOT ' + c.lot : '') + '" placeholder="Search objects"></div>' +
'<div class="row g-2">' + ['count|Treasures|1', 'coins|Coins per find|0', 'radius|Pickup distance|2'].map(function (x) { '<div class="row g-2">' + ['count|Treasures|1', 'coins|' + esc(GameText.term('coins')) + ' per find|0', 'radius|Pickup distance|2'].map(function (x) {
var bits = x.split('|'); var bits = x.split('|');
return '<div class="col-4">' + field(i, 'config.' + bits[0], bits[1], c[bits[0]], 'type="number" min="' + bits[2] + '" step="any"') + '</div>'; return '<div class="col-4">' + field(i, 'config.' + bits[0], bits[1], c[bits[0]], 'type="number" min="' + bits[2] + '" step="any"') + '</div>';
}).join('') + '</div>' + }).join('') + '</div>' +
'<div class="row g-2"><div class="col-8"><label class="form-label small" for="evp-' + i + '-item">Item reward <span class="text-body-secondary">(optional)</span></label><input class="form-control form-control-sm" id="evp-' + i + '-item" data-search="item" data-p="' + i + '" data-f="config.itemLot" data-value="' + esc(c.itemLot || '') + '" value="' + esc(c.itemLot ? 'LOT ' + c.itemLot : '') + '" placeholder="Search items"></div>' + '<div class="row g-2"><div class="col-8"><label class="form-label small" for="evp-' + i + '-item">Item reward <span class="text-body-secondary">(optional)</span></label><input class="form-control form-control-sm" id="evp-' + i + '-item" data-search="item" data-p="' + i + '" data-f="config.itemLot" data-value="' + esc(c.itemLot || '') + '" value="' + esc(c.itemLot ? 'LOT ' + c.itemLot : '') + '" placeholder="Search items"></div>' +
'<div class="col-4">' + field(i, 'config.itemCount', 'Count', c.itemCount, 'type="number" min="1" max="999"') + '</div></div>'; '<div class="col-4">' + field(i, 'config.itemCount', 'Count', c.itemCount, 'type="number" min="1" max="999"') + '</div></div>';
case 'bonus': case 'bonus':
return '<div class="row g-2">' + ['coins|Coins ×', 'uscore|U-score ×', 'lootChance|Loot chance ×'].map(function (x) { return '<div class="row g-2">' + ['coins|' + esc(GameText.term('coins')) + ' ×', 'uscore|' + esc(GameText.term('uscore')) + ' ×', 'lootChance|Loot chance ×'].map(function (x) {
var bits = x.split('|'); var bits = x.split('|');
return '<div class="col-4">' + field(i, 'config.' + bits[0], bits[1], c[bits[0]], 'type="number" min="1" step="0.1"') + '</div>'; return '<div class="col-4">' + field(i, 'config.' + bits[0], bits[1], c[bits[0]], 'type="number" min="1" step="0.1"') + '</div>';
}).join('') + '</div>'; }).join('') + '</div>';

View File

@@ -17,7 +17,7 @@
var set = o ? esc(o.price) + ' coins' + (o.periodDays ? ' / ' + esc(o.periodDays) + ' days' : '') + var set = o ? esc(o.price) + ' coins' + (o.periodDays ? ' / ' + esc(o.periodDays) + ' days' : '') +
'<div class="small text-body-secondary">' + esc(o.updated_by) + ', ' + esc(fmt.unix(o.updated_at)) + '</div>' : '<span class="text-body-secondary">Template</span>'; '<div class="small text-body-secondary">' + esc(o.updated_by) + ', ' + esc(fmt.unix(o.updated_at)) + '</div>' : '<span class="text-body-secondary">Template</span>';
var edit = canManage ? '<form class="d-flex gap-1 justify-content-end" data-map="' + esc(w.mapId) + '">' + var edit = canManage ? '<form class="d-flex gap-1 justify-content-end" data-map="' + esc(w.mapId) + '">' +
'<input type="number" class="form-control form-control-sm" style="width:7rem" min="0" name="price" placeholder="Coins" value="' + (o ? esc(o.price) : '') + '" aria-label="Price">' + '<input type="number" class="form-control form-control-sm" style="width:7rem" min="0" name="price" placeholder="' + esc(GameText.term('coins')) + '" value="' + (o ? esc(o.price) : '') + '" aria-label="Price">' +
'<input type="number" class="form-control form-control-sm" style="width:6rem" min="0" name="days" placeholder="Days" value="' + (o && o.periodDays ? esc(o.periodDays) : '') + '" aria-label="Period in days">' + '<input type="number" class="form-control form-control-sm" style="width:6rem" min="0" name="days" placeholder="Days" value="' + (o && o.periodDays ? esc(o.periodDays) : '') + '" aria-label="Period in days">' +
'<button type="submit" class="btn btn-sm btn-primary">Save</button>' + '<button type="submit" class="btn btn-sm btn-primary">Save</button>' +
(o ? '<button type="button" class="btn btn-sm btn-outline-secondary" data-reset="' + esc(w.mapId) + '">Template</button>' : '') + '</form>' : ''; (o ? '<button type="button" class="btn btn-sm btn-outline-secondary" data-reset="' + esc(w.mapId) + '">Template</button>' : '') + '</form>' : '';

View File

@@ -246,8 +246,8 @@
sourceTable(document.getElementById(ids.sources), summary, labels); sourceTable(document.getElementById(ids.sources), summary, labels);
} }
var COIN_LABELS = { title: 'Coins', up: 'Coins gained', down: 'Coins spent' }; var COIN_LABELS = { title: GameText.term('coins'), up: GameText.term('coins') + ' gained', down: GameText.term('coins') + ' spent' };
var USCORE_LABELS = { title: 'U-score', up: 'U-score gained', down: 'U-score lost' }; var USCORE_LABELS = { title: GameText.term('uscore'), up: GameText.term('uscore') + ' gained', down: GameText.term('uscore') + ' lost' };
var ITEM_LABELS = { title: 'Items', up: 'Items created', down: 'Items destroyed' }; var ITEM_LABELS = { title: 'Items', up: 'Items created', down: 'Items destroyed' };
function loadCoins() { function loadCoins() {
@@ -575,7 +575,7 @@
// ---- Saved views ---- // ---- Saved views ----
var TAB_TARGETS = { coins: '#tabCoins', uscore: '#tabUScore', items: '#tabItems', activity: '#tabActivity', map: '#tabMap', transfers: '#tabTransfers', trace: '#tabTrace', dupes: '#tabDupes', flags: '#tabFlags' }; var TAB_TARGETS = { coins: '#tabCoins', uscore: '#tabUScore', items: '#tabItems', activity: '#tabActivity', map: '#tabMap', transfers: '#tabTransfers', trace: '#tabTrace', dupes: '#tabDupes', flags: '#tabFlags' };
var TAB_NAMES = { coins: 'Coins', uscore: 'U-Score', items: 'Items', activity: 'Activity', map: 'World Map', transfers: 'Trades & Mail', trace: 'Trace', dupes: 'Duplicates', flags: 'Flags' }; var TAB_NAMES = { coins: GameText.term('coins'), uscore: GameText.term('uscore'), items: 'Items', activity: 'Activity', map: 'World Map', transfers: 'Trades & Mail', trace: 'Trace', dupes: 'Duplicates', flags: 'Flags' };
var views = [], emailReady = false; var views = [], emailReady = false;
function currentTab() { function currentTab() {
@@ -699,7 +699,7 @@
transfersTable = serverTable('#transfersTable', '/api/reports/transfers', [ transfersTable = serverTable('#transfersTable', '/api/reports/transfers', [
{ data: 'time', orderable: false, render: function (d) { return esc(fmt.unix(d)); } }, { data: 'time', orderable: false, render: function (d) { return esc(fmt.unix(d)); } },
{ data: 'method', orderable: false, render: function (d) { return methodBadge(d); } }, { data: 'method', orderable: false, render: function (d) { return methodBadge(d); } },
{ data: 'lot', orderable: false, render: function (d, t, row) { return d ? itemLink(d, row.name) : '<span class="text-body-secondary">Coins</span>'; } }, { data: 'lot', orderable: false, render: function (d, t, row) { return d ? itemLink(d, row.name) : '<span class="text-body-secondary">' + esc(GameText.term('coins')) + '</span>'; } },
{ data: 'count', orderable: false, className: 'text-end', render: function (d) { return d ? num(d) : ''; } }, { data: 'count', orderable: false, className: 'text-end', render: function (d) { return d ? num(d) : ''; } },
{ data: 'coins', orderable: false, className: 'text-end', render: function (d) { return d ? num(d) : ''; } }, { data: 'coins', orderable: false, className: 'text-end', render: function (d) { return d ? num(d) : ''; } },
{ data: 'from_character', orderable: false, render: function (d, t, row) { return charLink(d, row.from_name); } }, { data: 'from_character', orderable: false, render: function (d, t, row) { return charLink(d, row.from_name); } },
@@ -852,7 +852,7 @@
el.innerHTML = '<p class="text-body-secondary mb-0">Nothing recorded.</p>'; el.innerHTML = '<p class="text-body-secondary mb-0">Nothing recorded.</p>';
} else { } else {
el.innerHTML = '<table class="table table-sm align-middle mb-0"><thead><tr><th>Time</th><th>How</th><th>From</th><th>To</th><th>Zone</th>' + el.innerHTML = '<table class="table table-sm align-middle mb-0"><thead><tr><th>Time</th><th>How</th><th>From</th><th>To</th><th>Zone</th>' +
'<th class="text-end">Count</th><th class="text-end">Coins</th><th>Old ID → new ID</th></tr></thead><tbody>' + '<th class="text-end">Count</th><th class="text-end">' + esc(GameText.term('coins')) + '</th><th>Old ID → new ID</th></tr></thead><tbody>' +
history.map(function (h) { history.map(function (h) {
var marks = []; var marks = [];
if (h.role === 'BRANCH') marks.push('<span class="badge text-bg-light border" title="Another part of a stack this item came from">other part</span>'); if (h.role === 'BRANCH') marks.push('<span class="badge text-bg-light border" title="Another part of a stack this item came from">other part</span>');

View File

@@ -62,7 +62,7 @@
<fieldset class="border rounded p-3 mb-3"> <fieldset class="border rounded p-3 mb-3">
<legend class="fs-6 float-none w-auto px-1 mb-0">Rewards for everyone who helped</legend> <legend class="fs-6 float-none w-auto px-1 mb-0">Rewards for everyone who helped</legend>
<div class="row g-2"> <div class="row g-2">
<div class="col-sm-4"><label class="form-label small" for="chCoins">Coins</label><input type="number" class="form-control form-control-sm" id="chCoins" min="0" max="1000000" value="0"></div> <div class="col-sm-4"><label class="form-label small" for="chCoins">{{ game.terms.coins }}</label><input type="number" class="form-control form-control-sm" id="chCoins" min="0" max="1000000" value="0"></div>
<div class="col-sm-4"><label class="form-label small" for="chMin">Needs at least</label><input type="number" class="form-control form-control-sm" id="chMin" min="1" value="1"></div> <div class="col-sm-4"><label class="form-label small" for="chMin">Needs at least</label><input type="number" class="form-control form-control-sm" id="chMin" min="1" value="1"></div>
</div> </div>
<div class="mt-2"><label class="form-label small" for="chItemsSearch">Items (up to 5; one mail each)</label><div data-picker="item" data-multi="5" id="chItems"></div> <div class="mt-2"><label class="form-label small" for="chItemsSearch">Items (up to 5; one mail each)</label><div data-picker="item" data-multi="5" id="chItems"></div>

View File

@@ -152,19 +152,19 @@
</div> </div>
<div class="col-6 col-md-3"> <div class="col-6 col-md-3">
<div class="card text-center"><div class="card-body py-3"> <div class="card text-center"><div class="card-body py-3">
<div class="text-body-secondary small text-uppercase mb-1">Coins</div> <div class="text-body-secondary small text-uppercase mb-1">{{ game.terms.coins }}</div>
<div class="fs-3 fw-bold">{{ character.stats.coins }}</div> <div class="fs-3 fw-bold">{{ character.stats.coins }}</div>
</div></div> </div></div>
</div> </div>
<div class="col-6 col-md-3"> <div class="col-6 col-md-3">
<div class="card text-center"><div class="card-body py-3"> <div class="card text-center"><div class="card-body py-3">
<div class="text-body-secondary small text-uppercase mb-1">U-Score</div> <div class="text-body-secondary small text-uppercase mb-1">{{ game.terms.uscore }}</div>
<div class="fs-3 fw-bold">{{ character.stats.universe_score }}</div> <div class="fs-3 fw-bold">{{ character.stats.universe_score }}</div>
</div></div> </div></div>
</div> </div>
<div class="col-6 col-md-3"> <div class="col-6 col-md-3">
<div class="card text-center"><div class="card-body py-3"> <div class="card text-center"><div class="card-body py-3">
<div class="text-body-secondary small text-uppercase mb-1">Reputation</div> <div class="text-body-secondary small text-uppercase mb-1">{{ game.terms.reputation }}</div>
<div class="fs-3 fw-bold">{{ character.stats.reputation }}</div> <div class="fs-3 fw-bold">{{ character.stats.reputation }}</div>
</div></div> </div></div>
</div> </div>
@@ -196,7 +196,7 @@
<div class="card-body"> <div class="card-body">
<div class="row g-3"> <div class="row g-3">
<div class="col-md-4"> <div class="col-md-4">
<div class="d-flex justify-content-between"><span>Health</span><span>{{ character.stats.health }} / {{ character.stats.max_health }}</span></div> <div class="d-flex justify-content-between"><span>{{ game.terms.life }}</span><span>{{ character.stats.health }} / {{ character.stats.max_health }}</span></div>
<div class="progress mt-1" style="height: 8px;">{% if character.stats.max_health > 0 %}<div class="progress-bar bg-danger" style="width: {{ character.stats.health / character.stats.max_health * 100 }}%"></div>{% endif %}</div> <div class="progress mt-1" style="height: 8px;">{% if character.stats.max_health > 0 %}<div class="progress-bar bg-danger" style="width: {{ character.stats.health / character.stats.max_health * 100 }}%"></div>{% endif %}</div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
@@ -204,7 +204,7 @@
<div class="progress mt-1" style="height: 8px;">{% if character.stats.max_armor > 0 %}<div class="progress-bar bg-secondary" style="width: {{ character.stats.armor / character.stats.max_armor * 100 }}%"></div>{% endif %}</div> <div class="progress mt-1" style="height: 8px;">{% if character.stats.max_armor > 0 %}<div class="progress-bar bg-secondary" style="width: {{ character.stats.armor / character.stats.max_armor * 100 }}%"></div>{% endif %}</div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="d-flex justify-content-between"><span>Imagination</span><span>{{ character.stats.imagination }} / {{ character.stats.max_imagination }}</span></div> <div class="d-flex justify-content-between"><span>{{ game.terms.imagination }}</span><span>{{ character.stats.imagination }} / {{ character.stats.max_imagination }}</span></div>
<div class="progress mt-1" style="height: 8px;">{% if character.stats.max_imagination > 0 %}<div class="progress-bar bg-primary" style="width: {{ character.stats.imagination / character.stats.max_imagination * 100 }}%"></div>{% endif %}</div> <div class="progress mt-1" style="height: 8px;">{% if character.stats.max_imagination > 0 %}<div class="progress-bar bg-primary" style="width: {{ character.stats.imagination / character.stats.max_imagination * 100 }}%"></div>{% endif %}</div>
</div> </div>
</div> </div>
@@ -319,7 +319,7 @@
<div class="modal-body"> <div class="modal-body">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-hover mb-0"> <table class="table table-striped table-hover mb-0">
<thead><tr><th>Zone</th><th>Achievements</th><th>Coins</th><th>Enemies</th></tr></thead> <thead><tr><th>Zone</th><th>Achievements</th><th>{{ game.terms.coins }}</th><th>Enemies</th></tr></thead>
<tbody> <tbody>
{% for zs in character.stats.zone_stats %} {% for zs in character.stats.zone_stats %}
<tr> <tr>
@@ -381,8 +381,8 @@
<div class="modal-body"> <div class="modal-body">
<div class="alert alert-warning small">Saving disconnects the player if they are online. The current version is kept in the history, so it can be put back.</div> <div class="alert alert-warning small">Saving disconnects the player if they are online. The current version is kept in the history, so it can be put back.</div>
<div class="row g-2 mb-3"> <div class="row g-2 mb-3">
<div class="col-sm-4"><label class="form-label small" for="editCoins">Coins</label><input type="number" min="0" max="2000000000" class="form-control" id="editCoins"></div> <div class="col-sm-4"><label class="form-label small" for="editCoins">{{ game.terms.coins }}</label><input type="number" min="0" max="2000000000" class="form-control" id="editCoins"></div>
<div class="col-sm-4"><label class="form-label small" for="editUScore">U-score</label><input type="number" min="0" max="2000000000" class="form-control" id="editUScore"></div> <div class="col-sm-4"><label class="form-label small" for="editUScore">{{ game.terms.uscore }}</label><input type="number" min="0" max="2000000000" class="form-control" id="editUScore"></div>
<div class="col-sm-4"><label class="form-label small" for="editLevel">Level</label><input type="number" min="1" max="45" class="form-control" id="editLevel"></div> <div class="col-sm-4"><label class="form-label small" for="editLevel">Level</label><input type="number" min="1" max="45" class="form-control" id="editLevel"></div>
</div> </div>
<ul class="nav nav-tabs" id="editInventoryTabs" role="tablist"></ul> <ul class="nav nav-tabs" id="editInventoryTabs" role="tablist"></ul>
@@ -530,9 +530,9 @@ document.querySelectorAll('[data-restriction]').forEach(function (input) {
function statsHtml(stats) { function statsHtml(stats) {
var lines = []; var lines = [];
if (stats.life) lines.push('+' + esc(stats.life) + ' Health'); if (stats.life) lines.push('+' + esc(stats.life) + ' ' + esc(GameText.term('life')));
if (stats.armor) lines.push('+' + esc(stats.armor) + ' Armor'); if (stats.armor) lines.push('+' + esc(stats.armor) + ' Armor');
if (stats.imagination) lines.push('+' + esc(stats.imagination) + ' Imagination'); if (stats.imagination) lines.push('+' + esc(stats.imagination) + ' ' + esc(GameText.term('imagination')));
var html = lines.length ? '<div>' + lines.join('<br>') + '</div>' : ''; var html = lines.length ? '<div>' + lines.join('<br>') + '</div>' : '';
stats.skills.forEach(function (skill) { stats.skills.forEach(function (skill) {
html += '<div class="d-flex gap-1 mt-1 text-start">' + html += '<div class="d-flex gap-1 mt-1 text-start">' +

View File

@@ -6,7 +6,7 @@
<div class="d-flex flex-wrap justify-content-between align-items-end gap-3 mb-3"> <div class="d-flex flex-wrap justify-content-between align-items-end gap-3 mb-3">
<div> <div>
<h2 class="mb-1">Live Events</h2> <h2 class="mb-1">Live Events</h2>
<p class="text-body-secondary mb-0">Start something fun in game for a while: a treasure hunt, bonus coins, U-score or loot, an invasion or a <p class="text-body-secondary mb-0">Start something fun in game for a while: a treasure hunt, bonus {{ game.terms.coins }}, {{ game.terms.uscore }} or loot, an invasion or a
celebration. It is announced in its zones, runs in every instance of them (worlds that open later join in), and everything it spawned is removed celebration. It is announced in its zones, runs in every instance of them (worlds that open later join in), and everything it spawned is removed
when it ends. Players can follow it with <code>/challenge</code>. To run one at set times, together with other things, add it to a when it ends. Players can follow it with <code>/challenge</code>. To run one at set times, together with other things, add it to a
<a href="/events">scheduled event</a>.</p> <a href="/events">scheduled event</a>.</p>
@@ -36,7 +36,7 @@
<label class="form-label" for="evType">Kind of event</label> <label class="form-label" for="evType">Kind of event</label>
<select class="form-select" id="evType"> <select class="form-select" id="evType">
<option value="treasure_hunt">Treasure hunt: hide objects for players to find</option> <option value="treasure_hunt">Treasure hunt: hide objects for players to find</option>
<option value="bonus">Bonus: more coins, U-score or loot</option> <option value="bonus">Bonus: more {{ game.terms.coins }}, {{ game.terms.uscore }} or loot</option>
<option value="invasion">Invasion: waves of enemies</option> <option value="invasion">Invasion: waves of enemies</option>
<option value="celebration">Celebration: effects on every player</option> <option value="celebration">Celebration: effects on every player</option>
</select> </select>
@@ -67,7 +67,7 @@
<div class="row g-2"> <div class="row g-2">
<div class="col-sm-3"><label class="form-label small" for="thCount">How many (per instance)</label><input type="number" class="form-control form-control-sm" id="thCount" min="1" max="50" value="10"></div> <div class="col-sm-3"><label class="form-label small" for="thCount">How many (per instance)</label><input type="number" class="form-control form-control-sm" id="thCount" min="1" max="50" value="10"></div>
<div class="col-sm-3"><label class="form-label small" for="thRadius">Pickup distance</label><input type="number" class="form-control form-control-sm" id="thRadius" min="2" max="15" value="4"></div> <div class="col-sm-3"><label class="form-label small" for="thRadius">Pickup distance</label><input type="number" class="form-control form-control-sm" id="thRadius" min="2" max="15" value="4"></div>
<div class="col-sm-3"><label class="form-label small" for="thCoins">Coins per find</label><input type="number" class="form-control form-control-sm" id="thCoins" min="0" max="100000" value="250"></div> <div class="col-sm-3"><label class="form-label small" for="thCoins">{{ game.terms.coins }} per find</label><input type="number" class="form-control form-control-sm" id="thCoins" min="0" max="100000" value="250"></div>
<div class="col-sm-3"><label class="form-label small" for="thItemCount">Items per find</label><input type="number" class="form-control form-control-sm" id="thItemCount" min="1" max="999" value="1"></div> <div class="col-sm-3"><label class="form-label small" for="thItemCount">Items per find</label><input type="number" class="form-control form-control-sm" id="thItemCount" min="1" max="999" value="1"></div>
</div> </div>
<div class="mt-2"><label class="form-label small" for="thItemSearch">Item reward <span class="text-body-secondary">(optional)</span></label><div data-picker="item" id="thItem"></div></div> <div class="mt-2"><label class="form-label small" for="thItemSearch">Item reward <span class="text-body-secondary">(optional)</span></label><div data-picker="item" id="thItem"></div></div>
@@ -79,8 +79,8 @@
<fieldset class="border rounded p-3" data-for="bonus"> <fieldset class="border rounded p-3" data-for="bonus">
<legend class="fs-6 float-none w-auto px-1 mb-0">Multipliers</legend> <legend class="fs-6 float-none w-auto px-1 mb-0">Multipliers</legend>
<div class="row g-2"> <div class="row g-2">
<div class="col-sm-4"><label class="form-label small" for="bnCoins">Coins from loot</label><input type="number" class="form-control form-control-sm" id="bnCoins" min="1" max="10" step="0.25" value="2"></div> <div class="col-sm-4"><label class="form-label small" for="bnCoins">{{ game.terms.coins }} from loot</label><input type="number" class="form-control form-control-sm" id="bnCoins" min="1" max="10" step="0.25" value="2"></div>
<div class="col-sm-4"><label class="form-label small" for="bnUscore">U-score from missions</label><input type="number" class="form-control form-control-sm" id="bnUscore" min="1" max="10" step="0.25" value="1"></div> <div class="col-sm-4"><label class="form-label small" for="bnUscore">{{ game.terms.uscore }} from missions</label><input type="number" class="form-control form-control-sm" id="bnUscore" min="1" max="10" step="0.25" value="1"></div>
<div class="col-sm-4"><label class="form-label small" for="bnLoot">Loot drop chance</label><input type="number" class="form-control form-control-sm" id="bnLoot" min="1" max="10" step="0.25" value="1"></div> <div class="col-sm-4"><label class="form-label small" for="bnLoot">Loot drop chance</label><input type="number" class="form-control form-control-sm" id="bnLoot" min="1" max="10" step="0.25" value="1"></div>
</div> </div>
<p class="small text-body-secondary mb-0 mt-2">Leave the zones empty for every world. Bonuses running at once don't stack: the biggest one counts.</p> <p class="small text-body-secondary mb-0 mt-2">Leave the zones empty for every world. Bonuses running at once don't stack: the biggest one counts.</p>
@@ -132,7 +132,7 @@
if (e.type === 'invasion') return nf.format(t.kills || 0) + ' of ' + nf.format(t.spawned || 0) + ' invaders smashed, ' + nf.format(t.wavesSent || 0) + ' waves'; if (e.type === 'invasion') return nf.format(t.kills || 0) + ' of ' + nf.format(t.spawned || 0) + ' invaders smashed, ' + nf.format(t.wavesSent || 0) + ' waves';
if (e.type === 'celebration') return nf.format(t.bursts || 0) + ' bursts'; if (e.type === 'celebration') return nf.format(t.bursts || 0) + ' bursts';
var c = e.config || {}; var c = e.config || {};
return 'coins x' + c.coins + ', U-score x' + c.uscore + ', loot x' + c.lootChance; return GameText.term('coins') + ' x' + c.coins + ', ' + GameText.term('uscore') + ' x' + c.uscore + ', loot x' + c.lootChance;
} }
function instanceStatus(e, i) { function instanceStatus(e, i) {
var s = i.status || {}; var s = i.status || {};

View File

@@ -48,7 +48,7 @@
<div class="card-header"><h5 class="mb-0">Pending Properties</h5></div> <div class="card-header"><h5 class="mb-0">Pending Properties</h5></div>
<div class="card-body"> <div class="card-body">
<table id="pendingPropsTable" class="table table-striped table-hover table-stack"> <table id="pendingPropsTable" class="table table-striped table-hover table-stack">
<thead><tr><th>ID</th><th>Name</th><th>Owner</th><th>Models</th><th>Reputation</th><th>Zone</th><th>Actions</th></tr></thead> <thead><tr><th>ID</th><th>Name</th><th>Owner</th><th>Models</th><th>{{ game.terms.reputation }}</th><th>Zone</th><th>Actions</th></tr></thead>
<tbody></tbody> <tbody></tbody>
</table> </table>
</div> </div>

View File

@@ -44,7 +44,7 @@
<th>Name</th> <th>Name</th>
<th>Owner</th> <th>Owner</th>
<th>Moderation</th> <th>Moderation</th>
<th>Reputation</th> <th>{{ game.terms.reputation }}</th>
<th title="Load estimate of the placed models, reported by the owner's game client">Performance cost</th> <th title="Load estimate of the placed models, reported by the owner's game client">Performance cost</th>
<th>Zone</th> <th>Zone</th>
<th>Actions</th> <th>Actions</th>

View File

@@ -152,7 +152,7 @@ function renderDetails(p) {
field('Privacy', esc(Labels.name('privacy', p.privacy_option) || p.privacy_option)), field('Privacy', esc(Labels.name('privacy', p.privacy_option) || p.privacy_option)),
field('Moderation', p.mod_approved ? fmt.badge('Approved', 'success') : (p.rejection_reason ? fmt.badge('Rejected', 'danger') : fmt.badge('Unapproved', 'warning')) + field('Moderation', p.mod_approved ? fmt.badge('Approved', 'success') : (p.rejection_reason ? fmt.badge('Rejected', 'danger') : fmt.badge('Unapproved', 'warning')) +
(p.rejection_reason ? ' <span class="small">' + esc(p.rejection_reason) + '</span>' : '')), (p.rejection_reason ? ' <span class="small">' + esc(p.rejection_reason) + '</span>' : '')),
field('Reputation', num(p.reputation)), field(GameText.term('reputation'), num(p.reputation)),
field('Performance cost', perf ? num(perf, 2) : muted('Not reported'), 'Load estimate of the placed models, reported by the owner\'s game client after building'), field('Performance cost', perf ? num(perf, 2) : muted('Not reported'), 'Load estimate of the placed models, reported by the owner\'s game client after building'),
field('Claimed', esc(fmt.unix(p.claimed))), field('Claimed', esc(fmt.unix(p.claimed))),
field('Last updated', esc(fmt.unix(p.last_updated))), field('Last updated', esc(fmt.unix(p.last_updated))),
@@ -184,7 +184,7 @@ function renderDetails(p) {
field('Clone limit', num(t.cloneLimit)), field('Clone limit', num(t.cloneLimit)),
field('Achievement required', t.achievementRequired ? esc(t.achievementRequired) : muted('None')), field('Achievement required', t.achievementRequired ? esc(t.achievementRequired) : muted('None')),
field('Max build height', num(t.maxBuildHeight, 1)), field('Max build height', num(t.maxBuildHeight, 1)),
field('Reputation per minute', num(t.reputationPerMinute)), field(GameText.term('reputation') + ' per minute', num(t.reputationPerMinute)),
field('Zone position', [t.zoneX, t.zoneY, t.zoneZ].every((v) => typeof v === 'number') ? esc([t.zoneX, t.zoneY, t.zoneZ].map((v) => v.toFixed(1)).join(', ')) : muted('-')), field('Zone position', [t.zoneX, t.zoneY, t.zoneZ].every((v) => typeof v === 'number') ? esc([t.zoneX, t.zoneY, t.zoneZ].map((v) => v.toFixed(1)).join(', ')) : muted('-')),
field('Build area outline', Array.isArray(t.path) ? esc(t.path.length + ' points') : muted('-')) field('Build area outline', Array.isArray(t.path) ? esc(t.path.length + ' points') : muted('-'))
].concat(t.localized_description ? [ '<div class="col-12"><div class="label">Template description</div><div class="value small">' + esc(t.localized_description) + '</div></div>' ] : []).join(''); ].concat(t.localized_description ? [ '<div class="col-12"><div class="label">Template description</div><div class="value small">' + esc(t.localized_description) + '</div></div>' ] : []).join('');

View File

@@ -6,7 +6,7 @@
<div class="d-flex flex-wrap align-items-end justify-content-between gap-3 mb-3"> <div class="d-flex flex-wrap align-items-end justify-content-between gap-3 mb-3">
<div> <div>
<h2 class="mb-1">Economy</h2> <h2 class="mb-1">Economy</h2>
<p class="text-body-secondary mb-0">Where coins, U-score and items come from and go. World servers write totals every few seconds, and the page updates as they do.</p> <p class="text-body-secondary mb-0">Where {{ game.terms.coins }}, {{ game.terms.uscore }} and items come from and go. World servers write totals every few seconds, and the page updates as they do.</p>
</div> </div>
<div class="d-flex flex-wrap align-items-center gap-3" id="reportFilters"> <div class="d-flex flex-wrap align-items-center gap-3" id="reportFilters">
<div class="btn-group btn-group-sm" role="group" aria-label="Date range"> <div class="btn-group btn-group-sm" role="group" aria-label="Date range">
@@ -28,8 +28,8 @@
</div> </div>
<ul class="nav nav-tabs mb-3" role="tablist"> <ul class="nav nav-tabs mb-3" role="tablist">
<li class="nav-item" role="presentation"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tabCoins" type="button" role="tab">Coins</button></li> <li class="nav-item" role="presentation"><button class="nav-link active" data-bs-toggle="tab" data-bs-target="#tabCoins" type="button" role="tab">{{ game.terms.coins }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabUScore" type="button" role="tab">U-Score</button></li> <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabUScore" type="button" role="tab">{{ game.terms.uscore }}</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabItems" type="button" role="tab">Items</button></li> <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabItems" type="button" role="tab">Items</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabActivity" type="button" role="tab" id="activityTabBtn">Activity</button></li> <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabActivity" type="button" role="tab" id="activityTabBtn">Activity</button></li>
<li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabMap" type="button" role="tab" id="mapTabBtn">World Map</button></li> <li class="nav-item" role="presentation"><button class="nav-link" data-bs-toggle="tab" data-bs-target="#tabMap" type="button" role="tab" id="mapTabBtn">World Map</button></li>
@@ -44,7 +44,7 @@
<div class="tab-pane fade show active" id="tabCoins" role="tabpanel"> <div class="tab-pane fade show active" id="tabCoins" role="tabpanel">
<div class="row g-3 mb-3" id="coinTiles"></div> <div class="row g-3 mb-3" id="coinTiles"></div>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">Coins per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="coins"></div><a class="btn btn-sm btn-outline-secondary" data-csv="currency" download>CSV</a></div></div> <div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">{{ game.terms.coins }} per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="coins"></div><a class="btn btn-sm btn-outline-secondary" data-csv="currency" download>CSV</a></div></div>
<div class="card-body"><div class="viz" id="coinChart"></div> <div class="card-body"><div class="viz" id="coinChart"></div>
<details class="mt-2"><summary class="small text-body-secondary">Show as table</summary><div class="table-responsive mt-2" id="coinDaily"></div></details> <details class="mt-2"><summary class="small text-body-secondary">Show as table</summary><div class="table-responsive mt-2" id="coinDaily"></div></details>
</div> </div>
@@ -59,7 +59,7 @@
<div class="tab-pane fade" id="tabUScore" role="tabpanel"> <div class="tab-pane fade" id="tabUScore" role="tabpanel">
<div class="row g-3 mb-3" id="uscoreTiles"></div> <div class="row g-3 mb-3" id="uscoreTiles"></div>
<div class="card mb-3"> <div class="card mb-3">
<div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">U-score per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="uscore"></div><a class="btn btn-sm btn-outline-secondary" data-csv="uscore" download>CSV</a></div></div> <div class="card-header d-flex justify-content-between align-items-center"><h5 class="mb-0">{{ game.terms.uscore }} per day</h5><div class="d-flex align-items-center gap-3"><div class="viz-legend" data-legend="uscore"></div><a class="btn btn-sm btn-outline-secondary" data-csv="uscore" download>CSV</a></div></div>
<div class="card-body"><div class="viz" id="uscoreChart"></div> <div class="card-body"><div class="viz" id="uscoreChart"></div>
<details class="mt-2"><summary class="small text-body-secondary">Show as table</summary><div class="table-responsive mt-2" id="uscoreDaily"></div></details> <details class="mt-2"><summary class="small text-body-secondary">Show as table</summary><div class="table-responsive mt-2" id="uscoreDaily"></div></details>
</div> </div>
@@ -179,7 +179,7 @@
</form> </form>
<div class="card"><div class="card-body"> <div class="card"><div class="card-body">
<table id="transfersTable" class="table table-striped table-hover w-100"> <table id="transfersTable" class="table table-striped table-hover w-100">
<thead><tr><th>Time</th><th>How</th><th>Item</th><th>Count</th><th>Coins</th><th>From</th><th>To</th><th>Object ID <span class="fw-normal small text-body-secondary">(click to trace)</span></th></tr></thead> <thead><tr><th>Time</th><th>How</th><th>Item</th><th>Count</th><th>{{ game.terms.coins }}</th><th>From</th><th>To</th><th>Object ID <span class="fw-normal small text-body-secondary">(click to trace)</span></th></tr></thead>
<tbody></tbody> <tbody></tbody>
</table> </table>
</div></div> </div></div>