mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-26 04:25:31 +01:00
Implement playermarkers on the web-app
This commit is contained in:
parent
b6d358a097
commit
c4f0256ca0
@ -35,7 +35,6 @@
|
|||||||
import de.bluecolored.bluemap.common.MapType;
|
import de.bluecolored.bluemap.common.MapType;
|
||||||
import de.bluecolored.bluemap.common.RenderManager;
|
import de.bluecolored.bluemap.common.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
|
||||||
|
|
||||||
public class MapUpdateHandler implements ServerEventListener {
|
public class MapUpdateHandler implements ServerEventListener {
|
||||||
|
|
||||||
@ -136,19 +135,4 @@ public void flushTileBuffer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerJoin(UUID playerUuid) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlayerLeave(UUID playerUuid) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChatMessage(Text message) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@
|
|||||||
import de.bluecolored.bluemap.common.RenderManager;
|
import de.bluecolored.bluemap.common.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||||
|
import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater;
|
||||||
import de.bluecolored.bluemap.core.config.ConfigManager;
|
import de.bluecolored.bluemap.core.config.ConfigManager;
|
||||||
import de.bluecolored.bluemap.core.config.MainConfig;
|
import de.bluecolored.bluemap.core.config.MainConfig;
|
||||||
import de.bluecolored.bluemap.core.config.MainConfig.MapConfig;
|
import de.bluecolored.bluemap.core.config.MainConfig.MapConfig;
|
||||||
@ -86,6 +87,7 @@ public class Plugin {
|
|||||||
private Map<String, MapType> maps;
|
private Map<String, MapType> maps;
|
||||||
|
|
||||||
private MapUpdateHandler updateHandler;
|
private MapUpdateHandler updateHandler;
|
||||||
|
private PlayerSkinUpdater skinUpdater;
|
||||||
|
|
||||||
private RenderManager renderManager;
|
private RenderManager renderManager;
|
||||||
private BlueMapWebServer webServer;
|
private BlueMapWebServer webServer;
|
||||||
@ -269,6 +271,10 @@ public synchronized void load() throws IOException, ParseResourceException {
|
|||||||
this.updateHandler = new MapUpdateHandler(this);
|
this.updateHandler = new MapUpdateHandler(this);
|
||||||
serverInterface.registerListener(updateHandler);
|
serverInterface.registerListener(updateHandler);
|
||||||
|
|
||||||
|
//start skin updater
|
||||||
|
this.skinUpdater = new PlayerSkinUpdater(config.getWebRoot().resolve("assets").resolve("playerheads").toFile());
|
||||||
|
serverInterface.registerListener(skinUpdater);
|
||||||
|
|
||||||
//create/update webfiles
|
//create/update webfiles
|
||||||
WebFilesManager webFilesManager = new WebFilesManager(config.getWebRoot());
|
WebFilesManager webFilesManager = new WebFilesManager(config.getWebRoot());
|
||||||
if (webFilesManager.needsUpdate()) {
|
if (webFilesManager.needsUpdate()) {
|
||||||
|
@ -33,16 +33,16 @@
|
|||||||
|
|
||||||
public interface ServerEventListener {
|
public interface ServerEventListener {
|
||||||
|
|
||||||
void onWorldSaveToDisk(UUID world);
|
default void onWorldSaveToDisk(UUID world) {};
|
||||||
|
|
||||||
void onBlockChange(UUID world, Vector3i blockPos);
|
default void onBlockChange(UUID world, Vector3i blockPos) {};
|
||||||
|
|
||||||
void onChunkFinishedGeneration(UUID world, Vector2i chunkPos);
|
default void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {};
|
||||||
|
|
||||||
void onPlayerJoin(UUID playerUuid);
|
default void onPlayerJoin(UUID playerUuid) {};
|
||||||
|
|
||||||
void onPlayerLeave(UUID playerUuid);
|
default void onPlayerLeave(UUID playerUuid) {};
|
||||||
|
|
||||||
void onChatMessage(Text message);
|
default void onChatMessage(Text message) {};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.bluecolored.bluemap.common.plugin.skins;
|
||||||
|
|
||||||
|
import java.awt.Graphics2D;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
|
||||||
|
import com.google.gson.JsonArray;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
|
|
||||||
|
public class PlayerSkin {
|
||||||
|
|
||||||
|
private final UUID uuid;
|
||||||
|
private long lastUpdate;
|
||||||
|
|
||||||
|
public PlayerSkin(UUID uuid) {
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.lastUpdate = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(File storageFolder) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (lastUpdate > 0 && lastUpdate + 600000 > now) return; // only update if skin is older than 10 minutes
|
||||||
|
|
||||||
|
lastUpdate = now;
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Future<BufferedImage> futureSkin = loadSkin();
|
||||||
|
BufferedImage skin = futureSkin.get(10, TimeUnit.SECONDS);
|
||||||
|
BufferedImage head = createHead(skin);
|
||||||
|
ImageIO.write(head, "png", new File(storageFolder, uuid.toString() + ".png"));
|
||||||
|
} catch (ExecutionException | TimeoutException e) {
|
||||||
|
Logger.global.logWarning("Failed to load player-skin from mojang-servers: " + e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
Logger.global.logError("Failed to write player-head image!", e);
|
||||||
|
} catch (InterruptedException ignore) {}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BufferedImage createHead(BufferedImage skinTexture) {
|
||||||
|
BufferedImage head = new BufferedImage(8, 8, skinTexture.getType());
|
||||||
|
|
||||||
|
BufferedImage layer1 = skinTexture.getSubimage(8, 8, 8, 8);
|
||||||
|
BufferedImage layer2 = skinTexture.getSubimage(40, 8, 8, 8);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Graphics2D g = head.createGraphics();
|
||||||
|
g.drawImage(layer1, 0, 0, null);
|
||||||
|
g.drawImage(layer2, 0, 0, null);
|
||||||
|
} catch (Throwable t) { // There might be problems with headless servers when loading the graphics class
|
||||||
|
Logger.global.noFloodWarning("headless-graphics-fail",
|
||||||
|
"Could not access Graphics2D to render player-skin texture. Try adding '-Djava.awt.headless=true' to your startup flags or ignore this warning.");
|
||||||
|
|
||||||
|
layer1.copyData(head.getRaster());
|
||||||
|
}
|
||||||
|
|
||||||
|
return head;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Future<BufferedImage> loadSkin() {
|
||||||
|
CompletableFuture<BufferedImage> image = new CompletableFuture<>();
|
||||||
|
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
JsonParser parser = new JsonParser();
|
||||||
|
try (Reader reader = requestProfileJson()) {
|
||||||
|
String textureInfoJson = readTextureInfoJson(parser.parse(reader));
|
||||||
|
String textureUrl = readTextureUrl(parser.parse(textureInfoJson));
|
||||||
|
image.complete(ImageIO.read(new URL(textureUrl)));
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
image.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Reader requestProfileJson() throws IOException {
|
||||||
|
URL url = new URL("https://sessionserver.mojang.com/session/minecraft/profile/" + this.uuid);
|
||||||
|
return new InputStreamReader(url.openStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readTextureInfoJson(JsonElement json) throws IOException {
|
||||||
|
try {
|
||||||
|
JsonArray properties = json.getAsJsonObject().getAsJsonArray("properties");
|
||||||
|
|
||||||
|
for (JsonElement element : properties) {
|
||||||
|
if (element.getAsJsonObject().get("name").getAsString().equals("textures")) {
|
||||||
|
return new String(Base64.getDecoder().decode(element.getAsJsonObject().get("value").getAsString().getBytes()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new IOException("No texture info found!");
|
||||||
|
} catch (IllegalStateException | ClassCastException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readTextureUrl(JsonElement json) throws IOException {
|
||||||
|
try {
|
||||||
|
return json.getAsJsonObject()
|
||||||
|
.getAsJsonObject("textures")
|
||||||
|
.getAsJsonObject("SKIN")
|
||||||
|
.get("url").getAsString();
|
||||||
|
} catch (IllegalStateException | ClassCastException e) {
|
||||||
|
throw new IOException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of BlueMap, licensed under the MIT License (MIT).
|
||||||
|
*
|
||||||
|
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package de.bluecolored.bluemap.common.plugin.skins;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||||
|
|
||||||
|
public class PlayerSkinUpdater implements ServerEventListener {
|
||||||
|
|
||||||
|
private File storageFolder;
|
||||||
|
|
||||||
|
private Map<UUID, PlayerSkin> skins;
|
||||||
|
|
||||||
|
public PlayerSkinUpdater(File storageFolder) {
|
||||||
|
this.storageFolder = storageFolder;
|
||||||
|
this.skins = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
this.storageFolder.mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateSkin(UUID playerUuid) {
|
||||||
|
PlayerSkin skin = skins.get(playerUuid);
|
||||||
|
|
||||||
|
if (skin == null) {
|
||||||
|
skin = new PlayerSkin(playerUuid);
|
||||||
|
skins.put(playerUuid, skin);
|
||||||
|
}
|
||||||
|
|
||||||
|
skin.update(storageFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPlayerJoin(UUID playerUuid) {
|
||||||
|
updateSkin(playerUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -198,9 +198,7 @@ public synchronized Texture loadTexture(FileAccess fileAccess, String path) thro
|
|||||||
|
|
||||||
//crop off animation frames
|
//crop off animation frames
|
||||||
if (image.getHeight() > image.getWidth()){
|
if (image.getHeight() > image.getWidth()){
|
||||||
BufferedImage cropped = new BufferedImage(image.getWidth(), image.getWidth(), image.getType());
|
image = image.getSubimage(0, 0, image.getWidth(), image.getWidth());
|
||||||
image.copyData(cropped.getRaster());
|
|
||||||
image = cropped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//check halfTransparency
|
//check halfTransparency
|
||||||
|
BIN
BlueMapCore/src/main/webroot/assets/playerheads/alex.png
Normal file
BIN
BlueMapCore/src/main/webroot/assets/playerheads/alex.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
BlueMapCore/src/main/webroot/assets/playerheads/steve.png
Normal file
BIN
BlueMapCore/src/main/webroot/assets/playerheads/steve.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
@ -60,9 +60,10 @@ import { stringToImage, pathFromCoords } from './utils.js';
|
|||||||
import {cachePreventionNr, getCookie, setCookie} from "./utils";
|
import {cachePreventionNr, getCookie, setCookie} from "./utils";
|
||||||
|
|
||||||
export default class BlueMap {
|
export default class BlueMap {
|
||||||
constructor(element, dataRoot) {
|
constructor(element, dataRoot = "data/", liveApiRoot = "live/") {
|
||||||
this.element = $('<div class="bluemap-container"></div>').appendTo(element)[0];
|
this.element = $('<div class="bluemap-container"></div>').appendTo(element)[0];
|
||||||
this.dataRoot = dataRoot;
|
this.dataRoot = dataRoot;
|
||||||
|
this.liveApiRoot = liveApiRoot;
|
||||||
this.locationHash = '';
|
this.locationHash = '';
|
||||||
this.cacheSuffix = '';
|
this.cacheSuffix = '';
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import $ from "jquery";
|
|||||||
import ToggleButton from "../ui/ToggleButton";
|
import ToggleButton from "../ui/ToggleButton";
|
||||||
import Label from "../ui/Label";
|
import Label from "../ui/Label";
|
||||||
import {cachePreventionNr} from "../utils";
|
import {cachePreventionNr} from "../utils";
|
||||||
|
import PlayerMarkerSet from "./PlayerMarkerSet";
|
||||||
|
|
||||||
export default class MarkerManager {
|
export default class MarkerManager {
|
||||||
|
|
||||||
@ -10,14 +11,23 @@ export default class MarkerManager {
|
|||||||
this.blueMap = blueMap;
|
this.blueMap = blueMap;
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
|
|
||||||
|
this.markerData = null;
|
||||||
|
this.liveData = null;
|
||||||
this.markerSets = [];
|
this.markerSets = [];
|
||||||
|
|
||||||
|
this.playerMarkerSet = null;
|
||||||
|
|
||||||
this.readyPromise =
|
this.readyPromise =
|
||||||
|
Promise.all([
|
||||||
this.loadMarkerData()
|
this.loadMarkerData()
|
||||||
.catch(ignore => {
|
.catch(ignore => {
|
||||||
if (this.blueMap.debugInfo) console.debug("Failed load markers:", ignore);
|
if (this.blueMap.debugInfo) console.debug("Failed load markers:", ignore);
|
||||||
})
|
}),
|
||||||
.then(this.loadMarkers);
|
this.checkLiveAPI()
|
||||||
|
.then(this.initializePlayerMarkers)
|
||||||
|
])
|
||||||
|
.then(this.loadMarkers)
|
||||||
|
.then(this.updatePlayerMarkerLoop);
|
||||||
|
|
||||||
$(document).on('bluemap-map-change', this.onBlueMapMapChange);
|
$(document).on('bluemap-map-change', this.onBlueMapMapChange);
|
||||||
}
|
}
|
||||||
@ -41,6 +51,25 @@ export default class MarkerManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkLiveAPI() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.blueMap.fileLoader.load(this.blueMap.liveApiRoot + 'players?' + cachePreventionNr(),
|
||||||
|
liveData => {
|
||||||
|
try {
|
||||||
|
this.liveData = JSON.parse(liveData);
|
||||||
|
resolve();
|
||||||
|
} catch (e){
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xhr => {},
|
||||||
|
error => {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
loadMarkers = () => {
|
loadMarkers = () => {
|
||||||
if (this.markerData && this.markerData.markerSets) {
|
if (this.markerData && this.markerData.markerSets) {
|
||||||
this.markerData.markerSets.forEach(setData => {
|
this.markerData.markerSets.forEach(setData => {
|
||||||
@ -49,12 +78,27 @@ export default class MarkerManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
initializePlayerMarkers = () => {
|
||||||
|
if (this.liveData){
|
||||||
|
this.playerMarkerSet = new PlayerMarkerSet(this.blueMap);
|
||||||
|
this.markerSets.push(this.playerMarkerSet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
update(){
|
update(){
|
||||||
this.markerSets.forEach(markerSet => {
|
this.markerSets.forEach(markerSet => {
|
||||||
markerSet.update();
|
markerSet.update();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePlayerMarkerLoop = () => {
|
||||||
|
if (this.playerMarkerSet){
|
||||||
|
this.playerMarkerSet.updateLive();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(this.updatePlayerMarkerLoop, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
addMenuElements(menu){
|
addMenuElements(menu){
|
||||||
let addedLabel = false;
|
let addedLabel = false;
|
||||||
this.markerSets.forEach(markerSet => {
|
this.markerSets.forEach(markerSet => {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Marker from "./Marker";
|
import Marker from "./Marker";
|
||||||
import {CSS2DObject} from "./CSS2DRenderer";
|
import {CSS2DObject} from "./CSS2DRenderer";
|
||||||
import {Vector3} from "three";
|
|
||||||
|
|
||||||
import POI from "../../../assets/poi.svg";
|
import POI from "../../../assets/poi.svg";
|
||||||
|
|
||||||
|
90
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarker.js
Normal file
90
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarker.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
|
import Marker from "./Marker";
|
||||||
|
import {CSS2DObject} from "./CSS2DRenderer";
|
||||||
|
|
||||||
|
export default class PlayerMarker extends Marker {
|
||||||
|
|
||||||
|
constructor(blueMap, markerSet, markerData, playerUuid, worldUuid) {
|
||||||
|
super(blueMap, markerSet, markerData);
|
||||||
|
|
||||||
|
this.online = false;
|
||||||
|
this.player = playerUuid;
|
||||||
|
this.world = worldUuid;
|
||||||
|
|
||||||
|
this.animationRunning = false;
|
||||||
|
this.lastFrame = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisible(visible){
|
||||||
|
this.visible = visible && this.online && this.world === this.blueMap.settings.maps[this.blueMap.map].world;
|
||||||
|
|
||||||
|
this.blueMap.updateFrame = true;
|
||||||
|
|
||||||
|
if (!this.renderObject){
|
||||||
|
let iconElement = $(`<div class="marker-player"><img src="assets/playerheads/${this.player}.png" onerror="this.onerror=null;this.src='assets/playerheads/steve.png';"><div class="nameplate">${this.label}</div></div>`);
|
||||||
|
iconElement.find("img").click(this.onClick);
|
||||||
|
|
||||||
|
this.renderObject = new CSS2DObject(iconElement[0]);
|
||||||
|
this.renderObject.position.copy(this.position);
|
||||||
|
this.renderObject.onBeforeRender = (renderer, scene, camera) => {
|
||||||
|
let distanceSquared = this.position.distanceToSquared(camera.position);
|
||||||
|
if (distanceSquared > 1000000) {
|
||||||
|
iconElement.addClass("distant");
|
||||||
|
} else {
|
||||||
|
iconElement.removeClass("distant");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateRenderObject(this.renderObject, scene, camera);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.visible) {
|
||||||
|
this.blueMap.hudScene.add(this.renderObject);
|
||||||
|
} else {
|
||||||
|
this.blueMap.hudScene.remove(this.renderObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePosition = () => {
|
||||||
|
if (this.renderObject && !this.renderObject.position.equals(this.position)) {
|
||||||
|
if (this.visible) {
|
||||||
|
if (!this.animationRunning) {
|
||||||
|
this.animationRunning = true;
|
||||||
|
requestAnimationFrame(this.moveAnimation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.renderObject.position.copy(this.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
moveAnimation = (time) => {
|
||||||
|
let delta = time - this.lastFrame;
|
||||||
|
if (this.lastFrame === -1){
|
||||||
|
delta = 20;
|
||||||
|
}
|
||||||
|
this.lastFrame = time;
|
||||||
|
|
||||||
|
if (this.renderObject && !this.renderObject.position.equals(this.position)) {
|
||||||
|
this.renderObject.position.x += (this.position.x - this.renderObject.position.x) * 0.01 * delta;
|
||||||
|
this.renderObject.position.y += (this.position.y - this.renderObject.position.y) * 0.01 * delta;
|
||||||
|
this.renderObject.position.z += (this.position.z - this.renderObject.position.z) * 0.01 * delta;
|
||||||
|
|
||||||
|
if (this.renderObject.position.distanceToSquared(this.position) < 0.001) {
|
||||||
|
this.renderObject.position.copy(this.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.blueMap.updateFrame = true;
|
||||||
|
|
||||||
|
requestAnimationFrame(this.moveAnimation);
|
||||||
|
} else {
|
||||||
|
this.animationRunning = false;
|
||||||
|
this.lastFrame = -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onClick = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
85
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarkerSet.js
Normal file
85
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarkerSet.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import POIMarker from "./POIMarker";
|
||||||
|
import ShapeMarker from "./ShapeMarker";
|
||||||
|
import {cachePreventionNr} from "../utils";
|
||||||
|
import PlayerMarker from "./PlayerMarker";
|
||||||
|
import {Vector3} from "three";
|
||||||
|
|
||||||
|
export default class PlayerMarkerSet {
|
||||||
|
|
||||||
|
constructor(blueMap) {
|
||||||
|
this.blueMap = blueMap;
|
||||||
|
this.id = "bluemap-live-players";
|
||||||
|
this.label = "players";
|
||||||
|
this.toggleable = true;
|
||||||
|
this.defaultHide = false;
|
||||||
|
this.marker = [];
|
||||||
|
this.markerMap = {};
|
||||||
|
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.marker.forEach(marker => {
|
||||||
|
marker.setVisible(this.visible);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLive(){
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.blueMap.fileLoader.load(this.blueMap.liveApiRoot + 'players?' + cachePreventionNr(),
|
||||||
|
liveData => {
|
||||||
|
try {
|
||||||
|
liveData = JSON.parse(liveData);
|
||||||
|
resolve(liveData);
|
||||||
|
} catch (e){
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xhr => {},
|
||||||
|
error => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}).then((liveData) => {
|
||||||
|
this.updateWith(liveData)
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error("Failed to update player-markers!", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWith(liveData){
|
||||||
|
this.marker.forEach(marker => {
|
||||||
|
marker.nowOnline = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let i = 0; i < liveData.players.length; i++){
|
||||||
|
let player = liveData.players[i];
|
||||||
|
let marker = this.markerMap[player.uuid];
|
||||||
|
|
||||||
|
if (!marker){
|
||||||
|
marker = new PlayerMarker(this.blueMap, this, {
|
||||||
|
type: "playermarker",
|
||||||
|
map: null,
|
||||||
|
position: player.position,
|
||||||
|
label: player.name,
|
||||||
|
link: null,
|
||||||
|
newTab: false
|
||||||
|
}, player.uuid, player.world);
|
||||||
|
|
||||||
|
this.markerMap[player.uuid] = marker;
|
||||||
|
this.marker.push(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
marker.nowOnline = true;
|
||||||
|
marker.position = new Vector3(player.position.x, player.position.y + 1.5, player.position.z);
|
||||||
|
marker.updatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.marker.forEach(marker => {
|
||||||
|
if (marker.nowOnline !== marker.online){
|
||||||
|
marker.online = marker.nowOnline;
|
||||||
|
marker.setVisible(this.visible);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -76,7 +76,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bluemap-container .marker-poi {
|
.bluemap-container .marker-poi, .bluemap-container .marker-player {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
@ -87,3 +87,38 @@
|
|||||||
filter: drop-shadow(1px 1px 3px #0008);
|
filter: drop-shadow(1px 1px 3px #0008);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bluemap-container .marker-player {
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameplate {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -110%);
|
||||||
|
background: rgba(50, 50, 50, 0.75);
|
||||||
|
padding: 0.2em 0.3em;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.distant {
|
||||||
|
img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameplate {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user