Changed: Squaremap configs support LiveAtlas & Squaremap Markers

This commit is contained in:
2024-12-10 22:53:03 +00:00
parent 9e6198b15a
commit b1cb21fddf
162 changed files with 2337 additions and 2 deletions

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 };

View File

@@ -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 = `<img src='${this.getHeadUrl()}' class="head" />`;
}
if (P.worldList.curWorld.player_tracker.nameplates.show_armor && player.armor != null) {
armorImg = `<img src="images/armor/${Math.min(Math.max(player.armor, 0), 20)}.png" class="armor" />`;
}
if (P.worldList.curWorld.player_tracker.nameplates.show_health && player.health != null) {
healthImg = `<img src="images/health/${Math.min(Math.max(player.health, 0), 20)}.png" class="health" />`;
}
this.tooltip.setContent(`<ul><li>${headImg}</li><li>${this.displayName}${healthImg}${armorImg}</li>`);
}
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 };

View File

@@ -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 };