/* * 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-". */ 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 = '' + '
' + '

' + esc(name) + '

' + (p.title ? '
' + esc(p.title) + '
' : '') + '
' + '
' + 'Level ' + esc(p.level || 1) + '' + (p.guild ? '[' + esc(p.guildTag) + '] ' + esc(p.guild) + '' : '') + (p.rank ? '' + esc(p.rank) + '' : '') + '
' + '
' + '
Playtime' + esc(hours(p.playtime)) + '
' + '
Server level' + esc(p.serverLevel || 1) + '
' + (p.guildRole ? '
Guild role' + esc(p.guildRole) + '
' : '') + '
' + '

Open this map in the SCOPENET launcher to send a teleport request or message, or to invite ' + esc(name) + ' to your guild.

'; 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 }; })();