+
+
+
+
+
+
+
+
+
diff --git a/main/squaremap/web/js/addons/Ellipse.js b/main/squaremap/web/js/addons/Ellipse.js
new file mode 100644
index 0000000..4e9b21e
--- /dev/null
+++ b/main/squaremap/web/js/addons/Ellipse.js
@@ -0,0 +1,243 @@
+/**
+ * Copyright 2014 JD Fergason
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// https://github.com/jdfergason/Leaflet.Ellipse
+
+L.SVG.include ({
+ _updateEllipse: function (layer) {
+ var rx = layer._radiusX,
+ ry = layer._radiusY,
+ phi = layer._tiltDeg,
+ endPoint = layer._endPointParams;
+
+ var d = 'M' + endPoint.x0 + ',' + endPoint.y0 +
+ 'A' + rx + ',' + ry + ',' + phi + ',' +
+ endPoint.largeArc + ',' + endPoint.sweep + ',' +
+ endPoint.x1 + ',' + endPoint.y1 + ' z';
+ this._setPath(layer, d);
+ }
+});
+
+L.Canvas.include ({
+ _updateEllipse: function (layer) {
+ if (layer._empty()) { return; }
+
+ var p = layer._point,
+ ctx = this._ctx,
+ r = layer._radiusX,
+ s = (layer._radiusY || r) / r;
+
+ // this breaks "preferCanvas: true"
+ //this._drawnLayers[layer._leaflet_id] = layer;
+
+ ctx.save();
+
+ ctx.translate(p.x, p.y);
+ if (layer._tilt !== 0) {
+ ctx.rotate( layer._tilt );
+ }
+ if (s !== 1) {
+ ctx.scale(1, s);
+ }
+
+ ctx.beginPath();
+ ctx.arc(0, 0, r, 0, Math.PI * 2);
+ ctx.restore();
+
+ this._fillStroke(ctx, layer);
+ },
+});
+
+L.Ellipse = L.Path.extend({
+
+ options: {
+ fill: true,
+ startAngle: 0,
+ endAngle: 359.9
+ },
+
+ initialize: function (latlng, radii, tilt, options) {
+
+ L.setOptions(this, options);
+ this._latlng = L.latLng(latlng);
+
+ if (tilt) {
+ this._tiltDeg = tilt;
+ } else {
+ this._tiltDeg = 0;
+ }
+
+ if (radii) {
+ this._mRadiusX = radii[0];
+ this._mRadiusY = radii[1];
+ }
+ },
+
+ setRadius: function (radii) {
+ this._mRadiusX = radii[0];
+ this._mRadiusY = radii[1];
+ return this.redraw();
+ },
+
+ getRadius: function () {
+ return new L.point(this._mRadiusX, this._mRadiusY);
+ },
+
+ setTilt: function (tilt) {
+ this._tiltDeg = tilt;
+ return this.redraw();
+ },
+
+ getBounds: function () {
+ // TODO respect tilt (bounds are too big)
+ var lngRadius = this._getLngRadius(),
+ latRadius = this._getLatRadius(),
+ latlng = this._latlng;
+
+ return new L.LatLngBounds(
+ [latlng.lat - latRadius, latlng.lng - lngRadius],
+ [latlng.lat + latRadius, latlng.lng + lngRadius]);
+ },
+
+ // @method setLatLng(latLng: LatLng): this
+ // Sets the position of a circle marker to a new location.
+ setLatLng: function (latlng) {
+ this._latlng = L.latLng(latlng);
+ this.redraw();
+ return this.fire('move', {latlng: this._latlng});
+ },
+
+ // @method getLatLng(): LatLng
+ // Returns the current geographical position of the circle marker
+ getLatLng: function () {
+ return this._latlng;
+ },
+
+ setStyle: L.Path.prototype.setStyle,
+
+ _project: function () {
+ var lngRadius = this._getLngRadius(),
+ latRadius = this._getLatRadius(),
+ latlng = this._latlng,
+ pointLeft = this._map.latLngToLayerPoint([latlng.lat, latlng.lng - lngRadius]),
+ pointBelow = this._map.latLngToLayerPoint([latlng.lat - latRadius, latlng.lng]);
+
+ this._point = this._map.latLngToLayerPoint(latlng);
+ this._radiusX = Math.max(this._point.x - pointLeft.x, 1) * this._map.options.scale;
+ this._radiusY = Math.max(pointBelow.y - this._point.y, 1) * this._map.options.scale;
+ this._tilt = Math.PI * this._tiltDeg / 180;
+ this._endPointParams = this._centerPointToEndPoint();
+ this._updateBounds();
+ },
+
+ _updateBounds: function () {
+ // http://math.stackexchange.com/questions/91132/how-to-get-the-limits-of-rotated-ellipse
+ var sin = Math.sin(this._tilt);
+ var cos = Math.cos(this._tilt);
+ var sinSquare = sin * sin;
+ var cosSquare = cos * cos;
+ var aSquare = this._radiusX * this._radiusX;
+ var bSquare = this._radiusY * this._radiusY;
+ var halfWidth = Math.sqrt(aSquare*cosSquare+bSquare*sinSquare);
+ var halfHeight = Math.sqrt(aSquare*sinSquare+bSquare*cosSquare);
+ var w = this._clickTolerance();
+ var p = [halfWidth + w, halfHeight + w];
+ this._pxBounds = new L.Bounds(this._point.subtract(p), this._point.add(p));
+ },
+
+ _update: function () {
+ if (this._map) {
+ this._updatePath();
+ }
+ },
+
+ _updatePath: function () {
+ this._renderer._updateEllipse(this);
+ },
+
+ _getLatRadius: function () {
+ var simpleCrs = !!this._map.options.crs.infinite;
+ if(simpleCrs)
+ return this._mRadiusY;
+ else
+ return (this._mRadiusY / 40075017) * 360;
+ },
+
+ _getLngRadius: function () {
+ var simpleCrs = !!this._map.options.crs.infinite;
+ if(simpleCrs)
+ return this._mRadiusX;
+ else
+ return ((this._mRadiusX / 40075017) * 360) / Math.cos((Math.PI / 180) * this._latlng.lat);
+ },
+
+ _centerPointToEndPoint: function () {
+ // Convert between center point parameterization of an ellipse
+ // too SVG's end-point and sweep parameters. This is an
+ // adaptation of the perl code found here:
+ // http://commons.oreilly.com/wiki/index.php/SVG_Essentials/Paths
+ var c = this._point,
+ rx = this._radiusX,
+ ry = this._radiusY,
+ theta2 = (this.options.startAngle + this.options.endAngle) * (Math.PI / 180),
+ theta1 = this.options.startAngle * (Math.PI / 180),
+ delta = this.options.endAngle,
+ phi = this._tiltDeg * (Math.PI / 180);
+
+ // Determine start and end-point coordinates
+ var x0 = c.x + Math.cos(phi) * rx * Math.cos(theta1) +
+ Math.sin(-phi) * ry * Math.sin(theta1);
+ var y0 = c.y + Math.sin(phi) * rx * Math.cos(theta1) +
+ Math.cos(phi) * ry * Math.sin(theta1);
+
+ var x1 = c.x + Math.cos(phi) * rx * Math.cos(theta2) +
+ Math.sin(-phi) * ry * Math.sin(theta2);
+ var y1 = c.y + Math.sin(phi) * rx * Math.cos(theta2) +
+ Math.cos(phi) * ry * Math.sin(theta2);
+
+ var largeArc = (delta > 180) ? 1 : 0;
+ var sweep = (delta > 0) ? 1 : 0;
+
+ return {'x0': x0, 'y0': y0, 'tilt': phi, 'largeArc': largeArc,
+ 'sweep': sweep, 'x1': x1, 'y1': y1};
+ },
+
+ _empty: function () {
+ return this._radiusX && this._radiusY && !this._renderer._bounds.intersects(this._pxBounds);
+ },
+
+ _containsPoint : function (p) {
+ // http://stackoverflow.com/questions/7946187/point-and-ellipse-rotated-position-test-algorithm
+ var sin = Math.sin(this._tilt);
+ var cos = Math.cos(this._tilt);
+ var dx = p.x - this._point.x;
+ var dy = p.y - this._point.y;
+ var sumA = cos * dx + sin * dy;
+ var sumB = sin * dx - cos * dy;
+ if (this.options.fill === false) {
+ var x = this._radiusX - this.options.weight;
+ var y = this._radiusY - this.options.weight;
+ if (sumA * sumA / (x * x) + sumB * sumB / (y * y) <= 1) {
+ return false;
+ }
+ }
+ return sumA * sumA / (this._radiusX * this._radiusX) + sumB * sumB / (this._radiusY * this._radiusY) <= 1;
+ }
+});
+
+L.ellipse = function (latlng, radii, tilt, options) {
+ return new L.Ellipse(latlng, radii, tilt, options);
+};
diff --git a/main/squaremap/web/js/addons/RotateMarker.js b/main/squaremap/web/js/addons/RotateMarker.js
new file mode 100644
index 0000000..4592358
--- /dev/null
+++ b/main/squaremap/web/js/addons/RotateMarker.js
@@ -0,0 +1,82 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015 Benjamin Becquet
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * https://github.com/bbecquet/Leaflet.RotatedMarker
+ */
+(function() {
+ // save these original methods before they are overwritten
+ var proto_initIcon = L.Marker.prototype._initIcon;
+ var proto_setPos = L.Marker.prototype._setPos;
+
+ var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');
+
+ L.Marker.addInitHook(function () {
+ var iconOptions = this.options.icon && this.options.icon.options;
+ var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;
+ if (iconAnchor) {
+ iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');
+ }
+ this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom' ;
+ this.options.rotationAngle = this.options.rotationAngle || 0;
+
+ // Ensure marker keeps rotated during dragging
+ this.on('drag', function(e) { e.target._applyRotation(); });
+ });
+
+ L.Marker.include({
+ _initIcon: function() {
+ proto_initIcon.call(this);
+ },
+
+ _setPos: function (pos) {
+ proto_setPos.call(this, pos);
+ this._applyRotation();
+ },
+
+ _applyRotation: function () {
+ if(this.options.rotationAngle) {
+ this._icon.style[L.DomUtil.TRANSFORM+'Origin'] = this.options.rotationOrigin;
+
+ if(oldIE) {
+ // for IE 9, use the 2D rotation
+ this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';
+ } else {
+ // for modern browsers, prefer the 3D accelerated version
+ this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.rotationAngle + 'deg)';
+ }
+ }
+ },
+
+ setRotationAngle: function(angle) {
+ this.options.rotationAngle = angle;
+ this.update();
+ return this;
+ },
+
+ setRotationOrigin: function(origin) {
+ this.options.rotationOrigin = origin;
+ this.update();
+ return this;
+ }
+ });
+})();
diff --git a/main/squaremap/web/js/modules/LayerControl.js b/main/squaremap/web/js/modules/LayerControl.js
new file mode 100644
index 0000000..fc8c66e
--- /dev/null
+++ b/main/squaremap/web/js/modules/LayerControl.js
@@ -0,0 +1,106 @@
+import { P } from './Squaremap.js';
+import { SquaremapTileLayer } from './SquaremapTileLayer.js';
+
+class LayerControl {
+ constructor() {
+ this.layers = new Map();
+ }
+ init() {
+ this.currentLayer = 0;
+ this.updateInterval = 60;
+
+ this.playersLayer = new L.LayerGroup();
+ this.playersLayer.id = "players_layer";
+
+ this.controls = L.control.layers({}, {}, {
+ position: 'topleft',
+ sortLayers: true,
+ sortFunction: (a, b) => {
+ return a.order - b.order;
+ }
+ })
+ .addTo(P.map);
+ }
+ addOverlay(name, layer, hide) {
+ this.controls.addOverlay(layer, name);
+ if (this.shouldHide(layer, hide) !== true) {
+ layer.addTo(P.map);
+ }
+ }
+ removeOverlay(layer) {
+ this.ignoreLayer = layer;
+ this.controls.removeLayer(layer);
+ layer.remove();
+ this.ignoreLayer = null;
+ }
+ shouldHide(layer, def) {
+ const value = window.localStorage.getItem(`hide_${layer.id}`);
+ return value == null ? def : value === 'true';
+ }
+ hideLayer(layer) {
+ if (layer != this.ignoreLayer) {
+ window.localStorage.setItem(`hide_${layer.id}`, 'true');
+ }
+ }
+ showLayer(layer) {
+ if (layer != this.ignoreLayer) {
+ window.localStorage.setItem(`hide_${layer.id}`, 'false');
+ }
+ }
+ setupTileLayers(world) {
+ // setup the map tile layers
+ // we need 2 layers to swap between for seamless refreshing
+ if (this.tileLayer1 != null) {
+ P.map.removeLayer(this.tileLayer1);
+ }
+ if (this.tileLayer2 != null) {
+ P.map.removeLayer(this.tileLayer2);
+ }
+ this.tileLayer1 = this.createTileLayer(world);
+ this.tileLayer2 = this.createTileLayer(world);
+
+ // refresh player's control
+ this.removeOverlay(this.playersLayer);
+ if (world.player_tracker.show_controls) {
+ this.addOverlay(world.player_tracker.label,
+ this.playersLayer,
+ world.player_tracker.default_hidden);
+ }
+ this.playersLayer.order = world.player_tracker.priority;
+ this.playersLayer.setZIndex(world.player_tracker.z_index);
+ }
+ createTileLayer(world) {
+ return new SquaremapTileLayer(`tiles/${world.name}/{z}/{x}_{y}.png`, {
+ tileSize: 512,
+ minNativeZoom: 0,
+ maxNativeZoom: world.zoom.max,
+ errorTileUrl: 'images/clear.png'
+ }).addTo(P.map)
+ .addEventListener("load", () => {
+ // when all tiles are loaded, switch to this layer
+ this.switchTileLayer();
+ });
+ }
+ updateTileLayer() {
+ // redraw background tile layer
+ if (this.currentLayer == 1) {
+ this.tileLayer2.redraw();
+ } else {
+ this.tileLayer1.redraw();
+ }
+ }
+ switchTileLayer() {
+ // swap current tile layer
+ if (this.currentLayer == 1) {
+ this.tileLayer1.setZIndex(0);
+ this.tileLayer2.setZIndex(1);
+ this.currentLayer = 2;
+ } else {
+ this.tileLayer1.setZIndex(1);
+ this.tileLayer2.setZIndex(0);
+ this.currentLayer = 1;
+ }
+ }
+}
+
+export { LayerControl };
diff --git a/main/squaremap/web/js/modules/PlayerList.js b/main/squaremap/web/js/modules/PlayerList.js
new file mode 100644
index 0000000..1d33db4
--- /dev/null
+++ b/main/squaremap/web/js/modules/PlayerList.js
@@ -0,0 +1,161 @@
+import { Player } from "./util/Player.js";
+import { P } from './Squaremap.js';
+
+class PlayerList {
+ constructor(json) {
+ this.players = new Map();
+ this.markers = new Map();
+ this.following = null;
+ this.firstTick = true;
+ this.label = json.player_list_label;
+ P.map.createPane("nameplate").style.zIndex = 1000;
+ }
+ tick() {
+ if (P.tick_count % P.worldList.curWorld.player_tracker.update_interval == 0) {
+ P.getJSON("tiles/players.json", (json) => {
+ this.updatePlayerList(json.players);
+ const title = `${this.label}`
+ .replace(/{cur}/g, json.players.length)
+ .replace(/{max}/g, json.max == null ? "???" : json.max)
+ if (P.sidebar.players.legend.innerHTML !== title) {
+ P.sidebar.players.legend.innerHTML = title;
+ }
+ });
+ }
+ }
+ showPlayer(uuid) {
+ const player = this.players.get(uuid);
+ if (!P.worldList.worlds.has(player.world)) {
+ return false;
+ }
+ P.worldList.showWorld(player.world, () => {
+ P.map.panTo(P.toLatLng(player.x, player.z));
+ });
+ return true;
+ }
+ addToList(player) {
+ const head = document.createElement("img");
+ head.src = player.getHeadUrl();
+
+ const span = document.createElement("span");
+ span.innerHTML = player.displayName
+
+ const link = P.createElement("a", player.uuid, this);
+ link.onclick = function (e) {
+ if (this.parent.showPlayer(this.id)) {
+ this.parent.followPlayerMarker(this.id);
+ e.stopPropagation();
+ }
+ };
+ link.appendChild(head);
+ link.appendChild(span);
+ const fieldset = P.sidebar.players.element;
+ fieldset.appendChild(link);
+ Array.from(fieldset.getElementsByTagName("a"))
+ .sort((a, b) => {
+ return plain(a.getElementsByTagName("span")[0])
+ .localeCompare(plain(b.getElementsByTagName("span")[0]));
+ })
+ .forEach(link => fieldset.appendChild(link));
+ }
+ removeFromList(player) {
+ const link = document.getElementById(player.uuid);
+ if (link != null) {
+ link.remove();
+ }
+ this.players.delete(player.uuid);
+ player.removeMarker();
+ }
+ updatePlayerList(players) {
+ const playersToRemove = Array.from(this.players.keys());
+
+ let needsSort = false;
+
+ // update players from json
+ for (let i = 0; i < players.length; i++) {
+ let player = this.players.get(players[i].uuid);
+ if (player == null) {
+ // new player
+ player = new Player(players[i]);
+ this.players.set(player.uuid, player);
+ this.addToList(player);
+ } else {
+ const oldDisplayName = player.displayName;
+ player.update(players[i]);
+ if (oldDisplayName !== player.displayName) {
+ needsSort = true;
+ document.getElementById(player.uuid)
+ .getElementsByTagName("span")[0]
+ .innerHTML = player.displayName;
+ }
+ }
+ playersToRemove.remove(players[i].uuid);
+ }
+
+ // remove players not in json
+ for (let i = 0; i < playersToRemove.length; i++) {
+ const player = this.players.get(playersToRemove[i]);
+ this.removeFromList(player);
+ }
+
+ if (needsSort) {
+ const fieldset = P.sidebar.players.element;
+ Array.from(fieldset.getElementsByTagName("a"))
+ .sort((a, b) => {
+ return plain(a.getElementsByTagName("span")[0])
+ .localeCompare(plain(b.getElementsByTagName("span")[0]));
+ })
+ .forEach(link => fieldset.appendChild(link));
+ }
+
+ // first tick only
+ if (this.firstTick) {
+ this.firstTick = false;
+
+ // follow uuid from url
+ const follow = P.getUrlParam("uuid", null);
+ if (follow != null && this.players.get(follow) != null) {
+ this.followPlayerMarker(follow);
+ }
+ }
+
+ // follow highlighted player
+ if (this.following != null) {
+ const player = this.players.get(this.following);
+ if (player != null && P.worldList.curWorld != null) {
+ if (player.world !== P.worldList.curWorld.name) {
+ P.worldList.showWorld(player.world, () => {
+ P.map.panTo(P.toLatLng(player.x, player.z));
+ });
+ } else {
+ P.map.panTo(P.toLatLng(player.x, player.z));
+ }
+ }
+ }
+ }
+ clearPlayerMarkers() {
+ const playersToRemove = Array.from(this.players.keys());
+ for (let i = 0; i < playersToRemove.length; i++) {
+ const player = this.players.get(playersToRemove[i]);
+ player.removeMarker();
+ }
+ this.markers.clear();
+ //P.layerControl.playersLayer.clearLayers();
+ }
+ followPlayerMarker(uuid) {
+ if (this.following != null) {
+ document.getElementById(this.following).classList.remove("following");
+ this.following = null;
+ }
+ if (uuid != null) {
+ this.following = uuid;
+ document.getElementById(this.following).classList.add("following");
+ }
+ }
+}
+
+function plain(element) {
+ return element.textContent || element.innerText || "";
+}
+
+export { PlayerList };
diff --git a/main/squaremap/web/js/modules/Sidebar.js b/main/squaremap/web/js/modules/Sidebar.js
new file mode 100644
index 0000000..6eea37b
--- /dev/null
+++ b/main/squaremap/web/js/modules/Sidebar.js
@@ -0,0 +1,47 @@
+import { Pin } from "./util/Pin.js";
+import { Fieldset } from "./util/Fieldset.js";
+import { P } from './Squaremap.js';
+
+class Sidebar {
+ constructor(json, show) {
+ this.sidebar = P.createElement("div", "sidebar", this);
+ this.showSidebar = show;
+ if (!show) {
+ this.sidebar.style.display = "none";
+ }
+ this.sidebar.addEventListener("click", (e) => {
+ P.playerList.followPlayerMarker(null);
+ });
+ document.body.appendChild(this.sidebar);
+
+ this.pin = new Pin(json.pinned == "pinned");
+ this.show(this.pin.pinned);
+ if (json.pinned != "hide") {
+ this.sidebar.appendChild(this.pin.element);
+ }
+
+ this.worlds = new Fieldset("worlds", json.world_list_label);
+ this.sidebar.appendChild(this.worlds.element);
+
+ this.players = new Fieldset("players", json.player_list_label
+ .replace(/{cur}/g, 0)
+ .replace(/{max}/g, 0));
+ this.sidebar.appendChild(this.players.element);
+
+ this.sidebar.onmouseleave = () => {
+ if (!this.pin.pinned) {
+ this.show(false);
+ }
+ };
+ this.sidebar.onmouseenter = () => {
+ if (!this.pin.pinned) {
+ this.show(true);
+ }
+ };
+ }
+ show(show) {
+ this.sidebar.className = show ? "show" : "";
+ }
+}
+
+export { Sidebar };
diff --git a/main/squaremap/web/js/modules/Squaremap.js b/main/squaremap/web/js/modules/Squaremap.js
new file mode 100644
index 0000000..3e48cc7
--- /dev/null
+++ b/main/squaremap/web/js/modules/Squaremap.js
@@ -0,0 +1,171 @@
+import { Sidebar } from "./Sidebar.js";
+import { PlayerList } from "./PlayerList.js";
+import { WorldList } from "./WorldList.js";
+import { UICoordinates } from "./UICoordinates.js";
+import { UILink } from "./UILink.js";
+import { LayerControl } from "./LayerControl.js";
+
+class SquaremapMap {
+ constructor() {
+ this.map = L.map("map", {
+ crs: L.CRS.Simple,
+ center: [0, 0],
+ attributionControl: false,
+ preferCanvas: true,
+ noWrap: true
+ })
+ .on('overlayadd', (e) => {
+ this.layerControl.showLayer(e.layer);
+ })
+ .on('overlayremove', (e) => {
+ this.layerControl.hideLayer(e.layer);
+ })
+ .on('click', (e) => {
+ this.playerList.followPlayerMarker(null);
+ })
+ .on('dblclick', (e) => {
+ this.playerList.followPlayerMarker(null);
+ });
+
+ this.tick_count = 1;
+
+ this.layerControl = new LayerControl();
+
+ this.init();
+ }
+ loop() {
+ if (document.visibilityState === 'visible') {
+ this.tick();
+ this.tick_count++;
+ }
+ setTimeout(() => this.loop(), 1000);
+ }
+ tick() {
+ // tick player tracker
+ this.playerList.tick();
+ // tick world
+ this.worldList.curWorld.tick();
+ }
+ init() {
+ this.getJSON("tiles/settings.json", (json) => {
+ this.layerControl.init();
+
+ this.title = json.ui.title;
+ this.sidebar = new Sidebar(json.ui.sidebar, this.getUrlParam("show_sidebar", "true") === "true");
+ this.playerList = new PlayerList(json.ui.sidebar);
+ this.worldList = new WorldList(json.worlds);
+ this.coordinates = new UICoordinates(json.ui.coordinates, this.getUrlParam("show_coordinates", "true") === "true");
+ this.uiLink = new UILink(json.ui.link, this.getUrlParam("show_link_button", "true") === "true");
+
+ this.showControls = this.getUrlParam("show_controls", "true") === "true"
+ if (!this.showControls) {
+ let controlLayers = document.getElementsByClassName('leaflet-top leaflet-left');
+ controlLayers[0].style.display = "none";
+ }
+
+ this.worldList.loadInitialWorld(json, (world) => {
+ this.loop();
+ this.centerOn(
+ this.getUrlParam("x", world.spawn.x),
+ this.getUrlParam("z", world.spawn.z),
+ this.getUrlParam("zoom", world.zoom.def));
+ });
+
+ });
+ }
+ centerOn(x, z, zoom) {
+ this.map.setView(this.toLatLng(x, z), zoom);
+ this.uiLink.update();
+ return this.map;
+ }
+ toLatLng(x, z) {
+ return L.latLng(this.pixelsToMeters(-z), this.pixelsToMeters(x));
+ //return this.map.unproject([x, z], this.worldList.curWorld.zoom.max);
+ }
+ toPoint(latlng) {
+ return L.point(this.metersToPixels(latlng.lng), this.metersToPixels(-latlng.lat));
+ //return this.map.project(latlng, this.worldList.curWorld.zoom.max);
+ }
+ pixelsToMeters(num) {
+ return num * this.scale;
+ }
+ metersToPixels(num) {
+ return num / this.scale;
+ }
+ setScale(zoom) {
+ this.scale = (1 / Math.pow(2, zoom));
+ // store this on map for ellipse
+ this.map.options.scale = this.scale;
+ }
+ createElement(tag, id, parent) {
+ const element = document.createElement(tag);
+ element.id = id;
+ element.parent = parent;
+ return element;
+ }
+ createTextElement(tag, text) {
+ const element = document.createElement(tag);
+ element.appendChild(document.createTextNode(text));
+ return element;
+ }
+ getJSON(url, fn) {
+ fetch(url, {cache: "no-store"})
+ .then(async res => {
+ if (res.ok) {
+ fn(await res.json());
+ }
+ });
+ }
+ getUrlParam(query, def) {
+ const url = window.location.search.substring(1);
+ const vars = url.split('&');
+ for (let i = 0; i < vars.length; i++) {
+ const param = vars[i].split('=');
+ if (param[0] === query) {
+ const value = param[1] === undefined ? '' : decodeURIComponent(param[1]);
+ return value === '' ? def : value;
+ }
+ }
+ return def;
+ }
+ getUrlFromView() {
+ const center = this.toPoint(this.map.getCenter());
+ const zoom = this.map.getZoom();
+ const x = Math.floor(center.x);
+ const z = Math.floor(center.y);
+ const following = this.playerList.following ? `&uuid=${this.playerList.following}` : '';
+ let link = `?world=${this.worldList.curWorld.name}&zoom=${zoom}&x=${x}&z=${z}${following}`;
+ if (!this.showControls) {
+ link += "&show_controls=false"
+ }
+ if (!this.uiLink.showLinkButton) {
+ link += "&show_link_button=false"
+ }
+ if (!this.coordinates.showCoordinates) {
+ link += "&show_coordinates=false"
+ }
+ if (!this.sidebar.showSidebar) {
+ link += "&show_sidebar=false"
+ }
+ return link
+ }
+ updateBrowserUrl(url) {
+ window.history.replaceState(null, "", url);
+ }
+}
+
+
+export const P = new SquaremapMap();
+
+
+// https://stackoverflow.com/a/3955096
+Array.prototype.remove = function() {
+ var what, a = arguments, L = a.length, ax;
+ while (L && this.length) {
+ what = a[--L];
+ while ((ax = this.indexOf(what)) !== -1) {
+ this.splice(ax, 1);
+ }
+ }
+ return this;
+};
diff --git a/main/squaremap/web/js/modules/SquaremapTileLayer.js b/main/squaremap/web/js/modules/SquaremapTileLayer.js
new file mode 100644
index 0000000..d2877fe
--- /dev/null
+++ b/main/squaremap/web/js/modules/SquaremapTileLayer.js
@@ -0,0 +1,43 @@
+export var SquaremapTileLayer = L.TileLayer.extend({
+
+ // @method createTile(coords: Object, done?: Function): HTMLElement
+ // Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
+ // to return an `` HTML element with the appropriate image URL given `coords`. The `done`
+ // callback is called when the tile has been loaded.
+ createTile: function (coords, done) {
+ var tile = document.createElement('img');
+
+ L.DomEvent.on(tile, 'load', () => {
+ //Once image has loaded revoke the object URL as we don't need it anymore
+ URL.revokeObjectURL(tile.src);
+ this._tileOnLoad(done, tile)
+ });
+ L.DomEvent.on(tile, 'error', L.Util.bind(this._tileOnError, this, done, tile));
+
+ if (this.options.crossOrigin || this.options.crossOrigin === '') {
+ tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
+ }
+
+ tile.alt = '';
+ tile.setAttribute('role', 'presentation');
+
+ //Retrieve image via a fetch instead of just setting the src
+ //This works around the fact that browsers usually don't make a request for an image that was previously loaded,
+ //without resorting to changing the URL (which would break caching).
+ fetch(this.getTileUrl(coords))
+ .then(res => {
+ //Call leaflet's error handler if request fails for some reason
+ if (!res.ok) {
+ this._tileOnError(done, tile, null);
+ return;
+ }
+
+ //Get image data and convert into object URL so it can be used as a src
+ //Leaflet's onload listener will take it from here
+ res.blob().then(blob => tile.src = URL.createObjectURL(blob));
+ })
+ .catch(() => this._tileOnError(done, tile, null));
+
+ return tile;
+ }
+});
diff --git a/main/squaremap/web/js/modules/UICoordinates.js b/main/squaremap/web/js/modules/UICoordinates.js
new file mode 100644
index 0000000..fe88d4a
--- /dev/null
+++ b/main/squaremap/web/js/modules/UICoordinates.js
@@ -0,0 +1,44 @@
+import { P } from './Squaremap.js';
+
+class UICoordinates {
+ constructor(json, show) {
+ const Coords = L.Control.extend({
+ _container: null,
+ options: {
+ position: 'bottomleft'
+ },
+ onAdd: function () {
+ const coords = L.DomUtil.create('div', 'leaflet-control-layers coordinates');
+ this._coords = coords;
+ if (!show) {
+ this._coords.style.display = "none";
+ }
+ return coords;
+ },
+ update: function (html, point) {
+ this.x = point == null ? "---" : Math.floor(point.x);
+ this.z = point == null ? "---" : Math.floor(point.y);
+ if (html != null) {
+ this._coords.innerHTML = html
+ .replace(/{x}/g, this.x)
+ .replace(/{z}/g, this.z);
+ }
+ }
+ });
+ this.showCoordinates = show;
+ this.html = json.html == null ? "undefined" : json.html;
+ this.coords = new Coords();
+ P.map.addControl(this.coords)
+ .addEventListener('mousemove', (event) => {
+ if (P.worldList.curWorld != null) {
+ this.coords.update(this.html, P.toPoint(event.latlng));
+ }
+ });
+ if (!json.enabled) {
+ this.coords._coords.style.display = "none";
+ }
+ this.coords.update(this.html);
+ }
+}
+
+export { UICoordinates };
diff --git a/main/squaremap/web/js/modules/UILink.js b/main/squaremap/web/js/modules/UILink.js
new file mode 100644
index 0000000..ef13d12
--- /dev/null
+++ b/main/squaremap/web/js/modules/UILink.js
@@ -0,0 +1,39 @@
+import { P } from './Squaremap.js';
+
+class UILink {
+ constructor(json, show) {
+ const Link = L.Control.extend({
+ _container: null,
+ options: {
+ position: 'bottomleft'
+ },
+ onAdd: function () {
+ const link = L.DomUtil.create('div', 'leaflet-control-layers link');
+ this._link = link;
+ if (!show) {
+ this._link.style.display = "none";
+ }
+ this.update();
+ return link;
+ },
+ update: function() {
+ const url = P.worldList.curWorld == null ? "" : P.getUrlFromView();
+ //P.updateBrowserUrl(url); // this spams browser history
+ this._link.innerHTML = ``;
+ }
+ });
+ this.showLinkButton = show;
+ this.link = new Link();
+ P.map.addControl(this.link)
+ .addEventListener('move', () => this.update())
+ .addEventListener('zoom', () => this.update());
+ if (!json.enabled) {
+ this.link._link.style.display = "none";
+ }
+ }
+ update() {
+ this.link.update();
+ }
+}
+
+export { UILink };
diff --git a/main/squaremap/web/js/modules/WorldList.js b/main/squaremap/web/js/modules/WorldList.js
new file mode 100644
index 0000000..ba19424
--- /dev/null
+++ b/main/squaremap/web/js/modules/WorldList.js
@@ -0,0 +1,97 @@
+import { World } from "./util/World.js";
+import { P } from './Squaremap.js';
+
+class WorldList {
+ constructor(json) {
+ // get worlds from json
+ const unorderedMap = new Map();
+ for (let i = 0; i < json.length; i++) {
+ const world = new World(json[i]);
+ unorderedMap.set(world.name, world);
+ }
+
+ // sort worlds by order
+ this.worlds = new Map([...unorderedMap].sort((a, b) => a[1].order - b[1].order));
+
+ // set up world list link elements
+ for (const [name, world] of this.worlds) {
+ const link = P.createElement("a", name, this);
+ link.onclick = function () {
+ const curWorld = this.parent.curWorld;
+ if (curWorld.name == name) {
+ P.centerOn(world.spawn.x, world.spawn.z, world.zoom.def)
+ return;
+ }
+ P.playerList.clearPlayerMarkers();
+ this.parent.loadWorld(name, (world) => {
+ P.centerOn(world.spawn.x, world.spawn.z, world.zoom.def)
+ });
+ };
+
+ const img = document.createElement("img");
+ img.src = this.getIcon(world);
+
+ link.appendChild(img);
+ link.appendChild(P.createTextElement("span", world.display_name));
+
+ P.sidebar.worlds.element.appendChild(link);
+ }
+ }
+ getIcon(world) {
+ if (world.icon != null && world.icon != "") {
+ return `images/icon/${world.icon}.png`;
+ }
+ switch (world.type) {
+ case "nether":
+ return "images/icon/red-cube-smol.png";
+ case "the_end":
+ return "images/icon/purple-cube-smol.png";
+ case "normal":
+ default:
+ return "images/icon/green-cube-smol.png";
+ }
+ }
+ loadInitialWorld(json, callback) {
+ let updateUrl = false
+ let name = P.getUrlParam("world", null)
+ if (name != null) {
+ const world = this.worlds.get(name);
+ if (world == null) {
+ updateUrl = true
+ name = null;
+ }
+ }
+ if (name == null) {
+ name = json.worlds.sort((a, b) => a.order - b.order)[0].name
+ }
+ this.loadWorld(name, (a) => {
+ callback(a)
+ if (updateUrl) {
+ P.updateBrowserUrl(`?world=${this.curWorld.name}`);
+ }
+ })
+ }
+ loadWorld(name, callback) {
+ // unload current world
+ if (this.curWorld != null) {
+ this.curWorld.unload();
+ }
+
+ // load new world
+ const world = this.worlds.get(name);
+ this.curWorld = world;
+ world.load(callback);
+ }
+ showWorld(world, callback) {
+ if (this.curWorld.name == world) {
+ if (callback != null) {
+ callback();
+ }
+ return;
+ }
+ this.loadWorld(world, callback);
+ P.updateBrowserUrl(P.getUrlFromView());
+ }
+}
+
+export { WorldList };
diff --git a/main/squaremap/web/js/modules/util/Fieldset.js b/main/squaremap/web/js/modules/util/Fieldset.js
new file mode 100644
index 0000000..6adbd31
--- /dev/null
+++ b/main/squaremap/web/js/modules/util/Fieldset.js
@@ -0,0 +1,11 @@
+import { P } from '../Squaremap.js';
+
+class Fieldset {
+ constructor(id, title) {
+ this.element = P.createElement("fieldset", id);
+ this.legend = P.createTextElement("legend", title);
+ this.element.appendChild(this.legend);
+ }
+}
+
+export { Fieldset };
diff --git a/main/squaremap/web/js/modules/util/Markers.js b/main/squaremap/web/js/modules/util/Markers.js
new file mode 100644
index 0000000..6c15df9
--- /dev/null
+++ b/main/squaremap/web/js/modules/util/Markers.js
@@ -0,0 +1,163 @@
+import { P } from '../Squaremap.js';
+
+class Marker {
+ constructor(opts) {
+ this.opts = opts;
+ this.id = this.opts.pop("id");
+ this.popup = this.opts.pop("popup");
+ this.popup_sticky = true;
+ this.tooltip = this.opts.pop("tooltip");
+ this.tooltip_sticky = true;
+ }
+ init() {
+ if (this.popup != null) {
+ if (this.popup_sticky) {
+ this.marker.on('click', (e) => {
+ L.popup({
+ direction: this.opts.pop("tooltip_direction", "top")
+ })
+ .setLatLng(P.toLatLng(P.coordinates.coords.x, P.coordinates.coords.z))
+ .setContent(this.popup)
+ .openOn(P.map);
+ });
+ } else {
+ this.marker.bindPopup(() => this.popup, {
+ direction: this.opts.pop("tooltip_direction", "top")
+ });
+ }
+ }
+ if (this.tooltip != null) {
+ this.marker.bindTooltip(() => this.tooltip, {
+ direction: this.opts.pop("tooltip_direction", "top"),
+ sticky: this.tooltip_sticky
+ });
+ }
+ for (const key in this.opts) {
+ this.marker.options[key] = this.opts[key];
+ }
+ }
+ addTo(layer) {
+ this.marker.remove();
+ this.marker.addTo(layer);
+ }
+}
+
+class Options {
+ constructor(json) {
+ for (const prop in json) {
+ this[prop] = json[prop];
+ }
+ }
+ pop(key, def) {
+ const val = this[key];
+ delete this[key];
+ return val == null ? def : val;
+ }
+}
+
+class Rectangle extends Marker {
+ constructor(opts) {
+ super(opts);
+ const points = this.opts.pop("points");
+ this.marker = L.rectangle([P.toLatLng(points[0].x, points[0].z), P.toLatLng(points[1].x, points[1].z)]);
+ super.init();
+ }
+}
+
+class PolyLine extends Marker {
+ constructor(opts) {
+ super(opts);
+ const points = this.opts.pop("points");
+ const outer = [];
+ for (let i = 0; i < points.length; i++) {
+ if (Symbol.iterator in Object(points[i])) {
+ const inner = [];
+ for (let j = 0; j < points[i].length; j++) {
+ inner.push(P.toLatLng(points[i][j].x, points[i][j].z));
+ }
+ outer.push(inner);
+ } else {
+ outer.push(P.toLatLng(points[i].x, points[i].z));
+ }
+ }
+ this.marker = L.polyline(outer);
+ super.init();
+ }
+}
+
+class Polygon extends Marker {
+ constructor(opts) {
+ super(opts);
+ const points = this.opts.pop("points");
+ const outer = [];
+ for (let i = 0; i < points.length; i++) {
+ if (Symbol.iterator in Object(points[i])) {
+ const inner = [];
+ for (let j = 0; j < points[i].length; j++) {
+ if (Symbol.iterator in Object(points[i][j])) {
+ const inner2 = [];
+ for (let k = 0; k < points[i][j].length; k++) {
+ inner2.push(P.toLatLng(points[i][j][k].x, points[i][j][k].z));
+ }
+ inner.push(inner2);
+ } else {
+ inner.push(P.toLatLng(points[i][j].x, points[i][j].z));
+ }
+ }
+ outer.push(inner);
+ } else {
+ outer.push(P.toLatLng(points[i].x, points[i].z));
+ }
+ }
+ this.marker = L.polygon(outer);
+ super.init();
+ }
+}
+
+class Circle extends Marker {
+ constructor(opts) {
+ super(opts);
+ const center = this.opts.pop("center");
+ const radius = this.opts.pop("radius");
+ this.marker = L.circle(P.toLatLng(center.x, center.z), {
+ radius: P.pixelsToMeters(radius)
+ });
+ super.init();
+ }
+}
+
+class Ellipse extends Marker {
+ constructor(opts) {
+ super(opts);
+ const center = this.opts.pop("center");
+ const radiusX = this.opts.pop("radiusX");
+ const radiusZ = this.opts.pop("radiusZ");
+ const tilt = 0;
+ this.marker = L.ellipse(P.toLatLng(center.x, center.z), [radiusX, radiusZ], tilt);
+ super.init();
+ }
+}
+
+class Icon extends Marker {
+ constructor(opts) {
+ super(opts);
+ const point = this.opts.pop("point");
+ const size = this.opts.pop("size");
+ const anchor = this.opts.pop("anchor");
+ const tooltipAnchor = this.opts.pop("tooltip_anchor", L.point(0, -size.z / 2));
+ this.marker = L.marker(P.toLatLng(point.x, point.z), {
+ icon: L.icon({
+ iconUrl: `images/icon/registered/${opts.pop("icon")}.png`,
+ iconSize: [size.x, size.z],
+ iconAnchor: [anchor.x, anchor.z],
+ popupAnchor: [tooltipAnchor.x, tooltipAnchor.z],
+ tooltipAnchor: [tooltipAnchor.x, tooltipAnchor.z]
+ })
+ });
+ this.popup_sticky = false;
+ this.tooltip_sticky = false;
+ super.init();
+ }
+}
+
+export { Marker, Options, Rectangle, PolyLine, Polygon, Circle, Ellipse, Icon };
diff --git a/main/squaremap/web/js/modules/util/Pin.js b/main/squaremap/web/js/modules/util/Pin.js
new file mode 100644
index 0000000..c5de4e6
--- /dev/null
+++ b/main/squaremap/web/js/modules/util/Pin.js
@@ -0,0 +1,23 @@
+import { P } from '../Squaremap.js';
+
+class Pin {
+ constructor(def) {
+ this.pinned = def;
+
+ this.element = P.createElement("img", "pin", this);
+
+ this.element.onclick = () => this.toggle();
+
+ this.pin(this.pinned);
+ }
+ toggle() {
+ this.pin(!this.pinned);
+ }
+ pin(pin) {
+ this.pinned = pin;
+ this.element.className = pin ? "pinned" : "unpinned";
+ this.element.src = `images/${this.element.className}.png`;
+ }
+}
+
+export { Pin };
diff --git a/main/squaremap/web/js/modules/util/Player.js b/main/squaremap/web/js/modules/util/Player.js
new file mode 100644
index 0000000..2656781
--- /dev/null
+++ b/main/squaremap/web/js/modules/util/Player.js
@@ -0,0 +1,98 @@
+import { P } from '../Squaremap.js';
+
+class Player {
+ constructor(json) {
+ this.name = json.name;
+ this.uuid = json.uuid;
+ this.world = json.world;
+ this.displayName = json.display_name !== undefined ? json.display_name : json.name;
+ this.x = 0;
+ this.z = 0;
+ this.armor = 0;
+ this.health = 20;
+ this.tooltip = L.tooltip({
+ permanent: true,
+ direction: "right",
+ offset: [10, 0],
+ pane: "nameplate"
+ });
+ this.marker = L.marker(P.toLatLng(json.x, json.z), {
+ icon: L.icon({
+ iconUrl: 'images/icon/player.png',
+ iconSize: [17, 16],
+ iconAnchor: [8, 9],
+ tooltipAnchor: [0, 0]
+ }),
+ rotationAngle: (180 + json.yaw)
+ });
+ if (P.worldList.curWorld.player_tracker.nameplates.enabled) {
+ this.updateNameplate(json);
+ this.marker.bindTooltip(this.tooltip);
+ }
+ }
+ getHeadUrl() {
+ return P.worldList.curWorld.player_tracker.nameplates.heads_url
+ .replace(/{uuid}/g, this.uuid)
+ .replace(/{name}/g, this.name);
+ }
+ updateNameplate(player) {
+ let headImg = "";
+ let armorImg = "";
+ let healthImg = "";
+ if (P.worldList.curWorld.player_tracker.nameplates.show_heads) {
+ headImg = ``;
+ }
+ if (P.worldList.curWorld.player_tracker.nameplates.show_armor && player.armor != null) {
+ armorImg = ``;
+ }
+ if (P.worldList.curWorld.player_tracker.nameplates.show_health && player.health != null) {
+ healthImg = ``;
+ }
+ this.tooltip.setContent(`
${headImg}
${this.displayName}${healthImg}${armorImg}
`);
+ }
+ update(player) {
+ this.x = player.x;
+ this.z = player.z;
+ this.world = player.world;
+ this.armor = player.armor;
+ this.health = player.health;
+ this.displayName = player.display_name !== undefined ? player.display_name : player.name;
+ const link = document.getElementById(player.uuid);
+ const img = link.getElementsByTagName("img")[0];
+ const span = link.getElementsByTagName("span")[0];
+ if (P.worldList.curWorld.name == player.world) {
+ if (P.worldList.curWorld.player_tracker.enabled) {
+ this.addMarker();
+ }
+ const latlng = P.toLatLng(player.x, player.z);
+ if (!this.marker.getLatLng().equals(latlng)) {
+ this.marker.setLatLng(latlng);
+ }
+ const angle = 180 + player.yaw;
+ if (this.marker.options.rotationAngle != angle) {
+ this.marker.setRotationAngle(angle);
+ }
+ img.classList.remove("other-world");
+ span.classList.remove("other-world");
+ } else {
+ this.removeMarker();
+ img.classList.add("other-world");
+ span.classList.add("other-world");
+ }
+ this.updateNameplate(player);
+ }
+ removeMarker() {
+ this.marker.remove();
+ P.playerList.markers.delete(this.uuid);
+ P.map.removeLayer(this.marker);
+ P.layerControl.playersLayer.removeLayer(this.marker);
+ }
+ addMarker() {
+ if (!P.playerList.markers.has(this.uuid)) {
+ this.marker.addTo(P.layerControl.playersLayer);
+ P.playerList.markers.set(this.uuid, this.marker);
+ }
+ }
+}
+
+export { Player };
diff --git a/main/squaremap/web/js/modules/util/World.js b/main/squaremap/web/js/modules/util/World.js
new file mode 100644
index 0000000..9644a44
--- /dev/null
+++ b/main/squaremap/web/js/modules/util/World.js
@@ -0,0 +1,145 @@
+import { Options, Rectangle, PolyLine, Polygon, Circle, Ellipse, Icon } from "./Markers.js";
+import { P } from '../Squaremap.js';
+
+class World {
+ constructor(json) {
+ this.name = json.name;
+ this.order = json.order;
+ this.icon = json.icon;
+ this.type = json.type;
+ this.display_name = json.display_name;
+ this.markerLayers = new Map();
+ this.player_tracker = {};
+ this.marker_update_interval = 5;
+ this.tiles_update_interval = 15;
+ }
+ tick() {
+ // refresh map tile layer
+ if (P.tick_count % this.tiles_update_interval == 0) {
+ P.layerControl.updateTileLayer();
+ }
+ // load and draw markers
+ if (P.tick_count % this.marker_update_interval == 0) {
+ P.getJSON(`tiles/${this.name}/markers.json`, (json) => {
+ if (this === P.worldList.curWorld) {
+ this.markers(json);
+ }
+ });
+ }
+ }
+ unload() {
+ P.playerList.clearPlayerMarkers();
+ const keys = Array.from(this.markerLayers.keys());
+ for (let i = 0; i < keys.length; i++) {
+ const layer = this.markerLayers.get(keys[i]);
+ P.layerControl.controls.removeLayer(layer);
+ layer.remove();
+ this.markerLayers.delete(keys[i]);
+ }
+ }
+ load(callback) {
+ P.getJSON(`tiles/${this.name}/settings.json`, (json) => {
+ this.player_tracker = json.player_tracker;
+ this.zoom = json.zoom;
+ this.spawn = json.spawn;
+ this.marker_update_interval = json.marker_update_interval;
+ this.tiles_update_interval = json.tiles_update_interval;
+
+ // set the scale for our projection calculations
+ P.setScale(this.zoom.max);
+
+ // set center and zoom
+ P.centerOn(this.spawn.x, this.spawn.z, this.zoom.def)
+ .setMinZoom(0) // extra zoom out doesn't work :(
+ .setMaxZoom(this.zoom.max + this.zoom.extra);
+
+ // update page title
+ document.title = P.title
+ .replace(/{world}/g, this.display_name);
+
+ // setup background
+ document.getElementById("map").style.background = this.getBackground();
+
+ // setup tile layers
+ P.layerControl.setupTileLayers(this);
+
+ // force clear player markers
+ P.playerList.clearPlayerMarkers();
+
+ // tick now, reset counter
+ P.tick_count = 0;
+ P.tick();
+
+ // force clear player markers
+ P.playerList.clearPlayerMarkers();
+
+ if (callback != null) {
+ callback(this);
+ }
+ });
+ }
+ getBackground() {
+ switch (this.type) {
+ case "nether":
+ return "url('images/nether_sky.png')";
+ case "the_end":
+ return "url('images/end_sky.png')";
+ case "normal":
+ default:
+ return "url('images/overworld_sky.png')";
+ }
+ }
+ markers(json) {
+ // check if json is iterable
+ if (json == null || !(Symbol.iterator in Object(json))) {
+ return;
+ }
+ // iterate layers
+ for (const entry of json) {
+ // check if layer exists and needs updating
+ let layer = this.markerLayers.get(entry.id);
+ if (layer != null) {
+ if (layer.timestamp === entry.timestamp) {
+ continue; // skip
+ }
+ // clear existing layer to rebuild
+ P.layerControl.removeOverlay(layer);
+ // TODO
+ // implement marker tracker instead of clearing
+ // to reduce possible client side lag
+ }
+
+ // setup the layer
+ layer = new L.LayerGroup();
+ layer.order = entry.order;
+ layer.id = entry.id;
+ layer.timestamp = entry.timestamp;
+ layer.setZIndex(entry.z_index);
+ this.markerLayers.set(layer.id, layer);
+
+ // setup the layer control
+ if (entry.control === true) {
+ P.layerControl.addOverlay(entry.name, layer, entry.hide);
+ }
+
+ // setup the markers
+ for (const shape in entry.markers) {
+ let marker;
+ const opts = new Options(entry.markers[shape]);
+ switch(opts.pop("type")) {
+ case "rectangle": marker = new Rectangle(opts); break;
+ case "polyline": marker = new PolyLine(opts); break;
+ case "polygon": marker = new Polygon(opts); break;
+ case "circle": marker = new Circle(opts); break;
+ case "ellipse": marker = new Ellipse(opts); break;
+ case "icon": marker = new Icon(opts); break;
+ }
+ if (marker != null) {
+ marker.addTo(layer);
+ }
+ }
+ }
+ }
+}
+
+export { World };
diff --git a/main/squaremap/web/live-atlas/assets/index.1de34b3a.css b/main/squaremap/web/live-atlas/assets/index.1de34b3a.css
new file mode 100644
index 0000000..530e364
--- /dev/null
+++ b/main/squaremap/web/live-atlas/assets/index.1de34b3a.css
@@ -0,0 +1,111 @@
+.svg-icon{pointer-events:none;fill:currentColor;height:24px;width:24px}.world[data-v-219749ac]{display:flex;align-items:center;margin-bottom:.5rem;padding-left:.8rem}.world .world__name[data-v-219749ac]{word-break:break-word;overflow-wrap:break-word}.world .world__maps[data-v-219749ac]{display:flex;flex:0 0 auto;flex-wrap:wrap;max-width:11.1rem;align-items:center;margin-left:auto;padding-left:1rem;padding-right:.2rem;list-style:none;margin-right:-.5rem}.map[data-v-219749ac]{width:3.2rem;height:3.2rem;margin-right:.5rem}.map .svg-icon[data-v-219749ac],.map img[data-v-219749ac]{position:absolute;top:.2rem!important;right:.2rem!important;bottom:.2rem!important;left:.2rem!important;width:calc(100% - .4rem)!important;height:auto!important}#map-context-menu[data-v-074fda55]{position:fixed;z-index:150;min-width:15rem;max-width:22.5rem;top:0;left:0}#map-context-menu ul[data-v-074fda55]{background-color:var(--background-base);box-shadow:var(--box-shadow);color:var(--text-base);border-radius:var(--border-radius);padding:.5rem;position:relative;z-index:1}#map-context-menu[data-v-074fda55] .world{padding:.2rem 0 .2rem .8rem;margin-bottom:0}#map-context-menu[data-v-074fda55] .world li{width:2.8rem;height:2.8rem}@media screen and (max-width: 767px){#map-context-menu[data-v-074fda55]{bottom:.5rem;top:auto;left:0;right:0;margin:auto;width:90vw;max-width:40rem;overflow:auto;transform:none!important}#map-context-menu[data-v-074fda55]:before{content:"";position:fixed;top:auto;right:0;bottom:0;left:0;display:block;height:40rem;background-image:linear-gradient(0deg,var(--background-dark),transparent);z-index:-1}}/*!
+* Copyright 2022 James Lyne
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/.map[data-v-d68e1578]{width:100%;height:100%;background:transparent;z-index:0;cursor:default;box-sizing:border-box;position:relative}.map[data-v-d68e1578]:focus:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;border:.2rem solid var(--outline-focus);display:block;z-index:2000;pointer-events:none}.map[data-v-d68e1578]:focus:not(:focus-visible):before{content:none}.sidebar__section.following[data-v-20ac0cac]{margin-top:auto;flex:0 0 auto;position:sticky;bottom:.2rem;z-index:3}.sidebar__section.following .following__target[data-v-20ac0cac]{display:grid;grid-template-columns:min-content 1fr;grid-template-rows:1fr min-content min-content min-content 1fr;grid-template-areas:"icon ." "icon name" "icon status" "icon location" "icon .";grid-auto-flow:column;align-items:center}.sidebar__section.following .following__target .target__unfollow[data-v-20ac0cac]{position:absolute;top:1.5rem;right:1rem;width:2.5rem;height:2.5rem}.sidebar__section.following .following__target .target__unfollow[data-v-20ac0cac]:before{content:"";position:absolute;display:block;top:-1rem;right:-1rem;bottom:-1rem;left:-1rem}.sidebar__section.following .following__target .target__icon[data-v-20ac0cac]{margin-right:2rem;grid-area:icon}.sidebar__section.following .following__target .target__name[data-v-20ac0cac]{grid-area:name}.sidebar__section.following .following__target .target__status[data-v-20ac0cac]{grid-area:status;font-size:1.3rem}.sidebar__section.following .following__target .target__location[data-v-20ac0cac]{grid-area:location;font-family:monospace;cursor:pointer}.sidebar__section.following .following__target.following__target--hidden .target__icon[data-v-20ac0cac]{filter:grayscale(1);opacity:.5}.sidebar__section.following .following__target>*[data-v-20ac0cac]{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}@media (max-width: 480px),(max-height: 480px){.sidebar__section.following[data-v-20ac0cac]{margin-top:0}}/*!
+* Copyright 2022 James Lyne
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/.sidebar__section{background-color:var(--background-base);color:var(--text-base);border-radius:var(--border-radius);box-shadow:var(--box-shadow);display:flex;flex-direction:column;padding:1.5rem;position:relative}.sidebar__section{margin-bottom:var(--ui-element-spacing);box-sizing:border-box;width:100%;max-width:26rem;flex:0 0 auto}.sidebar__section[hidden]{display:none}.sidebar__section .section__heading{cursor:pointer;user-select:none;text-align:left;align-items:center;position:sticky;top:-.2rem;background-color:inherit;z-index:3;border-radius:inherit;margin:-1.5rem -1.5rem 0;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.sidebar__section .section__heading,.sidebar__section .section__heading button{padding:1.5rem}.sidebar__section .section__heading button{text-overflow:ellipsis;overflow:hidden;display:flex;font-size:2rem;background-color:transparent;font-weight:400;color:inherit;width:calc(100% + 3rem);align-items:center;text-shadow:var(--text-shadow);margin:-1.5rem}.sidebar__section .section__heading button .svg-icon{margin-left:auto;width:1.5rem;height:1.5rem}.sidebar__section .section__heading:hover,.sidebar__section .section__heading:focus-visible,.sidebar__section .section__heading.focus-visible,.sidebar__section .section__heading:active{background-color:inherit}.sidebar__section .section__content{padding:0 .5rem;margin:0 -.5rem 1rem;min-width:0;position:relative}.sidebar__section .section__content:last-child{margin-bottom:0}.sidebar__section .section__search{margin-bottom:1.5rem;padding:.5rem 1rem;box-sizing:border-box;width:100%;position:sticky;top:4.8rem;z-index:3;box-shadow:0 1.5rem .5em var(--background-base)}.sidebar__section .section__search+.section__skeleton{margin-top:0}.sidebar__section .section__skeleton{font-style:italic;color:var(--text-disabled);text-align:center;align-self:center;margin-top:1rem}.sidebar__section.section--collapsible .section__heading .svg-icon{transform:rotate(180deg)}.sidebar__section.section--collapsible.section--collapsed .section__heading .svg-icon{transform:none}.sidebar__section.section--collapsible.section--collapsed .section__heading,.sidebar__section.section--collapsible.section--collapsed .section__heading button{margin-bottom:-1.5rem}.sidebar__section.section--collapsible.section--collapsed .section__content{display:none}@media (max-width: 320px){.sidebar__section{box-sizing:border-box;width:100%}}.player[data-v-1f77067f]{display:flex!important;align-items:center}.player .player__icon[data-v-1f77067f]{position:relative;pointer-events:none;z-index:2;padding-right:1.5rem}.player.player--hidden:not(:hover) .player__name[data-v-1f77067f],.player.player--other-world:not(:hover) .player__name[data-v-1f77067f]{opacity:.5}.player.player--hidden .player__icon[data-v-1f77067f]{filter:grayscale(1)}.player:hover .player__name[data-v-1f77067f] span,.player:focus .player__name[data-v-1f77067f] span,.player:active .player__name[data-v-1f77067f] span{color:inherit!important}/*!
+* Copyright 2022 James Lyne
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/fieldset{appearance:none;border:none;margin:0;padding:0}[data-v-4f945470] .menu{scroll-margin-top:8.2rem}input[type=radio]+.marker[data-v-3a029710]{padding-left:3.9rem}input[type=radio]+.marker .marker__icon[data-v-3a029710]{max-width:1.6rem;position:absolute;top:0;left:.8rem;bottom:0;margin:auto}input[type=radio]+.marker .marker__location[data-v-3a029710]{font-size:1.4rem;font-family:monospace}.markers__back[data-v-1a038d7e]{width:3.2rem;height:3.2rem;flex-grow:0;margin-right:1rem;transform:rotate(90deg)}.markers__header[data-v-1a038d7e]{display:flex;align-items:center;padding-bottom:1rem;position:sticky;top:4.8rem;background-color:var(--background-base);z-index:3}.markers__set[data-v-1a038d7e]{flex:1 1 auto;min-width:0;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;margin:0}[data-v-c7f0f58c] .menu,[data-v-c7f0f58c] .menu input{scroll-margin-top:14.4rem;scroll-margin-bottom:6.5rem}[data-v-c7f0f58c] .section__search{top:9rem}.sidebar{position:fixed;z-index:110;top:0;right:0;bottom:0;display:flex;flex-direction:column;padding:var(--ui-element-spacing);font-size:1.5rem;will-change:transform;pointer-events:none}.sidebar ul,.sidebar ol,.sidebar li{padding:0;list-style:none;margin:0}.sidebar .sidebar__buttons{display:flex;flex-direction:row;align-items:center;justify-content:flex-end;margin-bottom:var(--ui-element-spacing);pointer-events:auto;align-self:flex-end}.sidebar .sidebar__buttons button{width:var(--ui-button-size);height:var(--ui-button-size);box-shadow:var(--box-shadow)}.sidebar .sidebar__buttons button+button{margin-left:var(--ui-element-spacing)}@media (max-width: 480px){.sidebar .sidebar__buttons{flex-direction:column;align-items:flex-end;margin:0;position:absolute;right:var(--ui-element-spacing);top:var(--ui-element-spacing)}.sidebar .sidebar__buttons button+button{margin-left:0;margin-top:var(--ui-element-spacing)}}.sidebar .sidebar__content{position:relative;display:flex;flex-direction:column;flex-shrink:1;min-height:0;overflow:auto;pointer-events:auto;margin-right:-.5rem;padding:.2rem .5rem 0 .2rem;width:26rem;align-items:flex-end;overscroll-behavior:contain;will-change:transform}.sidebar .sidebar__content:not(:hover):not(:focus-within){scrollbar-color:var(--background-base) transparent}.sidebar .sidebar__content:not(:hover):not(:focus-within)::-webkit-scrollbar-thumb{background-color:var(--background-base)}@media (max-width: 480px){.sidebar{padding-right:7rem;padding-top:.8rem}}@media (max-width: 400px){.sidebar{padding-right:6.5rem;padding-top:.3rem;padding-bottom:.3rem}}@media print{.sidebar{display:none}}.message .message__face{display:inline-block;vertical-align:baseline;margin-right:.5rem}.message .message__channel,.message .message__sender{margin-right:.5rem;word-wrap:break-word}.message .message__channel:not(:empty):before{content:"["}.message .message__channel:not(:empty):after{content:"]"}.message .message__sender:not(:empty):after{content:": "}.message .message__content{word-wrap:break-word}.message.message--playerjoin,.message.message--playerleave{font-style:italic}@media (max-width: 320px){.message.message--chat .message__sender:after{content:none}.message.message--chat .message__content{display:block;color:var(--text-emphasis)}}/*!
+* Copyright 2022 James Lyne
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/.chat{background-color:var(--background-base);color:var(--text-base);border-radius:var(--border-radius);box-shadow:var(--box-shadow);display:flex;flex-direction:column;padding:1.5rem;position:relative}.chat{position:absolute;bottom:calc(var(--ui-element-spacing) * 2 + var(--ui-button-size));left:calc(var(--ui-element-spacing) * 2 + var(--ui-button-size));width:50rem;max-width:calc(100% - 8rem);max-height:20rem;display:flex;box-sizing:border-box}.chat .chat__messages{display:flex;flex-direction:column-reverse;list-style:none;overflow:auto;margin:0;padding:0}.chat .chat__messages .message{font-size:1.6rem;line-height:1.9rem}.chat .chat__messages .message+.message{margin-bottom:.5rem}.chat .chat__messages .message.message--skeleton{font-style:italic;color:var(--text-subtle)}.chat .chat__form{display:flex;flex-wrap:wrap;align-items:stretch;margin:1.5rem -1.5rem -1.5rem}.chat .chat__form .chat__input{border-bottom-left-radius:var(--border-radius);flex-grow:1}.chat .chat__form .chat__send{padding-left:1rem;padding-right:1rem;border-radius:0 0 var(--border-radius) 0}.chat .chat__form .chat__error{background-color:var(--background-error);color:var(--text-emphasis);font-size:1.6rem;padding:.5rem 1rem;line-height:2rem;width:100%}.chat .chat__login{font-size:1.6rem;padding:1.2rem;background-color:var(--background-light);color:var(--text-subtle);margin:1.5rem -1.5rem -1.5rem;text-align:left;border-top-left-radius:0;border-top-right-radius:0}@media (max-width: 400px),(max-height: 480px){.chat{max-width:calc(100% - 7rem)}}@media (max-width: 320px){.chat .chat__messages .message+.message{margin-bottom:.7rem}}p[data-v-be3a4872]{white-space:pre-line}/*!
+* Copyright 2022 James Lyne
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/.modal .modal__header,.modal .modal__content{background-color:var(--background-base);color:var(--text-base);border-radius:var(--border-radius);box-shadow:var(--box-shadow);display:flex;flex-direction:column;padding:1.5rem;position:relative}.modal{position:fixed;z-index:120;top:0;right:0;bottom:0;left:0;display:none;align-items:center;flex-direction:column;justify-content:flex-start;padding:10vh 1rem;overflow:auto;pointer-events:none;cursor:default}.modal.modal--backdrop{pointer-events:auto;background-color:#000c}.modal.modal--visible{display:flex}.modal .modal__header,.modal .modal__content{max-width:80rem;box-sizing:border-box;width:100%;padding:2rem;pointer-events:auto}.modal .modal__header{border-bottom-left-radius:0;border-bottom-right-radius:0;position:relative;padding:2rem 3rem 0;text-align:center}.modal .modal__close{position:absolute;top:0;right:0;padding:1rem;width:3.5rem;height:3.5rem;display:block;border-top-left-radius:0;border-bottom-right-radius:0}.modal .modal__close .svg-icon{width:1.5rem!important;height:1.5rem!important}.modal .modal__content{border-top-left-radius:0;border-top-right-radius:0}#modal--login[data-v-9c90fcb6] .modal__content{display:flex;flex-direction:row;flex-wrap:wrap;align-items:flex-start;justify-content:space-between}#modal--login #login__error[data-v-9c90fcb6]{width:100%;text-align:center;margin-bottom:2rem}#modal--login .form[data-v-9c90fcb6]{width:calc(50% - 1.5rem);box-sizing:border-box}@media (max-width: 600px){#modal--login[data-v-9c90fcb6]{flex-direction:column}#modal--login .form[data-v-9c90fcb6]{width:100%}}/*! modern-normalize v1.1.0 | MIT License | https://github.com/sindresorhus/modern-normalize */html{-moz-tab-size:4;tab-size:4}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}hr{height:0;color:inherit}abbr[title]{text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button}::-moz-focus-inner{border-style:none;padding:0}:-moz-focusring{outline:1px dotted ButtonText}:-moz-ui-invalid{box-shadow:none}legend{padding:0}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}.leaflet-pane,.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile-container,.leaflet-pane>svg,.leaflet-pane>canvas,.leaflet-zoom-box,.leaflet-image-layer,.leaflet-layer{position:absolute;left:0;top:0}.leaflet-container{overflow:hidden}.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow{user-select:none}/*!* Safari renders non-retina tile on retina better with this, but Chrome is worse *!
+.leaflet-safari .leaflet-tile {
+ image-rendering: -webkit-optimize-contrast;
+ }
+/*!* hack that prevents hw layers "stretching" when loading new tiles *!
+.leaflet-safari .leaflet-tile-container {
+ width: 1600px;
+ height: 1600px;
+ -webkit-transform-origin: 0 0;
+/* }*/.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-overlay-pane svg{max-width:none!important;max-height:none!important}.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer,.leaflet-container .leaflet-tile{max-width:none!important;max-height:none!important;width:auto;padding:0}.leaflet-container.leaflet-touch-zoom{touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{width:0;height:0;box-sizing:border-box;z-index:800}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-control{position:relative;z-index:800;pointer-events:auto}.leaflet-top,.leaflet-bottom{position:absolute;z-index:1000;pointer-events:none}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-fade-anim .leaflet-popup{opacity:0;transition:opacity .2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{transform-origin:0 0}.leaflet-zoom-anim .leaflet-zoom-animated{transition:transform .25s cubic-bezier(0,0,.25,1)}.leaflet-zoom-anim .leaflet-tile,.leaflet-pan-anim .leaflet-tile{transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-popup-pane,.leaflet-control{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive{cursor:move;cursor:grabbing}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-image-layer,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-marker-icon.leaflet-interactive,.leaflet-image-layer.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:auto}.leaflet-bar a{display:block;text-align:center;text-decoration:none}.leaflet-control-zoom-in,.leaflet-control-zoom-out{font:700 18px Lucida Console,Monaco,monospace;text-indent:1px}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-popup{position:absolute;text-align:center;margin-bottom:20px}.leaflet-popup-content-wrapper{padding:1px;text-align:left}.leaflet-popup-content{line-height:1.3;font-size:13px;font-size:1.08333em;min-height:1px}.leaflet-popup-content p{margin:1.3em 0}.leaflet-popup-tip-container{width:40px;height:20px;position:absolute;left:50%;margin-top:-1px;margin-left:-20px;overflow:hidden;pointer-events:none}.leaflet-popup-tip{width:17px;height:17px;padding:1px;margin:-10px auto 0;pointer-events:auto;transform:rotate(45deg)}.leaflet-container a.leaflet-popup-close-button{position:absolute;top:0;right:0;border:none;font:16px/24px Tahoma,Verdana,sans-serif;color:#757575;text-decoration:none}.leaflet-container a.leaflet-popup-close-button:hover,.leaflet-container a.leaflet-popup-close-button:focus{color:#585858}.leaflet-popup-scrolled{overflow:auto}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{position:absolute;padding:6px;white-space:nowrap;user-select:none;pointer-events:none}.leaflet-tooltip.leaflet-interactive{cursor:pointer;pointer-events:auto}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}@media print{.leaflet-control{-webkit-print-color-adjust:exact;print-color-adjust:exact}}/*!
+ * Copyright 2022 James Lyne
+ *
+ * Some portions of this file were taken from https://github.com/webbukkit/dynmap.
+ * These portions are Copyright 2020 Dynmap Contributors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */button,[type=button],.leaflet-control-button,.leaflet-control-layers-toggle,.leaflet-control-logo,.leaflet-bar a,.leaflet-control a,.leaflet-control button{appearance:none;box-shadow:none;background-color:var(--background-base);color:var(--text-base);border-radius:var(--border-radius);border:none;cursor:pointer;display:block;text-align:center;position:relative;transition:color .2s ease-in,background-color .2s ease-in;font-size:1.6rem;font-family:Raleway,sans-serif;box-sizing:border-box;text-shadow:var(--text-shadow);user-select:none;padding:.8rem .8rem .7rem;line-height:2rem;-webkit-tap-highlight-color:transparent}button .svg-icon,[type=button] .svg-icon,.leaflet-control-button .svg-icon,.leaflet-control-layers-toggle .svg-icon,.leaflet-control-logo .svg-icon,.leaflet-bar a .svg-icon,.leaflet-control a .svg-icon{display:inline-block;max-width:3rem;max-height:3rem;pointer-events:none}button .svg-icon:only-child,[type=button] .svg-icon:only-child,.leaflet-control-button .svg-icon:only-child,.leaflet-control-layers-toggle .svg-icon:only-child,.leaflet-control-logo .svg-icon:only-child,.leaflet-bar a .svg-icon:only-child,.leaflet-control a .svg-icon:only-child{position:absolute;width:calc(100% - 1.6rem);height:calc(100% - 1.6rem);min-width:1.5rem;min-height:1.5rem;top:0;right:0;bottom:0;left:0;margin:auto}button>span,[type=button]>span,.leaflet-control-button>span,.leaflet-control-layers-toggle>span,.leaflet-control-logo>span,.leaflet-bar a>span,.leaflet-control a>span,.leaflet-control button>span{display:block;text-overflow:inherit;overflow:inherit;white-space:inherit}button:hover,[type=button]:hover,.leaflet-control-button:hover,.leaflet-control-layers-toggle:hover,.leaflet-control-logo:hover,.leaflet-bar a:hover,.leaflet-control a:hover{background-color:var(--background-hover);color:var(--text-hover)}button[aria-expanded=true],[aria-expanded=true][type=button],[aria-expanded=true].leaflet-control-button,[aria-expanded=true].leaflet-control-layers-toggle,[aria-expanded=true].leaflet-control-logo,.leaflet-bar a[aria-expanded=true],.leaflet-control a[aria-expanded=true],button[aria-pressed=true],[aria-pressed=true][type=button],[aria-pressed=true].leaflet-control-button,[aria-pressed=true].leaflet-control-layers-toggle,[aria-pressed=true].leaflet-control-logo,.leaflet-bar a[aria-pressed=true],.leaflet-control a[aria-pressed=true]{background-color:var(--background-selected);color:var(--text-selected);font-weight:700;text-shadow:none}button:focus,[type=button]:focus,.leaflet-control-button:focus,.leaflet-control-layers-toggle:focus,.leaflet-control-logo:focus,.leaflet-bar a:focus,.leaflet-control a:focus{outline:var(--outline-focus) auto thick!important;z-index:1;border-color:var(--background-dark)}button:focus:not(:focus-visible),[type=button]:focus:not(:focus-visible),.leaflet-control-button:focus:not(:focus-visible),.leaflet-control-layers-toggle:focus:not(:focus-visible),.leaflet-control-logo:focus:not(:focus-visible),.leaflet-bar a:focus:not(:focus-visible),.leaflet-control a:focus:not(:focus-visible){outline-style:none;z-index:auto}button:active,[type=button]:active,.leaflet-control-button:active,.leaflet-control-layers-toggle:active,.leaflet-control-logo:active,.leaflet-bar a:active,.leaflet-control a:active{background-color:var(--background-active);color:var(--text-active)}button[disabled],[disabled][type=button],[disabled].leaflet-control-button,[disabled].leaflet-control-layers-toggle,[disabled].leaflet-control-logo,.leaflet-bar a[disabled],.leaflet-control a[disabled],button[aria-disabled=true],[aria-disabled=true][type=button],[aria-disabled=true].leaflet-control-button,[aria-disabled=true].leaflet-control-layers-toggle,[aria-disabled=true].leaflet-control-logo,.leaflet-bar a[aria-disabled=true],.leaflet-control a[aria-disabled=true]{background-color:var(--background-disabled);color:var(--text-disabled);cursor:not-allowed}button[disabled]:hover,[disabled][type=button]:hover,[disabled].leaflet-control-button:hover,[disabled].leaflet-control-layers-toggle:hover,[disabled].leaflet-control-logo:hover,.leaflet-bar a[disabled]:hover,.leaflet-control a[disabled]:hover,button[disabled]:active,[disabled][type=button]:active,[disabled].leaflet-control-button:active,[disabled].leaflet-control-layers-toggle:active,[disabled].leaflet-control-logo:active,.leaflet-bar a[disabled]:active,.leaflet-control a[disabled]:active,button[disabled]:focus,[disabled][type=button]:focus,[disabled].leaflet-control-button:focus,[disabled].leaflet-control-layers-toggle:focus,[disabled].leaflet-control-logo:focus,.leaflet-bar a[disabled]:focus,.leaflet-control a[disabled]:focus,button[aria-disabled=true]:hover,[aria-disabled=true][type=button]:hover,[aria-disabled=true].leaflet-control-button:hover,[aria-disabled=true].leaflet-control-layers-toggle:hover,[aria-disabled=true].leaflet-control-logo:hover,.leaflet-bar a[aria-disabled=true]:hover,.leaflet-control a[aria-disabled=true]:hover,button[aria-disabled=true]:active,[aria-disabled=true][type=button]:active,[aria-disabled=true].leaflet-control-button:active,[aria-disabled=true].leaflet-control-layers-toggle:active,[aria-disabled=true].leaflet-control-logo:active,.leaflet-bar a[aria-disabled=true]:active,.leaflet-control a[aria-disabled=true]:active,button[aria-disabled=true]:focus,[aria-disabled=true][type=button]:focus,[aria-disabled=true].leaflet-control-button:focus,[aria-disabled=true].leaflet-control-layers-toggle:focus,[aria-disabled=true].leaflet-control-logo:focus,.leaflet-bar a[aria-disabled=true]:focus,.leaflet-control a[aria-disabled=true]:focus{background-color:var(--background-disabled);color:var(--text-disabled)}.clock,.leaflet-control-layers .leaflet-control-layers-list{background-color:var(--background-base);color:var(--text-base);border-radius:var(--border-radius);box-shadow:var(--box-shadow);display:flex;flex-direction:column;padding:1.5rem;position:relative}.vue-notification-group{z-index:130!important}.vue-notification-group .notification{margin:0 .5rem 1rem;padding:1rem 1.5rem;min-height:var(--ui-button-size);font-size:1.6rem;font-family:Raleway,sans-serif;color:var(--text-base);display:flex;flex-direction:column;justify-content:center;border-radius:var(--border-radius);box-shadow:var(--box-shadow);background:var(--background-base);border-left:none}.vue-notification-group .notification.success{background:#68cd86;border-left-color:#42a85f}.vue-notification-group .notification.warn{background:#ffb648;border-left-color:#f48a06}.vue-notification-group .notification.error{background:var(--background-error);border-left-color:#b82e24}@media screen and (max-width: 767px){.vue-notification-group .vue-notification-wrapper:first-child:before{content:"";position:fixed;bottom:0;left:0;right:0;display:block;height:10rem;background-image:linear-gradient(0deg,var(--background-dark),transparent);z-index:-1}}.leaflet-control{background-color:var(--background-base);border-radius:var(--border-radius);box-shadow:var(--box-shadow);margin:0;box-sizing:border-box;overflow:visible;font-size:1.5rem;flex-shrink:0}.leaflet-control>a:not(:hover):not(:focus):not(:active):not([aria-expanded=true]),.leaflet-control>button:not(:hover):not(:focus):not(:active):not([aria-expanded=true]){background-color:transparent}@media print{.leaflet-control{display:none!important}}.leaflet-bar{display:flex;align-items:center;padding:0;border:none;box-shadow:var(--box-shadow)}.leaflet-bar a{border-radius:0;border-bottom:.1rem solid var(--border-color)}.leaflet-bar a:first-child{border-top-left-radius:var(--border-radius);border-top-right-radius:var(--border-radius)}.leaflet-bar a:last-child{border-bottom-left-radius:var(--border-radius);border-bottom-right-radius:var(--border-radius);border-bottom:none}.leaflet-bar a.leaflet-disabled{background-color:var(--background-disabled);cursor:not-allowed}.leaflet-bar a.leaflet-disabled:hover{color:var(--text-disabled);border-bottom-color:var(--border-color)}.leaflet-bar a:hover{border-bottom-color:var(--border-color)}@media print{.leaflet-bar{display:none!important}}.leaflet-control-button,.leaflet-control-layers-toggle,.leaflet-control-logo,.leaflet-bar a{line-height:3.5rem;width:var(--ui-button-size);height:var(--ui-button-size)}.leaflet-control-zoom{flex-shrink:0}.leaflet-control-zoom a{font-family:sans-serif}.leaflet-control-coordinates{display:flex;align-items:center;padding:.5rem 1.5rem}.leaflet-control-coordinates .value{line-height:1;font-family:monospace;white-space:pre;font-size:2rem}.leaflet-control-coordinates .value[data-label]:before{content:attr(data-label);display:block;line-height:1;margin-bottom:.5rem;font-size:1.25rem;font-family:Raleway,sans-serif}.leaflet-control-coordinates .value+.value{margin-left:2rem}@media (max-width: 600px){.leaflet-control-coordinates .region{display:none}}@media (max-width: 480px),(max-height: 480px){.leaflet-control-coordinates .value{font-size:1.6rem}}@media (max-width: 384px){.leaflet-control-coordinates .chunk{display:none}}.leaflet-control-layers{width:auto;border:none;color:var(--text-base);position:relative}.leaflet-control-layers .leaflet-control-layers-list{display:block;position:absolute;top:0;left:calc(var(--ui-element-spacing) + var(--ui-button-size));overflow:auto;max-width:calc(100vw - 14rem);box-sizing:border-box}@media screen and (max-width: 400px){.leaflet-control-layers .leaflet-control-layers-list{max-width:calc(100vw - 13rem)}}.leaflet-control-layers .leaflet-control-layers-list .leaflet-control-layers-overlays{width:100%;max-width:30rem}.leaflet-control-layers .leaflet-control-layers-list .layer{cursor:pointer;padding:.8rem 0 .7rem}.leaflet-control-layers .leaflet-control-layers-list .layer:first-child{margin-top:-.4rem}.leaflet-control-layers .leaflet-control-layers-list .layer:last-child{margin-bottom:-.4rem}.leaflet-control-logo{flex-shrink:0}.leaflet-control-logo a{height:100%;width:100%;display:flex;align-items:center;justify-content:center}.leaflet-top,.leaflet-bottom,.leaflet-left,.leaflet-right{display:flex}.leaflet-left{padding-left:var(--ui-element-spacing)}.leaflet-right{padding-right:var(--ui-element-spacing)}.leaflet-top{padding-top:var(--ui-element-spacing);flex-direction:column;top:0;bottom:calc(var(--ui-element-spacing) * 2 + var(--ui-button-size));align-items:flex-start;z-index:1003}.leaflet-top .leaflet-control{order:2;min-width:var(--ui-button-size);margin-bottom:var(--ui-element-spacing)}.leaflet-top .leaflet-control:first-child{margin-top:0}.leaflet-top .leaflet-control-loading{order:3}.leaflet-top .leaflet-bar{flex-direction:column}.leaflet-top .leaflet-control-logo{order:1;margin-top:0!important}.leaflet-top .leaflet-control-logo+.leaflet-control-logo{margin-top:var(--ui-element-spacing)!important}.leaflet-top .leaflet-control-bottom{margin-top:auto}.leaflet-top .leaflet-control-bottom:last-child{margin-bottom:0}.leaflet-top .leaflet-control-bottom~.leaflet-control-bottom{margin-top:0}.leaflet-top .leaflet-control-chat{order:1000}.leaflet-top .leaflet-control-login{order:900}@media (max-width: 480px) and (pointer: coarse),(max-height: 480px) and (pointer: coarse),(max-height: 400px){.leaflet-top .leaflet-control-zoom{display:none}.leaflet-top .leaflet-control-zoom+.leaflet-control{margin-top:0}}.leaflet-bottom{padding-bottom:var(--ui-element-spacing);align-items:stretch;z-index:1002}.leaflet-bottom .leaflet-control{order:2}.leaflet-bottom .leaflet-control-link{order:1}.leaflet-bottom.leaflet-left .leaflet-control{margin-right:var(--ui-element-spacing)}.leaflet-center{left:0;right:0;justify-content:center;flex-direction:row;z-index:1001}.leaflet-center .leaflet-control{margin:0}.leaflet-control-loading{cursor:wait;animation:fade .3s linear;animation-fill-mode:forwards}.leaflet-control-loading:hover,.leaflet-control-loading:active,.leaflet-control-loading:focus{background-color:var(--background-base)}.leaflet-control-loading[hidden]{display:none}.leaflet-popup .leaflet-popup-content-wrapper,.leaflet-popup .leaflet-popup-tip{background-color:var(--background-base);color:var(--text-base);box-shadow:var(--box-shadow)}.leaflet-popup .leaflet-popup-content-wrapper{max-height:50vh;display:flex;border-radius:var(--border-radius);box-shadow:var(--box-shadow)}.leaflet-popup .leaflet-popup-content{margin:1rem;overflow:auto;word-break:break-word}.leaflet-popup.leaflet-popup--chat .leaflet-popup-content{margin:.5rem 1rem;display:flex;flex-direction:column}.leaflet-container a.leaflet-popup-close-button{background-color:var(--background-base);border-radius:var(--border-radius);width:2rem;height:2rem;top:-1rem;right:-1rem;text-align:center;padding:0;line-height:2rem}@media print{.leaflet-popup{display:none}}.leaflet-tooltip{background-color:var(--background-base);color:var(--text-base);box-shadow:var(--box-shadow);border-radius:var(--border-radius);border:none;will-change:transform}.leaflet-tooltip:before{content:none}/*!
+ * Copyright 2022 James Lyne
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */.map .marker{display:flex;align-items:center}.map .marker.marker--player{transition:transform .3s ease-in 0s;height:1.2rem;margin-left:-.6rem;margin-top:-.6rem}.map .marker.marker--player:before{content:"";display:block;width:.8rem;height:.8rem;position:absolute;left:0;background-color:var(--text-emphasis);border:.2rem solid var(--background-marker);border-radius:50%}.map .marker.marker--player .marker__label{display:grid!important;grid-template-columns:min-content 1fr;grid-template-rows:1fr min-content min-content min-content 1fr;grid-auto-flow:column;margin-left:1.6rem;background-clip:padding-box;padding:.2rem .5rem;align-items:center}.map .marker.marker--player .player__icon{grid-row:1/-1;margin-right:.5rem;margin-left:-.3rem}.map .marker.marker--player .player__name{grid-row:2}.map .marker.marker--player .player__name:not(:last-child){padding-bottom:.2rem}.map .marker.marker--player .player__health,.map .marker.marker--player .player__armor{width:7rem;height:.7rem;box-sizing:content-box}.map .marker.marker--player .player__health,.map .marker.marker--player .player__health::-webkit-meter-inner-element,.map .marker.marker--player .player__health::-webkit-meter-bar,.map .marker.marker--player .player__armor,.map .marker.marker--player .player__armor::-webkit-meter-inner-element,.map .marker.marker--player .player__armor::-webkit-meter-bar{border-radius:0;background:none;border:none;box-shadow:none;image-rendering:crisp-edges;image-rendering:pixelated;display:block;top:0;bottom:0}.map .marker.marker--player .player__health{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHBAMAAAA2fErgAAAAD1BMVEUAAAAXFhZFRkgsLC5hY2bAyCi+AAAAAXRSTlMAQObYZgAAAChJREFUCNdjYBQQZGAQUVQSYBByAhLCSsoCDIxGhgwMDMICQIKRgQEAKHQCAAUrkucAAAAASUVORK5CYII=) repeat-x left center}.map .marker.marker--player .player__health::-webkit-meter-optimum-value{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHBAMAAAA2fErgAAAAD1BMVEUAAAArAgvaJCSsCy/sV0SEKlj7AAAAAXRSTlMAQObYZgAAAChJREFUCNdjYBQQZGAQUVQSYBByAhLCSsoCDIxGhgwMDMICQIKRgQEAKHQCAAUrkucAAAAASUVORK5CYII=) repeat-x left center}.map .marker.marker--player .player__health::-moz-meter-bar{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHBAMAAAA2fErgAAAAD1BMVEUAAAArAgvaJCSsCy/sV0SEKlj7AAAAAXRSTlMAQObYZgAAAChJREFUCNdjYBQQZGAQUVQSYBByAhLCSsoCDIxGhgwMDMICQIKRgQEAKHQCAAUrkucAAAAASUVORK5CYII=) repeat-x left center}.map .marker.marker--player .player__armor{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHBAMAAAA2fErgAAAAD1BMVEUAAAAUFBVFRkgsLC5hY2YEDfiMAAAAAXRSTlMAQObYZgAAACdJREFUCNdjYBQQZGAQUlQSYBBSURZgYHQyZGBgVAIRxkCCQVCAAQAn5gI/LmmazgAAAABJRU5ErkJggg==) repeat-x left center}.map .marker.marker--player .player__armor::-webkit-meter-optimum-value{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHBAMAAAA2fErgAAAAD1BMVEUAAAAUFBW/wcSLjI7z9PWBWai1AAAAAXRSTlMAQObYZgAAACdJREFUCNdjYBQQZGAQUlQSYBBSURZgYHQyZGBgVAIRxkCCQVCAAQAn5gI/LmmazgAAAABJRU5ErkJggg==) repeat-x left center}.map .marker.marker--player .player__armor::-moz-meter-bar{background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHBAMAAAA2fErgAAAAD1BMVEUAAAAUFBW/wcSLjI7z9PWBWai1AAAAAXRSTlMAQObYZgAAACdJREFUCNdjYBQQZGAQUlQSYBBSURZgYHQyZGBgVAIRxkCCQVCAAQAn5gI/LmmazgAAAABJRU5ErkJggg==) repeat-x left center}.map .marker.marker--player .player__yaw{--player-yaw: 0deg;position:relative;transform:rotate(var(--player-yaw));transition:.1s transform ease-in}.map .marker.marker--player .player__yaw,.map .marker.marker--player .player__yaw:after{position:absolute;width:0;height:0;border-style:solid;border-color:transparent transparent var(--background-marker);border-width:0 .7rem 2rem .7rem}.map .marker.marker--player .player__yaw:after{content:"";top:.5rem;left:-.4rem;border-width:0 .4rem 1.3rem .4rem;border-bottom-color:var(--text-emphasis)}.no-animations .marker.marker--player .player__yaw{transition:none}.map .marker.marker--player.marker--compact .marker__label{background-color:transparent}.map .marker.marker--player.marker--compact .player__icon{margin-right:.2rem}.map .marker.marker--player.marker--compact .player__armor,.map .marker.marker--player.marker--compact .player__health,.map .marker.marker--player.marker--compact .player__name{background-color:var(--background-marker)}.map .marker.marker--player.marker--compact .player__name{padding:.2rem}.map .marker.marker--player.marker--compact .player__armor,.map .marker.marker--player.marker--compact .player__health{border:.2rem solid var(--background-marker);border-top-width:0;border-bottom-width:0}.map .marker.marker--player.marker--compact .player__armor:last-child,.map .marker.marker--player.marker--compact .player__health:last-child{border-bottom-width:.2rem}.map .marker.marker--player.player--yaw:before{content:none}.map .marker.marker--player.player--yaw .marker__label{margin-left:1.8rem}.map .marker .marker__label{flex:0 0 auto;z-index:20;font-size:1.5rem;line-height:1;white-space:pre-line;color:var(--text-marker);background:var(--background-marker);padding:.2rem;display:none;width:max-content;max-width:30rem;position:absolute;left:calc(100% + .2rem)}.leaflet-pane--show-labels .marker__label{display:block!important}.map .marker.marker--auto-size .marker__icon{margin-top:-50%;margin-left:-50%}.map .marker.marker--auto-size .marker__label{margin-top:-50%;left:calc(50% + .2rem)}.map .marker:hover,.map .marker:focus{z-index:1000}.map .marker:hover .marker__label,.map .marker:focus .marker__label{display:block;outline:auto}.map .marker:focus .marker__label{outline:auto}.map .marker:focus:not(:focus-visible){z-index:auto}.map .marker:focus:not(:focus-visible) .marker__label{display:none}.no-animations .marker.marker--player{transition:none}@media print{.map .marker{display:none!important}}.leaflet-container{font-family:inherit;font-size:1.5rem;font-weight:inherit;line-height:1}.leaflet-container *:focus{outline-style:auto!important}.leaflet-container *:focus:not(:focus-visible){outline-style:none!important}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@font-face{font-family:Raleway;font-style:normal;font-weight:400;font-display:swap;src:url(https://fonts.gstatic.com/s/raleway/v22/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVvaorCIPrE.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Raleway;font-style:normal;font-weight:700;font-display:swap;src:url(https://fonts.gstatic.com/s/raleway/v22/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVsEpbCIPrE.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}:root{--ui-button-size: 5rem;--ui-element-spacing: 1rem}@media (max-width: 400px),(max-height: 480px){:root{--ui-button-size: 4.4rem;--ui-element-spacing: .7rem}}h1,h2,h3,h4,h5,h6{font-weight:400;margin-top:0;color:var(--text-emphasis)}h1{font-size:3rem}h2{font-size:2rem;line-height:2.4rem;margin-bottom:1rem}a{color:var(--text-base)}input{appearance:none;background-color:var(--background-light);box-shadow:none;color:var(--text-base);font-size:1.6rem;padding:1rem;border:.2rem solid var(--border-color);border-radius:.3rem}input:focus{color:var(--text-emphasis);outline-color:var(--text-emphasis)}input:focus:not(:focus-visible){color:var(--text-base)}input[disabled]{background-color:var(--background-disabled);border-color:var(--border-color);cursor:not-allowed}:focus:not(:focus-visible){outline:none!important}:focus{outline:var(--outline-focus) auto thick!important}:focus-visible{outline:var(--outline-focus) auto thick!important}.checkbox{display:flex;position:relative;align-items:center}.checkbox:before,.checkbox svg,.checkbox input[type=checkbox]{position:absolute;top:0;bottom:0;left:0;margin:auto 1rem auto 0}.checkbox:before{content:"";width:2.2rem;height:2.2rem;border:.2rem solid #cccccc;border-radius:.3rem;background-color:transparent;box-sizing:border-box;z-index:0}.checkbox input[type=checkbox]{width:2.4rem;height:2.4rem;opacity:0;z-index:2}.checkbox input[type=checkbox]:checked~span{color:var(--text-base)}.checkbox input[type=checkbox]:checked+svg{opacity:1}.checkbox input[type=checkbox]:focus~span:after{content:"";outline:var(--outline-focus) auto thick;position:absolute;top:0;right:-.5rem;bottom:0;left:-.5rem;border-radius:.5rem}.checkbox input[type=checkbox]:focus:not(:focus-visible)~span:after{content:none}.checkbox svg{opacity:0;transition:opacity .2s ease-in;z-index:1}.checkbox span{color:var(--text-subtle);padding-left:3rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}.menu{list-style:none;margin:0;padding:0;font-size:1.6rem;min-width:0}.menu ul{list-style:none;margin:0;padding:0}.menu>li{display:flex;position:relative}.menu>li>button{background-color:transparent;flex-grow:1}.menu>input[type=radio]{clip:rect(1px,1px,1px,1px);height:1px;width:1px;position:absolute;padding:0;border:none}.menu>input[type=radio]+label{appearance:none;box-shadow:none;background-color:var(--background-base);color:var(--text-base);border-radius:var(--border-radius);border:none;cursor:pointer;display:block;text-align:center;position:relative;transition:color .2s ease-in,background-color .2s ease-in;font-size:1.6rem;font-family:Raleway,sans-serif;box-sizing:border-box;text-shadow:var(--text-shadow);user-select:none;padding:.8rem .8rem .7rem;line-height:2rem;-webkit-tap-highlight-color:transparent;background-color:transparent;margin-bottom:.1rem}.menu>input[type=radio]+label .svg-icon{display:inline-block;max-width:3rem;max-height:3rem;pointer-events:none}.menu>input[type=radio]+label .svg-icon:only-child{position:absolute;width:calc(100% - 1.6rem);height:calc(100% - 1.6rem);min-width:1.5rem;min-height:1.5rem;top:0;right:0;bottom:0;left:0;margin:auto}.menu>input[type=radio]+label>span{display:block;text-overflow:inherit;overflow:inherit;white-space:inherit}.menu>input[type=radio]+label:hover{background-color:var(--background-hover);color:var(--text-hover);position:relative}.menu>input[type=radio]:checked+label{background-color:var(--background-selected);color:var(--text-selected);font-weight:700;text-shadow:none;position:relative}.menu>input[type=radio]:focus+label{outline:var(--outline-focus) auto thick!important;z-index:1;border-color:var(--background-dark)}.menu>input[type=radio]:focus:not(:focus-visible)+label{z-index:auto;outline:none!important}.menu>input[type=radio]:active+label{background-color:var(--background-active);color:var(--text-active)}.menu>input[type=radio]:disabled+label{background-color:var(--background-disabled);color:var(--text-disabled);cursor:not-allowed}.menu>input[type=radio]:disabled+label:hover,.menu>input[type=radio]:disabled+label:active,.menu>input[type=radio]:disabled+label:focus{background-color:var(--background-disabled);color:var(--text-disabled)}.menu>li>button,.menu>input[type=radio]+label,.menu>button{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;text-align:left;border-radius:.5rem}.menu>button{width:100%;text-align:center}img{image-rendering:crisp-edges;image-rendering:pixelated}.clock{position:relative;width:15rem;height:6rem;z-index:50;font-family:monospace;display:flex;flex-direction:column;align-items:center;padding:.5rem 2rem;overflow:hidden}.clock .clock__time{text-align:center;font-size:2rem;line-height:2rem;margin-top:auto;background-color:var(--background-base);z-index:1;padding:.1rem .1rem 0;border-radius:.3rem}.clock .clock__time.night{color:var(--text-night)}.clock .clock__time.day{color:var(--text-day)}.clock .clock__time.night,.clock .clock__time.day{transition:color 8s 8s linear}.clock .clock__sun,.clock .clock__moon{position:absolute;top:0;bottom:0;left:0;right:0}.clock .clock__sun svg,.clock .clock__moon svg{width:15rem;height:12rem}.clock.clock--digital{justify-content:center;height:var(--ui-button-size);width:auto}.clock.clock--digital .clock__sun,.clock.clock--digital .clock__moon{display:none}.clock.clock--digital .clock__time{margin:0;font-size:3rem}@media (max-width: 480px),(max-height: 480px){.clock{transform:scale(.8333333333);transform-origin:top center}}.form .form__group{margin-bottom:1.5rem;display:flex;flex-direction:column}.form .form__label{font-size:1.6rem;margin-bottom:.5rem;color:var(--text-emphasis)}.form.form--invalid input:invalid{border-color:var(--border-error)}.alert{display:flex;flex-direction:column;padding:1rem;background-color:var(--background-error);border-radius:var(--border-radius)}@media print{@page{size:297mm 210mm}}
diff --git a/main/squaremap/web/live-atlas/assets/index.a82d2095.js b/main/squaremap/web/live-atlas/assets/index.a82d2095.js
new file mode 100644
index 0000000..3d9bd57
--- /dev/null
+++ b/main/squaremap/web/live-atlas/assets/index.a82d2095.js
@@ -0,0 +1,59 @@
+var $o=Object.defineProperty,Uo=Object.defineProperties;var Po=Object.getOwnPropertyDescriptors;var Ht=Object.getOwnPropertySymbols;var No=Object.prototype.hasOwnProperty,Ro=Object.prototype.propertyIsEnumerable;var Wt=(e,t,o)=>t in e?$o(e,t,{enumerable:!0,configurable:!0,writable:!0,value:o}):e[t]=o,Vt=(e,t)=>{for(var o in t||(t={}))No.call(t,o)&&Wt(e,o,t[o]);if(Ht)for(var o of Ht(t))Ro.call(t,o)&&Wt(e,o,t[o]);return e},Zt=(e,t)=>Uo(e,Po(t));import{n as ne,l as u,c as d,w as M,a as te,b as Oo,d as T,o as F,r as E,e as V,f as b,g as Q,F as O,h as w,i as m,j as A,t as Do,k as x,m as C,p as f,q as ie,s as W,v as ft,u as ce,x as J,y as Bo,z as Qt,A as nt,B as Z,C as Xe,D as re,E as j,G as Ho,H as Wo,I as Vo,J as Zo,K as jo,L as jt,M as Fo,N as Go,V as Yo}from"./vendor.d0ab50b1.js";const Ko=function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const i of r)if(i.type==="childList")for(const n of i.addedNodes)n.tagName==="LINK"&&n.rel==="modulepreload"&&a(n)}).observe(document,{childList:!0,subtree:!0});function o(r){const i={};return r.integrity&&(i.integrity=r.integrity),r.referrerpolicy&&(i.referrerPolicy=r.referrerpolicy),r.crossorigin==="use-credentials"?i.credentials="include":r.crossorigin==="anonymous"?i.credentials="omit":i.credentials="same-origin",i}function a(r){if(r.ep)return;r.ep=!0;const i=o(r);fetch(r.href,i)}};Ko();const Jt=["chatNoMessages","chatTitle","chatLogin","chatSend","chatPlaceholder","chatErrorDisabled","chatErrorUnknown","serversHeading","markersHeading","markersSkeleton","markersSetSkeleton","markersUnnamed","markersSearchPlaceholder","markersSearchSkeleton","worldsSkeleton","playersSkeleton","playersTitle","playersTitleHidden","playersTitleOtherWorld","playersSearchPlaceholder","playersSearchSkeleton","followingHeading","followingUnfollow","followingTitleUnfollow","followingHidden","linkTitle","loadingTitle","locationRegion","locationChunk","contextMenuCopyLink","contextMenuCenterHere","toggleTitle","mapTitle","layersTitle","copyToClipboardSuccess","copyToClipboardError","loginTitle","loginHeading","loginUsernameLabel","loginPasswordLabel","loginSubmit","loginErrorUnknown","loginErrorDisabled","loginErrorIncorrect","loginSuccess","registerHeading","registerDescription","registerConfirmPasswordLabel","registerCodeLabel","registerSubmit","registerErrorUnknown","registerErrorDisabled","registerErrorVerifyFailed","registerErrorIncorrect","logoutTitle","logoutErrorUnknown","logoutSuccess","closeTitle","showMore"],qo=["chatPlayerJoin","chatPlayerQuit","chatAnonymousJoin","chatAnonymousQuit","chatErrorNotAllowed","chatErrorRequiresLogin","chatErrorCooldown","worldsHeading","playersHeading"],Qo=document.createRange(),Jo=/ /g,Xt=/§[0-9a-f]/ig,Xo=/[_\s]?nether([\s_]|$)/i,ea=/(^|[_\s])end([\s_]|$)/i,Et=e=>{const t=e>=0&&e<13700;return{serverTime:e,days:Math.floor((e+8e3)/24e3),hours:(Math.floor(e/1e3)+6)%24,minutes:Math.floor(e/1e3%1*60),seconds:Math.floor(e/1e3%1*60%1*60),day:t,night:!t}},ta=e=>{const t=new URLSearchParams(e.search),o=e.hash.replace("#","");return o?oa(o):aa(t)},oa=e=>{let t,o,a,r;if(e=e.replace("#",""),e[0]==="/"&&e.split("/").length===7){const i=e.split("/");r=void 0,t=i[5],o=i[6],a=[i[1],i[2],i[3]].map(n=>parseFloat(n)).filter(n=>!isNaN(n)&&isFinite(n))}else{const i=e.split(";");t=i[0]||void 0,o=i[1]||void 0,a=(i[2]||"").split(",").map(n=>parseFloat(n)).filter(n=>!isNaN(n)&&isFinite(n)),r=typeof i[3]!="undefined"?parseInt(i[3]):void 0}return eo({world:t?decodeURIComponent(t):void 0,map:o?decodeURIComponent(o):void 0,location:a.length===3?{x:a[0],y:a[1],z:a[2]}:void 0,zoom:r,legacy:!1})},aa=e=>{let t=e.get("worldname")||e.get("world")||void 0,o=e.has("worldname")&&e.get("mapname")||void 0;const a=[e.get("x")||"",e.get("y")||"64",e.get("z")||""].map(i=>parseFloat(i)).filter(i=>!isNaN(i)&&isFinite(i)),r=e.has("zoom")?parseInt(e.get("zoom")):void 0;return t=t?decodeURIComponent(t):void 0,o=o?decodeURIComponent(o):void 0,eo({world:t,map:o,location:a.length===3?{x:a[0],y:a[1],z:a[2]}:void 0,zoom:r,legacy:!0})},eo=e=>(typeof e.zoom!="undefined"&&(isNaN(e.zoom)||e.zoom<0||!isFinite(e.zoom))&&(e.zoom=void 0),e.world?e:null),to=(e,t,o)=>{const a=Math.round(t.x),r=Math.round(t.y),i=Math.round(t.z),n=`${a},${r},${i}`;return e?`#${e.world.name};${e.name};${n};${o}`:""},ra=e=>{const t=document.querySelector(e);t&&t.focus()},Ft=document.createElement("textarea"),na=e=>(Ft.innerHTML=e,Ft.textContent||""),ee=e=>Qo.createContextualFragment(e.replace(Jo," ")).textContent||"",oo=()=>()=>ne(_().state.messages.copyToClipboardSuccess),ao=()=>e=>{ne({type:"error",text:_().state.messages.copyToClipboardError}),console.error("Error copying to clipboard",e)},ia=(e={})=>Object.assign(At(Jt,e),At(qo,e)),sa=(e={})=>At(Jt,e),At=(e,t={})=>{const o={};for(const a of e)o[a]=t[a]||`Missing message: ${a}`;return o},ro=(e,t,o)=>({min:{x:Math.min.apply(null,e),y:Math.min.apply(null,t),z:Math.min.apply(null,o)},max:{x:Math.max.apply(null,e),y:Math.max.apply(null,t),z:Math.max.apply(null,o)}}),xt=e=>{const t={max:{x:-1/0,y:-1/0,z:-1/0},min:{x:1/0,y:1/0,z:1/0}},o=a=>{Array.isArray(a)?a.map(o):(t.max.x=Math.max(a.x,t.max.x),t.max.y=Math.max(a.y,t.max.y),t.max.z=Math.max(a.z,t.max.z),t.min.x=Math.min(a.x,t.min.x),t.min.y=Math.min(a.y,t.min.y),t.min.z=Math.min(a.z,t.min.z))};return e.map(o),t},et=e=>({x:e.min.x+(e.max.x-e.min.x)/2,y:e.min.y+(e.max.y-e.min.y)/2,z:e.min.z+(e.max.z-e.min.z)/2}),la=async()=>{const e=document.createElement("iframe");return e.hidden=!0,e.sandbox.add("allow-scripts"),e.srcdoc=`