Add MapProvider, move dynmap api handling to DynmapMapProvider
This commit is contained in:
parent
c99215e259
commit
33b5b305e2
45
src/App.vue
45
src/App.vue
@ -43,21 +43,19 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore(),
|
const store = useStore(),
|
||||||
updateInterval = computed(() => store.state.configuration.updateInterval),
|
|
||||||
title = computed(() => store.state.configuration.title),
|
title = computed(() => store.state.configuration.title),
|
||||||
currentUrl = computed(() => store.getters.url),
|
currentUrl = computed(() => store.getters.url),
|
||||||
currentServer = computed(() => store.state.currentServer),
|
currentServer = computed(() => store.state.currentServer),
|
||||||
configurationHash = computed(() => store.state.configurationHash),
|
configurationHash = computed(() => store.state.configurationHash),
|
||||||
chatBoxEnabled = computed(() => store.state.components.chatBox),
|
chatBoxEnabled = computed(() => store.state.components.chatBox),
|
||||||
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
|
chatVisible = computed(() => store.state.ui.visibleElements.has('chat')),
|
||||||
updatesEnabled = ref(false),
|
|
||||||
updateTimeout = ref(0),
|
|
||||||
configAttempts = ref(0),
|
configAttempts = ref(0),
|
||||||
|
|
||||||
loadConfiguration = async () => {
|
loadConfiguration = async () => {
|
||||||
try {
|
try {
|
||||||
await store.dispatch(ActionTypes.LOAD_CONFIGURATION, undefined);
|
await store.dispatch(ActionTypes.LOAD_CONFIGURATION, undefined);
|
||||||
startUpdates();
|
await store.dispatch(ActionTypes.START_UPDATES, undefined);
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
hideSplash();
|
hideSplash();
|
||||||
|
|
||||||
@ -80,36 +78,6 @@ export default defineComponent({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
startUpdates = () => {
|
|
||||||
updatesEnabled.value = true;
|
|
||||||
update();
|
|
||||||
},
|
|
||||||
|
|
||||||
update = async () => {
|
|
||||||
//TODO: Error notification for repeated failures?
|
|
||||||
try {
|
|
||||||
await store.dispatch(ActionTypes.GET_UPDATE, undefined);
|
|
||||||
} finally {
|
|
||||||
if(updatesEnabled.value) {
|
|
||||||
if(updateTimeout.value) {
|
|
||||||
clearTimeout(updateTimeout.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTimeout.value = setTimeout(() => update(), updateInterval.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
stopUpdates = () => {
|
|
||||||
updatesEnabled.value = false;
|
|
||||||
|
|
||||||
if (updateTimeout.value) {
|
|
||||||
clearTimeout(updateTimeout.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateTimeout.value = 0;
|
|
||||||
},
|
|
||||||
|
|
||||||
handleUrl = () => {
|
handleUrl = () => {
|
||||||
const parsedUrl = parseUrl();
|
const parsedUrl = parseUrl();
|
||||||
|
|
||||||
@ -174,7 +142,6 @@ export default defineComponent({
|
|||||||
watch(currentUrl, (url) => window.history.replaceState({}, '', url));
|
watch(currentUrl, (url) => window.history.replaceState({}, '', url));
|
||||||
watch(currentServer, (newServer?: LiveAtlasServerDefinition) => {
|
watch(currentServer, (newServer?: LiveAtlasServerDefinition) => {
|
||||||
showSplash();
|
showSplash();
|
||||||
stopUpdates();
|
|
||||||
|
|
||||||
if(!newServer) {
|
if(!newServer) {
|
||||||
return;
|
return;
|
||||||
@ -190,17 +157,17 @@ export default defineComponent({
|
|||||||
window.history.replaceState({}, '', newServer.id);
|
window.history.replaceState({}, '', newServer.id);
|
||||||
loadConfiguration();
|
loadConfiguration();
|
||||||
}, {deep: true});
|
}, {deep: true});
|
||||||
watch(configurationHash, (newHash, oldHash) => {
|
watch(configurationHash, async (newHash, oldHash) => {
|
||||||
if(newHash && oldHash) {
|
if(newHash && oldHash) {
|
||||||
showSplash();
|
showSplash();
|
||||||
stopUpdates();
|
|
||||||
store.commit(MutationTypes.CLEAR_PARSED_URL, undefined);
|
store.commit(MutationTypes.CLEAR_PARSED_URL, undefined);
|
||||||
loadConfiguration();
|
await store.dispatch(ActionTypes.STOP_UPDATES, undefined);
|
||||||
|
await loadConfiguration();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
onMounted(() => loadConfiguration());
|
onMounted(() => loadConfiguration());
|
||||||
onBeforeUnmount(() => stopUpdates());
|
onBeforeUnmount(() => store.dispatch(ActionTypes.STOP_UPDATES, undefined));
|
||||||
|
|
||||||
handleUrl();
|
handleUrl();
|
||||||
onResize();
|
onResize();
|
||||||
|
777
src/api.ts
777
src/api.ts
@ -1,777 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
|
||||||
DynmapArea,
|
|
||||||
DynmapChat,
|
|
||||||
DynmapCircle,
|
|
||||||
DynmapComponentConfig,
|
|
||||||
DynmapConfigurationResponse,
|
|
||||||
DynmapLine,
|
|
||||||
DynmapMarker,
|
|
||||||
DynmapMarkerSet,
|
|
||||||
DynmapMarkerSetUpdates,
|
|
||||||
DynmapPlayer,
|
|
||||||
DynmapServerConfig,
|
|
||||||
DynmapTileUpdate,
|
|
||||||
DynmapUpdate,
|
|
||||||
DynmapUpdateResponse,
|
|
||||||
DynmapUpdates
|
|
||||||
} from "@/dynmap";
|
|
||||||
import {useStore} from "@/store";
|
|
||||||
import ChatError from "@/errors/ChatError";
|
|
||||||
import {LiveAtlasDimension, LiveAtlasServerMessageConfig, LiveAtlasWorldDefinition} from "@/index";
|
|
||||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
|
||||||
|
|
||||||
const titleColours = /§[0-9a-f]/ig,
|
|
||||||
netherWorldName = /_?nether(_|$)/i,
|
|
||||||
endWorldName = /(^|_)end(_|$)/i;
|
|
||||||
|
|
||||||
function buildServerConfig(response: any): DynmapServerConfig {
|
|
||||||
return {
|
|
||||||
version: response.dynmapversion || '',
|
|
||||||
grayHiddenPlayers: response.grayplayerswhenhidden || false,
|
|
||||||
defaultMap: response.defaultmap || undefined,
|
|
||||||
defaultWorld: response.defaultworld || undefined,
|
|
||||||
defaultZoom: response.defaultzoom || 0,
|
|
||||||
followMap: response.followmap || undefined,
|
|
||||||
followZoom: response.followzoom || 0,
|
|
||||||
updateInterval: response.updaterate || 3000,
|
|
||||||
showLayerControl: response.showlayercontrol && response.showlayercontrol !== 'false', //Sent as a string for some reason
|
|
||||||
title: response.title.replace(titleColours, '') || 'Dynmap',
|
|
||||||
loginEnabled: response['login-enabled'] || false,
|
|
||||||
maxPlayers: response.maxcount || 0,
|
|
||||||
expandUI: response.sidebaropened && response.sidebaropened !== 'false', //Sent as a string for some reason
|
|
||||||
hash: response.confighash || 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMessagesConfig(response: any): LiveAtlasServerMessageConfig {
|
|
||||||
return {
|
|
||||||
chatPlayerJoin: response.joinmessage || '',
|
|
||||||
chatPlayerQuit: response.quitmessage || '',
|
|
||||||
chatAnonymousJoin: response['msg-hiddennamejoin'] || '',
|
|
||||||
chatAnonymousQuit: response['msg-hiddennamequit'] || '',
|
|
||||||
chatErrorNotAllowed: response['msg-chatnotallowed'] || '',
|
|
||||||
chatErrorRequiresLogin: response['msg-chatrequireslogin'] || '',
|
|
||||||
chatErrorCooldown: response.spammessage || '',
|
|
||||||
worldsHeading: response['msg-maptypes'] || '',
|
|
||||||
playersHeading: response['msg-players'] || '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildWorlds(response: any): Array<LiveAtlasWorldDefinition> {
|
|
||||||
const worlds: Map<string, LiveAtlasWorldDefinition> = new Map<string, LiveAtlasWorldDefinition>();
|
|
||||||
|
|
||||||
//Get all the worlds first so we can handle append_to_world properly
|
|
||||||
(response.worlds || []).forEach((world: any) => {
|
|
||||||
let worldType: LiveAtlasDimension = 'overworld';
|
|
||||||
|
|
||||||
if (netherWorldName.test(world.name) || (world.name == 'DIM-1')) {
|
|
||||||
worldType = 'nether';
|
|
||||||
} else if (endWorldName.test(world.name) || (world.name == 'DIM1')) {
|
|
||||||
worldType = 'end';
|
|
||||||
}
|
|
||||||
|
|
||||||
worlds.set(world.name, {
|
|
||||||
seaLevel: world.sealevel || 64,
|
|
||||||
name: world.name,
|
|
||||||
dimension: worldType,
|
|
||||||
protected: world.protected || false,
|
|
||||||
title: world.title || '',
|
|
||||||
height: world.height || 256,
|
|
||||||
center: {
|
|
||||||
x: world.center.x || 0,
|
|
||||||
y: world.center.y || 0,
|
|
||||||
z: world.center.z || 0
|
|
||||||
},
|
|
||||||
maps: new Map(),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
(response.worlds || []).forEach((world: any) => {
|
|
||||||
(world.maps || []).forEach((map: any) => {
|
|
||||||
const worldName = map.append_to_world || world.name,
|
|
||||||
w = worlds.get(worldName);
|
|
||||||
|
|
||||||
if(!w) {
|
|
||||||
console.warn(`Ignoring map '${map.name}' associated with non-existent world '${worldName}'`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
w.maps.set(map.name, new LiveAtlasMapDefinition({
|
|
||||||
world: world, //Ignore append_to_world here otherwise things break
|
|
||||||
background: map.background || '#000000',
|
|
||||||
backgroundDay: map.backgroundday || '#000000',
|
|
||||||
backgroundNight: map.backgroundnight || '#000000',
|
|
||||||
icon: map.icon || undefined,
|
|
||||||
imageFormat: map['image-format'] || 'png',
|
|
||||||
name: map.name || '(Unnamed map)',
|
|
||||||
nightAndDay: map.nightandday || false,
|
|
||||||
prefix: map.prefix || '',
|
|
||||||
protected: map.protected || false,
|
|
||||||
title: map.title || '',
|
|
||||||
mapToWorld: map.maptoworld || undefined,
|
|
||||||
worldToMap: map.worldtomap || undefined,
|
|
||||||
nativeZoomLevels: map.mapzoomout || 1,
|
|
||||||
extraZoomLevels: map.mapzoomin || 0
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return Array.from(worlds.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildComponents(response: any): DynmapComponentConfig {
|
|
||||||
const components: DynmapComponentConfig = {
|
|
||||||
markers: {
|
|
||||||
showLabels: false,
|
|
||||||
},
|
|
||||||
chatBox: undefined,
|
|
||||||
chatBalloons: false,
|
|
||||||
playerMarkers: undefined,
|
|
||||||
coordinatesControl: undefined,
|
|
||||||
linkControl: false,
|
|
||||||
clockControl: undefined,
|
|
||||||
logoControls: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
(response.components || []).forEach((component: any) => {
|
|
||||||
const type = component.type || "unknown";
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case "markers":
|
|
||||||
components.markers = {
|
|
||||||
showLabels: component.showlabel || false,
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "playermarkers":
|
|
||||||
components.playerMarkers = {
|
|
||||||
hideByDefault: component.hidebydefault || false,
|
|
||||||
layerName: component.label || "Players",
|
|
||||||
layerPriority: component.layerprio || 0,
|
|
||||||
showBodies: component.showplayerbody || false,
|
|
||||||
showSkinFaces: component.showplayerfaces || false,
|
|
||||||
showHealth: component.showplayerhealth || false,
|
|
||||||
smallFaces: component.smallplayerfaces || false,
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "coord":
|
|
||||||
components.coordinatesControl = {
|
|
||||||
showY: !(component.hidey || false),
|
|
||||||
label: component.label || "Location: ",
|
|
||||||
showRegion: component['show-mcr'] || false,
|
|
||||||
showChunk: component['show-chunk'] || false,
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "link":
|
|
||||||
components.linkControl = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "digitalclock":
|
|
||||||
components.clockControl = {
|
|
||||||
showDigitalClock: true,
|
|
||||||
showWeather: false,
|
|
||||||
showTimeOfDay: false,
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "timeofdayclock":
|
|
||||||
components.clockControl = {
|
|
||||||
showTimeOfDay: true,
|
|
||||||
showDigitalClock: component.showdigitalclock || false,
|
|
||||||
showWeather: component.showweather || false,
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "logo":
|
|
||||||
components.logoControls.push({
|
|
||||||
text: component.text || '',
|
|
||||||
url: component.linkurl || undefined,
|
|
||||||
position: component.position.replace('-', '') || 'topleft',
|
|
||||||
image: component.logourl || undefined,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "chat":
|
|
||||||
if (response.allowwebchat) {
|
|
||||||
components.chatSending = {
|
|
||||||
loginRequired: response['webchat-requires-login'] || false,
|
|
||||||
maxLength: response['chatlengthlimit'] || 256,
|
|
||||||
cooldown: response['webchat-interval'] || 5,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "chatbox":
|
|
||||||
components.chatBox = {
|
|
||||||
allowUrlName: component.allowurlname || false,
|
|
||||||
showPlayerFaces: component.showplayerfaces || false,
|
|
||||||
messageLifetime: component.messagettl || Infinity,
|
|
||||||
messageHistory: component.scrollback || Infinity,
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "chatballoon":
|
|
||||||
components.chatBalloons = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMarkerSet(id: string, data: any): any {
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
label: data.label || "Unnamed set",
|
|
||||||
hidden: data.hide || false,
|
|
||||||
priority: data.layerprio || 0,
|
|
||||||
showLabels: data.showlabels || undefined,
|
|
||||||
minZoom: typeof data.minzoom !== 'undefined' && data.minzoom > -1 ? data.minzoom : undefined,
|
|
||||||
maxZoom: typeof data.maxzoom !== 'undefined' && data.maxzoom > -1 ? data.maxzoom : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMarkers(data: any): Map<string, DynmapMarker> {
|
|
||||||
const markers = Object.freeze(new Map()) as Map<string, DynmapMarker>;
|
|
||||||
|
|
||||||
for (const key in data) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
markers.set(key, buildMarker(data[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return markers;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildMarker(marker: any): DynmapMarker {
|
|
||||||
return {
|
|
||||||
label: marker.label || '',
|
|
||||||
location: {
|
|
||||||
x: marker.x || 0,
|
|
||||||
y: marker.y || 0,
|
|
||||||
z: marker.z || 0,
|
|
||||||
},
|
|
||||||
dimensions: marker.dim ? marker.dim.split('x') : [16, 16],
|
|
||||||
icon: marker.icon || "default",
|
|
||||||
isHTML: marker.markup || false,
|
|
||||||
minZoom: typeof marker.minzoom !== 'undefined' && marker.minzoom > -1 ? marker.minzoom : undefined,
|
|
||||||
maxZoom: typeof marker.maxzoom !== 'undefined' && marker.maxzoom > -1 ? marker.maxzoom : undefined,
|
|
||||||
popupContent: marker.desc || undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildAreas(data: any): Map<string, DynmapArea> {
|
|
||||||
const areas = Object.freeze(new Map()) as Map<string, DynmapArea>;
|
|
||||||
|
|
||||||
for (const key in data) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
areas.set(key, buildArea(data[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return areas;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildArea(area: any): DynmapArea {
|
|
||||||
return {
|
|
||||||
style: {
|
|
||||||
color: area.color || '#ff0000',
|
|
||||||
opacity: area.opacity || 1,
|
|
||||||
weight: area.weight || 1,
|
|
||||||
fillColor: area.fillcolor || '#ff0000',
|
|
||||||
fillOpacity: area.fillopacity || 0,
|
|
||||||
},
|
|
||||||
label: area.label || '',
|
|
||||||
isHTML: area.markup || false,
|
|
||||||
x: area.x || [0, 0],
|
|
||||||
y: [area.ybottom || 0, area.ytop || 0],
|
|
||||||
z: area.z || [0, 0],
|
|
||||||
minZoom: typeof area.minzoom !== 'undefined' && area.minzoom > -1 ? area.minzoom : undefined,
|
|
||||||
maxZoom: typeof area.maxzoom !== 'undefined' && area.maxzoom > -1 ? area.maxzoom : undefined,
|
|
||||||
popupContent: area.desc || undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLines(data: any): Map<string, DynmapLine> {
|
|
||||||
const lines = Object.freeze(new Map()) as Map<string, DynmapLine>;
|
|
||||||
|
|
||||||
for (const key in data) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lines.set(key, buildLine(data[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildLine(line: any): DynmapLine {
|
|
||||||
return {
|
|
||||||
x: line.x || [0, 0],
|
|
||||||
y: line.y || [0, 0],
|
|
||||||
z: line.z || [0, 0],
|
|
||||||
style: {
|
|
||||||
color: line.color || '#ff0000',
|
|
||||||
opacity: line.opacity || 1,
|
|
||||||
weight: line.weight || 1,
|
|
||||||
},
|
|
||||||
label: line.label || '',
|
|
||||||
isHTML: line.markup || false,
|
|
||||||
minZoom: typeof line.minzoom !== 'undefined' && line.minzoom > -1 ? line.minzoom : undefined,
|
|
||||||
maxZoom: typeof line.maxzoom !== 'undefined' && line.maxzoom > -1 ? line.maxzoom : undefined,
|
|
||||||
popupContent: line.desc || undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCircles(data: any): Map<string, DynmapCircle> {
|
|
||||||
const circles = Object.freeze(new Map()) as Map<string, DynmapCircle>;
|
|
||||||
|
|
||||||
for (const key in data) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
circles.set(key, buildCircle(data[key]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return circles;
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildCircle(circle: any): DynmapCircle {
|
|
||||||
return {
|
|
||||||
location: {
|
|
||||||
x: circle.x || 0,
|
|
||||||
y: circle.y || 0,
|
|
||||||
z: circle.z || 0,
|
|
||||||
},
|
|
||||||
radius: [circle.xr || 0, circle.zr || 0],
|
|
||||||
style: {
|
|
||||||
fillColor: circle.fillcolor || '#ff0000',
|
|
||||||
fillOpacity: circle.fillopacity || 0,
|
|
||||||
color: circle.color || '#ff0000',
|
|
||||||
opacity: circle.opacity || 1,
|
|
||||||
weight: circle.weight || 1,
|
|
||||||
},
|
|
||||||
label: circle.label || '',
|
|
||||||
isHTML: circle.markup || false,
|
|
||||||
|
|
||||||
minZoom: typeof circle.minzoom !== 'undefined' && circle.minzoom > -1 ? circle.minzoom : undefined,
|
|
||||||
maxZoom: typeof circle.maxzoom !== 'undefined' && circle.maxzoom > -1 ? circle.maxzoom : undefined,
|
|
||||||
popupContent: circle.desc || undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function buildUpdates(data: Array<any>): DynmapUpdates {
|
|
||||||
const updates = {
|
|
||||||
markerSets: new Map<string, DynmapMarkerSetUpdates>(),
|
|
||||||
tiles: [] as DynmapTileUpdate[],
|
|
||||||
chat: [] as DynmapChat[],
|
|
||||||
},
|
|
||||||
dropped = {
|
|
||||||
stale: 0,
|
|
||||||
noSet: 0,
|
|
||||||
noId: 0,
|
|
||||||
unknownType: 0,
|
|
||||||
unknownCType: 0,
|
|
||||||
incomplete: 0,
|
|
||||||
notImplemented: 0,
|
|
||||||
},
|
|
||||||
lastUpdate = useStore().state.updateTimestamp;
|
|
||||||
|
|
||||||
let accepted = 0;
|
|
||||||
|
|
||||||
for (const entry of data) {
|
|
||||||
switch (entry.type) {
|
|
||||||
case 'component': {
|
|
||||||
if (lastUpdate && entry.timestamp < lastUpdate) {
|
|
||||||
dropped.stale++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!entry.id) {
|
|
||||||
dropped.noId++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Set updates don't have a set field, the id is the set
|
|
||||||
const set = entry.msg.startsWith("set") ? entry.id : entry.set;
|
|
||||||
|
|
||||||
if (!set) {
|
|
||||||
dropped.noSet++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.ctype !== 'markers') {
|
|
||||||
dropped.unknownCType++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!updates.markerSets.has(set)) {
|
|
||||||
updates.markerSets.set(set, {
|
|
||||||
areaUpdates: [],
|
|
||||||
markerUpdates: [],
|
|
||||||
lineUpdates: [],
|
|
||||||
circleUpdates: [],
|
|
||||||
removed: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const markerSetUpdates = updates.markerSets.get(set),
|
|
||||||
update: DynmapUpdate = {
|
|
||||||
id: entry.id,
|
|
||||||
removed: entry.msg.endsWith('deleted'),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (entry.msg.startsWith("set")) {
|
|
||||||
markerSetUpdates!.removed = update.removed;
|
|
||||||
markerSetUpdates!.payload = update.removed ? undefined : buildMarkerSet(set, entry);
|
|
||||||
} else if (entry.msg.startsWith("marker")) {
|
|
||||||
update.payload = update.removed ? undefined : buildMarker(entry);
|
|
||||||
markerSetUpdates!.markerUpdates.push(Object.freeze(update));
|
|
||||||
} else if (entry.msg.startsWith("area")) {
|
|
||||||
update.payload = update.removed ? undefined : buildArea(entry);
|
|
||||||
markerSetUpdates!.areaUpdates.push(Object.freeze(update));
|
|
||||||
|
|
||||||
} else if (entry.msg.startsWith("circle")) {
|
|
||||||
update.payload = update.removed ? undefined : buildCircle(entry);
|
|
||||||
markerSetUpdates!.circleUpdates.push(Object.freeze(update));
|
|
||||||
|
|
||||||
} else if (entry.msg.startsWith("line")) {
|
|
||||||
update.payload = update.removed ? undefined : buildLine(entry);
|
|
||||||
markerSetUpdates!.lineUpdates.push(Object.freeze(update));
|
|
||||||
}
|
|
||||||
|
|
||||||
accepted++;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'chat':
|
|
||||||
if (!entry.message || !entry.timestamp) {
|
|
||||||
dropped.incomplete++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.timestamp < lastUpdate) {
|
|
||||||
dropped.stale++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.source !== 'player' && entry.source !== 'web') {
|
|
||||||
dropped.notImplemented++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.chat.push({
|
|
||||||
type: 'chat',
|
|
||||||
source: entry.source || undefined,
|
|
||||||
playerAccount: entry.account || undefined,
|
|
||||||
playerName: entry.playerName || undefined,
|
|
||||||
message: entry.message || "",
|
|
||||||
timestamp: entry.timestamp,
|
|
||||||
channel: entry.channel || undefined,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'playerjoin':
|
|
||||||
if (!entry.account || !entry.timestamp) {
|
|
||||||
dropped.incomplete++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.timestamp < lastUpdate) {
|
|
||||||
dropped.stale++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.chat.push({
|
|
||||||
type: 'playerjoin',
|
|
||||||
playerAccount: entry.account,
|
|
||||||
playerName: entry.playerName || "",
|
|
||||||
timestamp: entry.timestamp || undefined,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'playerquit':
|
|
||||||
if (!entry.account || !entry.timestamp) {
|
|
||||||
dropped.incomplete++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.timestamp < lastUpdate) {
|
|
||||||
dropped.stale++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.chat.push({
|
|
||||||
type: 'playerleave',
|
|
||||||
playerAccount: entry.account,
|
|
||||||
playerName: entry.playerName || "",
|
|
||||||
timestamp: entry.timestamp || undefined,
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'tile':
|
|
||||||
if (!entry.name || !entry.timestamp) {
|
|
||||||
dropped.incomplete++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastUpdate && entry.timestamp < lastUpdate) {
|
|
||||||
dropped.stale++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
updates.tiles.push({
|
|
||||||
name: entry.name,
|
|
||||||
timestamp: entry.timestamp,
|
|
||||||
});
|
|
||||||
|
|
||||||
accepted++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
dropped.unknownType++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Sort chat by newest first
|
|
||||||
updates.chat = updates.chat.sort((one, two) => {
|
|
||||||
return two.timestamp - one.timestamp;
|
|
||||||
});
|
|
||||||
|
|
||||||
console.debug(`Updates: ${accepted} accepted. Rejected: `, dropped);
|
|
||||||
|
|
||||||
return updates;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchJSON(url: string, signal: AbortSignal) {
|
|
||||||
let response, json;
|
|
||||||
|
|
||||||
try {
|
|
||||||
response = await fetch(url, {signal});
|
|
||||||
} catch(e) {
|
|
||||||
if(e instanceof DOMException && e.name === 'AbortError') {
|
|
||||||
console.warn(`Request aborted (${url}`);
|
|
||||||
throw e;
|
|
||||||
} else {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(`Network request failed`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`Network request failed (${response.statusText || 'Unknown'})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
json = await response.json();
|
|
||||||
} catch(e) {
|
|
||||||
if(e instanceof DOMException && e.name === 'AbortError') {
|
|
||||||
console.warn(`Request aborted (${url}`);
|
|
||||||
throw e;
|
|
||||||
} else {
|
|
||||||
throw new Error('Request returned invalid json');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
let configurationAbort: AbortController | undefined = undefined,
|
|
||||||
markersAbort: AbortController | undefined = undefined,
|
|
||||||
updateAbort: AbortController | undefined = undefined;
|
|
||||||
|
|
||||||
export default {
|
|
||||||
async getConfiguration(): Promise<DynmapConfigurationResponse> {
|
|
||||||
if(configurationAbort) {
|
|
||||||
configurationAbort.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
configurationAbort = new AbortController();
|
|
||||||
|
|
||||||
const response = await fetchJSON(useStore().getters.serverConfig.dynmap.configuration, configurationAbort.signal);
|
|
||||||
|
|
||||||
if (response.error === 'login-required') {
|
|
||||||
throw new Error("Login required");
|
|
||||||
} else if (response.error) {
|
|
||||||
throw new Error(response.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
config: buildServerConfig(response),
|
|
||||||
messages: buildMessagesConfig(response),
|
|
||||||
worlds: buildWorlds(response),
|
|
||||||
components: buildComponents(response),
|
|
||||||
loggedIn: response.loggedin || false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getUpdate(requestId: number, world: string, timestamp: number): Promise<DynmapUpdateResponse> {
|
|
||||||
let url = useStore().getters.serverConfig.dynmap.update;
|
|
||||||
url = url.replace('{world}', world);
|
|
||||||
url = url.replace('{timestamp}', timestamp.toString());
|
|
||||||
|
|
||||||
if(updateAbort) {
|
|
||||||
updateAbort.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
updateAbort = new AbortController();
|
|
||||||
|
|
||||||
const response = await fetchJSON(url, updateAbort.signal);
|
|
||||||
const players: Set<DynmapPlayer> = new Set();
|
|
||||||
|
|
||||||
(response.players || []).forEach((player: any) => {
|
|
||||||
const world = player.world && player.world !== '-some-other-bogus-world-' ? player.world : undefined;
|
|
||||||
|
|
||||||
players.add({
|
|
||||||
account: player.account || "",
|
|
||||||
health: player.health || 0,
|
|
||||||
armor: player.armor || 0,
|
|
||||||
name: player.name || "",
|
|
||||||
sort: player.sort || 0,
|
|
||||||
hidden: !world,
|
|
||||||
location: {
|
|
||||||
//Add 0.5 to position in the middle of a block
|
|
||||||
x: !isNaN(player.x) ? player.x + 0.5 : 0,
|
|
||||||
y: !isNaN(player.y) ? player.y : 0,
|
|
||||||
z: !isNaN(player.z) ? player.z + 0.5 : 0,
|
|
||||||
world: world,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//Extra fake players for testing
|
|
||||||
// for(let i = 0; i < 450; i++) {
|
|
||||||
// players.add({
|
|
||||||
// account: "VIDEO GAMES " + i,
|
|
||||||
// health: Math.round(Math.random() * 10),
|
|
||||||
// armor: Math.round(Math.random() * 10),
|
|
||||||
// name: "VIDEO GAMES " + i,
|
|
||||||
// sort: Math.round(Math.random() * 10),
|
|
||||||
// hidden: false,
|
|
||||||
// location: {
|
|
||||||
// x: Math.round(Math.random() * 1000) - 500,
|
|
||||||
// y: 64,
|
|
||||||
// z: Math.round(Math.random() * 1000) - 500,
|
|
||||||
// world: "world",
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
return {
|
|
||||||
worldState: {
|
|
||||||
timeOfDay: response.servertime || 0,
|
|
||||||
thundering: response.isThundering || false,
|
|
||||||
raining: response.hasStorm || false,
|
|
||||||
},
|
|
||||||
playerCount: response.count || 0,
|
|
||||||
configHash: response.confighash || 0,
|
|
||||||
timestamp: response.timestamp || 0,
|
|
||||||
players,
|
|
||||||
updates: buildUpdates(response.updates || []),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async getMarkerSets(world: string): Promise<Map<string, DynmapMarkerSet>> {
|
|
||||||
const url = `${useStore().getters.serverConfig.dynmap.markers}_markers_/marker_${world}.json`;
|
|
||||||
|
|
||||||
if(markersAbort) {
|
|
||||||
markersAbort.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
markersAbort = new AbortController();
|
|
||||||
|
|
||||||
const response = await fetchJSON(url, markersAbort.signal);
|
|
||||||
const sets: Map<string, DynmapMarkerSet> = new Map();
|
|
||||||
|
|
||||||
response.sets = response.sets || {};
|
|
||||||
|
|
||||||
for (const key in response.sets) {
|
|
||||||
if (!Object.prototype.hasOwnProperty.call(response.sets, key)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const set = response.sets[key],
|
|
||||||
markers = buildMarkers(set.markers || {}),
|
|
||||||
circles = buildCircles(set.circles || {}),
|
|
||||||
areas = buildAreas(set.areas || {}),
|
|
||||||
lines = buildLines(set.lines || {});
|
|
||||||
|
|
||||||
sets.set(key, {
|
|
||||||
...buildMarkerSet(key, set),
|
|
||||||
markers,
|
|
||||||
circles,
|
|
||||||
areas,
|
|
||||||
lines,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return sets;
|
|
||||||
},
|
|
||||||
|
|
||||||
sendChatMessage(message: string) {
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
if (!store.state.components.chatSending) {
|
|
||||||
return Promise.reject(store.state.messages.chatErrorDisabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
return fetch(useStore().getters.serverConfig.dynmap.sendmessage, {
|
|
||||||
method: 'POST',
|
|
||||||
body: JSON.stringify({
|
|
||||||
name: null,
|
|
||||||
message: message,
|
|
||||||
})
|
|
||||||
}).then((response) => {
|
|
||||||
if (response.status === 403) { //Rate limited
|
|
||||||
throw new ChatError(store.state.messages.chatErrorCooldown
|
|
||||||
.replace('%interval%', store.state.components.chatSending!.cooldown.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network request failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.json();
|
|
||||||
}).then(response => {
|
|
||||||
if (response.error !== 'none') {
|
|
||||||
throw new ChatError(store.state.messages.chatErrorNotAllowed);
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
if (!(e instanceof ChatError)) {
|
|
||||||
console.error(store.state.messages.chatErrorUnknown);
|
|
||||||
console.trace(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw e;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -45,7 +45,6 @@ import ChatControl from "@/components/map/control/ChatControl.vue";
|
|||||||
import LogoControl from "@/components/map/control/LogoControl.vue";
|
import LogoControl from "@/components/map/control/LogoControl.vue";
|
||||||
import {MutationTypes} from "@/store/mutation-types";
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
import {DynmapPlayer} from "@/dynmap";
|
import {DynmapPlayer} from "@/dynmap";
|
||||||
import {ActionTypes} from "@/store/action-types";
|
|
||||||
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
import LiveAtlasLeafletMap from "@/leaflet/LiveAtlasLeafletMap";
|
||||||
import {LoadingControl} from "@/leaflet/control/LoadingControl";
|
import {LoadingControl} from "@/leaflet/control/LoadingControl";
|
||||||
import MapContextMenu from "@/components/map/MapContextMenu.vue";
|
import MapContextMenu from "@/components/map/MapContextMenu.vue";
|
||||||
@ -165,8 +164,6 @@ export default defineComponent({
|
|||||||
if(newValue) {
|
if(newValue) {
|
||||||
let location: Coordinate | null = this.scheduledPan;
|
let location: Coordinate | null = this.scheduledPan;
|
||||||
|
|
||||||
store.dispatch(ActionTypes.GET_MARKER_SETS, undefined);
|
|
||||||
|
|
||||||
// Abort if follow target is present, to avoid panning twice
|
// Abort if follow target is present, to avoid panning twice
|
||||||
if(store.state.followTarget && store.state.followTarget.location.world === newValue.name) {
|
if(store.state.followTarget && store.state.followTarget.location.world === newValue.name) {
|
||||||
return;
|
return;
|
||||||
|
9
src/index.d.ts
vendored
9
src/index.d.ts
vendored
@ -136,3 +136,12 @@ interface LiveAtlasParsedUrl {
|
|||||||
zoom?: number;
|
zoom?: number;
|
||||||
legacy: boolean;
|
legacy: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LiveAtlasMapProvider {
|
||||||
|
loadServerConfiguration(): Promise<void>;
|
||||||
|
loadWorldConfiguration(world: LiveAtlasWorldDefinition): Promise<void>;
|
||||||
|
startUpdates(): void;
|
||||||
|
stopUpdates(): void;
|
||||||
|
sendChatMessage(message: string): void;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
||||||
|
846
src/providers/DynmapMapProvider.ts
Normal file
846
src/providers/DynmapMapProvider.ts
Normal file
@ -0,0 +1,846 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
LiveAtlasDimension,
|
||||||
|
LiveAtlasDynmapServerDefinition, LiveAtlasServerDefinition,
|
||||||
|
LiveAtlasServerMessageConfig,
|
||||||
|
LiveAtlasWorldDefinition
|
||||||
|
} from "@/index";
|
||||||
|
import {
|
||||||
|
DynmapArea, DynmapChat,
|
||||||
|
DynmapCircle,
|
||||||
|
DynmapComponentConfig,
|
||||||
|
DynmapLine,
|
||||||
|
DynmapMarker, DynmapMarkerSet, DynmapMarkerSetUpdates, DynmapPlayer,
|
||||||
|
DynmapServerConfig, DynmapTileUpdate, DynmapUpdate, DynmapUpdateResponse,
|
||||||
|
DynmapUpdates
|
||||||
|
} from "@/dynmap";
|
||||||
|
import {useStore} from "@/store";
|
||||||
|
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||||
|
import ChatError from "@/errors/ChatError";
|
||||||
|
import {MutationTypes} from "@/store/mutation-types";
|
||||||
|
import MapProvider from "@/providers/MapProvider";
|
||||||
|
import {ActionTypes} from "@/store/action-types";
|
||||||
|
import {endWorldNameRegex, netherWorldNameRegex, titleColoursRegex} from "@/util";
|
||||||
|
|
||||||
|
export default class DynmapMapProvider extends MapProvider {
|
||||||
|
private configurationAbort?: AbortController = undefined;
|
||||||
|
private markersAbort?: AbortController = undefined;
|
||||||
|
private updateAbort?: AbortController = undefined;
|
||||||
|
|
||||||
|
private updatesEnabled = false;
|
||||||
|
private updateTimeout: number = 0;
|
||||||
|
private updateTimestamp: Date = new Date();
|
||||||
|
private updateInterval: number = 3000;
|
||||||
|
|
||||||
|
constructor(config: LiveAtlasDynmapServerDefinition) {
|
||||||
|
super(config as LiveAtlasServerDefinition);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildServerConfig(response: any): DynmapServerConfig {
|
||||||
|
return {
|
||||||
|
version: response.dynmapversion || '',
|
||||||
|
grayHiddenPlayers: response.grayplayerswhenhidden || false,
|
||||||
|
defaultMap: response.defaultmap || undefined,
|
||||||
|
defaultWorld: response.defaultworld || undefined,
|
||||||
|
defaultZoom: response.defaultzoom || 0,
|
||||||
|
followMap: response.followmap || undefined,
|
||||||
|
followZoom: response.followzoom || 0,
|
||||||
|
updateInterval: response.updaterate || 3000,
|
||||||
|
showLayerControl: response.showlayercontrol && response.showlayercontrol !== 'false', //Sent as a string for some reason
|
||||||
|
title: response.title.replace(titleColoursRegex, '') || 'Dynmap',
|
||||||
|
loginEnabled: response['login-enabled'] || false,
|
||||||
|
maxPlayers: response.maxcount || 0,
|
||||||
|
expandUI: response.sidebaropened && response.sidebaropened !== 'false', //Sent as a string for some reason
|
||||||
|
hash: response.confighash || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildMessagesConfig(response: any): LiveAtlasServerMessageConfig {
|
||||||
|
return {
|
||||||
|
chatPlayerJoin: response.joinmessage || '',
|
||||||
|
chatPlayerQuit: response.quitmessage || '',
|
||||||
|
chatAnonymousJoin: response['msg-hiddennamejoin'] || '',
|
||||||
|
chatAnonymousQuit: response['msg-hiddennamequit'] || '',
|
||||||
|
chatErrorNotAllowed: response['msg-chatnotallowed'] || '',
|
||||||
|
chatErrorRequiresLogin: response['msg-chatrequireslogin'] || '',
|
||||||
|
chatErrorCooldown: response.spammessage || '',
|
||||||
|
worldsHeading: response['msg-maptypes'] || '',
|
||||||
|
playersHeading: response['msg-players'] || '',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildWorlds(response: any): Array<LiveAtlasWorldDefinition> {
|
||||||
|
const worlds: Map<string, LiveAtlasWorldDefinition> = new Map<string, LiveAtlasWorldDefinition>();
|
||||||
|
|
||||||
|
//Get all the worlds first so we can handle append_to_world properly
|
||||||
|
(response.worlds || []).forEach((world: any) => {
|
||||||
|
let worldType: LiveAtlasDimension = 'overworld';
|
||||||
|
|
||||||
|
if (netherWorldNameRegex.test(world.name) || (world.name == 'DIM-1')) {
|
||||||
|
worldType = 'nether';
|
||||||
|
} else if (endWorldNameRegex.test(world.name) || (world.name == 'DIM1')) {
|
||||||
|
worldType = 'end';
|
||||||
|
}
|
||||||
|
|
||||||
|
worlds.set(world.name, {
|
||||||
|
seaLevel: world.sealevel || 64,
|
||||||
|
name: world.name,
|
||||||
|
dimension: worldType,
|
||||||
|
protected: world.protected || false,
|
||||||
|
title: world.title || '',
|
||||||
|
height: world.height || 256,
|
||||||
|
center: {
|
||||||
|
x: world.center.x || 0,
|
||||||
|
y: world.center.y || 0,
|
||||||
|
z: world.center.z || 0
|
||||||
|
},
|
||||||
|
maps: new Map(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
(response.worlds || []).forEach((world: any) => {
|
||||||
|
(world.maps || []).forEach((map: any) => {
|
||||||
|
const worldName = map.append_to_world || world.name,
|
||||||
|
w = worlds.get(worldName);
|
||||||
|
|
||||||
|
if(!w) {
|
||||||
|
console.warn(`Ignoring map '${map.name}' associated with non-existent world '${worldName}'`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w.maps.set(map.name, new LiveAtlasMapDefinition({
|
||||||
|
world: world, //Ignore append_to_world here otherwise things break
|
||||||
|
background: map.background || '#000000',
|
||||||
|
backgroundDay: map.backgroundday || '#000000',
|
||||||
|
backgroundNight: map.backgroundnight || '#000000',
|
||||||
|
icon: map.icon || undefined,
|
||||||
|
imageFormat: map['image-format'] || 'png',
|
||||||
|
name: map.name || '(Unnamed map)',
|
||||||
|
nightAndDay: map.nightandday || false,
|
||||||
|
prefix: map.prefix || '',
|
||||||
|
protected: map.protected || false,
|
||||||
|
title: map.title || '',
|
||||||
|
mapToWorld: map.maptoworld || undefined,
|
||||||
|
worldToMap: map.worldtomap || undefined,
|
||||||
|
nativeZoomLevels: map.mapzoomout || 1,
|
||||||
|
extraZoomLevels: map.mapzoomin || 0
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(worlds.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildComponents(response: any): DynmapComponentConfig {
|
||||||
|
const components: DynmapComponentConfig = {
|
||||||
|
markers: {
|
||||||
|
showLabels: false,
|
||||||
|
},
|
||||||
|
chatBox: undefined,
|
||||||
|
chatBalloons: false,
|
||||||
|
playerMarkers: undefined,
|
||||||
|
coordinatesControl: undefined,
|
||||||
|
linkControl: false,
|
||||||
|
clockControl: undefined,
|
||||||
|
logoControls: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
(response.components || []).forEach((component: any) => {
|
||||||
|
const type = component.type || "unknown";
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case "markers":
|
||||||
|
components.markers = {
|
||||||
|
showLabels: component.showlabel || false,
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "playermarkers":
|
||||||
|
components.playerMarkers = {
|
||||||
|
hideByDefault: component.hidebydefault || false,
|
||||||
|
layerName: component.label || "Players",
|
||||||
|
layerPriority: component.layerprio || 0,
|
||||||
|
showBodies: component.showplayerbody || false,
|
||||||
|
showSkinFaces: component.showplayerfaces || false,
|
||||||
|
showHealth: component.showplayerhealth || false,
|
||||||
|
smallFaces: component.smallplayerfaces || false,
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "coord":
|
||||||
|
components.coordinatesControl = {
|
||||||
|
showY: !(component.hidey || false),
|
||||||
|
label: component.label || "Location: ",
|
||||||
|
showRegion: component['show-mcr'] || false,
|
||||||
|
showChunk: component['show-chunk'] || false,
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "link":
|
||||||
|
components.linkControl = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "digitalclock":
|
||||||
|
components.clockControl = {
|
||||||
|
showDigitalClock: true,
|
||||||
|
showWeather: false,
|
||||||
|
showTimeOfDay: false,
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "timeofdayclock":
|
||||||
|
components.clockControl = {
|
||||||
|
showTimeOfDay: true,
|
||||||
|
showDigitalClock: component.showdigitalclock || false,
|
||||||
|
showWeather: component.showweather || false,
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "logo":
|
||||||
|
components.logoControls.push({
|
||||||
|
text: component.text || '',
|
||||||
|
url: component.linkurl || undefined,
|
||||||
|
position: component.position.replace('-', '') || 'topleft',
|
||||||
|
image: component.logourl || undefined,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "chat":
|
||||||
|
if (response.allowwebchat) {
|
||||||
|
components.chatSending = {
|
||||||
|
loginRequired: response['webchat-requires-login'] || false,
|
||||||
|
maxLength: response['chatlengthlimit'] || 256,
|
||||||
|
cooldown: response['webchat-interval'] || 5,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "chatbox":
|
||||||
|
components.chatBox = {
|
||||||
|
allowUrlName: component.allowurlname || false,
|
||||||
|
showPlayerFaces: component.showplayerfaces || false,
|
||||||
|
messageLifetime: component.messagettl || Infinity,
|
||||||
|
messageHistory: component.scrollback || Infinity,
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "chatballoon":
|
||||||
|
components.chatBalloons = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildMarkerSet(id: string, data: any): any {
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
label: data.label || "Unnamed set",
|
||||||
|
hidden: data.hide || false,
|
||||||
|
priority: data.layerprio || 0,
|
||||||
|
showLabels: data.showlabels || undefined,
|
||||||
|
minZoom: typeof data.minzoom !== 'undefined' && data.minzoom > -1 ? data.minzoom : undefined,
|
||||||
|
maxZoom: typeof data.maxzoom !== 'undefined' && data.maxzoom > -1 ? data.maxzoom : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildMarkers(data: any): Map<string, DynmapMarker> {
|
||||||
|
const markers = Object.freeze(new Map()) as Map<string, DynmapMarker>;
|
||||||
|
|
||||||
|
for (const key in data) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
markers.set(key, DynmapMapProvider.buildMarker(data[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return markers;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildMarker(marker: any): DynmapMarker {
|
||||||
|
return {
|
||||||
|
label: marker.label || '',
|
||||||
|
location: {
|
||||||
|
x: marker.x || 0,
|
||||||
|
y: marker.y || 0,
|
||||||
|
z: marker.z || 0,
|
||||||
|
},
|
||||||
|
dimensions: marker.dim ? marker.dim.split('x') : [16, 16],
|
||||||
|
icon: marker.icon || "default",
|
||||||
|
isHTML: marker.markup || false,
|
||||||
|
minZoom: typeof marker.minzoom !== 'undefined' && marker.minzoom > -1 ? marker.minzoom : undefined,
|
||||||
|
maxZoom: typeof marker.maxzoom !== 'undefined' && marker.maxzoom > -1 ? marker.maxzoom : undefined,
|
||||||
|
popupContent: marker.desc || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildAreas(data: any): Map<string, DynmapArea> {
|
||||||
|
const areas = Object.freeze(new Map()) as Map<string, DynmapArea>;
|
||||||
|
|
||||||
|
for (const key in data) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
areas.set(key, DynmapMapProvider.buildArea(data[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return areas;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildArea(area: any): DynmapArea {
|
||||||
|
return {
|
||||||
|
style: {
|
||||||
|
color: area.color || '#ff0000',
|
||||||
|
opacity: area.opacity || 1,
|
||||||
|
weight: area.weight || 1,
|
||||||
|
fillColor: area.fillcolor || '#ff0000',
|
||||||
|
fillOpacity: area.fillopacity || 0,
|
||||||
|
},
|
||||||
|
label: area.label || '',
|
||||||
|
isHTML: area.markup || false,
|
||||||
|
x: area.x || [0, 0],
|
||||||
|
y: [area.ybottom || 0, area.ytop || 0],
|
||||||
|
z: area.z || [0, 0],
|
||||||
|
minZoom: typeof area.minzoom !== 'undefined' && area.minzoom > -1 ? area.minzoom : undefined,
|
||||||
|
maxZoom: typeof area.maxzoom !== 'undefined' && area.maxzoom > -1 ? area.maxzoom : undefined,
|
||||||
|
popupContent: area.desc || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildLines(data: any): Map<string, DynmapLine> {
|
||||||
|
const lines = Object.freeze(new Map()) as Map<string, DynmapLine>;
|
||||||
|
|
||||||
|
for (const key in data) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.set(key, DynmapMapProvider.buildLine(data[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildLine(line: any): DynmapLine {
|
||||||
|
return {
|
||||||
|
x: line.x || [0, 0],
|
||||||
|
y: line.y || [0, 0],
|
||||||
|
z: line.z || [0, 0],
|
||||||
|
style: {
|
||||||
|
color: line.color || '#ff0000',
|
||||||
|
opacity: line.opacity || 1,
|
||||||
|
weight: line.weight || 1,
|
||||||
|
},
|
||||||
|
label: line.label || '',
|
||||||
|
isHTML: line.markup || false,
|
||||||
|
minZoom: typeof line.minzoom !== 'undefined' && line.minzoom > -1 ? line.minzoom : undefined,
|
||||||
|
maxZoom: typeof line.maxzoom !== 'undefined' && line.maxzoom > -1 ? line.maxzoom : undefined,
|
||||||
|
popupContent: line.desc || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildCircles(data: any): Map<string, DynmapCircle> {
|
||||||
|
const circles = Object.freeze(new Map()) as Map<string, DynmapCircle>;
|
||||||
|
|
||||||
|
for (const key in data) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(data, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
circles.set(key, DynmapMapProvider.buildCircle(data[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return circles;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static buildCircle(circle: any): DynmapCircle {
|
||||||
|
return {
|
||||||
|
location: {
|
||||||
|
x: circle.x || 0,
|
||||||
|
y: circle.y || 0,
|
||||||
|
z: circle.z || 0,
|
||||||
|
},
|
||||||
|
radius: [circle.xr || 0, circle.zr || 0],
|
||||||
|
style: {
|
||||||
|
fillColor: circle.fillcolor || '#ff0000',
|
||||||
|
fillOpacity: circle.fillopacity || 0,
|
||||||
|
color: circle.color || '#ff0000',
|
||||||
|
opacity: circle.opacity || 1,
|
||||||
|
weight: circle.weight || 1,
|
||||||
|
},
|
||||||
|
label: circle.label || '',
|
||||||
|
isHTML: circle.markup || false,
|
||||||
|
|
||||||
|
minZoom: typeof circle.minzoom !== 'undefined' && circle.minzoom > -1 ? circle.minzoom : undefined,
|
||||||
|
maxZoom: typeof circle.maxzoom !== 'undefined' && circle.maxzoom > -1 ? circle.maxzoom : undefined,
|
||||||
|
popupContent: circle.desc || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildUpdates(data: Array<any>): DynmapUpdates {
|
||||||
|
const updates = {
|
||||||
|
markerSets: new Map<string, DynmapMarkerSetUpdates>(),
|
||||||
|
tiles: [] as DynmapTileUpdate[],
|
||||||
|
chat: [] as DynmapChat[],
|
||||||
|
},
|
||||||
|
dropped = {
|
||||||
|
stale: 0,
|
||||||
|
noSet: 0,
|
||||||
|
noId: 0,
|
||||||
|
unknownType: 0,
|
||||||
|
unknownCType: 0,
|
||||||
|
incomplete: 0,
|
||||||
|
notImplemented: 0,
|
||||||
|
},
|
||||||
|
lastUpdate = this.updateTimestamp;
|
||||||
|
|
||||||
|
let accepted = 0;
|
||||||
|
|
||||||
|
for (const entry of data) {
|
||||||
|
switch (entry.type) {
|
||||||
|
case 'component': {
|
||||||
|
if (lastUpdate && entry.timestamp < lastUpdate) {
|
||||||
|
dropped.stale++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry.id) {
|
||||||
|
dropped.noId++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set updates don't have a set field, the id is the set
|
||||||
|
const set = entry.msg.startsWith("set") ? entry.id : entry.set;
|
||||||
|
|
||||||
|
if (!set) {
|
||||||
|
dropped.noSet++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.ctype !== 'markers') {
|
||||||
|
dropped.unknownCType++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!updates.markerSets.has(set)) {
|
||||||
|
updates.markerSets.set(set, {
|
||||||
|
areaUpdates: [],
|
||||||
|
markerUpdates: [],
|
||||||
|
lineUpdates: [],
|
||||||
|
circleUpdates: [],
|
||||||
|
removed: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const markerSetUpdates = updates.markerSets.get(set),
|
||||||
|
update: DynmapUpdate = {
|
||||||
|
id: entry.id,
|
||||||
|
removed: entry.msg.endsWith('deleted'),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entry.msg.startsWith("set")) {
|
||||||
|
markerSetUpdates!.removed = update.removed;
|
||||||
|
markerSetUpdates!.payload = update.removed ? undefined : DynmapMapProvider.buildMarkerSet(set, entry);
|
||||||
|
} else if (entry.msg.startsWith("marker")) {
|
||||||
|
update.payload = update.removed ? undefined : DynmapMapProvider.buildMarker(entry);
|
||||||
|
markerSetUpdates!.markerUpdates.push(Object.freeze(update));
|
||||||
|
} else if (entry.msg.startsWith("area")) {
|
||||||
|
update.payload = update.removed ? undefined : DynmapMapProvider.buildArea(entry);
|
||||||
|
markerSetUpdates!.areaUpdates.push(Object.freeze(update));
|
||||||
|
|
||||||
|
} else if (entry.msg.startsWith("circle")) {
|
||||||
|
update.payload = update.removed ? undefined : DynmapMapProvider.buildCircle(entry);
|
||||||
|
markerSetUpdates!.circleUpdates.push(Object.freeze(update));
|
||||||
|
|
||||||
|
} else if (entry.msg.startsWith("line")) {
|
||||||
|
update.payload = update.removed ? undefined : DynmapMapProvider.buildLine(entry);
|
||||||
|
markerSetUpdates!.lineUpdates.push(Object.freeze(update));
|
||||||
|
}
|
||||||
|
|
||||||
|
accepted++;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'chat':
|
||||||
|
if (!entry.message || !entry.timestamp) {
|
||||||
|
dropped.incomplete++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.timestamp < lastUpdate) {
|
||||||
|
dropped.stale++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.source !== 'player' && entry.source !== 'web') {
|
||||||
|
dropped.notImplemented++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.chat.push({
|
||||||
|
type: 'chat',
|
||||||
|
source: entry.source || undefined,
|
||||||
|
playerAccount: entry.account || undefined,
|
||||||
|
playerName: entry.playerName || undefined,
|
||||||
|
message: entry.message || "",
|
||||||
|
timestamp: entry.timestamp,
|
||||||
|
channel: entry.channel || undefined,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'playerjoin':
|
||||||
|
if (!entry.account || !entry.timestamp) {
|
||||||
|
dropped.incomplete++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.timestamp < lastUpdate) {
|
||||||
|
dropped.stale++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.chat.push({
|
||||||
|
type: 'playerjoin',
|
||||||
|
playerAccount: entry.account,
|
||||||
|
playerName: entry.playerName || "",
|
||||||
|
timestamp: entry.timestamp || undefined,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'playerquit':
|
||||||
|
if (!entry.account || !entry.timestamp) {
|
||||||
|
dropped.incomplete++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.timestamp < lastUpdate) {
|
||||||
|
dropped.stale++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.chat.push({
|
||||||
|
type: 'playerleave',
|
||||||
|
playerAccount: entry.account,
|
||||||
|
playerName: entry.playerName || "",
|
||||||
|
timestamp: entry.timestamp || undefined,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'tile':
|
||||||
|
if (!entry.name || !entry.timestamp) {
|
||||||
|
dropped.incomplete++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastUpdate && entry.timestamp < lastUpdate) {
|
||||||
|
dropped.stale++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.tiles.push({
|
||||||
|
name: entry.name,
|
||||||
|
timestamp: entry.timestamp,
|
||||||
|
});
|
||||||
|
|
||||||
|
accepted++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
dropped.unknownType++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Sort chat by newest first
|
||||||
|
updates.chat = updates.chat.sort((one, two) => {
|
||||||
|
return two.timestamp - one.timestamp;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.debug(`Updates: ${accepted} accepted. Rejected: `, dropped);
|
||||||
|
|
||||||
|
return updates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async fetchJSON(url: string, signal: AbortSignal) {
|
||||||
|
let response, json;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await fetch(url, {signal});
|
||||||
|
} catch(e) {
|
||||||
|
if(e instanceof DOMException && e.name === 'AbortError') {
|
||||||
|
console.warn(`Request aborted (${url}`);
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Network request failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Network request failed (${response.statusText || 'Unknown'})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
json = await response.json();
|
||||||
|
} catch(e) {
|
||||||
|
if(e instanceof DOMException && e.name === 'AbortError') {
|
||||||
|
console.warn(`Request aborted (${url}`);
|
||||||
|
throw e;
|
||||||
|
} else {
|
||||||
|
throw new Error('Request returned invalid json');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getMarkerSets(world: string): Promise<Map<string, DynmapMarkerSet>> {
|
||||||
|
const url = `${useStore().getters.serverConfig.dynmap.markers}_markers_/marker_${world}.json`;
|
||||||
|
|
||||||
|
if(this.markersAbort) {
|
||||||
|
this.markersAbort.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.markersAbort = new AbortController();
|
||||||
|
|
||||||
|
const response = await DynmapMapProvider.fetchJSON(url, this.markersAbort.signal);
|
||||||
|
const sets: Map<string, DynmapMarkerSet> = new Map();
|
||||||
|
|
||||||
|
response.sets = response.sets || {};
|
||||||
|
|
||||||
|
for (const key in response.sets) {
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(response.sets, key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const set = response.sets[key],
|
||||||
|
markers = DynmapMapProvider.buildMarkers(set.markers || {}),
|
||||||
|
circles = DynmapMapProvider.buildCircles(set.circles || {}),
|
||||||
|
areas = DynmapMapProvider.buildAreas(set.areas || {}),
|
||||||
|
lines = DynmapMapProvider.buildLines(set.lines || {});
|
||||||
|
|
||||||
|
sets.set(key, {
|
||||||
|
...DynmapMapProvider.buildMarkerSet(key, set),
|
||||||
|
markers,
|
||||||
|
circles,
|
||||||
|
areas,
|
||||||
|
lines,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return sets;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async loadServerConfiguration(): Promise<void> {
|
||||||
|
if(this.configurationAbort) {
|
||||||
|
this.configurationAbort.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.configurationAbort = new AbortController();
|
||||||
|
|
||||||
|
const response = await DynmapMapProvider.fetchJSON(useStore().getters.serverConfig.dynmap.configuration, this.configurationAbort.signal);
|
||||||
|
|
||||||
|
if (response.error === 'login-required') {
|
||||||
|
throw new Error("Login required");
|
||||||
|
} else if (response.error) {
|
||||||
|
throw new Error(response.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const store = useStore(),
|
||||||
|
config = DynmapMapProvider.buildServerConfig(response);
|
||||||
|
|
||||||
|
this.updateInterval = config.updateInterval;
|
||||||
|
|
||||||
|
store.commit(MutationTypes.SET_SERVER_CONFIGURATION, config);
|
||||||
|
store.commit(MutationTypes.SET_SERVER_MESSAGES, DynmapMapProvider.buildMessagesConfig(response));
|
||||||
|
store.commit(MutationTypes.SET_WORLDS, this.buildWorlds(response));
|
||||||
|
store.commit(MutationTypes.SET_COMPONENTS, this.buildComponents(response));
|
||||||
|
store.commit(MutationTypes.SET_LOGGED_IN, response.loggedin || false);
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadWorldConfiguration(): Promise<void> {
|
||||||
|
const markerSets = await this.getMarkerSets(this.store.state.currentWorld!.name);
|
||||||
|
|
||||||
|
useStore().commit(MutationTypes.SET_MARKER_SETS, markerSets);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUpdate(): Promise<DynmapUpdateResponse> {
|
||||||
|
let url = useStore().getters.serverConfig.dynmap.update;
|
||||||
|
url = url.replace('{world}', this.store.state.currentWorld!.name);
|
||||||
|
url = url.replace('{timestamp}', this.updateTimestamp.getTime().toString());
|
||||||
|
|
||||||
|
if(this.updateAbort) {
|
||||||
|
this.updateAbort.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateAbort = new AbortController();
|
||||||
|
|
||||||
|
const response = await DynmapMapProvider.fetchJSON(url, this.updateAbort.signal);
|
||||||
|
const players: Set<DynmapPlayer> = new Set();
|
||||||
|
|
||||||
|
(response.players || []).forEach((player: any) => {
|
||||||
|
const world = player.world && player.world !== '-some-other-bogus-world-' ? player.world : undefined;
|
||||||
|
|
||||||
|
players.add({
|
||||||
|
account: player.account || "",
|
||||||
|
health: player.health || 0,
|
||||||
|
armor: player.armor || 0,
|
||||||
|
name: player.name || "",
|
||||||
|
sort: player.sort || 0,
|
||||||
|
hidden: !world,
|
||||||
|
location: {
|
||||||
|
//Add 0.5 to position in the middle of a block
|
||||||
|
x: !isNaN(player.x) ? player.x + 0.5 : 0,
|
||||||
|
y: !isNaN(player.y) ? player.y : 0,
|
||||||
|
z: !isNaN(player.z) ? player.z + 0.5 : 0,
|
||||||
|
world: world,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//Extra fake players for testing
|
||||||
|
// for(let i = 0; i < 450; i++) {
|
||||||
|
// players.add({
|
||||||
|
// account: "VIDEO GAMES " + i,
|
||||||
|
// health: Math.round(Math.random() * 10),
|
||||||
|
// armor: Math.round(Math.random() * 10),
|
||||||
|
// name: "VIDEO GAMES " + i,
|
||||||
|
// sort: Math.round(Math.random() * 10),
|
||||||
|
// hidden: false,
|
||||||
|
// location: {
|
||||||
|
// x: Math.round(Math.random() * 1000) - 500,
|
||||||
|
// y: 64,
|
||||||
|
// z: Math.round(Math.random() * 1000) - 500,
|
||||||
|
// world: "world",
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
return {
|
||||||
|
worldState: {
|
||||||
|
timeOfDay: response.servertime || 0,
|
||||||
|
thundering: response.isThundering || false,
|
||||||
|
raining: response.hasStorm || false,
|
||||||
|
},
|
||||||
|
playerCount: response.count || 0,
|
||||||
|
configHash: response.confighash || 0,
|
||||||
|
timestamp: response.timestamp || 0,
|
||||||
|
players,
|
||||||
|
updates: this.buildUpdates(response.updates || []),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sendChatMessage(message: string) {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
if (!store.state.components.chatSending) {
|
||||||
|
return Promise.reject(store.state.messages.chatErrorDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(useStore().getters.serverConfig.dynmap.sendmessage, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: null,
|
||||||
|
message: message,
|
||||||
|
})
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.status === 403) { //Rate limited
|
||||||
|
throw new ChatError(store.state.messages.chatErrorCooldown
|
||||||
|
.replace('%interval%', store.state.components.chatSending!.cooldown.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}).then(response => {
|
||||||
|
if (response.error !== 'none') {
|
||||||
|
throw new ChatError(store.state.messages.chatErrorNotAllowed);
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
if (!(e instanceof ChatError)) {
|
||||||
|
console.error(store.state.messages.chatErrorUnknown);
|
||||||
|
console.trace(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
startUpdates() {
|
||||||
|
this.updatesEnabled = true;
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async update() {
|
||||||
|
try {
|
||||||
|
const update = await this.getUpdate();
|
||||||
|
|
||||||
|
this.updateTimestamp = new Date(update.timestamp);
|
||||||
|
|
||||||
|
this.store.commit(MutationTypes.SET_WORLD_STATE, update.worldState);
|
||||||
|
this.store.commit(MutationTypes.ADD_MARKER_SET_UPDATES, update.updates.markerSets);
|
||||||
|
this.store.commit(MutationTypes.ADD_TILE_UPDATES, update.updates.tiles);
|
||||||
|
this.store.commit(MutationTypes.ADD_CHAT, update.updates.chat);
|
||||||
|
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION_HASH, update.configHash);
|
||||||
|
|
||||||
|
await this.store.dispatch(ActionTypes.SET_PLAYERS, update.players);
|
||||||
|
} finally {
|
||||||
|
if(this.updatesEnabled) {
|
||||||
|
if(this.updateTimeout) {
|
||||||
|
clearTimeout(this.updateTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTimeout = setTimeout(() => this.update(), this.updateInterval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopUpdates() {
|
||||||
|
this.updatesEnabled = false;
|
||||||
|
|
||||||
|
if (this.updateTimeout) {
|
||||||
|
clearTimeout(this.updateTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateTimeout = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
if(this.configurationAbort) {
|
||||||
|
this.configurationAbort.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.updateAbort) {
|
||||||
|
this.updateAbort.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.markersAbort) {
|
||||||
|
this.markersAbort.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
src/providers/MapProvider.ts
Normal file
25
src/providers/MapProvider.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {LiveAtlasMapProvider, LiveAtlasServerDefinition, LiveAtlasWorldDefinition} from "@/index";
|
||||||
|
import {useStore} from "@/store";
|
||||||
|
import {watch} from "vue";
|
||||||
|
import {computed} from "@vue/runtime-core";
|
||||||
|
|
||||||
|
export default abstract class MapProvider implements LiveAtlasMapProvider {
|
||||||
|
protected readonly store = useStore();
|
||||||
|
|
||||||
|
protected constructor(config: LiveAtlasServerDefinition) {
|
||||||
|
const currentWorld = computed(() => this.store.state.currentWorld);
|
||||||
|
|
||||||
|
watch(currentWorld, (newValue) => {
|
||||||
|
if(newValue) {
|
||||||
|
this.loadWorldConfiguration(newValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract destroy(): void;
|
||||||
|
abstract loadServerConfiguration(): Promise<void>;
|
||||||
|
abstract loadWorldConfiguration(world: LiveAtlasWorldDefinition): Promise<void>;
|
||||||
|
abstract sendChatMessage(message: string): void;
|
||||||
|
abstract startUpdates(): void;
|
||||||
|
abstract stopUpdates(): void;
|
||||||
|
}
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
export enum ActionTypes {
|
export enum ActionTypes {
|
||||||
LOAD_CONFIGURATION = "loadConfiguration",
|
LOAD_CONFIGURATION = "loadConfiguration",
|
||||||
GET_UPDATE = "getUpdate",
|
START_UPDATES = "startUpdates",
|
||||||
GET_MARKER_SETS = "getMarkerSets",
|
STOP_UPDATES = "stopUpdates",
|
||||||
SET_PLAYERS = "setPlayers",
|
SET_PLAYERS = "setPlayers",
|
||||||
POP_MARKER_UPDATES = "popMarkerUpdates",
|
POP_MARKER_UPDATES = "popMarkerUpdates",
|
||||||
POP_AREA_UPDATES = "popAreaUpdates",
|
POP_AREA_UPDATES = "popAreaUpdates",
|
||||||
|
@ -20,14 +20,11 @@ import {State} from "@/store/state";
|
|||||||
import {ActionTypes} from "@/store/action-types";
|
import {ActionTypes} from "@/store/action-types";
|
||||||
import {Mutations} from "@/store/mutations";
|
import {Mutations} from "@/store/mutations";
|
||||||
import {
|
import {
|
||||||
DynmapAreaUpdate, DynmapCircleUpdate,
|
DynmapAreaUpdate, DynmapCircleUpdate, DynmapLineUpdate,
|
||||||
DynmapConfigurationResponse, DynmapLineUpdate,
|
|
||||||
DynmapMarkerSet,
|
DynmapMarkerSet,
|
||||||
DynmapMarkerUpdate,
|
DynmapMarkerUpdate,
|
||||||
DynmapPlayer, DynmapTileUpdate,
|
DynmapPlayer, DynmapTileUpdate,
|
||||||
DynmapUpdateResponse
|
|
||||||
} from "@/dynmap";
|
} from "@/dynmap";
|
||||||
import {getAPI} from "@/util";
|
|
||||||
import {LiveAtlasWorldDefinition} from "@/index";
|
import {LiveAtlasWorldDefinition} from "@/index";
|
||||||
|
|
||||||
type AugmentedActionContext = {
|
type AugmentedActionContext = {
|
||||||
@ -40,13 +37,13 @@ type AugmentedActionContext = {
|
|||||||
export interface Actions {
|
export interface Actions {
|
||||||
[ActionTypes.LOAD_CONFIGURATION](
|
[ActionTypes.LOAD_CONFIGURATION](
|
||||||
{commit}: AugmentedActionContext,
|
{commit}: AugmentedActionContext,
|
||||||
):Promise<DynmapConfigurationResponse>
|
):Promise<void>
|
||||||
[ActionTypes.GET_UPDATE](
|
[ActionTypes.START_UPDATES](
|
||||||
{commit}: AugmentedActionContext,
|
{commit}: AugmentedActionContext,
|
||||||
):Promise<DynmapUpdateResponse>
|
):Promise<void>
|
||||||
[ActionTypes.GET_MARKER_SETS](
|
[ActionTypes.STOP_UPDATES](
|
||||||
{commit}: AugmentedActionContext,
|
{commit}: AugmentedActionContext,
|
||||||
):Promise<Map<string, DynmapMarkerSet>>
|
):Promise<void>
|
||||||
[ActionTypes.SET_PLAYERS](
|
[ActionTypes.SET_PLAYERS](
|
||||||
{commit}: AugmentedActionContext,
|
{commit}: AugmentedActionContext,
|
||||||
payload: Set<DynmapPlayer>
|
payload: Set<DynmapPlayer>
|
||||||
@ -78,21 +75,20 @@ export interface Actions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const actions: ActionTree<State, State> & Actions = {
|
export const actions: ActionTree<State, State> & Actions = {
|
||||||
async [ActionTypes.LOAD_CONFIGURATION]({commit, state}): Promise<DynmapConfigurationResponse> {
|
async [ActionTypes.LOAD_CONFIGURATION]({commit, state}): Promise<void> {
|
||||||
//Clear any existing has to avoid triggering a second config load, after this load changes the hash
|
//Clear any existing has to avoid triggering a second config load, after this load changes the hash
|
||||||
commit(MutationTypes.CLEAR_SERVER_CONFIGURATION_HASH, undefined);
|
commit(MutationTypes.CLEAR_SERVER_CONFIGURATION_HASH, undefined);
|
||||||
|
|
||||||
const config = await getAPI().getConfiguration();
|
if(!state.currentServer) {
|
||||||
|
console.warn('No current server');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
commit(MutationTypes.SET_SERVER_CONFIGURATION, config.config);
|
await state.currentMapProvider!.loadServerConfiguration();
|
||||||
commit(MutationTypes.SET_SERVER_MESSAGES, config.messages);
|
|
||||||
commit(MutationTypes.SET_WORLDS, config.worlds);
|
|
||||||
commit(MutationTypes.SET_COMPONENTS, config.components);
|
|
||||||
commit(MutationTypes.SET_LOGGED_IN, config.loggedIn);
|
|
||||||
|
|
||||||
//Skip default map/ui visibility logic if we already have a map selected (i.e config reload after hash change)
|
//Skip default map/ui visibility logic if we already have a map selected (i.e config reload after hash change)
|
||||||
if(state.currentMap) {
|
if(state.currentMap) {
|
||||||
return config;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Make UI visible if configured, there's enough space to do so, and this is the first config load
|
//Make UI visible if configured, there's enough space to do so, and this is the first config load
|
||||||
@ -104,8 +100,8 @@ export const actions: ActionTree<State, State> & Actions = {
|
|||||||
let worldName, mapName;
|
let worldName, mapName;
|
||||||
|
|
||||||
// Use config default world if it exists
|
// Use config default world if it exists
|
||||||
if(config.config.defaultWorld && state.worlds.has(config.config.defaultWorld)) {
|
if(state.configuration.defaultWorld && state.worlds.has(state.configuration.defaultWorld)) {
|
||||||
worldName = config.config.defaultWorld;
|
worldName = state.configuration.defaultWorld;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer world from parsed url if present and it exists
|
// Prefer world from parsed url if present and it exists
|
||||||
@ -122,8 +118,8 @@ export const actions: ActionTree<State, State> & Actions = {
|
|||||||
const world = state.worlds.get(worldName) as LiveAtlasWorldDefinition;
|
const world = state.worlds.get(worldName) as LiveAtlasWorldDefinition;
|
||||||
|
|
||||||
// Use config default map if it exists
|
// Use config default map if it exists
|
||||||
if(config.config.defaultMap && world.maps.has(config.config.defaultMap)) {
|
if(state.configuration.defaultMap && world.maps.has(state.configuration.defaultMap)) {
|
||||||
mapName = config.config.defaultMap;
|
mapName = state.configuration.defaultMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prefer map from parsed url if present and it exists
|
// Prefer map from parsed url if present and it exists
|
||||||
@ -142,27 +138,22 @@ export const actions: ActionTree<State, State> & Actions = {
|
|||||||
worldName, mapName
|
worldName, mapName
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return config;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async [ActionTypes.GET_UPDATE]({commit, dispatch, state}) {
|
async [ActionTypes.START_UPDATES]({state}) {
|
||||||
if(!state.currentWorld) {
|
if(!state.currentWorld) {
|
||||||
return Promise.reject("No current world");
|
return Promise.reject("No current world");
|
||||||
}
|
}
|
||||||
|
|
||||||
const update = await getAPI().getUpdate(state.updateRequestId, state.currentWorld.name, state.updateTimestamp.valueOf());
|
state.currentMapProvider!.startUpdates();
|
||||||
|
},
|
||||||
|
|
||||||
commit(MutationTypes.SET_WORLD_STATE, update.worldState);
|
async [ActionTypes.STOP_UPDATES]({state}) {
|
||||||
commit(MutationTypes.SET_UPDATE_TIMESTAMP, new Date(update.timestamp));
|
if(!state.currentWorld) {
|
||||||
commit(MutationTypes.INCREMENT_REQUEST_ID, undefined);
|
return Promise.reject("No current world");
|
||||||
commit(MutationTypes.ADD_MARKER_SET_UPDATES, update.updates.markerSets);
|
}
|
||||||
commit(MutationTypes.ADD_TILE_UPDATES, update.updates.tiles);
|
|
||||||
commit(MutationTypes.ADD_CHAT, update.updates.chat);
|
|
||||||
commit(MutationTypes.SET_SERVER_CONFIGURATION_HASH, update.configHash);
|
|
||||||
|
|
||||||
await dispatch(ActionTypes.SET_PLAYERS, update.players);
|
state.currentMapProvider!.stopUpdates();
|
||||||
return update;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
[ActionTypes.SET_PLAYERS]({commit, state}, players: Set<DynmapPlayer>) {
|
[ActionTypes.SET_PLAYERS]({commit, state}, players: Set<DynmapPlayer>) {
|
||||||
@ -191,17 +182,6 @@ export const actions: ActionTree<State, State> & Actions = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async [ActionTypes.GET_MARKER_SETS]({commit, state}) {
|
|
||||||
if(!state.currentWorld) {
|
|
||||||
throw new Error("No current world");
|
|
||||||
}
|
|
||||||
|
|
||||||
const markerSets = await getAPI().getMarkerSets(state.currentWorld.name)
|
|
||||||
commit(MutationTypes.SET_MARKER_SETS, markerSets);
|
|
||||||
|
|
||||||
return markerSets;
|
|
||||||
},
|
|
||||||
|
|
||||||
async [ActionTypes.POP_MARKER_UPDATES]({commit, state}, {markerSet, amount}: {markerSet: string, amount: number}): Promise<DynmapMarkerUpdate[]> {
|
async [ActionTypes.POP_MARKER_UPDATES]({commit, state}, {markerSet, amount}: {markerSet: string, amount: number}): Promise<DynmapMarkerUpdate[]> {
|
||||||
if(!state.markerSets.has(markerSet)) {
|
if(!state.markerSets.has(markerSet)) {
|
||||||
console.warn(`POP_MARKER_UPDATES: Marker set ${markerSet} doesn't exist`);
|
console.warn(`POP_MARKER_UPDATES: Marker set ${markerSet} doesn't exist`);
|
||||||
@ -263,6 +243,6 @@ export const actions: ActionTree<State, State> & Actions = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async [ActionTypes.SEND_CHAT_MESSAGE]({commit, state}, message: string): Promise<void> {
|
async [ActionTypes.SEND_CHAT_MESSAGE]({commit, state}, message: string): Promise<void> {
|
||||||
await getAPI().sendChatMessage(message);
|
await state.currentMapProvider!.sendChatMessage(message);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ export enum MutationTypes {
|
|||||||
CLEAR_MARKER_SETS = 'clearMarkerSets',
|
CLEAR_MARKER_SETS = 'clearMarkerSets',
|
||||||
ADD_WORLD = 'addWorld',
|
ADD_WORLD = 'addWorld',
|
||||||
SET_WORLD_STATE = 'setWorldState',
|
SET_WORLD_STATE = 'setWorldState',
|
||||||
SET_UPDATE_TIMESTAMP = 'setUpdateTimestamp',
|
|
||||||
ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates',
|
ADD_MARKER_SET_UPDATES = 'addMarkerSetUpdates',
|
||||||
ADD_TILE_UPDATES = 'addTileUpdates',
|
ADD_TILE_UPDATES = 'addTileUpdates',
|
||||||
ADD_CHAT = 'addChat',
|
ADD_CHAT = 'addChat',
|
||||||
@ -37,7 +36,6 @@ export enum MutationTypes {
|
|||||||
POP_CIRCLE_UPDATES = 'popCircleUpdates',
|
POP_CIRCLE_UPDATES = 'popCircleUpdates',
|
||||||
POP_LINE_UPDATES = 'popLineUpdates',
|
POP_LINE_UPDATES = 'popLineUpdates',
|
||||||
POP_TILE_UPDATES = 'popTileUpdates',
|
POP_TILE_UPDATES = 'popTileUpdates',
|
||||||
INCREMENT_REQUEST_ID = 'incrementRequestId',
|
|
||||||
SET_PLAYERS_ASYNC = 'setPlayersAsync',
|
SET_PLAYERS_ASYNC = 'setPlayersAsync',
|
||||||
CLEAR_PLAYERS = 'clearPlayers',
|
CLEAR_PLAYERS = 'clearPlayers',
|
||||||
SYNC_PLAYERS = 'syncPlayers',
|
SYNC_PLAYERS = 'syncPlayers',
|
||||||
|
@ -38,8 +38,9 @@ import {
|
|||||||
LiveAtlasParsedUrl,
|
LiveAtlasParsedUrl,
|
||||||
LiveAtlasGlobalConfig,
|
LiveAtlasGlobalConfig,
|
||||||
LiveAtlasGlobalMessageConfig,
|
LiveAtlasGlobalMessageConfig,
|
||||||
LiveAtlasServerMessageConfig
|
LiveAtlasServerMessageConfig, LiveAtlasDynmapServerDefinition
|
||||||
} from "@/index";
|
} from "@/index";
|
||||||
|
import DynmapMapProvider from "@/providers/DynmapMapProvider";
|
||||||
|
|
||||||
export type CurrentMapPayload = {
|
export type CurrentMapPayload = {
|
||||||
worldName: string;
|
worldName: string;
|
||||||
@ -59,7 +60,6 @@ export type Mutations<S = State> = {
|
|||||||
[MutationTypes.CLEAR_MARKER_SETS](state: S): void
|
[MutationTypes.CLEAR_MARKER_SETS](state: S): void
|
||||||
[MutationTypes.ADD_WORLD](state: S, world: LiveAtlasWorldDefinition): void
|
[MutationTypes.ADD_WORLD](state: S, world: LiveAtlasWorldDefinition): void
|
||||||
[MutationTypes.SET_WORLD_STATE](state: S, worldState: LiveAtlasWorldState): void
|
[MutationTypes.SET_WORLD_STATE](state: S, worldState: LiveAtlasWorldState): void
|
||||||
[MutationTypes.SET_UPDATE_TIMESTAMP](state: S, time: Date): void
|
|
||||||
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void
|
[MutationTypes.ADD_MARKER_SET_UPDATES](state: S, updates: Map<string, DynmapMarkerSetUpdates>): void
|
||||||
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array<DynmapTileUpdate>): void
|
[MutationTypes.ADD_TILE_UPDATES](state: S, updates: Array<DynmapTileUpdate>): void
|
||||||
[MutationTypes.ADD_CHAT](state: State, chat: Array<DynmapChat>): void
|
[MutationTypes.ADD_CHAT](state: State, chat: Array<DynmapChat>): void
|
||||||
@ -70,7 +70,6 @@ export type Mutations<S = State> = {
|
|||||||
[MutationTypes.POP_LINE_UPDATES](state: S, payload: {markerSet: string, amount: number}): void
|
[MutationTypes.POP_LINE_UPDATES](state: S, payload: {markerSet: string, amount: number}): void
|
||||||
[MutationTypes.POP_TILE_UPDATES](state: S, amount: number): void
|
[MutationTypes.POP_TILE_UPDATES](state: S, amount: number): void
|
||||||
|
|
||||||
[MutationTypes.INCREMENT_REQUEST_ID](state: S): void
|
|
||||||
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<DynmapPlayer>): Set<DynmapPlayer>
|
[MutationTypes.SET_PLAYERS_ASYNC](state: S, players: Set<DynmapPlayer>): Set<DynmapPlayer>
|
||||||
[MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void
|
[MutationTypes.SYNC_PLAYERS](state: S, keep: Set<string>): void
|
||||||
[MutationTypes.CLEAR_PLAYERS](state: S): void
|
[MutationTypes.CLEAR_PLAYERS](state: S): void
|
||||||
@ -257,11 +256,6 @@ export const mutations: MutationTree<State> & Mutations = {
|
|||||||
state.currentWorldState = Object.assign(state.currentWorldState, worldState);
|
state.currentWorldState = Object.assign(state.currentWorldState, worldState);
|
||||||
},
|
},
|
||||||
|
|
||||||
//Sets the timestamp of the last update fetch
|
|
||||||
[MutationTypes.SET_UPDATE_TIMESTAMP](state: State, timestamp: Date) {
|
|
||||||
state.updateTimestamp = timestamp;
|
|
||||||
},
|
|
||||||
|
|
||||||
//Adds markerset related updates from an update fetch to the pending updates list
|
//Adds markerset related updates from an update fetch to the pending updates list
|
||||||
[MutationTypes.ADD_MARKER_SET_UPDATES](state: State, updates: Map<string, DynmapMarkerSetUpdates>) {
|
[MutationTypes.ADD_MARKER_SET_UPDATES](state: State, updates: Map<string, DynmapMarkerSetUpdates>) {
|
||||||
for(const entry of updates) {
|
for(const entry of updates) {
|
||||||
@ -411,11 +405,6 @@ export const mutations: MutationTree<State> & Mutations = {
|
|||||||
state.pendingTileUpdates.splice(0, amount);
|
state.pendingTileUpdates.splice(0, amount);
|
||||||
},
|
},
|
||||||
|
|
||||||
//Increments the request id for the next update fetch
|
|
||||||
[MutationTypes.INCREMENT_REQUEST_ID](state: State) {
|
|
||||||
state.updateRequestId++;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Set up to 10 players at once
|
// Set up to 10 players at once
|
||||||
[MutationTypes.SET_PLAYERS_ASYNC](state: State, players: Set<DynmapPlayer>): Set<DynmapPlayer> {
|
[MutationTypes.SET_PLAYERS_ASYNC](state: State, players: Set<DynmapPlayer>): Set<DynmapPlayer> {
|
||||||
let count = 0;
|
let count = 0;
|
||||||
@ -493,6 +482,14 @@ export const mutations: MutationTree<State> & Mutations = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.currentServer = state.servers.get(serverName);
|
state.currentServer = state.servers.get(serverName);
|
||||||
|
|
||||||
|
if(state.currentMapProvider) {
|
||||||
|
state.currentMapProvider.stopUpdates();
|
||||||
|
state.currentMapProvider.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.currentMapProvider = Object.seal(
|
||||||
|
new DynmapMapProvider(state.servers.get(serverName) as LiveAtlasDynmapServerDefinition));
|
||||||
},
|
},
|
||||||
|
|
||||||
//Sets the currently active map/world
|
//Sets the currently active map/world
|
||||||
|
@ -29,7 +29,7 @@ import {
|
|||||||
LiveAtlasUIElement,
|
LiveAtlasUIElement,
|
||||||
LiveAtlasWorldDefinition,
|
LiveAtlasWorldDefinition,
|
||||||
LiveAtlasParsedUrl,
|
LiveAtlasParsedUrl,
|
||||||
LiveAtlasMessageConfig
|
LiveAtlasMessageConfig, LiveAtlasMapProvider
|
||||||
} from "@/index";
|
} from "@/index";
|
||||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||||
|
|
||||||
@ -60,6 +60,7 @@ export type State = {
|
|||||||
followTarget?: DynmapPlayer;
|
followTarget?: DynmapPlayer;
|
||||||
panTarget?: DynmapPlayer;
|
panTarget?: DynmapPlayer;
|
||||||
|
|
||||||
|
currentMapProvider?: Readonly<LiveAtlasMapProvider>;
|
||||||
currentServer?: LiveAtlasServerDefinition;
|
currentServer?: LiveAtlasServerDefinition;
|
||||||
currentWorldState: LiveAtlasWorldState;
|
currentWorldState: LiveAtlasWorldState;
|
||||||
currentWorld?: LiveAtlasWorldDefinition;
|
currentWorld?: LiveAtlasWorldDefinition;
|
||||||
@ -67,9 +68,6 @@ export type State = {
|
|||||||
currentLocation: Coordinate;
|
currentLocation: Coordinate;
|
||||||
currentZoom: number;
|
currentZoom: number;
|
||||||
|
|
||||||
updateRequestId: number;
|
|
||||||
updateTimestamp: Date;
|
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
playersAboveMarkers: boolean;
|
playersAboveMarkers: boolean;
|
||||||
playersSearch: boolean;
|
playersSearch: boolean;
|
||||||
@ -203,6 +201,7 @@ export const state: State = {
|
|||||||
followTarget: undefined,
|
followTarget: undefined,
|
||||||
panTarget: undefined,
|
panTarget: undefined,
|
||||||
|
|
||||||
|
currentMapProvider: undefined,
|
||||||
currentServer: undefined,
|
currentServer: undefined,
|
||||||
currentWorld: undefined,
|
currentWorld: undefined,
|
||||||
currentMap: undefined,
|
currentMap: undefined,
|
||||||
@ -218,9 +217,6 @@ export const state: State = {
|
|||||||
timeOfDay: 0,
|
timeOfDay: 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
updateRequestId: 0,
|
|
||||||
updateTimestamp: new Date(),
|
|
||||||
|
|
||||||
ui: {
|
ui: {
|
||||||
playersAboveMarkers: true,
|
playersAboveMarkers: true,
|
||||||
playersSearch: true,
|
playersSearch: true,
|
||||||
|
15
src/util.ts
15
src/util.ts
@ -14,7 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import API from '@/api';
|
|
||||||
import {DynmapPlayer} from "@/dynmap";
|
import {DynmapPlayer} from "@/dynmap";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition";
|
||||||
@ -32,6 +31,10 @@ const headCache = new Map<string, HTMLImageElement>(),
|
|||||||
|
|
||||||
headQueue: HeadQueueEntry[] = [];
|
headQueue: HeadQueueEntry[] = [];
|
||||||
|
|
||||||
|
export const titleColoursRegex = /§[0-9a-f]/ig;
|
||||||
|
export const netherWorldNameRegex = /_?nether(_|$)/i;
|
||||||
|
export const endWorldNameRegex = /(^|_)end(_|$)/i;
|
||||||
|
|
||||||
export const getMinecraftTime = (serverTime: number) => {
|
export const getMinecraftTime = (serverTime: number) => {
|
||||||
const day = serverTime >= 0 && serverTime < 13700;
|
const day = serverTime >= 0 && serverTime < 13700;
|
||||||
|
|
||||||
@ -211,16 +214,6 @@ export const parseMapSearchParams = (query: URLSearchParams) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAPI = () => {
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
if(!store.state.currentServer) {
|
|
||||||
throw new RangeError("No current server");
|
|
||||||
}
|
|
||||||
|
|
||||||
return API;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getUrlForLocation = (map: LiveAtlasMapDefinition, location: {
|
export const getUrlForLocation = (map: LiveAtlasMapDefinition, location: {
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
Loading…
Reference in New Issue
Block a user