From ef26d78c19073c9f931ff506366f3b61e00e0f7f Mon Sep 17 00:00:00 2001 From: James Lyne Date: Fri, 23 Jul 2021 20:32:15 +0100 Subject: [PATCH] Refactor map handling - Map definitions now have their own class - A map's projection is now a private property of the instance. Coordinates are converted via map instance methods. - Moved map icon handling to instance getter - Removed currentProjection and related mutations from store - Changed currentProjection watchers to currentMap --- src/api.ts | 17 +-- src/components/Map.vue | 17 +-- src/components/map/MapContextMenu.vue | 9 +- src/components/map/layer/MapLayer.vue | 6 +- src/components/map/marker/PlayerMarker.vue | 24 ++-- src/components/map/vector/Areas.vue | 12 +- src/components/map/vector/Circles.vue | 12 +- src/components/map/vector/Lines.vue | 12 +- src/components/map/vector/Markers.vue | 21 ++-- src/components/sidebar/WorldListItem.vue | 33 +---- src/dynmap.d.ts | 4 +- src/index.d.ts | 25 +--- src/leaflet/control/CoordinatesControl.ts | 4 +- src/leaflet/tileLayer/DynmapTileLayer.ts | 18 +-- src/model/LiveAtlasMapDefinition.ts | 118 ++++++++++++++++++ .../LiveAtlasProjection.ts} | 23 ++-- src/store/actions.ts | 4 +- src/store/getters.ts | 4 +- src/store/mutation-types.ts | 1 - src/store/mutations.ts | 17 +-- src/store/state.ts | 20 +-- src/util.ts | 22 ++-- src/util/markers.ts | 11 +- 23 files changed, 246 insertions(+), 188 deletions(-) create mode 100644 src/model/LiveAtlasMapDefinition.ts rename src/{leaflet/projection/DynmapProjection.ts => model/LiveAtlasProjection.ts} (79%) diff --git a/src/api.ts b/src/api.ts index 6b55e72..cc7acc7 100644 --- a/src/api.ts +++ b/src/api.ts @@ -33,7 +33,8 @@ import { } from "@/dynmap"; import {useStore} from "@/store"; import ChatError from "@/errors/ChatError"; -import {LiveAtlasDimension, LiveAtlasServerMessageConfig, LiveAtlasWorld} from "@/index"; +import {LiveAtlasDimension, LiveAtlasServerMessageConfig, LiveAtlasWorldDefinition} from "@/index"; +import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; const titleColours = /ยง[0-9a-f]/ig, netherWorldName = /_?nether(_|$)/i, @@ -72,8 +73,8 @@ function buildMessagesConfig(response: any): LiveAtlasServerMessageConfig { } } -function buildWorlds(response: any): Array { - const worlds: Map = new Map(); +function buildWorlds(response: any): Array { + const worlds: Map = new Map(); //Get all the worlds first so we can handle append_to_world properly (response.worlds || []).forEach((world: any) => { @@ -111,7 +112,7 @@ function buildWorlds(response: any): Array { return; } - w.maps.set(map.name, { + 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', @@ -123,11 +124,11 @@ function buildWorlds(response: any): Array { prefix: map.prefix || '', protected: map.protected || false, title: map.title || '', - mapToWorld: map.maptoworld || [0, 0, 0, 0, 0, 0, 0, 0, 0], - worldToMap: map.worldtomap || [0, 0, 0, 0, 0, 0, 0, 0, 0], + mapToWorld: map.maptoworld || undefined, + worldToMap: map.worldtomap || undefined, nativeZoomLevels: map.mapzoomout || 1, - extraZoomLevels: map.mapzoomin || 0, - }); + extraZoomLevels: map.mapzoomin || 0 + })); }); }); diff --git a/src/components/Map.vue b/src/components/Map.vue index 3b23e71..ea53818 100644 --- a/src/components/Map.vue +++ b/src/components/Map.vue @@ -81,7 +81,6 @@ export default defineComponent({ currentWorld = computed(() => store.state.currentWorld), currentMap = computed(() => store.state.currentMap), - currentProjection = computed(() => store.state.currentProjection), mapBackground = computed(() => store.getters.mapBackground), followTarget = computed(() => store.state.followTarget), @@ -114,7 +113,6 @@ export default defineComponent({ currentWorld, currentMap, - currentProjection, scheduledPan, scheduledZoom, @@ -142,7 +140,7 @@ export default defineComponent({ this.updateFollow(newValue, false); } }, - currentProjection(newValue, oldValue) { + currentMap(newValue, oldValue) { if(this.leaflet && newValue && oldValue) { const panTarget = this.scheduledPan || oldValue.latLngToLocation(this.leaflet.getCenter(), 64); @@ -196,12 +194,12 @@ export default defineComponent({ }, parsedUrl: { handler(newValue) { - if(!newValue || !this.currentWorld || !this.leaflet) { + if(!newValue || !this.currentMap || !this.leaflet) { return; } //URL points to different map - if(newValue.world !== this.currentWorld.name || newValue.map !== this.currentMap!.name) { + if(newValue.world !== this.currentWorld!.name || newValue.map !== this.currentMap!.name) { //Set scheduled pan for after map change this.scheduledPan = newValue.location; this.scheduledZoom = newValue.zoom; @@ -225,7 +223,7 @@ export default defineComponent({ animate: false, }); - this.leaflet.panTo(this.currentProjection.locationToLatLng(newValue.location), { + this.leaflet.panTo(this.currentMap.locationToLatLng(newValue.location), { animate: false, noMoveStart: true, }); @@ -259,7 +257,10 @@ export default defineComponent({ })); this.leaflet.on('moveend', () => { - useStore().commit(MutationTypes.SET_CURRENT_LOCATION, this.currentProjection.latLngToLocation(this.leaflet!.getCenter(), 64)); + if(this.currentMap) { + useStore().commit(MutationTypes.SET_CURRENT_LOCATION, this.currentMap + .latLngToLocation(this.leaflet!.getCenter(), 64)); + } }); this.leaflet.on('zoomend', () => { @@ -327,7 +328,7 @@ export default defineComponent({ console.log(`Switching map to match player ${targetWorld.name} ${map.name}`); store.commit(MutationTypes.SET_CURRENT_MAP, {worldName: targetWorld.name, mapName: map.name}); } else { - this.leaflet!.panTo(store.state.currentProjection.locationToLatLng(player.location)); + this.leaflet!.panTo(store.state.currentMap?.locationToLatLng(player.location)); if(newFollow) { console.log(`Setting zoom for new follow ${store.state.configuration.followZoom}`); diff --git a/src/components/map/MapContextMenu.vue b/src/components/map/MapContextMenu.vue index 8e066c5..6606a0d 100644 --- a/src/components/map/MapContextMenu.vue +++ b/src/components/map/MapContextMenu.vue @@ -59,18 +59,17 @@ export default defineComponent({ menuElement = ref(null), menuVisible = computed(() => !!event.value), - currentProjection = computed(() => store.state.currentProjection), currentWorld = computed(() => store.state.currentWorld), currentMap = computed(() => store.state.currentMap), currentZoom = computed(() => store.state.currentZoom), mapCount = computed(() => currentWorld.value ? currentWorld.value.maps.size : 0), location = computed(() => { - if (!event.value) { + if (!event.value || !currentMap.value) { return {x: 0, y: 0, z: 0} } - return currentProjection.value.latLngToLocation(event.value.latlng, 64); + return currentMap.value.latLngToLocation(event.value.latlng, 64); }), //Label for location button @@ -85,12 +84,12 @@ export default defineComponent({ //Url to copy url = computed(() => { - if (!currentWorld.value || !currentMap.value) { + if (!currentMap.value) { return ''; } const url = new URL(window.location.href); - url.hash = getUrlForLocation(currentWorld.value, currentMap.value, location.value, currentZoom.value); + url.hash = getUrlForLocation(currentMap.value, location.value, currentZoom.value); return url; }), diff --git a/src/components/map/layer/MapLayer.vue b/src/components/map/layer/MapLayer.vue index 17b05ed..17d1931 100644 --- a/src/components/map/layer/MapLayer.vue +++ b/src/components/map/layer/MapLayer.vue @@ -18,11 +18,10 @@ import {defineComponent, onUnmounted, computed, watch} from "@vue/runtime-core"; import {Map} from 'leaflet'; import {useStore} from "@/store"; -import {MutationTypes} from "@/store/mutation-types"; import {ActionTypes} from "@/store/action-types"; import {getMinecraftTime} from "@/util"; import {DynmapTileLayer} from "@/leaflet/tileLayer/DynmapTileLayer"; -import {LiveAtlasWorldMap} from "@/index"; +import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; export default defineComponent({ props: { @@ -31,7 +30,7 @@ export default defineComponent({ required: true }, map: { - type: Object as () => LiveAtlasWorldMap, + type: Object as () => LiveAtlasMapDefinition, required: true }, leaflet: { @@ -55,7 +54,6 @@ export default defineComponent({ active = computed(() => props.map === store.state.currentMap), enableLayer = () => { - useStore().commit(MutationTypes.SET_CURRENT_PROJECTION, layer.getProjection()); props.leaflet.addLayer(layer); stopUpdateWatch = watch(pendingUpdates, (newValue, oldValue) => { diff --git a/src/components/map/marker/PlayerMarker.vue b/src/components/map/marker/PlayerMarker.vue index 5b5d5ad..06935f9 100644 --- a/src/components/map/marker/PlayerMarker.vue +++ b/src/components/map/marker/PlayerMarker.vue @@ -39,7 +39,7 @@ export default defineComponent({ const store = useStore(), componentSettings = computed(() => store.state.components.playerMarkers), - currentProjection = computed(() => store.state.currentProjection), + currentMap = computed(() => store.state.currentMap), currentWorld = computed(() => store.state.currentWorld), chatBalloonsEnabled = computed(() => store.state.components.chatBalloons), @@ -147,8 +147,8 @@ export default defineComponent({ }, enableLayer = () => { - if(!markerVisible.value) { - const latLng = currentProjection.value.locationToLatLng(props.player.location); + if(currentMap.value && !markerVisible.value) { + const latLng = currentMap.value.locationToLatLng(props.player.location); props.layerGroup.addLayer(marker); marker.setLatLng(latLng); @@ -174,7 +174,7 @@ export default defineComponent({ }; onMounted(() => { - if(currentWorld.value && currentWorld.value.name === props.player.location.world) { + if(currentMap.value && currentWorld.value!.name === props.player.location.world) { enableLayer(); } }); @@ -183,7 +183,7 @@ export default defineComponent({ return { componentSettings, - currentProjection, + currentMap, currentWorld, chatBalloonsEnabled, @@ -203,11 +203,11 @@ export default defineComponent({ player: { deep: true, handler(newValue) { - if(this.currentWorld && newValue.location.world === this.currentWorld.name) { + if(this.currentMap && newValue.location.world === this.currentWorld!.name) { if(!this.markerVisible) { this.enableLayer(); } else { - const latLng = this.currentProjection.locationToLatLng(newValue.location); + const latLng = this.currentMap.locationToLatLng(newValue.location); this.marker.setLatLng(latLng); this.chatBalloon.setLatLng(latLng); @@ -232,11 +232,13 @@ export default defineComponent({ this.disableLayer(); } }, - currentProjection() { - const latLng = this.currentProjection.locationToLatLng(this.player.location); + currentMap(newValue) { + if(newValue) { + const latLng = newValue.locationToLatLng(this.player.location); - this.marker.setLatLng(latLng); - this.chatBalloon.setLatLng(latLng); + this.marker.setLatLng(latLng); + this.chatBalloon.setLatLng(latLng); + } } }, diff --git a/src/components/map/vector/Areas.vue b/src/components/map/vector/Areas.vue index ef9efbe..e9c9b2e 100644 --- a/src/components/map/vector/Areas.vue +++ b/src/components/map/vector/Areas.vue @@ -41,7 +41,7 @@ export default defineComponent({ let updateFrame = 0; const store = useStore(), - currentProjection = computed(() => store.state.currentProjection), + currentMap = computed(() => store.state.currentMap), pendingUpdates = computed(() => { const markerSetUpdates = store.state.pendingSetUpdates.get(props.set.id); @@ -102,11 +102,13 @@ export default defineComponent({ }; //FIXME: Prevent unnecessary repositioning when changing worlds - watch(currentProjection, () => { - const converter = getPointConverter(); + watch(currentMap, (newValue) => { + if(newValue) { + const converter = getPointConverter(); - for (const [id, area] of props.set.areas) { - updateArea(layers.get(id), area, converter); + for (const [id, area] of props.set.areas) { + updateArea(layers.get(id), area, converter); + } } }); diff --git a/src/components/map/vector/Circles.vue b/src/components/map/vector/Circles.vue index edd31e1..7276303 100644 --- a/src/components/map/vector/Circles.vue +++ b/src/components/map/vector/Circles.vue @@ -41,7 +41,7 @@ export default defineComponent({ let updateFrame = 0; const store = useStore(), - currentProjection = computed(() => store.state.currentProjection), + currentMap = computed(() => store.state.currentMap), pendingUpdates = computed(() => { const markerSetUpdates = store.state.pendingSetUpdates.get(props.set.id); @@ -102,11 +102,13 @@ export default defineComponent({ }; //FIXME: Prevent unnecessary repositioning when changing worlds - watch(currentProjection, () => { - const converter = getPointConverter(); + watch(currentMap, (newValue) => { + if(newValue) { + const converter = getPointConverter(); - for (const [id, circle] of props.set.circles) { - updateCircle(layers.get(id), circle, converter); + for (const [id, circle] of props.set.circles) { + updateCircle(layers.get(id), circle, converter); + } } }); diff --git a/src/components/map/vector/Lines.vue b/src/components/map/vector/Lines.vue index c64266a..18a77c7 100644 --- a/src/components/map/vector/Lines.vue +++ b/src/components/map/vector/Lines.vue @@ -40,7 +40,7 @@ export default defineComponent({ let updateFrame = 0; const store = useStore(), - currentProjection = computed(() => store.state.currentProjection), + currentMap = computed(() => store.state.currentMap), pendingUpdates = computed(() => { const markerSetUpdates = store.state.pendingSetUpdates.get(props.set.id); @@ -101,11 +101,13 @@ export default defineComponent({ }; //FIXME: Prevent unnecessary repositioning when changing worlds - watch(currentProjection, () => { - const converter = getPointConverter(); + watch(currentMap, (newValue) => { + if(newValue) { + const converter = getPointConverter(); - for (const [id, line] of props.set.lines) { - updateLine(layers.get(id), line, converter); + for (const [id, line] of props.set.lines) { + updateLine(layers.get(id), line, converter); + } } }); diff --git a/src/components/map/vector/Markers.vue b/src/components/map/vector/Markers.vue index 9a23782..3fae30d 100644 --- a/src/components/map/vector/Markers.vue +++ b/src/components/map/vector/Markers.vue @@ -22,6 +22,7 @@ import {DynmapMarker, DynmapMarkerSet} from "@/dynmap"; import {ActionTypes} from "@/store/action-types"; import {createMarker, updateMarker} from "@/util/markers"; import DynmapLayerGroup from "@/leaflet/layer/DynmapLayerGroup"; +import {getPointConverter} from "@/util"; export default defineComponent({ props: { @@ -39,7 +40,7 @@ export default defineComponent({ let updateFrame = 0; const store = useStore(), - currentProjection = computed(() => store.state.currentProjection), + currentMap = computed(() => store.state.currentMap), pendingUpdates = computed(() => { const markerSetUpdates = store.state.pendingSetUpdates.get(props.set.id); @@ -48,10 +49,10 @@ export default defineComponent({ layers = Object.freeze(new Map()) as Map, createMarkers = () => { - const projection = currentProjection.value; + const converter = getPointConverter(); props.set.markers.forEach((marker: DynmapMarker, id: string) => { - const layer = createMarker(marker, projection); + const layer = createMarker(marker, converter); layers.set(id, layer); props.layerGroup.addLayer(layer); @@ -75,13 +76,13 @@ export default defineComponent({ amount: 10, }); - const projection = currentProjection.value; + const converter = getPointConverter(); for(const update of updates) { if(update.removed) { deleteMarker(update.id); } else { - const layer = updateMarker(layers.get(update.id), update.payload as DynmapMarker, projection); + const layer = updateMarker(layers.get(update.id), update.payload as DynmapMarker, converter); if(!layers.has(update.id)) { props.layerGroup.addLayer(layer); @@ -100,11 +101,11 @@ export default defineComponent({ }; //FIXME: Prevent unnecessary repositioning when changing worlds - watch(currentProjection, () => { - const projection = currentProjection.value; - - for (const [id, marker] of props.set.markers) { - updateMarker(layers.get(id), marker, projection); + watch(currentMap, (newValue) => { + if(newValue) { + for (const [id, marker] of props.set.markers) { + updateMarker(layers.get(id), marker, getPointConverter()); + } } }); diff --git a/src/components/sidebar/WorldListItem.vue b/src/components/sidebar/WorldListItem.vue index bdb09be..ed105b7 100644 --- a/src/components/sidebar/WorldListItem.vue +++ b/src/components/sidebar/WorldListItem.vue @@ -23,7 +23,7 @@ v-bind:value="[world.name,map.name]" v-model="currentMap" :aria-labelledby="`${name}-${world.name}-${key}-label`"> @@ -46,14 +46,14 @@ import "@/assets/icons/block_the_end_surface.svg"; import "@/assets/icons/block_other.svg"; import "@/assets/icons/block_other_flat.svg"; import "@/assets/icons/block_skylands.svg"; -import {LiveAtlasWorld, LiveAtlasWorldMap} from "@/index"; +import {LiveAtlasWorldDefinition} from "@/index"; export default defineComponent({ name: 'WorldListItem', components: {SvgIcon}, props: { world: { - type: Object as () => LiveAtlasWorld, + type: Object as () => LiveAtlasWorldDefinition, required: true }, name: { @@ -72,33 +72,6 @@ export default defineComponent({ useStore().commit(MutationTypes.SET_CURRENT_MAP, {worldName: value[0], mapName: value[1]}); } } - }, - - methods: { - getMapIcon(map: LiveAtlasWorldMap): string { - let worldType: string, - mapType: string; - - switch(this.world.dimension) { - case 'nether': - worldType = 'nether'; - mapType = ['surface', 'nether'].includes(map.name) ? 'surface' : 'flat'; - break; - - case 'end': - worldType = 'the_end'; - mapType = ['surface', 'the_end'].includes(map.name) ? 'surface' : 'flat'; - break; - - case 'overworld': - default: - worldType = 'world'; - mapType = ['surface', 'flat', 'biome', 'cave'].includes(map.name) ? map.name : 'flat'; - break; - } - - return `block_${worldType}_${mapType}`; - } } }); diff --git a/src/dynmap.d.ts b/src/dynmap.d.ts index 4734b05..77193fd 100644 --- a/src/dynmap.d.ts +++ b/src/dynmap.d.ts @@ -22,7 +22,7 @@ import { Coordinate, LiveAtlasLocation, LiveAtlasServerMessageConfig, - LiveAtlasWorld, + LiveAtlasWorldDefinition, LiveAtlasWorldState } from "@/index"; @@ -102,7 +102,7 @@ interface DynmapChatSendingConfig { interface DynmapConfigurationResponse { config: DynmapServerConfig, messages: LiveAtlasServerMessageConfig, - worlds: Array, + worlds: Array, components: DynmapComponentConfig, loggedIn: boolean, } diff --git a/src/index.d.ts b/src/index.d.ts index 457af61..a55c725 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,5 +1,6 @@ import {State} from "@/store"; -import {DynmapPlayer, DynmapUrlConfig, LiveAtlasWorldMap} from "@/dynmap"; +import {DynmapPlayer, DynmapUrlConfig} from "@/dynmap"; +import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; declare module "*.png" { const value: any; @@ -111,7 +112,7 @@ interface LiveAtlasSortedPlayers extends Array { dirty?: boolean; } -interface LiveAtlasWorld { +interface LiveAtlasWorldDefinition { seaLevel: number; name: string; dimension: LiveAtlasDimension; @@ -119,7 +120,7 @@ interface LiveAtlasWorld { title: string; height: number; center: Coordinate; - maps: Map; + maps: Map; } interface LiveAtlasWorldState { @@ -128,24 +129,6 @@ interface LiveAtlasWorldState { timeOfDay: number; } -interface LiveAtlasWorldMap { - world: LiveAtlasWorld; - name: string; - icon: string; - title: string; - background: string; - nightAndDay: boolean; - backgroundDay?: string; - backgroundNight?: string; - imageFormat: string; - prefix: string; - protected: boolean; - mapToWorld: [number, number, number, number, number, number, number, number, number]; - worldToMap: [number, number, number, number, number, number, number, number, number]; - nativeZoomLevels: number; - extraZoomLevels: number; -} - interface LiveAtlasParsedUrl { world?: string; map?: string; diff --git a/src/leaflet/control/CoordinatesControl.ts b/src/leaflet/control/CoordinatesControl.ts index bc31736..7ac05b5 100644 --- a/src/leaflet/control/CoordinatesControl.ts +++ b/src/leaflet/control/CoordinatesControl.ts @@ -90,11 +90,11 @@ export class CoordinatesControl extends Control { } _onMouseMove(event: LeafletMouseEvent) { - if (!this._map || !store.state.currentWorld) { + if (!this._map || !store.state.currentMap) { return; } - this._location = store.state.currentProjection.latLngToLocation(event.latlng, store.state.currentWorld.seaLevel + 1); + this._location = store.state.currentMap.latLngToLocation(event.latlng, store.state.currentWorld!.seaLevel + 1); if(!this._locationChanged) { this._locationChanged = true; diff --git a/src/leaflet/tileLayer/DynmapTileLayer.ts b/src/leaflet/tileLayer/DynmapTileLayer.ts index 2dcd07b..01fa50b 100644 --- a/src/leaflet/tileLayer/DynmapTileLayer.ts +++ b/src/leaflet/tileLayer/DynmapTileLayer.ts @@ -18,20 +18,19 @@ */ import {TileLayer, Coords, DoneCallback, TileLayerOptions, DomUtil, Util, LatLng} from 'leaflet'; -import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import {store} from "@/store"; -import {Coordinate, LiveAtlasWorldMap} from "@/index"; +import {Coordinate} from "@/index"; +import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; export interface DynmapTileLayerOptions extends TileLayerOptions { - mapSettings: LiveAtlasWorldMap; + mapSettings: LiveAtlasMapDefinition; errorTileUrl: string; night?: boolean; } export interface DynmapTileLayer extends TileLayer { options: DynmapTileLayerOptions; - _projection: DynmapProjection; - _mapSettings: LiveAtlasWorldMap; + _mapSettings: LiveAtlasMapDefinition; _cachedTileUrls: Map; _namedTiles: Map; _tileTemplate: DynmapTileElement; @@ -87,11 +86,6 @@ export class DynmapTileLayer extends TileLayer { throw new TypeError("mapSettings missing"); } - this._projection = new DynmapProjection({ - mapToWorld: this._mapSettings.mapToWorld, - worldToMap: this._mapSettings.worldToMap, - nativeZoomLevels: this._mapSettings.nativeZoomLevels, - }); this._cachedTileUrls = Object.seal(new Map()); this._namedTiles = Object.seal(new Map()); this._loadQueue = []; @@ -276,10 +270,6 @@ export class DynmapTileLayer extends TileLayer { }; } - getProjection(): DynmapProjection { - return this._projection; - } - setNight(night: boolean) { if(this.options.night !== night) { this.options.night = night; diff --git a/src/model/LiveAtlasMapDefinition.ts b/src/model/LiveAtlasMapDefinition.ts new file mode 100644 index 0000000..ed021ef --- /dev/null +++ b/src/model/LiveAtlasMapDefinition.ts @@ -0,0 +1,118 @@ +/* + * Copyright 2021 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 {Coordinate, LiveAtlasWorldDefinition} from "@/index"; +import {LatLng} from "leaflet"; +import {LiveAtlasProjection} from "@/model/LiveAtlasProjection"; + +export interface LiveAtlasMapDefinitionOptions { + world: LiveAtlasWorldDefinition; + name: string; + title?: string; + icon?: string; + background?: string; + nightAndDay?: boolean; + backgroundDay?: string; + backgroundNight?: string; + imageFormat: string; + prefix?: string; + protected?: boolean; + mapToWorld?: [number, number, number, number, number, number, number, number, number]; + worldToMap?: [number, number, number, number, number, number, number, number, number]; + nativeZoomLevels: number; + extraZoomLevels: number; +} + +export default class LiveAtlasMapDefinition { + readonly world: LiveAtlasWorldDefinition; + readonly name: string; + readonly icon?: string; + readonly title: string; + readonly background: string; + readonly nightAndDay: boolean; + readonly backgroundDay?: string; + readonly backgroundNight?: string; + readonly imageFormat: string; + readonly prefix: string; + readonly protected: boolean; + private readonly projection?: Readonly; + readonly nativeZoomLevels: number; + readonly extraZoomLevels: number; + readonly scale: number; + + constructor(options: LiveAtlasMapDefinitionOptions) { + this.world = options.world; //Ignore append_to_world here otherwise things break + this.name = options.name; + this.icon = options.icon || undefined; + this.title = options.title || ''; + + this.background = options.background || '#000000'; + this.nightAndDay = options.nightAndDay || false; + this.backgroundDay = options.backgroundDay || '#000000'; + this.backgroundNight = options.backgroundNight || '#000000'; + + this.imageFormat = options.imageFormat; + this.prefix = options.prefix || ''; + this.protected = options.protected || false; + + this.nativeZoomLevels = options.nativeZoomLevels || 1; + this.extraZoomLevels = options.extraZoomLevels || 0; + this.scale = (1 / Math.pow(2, this.nativeZoomLevels)); + + if(options.mapToWorld || options.worldToMap) { + this.projection = Object.freeze(new LiveAtlasProjection({ + mapToWorld: options.mapToWorld || [0, 0, 0, 0, 0, 0, 0, 0, 0], + worldToMap: options.worldToMap || [0, 0, 0, 0, 0, 0, 0, 0, 0], + nativeZoomLevels: this.nativeZoomLevels, + })); + } + } + + locationToLatLng(location: Coordinate): LatLng { + return this.projection ? this.projection.locationToLatLng(location) + : new LatLng(-location.z * this.scale, location.x * this.scale); + } + + latLngToLocation(latLng: LatLng, y: number): Coordinate { + return this.projection ? this.projection.latLngToLocation(latLng, y) + : {x: latLng.lng / this.scale, y: y, z: -latLng.lat / this.scale}; + } + + getIcon(): string { + let worldType: string, + mapType: string; + + switch(this.world.dimension) { + case 'nether': + worldType = 'nether'; + mapType = ['surface', 'nether'].includes(this.name) ? 'surface' : 'flat'; + break; + + case 'end': + worldType = 'the_end'; + mapType = ['surface', 'the_end'].includes(this.name) ? 'surface' : 'flat'; + break; + + case 'overworld': + default: + worldType = 'world'; + mapType = ['surface', 'flat', 'biome', 'cave'].includes(this.name) ? this.name : 'flat'; + break; + } + + return `block_${worldType}_${mapType}`; + } +} diff --git a/src/leaflet/projection/DynmapProjection.ts b/src/model/LiveAtlasProjection.ts similarity index 79% rename from src/leaflet/projection/DynmapProjection.ts rename to src/model/LiveAtlasProjection.ts index b3e8d4c..310b30e 100644 --- a/src/leaflet/projection/DynmapProjection.ts +++ b/src/model/LiveAtlasProjection.ts @@ -17,27 +17,24 @@ * limitations under the License. */ -import {Util, LatLng, Class} from 'leaflet'; +import {LatLng} from 'leaflet'; import {Coordinate} from "@/index"; -export interface DynmapProjectionOptions { +export interface LiveAtlasProjectionOptions { mapToWorld: [number, number, number, number, number, number, number, number, number], worldToMap: [number, number, number, number, number, number, number, number, number], nativeZoomLevels: number } -export interface DynmapProjection { - options: DynmapProjectionOptions - locationToLatLng(location: Coordinate): LatLng; - latLngToLocation(latLng: LatLng, y: number): Coordinate; -} +export class LiveAtlasProjection { + private readonly options: LiveAtlasProjectionOptions -// noinspection JSUnusedGlobalSymbols -export class DynmapProjection extends Class { - - constructor(options: DynmapProjectionOptions) { - super(); - Util.setOptions(this, options); + constructor(options: LiveAtlasProjectionOptions) { + this.options = { + mapToWorld: options.mapToWorld || [0, 0, 0, 0, 0, 0, 0, 0], + worldToMap: options.worldToMap || [0, 0, 0, 0, 0, 0, 0, 0], + nativeZoomLevels: options.nativeZoomLevels || 1 + } } locationToLatLng(location: Coordinate): LatLng { diff --git a/src/store/actions.ts b/src/store/actions.ts index c0e1646..bc0d59b 100644 --- a/src/store/actions.ts +++ b/src/store/actions.ts @@ -28,7 +28,7 @@ import { DynmapUpdateResponse } from "@/dynmap"; import {getAPI} from "@/util"; -import {LiveAtlasWorld} from "@/index"; +import {LiveAtlasWorldDefinition} from "@/index"; type AugmentedActionContext = { commit( @@ -119,7 +119,7 @@ export const actions: ActionTree & Actions = { } if(worldName) { - const world = state.worlds.get(worldName) as LiveAtlasWorld; + const world = state.worlds.get(worldName) as LiveAtlasWorldDefinition; // Use config default map if it exists if(config.config.defaultMap && world.maps.has(config.config.defaultMap)) { diff --git a/src/store/getters.ts b/src/store/getters.ts index 361f0e9..a39a1d6 100644 --- a/src/store/getters.ts +++ b/src/store/getters.ts @@ -72,7 +72,7 @@ export const getters: GetterTree & Getters = { return ''; } - return getUrlForLocation(state.currentWorld, state.currentMap, {x,y,z}, zoom); + return getUrlForLocation(state.currentMap, {x,y,z}, zoom); }, serverConfig(state: State): LiveAtlasDynmapServerDefinition { @@ -82,4 +82,4 @@ export const getters: GetterTree & Getters = { return state.currentServer as LiveAtlasDynmapServerDefinition; }, -} \ No newline at end of file +} diff --git a/src/store/mutation-types.ts b/src/store/mutation-types.ts index d992dbb..0fcee97 100644 --- a/src/store/mutation-types.ts +++ b/src/store/mutation-types.ts @@ -44,7 +44,6 @@ export enum MutationTypes { SET_CURRENT_SERVER = 'setCurrentServer', SET_CURRENT_MAP = 'setCurrentMap', - SET_CURRENT_PROJECTION = 'setCurrentProjection', SET_CURRENT_LOCATION = 'setCurrentLocation', SET_CURRENT_ZOOM = 'setCurrentZoom', SET_PARSED_URL = 'setParsedUrl', diff --git a/src/store/mutations.ts b/src/store/mutations.ts index 597a1df..35ff007 100644 --- a/src/store/mutations.ts +++ b/src/store/mutations.ts @@ -28,14 +28,13 @@ import { DynmapServerConfig, DynmapTileUpdate, DynmapChat } from "@/dynmap"; -import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import { Coordinate, LiveAtlasWorldState, LiveAtlasSidebarSection, LiveAtlasSortedPlayers, LiveAtlasUIElement, - LiveAtlasWorld, + LiveAtlasWorldDefinition, LiveAtlasParsedUrl, LiveAtlasGlobalConfig, LiveAtlasGlobalMessageConfig, @@ -53,12 +52,12 @@ export type Mutations = { [MutationTypes.SET_SERVER_CONFIGURATION_HASH](state: S, hash: number): void [MutationTypes.CLEAR_SERVER_CONFIGURATION_HASH](state: S): void [MutationTypes.SET_SERVER_MESSAGES](state: S, messages: LiveAtlasServerMessageConfig): void - [MutationTypes.SET_WORLDS](state: S, worlds: Array): void + [MutationTypes.SET_WORLDS](state: S, worlds: Array): void [MutationTypes.CLEAR_WORLDS](state: S): void [MutationTypes.SET_COMPONENTS](state: S, worlds: DynmapComponentConfig): void [MutationTypes.SET_MARKER_SETS](state: S, worlds: Map): void [MutationTypes.CLEAR_MARKER_SETS](state: S): void - [MutationTypes.ADD_WORLD](state: S, world: LiveAtlasWorld): void + [MutationTypes.ADD_WORLD](state: S, world: LiveAtlasWorldDefinition): 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): void @@ -77,7 +76,6 @@ export type Mutations = { [MutationTypes.CLEAR_PLAYERS](state: S): void [MutationTypes.SET_CURRENT_SERVER](state: S, server: string): void [MutationTypes.SET_CURRENT_MAP](state: S, payload: CurrentMapPayload): void - [MutationTypes.SET_CURRENT_PROJECTION](state: S, payload: DynmapProjection): void [MutationTypes.SET_CURRENT_LOCATION](state: S, payload: Coordinate): void [MutationTypes.SET_CURRENT_ZOOM](state: S, payload: number): void [MutationTypes.SET_PARSED_URL](state: S, payload: LiveAtlasParsedUrl): void @@ -181,7 +179,7 @@ export const mutations: MutationTree & Mutations = { }, //Sets the list of worlds, and their settings, from the initial config fetch - [MutationTypes.SET_WORLDS](state: State, worlds: Array) { + [MutationTypes.SET_WORLDS](state: State, worlds: Array) { state.worlds.clear(); state.maps.clear(); @@ -250,7 +248,7 @@ export const mutations: MutationTree & Mutations = { state.pendingSetUpdates.clear(); }, - [MutationTypes.ADD_WORLD](state: State, world: LiveAtlasWorld) { + [MutationTypes.ADD_WORLD](state: State, world: LiveAtlasWorldDefinition) { state.worlds.set(world.name, world); }, @@ -521,11 +519,6 @@ export const mutations: MutationTree & Mutations = { state.currentMap = state.maps.get(mapName); }, - //Sets the projection to use for coordinate conversion in the current map - [MutationTypes.SET_CURRENT_PROJECTION](state: State, projection) { - state.currentProjection = projection; - }, - //Sets the current location the map is showing. This is called by the map itself, and calling elsewhere will not update the map. [MutationTypes.SET_CURRENT_LOCATION](state: State, payload: Coordinate) { state.currentLocation = payload; diff --git a/src/store/state.ts b/src/store/state.ts index 27b5709..024302e 100644 --- a/src/store/state.ts +++ b/src/store/state.ts @@ -20,7 +20,6 @@ import { DynmapServerConfig, DynmapTileUpdate, DynmapChat } from "@/dynmap"; -import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import { Coordinate, LiveAtlasWorldState, @@ -28,11 +27,11 @@ import { LiveAtlasSidebarSection, LiveAtlasSortedPlayers, LiveAtlasUIElement, - LiveAtlasWorld, - LiveAtlasWorldMap, + LiveAtlasWorldDefinition, LiveAtlasParsedUrl, LiveAtlasMessageConfig } from "@/index"; +import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; export type State = { version: string; @@ -44,8 +43,8 @@ export type State = { loggedIn: boolean; - worlds: Map; - maps: Map; + worlds: Map; + maps: Map; players: Map; sortedPlayers: LiveAtlasSortedPlayers; markerSets: Map; @@ -63,11 +62,10 @@ export type State = { currentServer?: LiveAtlasServerDefinition; currentWorldState: LiveAtlasWorldState; - currentWorld?: LiveAtlasWorld; - currentMap?: LiveAtlasWorldMap; + currentWorld?: LiveAtlasWorldDefinition; + currentMap?: LiveAtlasMapDefinition; currentLocation: Coordinate; currentZoom: number; - currentProjection: DynmapProjection; updateRequestId: number; updateTimestamp: Date; @@ -214,12 +212,6 @@ export const state: State = { z: 0, }, currentZoom: 0, - - currentProjection: new DynmapProjection({ - mapToWorld: [0, 0, 0, 0, 0, 0, 0, 0, 0], - worldToMap: [0, 0, 0, 0, 0, 0, 0, 0, 0], - nativeZoomLevels: 1 - }), //Projection for converting location <-> latlg. Object itself isn't reactive for performance reasons currentWorldState: { raining: false, thundering: false, diff --git a/src/util.ts b/src/util.ts index a86fb95..934a064 100644 --- a/src/util.ts +++ b/src/util.ts @@ -17,7 +17,7 @@ import API from '@/api'; import {DynmapPlayer} from "@/dynmap"; import {useStore} from "@/store"; -import {LiveAtlasWorld, LiveAtlasWorldMap} from "@/index"; +import LiveAtlasMapDefinition from "@/model/LiveAtlasMapDefinition"; interface HeadQueueEntry { cacheKey: string; @@ -115,11 +115,17 @@ export const concatURL = (base: string, addition: string) => { } export const getPointConverter = () => { - const projection = useStore().state.currentProjection; + const map = useStore().state.currentMap; - return (x: number, y: number, z: number) => { - return projection.locationToLatLng({x, y, z}); - }; + if(map) { + return (x: number, y: number, z: number) => { + return map.locationToLatLng({x, y, z}); + }; + } else { + return (x: number, y: number, z: number) => { + return LiveAtlasMapDefinition.defaultProjection.locationToLatLng({x, y, z}); + }; + } } export const parseUrl = () => { @@ -215,7 +221,7 @@ export const getAPI = () => { return API; } -export const getUrlForLocation = (world: LiveAtlasWorld, map: LiveAtlasWorldMap, location: { +export const getUrlForLocation = (map: LiveAtlasMapDefinition, location: { x: number, y: number, z: number }, zoom: number): string => { @@ -224,11 +230,11 @@ export const getUrlForLocation = (world: LiveAtlasWorld, map: LiveAtlasWorldMap, z = Math.round(location.z), locationString = `${x},${y},${z}`; - if(!world || !map) { + if(!map) { return ''; } - return `#${world.name};${map.name};${locationString};${zoom}`; + return `#${map.world.name};${map.name};${locationString};${zoom}`; } export const focus = (selector: string) => { diff --git a/src/util/markers.ts b/src/util/markers.ts index 30ce92f..1762ddd 100644 --- a/src/util/markers.ts +++ b/src/util/markers.ts @@ -20,11 +20,10 @@ import {LeafletMouseEvent, Marker} from "leaflet"; import {DynmapMarker} from "@/dynmap"; import {DynmapIcon} from "@/leaflet/icon/DynmapIcon"; -import {DynmapProjection} from "@/leaflet/projection/DynmapProjection"; import {GenericMarker} from "@/leaflet/marker/GenericMarker"; -export const createMarker = (options: DynmapMarker, projection: DynmapProjection): Marker => { - const marker = new GenericMarker(projection.locationToLatLng(options.location), { +export const createMarker = (options: DynmapMarker, converter: Function): Marker => { + const marker = new GenericMarker(converter(options.location.x, options.location.y, options.location.z), { icon: new DynmapIcon({ icon: options.icon, label: options.label, @@ -46,13 +45,13 @@ export const createMarker = (options: DynmapMarker, projection: DynmapProjection return marker; }; -export const updateMarker = (marker: Marker | undefined, options: DynmapMarker, projection: DynmapProjection): Marker => { +export const updateMarker = (marker: Marker | undefined, options: DynmapMarker, converter: Function): Marker => { if (!marker) { - return createMarker(options, projection); + return createMarker(options, converter); } const oldLocation = marker.getLatLng(), - newLocation = projection.locationToLatLng(options.location); + newLocation = converter(options.location.x, options.location.y, options.location.z); if(!oldLocation.equals(newLocation)) { marker.setLatLng(newLocation);