116 lines
5.2 KiB
JavaScript
116 lines
5.2 KiB
JavaScript
/*
|
||
* SCOPENET on BlueMap. Registered by the SCOPENET addon, so it runs inside BlueMap's own page: the same in a browser and
|
||
* inside the SCOPENET launcher.
|
||
*
|
||
* - Click a player on the map to see who they are (level, guild, rank, title...), read from players.json that the
|
||
* server keeps up to date next to this file.
|
||
* - Inside the launcher (this page is in an iframe) the click is handed to the launcher, which shows the full side panel
|
||
* with the actions that need a signed-in account: teleport request, message, guild invite.
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
var BASE = 'assets/scopenet/';
|
||
var PLAYER_PREFIX = 'bm-marker-bm-player-';
|
||
var embedded = window.parent && window.parent !== window;
|
||
var players = {};
|
||
var selected = null;
|
||
|
||
function esc(text) {
|
||
return String(text == null ? '' : text).replace(/[&<>"']/g, function (c) {
|
||
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
|
||
});
|
||
}
|
||
|
||
/** The uuid behind a player marker element, or null. BlueMap ids them "bm-marker-bm-player-<uuid>". */
|
||
function uuidFromId(id) {
|
||
return typeof id === 'string' && id.indexOf(PLAYER_PREFIX) === 0 ? id.substring(PLAYER_PREFIX.length) : null;
|
||
}
|
||
|
||
/** Which player marker is under this screen point? Works even where the marker ignores pointer events. */
|
||
function playerAt(x, y) {
|
||
var nodes = document.querySelectorAll('[id^="' + PLAYER_PREFIX + '"]');
|
||
for (var i = 0; i < nodes.length; i++) {
|
||
var r = nodes[i].getBoundingClientRect();
|
||
if (r.width === 0 && r.height === 0) continue;
|
||
if (x >= r.left - 6 && x <= r.right + 6 && y >= r.top - 6 && y <= r.bottom + 6) return uuidFromId(nodes[i].id);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
function hours(seconds) {
|
||
seconds = Number(seconds) || 0;
|
||
return seconds >= 3600 ? Math.floor(seconds / 3600) + 'h ' + Math.floor((seconds % 3600) / 60) + 'm' : Math.floor(seconds / 60) + 'm';
|
||
}
|
||
|
||
function panel() {
|
||
var el = document.getElementById('scopenet-panel');
|
||
if (!el) { el = document.createElement('div'); el.id = 'scopenet-panel'; document.body.appendChild(el); }
|
||
return el;
|
||
}
|
||
|
||
function render() {
|
||
var el = document.getElementById('scopenet-panel');
|
||
if (!selected) { if (el) el.remove(); return; }
|
||
var p = players[selected] || players[selected.toLowerCase()] || {};
|
||
el = panel();
|
||
var name = p.name || 'Player';
|
||
el.innerHTML =
|
||
'<button class="close" aria-label="Close">×</button>' +
|
||
'<div class="head"><img alt="" src="assets/playerheads/' + esc(selected) + '.png" onerror="this.src=\'assets/steve.png\'">' +
|
||
'<div><h3>' + esc(name) + '</h3>' + (p.title ? '<div class="title">' + esc(p.title) + '</div>' : '') + '</div></div>' +
|
||
'<div class="chips">' +
|
||
'<span class="chip lv">Level ' + esc(p.level || 1) + '</span>' +
|
||
(p.guild ? '<span class="chip guild">[' + esc(p.guildTag) + '] ' + esc(p.guild) + '</span>' : '') +
|
||
(p.rank ? '<span class="chip rank">' + esc(p.rank) + '</span>' : '') + '</div>' +
|
||
'<div class="facts">' +
|
||
'<div>Playtime<strong>' + esc(hours(p.playtime)) + '</strong></div>' +
|
||
'<div>Server level<strong>' + esc(p.serverLevel || 1) + '</strong></div>' +
|
||
(p.guildRole ? '<div>Guild role<strong>' + esc(p.guildRole) + '</strong></div>' : '') + '</div>' +
|
||
'<p class="note">Open this map in the SCOPENET launcher to send a teleport request or message, or to invite ' + esc(name) + ' to your guild.</p>';
|
||
el.querySelector('.close').addEventListener('click', function () { select(null); });
|
||
}
|
||
|
||
function select(uuid) {
|
||
selected = uuid;
|
||
if (embedded) {
|
||
window.parent.postMessage(uuid
|
||
? { source: 'scopenet-map', type: 'player', uuid: uuid, name: (players[uuid] || {}).name || '' }
|
||
: { source: 'scopenet-map', type: 'deselect' }, '*');
|
||
} else {
|
||
render();
|
||
}
|
||
}
|
||
|
||
function refresh() {
|
||
fetch(BASE + 'players.json?t=' + Date.now(), { cache: 'no-store' })
|
||
.then(function (r) { return r.ok ? r.json() : null; })
|
||
.then(function (j) { if (j && j.players) { players = j.players; if (!embedded && selected) render(); } })
|
||
.catch(function () { /* the file appears once the server has written it */ });
|
||
}
|
||
|
||
// A click is a press and release in nearly the same place; a drag moves the map.
|
||
var down = null;
|
||
document.addEventListener('pointerdown', function (e) { down = { x: e.clientX, y: e.clientY, t: Date.now() }; }, true);
|
||
document.addEventListener('pointerup', function (e) {
|
||
var start = down;
|
||
down = null;
|
||
if (!start || e.button > 0) return;
|
||
if (Math.hypot(e.clientX - start.x, e.clientY - start.y) > 6 || Date.now() - start.t > 700) return;
|
||
if (e.target && e.target.closest && e.target.closest('#scopenet-panel')) return;
|
||
var uuid = playerAt(e.clientX, e.clientY);
|
||
if (uuid) select(uuid);
|
||
else if (selected) select(null);
|
||
}, true);
|
||
|
||
window.addEventListener('message', function (e) {
|
||
if (e.source === window.parent && e.data && e.data.source === 'scopenet-launcher' && e.data.type === 'hello') { /* acknowledged */ }
|
||
});
|
||
|
||
refresh();
|
||
setInterval(refresh, 10000);
|
||
if (embedded) window.parent.postMessage({ source: 'scopenet-map', type: 'ready' }, '*');
|
||
|
||
// Exposed for tests.
|
||
window.__scopenet = { uuidFromId: uuidFromId, esc: esc, hours: hours };
|
||
})();
|