Implement player live-api on sponge

This commit is contained in:
Blue (Lukas Rieger) 2020-07-31 00:42:10 +02:00
parent 2e1254ddc8
commit 187649a613
13 changed files with 279 additions and 110 deletions

View File

@ -43,7 +43,7 @@ import org.bukkit.command.defaults.BukkitCommand;
import org.bukkit.plugin.java.JavaPlugin;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerInterface;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
@ -184,13 +184,13 @@ public class BukkitPlugin extends JavaPlugin implements ServerInterface {
}
@Override
public Collection<PlayerInterface> getOnlinePlayers() {
public Collection<PlayerState> getOnlinePlayers() {
// TODO Implement
return Collections.emptyList();
}
@Override
public Optional<PlayerInterface> getPlayer(UUID uuid) {
public Optional<PlayerState> getPlayer(UUID uuid) {
// TODO Implement
return Optional.empty();
}

View File

@ -26,6 +26,8 @@ package de.bluecolored.bluemap.common;
import java.io.IOException;
import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.web.FileRequestHandler;
import de.bluecolored.bluemap.core.web.WebFilesManager;
@ -36,6 +38,7 @@ public class BlueMapWebServer extends WebServer {
private WebFilesManager webFilesManager;
public BlueMapWebServer(WebServerConfig config) {
super(
config.getWebserverPort(),
@ -47,6 +50,17 @@ public class BlueMapWebServer extends WebServer {
this.webFilesManager = new WebFilesManager(config.getWebRoot());
}
public BlueMapWebServer(WebServerConfig config, ServerInterface server) {
super(
config.getWebserverPort(),
config.getWebserverMaxConnections(),
config.getWebserverBindAdress(),
new LiveAPIRequestHandler(server, new FileRequestHandler(config.getWebRoot(), "BlueMap/Webserver"))
);
this.webFilesManager = new WebFilesManager(config.getWebRoot());
}
public void updateWebfiles() throws IOException {
if (webFilesManager.needsUpdate()) {
Logger.global.logInfo("Webfiles are missing or outdated, updating...");

View File

@ -1,73 +0,0 @@
/*
* 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 com.google.gson.stream.JsonWriter;
public class Event {
private String type;
private long time;
private long expires;
private String jsonData;
public Event(String type, long time, long expires, String jsonData) {
this.type = type;
this.time = time;
this.expires = expires;
this.jsonData = jsonData;
}
public void serialize(JsonWriter writer) throws IOException {
writer.beginObject();
writer.name("type").value(type);
writer.name("time").value(time);
writer.name("jsonData").jsonValue(jsonData);
writer.endObject();
}
public String getType() {
return type;
}
public long getTime() {
return time;
}
public long getExpireTime() {
return expires;
}
public String getJsonData() {
return jsonData;
}
}

View File

@ -24,9 +24,16 @@
*/
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.Gamemode;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.webserver.HttpRequest;
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
import de.bluecolored.bluemap.core.webserver.HttpResponse;
@ -36,13 +43,15 @@ public class LiveAPIRequestHandler implements HttpRequestHandler {
private HttpRequestHandler notFoundHandler;
private Map<String, HttpRequestHandler> liveAPIRequests;
private ServerInterface server;
public LiveAPIRequestHandler(HttpRequestHandler notFoundHandler) {
public LiveAPIRequestHandler(ServerInterface server, HttpRequestHandler notFoundHandler) {
this.server = server;
this.notFoundHandler = notFoundHandler;
this.liveAPIRequests = new HashMap<>();
this.liveAPIRequests.put("live/events", this::handleEventsRequest);
this.liveAPIRequests.put("live/players", this::handlePlayersRequest);
}
@Override
@ -53,10 +62,44 @@ public class LiveAPIRequestHandler implements HttpRequestHandler {
return this.notFoundHandler.handle(request);
}
public HttpResponse handleEventsRequest(HttpRequest request) {
public HttpResponse handlePlayersRequest(HttpRequest request) {
if (!request.getMethod().equalsIgnoreCase("GET")) return new HttpResponse(HttpStatusCode.BAD_REQUEST);
return new HttpResponse(HttpStatusCode.NOT_IMPLEMENTED);
try (
StringWriter data = new StringWriter();
JsonWriter json = new JsonWriter(data);
){
json.beginObject();
json.name("players").beginArray();
for (PlayerState player : server.getOnlinePlayers()) {
if (player.isInvisible()) continue;
if (player.isSneaking()) continue;
if (player.getGamemode() == Gamemode.SPECTATOR) 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);
}
}
}

View File

@ -34,7 +34,6 @@ import com.google.common.collect.MultimapBuilder;
import de.bluecolored.bluemap.common.MapType;
import de.bluecolored.bluemap.common.RenderManager;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerInterface;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.text.Text;
@ -142,11 +141,6 @@ public class MapUpdateHandler implements ServerEventListener {
@Override
public void onPlayerLeave(UUID playerUuid) {
}
@Override
public void onPlayerMove(UUID playerUuid) {
}
@Override

View File

@ -297,7 +297,7 @@ public class Plugin {
//start webserver
if (config.isWebserverEnabled()) {
webServer = new BlueMapWebServer(config);
webServer = new BlueMapWebServer(config, serverInterface);
webServer.updateWebfiles();
webServer.start();
}

View File

@ -26,33 +26,86 @@ package de.bluecolored.bluemap.common.plugin.serverinterface;
import java.util.UUID;
import com.flowpowered.math.vector.Vector3d;
import com.google.common.base.MoreObjects;
import de.bluecolored.bluemap.common.plugin.text.Text;
public interface PlayerInterface extends CommandSource {
public class PlayerState {
UUID getUuid();
private final UUID uuid;
protected Text name;
protected UUID world;
protected Vector3d position = Vector3d.ZERO;
protected boolean online = false;
protected boolean sneaking = false;
protected boolean invisible = false;
protected Gamemode gamemode = Gamemode.SURVIVAL;
Text getName();
public PlayerState(UUID uuid, Text name, UUID world, Vector3d position) {
this.uuid = uuid;
this.name = name;
this.world = world;
this.position = position;
}
public UUID getUuid() {
return uuid;
}
public Text getName() {
return name;
}
public UUID getWorld() {
return world;
}
public Vector3d getPosition() {
return position;
}
boolean isOnline();
public boolean isOnline() {
return online;
}
/**
* 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
*/
boolean isSneaking();
public boolean isSneaking() {
return sneaking;
}
/**
* 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>
*/
boolean isInvisible();
public boolean isInvisible() {
return invisible;
}
/**
* Returns the {@link Gamemode} this player is in
* <p><i>If the player is offline the value of this method is undetermined.</i></p>
*/
Gamemode getGamemode();
public Gamemode getGamemode() {
return gamemode;
}
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
.add("uuid", uuid)
//.add("name", name)
//.add("world", world)
//.add("position", position)
//.add("online", online)
//.add("sneaking", sneaking)
//.add("invisible", invisible)
//.add("gamemode", gamemode)
.toString();
}
}

View File

@ -43,8 +43,6 @@ public interface ServerEventListener {
void onPlayerLeave(UUID playerUuid);
void onPlayerMove(UUID playerUuid);
void onChatMessage(Text message);
}

View File

@ -78,13 +78,13 @@ public interface ServerInterface {
/**
* Returns a collection of players that are currently online
*/
Collection<PlayerInterface> getOnlinePlayers();
Collection<PlayerState> getOnlinePlayers();
/**
* Returns the player with that UUID if present<br>
* this method is only guaranteed to return a player if the player is currently online.
*/
Optional<PlayerInterface> getPlayer(UUID uuid);
Optional<PlayerState> getPlayer(UUID uuid);
}

View File

@ -40,7 +40,7 @@ import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.commands.Commands;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerInterface;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
@ -212,13 +212,13 @@ public class ForgeMod implements ServerInterface {
}
@Override
public Collection<PlayerInterface> getOnlinePlayers() {
public Collection<PlayerState> getOnlinePlayers() {
// TODO Implement
return Collections.emptyList();
}
@Override
public Optional<PlayerInterface> getPlayer(UUID uuid) {
public Optional<PlayerState> getPlayer(UUID uuid) {
// TODO Implement
return Optional.empty();
}

View File

@ -32,6 +32,8 @@ import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.Order;
import org.spongepowered.api.event.block.ChangeBlockEvent;
import org.spongepowered.api.event.filter.type.Exclude;
import org.spongepowered.api.event.message.MessageChannelEvent;
import org.spongepowered.api.event.network.ClientConnectionEvent;
import org.spongepowered.api.event.world.SaveWorldEvent;
import org.spongepowered.api.event.world.chunk.PopulateChunkEvent;
import org.spongepowered.api.world.Location;
@ -40,6 +42,7 @@ import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.text.Text;
public class EventForwarder {
@ -73,4 +76,19 @@ public class EventForwarder {
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()));
}
}

View File

@ -0,0 +1,76 @@
/*
* 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 org.spongepowered.api.data.key.Keys;
import org.spongepowered.api.entity.living.player.Player;
import org.spongepowered.api.entity.living.player.gamemode.GameMode;
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
import de.bluecolored.bluemap.common.plugin.serverinterface.Gamemode;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.text.Text;
public class SpongePlayerState extends PlayerState {
private WeakReference<Player> playerRef;
public SpongePlayerState(Player player) {
super(player.getUniqueId(), Text.of(player.getName()), player.getWorld().getUniqueId(), player.getPosition());
this.playerRef = new WeakReference<Player>(player);
update();
}
void update() {
Player player = playerRef.get();
if (player == null) {
this.online = false;
return;
}
this.online = player.isOnline();
if (this.online) {
this.name = Text.of(player.getName());
this.world = player.getWorld().getUniqueId();
this.position = player.getPosition();
GameMode gm = player.get(Keys.GAME_MODE).orElse(GameModes.NOT_SET);
if (gm == GameModes.SURVIVAL) this.gamemode = Gamemode.SURVIVAL;
else if (gm == GameModes.CREATIVE) this.gamemode = Gamemode.CREATIVE;
else if (gm == GameModes.SPECTATOR) this.gamemode = Gamemode.SPECTATOR;
else if (gm == GameModes.ADVENTURE) this.gamemode = Gamemode.ADVENTURE;
else this.gamemode = Gamemode.SURVIVAL;
this.invisible = player.get(Keys.INVISIBLE).orElse(false);
this.sneaking = player.get(Keys.IS_SNEAKING).orElse(false);
}
}
}

View File

@ -28,9 +28,13 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Inject;
@ -41,6 +45,7 @@ import org.spongepowered.api.event.Listener;
import org.spongepowered.api.event.game.GameReloadEvent;
import org.spongepowered.api.event.game.state.GameStartingServerEvent;
import org.spongepowered.api.event.game.state.GameStoppingEvent;
import org.spongepowered.api.event.network.ClientConnectionEvent;
import org.spongepowered.api.plugin.PluginContainer;
import org.spongepowered.api.scheduler.SpongeExecutorService;
import org.spongepowered.api.util.Tristate;
@ -48,7 +53,7 @@ import org.spongepowered.api.world.World;
import org.spongepowered.api.world.storage.WorldProperties;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerInterface;
import de.bluecolored.bluemap.common.plugin.serverinterface.PlayerState;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.logger.Logger;
@ -74,13 +79,19 @@ public class SpongePlugin implements ServerInterface {
private Plugin bluemap;
private SpongeCommands commands;
private SpongeExecutorService syncExecutor;
private SpongeExecutorService asyncExecutor;
private long lastPlayerUpdate = -1;
private Map<UUID, PlayerState> onlinePlayers;
@Inject
public SpongePlugin(org.slf4j.Logger logger) {
Logger.global = new Slf4jLogger(logger);
this.onlinePlayers = new ConcurrentHashMap<>();
this.bluemap = new Plugin("sponge", this);
this.commands = new SpongeCommands(bluemap);
}
@ -88,6 +99,7 @@ public class SpongePlugin implements ServerInterface {
@Listener
public void onServerStart(GameStartingServerEvent evt) {
asyncExecutor = Sponge.getScheduler().createAsyncExecutor(this);
syncExecutor = Sponge.getScheduler().createSyncExecutor(this);
//save all world properties to generate level_sponge.dat files
for (WorldProperties properties : Sponge.getServer().getAllWorldProperties()) {
@ -130,6 +142,17 @@ public class SpongePlugin implements ServerInterface {
});
}
@Listener
public void onPlayerJoin(ClientConnectionEvent.Join evt) {
onlinePlayers.put(evt.getTargetEntity().getUniqueId(), new SpongePlayerState(evt.getTargetEntity()));
}
@Listener
public void onPlayerLeave(ClientConnectionEvent.Disconnect evt) {
onlinePlayers.remove(evt.getTargetEntity().getUniqueId());
}
@Override
public void registerListener(ServerEventListener listener) {
Sponge.getEventManager().registerListeners(this, new EventForwarder(listener));
@ -167,15 +190,38 @@ public class SpongePlugin implements ServerInterface {
}
@Override
public Collection<PlayerInterface> getOnlinePlayers() {
// TODO Implement
return Collections.emptyList();
public Collection<PlayerState> getOnlinePlayers() {
updatePlayers();
return onlinePlayers.values();
}
@Override
public Optional<PlayerInterface> getPlayer(UUID uuid) {
// TODO Implement
return Optional.empty();
public Optional<PlayerState> getPlayer(UUID uuid) {
updatePlayers();
return Optional.ofNullable(onlinePlayers.get(uuid));
}
private synchronized void updatePlayers() {
if (lastPlayerUpdate + 1000 > System.currentTimeMillis()) return; //only update once a second
if (Sponge.getServer().isMainThread()) {
updatePlayersSync();
} else {
try {
syncExecutor.submit(this::updatePlayersSync).get(3, TimeUnit.SECONDS);
} catch (TimeoutException | InterruptedException ignore) {
} catch (ExecutionException e) {
Logger.global.logError("Unexpected exception trying to update player-states!", e);
}
}
lastPlayerUpdate = System.currentTimeMillis();
}
private void updatePlayersSync() {
for (PlayerState player : onlinePlayers.values()) {
if (player instanceof SpongePlayerState) ((SpongePlayerState) player).update();
}
}
@Override