mirror of
https://github.com/BlueMap-Minecraft/BlueMap.git
synced 2024-11-15 23:25:32 +01:00
Merge branch 'feature/live' into base
This commit is contained in:
commit
9e40cb447b
@ -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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
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.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
@ -37,14 +44,20 @@
|
|||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.command.CommandMap;
|
import org.bukkit.command.CommandMap;
|
||||||
import org.bukkit.command.defaults.BukkitCommand;
|
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 org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
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.ServerEventListener;
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
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;
|
private static BukkitPlugin instance;
|
||||||
|
|
||||||
@ -52,9 +65,16 @@ public class BukkitPlugin extends JavaPlugin implements ServerInterface {
|
|||||||
private EventForwarder eventForwarder;
|
private EventForwarder eventForwarder;
|
||||||
private BukkitCommands commands;
|
private BukkitCommands commands;
|
||||||
|
|
||||||
|
private int playerUpdateIndex = 0;
|
||||||
|
private Map<UUID, Player> onlinePlayerMap;
|
||||||
|
private List<BukkitPlayer> onlinePlayerList;
|
||||||
|
|
||||||
public BukkitPlugin() {
|
public BukkitPlugin() {
|
||||||
Logger.global = new JavaLogger(getLogger());
|
Logger.global = new JavaLogger(getLogger());
|
||||||
|
|
||||||
|
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||||
|
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
this.eventForwarder = new EventForwarder();
|
this.eventForwarder = new EventForwarder();
|
||||||
this.bluemap = new Plugin("bukkit", this);
|
this.bluemap = new Plugin("bukkit", this);
|
||||||
this.commands = new BukkitCommands(this.bluemap);
|
this.commands = new BukkitCommands(this.bluemap);
|
||||||
@ -73,6 +93,7 @@ public void onEnable() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//register events
|
//register events
|
||||||
|
getServer().getPluginManager().registerEvents(this, this);
|
||||||
getServer().getPluginManager().registerEvents(eventForwarder, this);
|
getServer().getPluginManager().registerEvents(eventForwarder, this);
|
||||||
|
|
||||||
//register commands
|
//register commands
|
||||||
@ -92,6 +113,9 @@ public void onEnable() {
|
|||||||
//tab completions
|
//tab completions
|
||||||
getServer().getPluginManager().registerEvents(commands, this);
|
getServer().getPluginManager().registerEvents(commands, this);
|
||||||
|
|
||||||
|
//start updating players
|
||||||
|
getServer().getScheduler().runTaskTimer(this, this::updateSomePlayers, 1, 1);
|
||||||
|
|
||||||
//load bluemap
|
//load bluemap
|
||||||
getServer().getScheduler().runTaskAsynchronously(this, () -> {
|
getServer().getScheduler().runTaskAsynchronously(this, () -> {
|
||||||
try {
|
try {
|
||||||
@ -107,6 +131,7 @@ public void onEnable() {
|
|||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
Logger.global.logInfo("Stopping...");
|
Logger.global.logInfo("Stopping...");
|
||||||
|
getServer().getScheduler().cancelTasks(this);
|
||||||
bluemap.unload();
|
bluemap.unload();
|
||||||
Logger.global.logInfo("Saved and stopped!");
|
Logger.global.logInfo("Saved and stopped!");
|
||||||
}
|
}
|
||||||
@ -179,4 +204,51 @@ public static BukkitPlugin getInstance() {
|
|||||||
return instance;
|
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.BlockGrowEvent;
|
||||||
import org.bukkit.event.block.BlockPlaceEvent;
|
import org.bukkit.event.block.BlockPlaceEvent;
|
||||||
import org.bukkit.event.block.BlockSpreadEvent;
|
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.ChunkPopulateEvent;
|
||||||
import org.bukkit.event.world.WorldSaveEvent;
|
import org.bukkit.event.world.WorldSaveEvent;
|
||||||
|
|
||||||
@ -49,6 +52,7 @@
|
|||||||
import com.flowpowered.math.vector.Vector3i;
|
import com.flowpowered.math.vector.Vector3i;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||||
|
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||||
|
|
||||||
public class EventForwarder implements Listener {
|
public class EventForwarder implements Listener {
|
||||||
|
|
||||||
@ -68,7 +72,7 @@ public synchronized void removeAllListeners() {
|
|||||||
|
|
||||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
public synchronized void onWorldSaveToDisk(WorldSaveEvent evt) {
|
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)
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
@ -119,7 +123,7 @@ public void onBlockChange(BlockFertilizeEvent evt) {
|
|||||||
private synchronized void onBlockChange(Location loc) {
|
private synchronized void onBlockChange(Location loc) {
|
||||||
UUID world = loc.getWorld().getUID();
|
UUID world = loc.getWorld().getUID();
|
||||||
Vector3i pos = new Vector3i(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
|
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)
|
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||||
@ -127,7 +131,23 @@ public synchronized void onChunkFinishedGeneration(ChunkPopulateEvent evt) {
|
|||||||
Chunk chunk = evt.getChunk();
|
Chunk chunk = evt.getChunk();
|
||||||
UUID world = chunk.getWorld().getUID();
|
UUID world = chunk.getWorld().getUID();
|
||||||
Vector2i chunkPos = new Vector2i(chunk.getX(), chunk.getZ());
|
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
|
port: 8100
|
||||||
maxConnectionCount: 100
|
maxConnectionCount: 100
|
||||||
}
|
}
|
||||||
|
liveUpdates {
|
||||||
|
enabled: true
|
||||||
|
hiddenGameModes: []
|
||||||
|
hideInvisible: true
|
||||||
|
hideSneaking: false
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@ webroot: "bluemap/web"
|
|||||||
#webdata: "path/to/data/folder"
|
#webdata: "path/to/data/folder"
|
||||||
|
|
||||||
# If the web-application should use cookies to save the configurations of a user.
|
# If the web-application should use cookies to save the configurations of a user.
|
||||||
|
# Default is true
|
||||||
useCookies: true
|
useCookies: true
|
||||||
|
|
||||||
webserver {
|
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.flowpowered.math.vector.Vector2i;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.common.BlueMapWebServer;
|
||||||
import de.bluecolored.bluemap.common.MapType;
|
import de.bluecolored.bluemap.common.MapType;
|
||||||
import de.bluecolored.bluemap.common.RenderManager;
|
import de.bluecolored.bluemap.common.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.RenderTask;
|
import de.bluecolored.bluemap.common.RenderTask;
|
||||||
@ -72,7 +73,6 @@
|
|||||||
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
|
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
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.WebFilesManager;
|
||||||
import de.bluecolored.bluemap.core.web.WebSettings;
|
import de.bluecolored.bluemap.core.web.WebSettings;
|
||||||
import de.bluecolored.bluemap.core.world.SlicedWorld;
|
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
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.web;
|
package de.bluecolored.bluemap.common;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.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;
|
import de.bluecolored.bluemap.core.webserver.WebServer;
|
||||||
|
|
||||||
public class BlueMapWebServer extends WebServer {
|
public class BlueMapWebServer extends WebServer {
|
||||||
|
|
||||||
private WebFilesManager webFilesManager;
|
private WebFilesManager webFilesManager;
|
||||||
|
|
||||||
|
|
||||||
public BlueMapWebServer(WebServerConfig config) {
|
public BlueMapWebServer(WebServerConfig config) {
|
||||||
super(
|
super(
|
||||||
config.getWebserverPort(),
|
config.getWebserverPort(),
|
||||||
config.getWebserverMaxConnections(),
|
config.getWebserverMaxConnections(),
|
||||||
config.getWebserverBindAdress(),
|
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());
|
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 com.flowpowered.math.vector.Vector2i;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.common.BlueMapWebServer;
|
||||||
import de.bluecolored.bluemap.common.MapType;
|
import de.bluecolored.bluemap.common.MapType;
|
||||||
import de.bluecolored.bluemap.common.RenderManager;
|
import de.bluecolored.bluemap.common.RenderManager;
|
||||||
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||||
|
import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater;
|
||||||
import de.bluecolored.bluemap.core.config.ConfigManager;
|
import de.bluecolored.bluemap.core.config.ConfigManager;
|
||||||
import de.bluecolored.bluemap.core.config.MainConfig;
|
import de.bluecolored.bluemap.core.config.MainConfig;
|
||||||
import de.bluecolored.bluemap.core.config.MainConfig.MapConfig;
|
import de.bluecolored.bluemap.core.config.MainConfig.MapConfig;
|
||||||
@ -62,7 +64,6 @@
|
|||||||
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
|
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
|
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.WebFilesManager;
|
||||||
import de.bluecolored.bluemap.core.web.WebSettings;
|
import de.bluecolored.bluemap.core.web.WebSettings;
|
||||||
import de.bluecolored.bluemap.core.world.SlicedWorld;
|
import de.bluecolored.bluemap.core.world.SlicedWorld;
|
||||||
@ -86,6 +87,7 @@ public class Plugin {
|
|||||||
private Map<String, MapType> maps;
|
private Map<String, MapType> maps;
|
||||||
|
|
||||||
private MapUpdateHandler updateHandler;
|
private MapUpdateHandler updateHandler;
|
||||||
|
private PlayerSkinUpdater skinUpdater;
|
||||||
|
|
||||||
private RenderManager renderManager;
|
private RenderManager renderManager;
|
||||||
private BlueMapWebServer webServer;
|
private BlueMapWebServer webServer;
|
||||||
@ -269,6 +271,12 @@ public synchronized void load() throws IOException, ParseResourceException {
|
|||||||
this.updateHandler = new MapUpdateHandler(this);
|
this.updateHandler = new MapUpdateHandler(this);
|
||||||
serverInterface.registerListener(updateHandler);
|
serverInterface.registerListener(updateHandler);
|
||||||
|
|
||||||
|
//start skin updater
|
||||||
|
if (config.isLiveUpdatesEnabled()) {
|
||||||
|
this.skinUpdater = new PlayerSkinUpdater(config.getWebRoot().resolve("assets").resolve("playerheads").toFile());
|
||||||
|
serverInterface.registerListener(skinUpdater);
|
||||||
|
}
|
||||||
|
|
||||||
//create/update webfiles
|
//create/update webfiles
|
||||||
WebFilesManager webFilesManager = new WebFilesManager(config.getWebRoot());
|
WebFilesManager webFilesManager = new WebFilesManager(config.getWebRoot());
|
||||||
if (webFilesManager.needsUpdate()) {
|
if (webFilesManager.needsUpdate()) {
|
||||||
@ -293,7 +301,7 @@ public synchronized void load() throws IOException, ParseResourceException {
|
|||||||
|
|
||||||
//start webserver
|
//start webserver
|
||||||
if (config.isWebserverEnabled()) {
|
if (config.isWebserverEnabled()) {
|
||||||
webServer = new BlueMapWebServer(config);
|
webServer = new BlueMapWebServer(config, config, serverInterface);
|
||||||
webServer.updateWebfiles();
|
webServer.updateWebfiles();
|
||||||
webServer.start();
|
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.Vector2i;
|
||||||
import com.flowpowered.math.vector.Vector3i;
|
import com.flowpowered.math.vector.Vector3i;
|
||||||
|
|
||||||
|
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||||
|
|
||||||
public interface ServerEventListener {
|
public interface ServerEventListener {
|
||||||
|
|
||||||
void onWorldSaveToDisk(UUID world);
|
default void onWorldSaveToDisk(UUID world) {};
|
||||||
|
|
||||||
void onBlockChange(UUID world, Vector3i blockPos);
|
default void onBlockChange(UUID world, Vector3i blockPos) {};
|
||||||
|
|
||||||
void onChunkFinishedGeneration(UUID world, Vector2i chunkPos);
|
default void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {};
|
||||||
|
|
||||||
|
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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public interface ServerInterface {
|
public interface ServerInterface {
|
||||||
@ -73,5 +75,16 @@ default boolean isMetricsEnabled(boolean configValue) {
|
|||||||
return 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.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
@ -43,7 +45,7 @@
|
|||||||
import de.bluecolored.bluemap.core.web.WebServerConfig;
|
import de.bluecolored.bluemap.core.web.WebServerConfig;
|
||||||
import ninja.leaping.configurate.ConfigurationNode;
|
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 static final Pattern VALID_ID_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
|
||||||
|
|
||||||
private boolean downloadAccepted = false;
|
private boolean downloadAccepted = false;
|
||||||
@ -64,6 +66,11 @@ public class MainConfig implements WebServerConfig {
|
|||||||
|
|
||||||
private List<MapConfig> mapConfigs = new ArrayList<>();
|
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 {
|
public MainConfig(ConfigurationNode node) throws OutdatedConfigException, IOException {
|
||||||
checkOutdated(node);
|
checkOutdated(node);
|
||||||
|
|
||||||
@ -101,6 +108,19 @@ public MainConfig(ConfigurationNode node) throws OutdatedConfigException, IOExce
|
|||||||
|
|
||||||
//maps
|
//maps
|
||||||
loadMapConfigs(node.getNode("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 {
|
private void loadWebConfig(ConfigurationNode node) throws IOException {
|
||||||
@ -196,6 +216,26 @@ public List<MapConfig> getMapConfigs(){
|
|||||||
return mapConfigs;
|
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 {
|
public class MapConfig implements RenderSettings {
|
||||||
|
|
||||||
private String id;
|
private String id;
|
||||||
|
@ -198,9 +198,7 @@ public synchronized Texture loadTexture(FileAccess fileAccess, String path) thro
|
|||||||
|
|
||||||
//crop off animation frames
|
//crop off animation frames
|
||||||
if (image.getHeight() > image.getWidth()){
|
if (image.getHeight() > image.getWidth()){
|
||||||
BufferedImage cropped = new BufferedImage(image.getWidth(), image.getWidth(), image.getType());
|
image = image.getSubimage(0, 0, image.getWidth(), image.getWidth());
|
||||||
image.copyData(cropped.getRaster());
|
|
||||||
image = cropped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//check halfTransparency
|
//check halfTransparency
|
||||||
|
@ -33,9 +33,7 @@
|
|||||||
import java.nio.file.InvalidPathException;
|
import java.nio.file.InvalidPathException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.GregorianCalendar;
|
import java.util.GregorianCalendar;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -50,16 +48,18 @@
|
|||||||
import de.bluecolored.bluemap.core.webserver.HttpResponse;
|
import de.bluecolored.bluemap.core.webserver.HttpResponse;
|
||||||
import de.bluecolored.bluemap.core.webserver.HttpStatusCode;
|
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_MIN_SIZE = 10L * 1024L;
|
||||||
private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L;
|
private static final long DEFLATE_MAX_SIZE = 10L * 1024L * 1024L;
|
||||||
private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L;
|
private static final long INFLATE_MAX_SIZE = 10L * 1024L * 1024L;
|
||||||
|
|
||||||
private Path webRoot;
|
private Path webRoot;
|
||||||
|
private String serverName;
|
||||||
|
|
||||||
public BlueMapWebRequestHandler(Path webRoot) {
|
public FileRequestHandler(Path webRoot, String serverName) {
|
||||||
this.webRoot = webRoot;
|
this.webRoot = webRoot;
|
||||||
|
this.serverName = serverName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -70,11 +70,11 @@ public HttpResponse handle(HttpRequest request) {
|
|||||||
) return new HttpResponse(HttpStatusCode.NOT_IMPLEMENTED);
|
) return new HttpResponse(HttpStatusCode.NOT_IMPLEMENTED);
|
||||||
|
|
||||||
HttpResponse response = generateResponse(request);
|
HttpResponse response = generateResponse(request);
|
||||||
response.addHeader("Server", "BlueMap/WebServer");
|
response.addHeader("Server", this.serverName);
|
||||||
|
|
||||||
HttpStatusCode status = response.getStatusCode();
|
HttpStatusCode status = response.getStatusCode();
|
||||||
if (status.getCode() >= 400){
|
if (status.getCode() >= 400){
|
||||||
response.setData(status.getCode() + " - " + status.getMessage() + "\nBlueMap/Webserver");
|
response.setData(status.getCode() + " - " + status.getMessage() + "\n" + this.serverName);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@ -82,23 +82,7 @@ public HttpResponse handle(HttpRequest request) {
|
|||||||
|
|
||||||
@SuppressWarnings ("resource")
|
@SuppressWarnings ("resource")
|
||||||
private HttpResponse generateResponse(HttpRequest request) {
|
private HttpResponse generateResponse(HttpRequest request) {
|
||||||
String adress = request.getPath();
|
String path = 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);
|
|
||||||
|
|
||||||
Path filePath = webRoot;
|
Path filePath = webRoot;
|
||||||
try {
|
try {
|
@ -136,6 +136,7 @@ public void setFrom(TileRenderer tileRenderer, String mapId) {
|
|||||||
public void setFrom(World world, String mapId) {
|
public void setFrom(World world, String mapId) {
|
||||||
set(world.getSpawnPoint().getX(), "maps", mapId, "startPos", "x");
|
set(world.getSpawnPoint().getX(), "maps", mapId, "startPos", "x");
|
||||||
set(world.getSpawnPoint().getZ(), "maps", mapId, "startPos", "z");
|
set(world.getSpawnPoint().getZ(), "maps", mapId, "startPos", "z");
|
||||||
|
set(world.getUUID().toString(), "maps", mapId, "world");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFrom(MapConfig mapConfig, String mapId) {
|
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 static final Pattern REQUEST_PATTERN = Pattern.compile("^(\\w+) (\\S+) (.+)$");
|
||||||
|
|
||||||
private String method;
|
private String method;
|
||||||
private String path;
|
private String adress;
|
||||||
private String version;
|
private String version;
|
||||||
private Map<String, Set<String>> header;
|
private Map<String, Set<String>> header;
|
||||||
private Map<String, Set<String>> headerLC;
|
private Map<String, Set<String>> headerLC;
|
||||||
private byte[] data;
|
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.method = method;
|
||||||
this.path = path;
|
this.adress = adress;
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.header = header;
|
this.header = header;
|
||||||
this.headerLC = new HashMap<>();
|
this.headerLC = new HashMap<>();
|
||||||
@ -79,8 +82,8 @@ public String getMethod() {
|
|||||||
return method;
|
return method;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPath(){
|
public String getAdress(){
|
||||||
return path;
|
return adress;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
@ -92,7 +95,7 @@ public Map<String, Set<String>> getHeader() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, Set<String>> getLowercaseHeader() {
|
public Map<String, Set<String>> getLowercaseHeader() {
|
||||||
return header;
|
return headerLC;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> getHeader(String key){
|
public Set<String> getHeader(String key){
|
||||||
@ -107,6 +110,40 @@ public Set<String> getLowercaseHeader(String key){
|
|||||||
return headerValues;
|
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(){
|
public InputStream getData(){
|
||||||
return new ByteArrayInputStream(data);
|
return new ByteArrayInputStream(data);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
*/
|
*/
|
||||||
package de.bluecolored.bluemap.core.webserver;
|
package de.bluecolored.bluemap.core.webserver;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
public interface HttpRequestHandler {
|
public interface HttpRequestHandler {
|
||||||
|
|
||||||
HttpResponse handle(HttpRequest request);
|
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";
|
import {cachePreventionNr, getCookie, setCookie} from "./utils";
|
||||||
|
|
||||||
export default class BlueMap {
|
export default class BlueMap {
|
||||||
constructor(element, dataRoot) {
|
constructor(element, dataRoot = "data/", liveApiRoot = "live/") {
|
||||||
this.element = $('<div class="bluemap-container"></div>').appendTo(element)[0];
|
this.element = $('<div class="bluemap-container"></div>').appendTo(element)[0];
|
||||||
this.dataRoot = dataRoot;
|
this.dataRoot = dataRoot;
|
||||||
|
this.liveApiRoot = liveApiRoot;
|
||||||
this.locationHash = '';
|
this.locationHash = '';
|
||||||
this.cacheSuffix = '';
|
this.cacheSuffix = '';
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import $ from "jquery";
|
|||||||
import ToggleButton from "../ui/ToggleButton";
|
import ToggleButton from "../ui/ToggleButton";
|
||||||
import Label from "../ui/Label";
|
import Label from "../ui/Label";
|
||||||
import {cachePreventionNr} from "../utils";
|
import {cachePreventionNr} from "../utils";
|
||||||
|
import PlayerMarkerSet from "./PlayerMarkerSet";
|
||||||
|
|
||||||
export default class MarkerManager {
|
export default class MarkerManager {
|
||||||
|
|
||||||
@ -10,14 +11,23 @@ export default class MarkerManager {
|
|||||||
this.blueMap = blueMap;
|
this.blueMap = blueMap;
|
||||||
this.ui = ui;
|
this.ui = ui;
|
||||||
|
|
||||||
|
this.markerData = null;
|
||||||
|
this.liveData = null;
|
||||||
this.markerSets = [];
|
this.markerSets = [];
|
||||||
|
|
||||||
|
this.playerMarkerSet = null;
|
||||||
|
|
||||||
this.readyPromise =
|
this.readyPromise =
|
||||||
|
Promise.all([
|
||||||
this.loadMarkerData()
|
this.loadMarkerData()
|
||||||
.catch(ignore => {
|
.catch(ignore => {
|
||||||
if (this.blueMap.debugInfo) console.debug("Failed load markers:", ignore);
|
if (this.blueMap.debugInfo) console.debug("Failed load markers:", ignore);
|
||||||
})
|
}),
|
||||||
.then(this.loadMarkers);
|
this.checkLiveAPI()
|
||||||
|
.then(this.initializePlayerMarkers)
|
||||||
|
])
|
||||||
|
.then(this.loadMarkers)
|
||||||
|
.then(this.updatePlayerMarkerLoop);
|
||||||
|
|
||||||
$(document).on('bluemap-map-change', this.onBlueMapMapChange);
|
$(document).on('bluemap-map-change', this.onBlueMapMapChange);
|
||||||
}
|
}
|
||||||
@ -41,6 +51,25 @@ export default class MarkerManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkLiveAPI() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.blueMap.fileLoader.load(this.blueMap.liveApiRoot + 'players?' + cachePreventionNr(),
|
||||||
|
liveData => {
|
||||||
|
try {
|
||||||
|
this.liveData = JSON.parse(liveData);
|
||||||
|
resolve();
|
||||||
|
} catch (e){
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xhr => {},
|
||||||
|
error => {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
loadMarkers = () => {
|
loadMarkers = () => {
|
||||||
if (this.markerData && this.markerData.markerSets) {
|
if (this.markerData && this.markerData.markerSets) {
|
||||||
this.markerData.markerSets.forEach(setData => {
|
this.markerData.markerSets.forEach(setData => {
|
||||||
@ -49,12 +78,27 @@ export default class MarkerManager {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
initializePlayerMarkers = () => {
|
||||||
|
if (this.liveData){
|
||||||
|
this.playerMarkerSet = new PlayerMarkerSet(this.blueMap);
|
||||||
|
this.markerSets.push(this.playerMarkerSet);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
update(){
|
update(){
|
||||||
this.markerSets.forEach(markerSet => {
|
this.markerSets.forEach(markerSet => {
|
||||||
markerSet.update();
|
markerSet.update();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updatePlayerMarkerLoop = () => {
|
||||||
|
if (this.playerMarkerSet){
|
||||||
|
this.playerMarkerSet.updateLive();
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(this.updatePlayerMarkerLoop, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
addMenuElements(menu){
|
addMenuElements(menu){
|
||||||
let addedLabel = false;
|
let addedLabel = false;
|
||||||
this.markerSets.forEach(markerSet => {
|
this.markerSets.forEach(markerSet => {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import $ from 'jquery';
|
import $ from 'jquery';
|
||||||
import Marker from "./Marker";
|
import Marker from "./Marker";
|
||||||
import {CSS2DObject} from "./CSS2DRenderer";
|
import {CSS2DObject} from "./CSS2DRenderer";
|
||||||
import {Vector3} from "three";
|
|
||||||
|
|
||||||
import POI from "../../../assets/poi.svg";
|
import POI from "../../../assets/poi.svg";
|
||||||
|
|
||||||
|
90
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarker.js
Normal file
90
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarker.js
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import $ from 'jquery';
|
||||||
|
import Marker from "./Marker";
|
||||||
|
import {CSS2DObject} from "./CSS2DRenderer";
|
||||||
|
|
||||||
|
export default class PlayerMarker extends Marker {
|
||||||
|
|
||||||
|
constructor(blueMap, markerSet, markerData, playerUuid, worldUuid) {
|
||||||
|
super(blueMap, markerSet, markerData);
|
||||||
|
|
||||||
|
this.online = false;
|
||||||
|
this.player = playerUuid;
|
||||||
|
this.world = worldUuid;
|
||||||
|
|
||||||
|
this.animationRunning = false;
|
||||||
|
this.lastFrame = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
setVisible(visible){
|
||||||
|
this.visible = visible && this.online && this.world === this.blueMap.settings.maps[this.blueMap.map].world;
|
||||||
|
|
||||||
|
this.blueMap.updateFrame = true;
|
||||||
|
|
||||||
|
if (!this.renderObject){
|
||||||
|
let iconElement = $(`<div class="marker-player"><img src="assets/playerheads/${this.player}.png" onerror="this.onerror=null;this.src='assets/playerheads/steve.png';"><div class="nameplate">${this.label}</div></div>`);
|
||||||
|
iconElement.find("img").click(this.onClick);
|
||||||
|
|
||||||
|
this.renderObject = new CSS2DObject(iconElement[0]);
|
||||||
|
this.renderObject.position.copy(this.position);
|
||||||
|
this.renderObject.onBeforeRender = (renderer, scene, camera) => {
|
||||||
|
let distanceSquared = this.position.distanceToSquared(camera.position);
|
||||||
|
if (distanceSquared > 1000000) {
|
||||||
|
iconElement.addClass("distant");
|
||||||
|
} else {
|
||||||
|
iconElement.removeClass("distant");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateRenderObject(this.renderObject, scene, camera);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.visible) {
|
||||||
|
this.blueMap.hudScene.add(this.renderObject);
|
||||||
|
} else {
|
||||||
|
this.blueMap.hudScene.remove(this.renderObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updatePosition = () => {
|
||||||
|
if (this.renderObject && !this.renderObject.position.equals(this.position)) {
|
||||||
|
if (this.visible) {
|
||||||
|
if (!this.animationRunning) {
|
||||||
|
this.animationRunning = true;
|
||||||
|
requestAnimationFrame(this.moveAnimation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.renderObject.position.copy(this.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
moveAnimation = (time) => {
|
||||||
|
let delta = time - this.lastFrame;
|
||||||
|
if (this.lastFrame === -1){
|
||||||
|
delta = 20;
|
||||||
|
}
|
||||||
|
this.lastFrame = time;
|
||||||
|
|
||||||
|
if (this.renderObject && !this.renderObject.position.equals(this.position)) {
|
||||||
|
this.renderObject.position.x += (this.position.x - this.renderObject.position.x) * 0.01 * delta;
|
||||||
|
this.renderObject.position.y += (this.position.y - this.renderObject.position.y) * 0.01 * delta;
|
||||||
|
this.renderObject.position.z += (this.position.z - this.renderObject.position.z) * 0.01 * delta;
|
||||||
|
|
||||||
|
if (this.renderObject.position.distanceToSquared(this.position) < 0.001) {
|
||||||
|
this.renderObject.position.copy(this.position);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.blueMap.updateFrame = true;
|
||||||
|
|
||||||
|
requestAnimationFrame(this.moveAnimation);
|
||||||
|
} else {
|
||||||
|
this.animationRunning = false;
|
||||||
|
this.lastFrame = -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onClick = () => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
85
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarkerSet.js
Normal file
85
BlueMapCore/src/main/webroot/js/libs/hud/PlayerMarkerSet.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import POIMarker from "./POIMarker";
|
||||||
|
import ShapeMarker from "./ShapeMarker";
|
||||||
|
import {cachePreventionNr} from "../utils";
|
||||||
|
import PlayerMarker from "./PlayerMarker";
|
||||||
|
import {Vector3} from "three";
|
||||||
|
|
||||||
|
export default class PlayerMarkerSet {
|
||||||
|
|
||||||
|
constructor(blueMap) {
|
||||||
|
this.blueMap = blueMap;
|
||||||
|
this.id = "bluemap-live-players";
|
||||||
|
this.label = "players";
|
||||||
|
this.toggleable = true;
|
||||||
|
this.defaultHide = false;
|
||||||
|
this.marker = [];
|
||||||
|
this.markerMap = {};
|
||||||
|
|
||||||
|
this.visible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
update() {
|
||||||
|
this.marker.forEach(marker => {
|
||||||
|
marker.setVisible(this.visible);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateLive(){
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
this.blueMap.fileLoader.load(this.blueMap.liveApiRoot + 'players?' + cachePreventionNr(),
|
||||||
|
liveData => {
|
||||||
|
try {
|
||||||
|
liveData = JSON.parse(liveData);
|
||||||
|
resolve(liveData);
|
||||||
|
} catch (e){
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
xhr => {},
|
||||||
|
error => {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}).then((liveData) => {
|
||||||
|
this.updateWith(liveData)
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error("Failed to update player-markers!", e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateWith(liveData){
|
||||||
|
this.marker.forEach(marker => {
|
||||||
|
marker.nowOnline = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let i = 0; i < liveData.players.length; i++){
|
||||||
|
let player = liveData.players[i];
|
||||||
|
let marker = this.markerMap[player.uuid];
|
||||||
|
|
||||||
|
if (!marker){
|
||||||
|
marker = new PlayerMarker(this.blueMap, this, {
|
||||||
|
type: "playermarker",
|
||||||
|
map: null,
|
||||||
|
position: player.position,
|
||||||
|
label: player.name,
|
||||||
|
link: null,
|
||||||
|
newTab: false
|
||||||
|
}, player.uuid, player.world);
|
||||||
|
|
||||||
|
this.markerMap[player.uuid] = marker;
|
||||||
|
this.marker.push(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
marker.nowOnline = true;
|
||||||
|
marker.position = new Vector3(player.position.x, player.position.y + 1.5, player.position.z);
|
||||||
|
marker.updatePosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.marker.forEach(marker => {
|
||||||
|
if (marker.nowOnline !== marker.online){
|
||||||
|
marker.online = marker.nowOnline;
|
||||||
|
marker.setVisible(this.visible);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -76,7 +76,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.bluemap-container .marker-poi {
|
.bluemap-container .marker-poi, .bluemap-container .marker-player {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
||||||
> * {
|
> * {
|
||||||
@ -87,3 +87,38 @@
|
|||||||
filter: drop-shadow(1px 1px 3px #0008);
|
filter: drop-shadow(1px 1px 3px #0008);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bluemap-container .marker-player {
|
||||||
|
img {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
|
||||||
|
image-rendering: pixelated;
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameplate {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-50%, -110%);
|
||||||
|
background: rgba(50, 50, 50, 0.75);
|
||||||
|
padding: 0.2em 0.3em;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.distant {
|
||||||
|
img {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nameplate {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -35,10 +35,14 @@
|
|||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
|
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 de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
|
||||||
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
|
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
|
||||||
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
|
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
|
||||||
import net.minecraft.entity.player.PlayerEntity;
|
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.server.world.ServerWorld;
|
||||||
import net.minecraft.util.ActionResult;
|
import net.minecraft.util.ActionResult;
|
||||||
import net.minecraft.util.Hand;
|
import net.minecraft.util.Hand;
|
||||||
@ -60,13 +64,16 @@ public FabricEventForwarder(FabricMod mod) {
|
|||||||
ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize);
|
ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize);
|
||||||
AttackBlockCallback.EVENT.register(this::onBlockAttack);
|
AttackBlockCallback.EVENT.register(this::onBlockAttack);
|
||||||
UseBlockCallback.EVENT.register(this::onBlockUse);
|
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);
|
this.eventListeners.add(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeAllListeners() {
|
public synchronized void removeAllListeners() {
|
||||||
this.eventListeners.clear();
|
this.eventListeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,33 +94,47 @@ public ActionResult onBlockAttack(PlayerEntity player, World world, Hand hand, B
|
|||||||
return ActionResult.PASS;
|
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());
|
Vector3i position = new Vector3i(blockPos.getX(), blockPos.getY(), blockPos.getZ());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
UUID uuid = mod.getUUIDForWorld(world);
|
UUID uuid = mod.getUUIDForWorld(world);
|
||||||
eventListeners.forEach(e -> e.onBlockChange(uuid, position));
|
for (ServerEventListener listener : eventListeners) listener.onBlockChange(uuid, position);
|
||||||
} catch (IOException e) {
|
} 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 {
|
try {
|
||||||
UUID uuid = mod.getUUIDForWorld(world);
|
UUID uuid = mod.getUUIDForWorld(world);
|
||||||
eventListeners.forEach(e -> e.onWorldSaveToDisk(uuid));
|
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid);
|
||||||
} catch (IOException e) {
|
} 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 {
|
try {
|
||||||
UUID uuid = mod.getUUIDForWorld(world);
|
UUID uuid = mod.getUUIDForWorld(world);
|
||||||
eventListeners.forEach(e -> e.onChunkFinishedGeneration(uuid, chunkPos));
|
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos);
|
||||||
} catch (IOException e) {
|
} 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.File;
|
||||||
import java.io.IOException;
|
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.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
@ -39,32 +44,45 @@
|
|||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
import de.bluecolored.bluemap.common.plugin.commands.Commands;
|
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.ServerEventListener;
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
|
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.api.ModInitializer;
|
||||||
import net.fabricmc.fabric.api.event.server.ServerStartCallback;
|
import net.fabricmc.fabric.api.event.server.ServerStartCallback;
|
||||||
import net.fabricmc.fabric.api.event.server.ServerStopCallback;
|
import net.fabricmc.fabric.api.event.server.ServerStopCallback;
|
||||||
|
import net.fabricmc.fabric.api.event.server.ServerTickCallback;
|
||||||
import net.fabricmc.fabric.api.registry.CommandRegistry;
|
import net.fabricmc.fabric.api.registry.CommandRegistry;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
|
import net.minecraft.server.network.ServerPlayerEntity;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.server.world.ServerWorld;
|
||||||
|
|
||||||
public class FabricMod implements ModInitializer, ServerInterface {
|
public class FabricMod implements ModInitializer, ServerInterface {
|
||||||
|
|
||||||
private Plugin pluginInstance = null;
|
private Plugin pluginInstance = null;
|
||||||
|
private MinecraftServer serverInstance = null;
|
||||||
|
|
||||||
private Map<File, UUID> worldUuids;
|
private Map<File, UUID> worldUUIDs;
|
||||||
private FabricEventForwarder eventForwarder;
|
private FabricEventForwarder eventForwarder;
|
||||||
|
|
||||||
private LoadingCache<ServerWorld, UUID> worldUuidCache;
|
private LoadingCache<ServerWorld, UUID> worldUuidCache;
|
||||||
|
|
||||||
|
private int playerUpdateIndex = 0;
|
||||||
|
private Map<UUID, Player> onlinePlayerMap;
|
||||||
|
private List<FabricPlayer> onlinePlayerList;
|
||||||
|
|
||||||
public FabricMod() {
|
public FabricMod() {
|
||||||
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
|
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
|
||||||
|
|
||||||
|
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||||
|
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
pluginInstance = new Plugin("fabric", this);
|
pluginInstance = new Plugin("fabric", this);
|
||||||
|
|
||||||
this.worldUuids = new ConcurrentHashMap<>();
|
this.worldUUIDs = new ConcurrentHashMap<>();
|
||||||
this.eventForwarder = new FabricEventForwarder(this);
|
this.eventForwarder = new FabricEventForwarder(this);
|
||||||
this.worldUuidCache = CacheBuilder.newBuilder()
|
this.worldUuidCache = CacheBuilder.newBuilder()
|
||||||
.weakKeys()
|
.weakKeys()
|
||||||
@ -86,12 +104,14 @@ public void onInitialize() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ServerStartCallback.EVENT.register((MinecraftServer server) -> {
|
ServerStartCallback.EVENT.register((MinecraftServer server) -> {
|
||||||
|
this.serverInstance = server;
|
||||||
|
|
||||||
new Thread(()->{
|
new Thread(()->{
|
||||||
Logger.global.logInfo("Loading BlueMap...");
|
Logger.global.logInfo("Loading BlueMap...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
pluginInstance.load();
|
pluginInstance.load();
|
||||||
Logger.global.logInfo("BlueMap loaded!");
|
if (pluginInstance.isLoaded()) Logger.global.logInfo("BlueMap loaded!");
|
||||||
} catch (IOException | ParseResourceException e) {
|
} catch (IOException | ParseResourceException e) {
|
||||||
Logger.global.logError("Failed to load bluemap!", e);
|
Logger.global.logError("Failed to load bluemap!", e);
|
||||||
}
|
}
|
||||||
@ -102,6 +122,13 @@ public void onInitialize() {
|
|||||||
pluginInstance.unload();
|
pluginInstance.unload();
|
||||||
Logger.global.logInfo("BlueMap unloaded!");
|
Logger.global.logInfo("BlueMap unloaded!");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
PlayerJoinCallback.EVENT.register(this::onPlayerJoin);
|
||||||
|
PlayerLeaveCallback.EVENT.register(this::onPlayerLeave);
|
||||||
|
|
||||||
|
ServerTickCallback.EVENT.register((MinecraftServer server) -> {
|
||||||
|
if (server == this.serverInstance) this.updateSomePlayers();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -118,10 +145,10 @@ public void unregisterAllListeners() {
|
|||||||
public UUID getUUIDForWorld(File worldFolder) throws IOException {
|
public UUID getUUIDForWorld(File worldFolder) throws IOException {
|
||||||
worldFolder = worldFolder.getCanonicalFile();
|
worldFolder = worldFolder.getCanonicalFile();
|
||||||
|
|
||||||
UUID uuid = worldUuids.get(worldFolder);
|
UUID uuid = worldUUIDs.get(worldFolder);
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
uuid = UUID.randomUUID();
|
uuid = UUID.randomUUID();
|
||||||
worldUuids.put(worldFolder, uuid);
|
worldUUIDs.put(worldFolder, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uuid;
|
return uuid;
|
||||||
@ -147,4 +174,57 @@ public File getConfigFolder() {
|
|||||||
return new File("config/bluemap");
|
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
|
port: 8100
|
||||||
maxConnectionCount: 100
|
maxConnectionCount: 100
|
||||||
}
|
}
|
||||||
|
liveUpdates {
|
||||||
|
enabled: true
|
||||||
|
hiddenGameModes: []
|
||||||
|
hideInvisible: true
|
||||||
|
hideSneaking: false
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@ webroot: "bluemap/web"
|
|||||||
#webdata: "path/to/data/folder"
|
#webdata: "path/to/data/folder"
|
||||||
|
|
||||||
# If the web-application should use cookies to save the configurations of a user.
|
# If the web-application should use cookies to save the configurations of a user.
|
||||||
|
# Default is true
|
||||||
useCookies: true
|
useCookies: true
|
||||||
|
|
||||||
webserver {
|
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": [],
|
"mixins": [],
|
||||||
"client": [],
|
"client": [],
|
||||||
"server": [
|
"server": [
|
||||||
"MixinServerWorld",
|
"MixinChunkGenerator",
|
||||||
"MixinChunkGenerator"
|
"MixinPlayerManager",
|
||||||
|
"MixinServerWorld"
|
||||||
],
|
],
|
||||||
"injectors": {
|
"injectors": {
|
||||||
"defaultRequire": 1
|
"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,28 +28,35 @@
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
|
||||||
import com.flowpowered.math.vector.Vector3i;
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
import com.google.common.cache.CacheLoader;
|
import com.google.common.cache.CacheLoader;
|
||||||
import com.google.common.cache.LoadingCache;
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
import de.bluecolored.bluemap.common.plugin.Plugin;
|
||||||
import de.bluecolored.bluemap.common.plugin.commands.Commands;
|
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.ServerEventListener;
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
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.server.ServerWorld;
|
import net.minecraft.world.server.ServerWorld;
|
||||||
import net.minecraftforge.common.MinecraftForge;
|
import net.minecraftforge.common.MinecraftForge;
|
||||||
import net.minecraftforge.event.world.BlockEvent;
|
import net.minecraftforge.event.TickEvent.ServerTickEvent;
|
||||||
import net.minecraftforge.event.world.WorldEvent;
|
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.eventbus.api.SubscribeEvent;
|
||||||
import net.minecraftforge.fml.common.Mod;
|
import net.minecraftforge.fml.common.Mod;
|
||||||
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
|
import net.minecraftforge.fml.event.server.FMLServerStartingEvent;
|
||||||
@ -58,19 +65,28 @@
|
|||||||
@Mod(Plugin.PLUGIN_ID)
|
@Mod(Plugin.PLUGIN_ID)
|
||||||
public class ForgeMod implements ServerInterface {
|
public class ForgeMod implements ServerInterface {
|
||||||
|
|
||||||
private Plugin bluemap;
|
private Plugin pluginInstance = null;
|
||||||
private Commands<CommandSource> commands;
|
private MinecraftServer serverInstance = null;
|
||||||
private Map<String, UUID> worldUUIDs;
|
|
||||||
private Collection<ServerEventListener> eventListeners;
|
private Map<File, UUID> worldUUIDs;
|
||||||
|
private ForgeEventForwarder eventForwarder;
|
||||||
|
|
||||||
private LoadingCache<ServerWorld, UUID> worldUuidCache;
|
private LoadingCache<ServerWorld, UUID> worldUuidCache;
|
||||||
|
|
||||||
|
private int playerUpdateIndex = 0;
|
||||||
|
private Map<UUID, Player> onlinePlayerMap;
|
||||||
|
private List<ForgePlayer> onlinePlayerList;
|
||||||
|
|
||||||
public ForgeMod() {
|
public ForgeMod() {
|
||||||
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
|
Logger.global = new Log4jLogger(LogManager.getLogger(Plugin.PLUGIN_NAME));
|
||||||
|
|
||||||
this.bluemap = new Plugin("forge", this);
|
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||||
this.worldUUIDs = new HashMap<>();
|
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||||
this.eventListeners = new ArrayList<>(1);
|
|
||||||
|
this.pluginInstance = new Plugin("forge", this);
|
||||||
|
|
||||||
|
this.worldUUIDs = new ConcurrentHashMap<>();
|
||||||
|
this.eventForwarder = new ForgeEventForwarder(this);
|
||||||
this.worldUuidCache = CacheBuilder.newBuilder()
|
this.worldUuidCache = CacheBuilder.newBuilder()
|
||||||
.weakKeys()
|
.weakKeys()
|
||||||
.maximumSize(1000)
|
.maximumSize(1000)
|
||||||
@ -88,106 +104,54 @@ public UUID load(ServerWorld key) throws Exception {
|
|||||||
public void onServerStarting(FMLServerStartingEvent event) {
|
public void onServerStarting(FMLServerStartingEvent event) {
|
||||||
this.worldUUIDs.clear();
|
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
|
//register commands
|
||||||
this.commands = new Commands<>(bluemap, event.getCommandDispatcher(), forgeSource -> new ForgeCommandSource(this, bluemap, forgeSource));
|
new Commands<>(pluginInstance, event.getCommandDispatcher(), forgeSource -> new ForgeCommandSource(this, pluginInstance, forgeSource));
|
||||||
|
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
try {
|
|
||||||
Logger.global.logInfo("Loading...");
|
Logger.global.logInfo("Loading...");
|
||||||
bluemap.load();
|
|
||||||
if (bluemap.isLoaded()) Logger.global.logInfo("Loaded!");
|
try {
|
||||||
} catch (Throwable t) {
|
pluginInstance.load();
|
||||||
Logger.global.logError("Failed to load!", t);
|
if (pluginInstance.isLoaded()) Logger.global.logInfo("Loaded!");
|
||||||
|
} catch (IOException | ParseResourceException e) {
|
||||||
|
Logger.global.logError("Failed to load bluemap!", e);
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerWorld(ServerWorld world) throws IOException {
|
@SubscribeEvent
|
||||||
getUUIDForWorld(world);
|
public void onServerStopping(FMLServerStoppingEvent event) {
|
||||||
|
pluginInstance.unload();
|
||||||
|
Logger.global.logInfo("BlueMap unloaded!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscribeEvent
|
@SubscribeEvent
|
||||||
public void onServerStopping(FMLServerStoppingEvent event) {
|
public void onTick(ServerTickEvent evt) {
|
||||||
Logger.global.logInfo("Stopping...");
|
updateSomePlayers();
|
||||||
bluemap.unload();
|
|
||||||
Logger.global.logInfo("Saved and stopped!");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void registerListener(ServerEventListener listener) {
|
public void registerListener(ServerEventListener listener) {
|
||||||
eventListeners.add(listener);
|
eventForwarder.addEventListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unregisterAllListeners() {
|
public void unregisterAllListeners() {
|
||||||
eventListeners.clear();
|
eventForwarder.removeAllListeners();
|
||||||
}
|
|
||||||
|
|
||||||
@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) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public UUID getUUIDForWorld(File worldFolder) throws IOException {
|
public UUID getUUIDForWorld(File worldFolder) throws IOException {
|
||||||
synchronized (worldUUIDs) {
|
worldFolder = worldFolder.getCanonicalFile();
|
||||||
String key = worldFolder.getCanonicalPath();
|
|
||||||
|
|
||||||
UUID uuid = worldUUIDs.get(key);
|
UUID uuid = worldUUIDs.get(worldFolder);
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
throw new IOException("There is no world with this folder loaded: " + worldFolder.getPath());
|
uuid = UUID.randomUUID();
|
||||||
|
worldUUIDs.put(worldFolder, uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getUUIDForWorld(ServerWorld world) throws IOException {
|
public UUID getUUIDForWorld(ServerWorld world) throws IOException {
|
||||||
try {
|
try {
|
||||||
@ -200,8 +164,7 @@ public UUID getUUIDForWorld(ServerWorld world) throws IOException {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private UUID loadUUIDForWorld(ServerWorld world) throws IOException {
|
private UUID loadUUIDForWorld(ServerWorld world) throws IOException {
|
||||||
synchronized (worldUUIDs) {
|
File key = getFolderForWorld(world);
|
||||||
String key = getFolderForWorld(world).getPath();
|
|
||||||
|
|
||||||
UUID uuid = worldUUIDs.get(key);
|
UUID uuid = worldUUIDs.get(key);
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
@ -211,7 +174,6 @@ private UUID loadUUIDForWorld(ServerWorld world) throws IOException {
|
|||||||
|
|
||||||
return uuid;
|
return uuid;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private File getFolderForWorld(ServerWorld world) throws IOException {
|
private File getFolderForWorld(ServerWorld world) throws IOException {
|
||||||
File worldFolder = world.getSaveHandler().getWorldDirectory();
|
File worldFolder = world.getSaveHandler().getWorldDirectory();
|
||||||
@ -229,8 +191,59 @@ public File getConfigFolder() {
|
|||||||
return new File("config/bluemap");
|
return new File("config/bluemap");
|
||||||
}
|
}
|
||||||
|
|
||||||
public Commands<CommandSource> getCommands() {
|
public void onPlayerJoin(PlayerLoggedInEvent evt) {
|
||||||
return commands;
|
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 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.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.util.math.Vec3d;
|
||||||
|
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;
|
||||||
|
|
||||||
|
Vec3d 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
|
port: 8100
|
||||||
maxConnectionCount: 100
|
maxConnectionCount: 100
|
||||||
}
|
}
|
||||||
|
liveUpdates {
|
||||||
|
enabled: true
|
||||||
|
hiddenGameModes: []
|
||||||
|
hideInvisible: true
|
||||||
|
hideSneaking: false
|
||||||
|
}
|
||||||
|
@ -38,6 +38,7 @@ webroot: "bluemap/web"
|
|||||||
#webdata: "path/to/data/folder"
|
#webdata: "path/to/data/folder"
|
||||||
|
|
||||||
# If the web-application should use cookies to save the configurations of a user.
|
# If the web-application should use cookies to save the configurations of a user.
|
||||||
|
# Default is true
|
||||||
useCookies: true
|
useCookies: true
|
||||||
|
|
||||||
webserver {
|
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.Order;
|
||||||
import org.spongepowered.api.event.block.ChangeBlockEvent;
|
import org.spongepowered.api.event.block.ChangeBlockEvent;
|
||||||
import org.spongepowered.api.event.filter.type.Exclude;
|
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.SaveWorldEvent;
|
||||||
import org.spongepowered.api.event.world.chunk.PopulateChunkEvent;
|
import org.spongepowered.api.event.world.chunk.PopulateChunkEvent;
|
||||||
import org.spongepowered.api.world.Location;
|
import org.spongepowered.api.world.Location;
|
||||||
@ -40,6 +42,7 @@
|
|||||||
import com.flowpowered.math.vector.Vector3i;
|
import com.flowpowered.math.vector.Vector3i;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||||
|
import de.bluecolored.bluemap.common.plugin.text.Text;
|
||||||
|
|
||||||
public class EventForwarder {
|
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.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.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
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.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
@ -39,13 +45,16 @@
|
|||||||
import org.spongepowered.api.event.game.GameReloadEvent;
|
import org.spongepowered.api.event.game.GameReloadEvent;
|
||||||
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
|
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
|
||||||
import org.spongepowered.api.event.game.state.GameStoppingEvent;
|
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.plugin.PluginContainer;
|
||||||
import org.spongepowered.api.scheduler.SpongeExecutorService;
|
import org.spongepowered.api.scheduler.SpongeExecutorService;
|
||||||
|
import org.spongepowered.api.scheduler.Task;
|
||||||
import org.spongepowered.api.util.Tristate;
|
import org.spongepowered.api.util.Tristate;
|
||||||
import org.spongepowered.api.world.World;
|
import org.spongepowered.api.world.World;
|
||||||
import org.spongepowered.api.world.storage.WorldProperties;
|
import org.spongepowered.api.world.storage.WorldProperties;
|
||||||
|
|
||||||
import de.bluecolored.bluemap.common.plugin.Plugin;
|
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.ServerEventListener;
|
||||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
|
||||||
import de.bluecolored.bluemap.core.logger.Logger;
|
import de.bluecolored.bluemap.core.logger.Logger;
|
||||||
@ -74,10 +83,17 @@ public class SpongePlugin implements ServerInterface {
|
|||||||
|
|
||||||
private SpongeExecutorService asyncExecutor;
|
private SpongeExecutorService asyncExecutor;
|
||||||
|
|
||||||
|
private int playerUpdateIndex = 0;
|
||||||
|
private Map<UUID, Player> onlinePlayerMap;
|
||||||
|
private List<SpongePlayer> onlinePlayerList;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SpongePlugin(org.slf4j.Logger logger) {
|
public SpongePlugin(org.slf4j.Logger logger) {
|
||||||
Logger.global = new Slf4jLogger(logger);
|
Logger.global = new Slf4jLogger(logger);
|
||||||
|
|
||||||
|
this.onlinePlayerMap = new ConcurrentHashMap<>();
|
||||||
|
this.onlinePlayerList = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
this.bluemap = new Plugin("sponge", this);
|
this.bluemap = new Plugin("sponge", this);
|
||||||
this.commands = new SpongeCommands(bluemap);
|
this.commands = new SpongeCommands(bluemap);
|
||||||
}
|
}
|
||||||
@ -96,6 +112,12 @@ public void onServerStart(GameStartingServerEvent evt) {
|
|||||||
Sponge.getCommandManager().register(this, command, command.getLabel());
|
Sponge.getCommandManager().register(this, command, command.getLabel());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//start updating players
|
||||||
|
Task.builder()
|
||||||
|
.intervalTicks(1)
|
||||||
|
.execute(this::updateSomePlayers)
|
||||||
|
.submit(this);
|
||||||
|
|
||||||
asyncExecutor.execute(() -> {
|
asyncExecutor.execute(() -> {
|
||||||
try {
|
try {
|
||||||
Logger.global.logInfo("Loading...");
|
Logger.global.logInfo("Loading...");
|
||||||
@ -110,6 +132,7 @@ public void onServerStart(GameStartingServerEvent evt) {
|
|||||||
@Listener
|
@Listener
|
||||||
public void onServerStop(GameStoppingEvent evt) {
|
public void onServerStop(GameStoppingEvent evt) {
|
||||||
Logger.global.logInfo("Stopping...");
|
Logger.global.logInfo("Stopping...");
|
||||||
|
Sponge.getScheduler().getScheduledTasks(this).forEach(t -> t.cancel());
|
||||||
bluemap.unload();
|
bluemap.unload();
|
||||||
Logger.global.logInfo("Saved and stopped!");
|
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
|
@Override
|
||||||
public void registerListener(ServerEventListener listener) {
|
public void registerListener(ServerEventListener listener) {
|
||||||
Sponge.getEventManager().registerListeners(this, new EventForwarder(listener));
|
Sponge.getEventManager().registerListeners(this, new EventForwarder(listener));
|
||||||
@ -164,6 +204,16 @@ public File getConfigFolder() {
|
|||||||
return configurationDir.toFile();
|
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
|
@Override
|
||||||
public boolean isMetricsEnabled(boolean configValue) {
|
public boolean isMetricsEnabled(boolean configValue) {
|
||||||
PluginContainer pluginContainer = Sponge.getPluginManager().fromInstance(this).orElse(null);
|
PluginContainer pluginContainer = Sponge.getPluginManager().fromInstance(this).orElse(null);
|
||||||
@ -177,4 +227,25 @@ public boolean isMetricsEnabled(boolean configValue) {
|
|||||||
return Sponge.getMetricsConfigManager().getGlobalCollectionState().asBoolean();
|
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
|
port: 8100
|
||||||
maxConnectionCount: 100
|
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