From 04c66bed539dea97d39623dca7f82810b9593995 Mon Sep 17 00:00:00 2001
From: TechnicJelle <22576047+TechnicJelle@users.noreply.github.com>
Date: Tue, 3 Jan 2023 01:51:24 +0100
Subject: [PATCH] Updated to new BlueMapAPI and simplified A TON
---
README.md | 19 +-
pom.xml | 12 +-
.../technicjelle/bluemapfloodgate/Config.java | 92 -----
.../technicjelle/bluemapfloodgate/Main.java | 342 ++++--------------
src/main/resources/config.yml | 8 -
5 files changed, 85 insertions(+), 388 deletions(-)
delete mode 100644 src/main/java/com/technicjelle/bluemapfloodgate/Config.java
delete mode 100644 src/main/resources/config.yml
diff --git a/README.md b/README.md
index 2f65211..9ff63ee 100644
--- a/README.md
+++ b/README.md
@@ -7,24 +7,7 @@ by default, floodgate players don't have the correct playerhead image on BlueMap
| Without | With |
|----------------------------------------------------------|----------------------------------------------------|
-| ![without the plugin](.github/readme_assets/without.png) | ![with the plugin](.github/readme_assets/with.png) |
-
-You can safely reload the config by reloading BlueMap itself with `/bluemap reload`.
-
-### Config
-- `configVersion_DO_NOT_TOUCH`
- - Do not touch this one! It's needed internally to keep track of the config versions.
-- `cacheHours: 72`
- - How long to keep the playerheads cached for. The lower this number, the faster skin updates will appear, but the more network usage there will be.
-- `verboseLogging: true`
- - Set to `true` if you want more messages telling you what the plugin is up to.
-- `customAPI: ''`
- - **This option is hidden by default, but you can add it to the config manually.**
- - If you want to use a custom skin API, you can set it here. Keep empty or removed if you want to use my own custom skin grabber.\
- Please tell me if you do use a custom skin API, because I genuinely want to know why anyone would want to do that.
- - Make sure the API returns a full skin image, not just the head.
- - Available placeholders: `{UUID}`, `{NAME}` (case sensitive)\
- If there was no placeholder found in the URL, the UUID will be appended to the end of the URL.
+| ![without the plugin](.github/readme_assets/without.png) | ![with the plugin](.github/readme_assets/with.png) |
## [Click here to download!](../../releases/latest)
diff --git a/pom.xml b/pom.xml
index efc4722..0bce197 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.technicjelle
BlueMapFloodgate
- 1.6.0
+ 2.0.0
jar
BlueMapFloodgate
@@ -76,9 +76,13 @@
https://jitpack.io
- opencollab-snapshot
+ opencollab-releases
https://repo.opencollab.dev/maven-snapshots/
+
+
+
+
@@ -91,13 +95,13 @@
com.github.BlueMap-Minecraft
BlueMapAPI
- v2.1.0
+ v2.4.0
provided
org.geysermc.floodgate
api
- 2.0-SNAPSHOT
+ 2.2.0-SNAPSHOT
provided
diff --git a/src/main/java/com/technicjelle/bluemapfloodgate/Config.java b/src/main/java/com/technicjelle/bluemapfloodgate/Config.java
deleted file mode 100644
index 110cff3..0000000
--- a/src/main/java/com/technicjelle/bluemapfloodgate/Config.java
+++ /dev/null
@@ -1,92 +0,0 @@
-package com.technicjelle.bluemapfloodgate;
-
-import org.bstats.charts.SimplePie;
-import org.bukkit.configuration.file.FileConfiguration;
-import org.jetbrains.annotations.NotNull;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.Objects;
-
-import static com.technicjelle.bluemapfloodgate.Main.logger;
-
-public class Config {
-
- private final Main plugin;
-
- private @NotNull FileConfiguration configFile() {
- return plugin.getConfig();
- }
-
- private final String CONFIG_VERSION_KEY = "configVersion_DO_NOT_TOUCH";
- private final int CONFIG_VERSION_NEWEST = 4; //Also change in config.yml
- private final String VERBOSE_LOGGING_KEY = "verboseLogging";
- private final boolean VERBOSE_LOGGING_DEFAULT = false;
- private final String CACHE_HOURS_KEY = "cacheHours";
- private final int CACHE_HOURS_DEFAULT = 72;
- private final String CUSTOM_API_KEY = "customAPI";
- private final String CUSTOM_API_DEFAULT = "";
-
- private final boolean verboseLogging;
- private final long cacheHours;
- private final String customAPI;
-
- public Config(@NotNull Main plugin) {
- this.plugin = plugin;
-
- //Setup config directory
- if(plugin.getDataFolder().mkdirs()) logger.info("Created plugin directory");
-
- //Setup config file
- File configFile = new File(plugin.getDataFolder(), "config.yml");
- if (!configFile.exists()) {
- try {
- logger.info("Creating config file");
- Files.copy(Objects.requireNonNull(plugin.getResource("config.yml")), configFile.toPath());
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- //Load config from disk
- plugin.reloadConfig();
-
- //Load config values into variables
- int configVersionCurrent = configFile().getInt(CONFIG_VERSION_KEY, 0); //is 0 if the config file doesn't exist
-
- boolean outOfDate = configVersionCurrent != 0 && configVersionCurrent != CONFIG_VERSION_NEWEST
- //use config.contains to check for all old config settings
- || configFile().contains("verboseUpdateMessages")
- || configFile().contains("useTydiumCraftSkinAPI");
-
- if (outOfDate){
- logger.severe("Config is out of date, please delete the config.yml file and reload BlueMap to regenerate a new one.");
- logger.warning("If you have any custom settings, you will need to re-add them to the new config file.");
- logger.warning("Using built-in defaults for now.");
-
- cacheHours = CACHE_HOURS_DEFAULT;
- verboseLogging = VERBOSE_LOGGING_DEFAULT;
- customAPI = CUSTOM_API_DEFAULT;
- } else {
- //Config file is up-to-date
- cacheHours = configFile().getLong(CACHE_HOURS_KEY, CACHE_HOURS_DEFAULT);
- verboseLogging = configFile().getBoolean(VERBOSE_LOGGING_KEY, VERBOSE_LOGGING_DEFAULT);
- customAPI = configFile().getString(CUSTOM_API_KEY, CUSTOM_API_DEFAULT);
- }
-
- plugin.metrics.addCustomChart(new SimplePie("custom_api", () -> customAPI.isBlank() ? "Direct" : "Custom"));
- }
-
- public boolean verboseLogging() {
- return verboseLogging;
- }
-
- public long cacheHours() {
- return cacheHours;
- }
-
- public String customAPI() {
- return customAPI;
- }
-}
diff --git a/src/main/java/com/technicjelle/bluemapfloodgate/Main.java b/src/main/java/com/technicjelle/bluemapfloodgate/Main.java
index a065d93..bc6d9e0 100644
--- a/src/main/java/com/technicjelle/bluemapfloodgate/Main.java
+++ b/src/main/java/com/technicjelle/bluemapfloodgate/Main.java
@@ -4,12 +4,8 @@
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import de.bluecolored.bluemap.api.BlueMapAPI;
+import de.bluecolored.bluemap.api.plugin.SkinProvider;
import org.bstats.bukkit.Metrics;
-import org.bukkit.Bukkit;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
@@ -18,333 +14,147 @@
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
-import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardCopyOption;
-import java.util.Calendar;
-import java.util.HashSet;
-import java.util.Objects;
+import java.util.Optional;
import java.util.UUID;
import java.util.function.Consumer;
-import java.util.logging.Logger;
-public final class Main extends JavaPlugin implements Listener {
-
- private static class CachedPlayer {
- UUID uuid;
- String xuid;
- String name;
-
- CachedPlayer(UUID _uuid, String _xuid, String _name) {
- uuid = _uuid;
- xuid = _xuid;
- name = _name;
- }
-
- //Two functions to assist the Set in knowing if a CachedPlayer is already in the set
- @Override
- public boolean equals(Object obj) {
- if (obj == this)
- return true;
- if (!(obj instanceof CachedPlayer))
- return false;
- CachedPlayer in = (CachedPlayer) obj;
- boolean resultUUID = uuid.equals(in.uuid);
- boolean resultXUID = Objects.equals(xuid, in.xuid);
-// verboseLog(uuid + ":" + in.uuid + " & " + xuid + ":" + in.xuid + "==>" + resultUUID + resultXUID);
- return resultUUID && resultXUID;
- }
-
- @Override
- public int hashCode() {
-// verboseLog(uuid + xuid + "==>" + (uuid.toString() + xuid).hashCode());
- return (uuid.toString() + xuid).hashCode();
- }
+public final class Main extends JavaPlugin {
+ private final boolean VERBOSE_LOGGING = false;
+ private void verboseLog(String message) {
+ if (VERBOSE_LOGGING) getLogger().info(message);
}
- public Metrics metrics;
-
- public static Logger logger;
- public static Config config;
-
- private FloodgateApi floodgateApi;
- private File blueMapPlayerheadsDirectory;
- private File ownPlayerheadsDirectory;
-
- @Nullable private HashSet playersToProcess; //null when BlueMap is online, to keep track of players that join before BlueMap is online
-
@Override
public void onEnable() {
- metrics = new Metrics(this, 16426);
-
- logger = getLogger();
-
- floodgateApi = FloodgateApi.getInstance();
- playersToProcess = new HashSet<>();
+ new Metrics(this, 16426);
- getServer().getPluginManager().registerEvents(this, this);
-
- //all actual startup and shutdown logic moved to BlueMapAPI enable/disable methods, so `/bluemap reload` also reloads this plugin
BlueMapAPI.onEnable(blueMapOnEnableListener);
- BlueMapAPI.onDisable(blueMapOnDisableListener);
- }
- Consumer blueMapOnEnableListener = blueMapAPI -> {
- //Config
- config = new Config(this);
-
-
- //Directory
- ownPlayerheadsDirectory = new File(getDataFolder() + "/playerheads");
- if (ownPlayerheadsDirectory.mkdir()) logger.info(ownPlayerheadsDirectory.toString() + " directory made");
-
- //TODO: replace with new BlueMapAPI methods etc
- blueMapPlayerheadsDirectory = new File(blueMapAPI.getWebApp().getWebRoot() + "/assets/playerheads/");
+ getLogger().info("BlueMap Floodgate compatibility plugin enabled!");
+ }
- //Process all players that joined before BlueMap was online or while it was reloading
- Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
-// verboseLog(">async");
- if (playersToProcess != null) {
-// verboseLog(">not null");
- for (CachedPlayer cachedPlayer : playersToProcess) {
- floodgateJoin(cachedPlayer);
+ private final Consumer blueMapOnEnableListener = blueMapAPI -> {
+ SkinProvider floodgateSkinProvider = new SkinProvider() {
+ private final FloodgateApi floodgateApi = FloodgateApi.getInstance();
+ private final SkinProvider defaultSkinProvider = blueMapAPI.getPlugin().getSkinProvider();
+
+ @Override
+ public Optional load(UUID playerUUID) throws IOException {
+ if (floodgateApi.isFloodgatePlayer(playerUUID)) {
+ FloodgatePlayer floodgatePlayer = floodgateApi.getPlayer(playerUUID);
+ String xuid = floodgatePlayer.getXuid();
+ @Nullable String textureID = textureIDFromXUID(xuid);
+ if (textureID == null) {
+ verboseLog("TextureID for " + playerUUID + " is null");
+ return Optional.empty();
+ }
+ @Nullable BufferedImage skin = skinFromTextureID(textureID);
+ if (skin == null) {
+ verboseLog("Skin for " + playerUUID + " is null");
+ return Optional.empty();
+ }
+ verboseLog("Skin for " + playerUUID + " successfully gotten!");
+ return Optional.of(skin);
+ } else {
+ return defaultSkinProvider.load(playerUUID);
}
- playersToProcess = null;
- } else {
- playersToProcessNULL();
}
- });
+ };
- logger.info("BlueMap Floodgate compatibility plugin enabled!");
- };
+ blueMapAPI.getPlugin().setSkinProvider(floodgateSkinProvider);
- private void playersToProcessNULL() {
- logger.severe("playersToProcess was null! Please report this on GitHub! https://github.com/TechnicJelle/BlueMapFloodgate/issues");
- for (Player p : Bukkit.getOnlinePlayers()) {
- if (floodgateApi.isFloodgatePlayer(p.getUniqueId())) {
- FloodgatePlayer floodgatePlayer = floodgateApi.getPlayer(p.getUniqueId());
- String xuid = floodgatePlayer.getXuid();
- CachedPlayer cachedPlayer = new CachedPlayer(p.getUniqueId(), xuid, floodgatePlayer.getUsername());
- floodgateJoin(cachedPlayer);
- }
- }
- }
-
- Consumer blueMapOnDisableListener = blueMapAPI -> {
- playersToProcess = new HashSet<>();
- logger.info("BlueMap API Shutting down!");
};
@Override
public void onDisable() {
BlueMapAPI.unregisterListener(blueMapOnEnableListener);
- BlueMapAPI.unregisterListener(blueMapOnDisableListener);
-
- playersToProcess = null;
- logger.info("BlueMap Floodgate compatibility plugin disabled!");
- }
-
- private void verboseLog(String message) {
- if (config.verboseLogging()) logger.info(message);
+ getLogger().info("BlueMap Floodgate compatibility plugin disabled!");
}
- @EventHandler
- public void onJoin(PlayerJoinEvent e) {
- Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
- Player p = e.getPlayer();
- if (floodgateApi.isFloodgatePlayer(p.getUniqueId())) {
- FloodgatePlayer floodgatePlayer = floodgateApi.getPlayer(p.getUniqueId());
- String xuid = floodgatePlayer.getXuid();
- CachedPlayer cachedPlayer = new CachedPlayer(p.getUniqueId(), xuid, floodgatePlayer.getUsername());
+ // ================================================================================================================
+ // ===============================================Util Methods=====================================================
+ // ================================================================================================================
- BlueMapAPI.getInstance().ifPresentOrElse(api -> {
- //BlueMap IS currently loaded
- floodgateJoin(cachedPlayer);
- }, () -> {
- //BlueMap is currently NOT loaded
- if (playersToProcess != null) {
- if (playersToProcess.add(cachedPlayer)) {
- verboseLog("Added " + p.getUniqueId() + " to processing queue");
- } else {
- verboseLog("Player " + p.getUniqueId() + " was already in the processing queue");
- }
- } else {
- playersToProcessNULL();
- }
- });
- }
- });
- }
-
- private void floodgateJoin(@NotNull CachedPlayer cachedPlayer) {
- File ownHeadFile = new File(ownPlayerheadsDirectory + "/" + cachedPlayer.uuid + ".png");
- if (ownHeadFile.exists()) {
- long lastModified = ownHeadFile.lastModified(); //long value representing the time the file was last modified, measured in milliseconds since the epoch (00:00:00 GMT, January 1, 1970)
- Calendar currentDate = Calendar.getInstance();
- long dateNow = currentDate.getTimeInMillis();
- if (dateNow > lastModified + 1000L * 60L * 60L * config.cacheHours()) {
- verboseLog("Cache for " + cachedPlayer.uuid + " outdated");
- downloadHeadToCache(cachedPlayer, ownHeadFile);
-
- if (ownHeadFile.setLastModified(dateNow)) {
- verboseLog(" Cache updated");
- } else {
- logger.severe(" Cache wasn't updated. This should never happen! Please report this on GitHub! https://github.com/TechnicJelle/BlueMapFloodgate/issues");
- }
-
- } else {
- verboseLog("Head for " + cachedPlayer.uuid + " already cached");
- }
- } else {
- downloadHeadToCache(cachedPlayer, ownHeadFile);
- }
-
-
- //TODO: replace with new BlueMapAPI methods etc
- Path destination = Paths.get(blueMapPlayerheadsDirectory.toString(), ownHeadFile.getName());
- Bukkit.getScheduler().runTaskLater(this, () -> {
- verboseLog("Overwriting BlueMap's head with floodgate's head");
- try {
- Files.copy(ownHeadFile.toPath(), destination, StandardCopyOption.REPLACE_EXISTING);
- verboseLog("BlueMap file overwritten!");
- } catch (IOException e) {
- logger.warning("Failed to copy the head image file from own directory to BlueMap's directory");
- e.printStackTrace();
- }
- }, 30);
- }
-
- private void downloadHeadToCache(CachedPlayer cachedPlayer, File f) {
- String placeholderUUID = "{UUID}";
- String placeholderName = "{NAME}";
-
- BufferedImage skin;
-
- if (config.customAPI().isBlank()) {
- String textureID = textureIDFromXUID(cachedPlayer.xuid);
- skin = skinFromTextureID(textureID);
- } else {
- String link = config.customAPI().contains(placeholderUUID)
- ? config.customAPI().replace(placeholderUUID, cachedPlayer.uuid.toString())
- : config.customAPI() + cachedPlayer.uuid;
- link = link.replace(placeholderName, cachedPlayer.name);
-
- verboseLog("Getting " + cachedPlayer.name + "'s skin from custom Skin API: " + link);
- skin = imageFromURL(link);
- }
-
- BufferedImage head;
-
- if (skin == null) {
- logger.warning("Skin was null, falling back to Steve head!"
- + (config.verboseLogging() ? "" : " Turn on verbose logging in the config to find out why, next time"));
- head = getSteveHead();
- } else {
- //if skin is already a head, just use that
- if (skin.getWidth() == 8 && skin.getHeight() == 8) {
- verboseLog("Skin was already a head");
- head = skin;
- } else {
- verboseLog("Skin was not a head, cropping...");
- head = headFromSkin(skin);
- }
- }
-
- if (head == null) {
- logger.warning("Head was null!");
- } else {
- try {
- ImageIO.write(head, "png", f);
- verboseLog(f + " saved");
- } catch (IOException e) {
- logger.warning("Failed to write the head image file to own directory");
- e.printStackTrace();
- }
- }
- }
-
- //TODO: Make return nullable, check usages and make sure they can handle null
- //TODO: Inverts all these nested if-statements, replace with early returns
- private String textureIDFromXUID(@NotNull String xuid) {
+ /**
+ * @param xuid XUID of the floodgate player
+ * @return the texture ID of the player, or null if it could not be found
+ */
+ private @Nullable String textureIDFromXUID(@NotNull String xuid) {
try {
URL url = new URL("https://api.geysermc.org/v2/skin/" + xuid);
+ verboseLog("Getting textureID from " + url);
try {
URLConnection request = url.openConnection();
request.connect();
JsonObject joRoot = JsonParser.parseReader(new InputStreamReader((InputStream) request.getContent())).getAsJsonObject();
- if (joRoot != null) {
- JsonElement jeTextureID = joRoot.get("texture_id");
- if (jeTextureID != null) {
- String textureID = jeTextureID.getAsString();
- if (textureID != null) {
- return textureID;
- } else {
- verboseLog("textureID is null!");
- }
- } else {
- verboseLog("jeTextureID is null!");
- }
- } else {
+ if (joRoot == null) {
verboseLog("joRoot is null!");
+ return null;
}
+
+ JsonElement jeTextureID = joRoot.get("texture_id");
+ if (jeTextureID == null) {
+ verboseLog("jeTextureID is null!");
+ return null;
+ }
+
+ String textureID = jeTextureID.getAsString();
+ if (textureID == null) {
+ verboseLog("textureID is null!");
+ return null;
+ }
+
+ return textureID;
} catch (IOException e) {
- logger.warning("Failed to get the textureID");
+ getLogger().warning("Failed to get the textureID for " + xuid + " from " + url);
e.printStackTrace();
+ return null;
}
} catch (MalformedURLException e) {
e.printStackTrace();
+ return null;
}
- return null;
}
- private BufferedImage skinFromTextureID(String textureID) {
+ /**
+ * @param textureID texture ID of the floodgate player
+ * @return the skin of the player, or null if it could not be found
+ */
+ private @Nullable BufferedImage skinFromTextureID(@NotNull String textureID) {
verboseLog("Getting skin from textureID: " + textureID);
return imageFromURL("https://textures.minecraft.net/texture/" + textureID);
}
- private @Nullable BufferedImage imageFromURL(String url) {
+ /**
+ * @param url URL of the image
+ * @return the image, or null if it could not be found
+ */
+ private @Nullable BufferedImage imageFromURL(@NotNull String url) {
BufferedImage result;
try {
URL imageUrl = new URL(url);
+ verboseLog("Getting image from URL: " + url);
try {
InputStream in = imageUrl.openStream();
result = ImageIO.read(in);
in.close();
} catch (IOException e) {
- logger.warning("Failed to get the image from " + url);
+ getLogger().warning("Failed to get the image from " + url);
e.printStackTrace();
return null;
}
} catch (MalformedURLException e) {
- logger.warning("URL: " + url);
e.printStackTrace();
return null;
}
return result;
}
-
- //TODO: replace with new BlueMapAPI methods etc
- private BufferedImage headFromSkin(@NotNull BufferedImage skin) {
- return skin.getSubimage(8, 8, 8, 8);
- }
-
- private @Nullable BufferedImage getSteveHead() {
- try {
- return ImageIO.read(new File(blueMapPlayerheadsDirectory.getParent() + "/steve.png"));
- } catch (IOException e) {
- logger.warning("Failed to load BlueMap's fallback steve.png!");
- e.printStackTrace();
- return null;
- }
- }
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
deleted file mode 100644
index adc7445..0000000
--- a/src/main/resources/config.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-# Do not touch this one! It's needed internally to keep track of the config versions.
-configVersion_DO_NOT_TOUCH: 4
-
-# How long to keep the playerheads cached for. The lower this number, the faster skin updates will appear, but the more network usage there will be.
-cacheHours: 72
-
-# Set to true if you want more messages telling you what the plugin is up to.
-verboseLogging: false