2021-07-24 00:15:52 +00:00
|
|
|
/*
|
2021-07-25 14:12:40 +00:00
|
|
|
* Copyright 2021 James Lyne
|
2021-07-24 00:15:52 +00:00
|
|
|
*
|
2021-07-25 14:12:40 +00:00
|
|
|
* 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
|
2021-07-24 00:15:52 +00:00
|
|
|
*
|
2021-07-25 14:12:40 +00:00
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
2021-07-24 00:15:52 +00:00
|
|
|
*
|
2021-07-25 14:12:40 +00:00
|
|
|
* 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.
|
2021-07-24 00:15:52 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
import {
|
2021-07-25 00:57:59 +00:00
|
|
|
HeadQueueEntry,
|
2021-07-24 03:06:19 +00:00
|
|
|
LiveAtlasMarkerSet,
|
2021-09-10 14:30:29 +00:00
|
|
|
LiveAtlasPlayer,
|
2021-07-24 03:06:19 +00:00
|
|
|
LiveAtlasServerDefinition,
|
2021-07-24 00:15:52 +00:00
|
|
|
LiveAtlasWorldDefinition
|
|
|
|
} from "@/index";
|
|
|
|
import ChatError from "@/errors/ChatError";
|
|
|
|
import {MutationTypes} from "@/store/mutation-types";
|
|
|
|
import MapProvider from "@/providers/MapProvider";
|
|
|
|
import {ActionTypes} from "@/store/action-types";
|
2021-09-10 14:30:29 +00:00
|
|
|
import {
|
|
|
|
buildAreas,
|
|
|
|
buildCircles, buildComponents,
|
|
|
|
buildLines,
|
|
|
|
buildMarkers,
|
|
|
|
buildMarkerSet,
|
|
|
|
buildMessagesConfig,
|
|
|
|
buildServerConfig, buildUpdates, buildWorlds
|
|
|
|
} from "@/util/dynmap";
|
2021-09-29 13:33:52 +00:00
|
|
|
import {getImagePixelSize} from "@/util";
|
2021-07-24 00:15:52 +00:00
|
|
|
|
|
|
|
export default class DynmapMapProvider extends MapProvider {
|
|
|
|
private configurationAbort?: AbortController = undefined;
|
|
|
|
private markersAbort?: AbortController = undefined;
|
|
|
|
private updateAbort?: AbortController = undefined;
|
|
|
|
|
|
|
|
private updatesEnabled = false;
|
2021-09-12 19:37:51 +00:00
|
|
|
private updateTimeout: null | ReturnType<typeof setTimeout> = null;
|
2021-07-24 00:15:52 +00:00
|
|
|
private updateTimestamp: Date = new Date();
|
|
|
|
private updateInterval: number = 3000;
|
|
|
|
|
2021-07-25 01:02:42 +00:00
|
|
|
constructor(config: LiveAtlasServerDefinition) {
|
|
|
|
super(config);
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
|
2021-07-25 00:57:59 +00:00
|
|
|
private async getMarkerSets(world: LiveAtlasWorldDefinition): Promise<Map<string, LiveAtlasMarkerSet>> {
|
|
|
|
const url = `${this.config.dynmap!.markers}_markers_/marker_${world.name}.json`;
|
2021-07-24 00:15:52 +00:00
|
|
|
|
|
|
|
if(this.markersAbort) {
|
|
|
|
this.markersAbort.abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.markersAbort = new AbortController();
|
|
|
|
|
2021-09-30 19:13:23 +00:00
|
|
|
const response = await this.getJSON(url, this.markersAbort.signal);
|
2021-07-24 03:06:19 +00:00
|
|
|
const sets: Map<string, LiveAtlasMarkerSet> = new Map();
|
2021-07-24 00:15:52 +00:00
|
|
|
|
|
|
|
response.sets = response.sets || {};
|
|
|
|
|
|
|
|
for (const key in response.sets) {
|
|
|
|
if (!Object.prototype.hasOwnProperty.call(response.sets, key)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const set = response.sets[key],
|
2021-09-10 14:30:29 +00:00
|
|
|
markers = buildMarkers(set.markers || {}),
|
|
|
|
circles = buildCircles(set.circles || {}),
|
|
|
|
areas = buildAreas(set.areas || {}),
|
|
|
|
lines = buildLines(set.lines || {});
|
2021-07-24 00:15:52 +00:00
|
|
|
|
|
|
|
sets.set(key, {
|
2021-09-10 14:30:29 +00:00
|
|
|
...buildMarkerSet(key, set),
|
2021-07-24 00:15:52 +00:00
|
|
|
markers,
|
|
|
|
circles,
|
|
|
|
areas,
|
|
|
|
lines,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return sets;
|
|
|
|
}
|
|
|
|
|
|
|
|
async loadServerConfiguration(): Promise<void> {
|
|
|
|
if(this.configurationAbort) {
|
|
|
|
this.configurationAbort.abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.configurationAbort = new AbortController();
|
|
|
|
|
2021-09-30 19:13:23 +00:00
|
|
|
const response = await this.getJSON(this.config.dynmap!.configuration, this.configurationAbort.signal);
|
2021-08-30 21:27:41 +00:00
|
|
|
|
|
|
|
if (response.error) {
|
2021-07-24 00:15:52 +00:00
|
|
|
throw new Error(response.error);
|
|
|
|
}
|
|
|
|
|
2021-09-10 14:30:29 +00:00
|
|
|
const config = buildServerConfig(response);
|
2021-07-24 00:15:52 +00:00
|
|
|
|
2021-07-24 01:13:03 +00:00
|
|
|
this.updateInterval = response.updaterate || 3000;
|
2021-07-24 00:15:52 +00:00
|
|
|
|
2021-07-24 03:03:36 +00:00
|
|
|
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION, config);
|
|
|
|
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION_HASH, response.confighash || 0);
|
2021-07-28 22:50:59 +00:00
|
|
|
this.store.commit(MutationTypes.SET_MAX_PLAYERS, response.maxcount || 0);
|
2021-09-10 14:30:29 +00:00
|
|
|
this.store.commit(MutationTypes.SET_SERVER_MESSAGES, buildMessagesConfig(response));
|
|
|
|
this.store.commit(MutationTypes.SET_WORLDS, buildWorlds(response));
|
|
|
|
this.store.commit(MutationTypes.SET_COMPONENTS, buildComponents(response));
|
2021-07-24 03:03:36 +00:00
|
|
|
this.store.commit(MutationTypes.SET_LOGGED_IN, response.loggedin || false);
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
|
2021-07-25 00:57:59 +00:00
|
|
|
async populateWorld(world: LiveAtlasWorldDefinition): Promise<void> {
|
|
|
|
const markerSets = await this.getMarkerSets(world);
|
2021-07-24 00:15:52 +00:00
|
|
|
|
2021-09-08 14:21:27 +00:00
|
|
|
this.store.commit(MutationTypes.SET_MARKER_SETS, markerSets);
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
|
2021-07-24 03:04:43 +00:00
|
|
|
private async getUpdate(): Promise<void> {
|
2021-07-25 00:57:59 +00:00
|
|
|
let url = this.config.dynmap!.update;
|
2021-07-24 00:15:52 +00:00
|
|
|
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();
|
|
|
|
|
2021-09-30 19:13:23 +00:00
|
|
|
const response = await this.getJSON(url, this.updateAbort.signal);
|
2021-07-24 03:04:43 +00:00
|
|
|
const players: Set<LiveAtlasPlayer> = new Set(),
|
2021-09-10 14:30:29 +00:00
|
|
|
updates = buildUpdates(response.updates || [], this.updateTimestamp),
|
2021-07-24 03:04:43 +00:00
|
|
|
worldState = {
|
|
|
|
timeOfDay: response.servertime || 0,
|
|
|
|
thundering: response.isThundering || false,
|
|
|
|
raining: response.hasStorm || false,
|
|
|
|
};
|
2021-07-24 00:15:52 +00:00
|
|
|
|
|
|
|
(response.players || []).forEach((player: any) => {
|
|
|
|
const world = player.world && player.world !== '-some-other-bogus-world-' ? player.world : undefined;
|
|
|
|
|
|
|
|
players.add({
|
2021-07-24 01:27:25 +00:00
|
|
|
name: player.account || "",
|
|
|
|
displayName: player.name || "",
|
2021-07-24 00:15:52 +00:00
|
|
|
health: player.health || 0,
|
|
|
|
armor: player.armor || 0,
|
|
|
|
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",
|
|
|
|
// }
|
|
|
|
// });
|
|
|
|
// }
|
|
|
|
|
2021-07-24 03:04:43 +00:00
|
|
|
this.updateTimestamp = new Date(response.timestamp || 0);
|
|
|
|
|
|
|
|
this.store.commit(MutationTypes.SET_WORLD_STATE, worldState);
|
|
|
|
this.store.commit(MutationTypes.ADD_MARKER_SET_UPDATES, updates.markerSets);
|
|
|
|
this.store.commit(MutationTypes.ADD_TILE_UPDATES, updates.tiles);
|
|
|
|
this.store.commit(MutationTypes.ADD_CHAT, updates.chat);
|
2021-08-03 01:26:43 +00:00
|
|
|
|
|
|
|
if(response.configHash) {
|
|
|
|
this.store.commit(MutationTypes.SET_SERVER_CONFIGURATION_HASH, response.confighash || 0);
|
|
|
|
}
|
2021-07-24 03:04:43 +00:00
|
|
|
|
|
|
|
await this.store.dispatch(ActionTypes.SET_PLAYERS, players);
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
sendChatMessage(message: string) {
|
2021-09-08 14:21:27 +00:00
|
|
|
if (!this.store.state.components.chatSending) {
|
|
|
|
return Promise.reject(this.store.state.messages.chatErrorDisabled);
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
|
2021-07-25 00:57:59 +00:00
|
|
|
return fetch(this.config.dynmap!.sendmessage, {
|
2021-07-24 00:15:52 +00:00
|
|
|
method: 'POST',
|
2021-08-30 21:17:52 +00:00
|
|
|
credentials: 'include',
|
2021-07-24 00:15:52 +00:00
|
|
|
body: JSON.stringify({
|
|
|
|
name: null,
|
|
|
|
message: message,
|
|
|
|
})
|
|
|
|
}).then((response) => {
|
|
|
|
if (response.status === 403) { //Rate limited
|
2021-09-08 14:21:27 +00:00
|
|
|
throw new ChatError(this.store.state.messages.chatErrorCooldown
|
|
|
|
.replace('%interval%', this.store.state.components.chatSending!.cooldown.toString()));
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
throw new Error('Network request failed');
|
|
|
|
}
|
|
|
|
|
|
|
|
return response.json();
|
|
|
|
}).then(response => {
|
|
|
|
if (response.error !== 'none') {
|
2021-09-08 14:21:27 +00:00
|
|
|
throw new ChatError(this.store.state.messages.chatErrorNotAllowed);
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
}).catch(e => {
|
|
|
|
if (!(e instanceof ChatError)) {
|
2021-09-08 14:21:27 +00:00
|
|
|
console.error(this.store.state.messages.chatErrorUnknown);
|
2021-07-24 00:15:52 +00:00
|
|
|
console.trace(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
throw e;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
startUpdates() {
|
|
|
|
this.updatesEnabled = true;
|
|
|
|
this.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
private async update() {
|
|
|
|
try {
|
2021-07-24 03:04:43 +00:00
|
|
|
await this.getUpdate();
|
2021-07-24 00:15:52 +00:00
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
|
2021-09-12 19:37:51 +00:00
|
|
|
this.updateTimeout = null;
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|
|
|
|
|
2021-07-25 00:57:59 +00:00
|
|
|
getTilesUrl(): string {
|
|
|
|
return this.config.dynmap!.tiles;
|
|
|
|
}
|
|
|
|
|
|
|
|
getPlayerHeadUrl(head: HeadQueueEntry): string {
|
2021-09-29 13:33:52 +00:00
|
|
|
const baseUrl = `${this.config.dynmap!.markers}faces/`;
|
2021-07-25 00:57:59 +00:00
|
|
|
|
2021-09-29 13:33:52 +00:00
|
|
|
if(head.size === 'body') {
|
|
|
|
return `${baseUrl}body/${head.name}.png`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const pixels = getImagePixelSize(head.size);
|
|
|
|
return `${baseUrl}${pixels}x${pixels}/${head.name}.png`;
|
2021-07-25 00:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
getMarkerIconUrl(icon: string): string {
|
|
|
|
return `${this.config.dynmap!.markers}_markers_/${icon}.png`;
|
|
|
|
}
|
|
|
|
|
2021-08-30 21:27:41 +00:00
|
|
|
async login(data: any) {
|
2021-09-08 14:23:21 +00:00
|
|
|
if (!this.store.getters.loginEnabled) {
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.loginErrorDisabled);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
|
2021-09-08 14:21:27 +00:00
|
|
|
this.store.commit(MutationTypes.SET_LOGGED_IN, false);
|
2021-08-30 21:27:41 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
const body = new URLSearchParams();
|
|
|
|
|
|
|
|
body.append('j_username', data.username || '');
|
|
|
|
body.append('j_password', data.password || '');
|
|
|
|
|
|
|
|
|
|
|
|
const response = await DynmapMapProvider.fetchJSON(this.config.dynmap!.login, {
|
|
|
|
method: 'POST',
|
|
|
|
body,
|
|
|
|
});
|
|
|
|
|
|
|
|
switch(response.result) {
|
|
|
|
case 'success':
|
2021-09-08 14:21:27 +00:00
|
|
|
this.store.commit(MutationTypes.SET_LOGGED_IN, true);
|
2021-08-30 21:27:41 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
case 'loginfailed':
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.loginErrorIncorrect);
|
2021-08-30 21:27:41 +00:00
|
|
|
|
|
|
|
default:
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.loginErrorUnknown);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
} catch(e) {
|
2021-09-08 14:21:27 +00:00
|
|
|
console.error(this.store.state.messages.loginErrorUnknown);
|
2021-08-30 21:27:41 +00:00
|
|
|
console.trace(e);
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.loginErrorUnknown);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async logout() {
|
2021-09-08 14:23:21 +00:00
|
|
|
if (!this.store.getters.loginEnabled) {
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.loginErrorDisabled);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
await DynmapMapProvider.fetchJSON(this.config.dynmap!.login, {
|
|
|
|
method: 'POST',
|
|
|
|
});
|
|
|
|
|
2021-09-08 14:21:27 +00:00
|
|
|
this.store.commit(MutationTypes.SET_LOGGED_IN, false);
|
2021-08-30 21:27:41 +00:00
|
|
|
} catch(e) {
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.logoutErrorUnknown);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async register(data: any) {
|
2021-09-08 14:23:21 +00:00
|
|
|
if (!this.store.getters.loginEnabled) {
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.loginErrorDisabled);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
|
2021-09-08 14:21:27 +00:00
|
|
|
this.store.commit(MutationTypes.SET_LOGGED_IN, false);
|
2021-08-30 21:27:41 +00:00
|
|
|
|
|
|
|
try {
|
|
|
|
const body = new URLSearchParams();
|
|
|
|
|
|
|
|
body.append('j_username', data.username || '');
|
|
|
|
body.append('j_password', data.password || '');
|
|
|
|
body.append('j_verify_password', data.password || '');
|
|
|
|
body.append('j_passcode', data.code || '');
|
|
|
|
|
|
|
|
const response = await DynmapMapProvider.fetchJSON(this.config.dynmap!.register, {
|
|
|
|
method: 'POST',
|
|
|
|
body,
|
|
|
|
});
|
|
|
|
|
|
|
|
switch(response.result) {
|
|
|
|
case 'success':
|
2021-09-08 14:21:27 +00:00
|
|
|
this.store.commit(MutationTypes.SET_LOGGED_IN, true);
|
2021-08-30 21:27:41 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
case 'verifyfailed':
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.registerErrorVerifyFailed);
|
2021-08-30 21:27:41 +00:00
|
|
|
|
|
|
|
case 'registerfailed':
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.registerErrorIncorrect);
|
2021-08-30 21:27:41 +00:00
|
|
|
|
|
|
|
default:
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.registerErrorUnknown);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
} catch(e) {
|
2021-09-08 14:21:27 +00:00
|
|
|
console.error(this.store.state.messages.registerErrorUnknown);
|
2021-08-30 21:27:41 +00:00
|
|
|
console.trace(e);
|
2021-09-08 14:21:27 +00:00
|
|
|
return Promise.reject(this.store.state.messages.registerErrorUnknown);
|
2021-08-30 21:27:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-24 00:15:52 +00:00
|
|
|
destroy() {
|
2021-07-25 00:57:59 +00:00
|
|
|
super.destroy();
|
|
|
|
|
2021-07-24 00:15:52 +00:00
|
|
|
if(this.configurationAbort) {
|
|
|
|
this.configurationAbort.abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(this.updateAbort) {
|
|
|
|
this.updateAbort.abort();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(this.markersAbort) {
|
|
|
|
this.markersAbort.abort();
|
|
|
|
}
|
|
|
|
}
|
2021-09-30 19:13:23 +00:00
|
|
|
|
|
|
|
protected async getJSON(url: string, signal: AbortSignal) {
|
|
|
|
return MapProvider.fetchJSON(url, {signal, credentials: 'include'}).then(response => {
|
|
|
|
if(response.error === 'login-required') {
|
|
|
|
this.store.commit(MutationTypes.SET_LOGIN_REQUIRED, true);
|
|
|
|
throw new Error("Login required");
|
|
|
|
}
|
|
|
|
|
|
|
|
return response;
|
|
|
|
});
|
|
|
|
}
|
2021-07-24 00:15:52 +00:00
|
|
|
}
|