mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-12-28 11:59:21 +01:00
Merge branch 'base'
This commit is contained in:
commit
465a38080a
@ -0,0 +1,138 @@
|
||||
/*
|
||||
* 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.bukkit;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.GameMode;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
|
||||
public class BukkitPlayer implements Player {
|
||||
|
||||
private static final Map<GameMode, Gamemode> GAMEMODE_MAP = new EnumMap<>(GameMode.class);
|
||||
static {
|
||||
GAMEMODE_MAP.put(GameMode.ADVENTURE, Gamemode.ADVENTURE);
|
||||
GAMEMODE_MAP.put(GameMode.SURVIVAL, Gamemode.SURVIVAL);
|
||||
GAMEMODE_MAP.put(GameMode.CREATIVE, Gamemode.CREATIVE);
|
||||
GAMEMODE_MAP.put(GameMode.SPECTATOR, Gamemode.SPECTATOR);
|
||||
}
|
||||
|
||||
private UUID uuid;
|
||||
private Text name;
|
||||
private UUID world;
|
||||
private Vector3d position;
|
||||
private boolean online;
|
||||
private boolean sneaking;
|
||||
private boolean invisible;
|
||||
private Gamemode gamemode;
|
||||
|
||||
private WeakReference<org.bukkit.entity.Player> delegate;
|
||||
|
||||
public BukkitPlayer(org.bukkit.entity.Player delegate) {
|
||||
this.uuid = delegate.getUniqueId();
|
||||
this.delegate = new WeakReference<>(delegate);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUuid() {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3d getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return this.online;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking() {
|
||||
return this.sneaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvisible() {
|
||||
return this.invisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gamemode getGamemode() {
|
||||
return this.gamemode;
|
||||
}
|
||||
|
||||
/**
|
||||
* API access, only call on server thread!
|
||||
*/
|
||||
public void update() {
|
||||
org.bukkit.entity.Player player = delegate.get();
|
||||
if (player == null) {
|
||||
player = Bukkit.getPlayer(uuid);
|
||||
if (player == null) {
|
||||
this.online = false;
|
||||
return;
|
||||
}
|
||||
|
||||
delegate = new WeakReference<>(player);
|
||||
}
|
||||
|
||||
this.gamemode = GAMEMODE_MAP.get(player.getGameMode());
|
||||
|
||||
this.invisible = player.hasPotionEffect(PotionEffectType.INVISIBILITY);
|
||||
|
||||
this.name = Text.of(player.getName());
|
||||
this.online = player.isOnline();
|
||||
|
||||
Location location = player.getLocation();
|
||||
this.position = new Vector3d(location.getX(), location.getY(), location.getZ());
|
||||
this.sneaking = player.isSneaking();
|
||||
this.world = player.getWorld().getUID();
|
||||
}
|
||||
|
||||
}
|
@ -27,8 +27,15 @@
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
@ -37,14 +44,20 @@
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.defaults.BukkitCommand;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
|
||||
public class BukkitPlugin extends JavaPlugin implements ServerInterface {
|
||||
public class BukkitPlugin extends JavaPlugin implements ServerInterface, Listener {
|
||||
|
||||
private static BukkitPlugin instance;
|
||||
|
||||
@ -52,8 +65,15 @@ public class BukkitPlugin extends JavaPlugin implements ServerInterface {
|
||||
private EventForwarder eventForwarder;
|
||||
private BukkitCommands commands;
|
||||
|
||||
private int playerUpdateIndex = 0;
|
||||
private Map<UUID, Player> onlinePlayerMap;
|
||||
private List<BukkitPlayer> onlinePlayerList;
|
||||
|
||||
public BukkitPlugin() {
|
||||
Logger.global = new JavaLogger(getLogger());
|
||||
|
||||
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
this.eventForwarder = new EventForwarder();
|
||||
this.bluemap = new Plugin("bukkit", this);
|
||||
@ -73,6 +93,7 @@ public void onEnable() {
|
||||
}
|
||||
|
||||
//register events
|
||||
getServer().getPluginManager().registerEvents(this, this);
|
||||
getServer().getPluginManager().registerEvents(eventForwarder, this);
|
||||
|
||||
//register commands
|
||||
@ -92,6 +113,9 @@ public void onEnable() {
|
||||
//tab completions
|
||||
getServer().getPluginManager().registerEvents(commands, this);
|
||||
|
||||
//start updating players
|
||||
getServer().getScheduler().runTaskTimer(this, this::updateSomePlayers, 1, 1);
|
||||
|
||||
//load bluemap
|
||||
getServer().getScheduler().runTaskAsynchronously(this, () -> {
|
||||
try {
|
||||
@ -107,6 +131,7 @@ public void onEnable() {
|
||||
@Override
|
||||
public void onDisable() {
|
||||
Logger.global.logInfo("Stopping...");
|
||||
getServer().getScheduler().cancelTasks(this);
|
||||
bluemap.unload();
|
||||
Logger.global.logInfo("Saved and stopped!");
|
||||
}
|
||||
@ -179,4 +204,51 @@ public static BukkitPlugin getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerJoin(PlayerJoinEvent evt) {
|
||||
BukkitPlayer player = new BukkitPlayer(evt.getPlayer());
|
||||
onlinePlayerMap.put(evt.getPlayer().getUniqueId(), player);
|
||||
onlinePlayerList.add(player);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlayerLeave(PlayerQuitEvent evt) {
|
||||
UUID playerUUID = evt.getPlayer().getUniqueId();
|
||||
onlinePlayerMap.remove(playerUUID);
|
||||
synchronized (onlinePlayerList) {
|
||||
onlinePlayerList.removeIf(p -> p.getUuid().equals(playerUUID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getOnlinePlayers() {
|
||||
return onlinePlayerMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Player> getPlayer(UUID uuid) {
|
||||
return Optional.ofNullable(onlinePlayerMap.get(uuid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Only update some of the online players each tick to minimize performance impact on the server-thread.
|
||||
* Only call this method on the server-thread.
|
||||
*/
|
||||
private void updateSomePlayers() {
|
||||
int onlinePlayerCount = onlinePlayerList.size();
|
||||
if (onlinePlayerCount == 0) return;
|
||||
|
||||
int playersToBeUpdated = onlinePlayerCount / 20; //with 20 tps, each player is updated once a second
|
||||
if (playersToBeUpdated == 0) playersToBeUpdated = 1;
|
||||
|
||||
for (int i = 0; i < playersToBeUpdated; i++) {
|
||||
playerUpdateIndex++;
|
||||
if (playerUpdateIndex >= 20 && playerUpdateIndex >= onlinePlayerCount) playerUpdateIndex = 0;
|
||||
|
||||
if (playerUpdateIndex < onlinePlayerCount) {
|
||||
onlinePlayerList.get(i).update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -42,6 +42,9 @@
|
||||
import org.bukkit.event.block.BlockGrowEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.block.BlockSpreadEvent;
|
||||
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.world.ChunkPopulateEvent;
|
||||
import org.bukkit.event.world.WorldSaveEvent;
|
||||
|
||||
@ -49,6 +52,7 @@
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
|
||||
public class EventForwarder implements Listener {
|
||||
|
||||
@ -68,7 +72,7 @@ public synchronized void removeAllListeners() {
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public synchronized void onWorldSaveToDisk(WorldSaveEvent evt) {
|
||||
listeners.forEach(l -> l.onWorldSaveToDisk(evt.getWorld().getUID()));
|
||||
for (ServerEventListener listener : listeners) listener.onWorldSaveToDisk(evt.getWorld().getUID());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
@ -119,7 +123,7 @@ public void onBlockChange(BlockFertilizeEvent evt) {
|
||||
private synchronized void onBlockChange(Location loc) {
|
||||
UUID world = loc.getWorld().getUID();
|
||||
Vector3i pos = new Vector3i(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
||||
listeners.forEach(l -> l.onBlockChange(world, pos));
|
||||
for (ServerEventListener listener : listeners) listener.onBlockChange(world, pos);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
@ -127,7 +131,23 @@ public synchronized void onChunkFinishedGeneration(ChunkPopulateEvent evt) {
|
||||
Chunk chunk = evt.getChunk();
|
||||
UUID world = chunk.getWorld().getUID();
|
||||
Vector2i chunkPos = new Vector2i(chunk.getX(), chunk.getZ());
|
||||
listeners.forEach(l -> l.onChunkFinishedGeneration(world, chunkPos));
|
||||
for (ServerEventListener listener : listeners) listener.onChunkFinishedGeneration(world, chunkPos);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public synchronized void onPlayerJoin(PlayerJoinEvent evt) {
|
||||
for (ServerEventListener listener : listeners) listener.onPlayerJoin(evt.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public synchronized void onPlayerLeave(PlayerQuitEvent evt) {
|
||||
for (ServerEventListener listener : listeners) listener.onPlayerJoin(evt.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public synchronized void onPlayerChat(AsyncPlayerChatEvent evt) {
|
||||
String message = String.format(evt.getFormat(), evt.getPlayer().getDisplayName(), evt.getMessage());
|
||||
for (ServerEventListener listener : listeners) listener.onChatMessage(Text.of(message));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,3 +9,9 @@ webserver {
|
||||
port: 8100
|
||||
maxConnectionCount: 100
|
||||
}
|
||||
liveUpdates {
|
||||
enabled: true
|
||||
hiddenGameModes: []
|
||||
hideInvisible: true
|
||||
hideSneaking: false
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ webroot: "bluemap/web"
|
||||
#webdata: "path/to/data/folder"
|
||||
|
||||
# If the web-application should use cookies to save the configurations of a user.
|
||||
# Default is true
|
||||
useCookies: true
|
||||
|
||||
webserver {
|
||||
@ -165,3 +166,23 @@ maps: [
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
liveUpdates {
|
||||
# If the server should send live-updates and player-positions.
|
||||
# Default is true
|
||||
enabled: true
|
||||
|
||||
# A list of gamemodes that will prevent a player from appearing on the map.
|
||||
# Possible values are: survival, creative, spectator, adventure
|
||||
hiddenGameModes: [
|
||||
"spectator"
|
||||
]
|
||||
|
||||
# If this is true, players that have an invisibility (potion-)effect will be hidden on the map.
|
||||
# Default is true
|
||||
hideInvisible: true
|
||||
|
||||
# If this is true, players that are sneaking will be hidden on the map.
|
||||
# Default is false
|
||||
hideSneaking: false
|
||||
}
|
||||
|
@ -57,6 +57,7 @@
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.google.common.base.Preconditions;
|
||||
|
||||
import de.bluecolored.bluemap.common.BlueMapWebServer;
|
||||
import de.bluecolored.bluemap.common.MapType;
|
||||
import de.bluecolored.bluemap.common.RenderManager;
|
||||
import de.bluecolored.bluemap.common.RenderTask;
|
||||
@ -72,7 +73,6 @@
|
||||
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.web.BlueMapWebServer;
|
||||
import de.bluecolored.bluemap.core.web.WebFilesManager;
|
||||
import de.bluecolored.bluemap.core.web.WebSettings;
|
||||
import de.bluecolored.bluemap.core.world.SlicedWorld;
|
||||
|
@ -22,23 +22,41 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.web;
|
||||
package de.bluecolored.bluemap.common;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.core.config.LiveAPISettings;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.web.FileRequestHandler;
|
||||
import de.bluecolored.bluemap.core.web.WebFilesManager;
|
||||
import de.bluecolored.bluemap.core.web.WebServerConfig;
|
||||
import de.bluecolored.bluemap.core.webserver.WebServer;
|
||||
|
||||
public class BlueMapWebServer extends WebServer {
|
||||
|
||||
private WebFilesManager webFilesManager;
|
||||
|
||||
|
||||
public BlueMapWebServer(WebServerConfig config) {
|
||||
super(
|
||||
config.getWebserverPort(),
|
||||
config.getWebserverMaxConnections(),
|
||||
config.getWebserverBindAdress(),
|
||||
new BlueMapWebRequestHandler(config.getWebRoot())
|
||||
new FileRequestHandler(config.getWebRoot(), "BlueMap/Webserver")
|
||||
);
|
||||
|
||||
this.webFilesManager = new WebFilesManager(config.getWebRoot());
|
||||
}
|
||||
|
||||
public BlueMapWebServer(WebServerConfig config, LiveAPISettings liveSettings, ServerInterface server) {
|
||||
super(
|
||||
config.getWebserverPort(),
|
||||
config.getWebserverMaxConnections(),
|
||||
config.getWebserverBindAdress(),
|
||||
new LiveAPIRequestHandler(server, liveSettings, new FileRequestHandler(config.getWebRoot(), "BlueMap/Webserver"))
|
||||
);
|
||||
|
||||
this.webFilesManager = new WebFilesManager(config.getWebRoot());
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.live;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.core.config.LiveAPISettings;
|
||||
import de.bluecolored.bluemap.core.webserver.HttpRequest;
|
||||
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
|
||||
import de.bluecolored.bluemap.core.webserver.HttpResponse;
|
||||
import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
|
||||
|
||||
public class LiveAPIRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private HttpRequestHandler notFoundHandler;
|
||||
private Map<String, HttpRequestHandler> liveAPIRequests;
|
||||
private ServerInterface server;
|
||||
|
||||
private LiveAPISettings config;
|
||||
|
||||
public LiveAPIRequestHandler(ServerInterface server, LiveAPISettings config, HttpRequestHandler notFoundHandler) {
|
||||
this.server = server;
|
||||
this.notFoundHandler = notFoundHandler;
|
||||
|
||||
this.liveAPIRequests = new HashMap<>();
|
||||
|
||||
this.liveAPIRequests.put("live", this::handleLivePingRequest);
|
||||
this.liveAPIRequests.put("live/players", this::handlePlayersRequest);
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse handle(HttpRequest request) {
|
||||
if (!config.isLiveUpdatesEnabled()) return this.notFoundHandler.handle(request);
|
||||
|
||||
HttpRequestHandler handler = liveAPIRequests.get(request.getPath());
|
||||
if (handler != null) return handler.handle(request);
|
||||
|
||||
return this.notFoundHandler.handle(request);
|
||||
}
|
||||
|
||||
public HttpResponse handleLivePingRequest(HttpRequest request) {
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.setData("{\"status\":\"OK\"}");
|
||||
return response;
|
||||
}
|
||||
|
||||
public HttpResponse handlePlayersRequest(HttpRequest request) {
|
||||
if (!request.getMethod().equalsIgnoreCase("GET")) return new HttpResponse(HttpStatusCode.BAD_REQUEST);
|
||||
|
||||
try (
|
||||
StringWriter data = new StringWriter();
|
||||
JsonWriter json = new JsonWriter(data);
|
||||
){
|
||||
|
||||
json.beginObject();
|
||||
json.name("players").beginArray();
|
||||
for (Player player : server.getOnlinePlayers()) {
|
||||
|
||||
if (config.isHideInvisible() && player.isInvisible()) continue;
|
||||
if (config.isHideSneaking() && player.isSneaking()) continue;
|
||||
if (config.getHiddenGameModes().contains(player.getGamemode().getId())) continue;
|
||||
|
||||
json.beginObject();
|
||||
json.name("uuid").value(player.getUuid().toString());
|
||||
json.name("name").value(player.getName().toPlainString());
|
||||
json.name("world").value(player.getWorld().toString());
|
||||
json.name("position").beginObject();
|
||||
json.name("x").value(player.getPosition().getX());
|
||||
json.name("y").value(player.getPosition().getY());
|
||||
json.name("z").value(player.getPosition().getZ());
|
||||
json.endObject();
|
||||
json.endObject();
|
||||
}
|
||||
json.endArray();
|
||||
json.endObject();
|
||||
|
||||
HttpResponse response = new HttpResponse(HttpStatusCode.OK);
|
||||
response.setData(data.toString());
|
||||
|
||||
return response;
|
||||
|
||||
} catch (IOException ex) {
|
||||
return new HttpResponse(HttpStatusCode.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -46,10 +46,12 @@
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
|
||||
import de.bluecolored.bluemap.common.BlueMapWebServer;
|
||||
import de.bluecolored.bluemap.common.MapType;
|
||||
import de.bluecolored.bluemap.common.RenderManager;
|
||||
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
||||
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.MainConfig;
|
||||
import de.bluecolored.bluemap.core.config.MainConfig.MapConfig;
|
||||
@ -62,7 +64,6 @@
|
||||
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
||||
import de.bluecolored.bluemap.core.web.BlueMapWebServer;
|
||||
import de.bluecolored.bluemap.core.web.WebFilesManager;
|
||||
import de.bluecolored.bluemap.core.web.WebSettings;
|
||||
import de.bluecolored.bluemap.core.world.SlicedWorld;
|
||||
@ -86,6 +87,7 @@ public class Plugin {
|
||||
private Map<String, MapType> maps;
|
||||
|
||||
private MapUpdateHandler updateHandler;
|
||||
private PlayerSkinUpdater skinUpdater;
|
||||
|
||||
private RenderManager renderManager;
|
||||
private BlueMapWebServer webServer;
|
||||
@ -269,6 +271,12 @@ public synchronized void load() throws IOException, ParseResourceException {
|
||||
this.updateHandler = new MapUpdateHandler(this);
|
||||
serverInterface.registerListener(updateHandler);
|
||||
|
||||
//start skin updater
|
||||
if (config.isLiveUpdatesEnabled()) {
|
||||
this.skinUpdater = new PlayerSkinUpdater(config.getWebRoot().resolve("assets").resolve("playerheads").toFile());
|
||||
serverInterface.registerListener(skinUpdater);
|
||||
}
|
||||
|
||||
//create/update webfiles
|
||||
WebFilesManager webFilesManager = new WebFilesManager(config.getWebRoot());
|
||||
if (webFilesManager.needsUpdate()) {
|
||||
@ -293,7 +301,7 @@ public synchronized void load() throws IOException, ParseResourceException {
|
||||
|
||||
//start webserver
|
||||
if (config.isWebserverEnabled()) {
|
||||
webServer = new BlueMapWebServer(config);
|
||||
webServer = new BlueMapWebServer(config, config, serverInterface);
|
||||
webServer.updateWebfiles();
|
||||
webServer.start();
|
||||
}
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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.serverinterface;
|
||||
|
||||
public enum Gamemode {
|
||||
|
||||
SURVIVAL ("survival"),
|
||||
CREATIVE ("creative"),
|
||||
ADVENTURE ("adventure"),
|
||||
SPECTATOR ("spectator");
|
||||
|
||||
private final String id;
|
||||
|
||||
Gamemode(String id){
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public static Gamemode getById(String id) {
|
||||
if (id == null) throw new NullPointerException("id cannot be null");
|
||||
|
||||
for (Gamemode gamemode : values()) {
|
||||
if (gamemode.id.equals(id)) return gamemode;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("There is no Gamemode with id: '" + id + "'");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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.serverinterface;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
|
||||
public interface Player {
|
||||
|
||||
public UUID getUuid();
|
||||
|
||||
public Text getName();
|
||||
|
||||
public UUID getWorld();
|
||||
|
||||
public Vector3d getPosition();
|
||||
|
||||
public boolean isOnline();
|
||||
|
||||
/**
|
||||
* Return <code>true</code> if the player is sneaking.
|
||||
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
|
||||
* @return
|
||||
*/
|
||||
public boolean isSneaking();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the player has an invisibillity effect
|
||||
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
|
||||
*/
|
||||
public boolean isInvisible();
|
||||
|
||||
/**
|
||||
* Returns the {@link Gamemode} this player is in
|
||||
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
|
||||
*/
|
||||
public Gamemode getGamemode();
|
||||
|
||||
}
|
@ -29,12 +29,20 @@
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
|
||||
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) {};
|
||||
|
||||
default void onPlayerJoin(UUID playerUuid) {};
|
||||
|
||||
default void onPlayerLeave(UUID playerUuid) {};
|
||||
|
||||
default void onChatMessage(Text message) {};
|
||||
|
||||
}
|
||||
|
@ -26,6 +26,8 @@
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public interface ServerInterface {
|
||||
@ -73,5 +75,16 @@ default boolean isMetricsEnabled(boolean configValue) {
|
||||
return configValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a collection of the states of players that are currently online
|
||||
*/
|
||||
Collection<Player> getOnlinePlayers();
|
||||
|
||||
/**
|
||||
* Returns the state of the player with that UUID if present<br>
|
||||
* this method is only guaranteed to return a {@link PlayerState} if the player is currently online.
|
||||
*/
|
||||
Optional<Player> getPlayer(UUID uuid);
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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.core.config;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface LiveAPISettings {
|
||||
|
||||
boolean isLiveUpdatesEnabled();
|
||||
|
||||
Collection<String> getHiddenGameModes();
|
||||
|
||||
boolean isHideInvisible();
|
||||
|
||||
boolean isHideSneaking();
|
||||
|
||||
}
|
@ -31,6 +31,8 @@
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -43,7 +45,7 @@
|
||||
import de.bluecolored.bluemap.core.web.WebServerConfig;
|
||||
import ninja.leaping.configurate.ConfigurationNode;
|
||||
|
||||
public class MainConfig implements WebServerConfig {
|
||||
public class MainConfig implements WebServerConfig, LiveAPISettings {
|
||||
private static final Pattern VALID_ID_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
|
||||
|
||||
private boolean downloadAccepted = false;
|
||||
@ -64,6 +66,11 @@ public class MainConfig implements WebServerConfig {
|
||||
|
||||
private List<MapConfig> mapConfigs = new ArrayList<>();
|
||||
|
||||
private boolean liveUpdatesEnabled = false;
|
||||
private Collection<String> hiddenGameModes = Collections.emptyList();
|
||||
private boolean hideInvisible = false;
|
||||
private boolean hideSneaking = false;
|
||||
|
||||
public MainConfig(ConfigurationNode node) throws OutdatedConfigException, IOException {
|
||||
checkOutdated(node);
|
||||
|
||||
@ -101,6 +108,19 @@ public MainConfig(ConfigurationNode node) throws OutdatedConfigException, IOExce
|
||||
|
||||
//maps
|
||||
loadMapConfigs(node.getNode("maps"));
|
||||
|
||||
//live-updates
|
||||
ConfigurationNode liveNode = node.getNode("liveUpdates");
|
||||
liveUpdatesEnabled = liveNode.getNode("enabled").getBoolean(true);
|
||||
|
||||
hiddenGameModes = new ArrayList<>();
|
||||
for (ConfigurationNode gameModeNode : liveNode.getNode("hiddenGameModes").getChildrenList()) {
|
||||
hiddenGameModes.add(gameModeNode.getString());
|
||||
}
|
||||
hiddenGameModes = Collections.unmodifiableCollection(hiddenGameModes);
|
||||
|
||||
hideInvisible = liveNode.getNode("hideInvisible").getBoolean(true);
|
||||
hideSneaking = liveNode.getNode("hideSneaking").getBoolean(false);
|
||||
}
|
||||
|
||||
private void loadWebConfig(ConfigurationNode node) throws IOException {
|
||||
@ -196,6 +216,26 @@ public List<MapConfig> getMapConfigs(){
|
||||
return mapConfigs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLiveUpdatesEnabled() {
|
||||
return this.liveUpdatesEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<String> getHiddenGameModes() {
|
||||
return this.hiddenGameModes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHideInvisible() {
|
||||
return this.hideInvisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isHideSneaking() {
|
||||
return this.hideSneaking;
|
||||
}
|
||||
|
||||
public class MapConfig implements RenderSettings {
|
||||
|
||||
private String id;
|
||||
|
@ -198,9 +198,7 @@ public synchronized Texture loadTexture(FileAccess fileAccess, String path) thro
|
||||
|
||||
//crop off animation frames
|
||||
if (image.getHeight() > image.getWidth()){
|
||||
BufferedImage cropped = new BufferedImage(image.getWidth(), image.getWidth(), image.getType());
|
||||
image.copyData(cropped.getRaster());
|
||||
image = cropped;
|
||||
image = image.getSubimage(0, 0, image.getWidth(), image.getWidth());
|
||||
}
|
||||
|
||||
//check halfTransparency
|
||||
|
@ -33,9 +33,7 @@
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -50,16 +48,18 @@
|
||||
import de.bluecolored.bluemap.core.webserver.HttpResponse;
|
||||
import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
|
||||
|
||||
public class BlueMapWebRequestHandler implements HttpRequestHandler {
|
||||
public class FileRequestHandler implements HttpRequestHandler {
|
||||
|
||||
private static final long DEFLATE_MIN_SIZE = 10L * 1024L;
|
||||
private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L;
|
||||
private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L;
|
||||
|
||||
private Path webRoot;
|
||||
private String serverName;
|
||||
|
||||
public BlueMapWebRequestHandler(Path webRoot) {
|
||||
public FileRequestHandler(Path webRoot, String serverName) {
|
||||
this.webRoot = webRoot;
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -70,11 +70,11 @@ public HttpResponse handle(HttpRequest request) {
|
||||
) return new HttpResponse(HttpStatusCode.NOT_IMPLEMENTED);
|
||||
|
||||
HttpResponse response = generateResponse(request);
|
||||
response.addHeader("Server", "BlueMap/WebServer");
|
||||
response.addHeader("Server", this.serverName);
|
||||
|
||||
HttpStatusCode status = response.getStatusCode();
|
||||
if (status.getCode() >= 400){
|
||||
response.setData(status.getCode() + " - " + status.getMessage() + "\nBlueMap/Webserver");
|
||||
response.setData(status.getCode() + " - " + status.getMessage() + "\n" + this.serverName);
|
||||
}
|
||||
|
||||
return response;
|
||||
@ -82,23 +82,7 @@ public HttpResponse handle(HttpRequest request) {
|
||||
|
||||
@SuppressWarnings ("resource")
|
||||
private HttpResponse generateResponse(HttpRequest request) {
|
||||
String adress = request.getPath();
|
||||
if (adress.isEmpty()) adress = "/";
|
||||
String[] adressParts = adress.split("\\?", 2);
|
||||
String path = adressParts[0];
|
||||
String getParamString = adressParts.length > 1 ? adressParts[1] : "";
|
||||
|
||||
Map<String, String> getParams = new HashMap<>();
|
||||
for (String getParam : getParamString.split("&")){
|
||||
if (getParam.isEmpty()) continue;
|
||||
String[] kv = getParam.split("=", 2);
|
||||
String key = kv[0];
|
||||
String value = kv.length > 1 ? kv[1] : "";
|
||||
getParams.put(key, value);
|
||||
}
|
||||
|
||||
if (path.startsWith("/")) path = path.substring(1);
|
||||
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
|
||||
String path = request.getPath();
|
||||
|
||||
Path filePath = webRoot;
|
||||
try {
|
@ -136,6 +136,7 @@ public void setFrom(TileRenderer tileRenderer, String mapId) {
|
||||
public void setFrom(World world, String mapId) {
|
||||
set(world.getSpawnPoint().getX(), "maps", mapId, "startPos", "x");
|
||||
set(world.getSpawnPoint().getZ(), "maps", mapId, "startPos", "z");
|
||||
set(world.getUUID().toString(), "maps", mapId, "world");
|
||||
}
|
||||
|
||||
public void setFrom(MapConfig mapConfig, String mapId) {
|
||||
|
@ -50,15 +50,18 @@ public class HttpRequest {
|
||||
private static final Pattern REQUEST_PATTERN = Pattern.compile("^(\\w+) (\\S+) (.+)$");
|
||||
|
||||
private String method;
|
||||
private String path;
|
||||
private String adress;
|
||||
private String version;
|
||||
private Map<String, Set<String>> header;
|
||||
private Map<String, Set<String>> headerLC;
|
||||
private byte[] data;
|
||||
|
||||
public HttpRequest(String method, String path, String version, Map<String, Set<String>> header) {
|
||||
private String path = null;
|
||||
private Map<String, String> getParams = null;
|
||||
|
||||
public HttpRequest(String method, String adress, String version, Map<String, Set<String>> header) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.adress = adress;
|
||||
this.version = version;
|
||||
this.header = header;
|
||||
this.headerLC = new HashMap<>();
|
||||
@ -79,8 +82,8 @@ public String getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public String getPath(){
|
||||
return path;
|
||||
public String getAdress(){
|
||||
return adress;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
@ -92,7 +95,7 @@ public Map<String, Set<String>> getHeader() {
|
||||
}
|
||||
|
||||
public Map<String, Set<String>> getLowercaseHeader() {
|
||||
return header;
|
||||
return headerLC;
|
||||
}
|
||||
|
||||
public Set<String> getHeader(String key){
|
||||
@ -107,6 +110,40 @@ public Set<String> getLowercaseHeader(String key){
|
||||
return headerValues;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
if (path == null) parseAdress();
|
||||
return path;
|
||||
}
|
||||
|
||||
public Map<String, String> getGETParams() {
|
||||
if (getParams == null) parseAdress();
|
||||
return Collections.unmodifiableMap(getParams);
|
||||
}
|
||||
|
||||
private void parseAdress() {
|
||||
String adress = this.adress;
|
||||
if (adress.isEmpty()) adress = "/";
|
||||
String[] adressParts = adress.split("\\?", 2);
|
||||
String path = adressParts[0];
|
||||
String getParamString = adressParts.length > 1 ? adressParts[1] : "";
|
||||
|
||||
Map<String, String> getParams = new HashMap<>();
|
||||
for (String getParam : getParamString.split("&")){
|
||||
if (getParam.isEmpty()) continue;
|
||||
String[] kv = getParam.split("=", 2);
|
||||
String key = kv[0];
|
||||
String value = kv.length > 1 ? kv[1] : "";
|
||||
getParams.put(key, value);
|
||||
}
|
||||
|
||||
//normalize path
|
||||
if (path.startsWith("/")) path = path.substring(1);
|
||||
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
|
||||
|
||||
this.path = path;
|
||||
this.getParams = getParams;
|
||||
}
|
||||
|
||||
public InputStream getData(){
|
||||
return new ByteArrayInputStream(data);
|
||||
}
|
||||
|
@ -24,6 +24,7 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.core.webserver;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface HttpRequestHandler {
|
||||
|
||||
HttpResponse handle(HttpRequest request);
|
||||
|
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";
|
||||
|
||||
export default class BlueMap {
|
||||
constructor(element, dataRoot) {
|
||||
constructor(element, dataRoot = "data/", liveApiRoot = "live/") {
|
||||
this.element = $('<div class="bluemap-container"></div>').appendTo(element)[0];
|
||||
this.dataRoot = dataRoot;
|
||||
this.liveApiRoot = liveApiRoot;
|
||||
this.locationHash = '';
|
||||
this.cacheSuffix = '';
|
||||
|
||||
|
@ -3,6 +3,7 @@ import $ from "jquery";
|
||||
import ToggleButton from "../ui/ToggleButton";
|
||||
import Label from "../ui/Label";
|
||||
import {cachePreventionNr} from "../utils";
|
||||
import PlayerMarkerSet from "./PlayerMarkerSet";
|
||||
|
||||
export default class MarkerManager {
|
||||
|
||||
@ -10,14 +11,23 @@ export default class MarkerManager {
|
||||
this.blueMap = blueMap;
|
||||
this.ui = ui;
|
||||
|
||||
this.markerData = null;
|
||||
this.liveData = null;
|
||||
this.markerSets = [];
|
||||
|
||||
this.playerMarkerSet = null;
|
||||
|
||||
this.readyPromise =
|
||||
this.loadMarkerData()
|
||||
.catch(ignore => {
|
||||
if (this.blueMap.debugInfo) console.debug("Failed load markers:", ignore);
|
||||
})
|
||||
.then(this.loadMarkers);
|
||||
Promise.all([
|
||||
this.loadMarkerData()
|
||||
.catch(ignore => {
|
||||
if (this.blueMap.debugInfo) console.debug("Failed load markers:", ignore);
|
||||
}),
|
||||
this.checkLiveAPI()
|
||||
.then(this.initializePlayerMarkers)
|
||||
])
|
||||
.then(this.loadMarkers)
|
||||
.then(this.updatePlayerMarkerLoop);
|
||||
|
||||
$(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 = () => {
|
||||
if (this.markerData && this.markerData.markerSets) {
|
||||
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(){
|
||||
this.markerSets.forEach(markerSet => {
|
||||
markerSet.update();
|
||||
});
|
||||
}
|
||||
|
||||
updatePlayerMarkerLoop = () => {
|
||||
if (this.playerMarkerSet){
|
||||
this.playerMarkerSet.updateLive();
|
||||
}
|
||||
|
||||
setTimeout(this.updatePlayerMarkerLoop, 2000);
|
||||
};
|
||||
|
||||
addMenuElements(menu){
|
||||
let addedLabel = false;
|
||||
this.markerSets.forEach(markerSet => {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import $ from 'jquery';
|
||||
import Marker from "./Marker";
|
||||
import {CSS2DObject} from "./CSS2DRenderer";
|
||||
import {Vector3} from "three";
|
||||
|
||||
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;
|
||||
|
||||
> * {
|
||||
@ -86,4 +86,39 @@
|
||||
> img {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -35,10 +35,14 @@
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
|
||||
import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback;
|
||||
import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback;
|
||||
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
|
||||
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
|
||||
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.util.ActionResult;
|
||||
import net.minecraft.util.Hand;
|
||||
@ -60,13 +64,16 @@ public FabricEventForwarder(FabricMod mod) {
|
||||
ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize);
|
||||
AttackBlockCallback.EVENT.register(this::onBlockAttack);
|
||||
UseBlockCallback.EVENT.register(this::onBlockUse);
|
||||
|
||||
PlayerJoinCallback.EVENT.register(this::onPlayerJoin);
|
||||
PlayerLeaveCallback.EVENT.register(this::onPlayerLeave);
|
||||
}
|
||||
|
||||
public void addEventListener(ServerEventListener listener) {
|
||||
public synchronized void addEventListener(ServerEventListener listener) {
|
||||
this.eventListeners.add(listener);
|
||||
}
|
||||
|
||||
public void removeAllListeners() {
|
||||
public synchronized void removeAllListeners() {
|
||||
this.eventListeners.clear();
|
||||
}
|
||||
|
||||
@ -87,33 +94,47 @@ public ActionResult onBlockAttack(PlayerEntity player, World world, Hand hand, B
|
||||
return ActionResult.PASS;
|
||||
}
|
||||
|
||||
public void onBlockChange(ServerWorld world, BlockPos blockPos) {
|
||||
public synchronized void onBlockChange(ServerWorld world, BlockPos blockPos) {
|
||||
Vector3i position = new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||
|
||||
try {
|
||||
UUID uuid = mod.getUUIDForWorld(world);
|
||||
eventListeners.forEach(e -> e.onBlockChange(uuid, position));
|
||||
for (ServerEventListener listener : eventListeners) listener.onBlockChange(uuid, position);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to get UUID for world: " + world, e);
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onWorldSave(ServerWorld world) {
|
||||
public synchronized void onWorldSave(ServerWorld world) {
|
||||
try {
|
||||
UUID uuid = mod.getUUIDForWorld(world);
|
||||
eventListeners.forEach(e -> e.onWorldSaveToDisk(uuid));
|
||||
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to get UUID for world: " + world, e);
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void onChunkFinalize(ServerWorld world, Vector2i chunkPos) {
|
||||
public synchronized void onChunkFinalize(ServerWorld world, Vector2i chunkPos) {
|
||||
try {
|
||||
UUID uuid = mod.getUUIDForWorld(world);
|
||||
eventListeners.forEach(e -> e.onChunkFinishedGeneration(uuid, chunkPos));
|
||||
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to get UUID for world: " + world, e);
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) {
|
||||
if (this.mod.getServer() != server) return;
|
||||
|
||||
UUID uuid = player.getUuid();
|
||||
for (ServerEventListener listener : eventListeners) listener.onPlayerJoin(uuid);
|
||||
}
|
||||
|
||||
public synchronized void onPlayerLeave(MinecraftServer server, ServerPlayerEntity player) {
|
||||
if (this.mod.getServer() != server) return;
|
||||
|
||||
UUID uuid = player.getUuid();
|
||||
for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,12 @@
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@ -39,32 +44,45 @@
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.plugin.commands.Commands;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback;
|
||||
import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback;
|
||||
import net.fabricmc.api.ModInitializer;
|
||||
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.server.world.ServerWorld;
|
||||
import net.minecraft.world.dimension.DimensionType;
|
||||
|
||||
public class FabricMod implements ModInitializer, ServerInterface {
|
||||
|
||||
private Plugin pluginInstance = null;
|
||||
private MinecraftServer serverInstance = null;
|
||||
|
||||
private Map<File, UUID> worldUuids;
|
||||
private Map<File, UUID> worldUUIDs;
|
||||
private FabricEventForwarder eventForwarder;
|
||||
|
||||
private LoadingCache<ServerWorld, UUID> worldUuidCache;
|
||||
|
||||
private int playerUpdateIndex = 0;
|
||||
private Map<UUID, Player> onlinePlayerMap;
|
||||
private List<FabricPlayer> onlinePlayerList;
|
||||
|
||||
public FabricMod() {
|
||||
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
|
||||
|
||||
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
pluginInstance = new Plugin("fabric", this);
|
||||
|
||||
this.worldUuids = new ConcurrentHashMap<>();
|
||||
this.worldUUIDs = new ConcurrentHashMap<>();
|
||||
this.eventForwarder = new FabricEventForwarder(this);
|
||||
this.worldUuidCache = CacheBuilder.newBuilder()
|
||||
.weakKeys()
|
||||
@ -86,12 +104,14 @@ public void onInitialize() {
|
||||
});
|
||||
|
||||
ServerLifecycleEvents.SERVER_STARTED.register((MinecraftServer server) -> {
|
||||
this.serverInstance = server;
|
||||
|
||||
new Thread(()->{
|
||||
Logger.global.logInfo("Loading BlueMap...");
|
||||
Logger.global.logInfo("Loading BlueMap...");
|
||||
|
||||
try {
|
||||
pluginInstance.load();
|
||||
Logger.global.logInfo("BlueMap loaded!");
|
||||
if (pluginInstance.isLoaded()) Logger.global.logInfo("BlueMap loaded!");
|
||||
} catch (IOException | ParseResourceException e) {
|
||||
Logger.global.logError("Failed to load bluemap!", e);
|
||||
}
|
||||
@ -102,6 +122,13 @@ public void onInitialize() {
|
||||
pluginInstance.unload();
|
||||
Logger.global.logInfo("BlueMap unloaded!");
|
||||
});
|
||||
|
||||
PlayerJoinCallback.EVENT.register(this::onPlayerJoin);
|
||||
PlayerLeaveCallback.EVENT.register(this::onPlayerLeave);
|
||||
|
||||
ServerTickEvents.END_SERVER_TICK.register((MinecraftServer server) -> {
|
||||
if (server == this.serverInstance) this.updateSomePlayers();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -118,10 +145,10 @@ public void unregisterAllListeners() {
|
||||
public UUID getUUIDForWorld(File worldFolder) throws IOException {
|
||||
worldFolder = worldFolder.getCanonicalFile();
|
||||
|
||||
UUID uuid = worldUuids.get(worldFolder);
|
||||
UUID uuid = worldUUIDs.get(worldFolder);
|
||||
if (uuid == null) {
|
||||
uuid = UUID.randomUUID();
|
||||
worldUuids.put(worldFolder, uuid);
|
||||
worldUUIDs.put(worldFolder, uuid);
|
||||
}
|
||||
|
||||
return uuid;
|
||||
@ -150,5 +177,58 @@ private UUID loadUUIDForWorld(ServerWorld world) throws IOException {
|
||||
public File getConfigFolder() {
|
||||
return new File("config/bluemap");
|
||||
}
|
||||
|
||||
public void onPlayerJoin(MinecraftServer server, ServerPlayerEntity playerInstance) {
|
||||
if (this.serverInstance != server) return;
|
||||
|
||||
FabricPlayer player = new FabricPlayer(this, playerInstance);
|
||||
onlinePlayerMap.put(player.getUuid(), player);
|
||||
onlinePlayerList.add(player);
|
||||
}
|
||||
|
||||
public void onPlayerLeave(MinecraftServer server, ServerPlayerEntity player) {
|
||||
if (this.serverInstance != server) return;
|
||||
|
||||
UUID playerUUID = player.getUuid();
|
||||
onlinePlayerMap.remove(playerUUID);
|
||||
synchronized (onlinePlayerList) {
|
||||
onlinePlayerList.removeIf(p -> p.getUuid().equals(playerUUID));
|
||||
}
|
||||
}
|
||||
|
||||
public MinecraftServer getServer() {
|
||||
return this.serverInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getOnlinePlayers() {
|
||||
return onlinePlayerMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Player> getPlayer(UUID uuid) {
|
||||
return Optional.ofNullable(onlinePlayerMap.get(uuid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Only update some of the online players each tick to minimize performance impact on the server-thread.
|
||||
* Only call this method on the server-thread.
|
||||
*/
|
||||
private void updateSomePlayers() {
|
||||
int onlinePlayerCount = onlinePlayerList.size();
|
||||
if (onlinePlayerCount == 0) return;
|
||||
|
||||
int playersToBeUpdated = onlinePlayerCount / 20; //with 20 tps, each player is updated once a second
|
||||
if (playersToBeUpdated == 0) playersToBeUpdated = 1;
|
||||
|
||||
for (int i = 0; i < playersToBeUpdated; i++) {
|
||||
playerUpdateIndex++;
|
||||
if (playerUpdateIndex >= 20 && playerUpdateIndex >= onlinePlayerCount) playerUpdateIndex = 0;
|
||||
|
||||
if (playerUpdateIndex < onlinePlayerCount) {
|
||||
onlinePlayerList.get(i).update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* 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.fabric;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
import net.minecraft.entity.effect.StatusEffectInstance;
|
||||
import net.minecraft.entity.effect.StatusEffects;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
import net.minecraft.util.math.Vec3d;
|
||||
import net.minecraft.world.GameMode;
|
||||
|
||||
public class FabricPlayer implements Player {
|
||||
|
||||
private static final UUID UNKNOWN_WORLD_UUID = UUID.randomUUID();
|
||||
|
||||
private static final Map<GameMode, Gamemode> GAMEMODE_MAP = new EnumMap<>(GameMode.class);
|
||||
static {
|
||||
GAMEMODE_MAP.put(GameMode.ADVENTURE, Gamemode.ADVENTURE);
|
||||
GAMEMODE_MAP.put(GameMode.SURVIVAL, Gamemode.SURVIVAL);
|
||||
GAMEMODE_MAP.put(GameMode.CREATIVE, Gamemode.CREATIVE);
|
||||
GAMEMODE_MAP.put(GameMode.SPECTATOR, Gamemode.SPECTATOR);
|
||||
GAMEMODE_MAP.put(GameMode.NOT_SET, Gamemode.SURVIVAL);
|
||||
}
|
||||
|
||||
private UUID uuid;
|
||||
private Text name;
|
||||
private UUID world;
|
||||
private Vector3d position;
|
||||
private boolean online;
|
||||
private boolean sneaking;
|
||||
private boolean invisible;
|
||||
private Gamemode gamemode;
|
||||
|
||||
private FabricMod mod;
|
||||
private WeakReference<ServerPlayerEntity> delegate;
|
||||
|
||||
public FabricPlayer(FabricMod mod, ServerPlayerEntity delegate) {
|
||||
this.uuid = delegate.getUuid();
|
||||
this.mod = mod;
|
||||
this.delegate = new WeakReference<>(delegate);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUuid() {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3d getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return this.online;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking() {
|
||||
return this.sneaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvisible() {
|
||||
return this.invisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gamemode getGamemode() {
|
||||
return this.gamemode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call on server thread!
|
||||
*/
|
||||
public void update() {
|
||||
ServerPlayerEntity player = delegate.get();
|
||||
if (player == null) {
|
||||
MinecraftServer server = mod.getServer();
|
||||
if (server != null) {
|
||||
player = server.getPlayerManager().getPlayer(uuid);
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
this.online = false;
|
||||
return;
|
||||
}
|
||||
|
||||
delegate = new WeakReference<>(player);
|
||||
}
|
||||
|
||||
this.gamemode = GAMEMODE_MAP.get(player.interactionManager.getGameMode());
|
||||
|
||||
StatusEffectInstance invis = player.getStatusEffect(StatusEffects.INVISIBILITY);
|
||||
this.invisible = invis != null && invis.getDuration() > 0;
|
||||
|
||||
this.name = Text.of(player.getName().getString());
|
||||
this.online = true;
|
||||
|
||||
Vec3d pos = player.getPos();
|
||||
this.position = new Vector3d(pos.getX(), pos.getY(), pos.getZ());
|
||||
this.sneaking = player.isSneaking();
|
||||
|
||||
try {
|
||||
this.world = mod.getUUIDForWorld(player.getServerWorld());
|
||||
} catch (IOException e) {
|
||||
this.world = UNKNOWN_WORLD_UUID;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.fabric.events;
|
||||
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
public interface PlayerJoinCallback {
|
||||
Event<PlayerJoinCallback> EVENT = EventFactory.createArrayBacked(PlayerJoinCallback.class,
|
||||
(listeners) -> (server, player) -> {
|
||||
for (PlayerJoinCallback event : listeners) {
|
||||
event.onPlayerJoin(server, player);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player);
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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.fabric.events;
|
||||
|
||||
import net.fabricmc.fabric.api.event.Event;
|
||||
import net.fabricmc.fabric.api.event.EventFactory;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
public interface PlayerLeaveCallback {
|
||||
Event<PlayerLeaveCallback> EVENT = EventFactory.createArrayBacked(PlayerLeaveCallback.class,
|
||||
(listeners) -> (server, player) -> {
|
||||
for (PlayerLeaveCallback event : listeners) {
|
||||
event.onPlayerLeave(server, player);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
void onPlayerLeave(MinecraftServer server, ServerPlayerEntity player);
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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.fabric.mixin;
|
||||
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback;
|
||||
import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback;
|
||||
import net.minecraft.network.ClientConnection;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.PlayerManager;
|
||||
import net.minecraft.server.network.ServerPlayerEntity;
|
||||
|
||||
@Mixin(PlayerManager.class)
|
||||
public abstract class MixinPlayerManager {
|
||||
|
||||
@Shadow
|
||||
public abstract MinecraftServer getServer();
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "onPlayerConnect")
|
||||
public void onPlayerConnect(ClientConnection connection, ServerPlayerEntity player, CallbackInfo ci) {
|
||||
PlayerJoinCallback.EVENT.invoker().onPlayerJoin(this.getServer(), player);
|
||||
}
|
||||
|
||||
@Inject(at = @At("HEAD"), method = "remove")
|
||||
public void remove(ServerPlayerEntity player, CallbackInfo ci) {
|
||||
PlayerLeaveCallback.EVENT.invoker().onPlayerLeave(this.getServer(), player);
|
||||
}
|
||||
|
||||
}
|
@ -9,3 +9,9 @@ webserver {
|
||||
port: 8100
|
||||
maxConnectionCount: 100
|
||||
}
|
||||
liveUpdates {
|
||||
enabled: true
|
||||
hiddenGameModes: []
|
||||
hideInvisible: true
|
||||
hideSneaking: false
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ webroot: "bluemap/web"
|
||||
#webdata: "path/to/data/folder"
|
||||
|
||||
# If the web-application should use cookies to save the configurations of a user.
|
||||
# Default is true
|
||||
useCookies: true
|
||||
|
||||
webserver {
|
||||
@ -165,3 +166,23 @@ maps: [
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
liveUpdates {
|
||||
# If the server should send live-updates and player-positions.
|
||||
# Default is true
|
||||
enabled: true
|
||||
|
||||
# A list of gamemodes that will prevent a player from appearing on the map.
|
||||
# Possible values are: survival, creative, spectator, adventure
|
||||
hiddenGameModes: [
|
||||
"spectator"
|
||||
]
|
||||
|
||||
# If this is true, players that have an invisibility (potion-)effect will be hidden on the map.
|
||||
# Default is true
|
||||
hideInvisible: true
|
||||
|
||||
# If this is true, players that are sneaking will be hidden on the map.
|
||||
# Default is false
|
||||
hideSneaking: false
|
||||
}
|
||||
|
@ -6,8 +6,9 @@
|
||||
"mixins": [],
|
||||
"client": [],
|
||||
"server": [
|
||||
"MixinServerWorld",
|
||||
"MixinChunkGenerator"
|
||||
"MixinChunkGenerator",
|
||||
"MixinPlayerManager",
|
||||
"MixinServerWorld"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.forge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
public class ForgeEventForwarder {
|
||||
|
||||
private ForgeMod mod;
|
||||
private Collection<ServerEventListener> eventListeners;
|
||||
|
||||
public ForgeEventForwarder(ForgeMod mod) {
|
||||
this.mod = mod;
|
||||
this.eventListeners = new ArrayList<>(1);
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
|
||||
public synchronized void addEventListener(ServerEventListener listener) {
|
||||
this.eventListeners.add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeAllListeners() {
|
||||
this.eventListeners.clear();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockBreak(BlockEvent.BreakEvent evt) {
|
||||
onBlockChange(evt);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockPlace(BlockEvent.EntityPlaceEvent evt) {
|
||||
onBlockChange(evt);
|
||||
}
|
||||
|
||||
private synchronized void onBlockChange(BlockEvent evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
try {
|
||||
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
Vector3i position = new Vector3i(
|
||||
evt.getPos().getX(),
|
||||
evt.getPos().getY(),
|
||||
evt.getPos().getZ()
|
||||
);
|
||||
|
||||
for (ServerEventListener listener : eventListeners) listener.onBlockChange(world, position);
|
||||
|
||||
} catch (IOException e) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onWorldSave(WorldEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
try {
|
||||
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world);
|
||||
} catch (IOException e) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) {
|
||||
UUID uuid = evt.getPlayer().getUniqueID();
|
||||
for (ServerEventListener listener : eventListeners) listener.onPlayerJoin(uuid);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onPlayerLeave(PlayerLoggedOutEvent evt) {
|
||||
UUID uuid = evt.getPlayer().getUniqueID();
|
||||
for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid);
|
||||
}
|
||||
|
||||
}
|
@ -28,30 +28,36 @@
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.LoadingCache;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.plugin.commands.Commands;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
import net.minecraft.command.CommandSource;
|
||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.DimensionType;
|
||||
import net.minecraft.world.server.ServerWorld;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.event.TickEvent.ServerTickEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
|
||||
@ -59,20 +65,29 @@
|
||||
|
||||
@Mod(Plugin.PLUGIN_ID)
|
||||
public class ForgeMod implements ServerInterface {
|
||||
|
||||
private Plugin pluginInstance = null;
|
||||
private MinecraftServer serverInstance = null;
|
||||
|
||||
private Plugin bluemap;
|
||||
private Commands<CommandSource> commands;
|
||||
private Map<String, UUID> worldUUIDs;
|
||||
private Collection<ServerEventListener> eventListeners;
|
||||
private Map<File, UUID> worldUUIDs;
|
||||
private ForgeEventForwarder eventForwarder;
|
||||
|
||||
private LoadingCache<ServerWorld, UUID> worldUuidCache;
|
||||
|
||||
private int playerUpdateIndex = 0;
|
||||
private Map<UUID, Player> onlinePlayerMap;
|
||||
private List<ForgePlayer> onlinePlayerList;
|
||||
|
||||
public ForgeMod() {
|
||||
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
|
||||
|
||||
this.bluemap = new Plugin("forge", this);
|
||||
this.worldUUIDs = new HashMap<>();
|
||||
this.eventListeners = new ArrayList<>(1);
|
||||
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
this.pluginInstance = new Plugin("forge", this);
|
||||
|
||||
this.worldUUIDs = new ConcurrentHashMap<>();
|
||||
this.eventForwarder = new ForgeEventForwarder(this);
|
||||
this.worldUuidCache = CacheBuilder.newBuilder()
|
||||
.weakKeys()
|
||||
.maximumSize(1000)
|
||||
@ -82,113 +97,61 @@ public UUID load(ServerWorld key) throws Exception {
|
||||
return loadUUIDForWorld(key);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
MinecraftForge.EVENT_BUS.register(this);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerStarting(FMLServerStartingEvent event) {
|
||||
this.worldUUIDs.clear();
|
||||
|
||||
for (ServerWorld world : event.getServer().getWorlds()) {
|
||||
try {
|
||||
registerWorld(world);
|
||||
} catch (IOException e) {
|
||||
Logger.global.logError("Failed to register world: " + world.getProviderName(), e);
|
||||
}
|
||||
|
||||
try {
|
||||
world.save(null, false, false);
|
||||
} catch (Throwable t) {
|
||||
Logger.global.logError("Failed to save world: " + world.getProviderName(), t);
|
||||
}
|
||||
}
|
||||
|
||||
//register commands
|
||||
this.commands = new Commands<>(bluemap, event.getServer().getCommandManager().getDispatcher(), forgeSource -> new ForgeCommandSource(this, bluemap, forgeSource));
|
||||
new Commands<>(pluginInstance, event.getServer().getCommandManager().getDispatcher(), forgeSource -> new ForgeCommandSource(this, pluginInstance, forgeSource));
|
||||
|
||||
new Thread(() -> {
|
||||
Logger.global.logInfo("Loading...");
|
||||
|
||||
try {
|
||||
Logger.global.logInfo("Loading...");
|
||||
bluemap.load();
|
||||
if (bluemap.isLoaded()) Logger.global.logInfo("Loaded!");
|
||||
} catch (Throwable t) {
|
||||
Logger.global.logError("Failed to load!", t);
|
||||
pluginInstance.load();
|
||||
if (pluginInstance.isLoaded()) Logger.global.logInfo("Loaded!");
|
||||
} catch (IOException | ParseResourceException e) {
|
||||
Logger.global.logError("Failed to load bluemap!", e);
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void registerWorld(ServerWorld world) throws IOException {
|
||||
getUUIDForWorld(world);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onServerStopping(FMLServerStoppingEvent event) {
|
||||
Logger.global.logInfo("Stopping...");
|
||||
bluemap.unload();
|
||||
Logger.global.logInfo("Saved and stopped!");
|
||||
pluginInstance.unload();
|
||||
Logger.global.logInfo("BlueMap unloaded!");
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onTick(ServerTickEvent evt) {
|
||||
updateSomePlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener(ServerEventListener listener) {
|
||||
eventListeners.add(listener);
|
||||
eventForwarder.addEventListener(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterAllListeners() {
|
||||
eventListeners.clear();
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockBreak(BlockEvent.BreakEvent evt) {
|
||||
onBlockChange(evt);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onBlockPlace(BlockEvent.EntityPlaceEvent evt) {
|
||||
onBlockChange(evt);
|
||||
}
|
||||
|
||||
private void onBlockChange(BlockEvent evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
try {
|
||||
UUID world = getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
Vector3i position = new Vector3i(
|
||||
evt.getPos().getX(),
|
||||
evt.getPos().getY(),
|
||||
evt.getPos().getZ()
|
||||
);
|
||||
|
||||
for (ServerEventListener listener : eventListeners) listener.onBlockChange(world, position);
|
||||
|
||||
} catch (IOException ignore) {}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public void onWorldSave(WorldEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
try {
|
||||
UUID world = getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
|
||||
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world);
|
||||
|
||||
} catch (IOException ignore) {}
|
||||
eventForwarder.removeAllListeners();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUIDForWorld(File worldFolder) throws IOException {
|
||||
synchronized (worldUUIDs) {
|
||||
String key = worldFolder.getCanonicalPath();
|
||||
|
||||
UUID uuid = worldUUIDs.get(key);
|
||||
if (uuid == null) {
|
||||
throw new IOException("There is no world with this folder loaded: " + worldFolder.getPath());
|
||||
}
|
||||
|
||||
return uuid;
|
||||
worldFolder = worldFolder.getCanonicalFile();
|
||||
|
||||
UUID uuid = worldUUIDs.get(worldFolder);
|
||||
if (uuid == null) {
|
||||
uuid = UUID.randomUUID();
|
||||
worldUUIDs.put(worldFolder, uuid);
|
||||
}
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public UUID getUUIDForWorld(ServerWorld world) throws IOException {
|
||||
@ -202,17 +165,15 @@ public UUID getUUIDForWorld(ServerWorld world) throws IOException {
|
||||
}
|
||||
|
||||
private UUID loadUUIDForWorld(ServerWorld world) throws IOException {
|
||||
synchronized (worldUUIDs) {
|
||||
String key = getFolderForWorld(world).getPath();
|
||||
|
||||
UUID uuid = worldUUIDs.get(key);
|
||||
if (uuid == null) {
|
||||
uuid = UUID.randomUUID();
|
||||
worldUUIDs.put(key, uuid);
|
||||
}
|
||||
|
||||
return uuid;
|
||||
File key = getFolderForWorld(world);
|
||||
|
||||
UUID uuid = worldUUIDs.get(key);
|
||||
if (uuid == null) {
|
||||
uuid = UUID.randomUUID();
|
||||
worldUUIDs.put(key, uuid);
|
||||
}
|
||||
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private File getFolderForWorld(ServerWorld world) throws IOException {
|
||||
@ -227,9 +188,60 @@ private File getFolderForWorld(ServerWorld world) throws IOException {
|
||||
public File getConfigFolder() {
|
||||
return new File("config/bluemap");
|
||||
}
|
||||
|
||||
public void onPlayerJoin(PlayerLoggedInEvent evt) {
|
||||
PlayerEntity playerInstance = evt.getPlayer();
|
||||
if (!(playerInstance instanceof ServerPlayerEntity)) return;
|
||||
|
||||
ForgePlayer player = new ForgePlayer(this, (ServerPlayerEntity) playerInstance);
|
||||
onlinePlayerMap.put(player.getUuid(), player);
|
||||
onlinePlayerList.add(player);
|
||||
}
|
||||
|
||||
public void onPlayerLeave(PlayerLoggedOutEvent evt) {
|
||||
PlayerEntity player = evt.getPlayer();
|
||||
if (!(player instanceof ServerPlayerEntity)) return;
|
||||
|
||||
UUID playerUUID = player.getUniqueID();
|
||||
onlinePlayerMap.remove(playerUUID);
|
||||
synchronized (onlinePlayerList) {
|
||||
onlinePlayerList.removeIf(p -> p.getUuid().equals(playerUUID));
|
||||
}
|
||||
}
|
||||
|
||||
public Commands<CommandSource> getCommands() {
|
||||
return commands;
|
||||
public MinecraftServer getServer() {
|
||||
return this.serverInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getOnlinePlayers() {
|
||||
return onlinePlayerMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Player> getPlayer(UUID uuid) {
|
||||
return Optional.ofNullable(onlinePlayerMap.get(uuid));
|
||||
}
|
||||
|
||||
/**
|
||||
* Only update some of the online players each tick to minimize performance impact on the server-thread.
|
||||
* Only call this method on the server-thread.
|
||||
*/
|
||||
private void updateSomePlayers() {
|
||||
int onlinePlayerCount = onlinePlayerList.size();
|
||||
if (onlinePlayerCount == 0) return;
|
||||
|
||||
int playersToBeUpdated = onlinePlayerCount / 20; //with 20 tps, each player is updated once a second
|
||||
if (playersToBeUpdated == 0) playersToBeUpdated = 1;
|
||||
|
||||
for (int i = 0; i < playersToBeUpdated; i++) {
|
||||
playerUpdateIndex++;
|
||||
if (playerUpdateIndex >= 20 && playerUpdateIndex >= onlinePlayerCount) playerUpdateIndex = 0;
|
||||
|
||||
if (playerUpdateIndex < onlinePlayerCount) {
|
||||
onlinePlayerList.get(i).update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,155 @@
|
||||
/*
|
||||
* 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.forge;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
import net.minecraft.entity.player.ServerPlayerEntity;
|
||||
import net.minecraft.potion.EffectInstance;
|
||||
import net.minecraft.potion.Effects;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.world.GameType;
|
||||
|
||||
public class ForgePlayer implements Player {
|
||||
|
||||
private static final UUID UNKNOWN_WORLD_UUID = UUID.randomUUID();
|
||||
|
||||
private static final Map<GameType, Gamemode> GAMEMODE_MAP = new EnumMap<>(GameType.class);
|
||||
static {
|
||||
GAMEMODE_MAP.put(GameType.ADVENTURE, Gamemode.ADVENTURE);
|
||||
GAMEMODE_MAP.put(GameType.SURVIVAL, Gamemode.SURVIVAL);
|
||||
GAMEMODE_MAP.put(GameType.CREATIVE, Gamemode.CREATIVE);
|
||||
GAMEMODE_MAP.put(GameType.SPECTATOR, Gamemode.SPECTATOR);
|
||||
GAMEMODE_MAP.put(GameType.NOT_SET, Gamemode.SURVIVAL);
|
||||
}
|
||||
|
||||
private UUID uuid;
|
||||
private Text name;
|
||||
private UUID world;
|
||||
private Vector3d position;
|
||||
private boolean online;
|
||||
private boolean sneaking;
|
||||
private boolean invisible;
|
||||
private Gamemode gamemode;
|
||||
|
||||
private ForgeMod mod;
|
||||
private WeakReference<ServerPlayerEntity> delegate;
|
||||
|
||||
public ForgePlayer(ForgeMod mod, ServerPlayerEntity delegate) {
|
||||
this.uuid = delegate.getUniqueID();
|
||||
this.mod = mod;
|
||||
this.delegate = new WeakReference<>(delegate);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUuid() {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3d getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return this.online;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking() {
|
||||
return this.sneaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvisible() {
|
||||
return this.invisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gamemode getGamemode() {
|
||||
return this.gamemode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only call on server thread!
|
||||
*/
|
||||
public void update() {
|
||||
ServerPlayerEntity player = delegate.get();
|
||||
if (player == null) {
|
||||
MinecraftServer server = mod.getServer();
|
||||
if (server != null) {
|
||||
player = server.getPlayerList().getPlayerByUUID(uuid);
|
||||
}
|
||||
|
||||
if (player == null) {
|
||||
this.online = false;
|
||||
return;
|
||||
}
|
||||
|
||||
delegate = new WeakReference<>(player);
|
||||
}
|
||||
|
||||
this.gamemode = GAMEMODE_MAP.get(player.interactionManager.getGameType());
|
||||
|
||||
EffectInstance invis = player.getActivePotionEffect(Effects.INVISIBILITY);
|
||||
this.invisible = invis != null && invis.getDuration() > 0;
|
||||
|
||||
this.name = Text.of(player.getName().getString());
|
||||
this.online = true;
|
||||
|
||||
net.minecraft.util.math.vector.Vector3d pos = player.getPositionVec();
|
||||
this.position = new Vector3d(pos.getX(), pos.getY(), pos.getZ());
|
||||
this.sneaking = player.isSneaking();
|
||||
|
||||
try {
|
||||
this.world = mod.getUUIDForWorld(player.getServerWorld());
|
||||
} catch (IOException e) {
|
||||
this.world = UNKNOWN_WORLD_UUID;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -9,3 +9,9 @@ webserver {
|
||||
port: 8100
|
||||
maxConnectionCount: 100
|
||||
}
|
||||
liveUpdates {
|
||||
enabled: true
|
||||
hiddenGameModes: []
|
||||
hideInvisible: true
|
||||
hideSneaking: false
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ webroot: "bluemap/web"
|
||||
#webdata: "path/to/data/folder"
|
||||
|
||||
# If the web-application should use cookies to save the configurations of a user.
|
||||
# Default is true
|
||||
useCookies: true
|
||||
|
||||
webserver {
|
||||
@ -165,3 +166,23 @@ maps: [
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
liveUpdates {
|
||||
# If the server should send live-updates and player-positions.
|
||||
# Default is true
|
||||
enabled: true
|
||||
|
||||
# A list of gamemodes that will prevent a player from appearing on the map.
|
||||
# Possible values are: survival, creative, spectator, adventure
|
||||
hiddenGameModes: [
|
||||
"spectator"
|
||||
]
|
||||
|
||||
# If this is true, players that have an invisibility (potion-)effect will be hidden on the map.
|
||||
# Default is true
|
||||
hideInvisible: true
|
||||
|
||||
# If this is true, players that are sneaking will be hidden on the map.
|
||||
# Default is false
|
||||
hideSneaking: false
|
||||
}
|
||||
|
@ -32,6 +32,8 @@
|
||||
import org.spongepowered.api.event.Order;
|
||||
import org.spongepowered.api.event.block.ChangeBlockEvent;
|
||||
import org.spongepowered.api.event.filter.type.Exclude;
|
||||
import org.spongepowered.api.event.message.MessageChannelEvent;
|
||||
import org.spongepowered.api.event.network.ClientConnectionEvent;
|
||||
import org.spongepowered.api.event.world.SaveWorldEvent;
|
||||
import org.spongepowered.api.event.world.chunk.PopulateChunkEvent;
|
||||
import org.spongepowered.api.world.Location;
|
||||
@ -40,6 +42,7 @@
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
|
||||
public class EventForwarder {
|
||||
|
||||
@ -73,4 +76,19 @@ public void onChunkFinishedGeneration(PopulateChunkEvent.Post evt) {
|
||||
listener.onChunkFinishedGeneration(evt.getTargetChunk().getWorld().getUniqueId(), new Vector2i(chunkPos.getX(), chunkPos.getZ()));
|
||||
}
|
||||
|
||||
@Listener(order = Order.POST)
|
||||
public void onPlayerJoin(ClientConnectionEvent.Join evt) {
|
||||
listener.onPlayerJoin(evt.getTargetEntity().getUniqueId());
|
||||
}
|
||||
|
||||
@Listener(order = Order.POST)
|
||||
public void onPlayerLeave(ClientConnectionEvent.Disconnect evt) {
|
||||
listener.onPlayerJoin(evt.getTargetEntity().getUniqueId());
|
||||
}
|
||||
|
||||
@Listener(order = Order.POST)
|
||||
public void onPlayerChat(MessageChannelEvent.Chat evt) {
|
||||
listener.onChatMessage(Text.of(evt.getMessage().toPlain()));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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.sponge;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.data.key.Keys;
|
||||
import org.spongepowered.api.effect.potion.PotionEffect;
|
||||
import org.spongepowered.api.effect.potion.PotionEffectTypes;
|
||||
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
|
||||
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
|
||||
|
||||
import com.flowpowered.math.vector.Vector3d;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||
|
||||
public class SpongePlayer implements Player {
|
||||
|
||||
private static final Map<GameMode, Gamemode> GAMEMODE_MAP = new HashMap<>(5);
|
||||
static {
|
||||
GAMEMODE_MAP.put(GameModes.ADVENTURE, Gamemode.ADVENTURE);
|
||||
GAMEMODE_MAP.put(GameModes.SURVIVAL, Gamemode.SURVIVAL);
|
||||
GAMEMODE_MAP.put(GameModes.CREATIVE, Gamemode.CREATIVE);
|
||||
GAMEMODE_MAP.put(GameModes.SPECTATOR, Gamemode.SPECTATOR);
|
||||
GAMEMODE_MAP.put(GameModes.NOT_SET, Gamemode.SURVIVAL);
|
||||
}
|
||||
|
||||
private UUID uuid;
|
||||
private Text name;
|
||||
private UUID world;
|
||||
private Vector3d position;
|
||||
private boolean online;
|
||||
private boolean sneaking;
|
||||
private boolean invisible;
|
||||
private Gamemode gamemode;
|
||||
|
||||
private WeakReference<org.spongepowered.api.entity.living.player.Player> delegate;
|
||||
|
||||
public SpongePlayer(org.spongepowered.api.entity.living.player.Player delegate) {
|
||||
this.uuid = delegate.getUniqueId();
|
||||
this.delegate = new WeakReference<>(delegate);
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUuid() {
|
||||
return this.uuid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Text getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getWorld() {
|
||||
return this.world;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Vector3d getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOnline() {
|
||||
return this.online;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSneaking() {
|
||||
return this.sneaking;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInvisible() {
|
||||
return this.invisible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Gamemode getGamemode() {
|
||||
return this.gamemode;
|
||||
}
|
||||
|
||||
/**
|
||||
* API access, only call on server thread!
|
||||
*/
|
||||
public void update() {
|
||||
org.spongepowered.api.entity.living.player.Player player = delegate.get();
|
||||
if (player == null) {
|
||||
player = Sponge.getServer().getPlayer(uuid).orElse(null);
|
||||
if (player == null) {
|
||||
this.online = false;
|
||||
return;
|
||||
}
|
||||
|
||||
delegate = new WeakReference<>(player);
|
||||
}
|
||||
|
||||
this.gamemode = GAMEMODE_MAP.get(player.get(Keys.GAME_MODE).orElse(GameModes.NOT_SET));
|
||||
|
||||
boolean invis = player.get(Keys.VANISH).orElse(false);
|
||||
if (!invis && player.get(Keys.INVISIBLE).orElse(false)) invis = true;
|
||||
if (!invis) {
|
||||
Optional<List<PotionEffect>> effects = player.get(Keys.POTION_EFFECTS);
|
||||
if (effects.isPresent()) {
|
||||
for (PotionEffect effect : effects.get()) {
|
||||
if (effect.getType().equals(PotionEffectTypes.INVISIBILITY) && effect.getDuration() > 0) invis = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.invisible = invis;
|
||||
|
||||
this.name = Text.of(player.getName());
|
||||
this.online = player.isOnline();
|
||||
this.position = player.getPosition();
|
||||
this.sneaking = player.get(Keys.IS_SNEAKING).orElse(false);
|
||||
this.world = player.getWorld().getUniqueId();
|
||||
}
|
||||
|
||||
}
|
@ -27,8 +27,14 @@
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
@ -39,13 +45,16 @@
|
||||
import org.spongepowered.api.event.game.GameReloadEvent;
|
||||
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
|
||||
import org.spongepowered.api.event.game.state.GameStoppingEvent;
|
||||
import org.spongepowered.api.event.network.ClientConnectionEvent;
|
||||
import org.spongepowered.api.plugin.PluginContainer;
|
||||
import org.spongepowered.api.scheduler.SpongeExecutorService;
|
||||
import org.spongepowered.api.scheduler.Task;
|
||||
import org.spongepowered.api.util.Tristate;
|
||||
import org.spongepowered.api.world.World;
|
||||
import org.spongepowered.api.world.storage.WorldProperties;
|
||||
|
||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||
import de.bluecolored.bluemap.core.logger.Logger;
|
||||
@ -53,7 +62,7 @@
|
||||
import net.querz.nbt.CompoundTag;
|
||||
import net.querz.nbt.NBTUtil;
|
||||
|
||||
@org.spongepowered.api.plugin.Plugin(
|
||||
@org.spongepowered.api.plugin.Plugin (
|
||||
id = Plugin.PLUGIN_ID,
|
||||
name = Plugin.PLUGIN_NAME,
|
||||
authors = { "Blue (Lukas Rieger)" },
|
||||
@ -71,13 +80,20 @@ public class SpongePlugin implements ServerInterface {
|
||||
|
||||
private Plugin bluemap;
|
||||
private SpongeCommands commands;
|
||||
|
||||
|
||||
private SpongeExecutorService asyncExecutor;
|
||||
|
||||
private int playerUpdateIndex = 0;
|
||||
private Map<UUID, Player> onlinePlayerMap;
|
||||
private List<SpongePlayer> onlinePlayerList;
|
||||
|
||||
@Inject
|
||||
public SpongePlugin(org.slf4j.Logger logger) {
|
||||
Logger.global = new Slf4jLogger(logger);
|
||||
|
||||
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
this.bluemap = new Plugin("sponge", this);
|
||||
this.commands = new SpongeCommands(bluemap);
|
||||
}
|
||||
@ -96,6 +112,12 @@ public void onServerStart(GameStartingServerEvent evt) {
|
||||
Sponge.getCommandManager().register(this, command, command.getLabel());
|
||||
}
|
||||
|
||||
//start updating players
|
||||
Task.builder()
|
||||
.intervalTicks(1)
|
||||
.execute(this::updateSomePlayers)
|
||||
.submit(this);
|
||||
|
||||
asyncExecutor.execute(() -> {
|
||||
try {
|
||||
Logger.global.logInfo("Loading...");
|
||||
@ -110,6 +132,7 @@ public void onServerStart(GameStartingServerEvent evt) {
|
||||
@Listener
|
||||
public void onServerStop(GameStoppingEvent evt) {
|
||||
Logger.global.logInfo("Stopping...");
|
||||
Sponge.getScheduler().getScheduledTasks(this).forEach(t -> t.cancel());
|
||||
bluemap.unload();
|
||||
Logger.global.logInfo("Saved and stopped!");
|
||||
}
|
||||
@ -127,6 +150,23 @@ public void onServerReload(GameReloadEvent evt) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Listener
|
||||
public void onPlayerJoin(ClientConnectionEvent.Join evt) {
|
||||
SpongePlayer player = new SpongePlayer(evt.getTargetEntity());
|
||||
onlinePlayerMap.put(evt.getTargetEntity().getUniqueId(), player);
|
||||
onlinePlayerList.add(player);
|
||||
}
|
||||
|
||||
@Listener
|
||||
public void onPlayerLeave(ClientConnectionEvent.Disconnect evt) {
|
||||
UUID playerUUID = evt.getTargetEntity().getUniqueId();
|
||||
onlinePlayerMap.remove(playerUUID);
|
||||
synchronized (onlinePlayerList) {
|
||||
onlinePlayerList.removeIf(p -> p.getUuid().equals(playerUUID));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener(ServerEventListener listener) {
|
||||
Sponge.getEventManager().registerListeners(this, new EventForwarder(listener));
|
||||
@ -163,6 +203,16 @@ public String getWorldName(UUID worldUUID) {
|
||||
public File getConfigFolder() {
|
||||
return configurationDir.toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getOnlinePlayers() {
|
||||
return onlinePlayerMap.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Player> getPlayer(UUID uuid) {
|
||||
return Optional.ofNullable(onlinePlayerMap.get(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMetricsEnabled(boolean configValue) {
|
||||
@ -177,4 +227,25 @@ public boolean isMetricsEnabled(boolean configValue) {
|
||||
return Sponge.getMetricsConfigManager().getGlobalCollectionState().asBoolean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Only update some of the online players each tick to minimize performance impact on the server-thread.
|
||||
* Only call this method on the server-thread.
|
||||
*/
|
||||
private void updateSomePlayers() {
|
||||
int onlinePlayerCount = onlinePlayerList.size();
|
||||
if (onlinePlayerCount == 0) return;
|
||||
|
||||
int playersToBeUpdated = onlinePlayerCount / 20; //with 20 tps, each player is updated once a second
|
||||
if (playersToBeUpdated == 0) playersToBeUpdated = 1;
|
||||
|
||||
for (int i = 0; i < playersToBeUpdated; i++) {
|
||||
playerUpdateIndex++;
|
||||
if (playerUpdateIndex >= 20 && playerUpdateIndex >= onlinePlayerCount) playerUpdateIndex = 0;
|
||||
|
||||
if (playerUpdateIndex < onlinePlayerCount) {
|
||||
onlinePlayerList.get(i).update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,3 +9,9 @@ webserver {
|
||||
port: 8100
|
||||
maxConnectionCount: 100
|
||||
}
|
||||
liveUpdates {
|
||||
enabled: true
|
||||
hiddenGameModes: []
|
||||
hideInvisible: true
|
||||
hideSneaking: false
|
||||
}
|
||||
|
@ -160,3 +160,23 @@ maps: [
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
liveUpdates {
|
||||
# If the server should send live-updates and player-positions.
|
||||
# Default is true
|
||||
enabled: true
|
||||
|
||||
# A list of gamemodes that will prevent a player from appearing on the map.
|
||||
# Possible values are: survival, creative, spectator, adventure
|
||||
hiddenGameModes: [
|
||||
"spectator"
|
||||
]
|
||||
|
||||
# If this is true, players that have an invisibility (potion-)effect will be hidden on the map.
|
||||
# Default is true
|
||||
hideInvisible: true
|
||||
|
||||
# If this is true, players that are sneaking will be hidden on the map.
|
||||
# Default is false
|
||||
hideSneaking: false
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user