From 29ae27adacac982b92c3a2dd52a31670d21bdc2e Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 17 Jul 2024 05:01:25 +0100 Subject: [PATCH 01/33] - First closed alpha build --- pom.xml | 4 +- .../ResourcePackManager.java | 7 +- .../autohost/AutoHost.java | 149 ++++++++++++++---- .../commands/CommandManager.java | 1 + .../DataComplianceRequestCommand.java | 43 +++++ .../config/BlueprintFolder.java | 46 ++++++ .../config/ConfigurationEngine.java | 9 ++ .../config/DefaultConfig.java | 12 ++ .../datacompliance/DataCompliance.java | 4 + .../resourcepackmanager/mixer/Mix.java | 21 ++- .../playermanager/PlayerManager.java | 16 ++ .../thirdparty/ItemsAdder.java | 11 +- .../resourcepackmanager/thirdparty/Nova.java | 11 +- .../thirdparty/Oraxen.java | 13 +- .../thirdparty/ResourcePackManager.java | 13 ++ .../thirdparty/ThirdPartyResourcePack.java | 9 +- .../utils/ChatColorConverter.java | 24 +++ .../utils/SHA1Generator.java | 25 +++ src/main/resources/ReadMe.md | 39 +++++ src/main/resources/pack.mcmeta | 6 + src/main/resources/pack.png | Bin 0 -> 22239 bytes src/main/resources/plugin.yml | 2 +- 22 files changed, 416 insertions(+), 49 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java create mode 100644 src/main/resources/ReadMe.md create mode 100644 src/main/resources/pack.mcmeta create mode 100644 src/main/resources/pack.png diff --git a/pom.xml b/pom.xml index bc2f010..239370e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.0-SNAPSHOT + 0.0.1 22 @@ -50,6 +50,8 @@ + ResourcePackManager + org.apache.maven.plugins diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 179a3af..6fcfe8b 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -2,8 +2,10 @@ import com.magmaguy.resourcepackmanager.autohost.AutoHost; import com.magmaguy.resourcepackmanager.commands.CommandManager; +import com.magmaguy.resourcepackmanager.config.BlueprintFolder; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.mixer.Mix; +import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; import org.bukkit.Bukkit; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -20,12 +22,15 @@ public void onEnable() { " | /\\__ \\ _/ |\\/| / _` | ' \\/ _` / _` / -_) '_|\n" + " |_|_\\|___/_| |_| |_\\__,_|_||_\\__,_\\__, \\___|_| \n" + " |___/ "); - Logger.info("Enabling ResourcePackManager v." + this.getDescription().getVersion()); + Logger.info("ResourcePackManager v." + this.getDescription().getVersion()); plugin = this; DefaultConfig.initializeConfig(); + BlueprintFolder.initialize(); Mix.initialize(); AutoHost.initialize(); new CommandManager(this); + if (DefaultConfig.isAutoHost()) + Bukkit.getPluginManager().registerEvents(new PlayerManager(), this); } @Override diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index a520e44..412ca91 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -4,99 +4,182 @@ import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.mixer.Mix; +import lombok.Getter; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ContentType; +import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.http.io.entity.FileEntity; +import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; import java.io.*; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.UUID; public class AutoHost { - private static final String hostURL = "http://localhost:3000/sha1"; - private static final String stillAliveURL = "http://localhost:3000/still_alive"; - private static final String rspURL = "http://localhost:3000/upload"; + // private static final String finalURL = "http://localhost:3000/"; + private static final String finalURL = "http://magmaguy.com:50000/"; + private static final UUID pluginRSPUUID = UUID.randomUUID(); private static BukkitTask keepAlive = null; + @Getter + private static String rspUUID = null; private AutoHost() { } + public static void sendResourcePack(Player player) { + if (rspUUID == null) return; + player.addResourcePack(pluginRSPUUID, finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); + } + public static void initialize() { if (!DefaultConfig.isAutoHost()) return; if (Mix.getFinalResourcePack() == null) return; + checkFileExistence(); keepAlive = new BukkitRunnable() { + int counter = 0; + @Override public void run() { - try { - if (!sendSHA1(Mix.getFinalSHA1(), hostURL)) { - uploadFile(Mix.getFinalResourcePack(), rspURL); + if (rspUUID != null) { + counter = 0; + try { + sendStillAlive(); + } catch (Exception e) { + rspUUID = null; + Logger.warn("Failed to autohost resource pack!"); } - sendStillAlive(stillAliveURL); - } catch (Exception e) { - Logger.warn("Failed to autohost resource pack!"); - e.printStackTrace(); + } else { + checkFileExistence(); + if (rspUUID == null && counter % 10 == 0) { + Logger.warn("Failed to connect to remote server to autohost the resource pack!"); + } + counter++; } } - }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 3 * 24 * 60 * 60 * 20L); + }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 60 * 20L); + } + + private static void checkFileExistence() { + initializeLink(); + if (rspUUID == null) return; + if (!sendSHA1()) uploadFile(); + } + + public static void initializeLink() { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost httpPost = new HttpPost(finalURL + "initialize"); + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + String responseString = EntityUtils.toString(response.getEntity()); + rspUUID = responseString.trim(); + } catch (Exception e) { + Logger.warn("Failed to communicate with remote server!"); + } + } catch (Exception e) { + rspUUID = null; + Logger.warn("Failed remote server initialization."); + } } - public static void uploadFile(File file, String url) throws IOException { + public static void uploadFile() { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost uploadFile = new HttpPost(url); + Logger.warn("uploading file"); + HttpPost uploadFile = new HttpPost(finalURL + "upload"); - // Use FileEntity for streaming large files - FileEntity fileEntity = new FileEntity(file, ContentType.APPLICATION_OCTET_STREAM); - uploadFile.setEntity(fileEntity); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("uuid", rspUUID, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + builder.addBinaryBody("file", Mix.getFinalResourcePack(), ContentType.APPLICATION_OCTET_STREAM, Mix.getFinalResourcePack().getName()); + + uploadFile.setEntity(builder.build()); try (CloseableHttpResponse response = httpClient.execute(uploadFile)) { + } catch (Exception e) { + Logger.warn("Failed to communicate with remote server!"); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static boolean sendSHA1() { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost httpPost = new HttpPost(finalURL + "sha1"); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("uuid", rspUUID, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + builder.addTextBody("sha1", Mix.getFinalSHA1(), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + + HttpEntity entity = builder.build(); + httpPost.setEntity(entity); + + // Convert the entity to a string for logging + ByteArrayOutputStream out = new ByteArrayOutputStream(); + entity.writeTo(out); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseString = EntityUtils.toString(response.getEntity()); - System.out.println("Response from server: " + responseString); + return Boolean.valueOf(responseString); } catch (Exception e) { - throw new RuntimeException(e); + Logger.warn("Failed to communicate with remote server!"); } + } catch (Exception e) { + } + return false; } - private static Boolean sendSHA1(String stringData, String url) throws IOException { + private static void sendStillAlive() throws IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost(url); + HttpPost httpPost = new HttpPost(finalURL + "still_alive"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addTextBody("stringData", stringData, ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + builder.addTextBody("uuid", rspUUID); httpPost.setEntity(builder.build()); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { - String responseString = EntityUtils.toString(response.getEntity()); - System.out.println("Response from server: " + responseString); - return Boolean.parseBoolean(responseString.trim()); } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); - e.printStackTrace(); - return null; } } } - private static void sendStillAlive(String url) throws IOException { + public static void dataComplianceRequest() throws IOException { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost(url); + HttpPost httpPost = new HttpPost(finalURL + "data_compliance"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); - builder.addTextBody("status", "alive", ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + builder.addTextBody("uuid", rspUUID); httpPost.setEntity(builder.build()); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { - String responseString = EntityUtils.toString(response.getEntity()); - System.out.println("Response from server: " + responseString); + HttpEntity responseEntity = response.getEntity(); + + if (responseEntity != null) { + // Save the response as a zip file + File zipFile = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "data_compliance" + File.separatorChar + "data.zip"); + if (!zipFile.getParentFile().exists()) zipFile.mkdirs(); + if (zipFile.exists()) zipFile.delete(); + zipFile.createNewFile(); + try (FileOutputStream outStream = new FileOutputStream(zipFile)) { + responseEntity.writeTo(outStream); + } + InputStream inputStream = ResourcePackManager.plugin.getResource("ReadMe.md"); + + File readMe = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "data_compliance" + File.separatorChar + "ReadMe.md"); + if (!readMe.exists()) readMe.createNewFile(); + + // Copy the InputStream to the file + Files.copy(inputStream, readMe.toPath(), StandardCopyOption.REPLACE_EXISTING); + } } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); - e.printStackTrace(); } } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java index 520ffff..bc6ed70 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java @@ -20,6 +20,7 @@ public CommandManager(JavaPlugin javaPlugin) { private void registerCommands() { registerCommand(new ReloadCommand()); + registerCommand(new DataComplianceRequestCommand()); } public void registerCommand(AdvancedCommand command) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java new file mode 100644 index 0000000..7d78ab2 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java @@ -0,0 +1,43 @@ +package com.magmaguy.resourcepackmanager.commands; + +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.autohost.AutoHost; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.scheduler.BukkitRunnable; + +import java.util.List; + +public class DataComplianceRequestCommand extends AdvancedCommand { + + public DataComplianceRequestCommand() { + super(List.of("data_compliance_request"), "Downloads a copy of all data associated to this server from the autohoster", "*", false, "/fmm data_compliance_request"); + } + + @Override + public void execute(CommandSender sender, String[] arguments) { + if (AutoHost.getRspUUID() == null) { + sender.sendMessage("[ResourcePackManager] Seems like the auto-hoster is either disabled or not working, no data is stored in remote servers because no connection to remote servers is established"); + return; + } + + new BukkitRunnable() { + @Override + public void run() { + try { + AutoHost.dataComplianceRequest(); + sender.sendMessage("[ResourcePackManager] Data compliance request completed, check ~/plugins/ResourcePackManager/data_compliance to see all data the remote server has stored for this server."); + } catch (Exception e) { + sender.sendMessage("[ResourcePackManager] Failed to request data, check console for error logs!"); + e.printStackTrace(); + } + } + }.runTaskAsynchronously(ResourcePackManager.plugin); + + } + + @Override + public List onTabComplete(CommandSender commandSender, Command command, String label, String[] args) { + return List.of(); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java new file mode 100644 index 0000000..5abc7b7 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java @@ -0,0 +1,46 @@ +package com.magmaguy.resourcepackmanager.config; + +import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.utils.ZipFile; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; + +public class BlueprintFolder { + private BlueprintFolder() { + } + + public static void initialize() { + Logger.warn("Creating blueprint folder"); + File blueprintDirectory = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "blueprint"); + if (!blueprintDirectory.exists()) blueprintDirectory.mkdir(); + Logger.warn("Copying image"); + File imageFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.png"); + if (!imageFile.exists()) { + try { + InputStream inputStream = ResourcePackManager.plugin.getResource("pack.png"); + Files.copy(inputStream, imageFile.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + Logger.warn("Copying mcmeta"); + File mcmetaFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.mcmeta"); + if (!mcmetaFile.exists()) { + try { + InputStream inputStream = ResourcePackManager.plugin.getResource("pack.mcmeta"); + Files.copy(inputStream, mcmetaFile.toPath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + try { + ZipFile.ZipUtility.zip(blueprintDirectory, blueprintDirectory.getAbsolutePath() + File.separatorChar + "blueprint.zip"); + } catch (Exception e) { + Logger.warn("Failed to zip blueprint resource pack!"); + } + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java index 7e6c8a0..024b65c 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java @@ -2,6 +2,7 @@ import com.magmaguy.resourcepackmanager.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.utils.ChatColorConverter; import org.bukkit.Bukkit; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -134,6 +135,14 @@ public static List setList(List comment, File file, FileConfiguration fi setComments(fileConfiguration, key, comment); return value; } + public static String setString(File file, FileConfiguration fileConfiguration, String key, String defaultValue) { + return ChatColorConverter.convert(fileConfiguration.getString(key)); + } + public static String setString(List comments, File file, FileConfiguration fileConfiguration, String key, String defaultValue) { + String value = setString(file, fileConfiguration, key, defaultValue); + setComments(fileConfiguration, key, comments); + return value; + } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index 702aea3..c7d59ce 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -13,6 +13,10 @@ public class DefaultConfig { private static List priorityOrder; @Getter private static boolean autoHost; + @Getter + private static boolean forceResourcePack; + @Getter + private static String resourcePackPrompt; public static void initializeConfig() { file = ConfigurationEngine.fileCreator("config.yml"); @@ -31,6 +35,14 @@ public static void initializeConfig() { "These servers cost money to keep running. There is no guarantee this will be an option forever."), fileConfiguration, "autoHost", true); + forceResourcePack = ConfigurationEngine.setBoolean( + List.of("Sets whether the resource pack use will be forced to clients"), + fileConfiguration, "forceResourcePack", false); + + resourcePackPrompt = ConfigurationEngine.setString( + List.of("Sets whether the resource pack use will be forced to clients"), + file, fileConfiguration, "resourcePackPrompt", "Use recommended resource pack?"); + ConfigurationEngine.fileSaverOnlyDefaults(fileConfiguration, file); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java b/src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java new file mode 100644 index 0000000..bdd04bb --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java @@ -0,0 +1,4 @@ +package com.magmaguy.resourcepackmanager.datacompliance; + +public class DataCompliance { +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 608d64e..242cdc5 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -4,10 +4,7 @@ import com.google.gson.stream.JsonReader; import com.magmaguy.resourcepackmanager.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; -import com.magmaguy.resourcepackmanager.thirdparty.EliteMobs; -import com.magmaguy.resourcepackmanager.thirdparty.FreeMinecraftModels; -import com.magmaguy.resourcepackmanager.thirdparty.ModelEngine; -import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; +import com.magmaguy.resourcepackmanager.thirdparty.*; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import com.magmaguy.resourcepackmanager.utils.ZipFile; import lombok.Getter; @@ -32,6 +29,8 @@ public class Mix { private static File finalResourcePack; @Getter private static String finalSHA1; + @Getter + private static byte[] finalSHA1Bytes; private Mix() { } @@ -60,6 +59,9 @@ private static boolean initializeDefaultPluginFolders() { private static void initializeThirdPartyResourcePacks() { ArrayList tempList = new ArrayList<>(); + com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager resourcePackManager = new com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager(); + if (resourcePackManager.isEnabled()) + tempList.add(resourcePackManager); EliteMobs eliteMobs = new EliteMobs(); if (eliteMobs.isEnabled()) tempList.add(eliteMobs); @@ -69,7 +71,15 @@ private static void initializeThirdPartyResourcePacks() { ModelEngine modelEngine = new ModelEngine(); if (modelEngine.isEnabled()) tempList.add(modelEngine); - //todo: add the rest + ItemsAdder itemsAdder = new ItemsAdder(); + if (itemsAdder.isEnabled()) + tempList.add(itemsAdder); + Nova nova = new Nova(); + if (nova.isEnabled()) + tempList.add(nova); + Oraxen oraxen = new Oraxen(); + if (oraxen.isEnabled()) + tempList.add(oraxen); thirdPartyResourcePacks = new ArrayList<>(); for (int i = 0; i < tempList.size(); i++) { for (ThirdPartyResourcePack thirdPartyResourcePack : tempList) { @@ -127,6 +137,7 @@ private static void createOutputDefaultElements() { finalResourcePack = file; try { finalSHA1 = SHA1Generator.sha1CodeString(finalResourcePack); + finalSHA1Bytes = SHA1Generator.sha1CodeByteArray(finalResourcePack); } catch (Exception e) { Logger.warn("Failed to get SHA1 from zipped resource pack!"); finalResourcePack = null; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java new file mode 100644 index 0000000..1845077 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java @@ -0,0 +1,16 @@ +package com.magmaguy.resourcepackmanager.playermanager; + +import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.resourcepackmanager.autohost.AutoHost; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; + +public class PlayerManager implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Logger.info("event"); + AutoHost.sendResourcePack(event.getPlayer()); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java index 3edad18..857c541 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java @@ -1,4 +1,13 @@ package com.magmaguy.resourcepackmanager.thirdparty; -public class ItemsAdder { +import java.io.File; + +public class ItemsAdder extends ThirdPartyResourcePack { + public ItemsAdder() { + super("ItemsAdder", + "ItemsAdder" + File.separatorChar + "output" + File.separatorChar + "generated.zip", + false, + false, + ""); + } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java index 0494ff6..f11caa9 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java @@ -1,4 +1,13 @@ package com.magmaguy.resourcepackmanager.thirdparty; -public class Nova { +import java.io.File; + +public class Nova extends ThirdPartyResourcePack { + public Nova() { + super("Nova", + "Nova" + File.separatorChar + "resource_pack" + File.separatorChar + "ResourcePack.zip", + false, + false, + ""); + } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java index 3d409ac..aa7e9e7 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java @@ -1,4 +1,13 @@ package com.magmaguy.resourcepackmanager.thirdparty; -public class Oraxen { -} +import java.io.File; + +public class Oraxen extends ThirdPartyResourcePack { + public Oraxen() { + super("Oraxen", + "Oraxen" + File.separatorChar + "pack" + File.separatorChar + "pack.zip", + false, + false, + ""); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java new file mode 100644 index 0000000..ad2cc7e --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.thirdparty; + +import java.io.File; + +public class ResourcePackManager extends ThirdPartyResourcePack { + public ResourcePackManager() { + super("ResourcePackManager", + "ResourcePackManager" + File.separatorChar + "blueprint" + File.separatorChar + "blueprint.zip", + false, + false, + "elitemobs reload"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 59e6c2f..113f560 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -14,10 +14,10 @@ public class ThirdPartyResourcePack implements GeneratorInterface { @Getter - private final File file; - private final boolean encrypts; - private final boolean distributes; - private final String reloadCommand; + private File file; + private boolean encrypts; + private boolean distributes; + private String reloadCommand; @Getter private boolean isEnabled; private String SHA1; @@ -31,6 +31,7 @@ public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, isEnabled = Bukkit.getPluginManager().isPluginEnabled(pluginName); if (isEnabled) Logger.info("Initializing " + pluginName + "'s resource pack"); + else return; this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); if (!file.exists()) { Logger.warn("Found " + pluginName + " but could not find resource pack at location " + file.getPath() + " ! ResourcePackManager will not be able to merge the resource pack from this plugin."); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java new file mode 100644 index 0000000..cfcae6d --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java @@ -0,0 +1,24 @@ +package com.magmaguy.resourcepackmanager.utils; + +import org.bukkit.ChatColor; + +import java.util.ArrayList; +import java.util.List; + +public class ChatColorConverter { + private ChatColorConverter() { + } + + public static String convert(String string) { + if (string == null) return ""; + return ChatColor.translateAlternateColorCodes('&', string); + } + + public static List convert(List list) { + if (list == null) return new ArrayList<>(); + List convertedList = new ArrayList<>(); + for (Object value : list) + convertedList.add(convert(value + "")); + return convertedList; + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java index c1d4edb..d0d1412 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java @@ -1,8 +1,11 @@ package com.magmaguy.resourcepackmanager.utils; +import com.magmaguy.resourcepackmanager.Logger; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.DigestInputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -21,6 +24,28 @@ public static String sha1CodeString(File file) throws IOException, NoSuchAlgorit } } + public static String sha1CodeString(String text) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + byte[] bytes = text.getBytes(StandardCharsets.UTF_8); + byte[] resultByteArry = digest.digest(bytes); + return bytesToHexString(resultByteArry); + } catch (NoSuchAlgorithmException e) { + Logger.warn("Failed to find SHA-1 algorithm."); + } + return null; + } + + public static byte[] sha1CodeByteArray(File file) throws IOException, NoSuchAlgorithmException { + FileInputStream fileInputStream = new FileInputStream(file); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + DigestInputStream digestInputStream = new DigestInputStream(fileInputStream, digest); + byte[] bytes = new byte[1024]; + // read all file content + while (digestInputStream.read(bytes) > 0) digest = digestInputStream.getMessageDigest(); + return digest.digest(); + } + public static String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { diff --git a/src/main/resources/ReadMe.md b/src/main/resources/ReadMe.md new file mode 100644 index 0000000..82ef310 --- /dev/null +++ b/src/main/resources/ReadMe.md @@ -0,0 +1,39 @@ +### ResourcePackManager Data Policy and Compliance + +**ResourcePackManager**, developed by MagmaGuy for the Nightbreak game studio, includes an optional auto-host feature that temporarily hosts resource pack data on a remote server. + +As of this writing, the hosted data is fully anonymous and serves the sole purpose of simplifying the distribution of resource packs to clients of servers utilizing this service. Future versions of this document may be updated to reflect any changes in data policy and other related matters. + +This system complies with: +- **Directive 2000/31/EC** of the European Parliament and of the Council of 8 June 2000 +- **Regulation (EU) 2022/2065** of the European Parliament and of the Council of 19 October 2022 + +For data hosting transparency and compliance with these and other European norms, it is possible to obtain all data related to a server via the `/resourcepackmanager data_compliance_request` command. This command packages a full copy of all files and data associated with the requesting server. + +To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\nightbreak.io` (replace `/at\ ` with `@`). However, due to the system's design, data is only retained for up to two minutes after a server using ResourcePackManager shuts down, making email requests largely unnecessary. Nonetheless, the option remains available to ensure full compliance with European norms. + +### Data Handling by ResourcePackManager and Nightbreak Servers + +1. **Resource Pack Creation** + - ResourcePackManager aggregates resource packs on your server into a single zipped file containing all custom content. + +2. **Initialization Request** + - An initialization request is sent to remote servers, creating a `.txt` file with a random UUID. This file can be obtained via the `/resourcepackmanager data_compliance_request`. + +3. **SHA1 Request** + - ResourcePackManager transmits the SHA1 code of your resource pack to the remote server, which is saved in the `.txt` file. + +4. **File Transmission** + - The zipped resource pack file is sent to the remote server, assigned the same UUID as the `.txt` file. This file can be obtained through `/resourcepackmanager data_compliance_request` and verified to be identical to the original in your output folder, as it is not modified by the Nightbreak servers. + +5. **"Still Alive" Ping** + - ResourcePackManager sends a "still alive" ping every minute, transmitting the UUID to the server, which updates the timestamp in the `.txt` file. + - If no "still alive" ping is received for over two minutes, all data associated with that UUID (the `.txt` file and the resource pack) is deleted from the Nightbreak servers. + +### Data Policy + +- **Pseudonymous Identification:** Nightbreak assigns a random UUID to your server's files each time the server reboots, ensuring no IP address or identifiable information is stored unless users manually add such information to their resource packs. +- **No Download Logging:** Nightbreak does not log any data related to download requests by Minecraft clients. +- **No Data Sales:** Data uploaded to Nightbreak is not, has never been, and will never be sold. +- **Compliance with Takedown Requests:** Nightbreak will comply with takedown requests from both server administrators and law enforcement agencies. +- **Automatic Data Removal:** All data associated with your server is automatically removed two minutes after your server shuts down and is reuploaded on every restart for as long as ResourcePackManager is in use and using the auto-host feature. \ No newline at end of file diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..31d8f2e --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "pack_format": 34, + "description": "= ResourcePackManagerRSP =" + } +} \ No newline at end of file diff --git a/src/main/resources/pack.png b/src/main/resources/pack.png new file mode 100644 index 0000000000000000000000000000000000000000..485d0af91a9030be2ffd8f756a475a91ba24e287 GIT binary patch literal 22239 zcmaI6WmH{3vn{%j;O@@G9X9S7TsIcn-QC?G1b2rJ2=4Cg7Tn$49rE&>d(XY^$2)J0 zu~y5dIjg$6dUW;bjsh!6AtMkV0001F8EJ8qe`(-%pVe#}VddrJWdX6Vu(2`ylVEc3vUfG|WU_am_-_Vr zGZzzQD@Ru=2YcXu7>$e_+*}38{~7zgT4Cq-e`xJp{&!FP>oXQlBS#iiX3&3H_1}UD z3jhD2c6R?q+Qn7H?El*P|4Hnk=H+O{qGIOa;O1=dZ|uw|{-er~@2j(!k*kBVnuCMw ze|IF<(!tfi#nQnM$i~Uc1*FxmvNv_`aH0DzfPw;_jJ=Dik-dqTjJP2AKMH0mD^orm z5QjJyD+e0~JBKJMtAwN|mpF$wCxM3 z<{qbdJAI+M;eF~ed6AE%4}&^_JQQ@;{WAO~4($S#?{oy~Z#&0N z5!XJ8{po(i!I$yN+}dOT8k8!j&lIRwSte98KbME!y;tbk&DXu%$axq-oi8y3A`{1V zMn8NP(e%H>jphYzasRy1Y;wIH-pE^pxqA5g!~8z6#99~+1nSb)0#_i+UM_%bvuK!l z0|SZgvFYir%_K8R1~NR!W!qK02sLkpyJ8YCx?vme7+phG(ucb^*pE1@an6u?jnJgg ztj>X9_)eH^dhi?XX3A!ePIS z5Tlf_jdoDz0w_?GQnYF|AyZ%K@UI107$~Kys-vNQhCanoInmu1#8Xyq`{TV+71DbB z$toibYN|Xd4#QqHXzv~Cfpqfwgr3Hum1K?^fC06NFaL!^NYN@P$@c3y<8FptI)4et znH?97*NtPs9iUW^Jf}?m8}roc8m3!@OQz2sb&{BVf#mnrSXNV9TKFiN$OhFy(5zV|jx z*j{MQ_h-3aos;+~SHD-jsiTbbpDsTUV7sH7B>#L3K9HOyOs9uOM1mKOCJORp9}k0m zDZ?N;m!mO&?piUqZ4f9_>|DQ@8A{bTeZ8?FY zpj;ifEj)J^q9ILNDJXa_5-JDNAO4Q2j}-sQ>Nj(Qa4l#9JqiQ2(fh4(-21W7^^mhg z-8ACvCYc;>coxMZOs6yIi1Em-LFOG3_k9z+s@YQg`$F< z^Iq4K=UfFKs?dgML+XVdvIF@33V^crjbiEt|KsSPFcJbK{>#=aS>jbff&pBVsq%)) zS?TrNPLlB1%RS2Nkv5Aaj$n7Y9^VUlRin07j|SPJ!&-nfZ_m+4nu)=uGW7Xd8T88; zu*NODP#YrNoU7|1&xI6jsrAZT2KtB#0qb%QKO_Xh;%my)_BVz^(Hq z6`o2V5Cv`D_{O0b(HY0L4xz??(wGNG}?^-mmCi^37bb;b4r%=PruE-8aF$N8Ra8(W~rmuuK)-TGWD4x7`FYB7*j@q}% zpWjqoA3w#-QS!V}1c+Iilf%~O^Wz8BtH85fYN2KZ4`?#I*+aVL%D@Y;V@R^~Q?e)o zTE{3~8mut6b&H@5>W1MZvA14c5G;UpcM24=Lk);HRET9um2GGIvWbo>(;d}PyYA+AtHI`IQX-Rxb;y{5*h#%c8R0tca)B5vk zYI-GX)mCW}e1Iuw4cNcuceP+nrv+^juTKtAlik6kqDph{Y#enTwpvE(oW z@2gH5DLimwr#2_}*zsAJjVL^+bqx0giL+Z8VfP2)jK~8ZNuezE<;s?mmQD5Jn)-7E zl^)l~uA$i$xsY78>;o#KxEul(lJ1XY9a7!g8sSO3;* zk-Tp~<$a`IV?B-Vn#qd-14mGC^TEC$do|>Lumgh`ld+<5BNBuXy-8s`8`0T z1EZhb$qB%Rk)_*xin8P#Kp9cVVFIt)vI@|9uXVU>ljgU>nGY;d{9f7q&S?Tw!wt-( zGzC8VrrWbx-H-VAY>{0R)LDM%_<6$=*q0DES)}zJAc&(iX9NF2EITgJU#C%d~(I8g*vq%ycpCS8R_lg1G8#eeuTHVoKpZCG_sM;=d z^Q3%liHq(iw;xe8y^5ZPiIM^Z@P43_(n6%ZQQ&wd z&4;4mBXEfudW`3qpv>_-Ft{eOx@=$qD)n7Dh7mq<6m^q=><>2WdL;i?2=ek0l+d5v zW%^M3ANL3aUB5=3Q!LtrTGbWHuF@eWd2W38uWhEu6%vj|=?Jm5VT(9BacXpnbqf^n z*;mu82*^q2<$ghieauJ#U(i3|-Wjl1B1I~0vHA|s7M$v9V*_*32JUzNgV0FMb`Udqf^?^_ypADiNQl( zS|-jSE}9@FtyH3fMd2k?WFH~lc@01TZsi9{}&nz1O4R7d+l^bF{6%QHb3;#5nHEFAk%JP{*2gl7_&>l9Va(R z;go1{Fs$u6Em)JE%!EumDy#zsq0VL9rC6g+G&26o^AHP^CW{h0r0YJCr}?N*P6X*& ze3tPGDku;~Tmn#8`ZEx!C@CUlcl5mT>umQ>G&D^E{K-8!K3pZ;dM6nF?c@V3>FYYm z1Sp*dSOoMPF!4$x0$PQ$_*RPeej&=2n&?f$0u#W}@ycM#L6zlOImS~Bqkm;ecM!&r z#gE9qmMTcy{iJMUL`wb%oIrbz!qbRMNrL=lB<60Nml5K8EQ6t%^k8JO{c@FfohkPPgr0N2nUGJD|dNVKDj zwA&9PI3t|u9ii&@trG0*=s@DUQ_dE=u&q15WhWZ>gXZx!8niuzA@_K4^76;tFQtKfvP0>FO+pOb! zP8wW2yv71j-|$vNkswv(q|WJSy^Di&3Il?^2EUJB$1qIjTfGe(?R?jt^s<3Tk9nN_ zD7cwTD*1*jHm$-j$6~Cmbz6WtERB?cz7!<*Wmi)x;ds@oDdwaCxH(ln^QY$Pu`3z6 z_HKT7SXsWM7bS}}k_~5TG9n3F_pfRd%2=MLgPQ_K_VKQ`{=D8e!I1>H@TEvK$U51X z`*Y9fQrvTm+`Q5ijHLkwy`i?cM5T{on?fm)>W$l;zEquTPkK1$eD#cPRn=zR<-l>~ z6#2L+0Pf}b!R(PrAw+k-vymG#Qk>87G}hpq zP$(Ni&b&VlM(!UPOKp&q)S49{2Efhp?N=j&fh)VDAm|Vut|V<<-f{ z--{3+8)E2#?8((qTP3L}A4VWVG3HF{aaFNM#-GUIB&uD%d%fao!%!>X%ZGX6E0x+)}byN1aVtmy?;6yf13?EOPnV_Mj{lw@j^WBH#Bc z*VCftpvFY}aLI5d%IRB4!Zw(9g2v^3jNs~~9k~Qj8`6N}Y7MQiK`ciY!8X!;V#wGp zBFLD06B65Hff-)%dn20CsdN2iWII3bW}obAOYHCnMPmAVWWm`?fou6}K>=`vHJvRr zPxmf_L#S)T6lbyq4r|g5#q~_~?U%BpWO}5uU-UI}=`v!s*3;_s9K1`N-3mH%X}6xo zJlyl%PNF6~0EF^_IFd=Vlmzftfw)=)C&098Kx+NDv6QI%d;cj+*fveLB?P*1@6&i* zx==wnt`s}85ncK(N!b%$svjSGl=CXo!%zWgJGA~NdN*HfCt{MRgkgVzu?Kdu&x%EqqfF_VOLqqv0ZMFyf3f6BhMVy=B-y^P?qKWj-bkb`aUO z>cYb0^2ECDSA{~2B&0|c3d5oTFBB3)UBh|>i4H~fg6R@%MT|t9)487@P#I8-#v+Ex zO!wsk4}$(u7x)i!4vE=@WO%~-Y5P|Elu|M9pA?07Q*Ebz&*Z#KN z_LM%rWhj%32LE#3-;oNLyHqqqFiEF+%8mdL{}5k@<|l!~X_kunYn%$gFX|+-2SlNc zVZAPfxu60+aEs_ty$s(E>ObSJ2E{XS^3ts>r zATvXh8=04Os4xQ38SThAO zgnng10Ckcg|3p<?SCfqsQU#RlLQe3S`G*X=3g?Y z$nL%ECqly=^_2dOpi$CNKpdP;*t88$YGcQQGX2w__o(+Z1sR)ZDO=S={kc`!di(50 zm&`2TT}qU%UxQ$6ISX%g%x@FzFH>h;ZgWsvNRP!q$cPLJ<%lW1R%XLzNR?cV@l01pH{~*=%7;+j#1dY?yNUzkw+6UV1*!FSOKV8!pg za@3`F8neQ0WPE-NksacY7o?XhMl}?~OWXFcDaB$?82OGEZ3rp>rp5;Y3u0ZD1ADKQ z%wBuaP25FPZbe2u$8G9pcqu^@f0oS8PZVNU7W4@WNDL|ztL7IyiX8C9V%&buvKGw( z%OPw-?Ajeai777paz@jbS#tAqO7uPy*P?}M?Yp{L->g3Rx4aof21@dXw z7beBZIzB1j)Y)Wx#QtV(#Rq!v@FfQ@zMf6g_tTcRU)Mt-^uMW~{Vh;TJ-7H?JL7ab zdN((8xAmkS;M8hQkcqUsHiwxXqd~8rAQ>Kug$??R6lqpu{c--@cX04J;G7a3Yuav< z2@`mQmB^u>e%j=b!xcF6D@t}o?UzkmUb;xrUZ=O$OP{ZopOW_y*xkfKM|*CJno+3c zMX~Z^pFv^zZlXAYMLSpfsp~`7ID$lqd*ke~n{Y6ZDTN=)3EZqvFcuz!KFVTppS2ok zuuX=wEmWM+3A!!$t_&oSiELDpu6OPQ@*Ev`TD_9@skQYrtV9jCY&9x&>Q{0lvOZ9F z*!f*O_P&(9Anx6{97s}QukkGB${YijkH!z9TE__44{pLR1pZn|IjdUKowOX`lpP(7 zsh;kJf2|2amO~_^ zC(fkFdIrjX)OR*n8q@qk|HUjSYv;AukQl2+o@T_aAJcEh^Zr`cf3@+>lc90bhT!Nn zv}EN}->v;NlS`*`6+3R#&gU5@>Csd@?2di5nzIYR-EHfPdaE^Eu7;`{>LB=ye@ zzFkDacK==R@yIr)T9D_qvc%WZPg8NPK9e3)pL| zD%$gIH4=2czg1|yX!}^S%M9(c@_bE+o+if6(_##vgG`$7yn%#BlzRI0lWrA!1J;WpABcj6GqZK zh7ifp<$1r`*eQJ`akIlmK%77ChroWmdt9EL*=A`m2Au~Skmm7w_`mk4z>^4>;{`4n z9o(Kp791EBUN@&G&(f+fzl>46sYX??R?E@vp)u4sERr>Amabb z1_(1)dCOBh`ua$nFZRw$b7_@iav|TM9`5=`Ip^5bdTo0PaH%#aPN@@eeXG5j{x>HX z=3_|-^Kz!29zBL!ANoCP1zS8e-enKw2fYSs@5g`tm=k`wZ&_+*wk06uYB*mGYD;5GH!)~)AAC=)`& z*{VXw{OpVm+-AtsU5Zxa2~P)9s9cDVG@sw*nr+X6T!P)7C|fOq`7e+?nuE~EhlpL%#yF5Uhr4%a}Tut{!2ySN|q|0xcgxtvPNn+bqLLRTtTi!1Rm`Myp z)bT?z>g6gAWo8{uRnkP$g@&ZCL#a3Y)53>)i1&xt{NnBHlAA(Ig zk5CP^nVSf{>9Q^YiAJO2;Sw25{gl&P)#45FDGu68L*2(O3;cB6l(G$c9~*BnicXhE zgdd*^>bD^u%x zy~v&X@{$F@?g__>c#xTc9RHBvd(E8{|Y!aaU7q)x27P zzj-aIvSnt~+MlW;B)27LDGwEPVpPVP&NXkLhSqVgDcU^lP}*IpsZ8C47rb=?YSr|` zr9%jRWjYFvha*dCOP7USg!yq2CoUbrvM=ysF?ZlKR$NKeflt3wCd5zN!SjM{uSa?* zZd!3X%CmGH8mh*w;w@aE^X%2d@cFx=%F^R{s%Hjjo7v`dsHQU9Ph};7R0?`2bKZU0 z`TPE4Ytu*(o*0R9%|hw1YEl#!G($?0-Ysn0V0xR(pE{}B-6{$L4XB%KMpsQ;{lBZG z6eh575FIA=5wY)j+AeE9ZFR-~NGNc``6vQMni}3bxOvPOzYohX8DeDS-iQ0^ziRD; zcVTtpO%41;d`M%i*43=|Ca)u5Y2u)x!R$x1rCGJ2l&!_brM0}7L~thCMDrlF;bm9g z>fBYQ*%;W%OMHPhd44X_NW)Qc_v8M$tAJq{oW^)Gzdu%+As~IO3F$z3d_r=Ge=&Yq zreYTe3S6_z;94(cXq4CQF}g}LKoFN>(Bw$QR^s)Svym%D{32MU8P41Q+y);(*P7yo zNnlH(@TF$Oc(LjALz*Oi}FA!mShe~&insBqGu#jp7BZsJ#TovLWnyGTi!H(^Fc)=s_SFONa zhR#Q?8NIaBso>X4hDXy~ST5H*{SrlN1u)NsjGvB!vyDetQWa(1ePR^@0R1Sf{O)Dd zxQj$}rfYM3G-dTrk3ug8AK=kgykOY}*r^bSF7a z84H8Mh)3Hzh}|d(c&^HAYc;^rp)!L_fwLdR4B>m67${JJKltF9UtMLR(}d!8H!oJZ zLS#2*w?>#Z1_kfU&r8@72t`vy9Y}6vV&PmNSX)}|;?Tr(a4Ig=_o$~(-0an z22Y)9VP;_`Xfqz=NBPnmCQ>FFsxpfg8tll0^wZ(vtTGD{e~wDW>Dpi>Ty+}DOhBq` zW8!o>H9?!viIwvojy{RVIZbDXQhXXMlZ8~T=oZsA2%|eD{_H1@7r@wMQMZ;;Sc}A` z2+3HO$1jor(mG2kH%9G^&GmrHh-FYGJE3>ujVFW7UGwvz z_oiOxx;Ms5Lt?tjQv(mkCd5VBP`Y?uS>HkaLD+6EX%KV7}r;ELO;83boiV7NeQ;Cg}Wlf_&tKzfGC-GJ?v z_*ps3!8^pprgT14uATG*U0Xj3V=1LFiqbWbs=)m@XVL529t=ht>m(OLHv&MeNoF0m zc{nyY-Sj{$1Fq{QZ4Y+RFX=x7F@{D`n3IFx z4*W0jBcw!Rf0lLANx@;}07_E^r(c?a6QNAKebrJhE<#qA7PGBij~uASwE zEGhCmOMz-4bAQ|}9I|zE%i8FOgUn&+<<%z!iv{Cn-Y^IRUKcWU*Nxrs`30Bf7IVJ` zDblJ>NUF-x#N>Qr{CONGI#^wKSzV}8uXQw7)!ahgT1g|Ape8~MS<`h`7I`AV!&b^1 z*Z7S~&k&X=+gIki;>yCPacF}R6*R{)<~*lm+oHqO+}upl!)fGv8E9#!wA9d241q>} zAZO+Xv}=E;9mJu~(gcr7=^i6)pLmP3Ft$>~+3Zcd= z9AB5&pbV${!If=uBf)CASTrE`W4&MqF?13g~7 z=ug)7K}uV$iaoi51LL`}3`3=ubQEzG&MftDC>sbqmi&Fwh2B_Le^Q_VqG{M6jp3KG zvTxOEE_aTJ2v!?lQ!ULW%a|9WNYu|n?Xf9eO9FakeGMsgV;;uf>!hpQUo=J{TA)t` zwhu;H4`j^GQs`Pu;7O5e=QoG+NlEIYMb}&CC{JC{GV1aV!IQf*K)|@l57e&UxTMUV zWTlq85x7i&G9OwWG`|CdE?+mB6h4T1(r;egeFx=7p7%V?iSqP?eEo+~X3iI0V6T?F z_9qS~(r_`I>_BPSOG#PUjJmqC>Sv20%)GN3UY7sdabu?;b$-8v+VzNR)S4$M9Ye`_ z`!yKt7xJysoU1vmY>hpE0-`;CQfNvA23BZTRG^AYZ2`|PQnV4|1cTd0A{mh@D(!6y zX<{ff1%&b!CU;Rx7M76hoLr)$FtcB`V;%mtR6H+}?NUuuR}YccZk7adP}G6Gy<;;R zoW+FU#Z`opL49BJf^=z0wgGxbwg5ygquPJ*{-u|phmVwO!moY}1QuPo9PKJjqHH+= z(%0=SrZ+Awj+|d?2@O_9ZA@?69w@GTn6c9-N=K8pRai>Xsci6m{cX9q4}N=QXZI<- z+DZHvumApTe25@c(x-X9vhg=P27d;v&2cV#F(}Z@%N(u7Drbf$fFpX=rT&vD6HH&u zX-hYE^2bouCJDq)4~{gEjE?l2yt;mKnC=CZ}m95OOVhf;Gy!%~ov8WH z^RM~ec&qm9V9nTnkwJ>UbX^WilzyS9{M86meFo3_W&A15I$xV(WdT9&I+${2B%PFH z28)MwShe4%QB0tuF%nNsdO%~x#hHW=fr+%ezGGZ91Xf;PO;vu^FM!_^f>=_hSYfT zRjNv!MP2x*@$28~&gJ3D&6{4GrXTkf-ET=rN*%7Bts27+h@}Xt0`s*2Z` zs+Ef)X7{~Wiwjwc&+_<-RI=3cO&U+@#d5OKJ!UrZ7sjpnv_Zr*^(W~^L1$GKt3825 zfdL}kfs$=yw708C#PK4uMELCtikA0h(eFJFap5pjs$#g!mNw)CE!W6cOf8A;%03kw zV+rz@BGBai9sB!iD@|%YRqWL0AAAcSN%!Sa7+hY601oP^R`{H>q&+bgt>VRUOcnFQ zYd+Im1u84h;rN;6T8i=-0WZ149weGitECw<`W=AP?VT$UuiHUHlMrI5>B(<4=2c8W z^I2U+CV#<7I08DL$9%xqgL&hkr9<4{?$B6DX2GaV^HXj|!?B7RVk%w@%5{ro1ymHF zLY8<05>t+?9CeJQyqhgmFQ@k7fm zm|BiALrh_nWx7FR1ytV(HhkME+Z;e$G@9+gI%shZLOz;d`sgRMnZQ%*w4u=Ail{to)>$nLefIyZ7J8g>3A4UC z8mCJRv=}7h`C5EY`?JoBp^E5=9(LVmJEJ!Jg2kF`>`AFpsh!CfGC!v*$JT-RTTOkx&ArHY z*zKag$3ysb;zUg>Vyjo;6n_Hw$^3i%0b@-$$+KU+qyPa5)F1>Lwb8&s1G!DEosXj; z51BBe@7K8PkJ?yU3m^KFNOkM{2x%PcB+iUUt$Za|X2F}&!Jsi;=&WM|V8Po6rujqz zN+MF(y!yTeY$QDl;>M$>EmU-2?x53HdGYZgQ$4u4*pQWh;F>RwCE!ZR<*K=YiSTWX zRiwn(!-rtssYKZ0^nHC0A3PGswI04_TJ~UEfTMKms8(-9sA;rAsqGv?e665o%f}^_ zsto)__!;^Dp<>(4yp1XG%?`K2aM~kspQmRyorHTg6LM^kAvLqeIm9>GH8?>W@QfNF zcrhwYl+WIQdW<=Ov;=+XqJcoh!~1|1iH!pb1uq1V*Mv6(hdjfJ0wd%p)-3^U$MEd? z$xcL8kL9{2e$@B6k?4OGv5bV0h!U_^VJ;TIiQ?UVJh3a;uT8gPt-uZ|r$5*)uHNUiVCROTF%K5n%o&-u!$EM8s?(rm^P znCDhKWr`E4;U=@hsXay+0%oa7HcuPhj_ikmfM!!mUc`FFKfQvqg?UA$MN%tfZ}c?F zkM1l+hKckXB(O0=NkPDo?fUq0<{#Z*=t?EjsdG!Hg`a%yS(;6tq8|GrA91}_y;$-- zbnxeyqbuYy%OgNj|GJfOydLq|6pvvbPV}0wG2Ci3_9yKLG%XHG(YTf1x7v*ZnA#u! zgy@SROY-XKZjr8^ZNuD541!EF4=d*-%~v>&K^4vn1-;&1zoW5AW~*qQ*N1R`QbdBC>gMhOOn)XU4B?T2wR=sYCM+C#?nt3ENR7z4wC3+@r@ zn{y}ewnkY~C_T{m-yKSrOx#@9dFJPI^|^`#`FQ4;5WBX_cx4TxrfP2fW-ug53PTKc ztU~t1cub7D-3nX$y!sL(;r5{GMn7IfKL1y(^@e0(Xu=IB5|dM5>?Ef zpxA>IdL9I|N)xaq5LJELxRcy=1STCVUq}}!lw^QJQ?ryiQ(@K~IMIVj?~woku5P&l zB#wlDN;5R3bkGn1oW?U>X7KTcg8Cs2JtyIGUDp`|7(^0IGJHE%=Z#9|Rsw4PyFGw! zOiF5@NQt@CToC7Y-%U$Qy_yCa^>o5t+v<*(Stb~9Ac_=qwK@c9J)L}w&K5r_3B%WB zORXENXv@k9Ir*vGB)+h0G*l3fY0WuF+r#E^n;&=TMO8gEJ<8vjxkA|Y-%x@Ysafe` zyZ005%TwicT@6cmWI1|B*^Q9f6~M8%3|#dll=FPiROb`Fi;SkFOJd=L9{bU=ooEvi zk(#7LL?PM9kNvZFW8M2CExLn|9y}~C+K_B;Top=J9f{M1jg6Rmp!0jw0o$4=QC__+ zivsY_Sc^dtjAgJ>M@(-+IH?RRmN)dtHEm6(da)0l$Y+0jeo1^LO_2TKbR+Z*VMYgKM z4wA>76xRDPwZ`|FfQB1>`9roejpalt%g#N!^;}R$0`);FUt&lrOnO|sM8dmZEG6au zD@KB~%CpM{_~`J@RK|-~fm&td@(-H@>q6ZMYCtL;p8)!?t)uOC4qye?153BrxxbWK zp#{ChlQ6uUphlL6rF1MR6k^QU+4^Sod0m0qe!a;J4#`u_gapmq>5IUn_FNriIDx8} z-qPGCW`r~|~1TX{@z2*k5-jGJa{ADTozo7FlX4609=jA_$ znR(Qj4cETq+S+OrZWaZmoLo(El^ySG_z)Gzveg7R^yMQjLu?Sr*$=~nB?1K=b@?TL zY%ejVZnkyw-#)vn*qrRlye}@7m00v?RTB$k4Rzm?!lc<;sVUJS zLg`>~(Ms$4t}NADf?iFz&Kbf_c`dSy$gup=G8xzeuq~eXPf>+&m@8bPcr%BVZsLIz zC3Fe^#*1$yxPmINcGKSYrx{lGq8C0ay1It4W)XcU+g3-d!$#ZX-w%gewrwSme8VHe zl)&!|WIgt1Pq=59_IUJv9!{`Iu)#b%hlpuMu|!!TrWmkoLts}5bT$Slb(}j19-}$P zxEYh?%t=a@3`);IHQQd{8hH7K4x9`uHCCLmoj!H69DTY=c=QX@Yjqsb2hq(t%dbF$ zr))zfTPOZ3>8y4~Zo@?Y*Dse3Lq>)q3&b$`*?m*`#Z;MZ49GlaxfbT7Z0+D#HDoo` z9#OukP@ZXk37}z&QgIt(3OV(s&)KVmdnxo`$CFUiTSo`d)&v^WGd+Adai8)#5mbr^ zZGJ+aY15ISLUmwi5GQQg=8S28K6I-A*vW!rT5%eeP$cSXuvaXaA5Lg{{* zSO8zzd%iqrf4W?3w}2bqk1%0Ej|~;WXmcvtb?D^=I4truTj9VgLl6c`L-bmHzoZ)( z{Pp}h-a+=e4&zeydrp7zg`Skp&)Q;}jZXYTI_iDH$t<3XNITsn)H5q%d-#r6-!AXl zebX+rcB3b_(!{tk%>+C6;T>b61>zMX<0;3SZ$_$d6_;4xjV)bhe^-iah#SWUB*t8iF1dQ(0wfyVUjB(195UOv_Cd=TlU zQI9gzL*rjT9|K|Uh8p8khC}XL9++HWyNLe$T{gO}a8pS{*;NT0ypfLd===fxkI$ti z1wo8Ind$-TlC!nhK+#sDkUUL^Q~7(29eZU@hesG(HWSQ8oc1Yf4)x8{NcBRqe-Oh?&x zTzFZV@p7?h^^$RJ2-X8d_x;&R(aY|7Yv01s=5p{$&e_-Uch~V?h*79BMQ>WAIwGB&G{4_VyIAlvYz}93yZy4Q=Rl9#T=*B^= z=cq<{z9|`Ih*Pp8ya(4Z{TCk4pkkUOwVc_``?ty9Y?*KWA=!1CTi8BWVK+htdHj%K z5exE%v=#y_XNwiqR5wO`OdeKm!c@y*5GNj@C!6cqJUA>&CKL@$!q<=}$1tw{7v3%Y zaO^AxK8N#U!_m>v+zFOp%gc(@FUyRA56L!<%K-|Y=|gymRkQ=~iBL+bHYkVuSc|aV zS(uxZyA!<3vA&Tbr-pm<8X>vlu-RqvOJBf{xWDiLLvYA@*z&e)(8y%%Swl5c?zua_jwXuH{ zJm4~!F!UOAQWR9BwsefAXlmUk%a;SA40-fhS<9VRPTjSJ1v!%(rsmgpiEwH2aPmQT zAz>gvk@ZFgU$cPlRo7KPgh(7N!b?|gLfqf6DFsY*X3q>-@Z=ARv6@6vP?qwNY^^*DwWB~OEiWnlnJ?g5r?>DKrEQ^M#E6ty|I>e z3W5f))xnBp>F&72h$p_O4%ryRmL4&%hIG)bkxWXx^n zksi=EgCa9IjNwim^wbV`B@O~Hu@Ljb{b{uHa5e~#6BpB zs7{EiPLY}|;=W`zZQjo1C!&PPQSo(r&O%S->})I+#g$QyX>x(_B<`ww5(`0!`R_rR z0p5ij;l*?X=|s@wEC8j1t+F>!+AiV(N3Kz&6`myb%uBk&3Gpn!PegoqS0du?9Tw}B zad=#6e=i3@#m{3L!#;FLflJrcexF?_6)|GkJ3{N7>lc6}^zv~FzlUbkS& z;gXZFvtu;JarWRH)?r;&hC`@cL{DMl%yeBhTLNJ@O0~-df09qgFT;o~}_eDb}P;jTJ{)caM}L6?wtmiT7#;#WsmRZT8e=u+7fOtyE4C52C7si4T7%RFWO<3aOL(~28x`maMx`1qF|qrek%+bH`@h~jy@1rSj3UG zzSH}vmAD`6t|U)UXPC`VMr&{$$*D6CL|lskakx%;jU%~0H3Nl+uO^T-<&qd6(Oq;z zpC(Cn&KtBOzmB0ML5Sa?)ApK6X2JQ~IVoaR*0rB~H#9U|_npoxn+!crz?&OGqsx~iA|n{O))0n)_FNM|F--7q(bAXv7*m1AMGKRx=uUS z&kOx&m2-0x8z)m!x&|7GeEcj^BvvwrDpX`)K31=l-o1G|ySy%C3dHnwtn~R`OXb#z>(tw zbrAA~ZFoWELYAX(@7ec>ACEB*u}Ih6N;HH@0i)q-~^OmCNR|E>x_ zGlm@kciizRCGrI#F#!&N+*vXweuR(3Fi&b}%%v~@P@@dB7#2vO|I3lgvFyt=Cq7zL zWhi>P8~b};X8rVJrW7-`KH<}6_13p$A}|-}%#geE_c3J3lb`{A5Bk`}0z)SP*_vsi zUlMc}EPW%W@i54&uCiE6wI*}u(xxdx6zBpU&sI+pMrY5{%G5?gnhqDFXX{*!A0ZKy z{b@dX;o>%rnBj{Y#HTXlM+O1Tr+wx9I`(IHo%nN~jGEAbIV(ekN-59^wU$uc{A+w` za~l^{;$veQ=9Tld9Ih%8g~8M1#dB49(4@!3?+1s%3gtTXD%_;trK^N{EG0uP0g=F^bs|()hKM4{{ueOMG91ftNoYPj zBkY=2(kKw&u*vrKUeF>0N(6vSn=zML`hRb!4;%eF@@xiG@j7=;P4Wqv=TY#-yrVpPEkKEZEDc_qZbh zC8EC?hCStJTy9h_Dy)EYU`m*=pR;$7CM`E14&8%Ze=h=Dy!2abn@M?WPkNg8wnp<0BkXvXF%1iW6Xqk)lM~9v4frVk^DRK{3D$^Qd)Id3SqzM5)wb3C6wro34>6 zN5US2yxYYqBi9|mXpT&N4gG<3sTz$N)v}F^+ZgAHZ{=(ssXTPOU~Y{+yqo7$c&S6MwT!Swuqxzv{O#r$KU_MN zmqkZ0-^M{=6ItkFadP&qLH)D;PvvueAn9uS{X5^00hq{XTE)b=hBS$kc&5{_Es4Hz zwLxmx>MOS8E-fy8;|u?B%bwe|-SUehhpsBASR8DC7>)+&YqCzam2zPTlq+?Bn+_={ zB(f7X^js#>o=wGQrJ_SBQ86qB+a7@5p8b`@4ENGoZOhO(gBhs5zeLV9GgZ%W#KvZ1U~iJUp_T ziaVE25y1!QJDe;;SoxGJVLMq#7*8!pVJ^$h3FFB0fndKe0(bmHgC$Z&sgO((@1*xU z_m!vJpMO33m%pTrbiNb)b97>TZ=38Zb6`Wmlq{3QwOkeSLtO9BwVdl&jr0{vYBS@S zJ~_KQH-2&VmQgtp4Rt4HSElCjF^%XJQWs>z5{Wpd9iq)hbvvo`bR}1HQPY;io`CvF45Y zUve-+Jy;oJgAY0&oDF6tGkV?Eo_1gP<~yc71E3@Q*~P0RUyTsY8pIZeMOL*Jzqe)1I2mSL>GQ#}$x_3gz0xNf*3_cs8Dh89g0sxl9TUU_+#A$8MOQ zZ(MNl0NF;i9h9Waqg_vY_b{7$__lfQm%mv2(|-h|W^B7tqDz7Is?b@vgifqT4ttUP z>T%GCjb&krdOme8++=`ho1n9U8h1=<9G#=jyi4lC*99 zc~}KpD^N=9*+g$|@4&#|&_Gu@PRiEB*-7Ai!Aa7oSbtB)=s;IT4it_=$F(@+dZSot z)GabO1uo^do>;xX3J^LpnfQZm|D~=YJg{EV_db&4jKFsuq&Yhr(gI8Bk0h8ELQ z+riDIsl{`NRNMBALwDY^ar?%8&+~#W=$hK!lNlb!b>!LB)*H`p2t+_9)jia6LSYJEji8Xf+VJkGxCjGXT0_Ad6iuZ(55DvqT4!Xk;=2(HjIjlFm^+^2jtj z$JM7!KTqbzAiX1-y#2bv3l1HBW!b{)#tNAM@OveLfEpp44z^CZm;bfvO=&-aA2VYvFO{Sz~!f{KsQ!wqi z?a;lolw$j)D|h~pS5e*h(!1n=`Np@*b3Y~HI56Tq6Kcsuwru5t6m|D~V)>P@PnFoF zO52Rj9&%58?_Y)b?ozq_gLBo7{KrcJx8QhMDa@;89oQf%IHi+1XH}C^i5M9a=D0SE znvrfrRVgn|)+z;zG+k33KXd^Y>9+ zIecl^Q0buejr;Nazc;t(o<^e6%g-Zc;ho`WZc%bcea~p^kw*|pD7lfyz{=47#s2ge z&ILui;X~oL4zcNy?J(kRoamf;!F%Jew@ZCzV#$2vlD4t8Rs0W~^V}h2>waQW1~#~R z?#8K{JU^b*rJnZq(n@)DvCJlH(ZOOQKVvyI+`nNYz{_<>iX8vs#VQX+&&ekLA^fs(D%8sJn{D^1dqtSYA`it2{l_`csoH$5n&JNV}2U=of! zAG;&6b$8?GQ%G0kOyceF;L%4353<8zI-c8M6l@^ik4sQ@f2g(OG1wN@XNq_B1@H9t z-vRZDfBuN;j^ULlZA-uSQSF;AE8Fi?y83wws4RPZW8SVioNV8KT#mbqVzoKHRA$4& zLH{HY2557O&5$9@PAC!6e*EG%TLJJ+S1vKqmjSELv>gCE@Bk#Jq$uS|b82p7DPI8> zOw(lG=VIsfnf(vRw|)#|x`kvmQZk*5M;}gncpp+Zx@|mutn#I2%oi`{`^Vlk555CD zNWwZmn71c!>I*X+SV4)R5fvo#84De&j9u>eL++V(NPYF-uPeI`TE{Nqfb#R`^cxUtO|YQ5>{ho$H=Ab{SCb>S)GqzQxq~StAUj%g9>`~ z)hlcTz^fyd=gMc1>B{U%vC;q>1uJQ#Z_gWBfBE9Ttti{g&_`sS zckpaxC*-WH4t8+rKm1(!_r4$}x@-UV&9}{i-J`YdK8hsJMZ@CUB_uSnu<2xqvin2g z2SFH=_i3L0nS1t~Qvc>(QTN`BVkx5nzrve7#+G4$3DOMckIPOZmIyGCY)S3iV zscLOt$E6#7Woq=!hLI8WF^p^qOzy(5ol8fIn>H}&r#|%sCEG9eY>*_Q_Rrta_q}}{ zeD6C5Qy$=;n^ml)Bq1!c_LknB(v63A^ z13L<50U%-Bb3~%6WACwEpN!x489CEOsZWw^;Cm{aV@QhCzWK=8<3Tp5j`cL2I&2Kz z6!xs*h`gcLuYXZ26kXnz4*YV8b0B_}V{CcVXXOBq9VQ=>QTSpUbyjOqlQ+#Lk zaQkR-Ve_%SvtRk~wZ1sk-?n8m3l{c;6LYi$p0Gfn(}myiqWXJ282|9^ibx!rb@QpO znXiSH)AxKJe)n&Y3 zp$ot-=@&*6hkMfd&KHQ^y1i0zZ}HC_^+;dQbx~j2`7xkAzK)mu+LPdG(PmLkXq!Ub zr4zrsZ|qIJ_@DpcbK@&GW@wnt6(^busDC{dwO;*kOFxzQ$`fDw?T?Aa{QH|XI+srR z8#1`a!Pco~{`Rw<@K50s|I4?Wsg>)bzG@uMfcl<N7XHFBf)LG@Ez~`amogVmQ7;!(g}Cw;#+=kYUK9kY6Cc#jabv` zzNj;PLShjc+m+Vb9FBxCNYaJ`*JzAW{Lt9=QNn}C(H-|YYTaXAye7WLI_A5}IUT>U(Yi;F+};SX1T>*0qVhJTM8 zJNA_e$CUNE0L;4+r+zcJwC7_72L}h)F;6}9)b#WkTiAZ#m+pXnfA8s~6DT zd!#S@r?+*{uby+&kzFKn;(BYADq#?R7SZ%tHdmAM4R^qs#Xr4fI1+>vsIM3|8k_GA z_itMx9!z5x$Zv}eX2S8^>%+hbQg_}*c<|?I@PJc)>x1g1+oZK-2h7jU|IVL(P8r-L z?mb|=_$?Os{_>Zc;Z~fM91>XfCK0IEPBK;rI7e9U*JLZ$0 z{G|QSA4vP|Tl1oy`l~M}8+IcptgmVQtUheHF-d&4+PN<#5UhAjyGO=%mW!&#(o0|iH@Keh{>_ad?V zgh=5y*`Bv$y-N<+`Q#evn5b!n8s+QXNQ_kx1KC}wOaPm%#L#pZW&;DXvSzn%cn|Uw6=0_aN|q*#nXK2uJ5CUi;2f2}M>(*g6oI zO2Z2jVIA184*tuxQlGr5{MV1Gn}0#wa4_5+i!f7OQ`sRp=moJ|k6RjQ2m?G4q+go> zL{u>^A6T8%KQ`Q%n&*LQD*d$?fccbeEJgYCW#oSKiW5Ldw%v3ileD zgZB3J{{8><$LyC@)rP|X4saD?-9cGTRX=}7AN>WrfA4BdD81=6$ZFV%8WxF$15jF~ z!EuLiXpb_uhp8tl+>zL$(JIHYXZ-CwT7?5sT99d8YpwvtbuS$k_79T2n(uh{>pz80 zVz>V);Z0;-b97;`opC9ZTP2-w-NS1F2Y7XNcZ+JLb!`PZv@@{YzgO$q8`N4!k*aij zT`NG$MTWEKg_aK#_BHv8H8i0 zMY(8=9E^>DSzF_d5K{WKsw4aL%?Bki!U<^`5B|w_5B<-d`|_tAypw%pO&J9J^Wr}| zC*SyCVTk^ksv6uVmuG0b)?+AYaEba{rGh^9xzBy+k>`Hr<9D%pV1NGofBoa&W{|jj z&s&T5TO6bYPzkqg_#g$bIX-uV`v~I18)3AfFhcP9UkLjfebbg>;DMY1NY{LejOOIY z#^gzD=iSBt7|`@YHa#|xMa1iEo%}JOSolh#PkHquKv(t8NRkD8bmkYg_}CFbL7=_fMQS0slVp%rk%TtwY*|gM8~;u(jA$-TBbqZaAz;HXN|2N@IxNtI`!v2&ldRM z!-ug|=Y3W1{o0aOU7jAk|EuSvpk*Yqz##j}SH8GM+O=yJG0)SdC423sPbQP>n3mTU zzxc)Eou8Ex?~{{Vbc!Cj9u2bE#$ySgx3|=ztQ4vu!Zy_a%LrR(M0L6_Dg^mMal{Su z#9@uC4sFktAUoaaEMva2fm>aAPwm;QTf?@+r=Na$cF$*IBf~DG% zcAW{)qo8skC@Z{X9ioJ=7~+V!JM{F<BfJG8fY(bv)=QnbpL-L&U-KluHlYcelLji5JIHb|22yJd|kj$~r`A7OC< z1qybY32VB7IVmXUJ$q{{bnQFF{z%fb5a-IIo1c=TmK~i0FFHC^JiKT<>b;@BSbujpbk6gT-mAKaKD|A3=c8JA!1_uZ4 zx#u2u`+)}@*t2JPO<#;Dbbke0)AFEjTCnfNc*0hWlC*XJ3a`cOf8`erRsizl93vEo zz#%Nm@CZZ#`Qu%Mso_#sXK^iIl%#MH3tCH5kDrrF1`CTkorlB%d=!p^MAHASFEy;o!Qm(;Y1M?{U^@ao@`=Jxa>3q* zsAa-d>w3o5P(y(eNH8KfgY%ZsV{E>sWjN0o2MJr32EG3n2a|@cCzXu;zg`IcuK)u8 Xh#)nR5nB3o00000NkvXXu0mjfldJd4 literal 0 HcmV?d00001 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b02ba46..b57b333 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.0.0-SNAPSHOT +version: 0.0.1 api-version: 1.14 softdepend: - EliteMobs From 63d272d9529ba12ee50879473cf3938973a0bc70 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 29 Jul 2024 15:37:51 +0100 Subject: [PATCH 02/33] ResourcePackManager 0.0.2: - [New] Data retention changed to 24 hours - [New] Changed communication to avoid having players download a fresh copy of the resource pack after every server restart - [Fix] Fixed nesting of the blueprint resource pack - [New] Added MMOInventory compatibility - [New] Added ValhallaMMO compatibility - [New] Added BetterHUD compatibility - [New] Added 50000KB RSP size limit - [Tweak] Fixed up debug / warning messages First public alpha test! --- pom.xml | 42 ++++- .../magmaguy/resourcepackmanager/Logger.java | 14 -- .../ResourcePackManager.java | 26 ++- .../autohost/AutoHost.java | 77 ++++++--- .../commands/AdvancedCommand.java | 36 ---- .../commands/CommandManager.java | 104 ------------ .../DataComplianceRequestCommand.java | 22 +-- .../commands/ReloadCommand.java | 23 +-- .../config/BlueprintFolder.java | 11 +- .../config/ConfigurationEngine.java | 148 ----------------- .../config/DataConfig.java | 34 ++++ .../config/DefaultConfig.java | 25 ++- .../config/UnusedNodeHandler.java | 31 ---- .../listeners/ResourcePackGeneratedEvent.java | 16 ++ .../resourcepackmanager/mixer/Mix.java | 14 +- .../playermanager/PlayerManager.java | 2 +- .../thirdparty/BetterHUD.java | 15 ++ .../thirdparty/EliteMobs.java | 2 + .../thirdparty/FreeMinecraftModels.java | 2 + .../thirdparty/ItemsAdder.java | 2 + .../thirdparty/MMOInventory.java | 15 ++ .../thirdparty/ModelEngine.java | 2 + .../resourcepackmanager/thirdparty/Nova.java | 2 + .../thirdparty/Oraxen.java | 2 + .../thirdparty/ResourcePackManager.java | 2 + .../thirdparty/ThirdPartyResourcePack.java | 86 ++++++++-- .../thirdparty/ValhallaMMO.java | 15 ++ .../utils/SHA1Generator.java | 2 +- .../resourcepackmanager/utils/ZipFile.java | 155 ------------------ src/main/resources/ReadMe.md | 20 ++- src/main/resources/plugin.yml | 2 +- 31 files changed, 357 insertions(+), 592 deletions(-) delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/Logger.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/commands/AdvancedCommand.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/UnusedNodeHandler.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/utils/ZipFile.java diff --git a/pom.xml b/pom.xml index 239370e..f173581 100644 --- a/pom.xml +++ b/pom.xml @@ -5,11 +5,11 @@ com.magmaguy ResourcePackManager - 0.0.1 + 0.0.2 - 22 - 22 + 17 + 17 UTF-8 @@ -22,6 +22,11 @@ central https://repo1.maven.org/maven2/ + + magmaguy-repo-releases + MagmaGuy's Repository + http://magmaguy.com:50001/releases + @@ -47,12 +52,32 @@ httpclient5 5.3.1 + + com.magmaguy + MagmaCore + 1.0 + compile + + + com.magmaguy + FreeMinecraftModels + 1.4.0 + provided + ResourcePackManager + + maven-compiler-plugin + + 17 + 17 + + + org.apache.maven.plugins maven-shade-plugin @@ -77,4 +102,15 @@ + + + magmaguy-repo-snapshots + http://magmaguy.com:50001/snapshots + + + magmaguy-repo-snapshots + MagmaGuy's Repository + http://magmaguy.com:50001/releases + + diff --git a/src/main/java/com/magmaguy/resourcepackmanager/Logger.java b/src/main/java/com/magmaguy/resourcepackmanager/Logger.java deleted file mode 100644 index 3881571..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/Logger.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.magmaguy.resourcepackmanager; - -import org.bukkit.Bukkit; - -public class Logger { - private Logger(){} - public static void warn(String message){ - Bukkit.getLogger().warning("[ResourcePackManager] " + message); - } - - public static void info(String message){ - Bukkit.getLogger().info("[ResourcePackManager] " + message); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 6fcfe8b..471567f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -1,18 +1,24 @@ package com.magmaguy.resourcepackmanager; +import com.magmaguy.magmacore.MagmaCore; +import com.magmaguy.magmacore.command.CommandManager; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.autohost.AutoHost; -import com.magmaguy.resourcepackmanager.commands.CommandManager; +import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import com.magmaguy.resourcepackmanager.config.BlueprintFolder; +import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; +import com.magmaguy.resourcepackmanager.listeners.ResourcePackGeneratedEvent; import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; import org.bukkit.Bukkit; -import org.bukkit.plugin.Plugin; +import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; public class ResourcePackManager extends JavaPlugin { - public static Plugin plugin; + public static JavaPlugin plugin; @Override public void onEnable() { @@ -22,15 +28,21 @@ public void onEnable() { " | /\\__ \\ _/ |\\/| / _` | ' \\/ _` / _` / -_) '_|\n" + " |_|_\\|___/_| |_| |_\\__,_|_||_\\__,_\\__, \\___|_| \n" + " |___/ "); - Logger.info("ResourcePackManager v." + this.getDescription().getVersion()); + Bukkit.getLogger().info("ResourcePackManager v." + this.getDescription().getVersion()); + MagmaCore.createInstance(this); + plugin = this; - DefaultConfig.initializeConfig(); + new DefaultConfig(); + new DataConfig(); BlueprintFolder.initialize(); Mix.initialize(); - AutoHost.initialize(); - new CommandManager(this); if (DefaultConfig.isAutoHost()) Bukkit.getPluginManager().registerEvents(new PlayerManager(), this); + CommandManager commandManager = new CommandManager(this, "resourcepackmanager"); + commandManager.registerCommand(new ReloadCommand()); + commandManager.registerCommand(new DataComplianceRequestCommand()); + Bukkit.getPluginManager().registerEvents(new ResourcePackGeneratedEvent(), this); + AutoHost.initialize(); } @Override diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 412ca91..d7ea962 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -1,7 +1,8 @@ package com.magmaguy.resourcepackmanager.autohost; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.mixer.Mix; import lombok.Getter; @@ -13,6 +14,7 @@ import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; @@ -24,47 +26,57 @@ import java.util.UUID; public class AutoHost { - // private static final String finalURL = "http://localhost:3000/"; +// private static final String finalURL = "http://localhost:3000/"; private static final String finalURL = "http://magmaguy.com:50000/"; private static final UUID pluginRSPUUID = UUID.randomUUID(); private static BukkitTask keepAlive = null; @Getter private static String rspUUID = null; + private static boolean firstUpload = true; private AutoHost() { } public static void sendResourcePack(Player player) { if (rspUUID == null) return; - player.addResourcePack(pluginRSPUUID, finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); +// player.setResourcePack(pluginRSPUUID, finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); + player.setResourcePack(finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); + } public static void initialize() { if (!DefaultConfig.isAutoHost()) return; if (Mix.getFinalResourcePack() == null) return; - checkFileExistence(); - keepAlive = new BukkitRunnable() { - int counter = 0; - + firstUpload = true; + new BukkitRunnable() { @Override public void run() { - if (rspUUID != null) { - counter = 0; - try { - sendStillAlive(); - } catch (Exception e) { - rspUUID = null; - Logger.warn("Failed to autohost resource pack!"); + checkFileExistence(); + keepAlive = new BukkitRunnable() { + int counter = 0; + + @Override + public void run() { + if (rspUUID != null) { + counter = 0; + try { + sendStillAlive(); + } catch (Exception e) { + rspUUID = null; + Logger.warn("Failed to autohost resource pack!"); + e.printStackTrace(); + } + } else { + checkFileExistence(); + if (rspUUID == null && counter % 10 == 0) { + Logger.warn("Failed to connect to remote server to autohost the resource pack!"); + } + counter++; + } } - } else { - checkFileExistence(); - if (rspUUID == null && counter % 10 == 0) { - Logger.warn("Failed to connect to remote server to autohost the resource pack!"); - } - counter++; - } + }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 12 * 60 * 60 * 20L); } - }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 60 * 20L); + }.runTaskAsynchronously(ResourcePackManager.plugin); } private static void checkFileExistence() { @@ -76,21 +88,27 @@ private static void checkFileExistence() { public static void initializeLink() { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost httpPost = new HttpPost(finalURL + "initialize"); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.addTextBody("uuid", DataConfig.getRspUUID(), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); + httpPost.setEntity(builder.build()); + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseString = EntityUtils.toString(response.getEntity()); rspUUID = responseString.trim(); + DataConfig.setRspUUID(rspUUID); } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); + e.printStackTrace(); } } catch (Exception e) { rspUUID = null; Logger.warn("Failed remote server initialization."); + e.printStackTrace(); } } public static void uploadFile() { try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - Logger.warn("uploading file"); HttpPost uploadFile = new HttpPost(finalURL + "upload"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -102,7 +120,15 @@ public static void uploadFile() { try (CloseableHttpResponse response = httpClient.execute(uploadFile)) { } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); + e.printStackTrace(); + return; } + Logger.info("Uploaded resource pack for automatic hosting!"); + if (firstUpload) + //Recover from a reload by sending the pack to online players + for (Player player : Bukkit.getOnlinePlayers()) + AutoHost.sendResourcePack(player); + firstUpload = false; } catch (IOException e) { throw new RuntimeException(e); } @@ -128,9 +154,10 @@ private static boolean sendSHA1() { return Boolean.valueOf(responseString); } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); + e.printStackTrace(); } } catch (Exception e) { - + e.printStackTrace(); } return false; } @@ -146,6 +173,7 @@ private static void sendStillAlive() throws IOException { try (CloseableHttpResponse response = httpClient.execute(httpPost)) { } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); + e.printStackTrace(); } } } @@ -180,6 +208,7 @@ public static void dataComplianceRequest() throws IOException { } } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); + e.printStackTrace(); } } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/AdvancedCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/AdvancedCommand.java deleted file mode 100644 index a6a651a..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/AdvancedCommand.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.magmaguy.resourcepackmanager.commands; - -import org.bukkit.command.CommandSender; - -import java.util.ArrayList; -import java.util.List; - -public abstract class AdvancedCommand { - public final List aliases; - public final String description; - public final String permission; - public final boolean onlyForPlayers; - public final boolean enabled = true; - public final String usage; - - public AdvancedCommand(List aliases, String description, String permission, boolean onlyForPlayers, String usage) { - this.aliases = aliases; - this.description = description; - this.permission = permission; - this.onlyForPlayers = onlyForPlayers; - this.usage = usage; - } - - public abstract void execute(CommandSender sender, String[] arguments); - - public abstract List onTabComplete(CommandSender commandSender, org.bukkit.command.Command command, String label, String[] args); - - protected List trimSuggestions(List suggestions, String input) { - if (input.isEmpty()) return suggestions; - List newList = new ArrayList<>(); - for (String suggestion : suggestions) - if (suggestion.contains(input)) - newList.add(suggestion); - return newList; - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java deleted file mode 100644 index bc6ed70..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/CommandManager.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.magmaguy.resourcepackmanager.commands; - -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; -import org.bukkit.plugin.java.JavaPlugin; - -import java.util.ArrayList; -import java.util.List; - -public class CommandManager implements CommandExecutor, TabCompleter { - public final List commands = new ArrayList<>(); - - public CommandManager(JavaPlugin javaPlugin) { - javaPlugin.getCommand("resourcepackmanager").setExecutor(this); - registerCommands(); - } - - private void registerCommands() { - registerCommand(new ReloadCommand()); - registerCommand(new DataComplianceRequestCommand()); - } - - public void registerCommand(AdvancedCommand command) { - commands.add(command); - } - - public void unregisterCommand(Command command) { - commands.remove(command); - } - - - private void sendMessage(CommandSender commandSender, String message) { - commandSender.sendMessage("[ResourcePackManager] " + message); - } - - @Override - public boolean onCommand(CommandSender commandSender, Command cmd, String label, String[] args) { - if (args.length == 0) { - sendMessage(commandSender, "Valid commands:"); - commands.forEach(command -> commandSender.sendMessage(command.usage)); - return true; - } - - for (AdvancedCommand command : commands) { - // We don't want to execute other commands or ones that are disabled - if (!(command.aliases.contains(args[0]) && command.enabled)) { - continue; - } - - if (command.onlyForPlayers && !(commandSender instanceof Player)) { - // Must be a player - commandSender.sendMessage("[ResourcePackManager] This command must be run as a player!"); - return false; - } - - if (!((commandSender.hasPermission(command.permission) || - command.permission.equalsIgnoreCase("") || - command.permission.equalsIgnoreCase("resourcepackmanager.")) && - command.enabled)) { - // No permissions - commandSender.sendMessage("[ResourcePackManager] You do not have the permission to run this command!"); - return false; - } - - command.execute(commandSender, args); - return true; - } - // Unknown command message - commandSender.sendMessage("[ResourcePackManager] Unknown command!"); - return false; - } - - @Override - public List onTabComplete(CommandSender commandSender, Command cmd, String label, String[] args) { - // Handle the tab completion if it's a sub-command. - if (args.length == 1) { - List result = new ArrayList<>(); - for (AdvancedCommand command : commands) { - for (String alias : command.aliases) { - if (alias.toLowerCase().startsWith(args[0].toLowerCase()) && ( - command.enabled && (commandSender.hasPermission(command.permission) - || command.permission.equalsIgnoreCase("") || command.permission - .equalsIgnoreCase("resourcepackmanager.")))) { - result.add(alias); - } - } - } - return result; - } - - // Let the sub-command handle the tab completion - for (AdvancedCommand command : commands) { - if (command.aliases.contains(args[0]) && (command.enabled && ( - commandSender.hasPermission(command.permission) || command.permission.equalsIgnoreCase("") - || command.permission.equalsIgnoreCase("resourcepackmanager.")))) { - return command.onTabComplete(commandSender, cmd, label, args); - } - } - return null; - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java index 7d78ab2..b736562 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java @@ -1,5 +1,8 @@ package com.magmaguy.resourcepackmanager.commands; +import com.magmaguy.magmacore.command.AdvancedCommand; +import com.magmaguy.magmacore.command.CommandData; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.autohost.AutoHost; import org.bukkit.command.Command; @@ -11,13 +14,16 @@ public class DataComplianceRequestCommand extends AdvancedCommand { public DataComplianceRequestCommand() { - super(List.of("data_compliance_request"), "Downloads a copy of all data associated to this server from the autohoster", "*", false, "/fmm data_compliance_request"); + super(List.of("data_compliance_request")); + setDescription("Downloads a copy of all data associated to this server from the autohoster"); + setPermission("resourcepackmanager.*"); + setUsage("/fmm data_compliance_request"); } @Override - public void execute(CommandSender sender, String[] arguments) { + public void execute(CommandData commandData) { if (AutoHost.getRspUUID() == null) { - sender.sendMessage("[ResourcePackManager] Seems like the auto-hoster is either disabled or not working, no data is stored in remote servers because no connection to remote servers is established"); + Logger.sendMessage(commandData.getCommandSender(), "Seems like the auto-hoster is either disabled or not working, no data is stored in remote servers because no connection to remote servers is established"); return; } @@ -26,18 +32,12 @@ public void execute(CommandSender sender, String[] arguments) { public void run() { try { AutoHost.dataComplianceRequest(); - sender.sendMessage("[ResourcePackManager] Data compliance request completed, check ~/plugins/ResourcePackManager/data_compliance to see all data the remote server has stored for this server."); + Logger.sendMessage(commandData.getCommandSender(), "Data compliance request completed, check ~/plugins/ResourcePackManager/data_compliance to see all data the remote server has stored for this server."); } catch (Exception e) { - sender.sendMessage("[ResourcePackManager] Failed to request data, check console for error logs!"); + Logger.sendMessage(commandData.getCommandSender(), "Failed to request data, check console for error logs!"); e.printStackTrace(); } } }.runTaskAsynchronously(ResourcePackManager.plugin); - - } - - @Override - public List onTabComplete(CommandSender commandSender, Command command, String label, String[] args) { - return List.of(); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java index 7da4f08..51dfa2e 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java @@ -1,7 +1,10 @@ package com.magmaguy.resourcepackmanager.commands; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.command.AdvancedCommand; +import com.magmaguy.magmacore.command.CommandData; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import java.util.Collections; import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @@ -9,22 +12,22 @@ public class ReloadCommand extends AdvancedCommand { public ReloadCommand() { - super(List.of("reload"), "Reloads the plugin", "*", false, "/fmm reload"); + super(List.of("reload")); + setDescription("Reloads the plugin"); + setPermission("resourcepackmanager.*"); + setUsage("/fmm reload"); } public static void reloadPlugin(CommandSender sender) { ResourcePackManager.plugin.onDisable(); ResourcePackManager.plugin.onEnable(); - sender.sendMessage("[ResourcePackManager] Reloaded plugin!"); + Logger.sendMessage(sender, "Reloaded the plugin!"); } @Override - public void execute(CommandSender sender, String[] arguments) { - reloadPlugin(sender); - } - - @Override - public List onTabComplete(CommandSender commandSender, Command command, String label, String[] args) { - return null; + public void execute(CommandData commandData) { + ResourcePackManager.plugin.onDisable(); + ResourcePackManager.plugin.onEnable(); + Logger.sendMessage(commandData.getCommandSender(), "Reloaded the plugin!"); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java index 5abc7b7..3a8c9f4 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java @@ -1,8 +1,8 @@ package com.magmaguy.resourcepackmanager.config; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; -import com.magmaguy.resourcepackmanager.utils.ZipFile; import java.io.File; import java.io.IOException; @@ -14,10 +14,10 @@ private BlueprintFolder() { } public static void initialize() { - Logger.warn("Creating blueprint folder"); + Logger.info("Creating blueprint folder"); File blueprintDirectory = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "blueprint"); if (!blueprintDirectory.exists()) blueprintDirectory.mkdir(); - Logger.warn("Copying image"); + Logger.info("Copying image"); File imageFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.png"); if (!imageFile.exists()) { try { @@ -27,12 +27,13 @@ public static void initialize() { e.printStackTrace(); } } - Logger.warn("Copying mcmeta"); + Logger.info("Copying mcmeta"); File mcmetaFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.mcmeta"); if (!mcmetaFile.exists()) { try { InputStream inputStream = ResourcePackManager.plugin.getResource("pack.mcmeta"); Files.copy(inputStream, mcmetaFile.toPath()); + inputStream.close(); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java deleted file mode 100644 index 024b65c..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/ConfigurationEngine.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.magmaguy.resourcepackmanager.config; - -import com.magmaguy.resourcepackmanager.Logger; -import com.magmaguy.resourcepackmanager.ResourcePackManager; -import com.magmaguy.resourcepackmanager.utils.ChatColorConverter; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.List; - -public class ConfigurationEngine { - - public static File fileCreator(String path, String fileName) { - File file = new File(ResourcePackManager.plugin.getDataFolder().getPath() + "/" + path + "/", fileName); - return fileCreator(file); - } - - public static File fileCreator(String fileName) { - File file = new File(ResourcePackManager.plugin.getDataFolder().getPath(), fileName); - return fileCreator(file); - } - - public static File fileCreator(File file) { - - if (!file.exists()) - try { - file.getParentFile().mkdirs(); - file.createNewFile(); - } catch (IOException ex) { - Bukkit.getLogger().warning("[EliteMobs] Error generating the plugin file: " + file.getName()); - } - - return file; - - } - - public static FileConfiguration fileConfigurationCreator(File file) { - try { - return YamlConfiguration.loadConfiguration(new InputStreamReader(new FileInputStream(file), StandardCharsets.UTF_8)); - } catch (Exception exception) { - Logger.warn("Failed to read configuration from file " + file.getName()); - return null; - } - } - - public static void fileSaverCustomValues(FileConfiguration fileConfiguration, File file) { - fileConfiguration.options().copyDefaults(true); - - try { - fileConfiguration.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - - } - - public static void fileSaverOnlyDefaults(FileConfiguration fileConfiguration, File file) { - fileConfiguration.options().copyDefaults(true); - UnusedNodeHandler.clearNodes(fileConfiguration); - - try { - fileConfiguration.save(file); - } catch (IOException e) { - e.printStackTrace(); - } - - } - - private static void setComments(FileConfiguration fileConfiguration, String key, List comments) { - fileConfiguration.setComments(key, comments); - } - - public static Boolean setBoolean(FileConfiguration fileConfiguration, String key, boolean defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getBoolean(key); - } - - public static Boolean setBoolean(List comments, FileConfiguration fileConfiguration, String key, boolean defaultValue) { - boolean value = setBoolean(fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comments); - return value; - } - - - public static int setInt(FileConfiguration fileConfiguration, String key, int defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getInt(key); - } - - public static int setInt(List comments, FileConfiguration fileConfiguration, String key, int defaultValue) { - int value = setInt(fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comments); - return value; - } - - public static double setDouble(FileConfiguration fileConfiguration, String key, double defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getDouble(key); - } - - public static double setDouble(List comments, FileConfiguration fileConfiguration, String key, double defaultValue) { - double value = setDouble(fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comments); - return value; - } - - public static boolean writeValue(Object value, File file, FileConfiguration fileConfiguration, String path) { - fileConfiguration.set(path, value); - try { - fileSaverCustomValues(fileConfiguration, file); - } catch (Exception exception) { - Logger.warn("Failed to write value for " + path + " in file " + file.getName()); - return false; - } - return true; - } - - public static void removeValue(File file, FileConfiguration fileConfiguration, String path) { - writeValue(null, file, fileConfiguration, path); - } - - public static List setList(File file, FileConfiguration fileConfiguration, String key, List defaultValue) { - fileConfiguration.addDefault(key, defaultValue); - return fileConfiguration.getList(key); - } - - public static List setList(List comment, File file, FileConfiguration fileConfiguration, String key, List defaultValue) { - List value = setList(file, fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comment); - return value; - } - public static String setString(File file, FileConfiguration fileConfiguration, String key, String defaultValue) { - return ChatColorConverter.convert(fileConfiguration.getString(key)); - } - - public static String setString(List comments, File file, FileConfiguration fileConfiguration, String key, String defaultValue) { - String value = setString(file, fileConfiguration, key, defaultValue); - setComments(fileConfiguration, key, comments); - return value; - } - -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java new file mode 100644 index 0000000..919d8e0 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java @@ -0,0 +1,34 @@ +package com.magmaguy.resourcepackmanager.config; + +import com.magmaguy.magmacore.config.ConfigurationFile; +import com.magmaguy.magmacore.util.Logger; + +public class DataConfig extends ConfigurationFile { + private static DataConfig instance = null; + + public DataConfig() { + super("data.yml"); + instance = this; + } + + public static String getRspUUID() { + String uuid = instance.getFileConfiguration().getString("uuid"); + if (uuid == null) uuid = ""; + return uuid; + } + + public static void setRspUUID(String rspUUID) { + instance.getFileConfiguration().set("uuid", rspUUID); + try { + instance.getFileConfiguration().save(instance.file); + } catch (Exception e) { + Logger.warn("Failed to save uuid!"); + e.printStackTrace(); + } + } + + @Override + public void initializeValues() { + + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index c7d59ce..e9f3fdd 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -1,14 +1,13 @@ package com.magmaguy.resourcepackmanager.config; +import com.magmaguy.magmacore.config.ConfigurationEngine; +import com.magmaguy.magmacore.config.ConfigurationFile; import lombok.Getter; -import org.bukkit.configuration.file.FileConfiguration; -import java.io.File; import java.util.List; -public class DefaultConfig { - private static File file = null; - private static FileConfiguration fileConfiguration = null; +public class DefaultConfig extends ConfigurationFile { + @Getter private static List priorityOrder; @Getter @@ -18,16 +17,18 @@ public class DefaultConfig { @Getter private static String resourcePackPrompt; - public static void initializeConfig() { - file = ConfigurationEngine.fileCreator("config.yml"); - fileConfiguration = ConfigurationEngine.fileConfigurationCreator(file); + public DefaultConfig() { + super("config.yml"); + } + @Override + public void initializeValues() { priorityOrder = ConfigurationEngine.setList( List.of( "Sets the list, from highest priority (top) to lowest priority (bottom), in which the resource" + " packs will automatically resolve merge conflicts.", "The defaults use plugin names. If you manually added your own resource pack in the mixer folder to be merged in, add its exact filename, including .zip in the name"), - file, fileConfiguration, "priorityOrder", + fileConfiguration, "priorityOrder", List.of("ResourcePackManager", "EliteMobs", "FreeMinecraftModels", "ModelEngine", "ItemsAdder", "Nova", "Oraxen")); autoHost = ConfigurationEngine.setBoolean( @@ -41,10 +42,6 @@ public static void initializeConfig() { resourcePackPrompt = ConfigurationEngine.setString( List.of("Sets whether the resource pack use will be forced to clients"), - file, fileConfiguration, "resourcePackPrompt", "Use recommended resource pack?"); - - ConfigurationEngine.fileSaverOnlyDefaults(fileConfiguration, file); + fileConfiguration, "resourcePackPrompt", "Use recommended resource pack?"); } - - } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/UnusedNodeHandler.java b/src/main/java/com/magmaguy/resourcepackmanager/config/UnusedNodeHandler.java deleted file mode 100644 index e05eb13..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/UnusedNodeHandler.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.magmaguy.resourcepackmanager.config; - -import com.magmaguy.resourcepackmanager.Logger; -import org.bukkit.Bukkit; -import org.bukkit.configuration.Configuration; -import org.bukkit.configuration.file.FileConfiguration; - -public class UnusedNodeHandler { - - private UnusedNodeHandler() { - } - - public static Configuration clearNodes(FileConfiguration configuration) { - - for (String actual : configuration.getKeys(false)) { - boolean keyExists = false; - for (String defaults : configuration.getDefaults().getKeys(true)) - if (actual.equals(defaults)) { - keyExists = true; - break; - } - - if (!keyExists) { - configuration.set(actual, null); - Bukkit.getLogger().warning(actual); - Logger.warn("Deleting unused config values."); - } - } - return configuration; - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java new file mode 100644 index 0000000..d92814e --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java @@ -0,0 +1,16 @@ +package com.magmaguy.resourcepackmanager.listeners; + +import com.magmaguy.freeminecraftmodels.events.ResourcePackGenerationEvent; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class ResourcePackGeneratedEvent implements Listener { + @EventHandler + public void onResourcePackGenerated(ResourcePackGenerationEvent event) { + ReloadCommand.reloadPlugin(Bukkit.getConsoleSender()); + Logger.sendMessage(Bukkit.getConsoleSender(), "Resource pack regenerated!"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 242cdc5..956692f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -2,11 +2,11 @@ import com.google.gson.*; import com.google.gson.stream.JsonReader; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.thirdparty.*; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; -import com.magmaguy.resourcepackmanager.utils.ZipFile; import lombok.Getter; import java.io.File; @@ -15,12 +15,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributeView; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; @@ -80,6 +76,12 @@ private static void initializeThirdPartyResourcePacks() { Oraxen oraxen = new Oraxen(); if (oraxen.isEnabled()) tempList.add(oraxen); + BetterHUD betterHUD = new BetterHUD(); + if (betterHUD.isEnabled()) + tempList.add(betterHUD); + ValhallaMMO valhallaMMO = new ValhallaMMO(); + if (valhallaMMO.isEnabled()) + tempList.add(valhallaMMO); thirdPartyResourcePacks = new ArrayList<>(); for (int i = 0; i < tempList.size(); i++) { for (ThirdPartyResourcePack thirdPartyResourcePack : tempList) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java index 1845077..d8e67ae 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java @@ -1,6 +1,6 @@ package com.magmaguy.resourcepackmanager.playermanager; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.autohost.AutoHost; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java new file mode 100644 index 0000000..22602fe --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.thirdparty; + +import java.io.File; + +public class BetterHUD extends ThirdPartyResourcePack { + public BetterHUD() { + super("BetterHUD", + "BetterHUD" + File.separatorChar + "build.zip", + true, + false, + true, + true, + "betterhud reload"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java index 6a69bb7..f4ca084 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java @@ -10,6 +10,8 @@ public EliteMobs() { "EliteMobs" + File.separatorChar + "exports" + File.separatorChar + "elitemobs_resource_pack.zip", false, false, + true, + true, "elitemobs reload"); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java index 7697c26..6b790a8 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java @@ -8,6 +8,8 @@ public FreeMinecraftModels() { "FreeMinecraftModels" + File.separatorChar + "output" + File.separatorChar + "FreeMinecraftModels.zip", false, false, + true, + true, "freeminecraftmodels reload"); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java index 857c541..a4b21e5 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java @@ -8,6 +8,8 @@ public ItemsAdder() { "ItemsAdder" + File.separatorChar + "output" + File.separatorChar + "generated.zip", false, false, + true, + true, ""); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java new file mode 100644 index 0000000..505dc3b --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.thirdparty; + +import java.io.File; + +public class MMOInventory extends ThirdPartyResourcePack { + public MMOInventory() { + super("MMOInventory", + "https://www.dropbox.com/s/1lftxmzh0q4b5yu/mmoinv_rp_3.zip?dl=1", + false, + false, + true, + false, + ""); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java index e3125e9..1760ba1 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java @@ -8,6 +8,8 @@ public ModelEngine() { "ModelEngine" + File.separatorChar + "resource pack.zip", false, false, + true, + true, "meg reload"); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java index f11caa9..13a017c 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java @@ -8,6 +8,8 @@ public Nova() { "Nova" + File.separatorChar + "resource_pack" + File.separatorChar + "ResourcePack.zip", false, false, + true, + true, ""); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java index aa7e9e7..b828c09 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java @@ -8,6 +8,8 @@ public Oraxen() { "Oraxen" + File.separatorChar + "pack" + File.separatorChar + "pack.zip", false, false, + true, + true, ""); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java index ad2cc7e..59ad392 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java @@ -8,6 +8,8 @@ public ResourcePackManager() { "ResourcePackManager" + File.separatorChar + "blueprint" + File.separatorChar + "blueprint.zip", false, false, + true, + true, "elitemobs reload"); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 113f560..6fb2855 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -1,22 +1,32 @@ package com.magmaguy.resourcepackmanager.thirdparty; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; +import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.HttpEntity; import org.bukkit.Bukkit; import java.io.File; +import java.io.FileOutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; public class ThirdPartyResourcePack implements GeneratorInterface { @Getter - private File file; + private File file = null; + private String url; private boolean encrypts; private boolean distributes; + private boolean zips; + private boolean local; private String reloadCommand; @Getter private boolean isEnabled; @@ -26,13 +36,19 @@ public class ThirdPartyResourcePack implements GeneratorInterface { private File mixerResourcePack = null; @Getter private int priority = -1; + private final String pluginName; - public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, boolean distributes, String reloadCommand) { + public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, boolean distributes, boolean zips, boolean local, String reloadCommand) { + this.pluginName = pluginName; isEnabled = Bukkit.getPluginManager().isPluginEnabled(pluginName); if (isEnabled) Logger.info("Initializing " + pluginName + "'s resource pack"); else return; - this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); + this.local = local; + if (local) + this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); + else + this.url = path; if (!file.exists()) { Logger.warn("Found " + pluginName + " but could not find resource pack at location " + file.getPath() + " ! ResourcePackManager will not be able to merge the resource pack from this plugin."); isEnabled = false; @@ -40,7 +56,8 @@ public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, this.encrypts = encrypts; this.distributes = distributes; this.reloadCommand = reloadCommand; - if (isEnabled) SHA1 = getSHA1(file); + this.zips = zips; + if (isEnabled && local) SHA1 = getSHA1(file); process(); if (DefaultConfig.getPriorityOrder().contains(pluginName)) priority = DefaultConfig.getPriorityOrder().indexOf(pluginName); @@ -48,15 +65,17 @@ public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, public void process() { if (!isEnabled) return; - if (mixerCloneExists()) { - if (getSHA1(new File(getTarget().toUri())).equals(SHA1)) { - mixerResourcePack = getTarget().toFile(); - return; - } else { - Logger.info("Clearing outdated resource pack in " + getTarget()); - getTarget().toFile().delete(); + if (local && zips) { + if (mixerCloneExists()) { + if (getSHA1(new File(getTarget().toUri())).equals(SHA1)) { + mixerResourcePack = getTarget().toFile(); + return; + } else { + Logger.info("Clearing outdated resource pack in " + getTarget()); + getTarget().toFile().delete(); + } } - } + } else if (local && mixerCloneExists()) getTarget().toFile().delete(); if (encrypts) decrypt(); if (distributes) unpublish(); cloneResourcePackFile(); @@ -83,15 +102,48 @@ public void unpublish() { @Override public void cloneResourcePackFile() { + if (local) cloneLocalRSP(); + else cloneRemoteRSP(); + resourcePackUpdated = true; + } + + private void cloneLocalRSP() { try { - Logger.info("Cloning resource pack from " + file.toPath()); - mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); + if (zips) { + Logger.info("Cloning resource pack from " + file.toPath()); + mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); + } else { + if (mixerCloneExists()) getTarget().toFile().delete(); + Logger.info("Cloning resource pack from " + file.toPath()); + ZipFile.zip(file, getTarget().toString()); + } } catch (Exception e) { Logger.warn("Failed to clone resource pack from " + file.getPath() + " to the mixer folder!"); e.printStackTrace(); } + } - resourcePackUpdated = true; + private void cloneRemoteRSP() { + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + HttpPost httpPost = new HttpPost(url); + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + HttpEntity responseEntity = response.getEntity(); + + if (responseEntity != null) { + // Save the response as a zip file + File zipFile = getTarget().toFile(); + if (zipFile.exists()) zipFile.delete(); + zipFile.createNewFile(); + try (FileOutputStream outStream = new FileOutputStream(zipFile)) { + responseEntity.writeTo(outStream); + } + } + } catch (Exception e) { + Logger.warn("Failed to communicate with remote server when downloading resource pack for plugin " + pluginName + "!"); + } + } catch (Exception e) { + Logger.warn("Failed to connect to url " + url + " to download resource pack for plugin " + pluginName); + } } @Override @@ -104,6 +156,6 @@ private boolean mixerCloneExists() { } private Path getTarget() { - return Path.of(ResourcePackManager.plugin.getDataFolder().toString(), "mixer", file.getName()); + return Path.of(ResourcePackManager.plugin.getDataFolder().toString(), "mixer", file.getName().endsWith(".zip") ? file.getName() : file.getName() + ".zip"); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java new file mode 100644 index 0000000..d593d41 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.thirdparty; + +import java.io.File; + +public class ValhallaMMO extends ThirdPartyResourcePack { + public ValhallaMMO() { + super("ValhallaMMO", + "Nova" + File.separatorChar + "resource_pack" + File.separatorChar + "ResourcePack.zip", + false, + false, + true, + true, + ""); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java index d0d1412..5cd8649 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java @@ -1,6 +1,6 @@ package com.magmaguy.resourcepackmanager.utils; -import com.magmaguy.resourcepackmanager.Logger; +import com.magmaguy.magmacore.util.Logger; import java.io.File; import java.io.FileInputStream; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/ZipFile.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/ZipFile.java deleted file mode 100644 index 757dee4..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/ZipFile.java +++ /dev/null @@ -1,155 +0,0 @@ -package com.magmaguy.resourcepackmanager.utils; - -import com.magmaguy.resourcepackmanager.Logger; - -import java.io.*; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; -import java.util.zip.ZipOutputStream; - -public class ZipFile { - private ZipFile() { - } - - public static boolean zip(File directory, String targetZipPath) { - if (!directory.exists()) { - Logger.warn("Failed to zip directory " + directory.getPath() + " because it does not exist!"); - return false; - } - - try { - ZipUtility.zip(directory, targetZipPath); - return true; - } catch (IOException e) { - e.printStackTrace(); - return false; - } - } - - public static File unzip(File zippedFile, File destinationUnzippedFile) throws IOException { - byte[] buffer = new byte[1024]; - ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(zippedFile)); - ZipEntry zipEntry = zipInputStream.getNextEntry(); - while (zipEntry != null) { - File newFile = newFile(destinationUnzippedFile, zipEntry); - if (zipEntry.isDirectory()) { - if (!newFile.isDirectory() && !newFile.mkdirs()) { - throw new IOException("Failed to create directory " + newFile); - } - } else { - // Fix for Windows-created archives - File parent = newFile.getParentFile(); - if (!parent.isDirectory() && !parent.mkdirs()) { - throw new IOException("Failed to create directory " + parent); - } - - // Write file content - FileOutputStream fileOutputStream = new FileOutputStream(newFile); - int len; - while ((len = zipInputStream.read(buffer)) > 0) { - fileOutputStream.write(buffer, 0, len); - } - fileOutputStream.close(); - } - newFile.setLastModified(zipEntry.getTime()); - zipEntry = zipInputStream.getNextEntry(); - } - zipInputStream.closeEntry(); - zipInputStream.close(); - return destinationUnzippedFile; - } - - private static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException { - File destFile = new File(destinationDir, zipEntry.getName()); - - String destDirPath = destinationDir.getCanonicalPath(); - String destFilePath = destFile.getCanonicalPath(); - - if (!destFilePath.startsWith(destDirPath + File.separatorChar)) { - throw new IOException("Entry is outside of the target dir: " + zipEntry.getName()); - } - - return destFile; - } - - public static class ZipUtility { - /** - * A constant for buffer size used to read/write data - */ - private static final int BUFFER_SIZE = 4096; - - /** - * Compresses a list of files to a destination zip file - * - * @param file File to zip - * @param destZipFile The path of the destination zip file - * @throws FileNotFoundException - * @throws IOException - */ - public static void zip(File file, String destZipFile) throws FileNotFoundException, IOException { - FileOutputStream fileOutputStream = new FileOutputStream(destZipFile); - ZipOutputStream zos = new ZipOutputStream(fileOutputStream); - // This slight tweak avoids making the directory zipped be in the zipped file when what we are looking for is to - // zip the contents of the directory, outside of the directory itself - if (file.isDirectory()) { - for (File file1 : file.listFiles()) { - if (file1.isDirectory()) - zipDirectory(file1, file1.getName(), zos); - else - zipFile(file1, zos); - } - } else { - zipFile(file, zos); - } - zos.flush(); - zos.close(); - fileOutputStream.close(); - } - - /** - * Adds a directory to the current zip output stream - * - * @param folder the directory to be added - * @param parentFolder the path of parent directory - * @param zos the current zip output stream - * @throws FileNotFoundException - * @throws IOException - */ - private static void zipDirectory(File folder, String parentFolder, ZipOutputStream zos) throws FileNotFoundException, IOException { - for (File file : folder.listFiles()) { - if (file.isDirectory()) { - zipDirectory(file, parentFolder + "/" + file.getName(), zos); - continue; - } - ZipEntry zipEntry = new ZipEntry(parentFolder + "/" + file.getName()); - zippedySplit(zos, file, zipEntry); - } - } - - /** - * Adds a file to the current zip output stream - * - * @param file the file to be added - * @param zos the current zip output stream - * @throws FileNotFoundException - * @throws IOException - */ - private static void zipFile(File file, ZipOutputStream zos) throws FileNotFoundException, IOException { - ZipEntry zipEntry = new ZipEntry(file.getName()); - zippedySplit(zos, file, zipEntry); - } - - private static void zippedySplit(ZipOutputStream zos, File file, ZipEntry zipEntry) throws IOException { - zipEntry.setTime(0L); - zos.putNextEntry(zipEntry); - BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file)); - byte[] bytesIn = new byte[BUFFER_SIZE]; - int read; - while ((read = bis.read(bytesIn)) != -1) { - zos.write(bytesIn, 0, read); - } - zos.closeEntry(); - bis.close(); - } - } -} diff --git a/src/main/resources/ReadMe.md b/src/main/resources/ReadMe.md index 82ef310..d24b2df 100644 --- a/src/main/resources/ReadMe.md +++ b/src/main/resources/ReadMe.md @@ -10,7 +10,7 @@ This system complies with: For data hosting transparency and compliance with these and other European norms, it is possible to obtain all data related to a server via the `/resourcepackmanager data_compliance_request` command. This command packages a full copy of all files and data associated with the requesting server. -To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\nightbreak.io` (replace `/at\ ` with `@`). However, due to the system's design, data is only retained for up to two minutes after a server using ResourcePackManager shuts down, making email requests largely unnecessary. Nonetheless, the option remains available to ensure full compliance with European norms. +To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\nightbreak.io` (replace `/at\ ` with `@`). However, due to the system's design, data is only retained for up to 24h after a server using ResourcePackManager shuts down, making email requests largely unnecessary. Nonetheless, the option remains available to ensure full compliance with European norms. ### Data Handling by ResourcePackManager and Nightbreak Servers @@ -27,8 +27,8 @@ To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\ - The zipped resource pack file is sent to the remote server, assigned the same UUID as the `.txt` file. This file can be obtained through `/resourcepackmanager data_compliance_request` and verified to be identical to the original in your output folder, as it is not modified by the Nightbreak servers. 5. **"Still Alive" Ping** - - ResourcePackManager sends a "still alive" ping every minute, transmitting the UUID to the server, which updates the timestamp in the `.txt` file. - - If no "still alive" ping is received for over two minutes, all data associated with that UUID (the `.txt` file and the resource pack) is deleted from the Nightbreak servers. + - ResourcePackManager sends a "still alive" ping every 12 hours, transmitting the UUID to the server, which updates the timestamp in the `.txt` file. + - If no "still alive" ping is received for over 24 hours, all data associated with that UUID (the `.txt` file and the resource pack) is deleted from the Nightbreak servers. ### Data Policy @@ -36,4 +36,16 @@ To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\ - **No Download Logging:** Nightbreak does not log any data related to download requests by Minecraft clients. - **No Data Sales:** Data uploaded to Nightbreak is not, has never been, and will never be sold. - **Compliance with Takedown Requests:** Nightbreak will comply with takedown requests from both server administrators and law enforcement agencies. -- **Automatic Data Removal:** All data associated with your server is automatically removed two minutes after your server shuts down and is reuploaded on every restart for as long as ResourcePackManager is in use and using the auto-host feature. \ No newline at end of file +- **Automatic Data Removal:** All data associated with your server is automatically removed 24 hours after your server shuts down and is reuploaded on every restart for as long as ResourcePackManager is in use and using the auto-host feature. + +### Terms of service + +As of writing this, the hosting service is provided for free for all users of ResourcePackManager. + +It is the user's responsibility to ensure that the data uploaded to servers is not illegal and complies with any Mojang TOS as defined in their EULA. + +Abusing the service to host material other than resource packages may result in a permanent denial of service for the offending IP. + +The service may, at any time, cease or be modified in such a way that makes old versions unable to connect to it. + +We reserve the right to unilaterally terminate this service at any time and for any reason. \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index b57b333..c7dbc86 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 0.0.1 +version: 0.0.2 api-version: 1.14 softdepend: - EliteMobs From 4527e36ef6be2760c734de6535e17f22f6d6ce6a Mon Sep 17 00:00:00 2001 From: BuildTools Date: Fri, 2 Aug 2024 13:02:47 +0100 Subject: [PATCH 03/33] ResourcePackManager 1.0.0: - [New] Data retention changed to 24 hours - [New] Changed communication to avoid having players download a fresh copy of the resource pack after every server restart - [New] Moved core features to MagmaCore - Redid all the commands - Redid all the configuration systems - [Fix] Fixed nesting of the blueprint resource pack - [New] Added MMOInventory compatibility - [New] Added ValhallaMMO compatibility - [New] Added BetterHUD compatibility - [New] Added 50000KB RSP size limit - [Tweak] Fixed up debug / warning messages First public alpha test! - [Fix] Fixed issue where events would not get unregistered correctly --- pom.xml | 4 ++-- .../magmaguy/resourcepackmanager/ResourcePackManager.java | 6 ++++-- .../magmaguy/resourcepackmanager/config/DefaultConfig.java | 2 +- .../listeners/ResourcePackGeneratedEvent.java | 2 -- src/main/resources/plugin.yml | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index f173581..7b92717 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 0.0.2 + 1.0.0 17 @@ -55,7 +55,7 @@ com.magmaguy MagmaCore - 1.0 + 1.7 compile diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 471567f..b028f25 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -13,7 +13,7 @@ import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; import org.bukkit.Bukkit; -import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; public class ResourcePackManager extends JavaPlugin { @@ -41,13 +41,15 @@ public void onEnable() { CommandManager commandManager = new CommandManager(this, "resourcepackmanager"); commandManager.registerCommand(new ReloadCommand()); commandManager.registerCommand(new DataComplianceRequestCommand()); - Bukkit.getPluginManager().registerEvents(new ResourcePackGeneratedEvent(), this); + if (Bukkit.getPluginManager().isPluginEnabled("FreeMinecraftModels")) + Bukkit.getPluginManager().registerEvents(new ResourcePackGeneratedEvent(), this); AutoHost.initialize(); } @Override public void onDisable() { Logger.info("Disabling ResourcePackManager"); + HandlerList.unregisterAll(this); AutoHost.shutdown(); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index e9f3fdd..07574e9 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -29,7 +29,7 @@ public void initializeValues() { " packs will automatically resolve merge conflicts.", "The defaults use plugin names. If you manually added your own resource pack in the mixer folder to be merged in, add its exact filename, including .zip in the name"), fileConfiguration, "priorityOrder", - List.of("ResourcePackManager", "EliteMobs", "FreeMinecraftModels", "ModelEngine", "ItemsAdder", "Nova", "Oraxen")); + List.of("ResourcePackManager", "EliteMobs", "FreeMinecraftModels", "ModelEngine", "ItemsAdder", "Nova", "Oraxen", "BetterHUD", "ValhallaMMO", "MMOInventory")); autoHost = ConfigurationEngine.setBoolean( List.of("Automatically host the resource pack on MagmaGuy's servers", diff --git a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java index d92814e..07a5d6b 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java @@ -1,7 +1,6 @@ package com.magmaguy.resourcepackmanager.listeners; import com.magmaguy.freeminecraftmodels.events.ResourcePackGenerationEvent; -import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; @@ -11,6 +10,5 @@ public class ResourcePackGeneratedEvent implements Listener { @EventHandler public void onResourcePackGenerated(ResourcePackGenerationEvent event) { ReloadCommand.reloadPlugin(Bukkit.getConsoleSender()); - Logger.sendMessage(Bukkit.getConsoleSender(), "Resource pack regenerated!"); } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c7dbc86..a4c9fbc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 0.0.2 +version: 1.0.0 api-version: 1.14 softdepend: - EliteMobs From 76ad4b3318d34b38eebebc6526e1270f8e933362 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Fri, 2 Aug 2024 13:12:45 +0100 Subject: [PATCH 04/33] - Added Bstats --- pom.xml | 11 +++++++++++ .../resourcepackmanager/ResourcePackManager.java | 3 +++ 2 files changed, 14 insertions(+) diff --git a/pom.xml b/pom.xml index 7b92717..6dbd6a2 100644 --- a/pom.xml +++ b/pom.xml @@ -64,6 +64,13 @@ 1.4.0 provided + + + org.bstats + bstats-bukkit + 3.0.2 + compile + @@ -95,6 +102,10 @@ org.apache.hc com.magmaguy.resourcepackmanager.org.apache.hc + + org.bstats + com.magmaguy.freeminecraftmodels + diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index b028f25..6b1fe03 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -1,5 +1,6 @@ package com.magmaguy.resourcepackmanager; +import com.magmaguy.freeminecraftmodels.bukkit.Metrics; import com.magmaguy.magmacore.MagmaCore; import com.magmaguy.magmacore.command.CommandManager; import com.magmaguy.magmacore.util.Logger; @@ -44,6 +45,8 @@ public void onEnable() { if (Bukkit.getPluginManager().isPluginEnabled("FreeMinecraftModels")) Bukkit.getPluginManager().registerEvents(new ResourcePackGeneratedEvent(), this); AutoHost.initialize(); + + Metrics metrics = new Metrics(this, 22867); } @Override From be0acb3b4561df86bae2b54c9d4f63906a955a59 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sat, 3 Aug 2024 07:02:55 +0100 Subject: [PATCH 05/33] Note: Make sure to delete the mixer folder for this update. This should be the only time this is ever necessary. ResourcePackManager 1.0.1: - [New] Added Bstats - [New] Resource packs now get named after the plugin they come from to avoid namespace collisions - [New] Added BackpackPlus compatibility - [New] Added RealisticSurvival compatibility - [New] Added VaneCore compatibility - [New] Priority list in config.yml now lets admins define custom resource packs not associated to supported plugins - [New] Added new version notifier - [Fix] Mixer folder now correctly allows users to put custom resource packs in them for the resource pack generation - [Fix] Fixed ValhallaMMO compatibility --- .../ResourcePackManager.java | 3 + .../autohost/AutoHost.java | 2 +- .../config/DefaultConfig.java | 14 ++- .../resourcepackmanager/mixer/Mix.java | 83 +++++++++++--- .../thirdparty/BackpackPlus.java | 15 +++ .../thirdparty/RealisticSurvival.java | 13 +++ .../thirdparty/ThirdPartyResourcePack.java | 25 ++++- .../thirdparty/ValhallaMMO.java | 4 +- .../thirdparty/VaneCore.java | 13 +++ .../utils/VersionChecker.java | 106 ++++++++++++++++++ src/main/resources/plugin.yml | 5 +- 11 files changed, 260 insertions(+), 23 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 6b1fe03..8ee6a1d 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -13,6 +13,7 @@ import com.magmaguy.resourcepackmanager.listeners.ResourcePackGeneratedEvent; import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; +import com.magmaguy.resourcepackmanager.utils.VersionChecker; import org.bukkit.Bukkit; import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; @@ -44,9 +45,11 @@ public void onEnable() { commandManager.registerCommand(new DataComplianceRequestCommand()); if (Bukkit.getPluginManager().isPluginEnabled("FreeMinecraftModels")) Bukkit.getPluginManager().registerEvents(new ResourcePackGeneratedEvent(), this); + Bukkit.getPluginManager().registerEvents(new VersionChecker.VersionCheckerEvents(), this); AutoHost.initialize(); Metrics metrics = new Metrics(this, 22867); + VersionChecker.checkPluginVersion(); } @Override diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index d7ea962..c97db1d 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -26,7 +26,7 @@ import java.util.UUID; public class AutoHost { -// private static final String finalURL = "http://localhost:3000/"; +// private static final String finalURL = "http://localhost:50000/"; private static final String finalURL = "http://magmaguy.com:50000/"; private static final UUID pluginRSPUUID = UUID.randomUUID(); private static BukkitTask keepAlive = null; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index 07574e9..d7e126c 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -29,7 +29,19 @@ public void initializeValues() { " packs will automatically resolve merge conflicts.", "The defaults use plugin names. If you manually added your own resource pack in the mixer folder to be merged in, add its exact filename, including .zip in the name"), fileConfiguration, "priorityOrder", - List.of("ResourcePackManager", "EliteMobs", "FreeMinecraftModels", "ModelEngine", "ItemsAdder", "Nova", "Oraxen", "BetterHUD", "ValhallaMMO", "MMOInventory")); + List.of( + "ResourcePackManager", + "EliteMobs", + "FreeMinecraftModels", + "ModelEngine", + "ItemsAdder", + "Nova", + "Oraxen", + "BetterHUD", + "ValhallaMMO", + "MMOInventory", + "vane-core", + "RealisticSurvival")); autoHost = ConfigurationEngine.setBoolean( List.of("Automatically host the resource pack on MagmaGuy's servers", diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 956692f..f0a61b1 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -5,6 +5,7 @@ import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.thirdparty.*; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; @@ -16,17 +17,21 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; - private static List thirdPartyResourcePacks; + private static final HashMap priorities = new HashMap<>(); + private static List resourcePacks; @Getter private static File finalResourcePack; @Getter private static String finalSHA1; @Getter private static byte[] finalSHA1Bytes; + private static File mixerFolder; private Mix() { } @@ -34,13 +39,13 @@ private Mix() { public static void initialize() { if (!initializeDefaultPluginFolders()) return; initializeThirdPartyResourcePacks(); - cloneToOutPutAndUnzip(); + cloneToOutputAndUnzip(); createOutputDefaultElements(); } private static boolean initializeDefaultPluginFolders() { try { - File mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "mixer"); + mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "mixer"); if (!mixerFolder.exists()) mixerFolder.mkdir(); File outputFolder = getOutputFolder(); @@ -82,27 +87,81 @@ private static void initializeThirdPartyResourcePacks() { ValhallaMMO valhallaMMO = new ValhallaMMO(); if (valhallaMMO.isEnabled()) tempList.add(valhallaMMO); - thirdPartyResourcePacks = new ArrayList<>(); + VaneCore vaneCore = new VaneCore(); + if (vaneCore.isEnabled()) + tempList.add(vaneCore); + BackpackPlus backpackPlus = new BackpackPlus(); + if (backpackPlus.isEnabled()) + tempList.add(backpackPlus); + RealisticSurvival realisticSurvival = new RealisticSurvival(); + if (realisticSurvival.isEnabled()) + tempList.add(backpackPlus); + resourcePacks = new ArrayList<>(); + List customFiles = new ArrayList<>(); + for (File file : mixerFolder.listFiles()) { + boolean isDefault = false; + for (File supportedRSP : resourcePacks) { + if (supportedRSP.equals(file)) { + isDefault = true; + break; + } + } + if (!isDefault) customFiles.add(file); + } + //Do resource packs with priority for (int i = 0; i < tempList.size(); i++) { + boolean foundFileAtPriority = false; for (ThirdPartyResourcePack thirdPartyResourcePack : tempList) { if (thirdPartyResourcePack.getPriority() == i) { - thirdPartyResourcePacks.add(thirdPartyResourcePack); + if (thirdPartyResourcePack.getMixerResourcePack() == null) continue; + resourcePacks.add(thirdPartyResourcePack.getMixerResourcePack()); tempList.remove(thirdPartyResourcePack); + foundFileAtPriority = true; + Logger.info("Added supported resource pack " + thirdPartyResourcePack.getFile().getName() + " at priority " + i); break; } } + if (!foundFileAtPriority) { + Iterator iterator = customFiles.iterator(); + while (iterator.hasNext()) { + File file = iterator.next(); + int priority = DefaultConfig.getPriorityOrder().indexOf(file.getName()); + if (priority == i) { + resourcePacks.add(file); + iterator.remove(); // Use iterator's remove method to avoid ConcurrentModificationException + Logger.info("Added custom resource pack " + file.getName() + " at priority " + i); + } + } + } + } + //Do resource packs with no priority + for (ThirdPartyResourcePack thirdPartyResourcePack : tempList) { + if (thirdPartyResourcePack.getMixerResourcePack() == null) continue; + resourcePacks.add(thirdPartyResourcePack.getMixerResourcePack()); + Logger.info("Added supported resource pack " + thirdPartyResourcePack.getMixerFilename() + " without a priority!"); + } + + for (File customFile : customFiles) { + Logger.info("Added custom resource pack " + customFile.getName() + " without a priority!"); + resourcePacks.add(customFile); } - thirdPartyResourcePacks.addAll(tempList); } - private static void cloneToOutPutAndUnzip() { - thirdPartyResourcePacks.forEach(thirdPartyResourcePack -> { + private static void cloneToOutputAndUnzip() { + resourcePacks.forEach(resourcePack -> { try { - File file = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "output" + File.separatorChar + thirdPartyResourcePack.getMixerResourcePack().getName().replace(".zip", "")); - ZipFile.unzip(thirdPartyResourcePack.getMixerResourcePack(), file); + if (resourcePack == null) { + Logger.warn("A resource pack was null by the time it was meant to be unzipped!"); + return; + } + File file = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "output" + File.separatorChar + resourcePack.getName().replace(".zip", "")); + ZipFile.unzip(resourcePack, file); stripDirectoryMetadata(file); } catch (Exception e) { - Logger.warn("Failed to extract file " + thirdPartyResourcePack.getMixerResourcePack().getAbsolutePath() + " ! The file might be encrypted."); + if (resourcePack == null) + Logger.warn("Failed to extract resource pack! A file might be encrypted."); + else + Logger.warn("Failed to extract resource pack " + resourcePack.getName() + " ! The file might be encrypted."); e.printStackTrace(); } }); @@ -167,14 +226,12 @@ private static void recursivelyDeleteDirectory(File directory) { Files.delete(directory.toPath()); } catch (Exception e) { Logger.warn("Failed to delete directory " + directory.getPath()); -// e.printStackTrace(); } } else { try { Files.delete(directory.toPath()); } catch (IOException e) { Logger.warn("Failed to delete file " + directory.getPath()); -// e.printStackTrace(); } } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java new file mode 100644 index 0000000..9ca5088 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.thirdparty; + +import java.io.File; + +public class BackpackPlus extends ThirdPartyResourcePack { + public BackpackPlus() { + super("BackpackPlus", + "https://download.mc-packs.net/pack/6998056b7754a0a2c71a61ba66f26908f6816194.zip", + false, + false, + true, + false, + ""); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java new file mode 100644 index 0000000..c69eb94 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.thirdparty; + +public class RealisticSurvival extends ThirdPartyResourcePack { + public RealisticSurvival() { + super("RealisticSurvival", + "https://www.dropbox.com/scl/fi/5ggc7t20bfxmrzsbb1kok/Realistic-Survival-RP-1.2.8-RELEASE.zip?rlkey=55llpn4zj146usfmm8okf1ier&dl=1", + false, + false, + true, + false, + ""); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 6fb2855..f1555a9 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -20,6 +20,7 @@ import java.nio.file.StandardCopyOption; public class ThirdPartyResourcePack implements GeneratorInterface { + private final String pluginName; @Getter private File file = null; private String url; @@ -35,8 +36,9 @@ public class ThirdPartyResourcePack implements GeneratorInterface { @Getter private File mixerResourcePack = null; @Getter + private String mixerFilename; + @Getter private int priority = -1; - private final String pluginName; public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, boolean distributes, boolean zips, boolean local, String reloadCommand) { this.pluginName = pluginName; @@ -49,14 +51,22 @@ public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); else this.url = path; - if (!file.exists()) { + if (file != null && !file.exists()) { Logger.warn("Found " + pluginName + " but could not find resource pack at location " + file.getPath() + " ! ResourcePackManager will not be able to merge the resource pack from this plugin."); isEnabled = false; + return; } this.encrypts = encrypts; this.distributes = distributes; this.reloadCommand = reloadCommand; this.zips = zips; + mixerFilename = pluginName + "_" + (file.getName().endsWith(".zip") ? file.getName() : file.getName() + ".zip"); + if (!zips) { + ZipFile.zip(file, getTarget().toString()); + file = new File(getTarget().toUri()); + resourcePackUpdated = true; + mixerFilename = file.getName(); + } if (isEnabled && local) SHA1 = getSHA1(file); process(); if (DefaultConfig.getPriorityOrder().contains(pluginName)) @@ -75,10 +85,12 @@ public void process() { getTarget().toFile().delete(); } } - } else if (local && mixerCloneExists()) getTarget().toFile().delete(); + } +// } else if (local && mixerCloneExists()) getTarget().toFile().delete(); if (encrypts) decrypt(); if (distributes) unpublish(); - cloneResourcePackFile(); + if (zips) + cloneResourcePackFile(); } private String getSHA1(File file) { @@ -124,6 +136,7 @@ private void cloneLocalRSP() { } private void cloneRemoteRSP() { + Logger.info("Getting resource pack from remote URL! This is not ideal but not optional for some plugins. URL: " + url); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost httpPost = new HttpPost(url); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { @@ -136,6 +149,8 @@ private void cloneRemoteRSP() { zipFile.createNewFile(); try (FileOutputStream outStream = new FileOutputStream(zipFile)) { responseEntity.writeTo(outStream); + } catch (Exception e) { + Logger.warn("Failed to write resource pack from remote!"); } } } catch (Exception e) { @@ -156,6 +171,6 @@ private boolean mixerCloneExists() { } private Path getTarget() { - return Path.of(ResourcePackManager.plugin.getDataFolder().toString(), "mixer", file.getName().endsWith(".zip") ? file.getName() : file.getName() + ".zip"); + return Path.of(ResourcePackManager.plugin.getDataFolder().toString(), "mixer", mixerFilename); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java index d593d41..b7abf4d 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java @@ -5,10 +5,10 @@ public class ValhallaMMO extends ThirdPartyResourcePack { public ValhallaMMO() { super("ValhallaMMO", - "Nova" + File.separatorChar + "resource_pack" + File.separatorChar + "ResourcePack.zip", + "ValhallaMMO" + File.separatorChar + "resourcepack", + false, false, false, - true, true, ""); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java new file mode 100644 index 0000000..54b69bb --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.thirdparty; + +public class VaneCore extends ThirdPartyResourcePack { + public VaneCore() { + super("vane-core", + "vane-resource-pack.zip", + false, + true, + true, + true, + ""); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java new file mode 100644 index 0000000..5341e96 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java @@ -0,0 +1,106 @@ +package com.magmaguy.resourcepackmanager.utils; + +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Scanner; + +public class VersionChecker { + private static boolean pluginIsUpToDate = true; + + private VersionChecker() { + } + + public static void checkPluginVersion() { + new BukkitRunnable() { + @Override + public void run() { + String currentVersion = ResourcePackManager.plugin.getDescription().getVersion(); + boolean snapshot = false; + if (currentVersion.contains("SNAPSHOT")) { + snapshot = true; + currentVersion = currentVersion.split("-")[0]; + } + String publicVersion = ""; + + try { + Logger.info("Latest public release is " + VersionChecker.readStringFromURL("https://api.spigotmc.org/legacy/update.php?resource=118574")); + Logger.info("Your version is " + ResourcePackManager.plugin.getDescription().getVersion()); + publicVersion = VersionChecker.readStringFromURL("https://api.spigotmc.org/legacy/update.php?resource=118574"); + } catch (IOException e) { + Logger.warn("Couldn't check latest version"); + return; + } + + if (Double.parseDouble(currentVersion.split("\\.")[0]) < Double.parseDouble(publicVersion.split("\\.")[0])) { + outOfDateHandler(); + return; + } + + if (Double.parseDouble(currentVersion.split("\\.")[0]) == Double.parseDouble(publicVersion.split("\\.")[0])) { + + if (Double.parseDouble(currentVersion.split("\\.")[1]) < Double.parseDouble(publicVersion.split("\\.")[1])) { + outOfDateHandler(); + return; + } + + if (Double.parseDouble(currentVersion.split("\\.")[1]) == Double.parseDouble(publicVersion.split("\\.")[1])) { + if (Double.parseDouble(currentVersion.split("\\.")[2]) < Double.parseDouble(publicVersion.split("\\.")[2])) { + outOfDateHandler(); + return; + } + } + } + + if (!snapshot) + Logger.info("You are running the latest version!"); + else + Logger.info("You are running a snapshot version! You can check for updates in the #releases channel on the Nightbreak Discord!"); + + pluginIsUpToDate = true; + } + }.runTaskAsynchronously(ResourcePackManager.plugin); + } + + private static String readStringFromURL(String url) throws IOException { + + try (Scanner scanner = new Scanner(new URL(url).openStream(), + StandardCharsets.UTF_8)) { + scanner.useDelimiter("\\A"); + return scanner.hasNext() ? scanner.next() : ""; + } + + } + + private static void outOfDateHandler() { + Logger.warn("A newer version of this plugin is available for download!"); + pluginIsUpToDate = false; + } + + public static class VersionCheckerEvents implements Listener { + @EventHandler + public void onPlayerLogin(PlayerJoinEvent event) { + + if (!event.getPlayer().hasPermission("elitemobs.versionnotification")) return; + + new BukkitRunnable() { + @Override + public void run() { + if (!event.getPlayer().isOnline()) return; + if (!pluginIsUpToDate) + event.getPlayer().sendMessage(com.magmaguy.magmacore.util.ChatColorConverter.convert("&cYour version of ResourcePackManager is outdated." + + " &aYou can download the latest version from &3&n&ohttps://www.spigotmc.org/resources/resource-pack-manager.118574/")); + } + }.runTaskLater(ResourcePackManager.plugin, 20L * 3); + + } + } + +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a4c9fbc..0db269f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.0.0 +version: 1.0.1 api-version: 1.14 softdepend: - EliteMobs @@ -9,6 +9,9 @@ softdepend: - Nova - Oraxen - ItemsAdder +- BackpackPlus +- vane-core +- RealisticSurvival commands: resourcepackmanager: From 7925925fc91ee1f29ea9833bc39c9b688762bd90 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sat, 3 Aug 2024 16:44:50 +0100 Subject: [PATCH 06/33] ResourcePackManager 1.0.2: - [Fix] Fixed priority order not working correctly --- pom.xml | 2 +- .../autohost/AutoHost.java | 2 - .../resourcepackmanager/mixer/Mix.java | 148 +++++++++--------- .../thirdparty/ResourcePackManager.java | 2 +- .../thirdparty/ThirdPartyResourcePack.java | 7 +- src/main/resources/plugin.yml | 2 +- 6 files changed, 83 insertions(+), 80 deletions(-) diff --git a/pom.xml b/pom.xml index 6dbd6a2..5331a63 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.0.0 + 1.0.2 17 diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index c97db1d..1944d54 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -26,7 +26,6 @@ import java.util.UUID; public class AutoHost { -// private static final String finalURL = "http://localhost:50000/"; private static final String finalURL = "http://magmaguy.com:50000/"; private static final UUID pluginRSPUUID = UUID.randomUUID(); private static BukkitTask keepAlive = null; @@ -39,7 +38,6 @@ private AutoHost() { public static void sendResourcePack(Player player) { if (rspUUID == null) return; -// player.setResourcePack(pluginRSPUUID, finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); player.setResourcePack(finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index f0a61b1..ee4c240 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -16,15 +16,13 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; +import java.util.*; public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; private static final HashMap priorities = new HashMap<>(); private static List resourcePacks; + private static List orderedResourcePacks = new ArrayList<>(); @Getter private static File finalResourcePack; @Getter @@ -59,94 +57,99 @@ private static boolean initializeDefaultPluginFolders() { } private static void initializeThirdPartyResourcePacks() { - ArrayList tempList = new ArrayList<>(); - com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager resourcePackManager = new com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager(); - if (resourcePackManager.isEnabled()) - tempList.add(resourcePackManager); - EliteMobs eliteMobs = new EliteMobs(); - if (eliteMobs.isEnabled()) - tempList.add(eliteMobs); - FreeMinecraftModels freeMinecraftModels = new FreeMinecraftModels(); - if (freeMinecraftModels.isEnabled()) - tempList.add(freeMinecraftModels); - ModelEngine modelEngine = new ModelEngine(); - if (modelEngine.isEnabled()) - tempList.add(modelEngine); - ItemsAdder itemsAdder = new ItemsAdder(); - if (itemsAdder.isEnabled()) - tempList.add(itemsAdder); - Nova nova = new Nova(); - if (nova.isEnabled()) - tempList.add(nova); - Oraxen oraxen = new Oraxen(); - if (oraxen.isEnabled()) - tempList.add(oraxen); - BetterHUD betterHUD = new BetterHUD(); - if (betterHUD.isEnabled()) - tempList.add(betterHUD); - ValhallaMMO valhallaMMO = new ValhallaMMO(); - if (valhallaMMO.isEnabled()) - tempList.add(valhallaMMO); - VaneCore vaneCore = new VaneCore(); - if (vaneCore.isEnabled()) - tempList.add(vaneCore); - BackpackPlus backpackPlus = new BackpackPlus(); - if (backpackPlus.isEnabled()) - tempList.add(backpackPlus); - RealisticSurvival realisticSurvival = new RealisticSurvival(); - if (realisticSurvival.isEnabled()) - tempList.add(backpackPlus); + List tempList = new ArrayList<>(); + List resourcePackManagers = Arrays.asList( + new com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager(), + new EliteMobs(), + new FreeMinecraftModels(), + new ModelEngine(), + new ItemsAdder(), + new Nova(), + new Oraxen(), + new BetterHUD(), + new ValhallaMMO(), + new VaneCore(), + new BackpackPlus(), + new RealisticSurvival() + ); + + resourcePackManagers.stream() + .filter(ThirdPartyResourcePack::isEnabled) + .forEach(tempList::add); + resourcePacks = new ArrayList<>(); List customFiles = new ArrayList<>(); + List thirdPartyFiles = new ArrayList<>(); + + tempList.forEach(rsp -> { + if (rsp.getMixerResourcePack() != null) { + thirdPartyFiles.add(rsp.getMixerResourcePack()); + } + }); + for (File file : mixerFolder.listFiles()) { - boolean isDefault = false; - for (File supportedRSP : resourcePacks) { - if (supportedRSP.equals(file)) { - isDefault = true; + boolean isThirdParty = false; + for (File thirdPartyFile : thirdPartyFiles) { + if (file.getName().equals(thirdPartyFile.getName())) { + isThirdParty = true; break; } } - if (!isDefault) customFiles.add(file); + if (!isThirdParty) { + customFiles.add(file); + } } - //Do resource packs with priority - for (int i = 0; i < tempList.size(); i++) { + + for (int i = 0; i < DefaultConfig.getPriorityOrder().size(); i++) { boolean foundFileAtPriority = false; - for (ThirdPartyResourcePack thirdPartyResourcePack : tempList) { + Iterator iterator = tempList.iterator(); + + while (iterator.hasNext()) { + ThirdPartyResourcePack thirdPartyResourcePack = iterator.next(); if (thirdPartyResourcePack.getPriority() == i) { - if (thirdPartyResourcePack.getMixerResourcePack() == null) continue; - resourcePacks.add(thirdPartyResourcePack.getMixerResourcePack()); - tempList.remove(thirdPartyResourcePack); - foundFileAtPriority = true; - Logger.info("Added supported resource pack " + thirdPartyResourcePack.getFile().getName() + " at priority " + i); - break; + File resourcePackFile = thirdPartyResourcePack.getMixerResourcePack(); + if (resourcePackFile != null) { + resourcePacks.add(resourcePackFile); + orderedResourcePacks.add(resourcePackFile.getName().replace(".zip", "")); + iterator.remove(); + foundFileAtPriority = true; + break; + } } } + if (!foundFileAtPriority) { - Iterator iterator = customFiles.iterator(); - while (iterator.hasNext()) { - File file = iterator.next(); + Iterator fileIterator = customFiles.iterator(); + while (fileIterator.hasNext()) { + File file = fileIterator.next(); int priority = DefaultConfig.getPriorityOrder().indexOf(file.getName()); if (priority == i) { resourcePacks.add(file); - iterator.remove(); // Use iterator's remove method to avoid ConcurrentModificationException + orderedResourcePacks.add(file.getName().replace(".zip", "")); + fileIterator.remove(); Logger.info("Added custom resource pack " + file.getName() + " at priority " + i); } } } } - //Do resource packs with no priority - for (ThirdPartyResourcePack thirdPartyResourcePack : tempList) { - if (thirdPartyResourcePack.getMixerResourcePack() == null) continue; - resourcePacks.add(thirdPartyResourcePack.getMixerResourcePack()); - Logger.info("Added supported resource pack " + thirdPartyResourcePack.getMixerFilename() + " without a priority!"); - } - for (File customFile : customFiles) { - Logger.info("Added custom resource pack " + customFile.getName() + " without a priority!"); + tempList.stream() + .map(ThirdPartyResourcePack::getMixerResourcePack) + .filter(Objects::nonNull) + .forEach(resourcePack -> { + resourcePacks.add(resourcePack); + orderedResourcePacks.add(resourcePack.getName().replace(".zip", "")); + Logger.info("Added supported resource pack " + resourcePack.getName() + " without a priority!"); + }); + + customFiles.forEach(customFile -> { resourcePacks.add(customFile); - } + orderedResourcePacks.add(customFile.getName().replace(".zip", "")); + Logger.info("Added custom resource pack " + customFile.getName() + " without a priority!"); + }); } + private static void cloneToOutputAndUnzip() { resourcePacks.forEach(resourcePack -> { try { @@ -178,8 +181,13 @@ private static void createOutputDefaultElements() { throw new RuntimeException(e); } - for (File file : getOutputFolder().listFiles()) { - if (file.getName().equals(resourcePackName)) continue; + List orderedFiles = new ArrayList<>(); + for (String filename : orderedResourcePacks){ + orderedFiles.add(new File(getOutputFolder().getPath() + File.separatorChar + filename)); + } + + for (File file : orderedFiles) { + if (file.getName().equals(resourcePackName + ".zip")) continue; if (!file.isDirectory()) { if (file.getName().endsWith(".zip")) continue; Logger.warn("Somehow a non-folder file made its way to the output folder! This isn't good. File: " + file.getAbsolutePath()); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java index 59ad392..a15d979 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java @@ -10,6 +10,6 @@ public ResourcePackManager() { false, true, true, - "elitemobs reload"); + ""); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index f1555a9..f2c6d8b 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -20,6 +20,7 @@ import java.nio.file.StandardCopyOption; public class ThirdPartyResourcePack implements GeneratorInterface { + @Getter private final String pluginName; @Getter private File file = null; @@ -66,6 +67,7 @@ public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, file = new File(getTarget().toUri()); resourcePackUpdated = true; mixerFilename = file.getName(); + mixerResourcePack = file; } if (isEnabled && local) SHA1 = getSHA1(file); process(); @@ -86,7 +88,6 @@ public void process() { } } } -// } else if (local && mixerCloneExists()) getTarget().toFile().delete(); if (encrypts) decrypt(); if (distributes) unpublish(); if (zips) @@ -124,10 +125,6 @@ private void cloneLocalRSP() { if (zips) { Logger.info("Cloning resource pack from " + file.toPath()); mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); - } else { - if (mixerCloneExists()) getTarget().toFile().delete(); - Logger.info("Cloning resource pack from " + file.toPath()); - ZipFile.zip(file, getTarget().toString()); } } catch (Exception e) { Logger.warn("Failed to clone resource pack from " + file.getPath() + " to the mixer folder!"); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 0db269f..440faed 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.0.1 +version: 1.0.2 api-version: 1.14 softdepend: - EliteMobs From 08f61c8f3174ad3e6b21de6f097138968c9776c4 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 7 Aug 2024 14:18:53 +0100 Subject: [PATCH 07/33] ResourcePackManager 1.1.0 - [New] Added a prototype API for plugins to register their plugins - [Fix] Fixed reloads breaking load order - [Fix] Fixed remote download not working for resource packs hosted in dropbox - [Tweak] Changed the way the plugin names resource packs in the mixer folder. Deleting the mixer folder for this update is very highly recommended. --- pom.xml | 8 +-- .../ResourcePackManager.java | 6 ++- .../api/ResourcePackManagerAPI.java | 41 ++++++++++++++ .../autohost/AutoHost.java | 2 +- .../config/DefaultConfig.java | 2 +- .../resourcepackmanager/mixer/Mix.java | 10 +++- .../thirdparty/BackpackPlus.java | 4 +- .../thirdparty/ThirdPartyResourcePack.java | 54 +++++++++++++------ src/main/resources/ReadMe.md | 2 +- src/main/resources/plugin.yml | 3 +- 10 files changed, 103 insertions(+), 29 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java diff --git a/pom.xml b/pom.xml index 5331a63..50a2c43 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.0.2 + 1.2.0 17 @@ -25,7 +25,7 @@ magmaguy-repo-releases MagmaGuy's Repository - http://magmaguy.com:50001/releases + https://repo.magmaguy.com/releases @@ -116,12 +116,12 @@ magmaguy-repo-snapshots - http://magmaguy.com:50001/snapshots + https://magmaguy.com/snapshots magmaguy-repo-snapshots MagmaGuy's Repository - http://magmaguy.com:50001/releases + https://repo.magmaguy.com/releases diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 8ee6a1d..93ff44a 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -31,7 +31,6 @@ public void onEnable() { " |_|_\\|___/_| |_| |_\\__,_|_||_\\__,_\\__, \\___|_| \n" + " |___/ "); Bukkit.getLogger().info("ResourcePackManager v." + this.getDescription().getVersion()); - MagmaCore.createInstance(this); plugin = this; new DefaultConfig(); @@ -52,6 +51,11 @@ public void onEnable() { VersionChecker.checkPluginVersion(); } + @Override + public void onLoad(){ + MagmaCore.createInstance(this); + } + @Override public void onDisable() { Logger.info("Disabling ResourcePackManager"); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java new file mode 100644 index 0000000..d1723bc --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java @@ -0,0 +1,41 @@ +package com.magmaguy.resourcepackmanager.api; + +import com.magmaguy.magmacore.command.CommandManager; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; +import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; + +import java.util.HashMap; + +public class ResourcePackManagerAPI { + public static HashMap thirdPartyResourcePackHashMap = new HashMap<>(); + + /** + * Please read and follow the parameters carefully. + * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. + * @param path The absolute path to the resource pack file (zipped or folder), or the URL to the resource pack. Zipped, local resource packs are strongly recommended. + * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. + * @param distributes Whether the plugin can distribute the pack. Currently does nothing. + * @param zips Whether the resource pack linked to is zipped. + * @param local Whether the resource pack is local or not, meaning whether it appears in the directory of the server or is provided as a download link. Local is strongly recommended. + * @param reloadCommand The reload command of the plugin adding a pack. Currently does nothing. + */ + public static void registerResourcePack(String pluginName, + String path, + boolean encrypts, + boolean distributes, + boolean zips, + boolean local, + String reloadCommand) { + thirdPartyResourcePackHashMap.put(pluginName, + new ThirdPartyResourcePack(pluginName, path, encrypts, distributes, zips, local, reloadCommand)); + } + + /** + * Reloads the plugin, thereby redoing everything necessary to merge and host the resource pack + */ + public static void reloadResourcePack(){ + ReloadCommand.reloadPlugin(Bukkit.getConsoleSender()); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 1944d54..0751f81 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -72,7 +72,7 @@ public void run() { counter++; } } - }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 12 * 60 * 60 * 20L); + }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 6 * 60 * 60 * 20L); } }.runTaskAsynchronously(ResourcePackManager.plugin); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index d7e126c..b1ab705 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -34,8 +34,8 @@ public void initializeValues() { "EliteMobs", "FreeMinecraftModels", "ModelEngine", - "ItemsAdder", "Nova", + "ItemsAdder", "Oraxen", "BetterHUD", "ValhallaMMO", diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index ee4c240..92b21f1 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -5,6 +5,7 @@ import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.api.ResourcePackManagerAPI; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.thirdparty.*; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; @@ -20,9 +21,9 @@ public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; - private static final HashMap priorities = new HashMap<>(); + private static HashMap priorities; private static List resourcePacks; - private static List orderedResourcePacks = new ArrayList<>(); + private static List orderedResourcePacks; @Getter private static File finalResourcePack; @Getter @@ -57,6 +58,8 @@ private static boolean initializeDefaultPluginFolders() { } private static void initializeThirdPartyResourcePacks() { + orderedResourcePacks = new ArrayList<>(); + priorities = new HashMap(); List tempList = new ArrayList<>(); List resourcePackManagers = Arrays.asList( new com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager(), @@ -73,6 +76,9 @@ private static void initializeThirdPartyResourcePacks() { new RealisticSurvival() ); + //API resource packs + resourcePackManagers.addAll(ResourcePackManagerAPI.thirdPartyResourcePackHashMap.values()); + resourcePackManagers.stream() .filter(ThirdPartyResourcePack::isEnabled) .forEach(tempList::add); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java index 9ca5088..95f41e1 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java @@ -5,11 +5,11 @@ public class BackpackPlus extends ThirdPartyResourcePack { public BackpackPlus() { super("BackpackPlus", - "https://download.mc-packs.net/pack/6998056b7754a0a2c71a61ba66f26908f6816194.zip", + "BackpackPlus" +File.separatorChar+ "pack" +File.separatorChar + "resourcepack.zip", false, false, true, - false, + true, ""); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index f2c6d8b..20eb936 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -6,6 +6,7 @@ import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; +import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -13,8 +14,10 @@ import org.apache.hc.core5.http.HttpEntity; import org.bukkit.Bukkit; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; +import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; @@ -43,6 +46,7 @@ public class ThirdPartyResourcePack implements GeneratorInterface { public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, boolean distributes, boolean zips, boolean local, String reloadCommand) { this.pluginName = pluginName; + this.mixerFilename = pluginName + "_resource_pack.zip"; isEnabled = Bukkit.getPluginManager().isPluginEnabled(pluginName); if (isEnabled) Logger.info("Initializing " + pluginName + "'s resource pack"); @@ -61,12 +65,10 @@ public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, this.distributes = distributes; this.reloadCommand = reloadCommand; this.zips = zips; - mixerFilename = pluginName + "_" + (file.getName().endsWith(".zip") ? file.getName() : file.getName() + ".zip"); if (!zips) { ZipFile.zip(file, getTarget().toString()); file = new File(getTarget().toUri()); resourcePackUpdated = true; - mixerFilename = file.getName(); mixerResourcePack = file; } if (isEnabled && local) SHA1 = getSHA1(file); @@ -132,23 +134,43 @@ private void cloneLocalRSP() { } } - private void cloneRemoteRSP() { + public void cloneRemoteRSP() { Logger.info("Getting resource pack from remote URL! This is not ideal but not optional for some plugins. URL: " + url); try (CloseableHttpClient httpClient = HttpClients.createDefault()) { - HttpPost httpPost = new HttpPost(url); - try (CloseableHttpResponse response = httpClient.execute(httpPost)) { - HttpEntity responseEntity = response.getEntity(); - - if (responseEntity != null) { - // Save the response as a zip file - File zipFile = getTarget().toFile(); - if (zipFile.exists()) zipFile.delete(); - zipFile.createNewFile(); - try (FileOutputStream outStream = new FileOutputStream(zipFile)) { - responseEntity.writeTo(outStream); - } catch (Exception e) { - Logger.warn("Failed to write resource pack from remote!"); + HttpGet httpGet = new HttpGet(url); + try (CloseableHttpResponse response = httpClient.execute(httpGet)) { + int statusCode = response.getCode(); + Logger.info("Response status code: " + statusCode); + + if (statusCode == 200) { + HttpEntity responseEntity = response.getEntity(); + if (responseEntity != null) { + // Save the response as a zip file + File zipFile = getTarget().toFile(); + if (zipFile.exists()) { + Logger.info("Target file exists, deleting it: " + zipFile.getAbsolutePath()); + zipFile.delete(); + } + zipFile.createNewFile(); + + try (InputStream inStream = new BufferedInputStream(responseEntity.getContent()); + FileOutputStream outStream = new FileOutputStream(zipFile)) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inStream.read(buffer)) != -1) { + outStream.write(buffer, 0, bytesRead); + } + + Logger.info("Successfully downloaded the resource pack to " + zipFile.getAbsolutePath()); + } catch (Exception e) { + Logger.warn("Failed to write resource pack from remote!"); + } + } else { + Logger.warn("Response entity is null"); } + } else { + Logger.warn("Unexpected response status: " + statusCode); } } catch (Exception e) { Logger.warn("Failed to communicate with remote server when downloading resource pack for plugin " + pluginName + "!"); diff --git a/src/main/resources/ReadMe.md b/src/main/resources/ReadMe.md index d24b2df..b7ca72c 100644 --- a/src/main/resources/ReadMe.md +++ b/src/main/resources/ReadMe.md @@ -27,7 +27,7 @@ To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\ - The zipped resource pack file is sent to the remote server, assigned the same UUID as the `.txt` file. This file can be obtained through `/resourcepackmanager data_compliance_request` and verified to be identical to the original in your output folder, as it is not modified by the Nightbreak servers. 5. **"Still Alive" Ping** - - ResourcePackManager sends a "still alive" ping every 12 hours, transmitting the UUID to the server, which updates the timestamp in the `.txt` file. + - ResourcePackManager sends a "still alive" ping every 6 hours, transmitting the UUID to the server, which updates the timestamp in the `.txt` file. - If no "still alive" ping is received for over 24 hours, all data associated with that UUID (the `.txt` file and the resource pack) is deleted from the Nightbreak servers. ### Data Policy diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 440faed..acd722c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,7 +1,8 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.0.2 +version: 1.2.0 api-version: 1.14 +author: MagmaGuy softdepend: - EliteMobs - FreeMinecraftModels From f224fc405a9cd7b586455ee0be48fc3d820d1bf9 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sun, 11 Aug 2024 23:22:35 +0100 Subject: [PATCH 08/33] ResourcePackManager 1.2.0 - [New] Added resource pack rerouting, which allows an optional setting in config.yml that will send the merged resource pack to a directory for use in other plugins --- pom.xml | 2 +- .../resourcepackmanager/autohost/AutoHost.java | 5 ++++- .../config/DefaultConfig.java | 13 ++++++++++++- .../magmaguy/resourcepackmanager/mixer/Mix.java | 16 ++++++++++++++++ .../playermanager/PlayerManager.java | 1 - src/main/resources/plugin.yml | 2 +- 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 50a2c43..e7614ec 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.2.0 + 1.2.1 17 diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 0751f81..5cdeaaf 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -38,13 +38,14 @@ private AutoHost() { public static void sendResourcePack(Player player) { if (rspUUID == null) return; + Logger.info("Sending resource pack to " + player.getName()); player.setResourcePack(finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); - } public static void initialize() { if (!DefaultConfig.isAutoHost()) return; if (Mix.getFinalResourcePack() == null) return; + Logger.info("Starting autohost!"); firstUpload = true; new BukkitRunnable() { @Override @@ -106,6 +107,8 @@ public static void initializeLink() { } public static void uploadFile() { + Logger.info("Uploading resource!"); + try (CloseableHttpClient httpClient = HttpClients.createDefault()) { HttpPost uploadFile = new HttpPost(finalURL + "upload"); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index b1ab705..466c341 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -16,6 +16,9 @@ public class DefaultConfig extends ConfigurationFile { private static boolean forceResourcePack; @Getter private static String resourcePackPrompt; + @Getter + private static String resourcePackRerouting; + public DefaultConfig() { super("config.yml"); @@ -51,9 +54,17 @@ public void initializeValues() { forceResourcePack = ConfigurationEngine.setBoolean( List.of("Sets whether the resource pack use will be forced to clients"), fileConfiguration, "forceResourcePack", false); - resourcePackPrompt = ConfigurationEngine.setString( List.of("Sets whether the resource pack use will be forced to clients"), fileConfiguration, "resourcePackPrompt", "Use recommended resource pack?"); + resourcePackRerouting = ConfigurationEngine.setString( + List.of( + "OPTIONAL: Copy the merged directory to a custom directory location. Useful for unusual setups, like people trying to host with a different plugin.", + "If you are hosting with a different plugin make sure to disable Auto-hosting here!", + "This will use the plugin directory as the base directory.", + "As an example, if you wanted to target ResourcePackManager's output folder, you'd do:", + "ResourcePackManage/output", + "If you don't know what any of what is written here means, just don't touch this setting!"), + fileConfiguration, "resourcePackRerouting", ""); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 92b21f1..f59ab6f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -10,6 +10,7 @@ import com.magmaguy.resourcepackmanager.thirdparty.*; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; +import org.bukkit.Bukkit; import java.io.File; import java.io.FileReader; @@ -207,6 +208,21 @@ private static void createOutputDefaultElements() { Logger.warn("Failed to zip merged resource pack!"); return; } + if (!DefaultConfig.getResourcePackRerouting().isEmpty() && !DefaultConfig.getResourcePackRerouting().isBlank()){ + try{ + File reroutFolder = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().getAbsolutePath() + File.separatorChar + DefaultConfig.getResourcePackRerouting()); + if (!reroutFolder.exists()) { + Logger.warn("Failed to reroute zipped file to " + reroutFolder.getAbsolutePath() + " because that folder does not exist!"); + } else if (!reroutFolder.isDirectory()){ + Logger.warn("Failed to reroute zipped file to " + reroutFolder.getAbsolutePath() + " because that is a file and not a folder!"); + } else if (!ZipFile.zip(getOutputResourcePackFolder(), reroutFolder.getPath() + ".zip")) { + Logger.warn("Failed to zip merged resource pack into reroute directory!"); + return; + } + } catch (Exception e){ + Logger.warn("Failed to reroute zipped file to " + DefaultConfig.getResourcePackRerouting()); + } + } for (File file : getOutputFolder().listFiles()) { if (file.getName().equals(resourcePackName + ".zip")) { finalResourcePack = file; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java index d8e67ae..3cfe328 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java @@ -10,7 +10,6 @@ public class PlayerManager implements Listener { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { - Logger.info("event"); AutoHost.sendResourcePack(event.getPlayer()); } } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index acd722c..a4168fb 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.2.0 +version: 1.2.1 api-version: 1.14 author: MagmaGuy softdepend: From ac12a7767ece5d2a855bfb4e0a75eb6bc933aab5 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Mon, 11 Nov 2024 01:19:16 +0000 Subject: [PATCH 09/33] Fix file handling inconsistencies in BetterHUD and Mix.java. Corrected the case sensitivity issue in the file path for BetterHUD. Renamed variable 'reroutFolder' to 'rerouteFolder' in Mix.java to ensure consistency in the rerouting mechanism. --- .../com/magmaguy/resourcepackmanager/mixer/Mix.java | 12 ++++++------ .../resourcepackmanager/thirdparty/BetterHUD.java | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index f59ab6f..0e5a13a 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -210,12 +210,12 @@ private static void createOutputDefaultElements() { } if (!DefaultConfig.getResourcePackRerouting().isEmpty() && !DefaultConfig.getResourcePackRerouting().isBlank()){ try{ - File reroutFolder = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().getAbsolutePath() + File.separatorChar + DefaultConfig.getResourcePackRerouting()); - if (!reroutFolder.exists()) { - Logger.warn("Failed to reroute zipped file to " + reroutFolder.getAbsolutePath() + " because that folder does not exist!"); - } else if (!reroutFolder.isDirectory()){ - Logger.warn("Failed to reroute zipped file to " + reroutFolder.getAbsolutePath() + " because that is a file and not a folder!"); - } else if (!ZipFile.zip(getOutputResourcePackFolder(), reroutFolder.getPath() + ".zip")) { + File rerouteFolder = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().getAbsolutePath() + File.separatorChar + DefaultConfig.getResourcePackRerouting()); + if (!rerouteFolder.exists()) { + Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that folder does not exist!"); + } else if (!rerouteFolder.isDirectory()){ + Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that is a file and not a folder!"); + } else if (!ZipFile.zip(getOutputResourcePackFolder(), rerouteFolder.getPath() + ".zip")) { Logger.warn("Failed to zip merged resource pack into reroute directory!"); return; } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java index 22602fe..b1b19d8 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java @@ -5,7 +5,7 @@ public class BetterHUD extends ThirdPartyResourcePack { public BetterHUD() { super("BetterHUD", - "BetterHUD" + File.separatorChar + "build.zip", + "BetterHud" + File.separatorChar + "build.zip", true, false, true, From a9b03cdbddecbf8f929461a05fa1c7d6d17193c3 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Sat, 14 Dec 2024 03:37:22 +0000 Subject: [PATCH 10/33] ResourcePackManager 1.2.2 - Fixed problem with rerouting files - Fixed compatibility for some plugins --- pom.xml | 2 +- src/main/resources/plugin.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index e7614ec..776118c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.2.1 + 1.2.2 17 diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a4168fb..1875fae 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.2.1 +version: 1.2.2 api-version: 1.14 author: MagmaGuy softdepend: From a139378517fd08228bdc2513ecddce165248baf8 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Fri, 7 Feb 2025 20:10:43 +0000 Subject: [PATCH 11/33] ResourcePackManager 1.3.0 - [New] Improved commands - [New] Added /logify command to allow admins to easily upload their latest log to maclo.gs - [Fix] Fixed problem with rerouting files - [Fix] Fixed compatibility for some plugins --- pom.xml | 4 ++-- .../resourcepackmanager/ResourcePackManager.java | 9 +++++---- .../api/ResourcePackManagerAPI.java | 2 -- .../resourcepackmanager/autohost/AutoHost.java | 4 ++-- .../commands/DataComplianceRequestCommand.java | 2 -- .../resourcepackmanager/commands/ReloadCommand.java | 2 -- .../resourcepackmanager/config/BlueprintFolder.java | 2 +- .../listeners/ResourcePackGeneratedEvent.java | 4 ++-- .../com/magmaguy/resourcepackmanager/mixer/Mix.java | 3 +-- .../playermanager/PlayerManager.java | 1 - .../resourcepackmanager/thirdparty/EliteMobs.java | 2 -- .../thirdparty/GeneratorInterface.java | 2 -- .../resourcepackmanager/thirdparty/MMOInventory.java | 2 -- .../thirdparty/ThirdPartyResourcePack.java | 5 ++--- src/main/resources/plugin.yml | 10 ++++++++-- 15 files changed, 23 insertions(+), 31 deletions(-) diff --git a/pom.xml b/pom.xml index 776118c..8ea2da1 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.2.2 + 1.3.0 17 @@ -55,7 +55,7 @@ com.magmaguy MagmaCore - 1.7 + 1.8 compile diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 93ff44a..4da25dd 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -1,19 +1,19 @@ package com.magmaguy.resourcepackmanager; +import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; +import com.magmaguy.resourcepackmanager.utils.VersionChecker; import com.magmaguy.freeminecraftmodels.bukkit.Metrics; import com.magmaguy.magmacore.MagmaCore; import com.magmaguy.magmacore.command.CommandManager; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.autohost.AutoHost; -import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; -import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import com.magmaguy.resourcepackmanager.config.BlueprintFolder; import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; -import com.magmaguy.resourcepackmanager.listeners.ResourcePackGeneratedEvent; +import com.magmaguy.resourcepackmanager.Listeners.ResourcePackGeneratedEvent; import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; -import com.magmaguy.resourcepackmanager.utils.VersionChecker; import org.bukkit.Bukkit; import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; @@ -31,6 +31,7 @@ public void onEnable() { " |_|_\\|___/_| |_| |_\\__,_|_||_\\__,_\\__, \\___|_| \n" + " |___/ "); Bukkit.getLogger().info("ResourcePackManager v." + this.getDescription().getVersion()); + MagmaCore.onEnable(); plugin = this; new DefaultConfig(); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java index d1723bc..84ed292 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java @@ -1,10 +1,8 @@ package com.magmaguy.resourcepackmanager.api; -import com.magmaguy.magmacore.command.CommandManager; import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; import java.util.HashMap; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 5cdeaaf..1c5807f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -1,10 +1,10 @@ package com.magmaguy.resourcepackmanager.autohost; -import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.mixer.Mix; +import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; -import com.magmaguy.resourcepackmanager.mixer.Mix; import lombok.Getter; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java index b736562..6b4475c 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java @@ -5,8 +5,6 @@ import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.autohost.AutoHost; -import org.bukkit.command.Command; -import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitRunnable; import java.util.List; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java index 51dfa2e..ad59f51 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java @@ -4,8 +4,6 @@ import com.magmaguy.magmacore.command.CommandData; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.ResourcePackManager; -import java.util.Collections; -import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import java.util.List; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java index 3a8c9f4..03966aa 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java @@ -1,8 +1,8 @@ package com.magmaguy.resourcepackmanager.config; +import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; -import com.magmaguy.resourcepackmanager.ResourcePackManager; import java.io.File; import java.io.IOException; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java index 07a5d6b..663c0e0 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java @@ -1,7 +1,7 @@ -package com.magmaguy.resourcepackmanager.listeners; +package com.magmaguy.resourcepackmanager.Listeners; -import com.magmaguy.freeminecraftmodels.events.ResourcePackGenerationEvent; import com.magmaguy.resourcepackmanager.commands.ReloadCommand; +import com.magmaguy.freeminecraftmodels.events.ResourcePackGenerationEvent; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 0e5a13a..a01f5c4 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -1,16 +1,15 @@ package com.magmaguy.resourcepackmanager.mixer; +import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.google.gson.*; import com.google.gson.stream.JsonReader; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; -import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.api.ResourcePackManagerAPI; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.thirdparty.*; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; -import org.bukkit.Bukkit; import java.io.File; import java.io.FileReader; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java index 3cfe328..d75e125 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/playermanager/PlayerManager.java @@ -1,6 +1,5 @@ package com.magmaguy.resourcepackmanager.playermanager; -import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.autohost.AutoHost; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java index f4ca084..84903f7 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java @@ -1,7 +1,5 @@ package com.magmaguy.resourcepackmanager.thirdparty; -import lombok.Getter; - import java.io.File; public class EliteMobs extends ThirdPartyResourcePack { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java index 7f99791..a0c6e03 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java @@ -1,7 +1,5 @@ package com.magmaguy.resourcepackmanager.thirdparty; -import java.nio.file.Path; - public interface GeneratorInterface { public void decrypt(); public void unpublish(); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java index 505dc3b..3323975 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java @@ -1,7 +1,5 @@ package com.magmaguy.resourcepackmanager.thirdparty; -import java.io.File; - public class MMOInventory extends ThirdPartyResourcePack { public MMOInventory() { super("MMOInventory", diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 20eb936..16f514e 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -1,13 +1,12 @@ package com.magmaguy.resourcepackmanager.thirdparty; +import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; -import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; @@ -53,7 +52,7 @@ public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, else return; this.local = local; if (local) - this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); + this.file = new File(com.magmaguy.resourcepackmanager.ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); else this.url = path; if (file != null && !file.exists()) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 1875fae..ebcd730 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.2.2 +version: 1.3.0 api-version: 1.14 author: MagmaGuy softdepend: @@ -18,4 +18,10 @@ commands: resourcepackmanager: aliases: - rspm - description: "Main command" \ No newline at end of file + description: "Main command" + logify: + description: Posts current latest.log in mclo.gs, to make reporting bugs easier for admins. +permissions: + logify.*: + description: Lets admins run the /logify command, which sends the current latest server log to mclo.gs. + default: op \ No newline at end of file From cb4c169473311ca774e70bddc5c2dbc21044f756 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Fri, 20 Jun 2025 23:22:10 +0100 Subject: [PATCH 12/33] ResourcePackManager 1.4.1 - Fixed problem with rerouting files - Fixed compatibility for some plugins - [Fix] Fixed reported security issue with the logify command - [Fix] Fixed warnings related to the logify command --- pom.xml | 4 +- .../resourcepackmanager/JsonMerger.java | 4 - .../commands/ReloadCommand.java | 2 +- .../resourcepackmanager/mixer/Mix.java | 594 ++++++++++++++++-- src/main/resources/plugin.yml | 10 +- 5 files changed, 553 insertions(+), 61 deletions(-) delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/JsonMerger.java diff --git a/pom.xml b/pom.xml index 8ea2da1..21d1296 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.3.0 + 1.4.1 17 @@ -55,7 +55,7 @@ com.magmaguy MagmaCore - 1.8 + 1.15-SNAPSHOT compile diff --git a/src/main/java/com/magmaguy/resourcepackmanager/JsonMerger.java b/src/main/java/com/magmaguy/resourcepackmanager/JsonMerger.java deleted file mode 100644 index 0f9afb5..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/JsonMerger.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.magmaguy.resourcepackmanager; - -public class JsonMerger { -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java index ad59f51..5feb568 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java @@ -13,7 +13,7 @@ public ReloadCommand() { super(List.of("reload")); setDescription("Reloads the plugin"); setPermission("resourcepackmanager.*"); - setUsage("/fmm reload"); + setUsage("/rspm reload"); } public static void reloadPlugin(CommandSender sender) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index a01f5c4..07eb578 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -11,13 +11,13 @@ import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; +import java.io.*; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.security.NoSuchAlgorithmException; import java.util.*; +import java.util.stream.Stream; public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; @@ -59,9 +59,10 @@ private static boolean initializeDefaultPluginFolders() { private static void initializeThirdPartyResourcePacks() { orderedResourcePacks = new ArrayList<>(); - priorities = new HashMap(); - List tempList = new ArrayList<>(); - List resourcePackManagers = Arrays.asList( + priorities = new HashMap<>(); + + // Use a mutable list so we can addAll() to it + List resourcePackManagers = new ArrayList<>(Arrays.asList( new com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager(), new EliteMobs(), new FreeMinecraftModels(), @@ -74,11 +75,13 @@ private static void initializeThirdPartyResourcePacks() { new VaneCore(), new BackpackPlus(), new RealisticSurvival() - ); + )); - //API resource packs + // Add any API-registered packs resourcePackManagers.addAll(ResourcePackManagerAPI.thirdPartyResourcePackHashMap.values()); + // Filter enabled packs into a working list + List tempList = new ArrayList<>(); resourcePackManagers.stream() .filter(ThirdPartyResourcePack::isEnabled) .forEach(tempList::add); @@ -87,67 +90,68 @@ private static void initializeThirdPartyResourcePacks() { List customFiles = new ArrayList<>(); List thirdPartyFiles = new ArrayList<>(); + // Collect actual pack files from each enabled pack tempList.forEach(rsp -> { - if (rsp.getMixerResourcePack() != null) { - thirdPartyFiles.add(rsp.getMixerResourcePack()); + File mixerPack = rsp.getMixerResourcePack(); + if (mixerPack != null) { + thirdPartyFiles.add(mixerPack); } }); + // Separate out truly custom files in the mixer folder for (File file : mixerFolder.listFiles()) { - boolean isThirdParty = false; - for (File thirdPartyFile : thirdPartyFiles) { - if (file.getName().equals(thirdPartyFile.getName())) { - isThirdParty = true; - break; - } - } + boolean isThirdParty = thirdPartyFiles.stream() + .anyMatch(tp -> tp.getName().equals(file.getName())); if (!isThirdParty) { customFiles.add(file); } } - for (int i = 0; i < DefaultConfig.getPriorityOrder().size(); i++) { - boolean foundFileAtPriority = false; - Iterator iterator = tempList.iterator(); - - while (iterator.hasNext()) { - ThirdPartyResourcePack thirdPartyResourcePack = iterator.next(); - if (thirdPartyResourcePack.getPriority() == i) { - File resourcePackFile = thirdPartyResourcePack.getMixerResourcePack(); - if (resourcePackFile != null) { - resourcePacks.add(resourcePackFile); - orderedResourcePacks.add(resourcePackFile.getName().replace(".zip", "")); - iterator.remove(); - foundFileAtPriority = true; + // Add in order of configured priority + List priorityOrder = DefaultConfig.getPriorityOrder(); + for (int i = 0; i < priorityOrder.size(); i++) { + boolean foundAtThisPriority = false; + + Iterator it = tempList.iterator(); + while (it.hasNext()) { + ThirdPartyResourcePack pack = it.next(); + if (pack.getPriority() == i) { + File f = pack.getMixerResourcePack(); + if (f != null) { + resourcePacks.add(f); + orderedResourcePacks.add(f.getName().replace(".zip", "")); + it.remove(); + foundAtThisPriority = true; break; } } } - if (!foundFileAtPriority) { - Iterator fileIterator = customFiles.iterator(); - while (fileIterator.hasNext()) { - File file = fileIterator.next(); - int priority = DefaultConfig.getPriorityOrder().indexOf(file.getName()); - if (priority == i) { - resourcePacks.add(file); - orderedResourcePacks.add(file.getName().replace(".zip", "")); - fileIterator.remove(); - Logger.info("Added custom resource pack " + file.getName() + " at priority " + i); + if (!foundAtThisPriority) { + Iterator fileIt = customFiles.iterator(); + while (fileIt.hasNext()) { + File custom = fileIt.next(); + int prio = priorityOrder.indexOf(custom.getName()); + if (prio == i) { + resourcePacks.add(custom); + orderedResourcePacks.add(custom.getName().replace(".zip", "")); + fileIt.remove(); } } } } + // Any remaining third-party packs without explicit priority tempList.stream() .map(ThirdPartyResourcePack::getMixerResourcePack) .filter(Objects::nonNull) - .forEach(resourcePack -> { - resourcePacks.add(resourcePack); - orderedResourcePacks.add(resourcePack.getName().replace(".zip", "")); - Logger.info("Added supported resource pack " + resourcePack.getName() + " without a priority!"); + .forEach(packFile -> { + resourcePacks.add(packFile); + orderedResourcePacks.add(packFile.getName().replace(".zip", "")); + Logger.info("Added supported resource pack " + packFile.getName() + " without a priority!"); }); + // Any leftover custom files without explicit priority customFiles.forEach(customFile -> { resourcePacks.add(customFile); orderedResourcePacks.add(customFile.getName().replace(".zip", "")); @@ -155,7 +159,6 @@ private static void initializeThirdPartyResourcePacks() { }); } - private static void cloneToOutputAndUnzip() { resourcePacks.forEach(resourcePack -> { try { @@ -207,6 +210,7 @@ private static void createOutputDefaultElements() { Logger.warn("Failed to zip merged resource pack!"); return; } + if (!DefaultConfig.getResourcePackRerouting().isEmpty() && !DefaultConfig.getResourcePackRerouting().isBlank()){ try{ File rerouteFolder = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().getAbsolutePath() + File.separatorChar + DefaultConfig.getResourcePackRerouting()); @@ -222,6 +226,7 @@ private static void createOutputDefaultElements() { Logger.warn("Failed to reroute zipped file to " + DefaultConfig.getResourcePackRerouting()); } } + for (File file : getOutputFolder().listFiles()) { if (file.getName().equals(resourcePackName + ".zip")) { finalResourcePack = file; @@ -236,6 +241,9 @@ private static void createOutputDefaultElements() { } recursivelyDeleteDirectory(file); } + +// // ADD THE BEDROCK CONVERSION CALL HERE - AFTER finalResourcePack IS SET: +// generateBedrockResourcePack(); } private static File getOutputFolder() { @@ -379,4 +387,498 @@ private static JsonArray mergeJsonArrays(JsonArray array1, JsonArray array2) { return mergedArray; } + +// +// @Getter +// private static File finalBedrockResourcePack; +// @Getter +// private static String finalBedrockSHA1; +// +// // Call this method in createOutputDefaultElements() after finalResourcePack is set +// private static void generateBedrockResourcePack() { +// if (finalResourcePack == null) { +// Logger.warn("Cannot generate Bedrock resource pack - Java resource pack was not created successfully!"); +// return; +// } +// +// Logger.info("Starting Bedrock resource pack generation..."); +// +// File bedrockOutputFolder = new File(getOutputFolder().getAbsolutePath() + File.separatorChar + resourcePackName + "_Bedrock"); +// +// // Clean up any existing Bedrock folder +// if (bedrockOutputFolder.exists()) { +// recursivelyDeleteDirectory(bedrockOutputFolder); +// } +// +// try { +// bedrockOutputFolder.mkdirs(); +// +// // Extract the Java resource pack to process it +// File tempJavaFolder = new File(getOutputFolder().getAbsolutePath() + File.separatorChar + "temp_java_extract"); +// if (tempJavaFolder.exists()) { +// recursivelyDeleteDirectory(tempJavaFolder); +// } +// tempJavaFolder.mkdirs(); +// +// ZipFile.unzip(finalResourcePack, tempJavaFolder); +// +// // Generate Bedrock pack contents +// generateBedrockManifest(bedrockOutputFolder); +// generateBehaviorPackManifest(bedrockOutputFolder); +// convertFreeMinecraftModelsAssets(tempJavaFolder, bedrockOutputFolder); +// +// // Create the Bedrock resource pack zip +// String bedrockPackName = resourcePackName + "_Bedrock.mcpack"; +// File bedrockZipFile = new File(getOutputFolder().getAbsolutePath() + File.separatorChar + bedrockPackName); +// +// if (ZipFile.zip(bedrockOutputFolder, bedrockZipFile.getAbsolutePath())) { +// finalBedrockResourcePack = bedrockZipFile; +// try { +// finalBedrockSHA1 = SHA1Generator.sha1CodeString(finalBedrockResourcePack); +// Logger.info("Successfully generated Bedrock resource pack: " + bedrockPackName); +// } catch (Exception e) { +// Logger.warn("Failed to generate SHA1 for Bedrock resource pack!"); +// } +// } else { +// Logger.warn("Failed to zip Bedrock resource pack!"); +// } +// +// // Clean up temporary folders +// recursivelyDeleteDirectory(tempJavaFolder); +// recursivelyDeleteDirectory(bedrockOutputFolder); +// +// } catch (Exception e) { +// Logger.warn("Failed to generate Bedrock resource pack!"); +// e.printStackTrace(); +// } +// } +// +// private static void generateBedrockManifest(File bedrockOutputFolder) { +// try { +// JsonObject manifest = new JsonObject(); +// manifest.addProperty("format_version", 2); +// +// // Header +// JsonObject header = new JsonObject(); +// header.addProperty("name", "FreeMinecraftModels Bedrock"); +// header.addProperty("description", "Bedrock Edition conversion of FreeMinecraftModels resource pack"); +// header.addProperty("uuid", UUID.randomUUID().toString()); +// JsonArray headerVersion = new JsonArray(); +// headerVersion.add(1); headerVersion.add(0); headerVersion.add(0); +// header.add("version", headerVersion); +// JsonArray minEngineVersion = new JsonArray(); +// minEngineVersion.add(1); minEngineVersion.add(21); minEngineVersion.add(0); +// header.add("min_engine_version", minEngineVersion); +// manifest.add("header", header); +// +// // Modules +// JsonArray modules = new JsonArray(); +// JsonObject resourceModule = new JsonObject(); +// resourceModule.addProperty("type", "resources"); +// resourceModule.addProperty("uuid", UUID.randomUUID().toString()); +// JsonArray moduleVersion = new JsonArray(); +// moduleVersion.add(1); moduleVersion.add(0); moduleVersion.add(0); +// resourceModule.add("version", moduleVersion); +// modules.add(resourceModule); +// manifest.add("modules", modules); +// +// // Write manifest.json +// File manifestFile = new File(bedrockOutputFolder, "manifest.json"); +// try (FileWriter writer = new FileWriter(manifestFile)) { +// new Gson().toJson(manifest, writer); +// } +// +// } catch (Exception e) { +// Logger.warn("Failed to generate Bedrock manifest!"); +// e.printStackTrace(); +// } +// } +// +// private static void generateBehaviorPackManifest(File bedrockOutputFolder) { +// try { +// JsonObject manifest = new JsonObject(); +// manifest.addProperty("format_version", 2); +// +// // Header +// JsonObject header = new JsonObject(); +// header.addProperty("name", "FreeMinecraftModels Bedrock Behavior"); +// header.addProperty("description", "Behavior pack for FreeMinecraftModels custom bone items"); +// header.addProperty("uuid", UUID.randomUUID().toString()); +// JsonArray headerVersion = new JsonArray(); +// headerVersion.add(1); headerVersion.add(0); headerVersion.add(0); +// header.add("version", headerVersion); +// JsonArray minEngineVersion = new JsonArray(); +// minEngineVersion.add(1); minEngineVersion.add(21); minEngineVersion.add(0); +// header.add("min_engine_version", minEngineVersion); +// manifest.add("header", header); +// +// // Modules +// JsonArray modules = new JsonArray(); +// JsonObject behaviorModule = new JsonObject(); +// behaviorModule.addProperty("type", "data"); +// behaviorModule.addProperty("uuid", UUID.randomUUID().toString()); +// JsonArray moduleVersion = new JsonArray(); +// moduleVersion.add(1); moduleVersion.add(0); moduleVersion.add(0); +// behaviorModule.add("version", moduleVersion); +// modules.add(behaviorModule); +// manifest.add("modules", modules); +// +// // Write BP manifest.json +// File bpFolder = new File(bedrockOutputFolder, "BP"); +// bpFolder.mkdirs(); +// File manifestFile = new File(bpFolder, "manifest.json"); +// try (FileWriter writer = new FileWriter(manifestFile)) { +// new Gson().toJson(manifest, writer); +// } +// +// } catch (Exception e) { +// Logger.warn("Failed to generate Behavior Pack manifest!"); +// e.printStackTrace(); +// } +// } +// +// private static void convertFreeMinecraftModelsAssets(File javaFolder, File bedrockFolder) { +// File freeMinecraftModelsAssets = new File(javaFolder, "assets" + File.separator + "freeminecraftmodels"); +// if (!freeMinecraftModelsAssets.exists()) { +// Logger.info("No FreeMinecraftModels assets found to convert"); +// return; +// } +// +// Logger.info("Converting FreeMinecraftModels assets to Bedrock format..."); +// +// try { +// // Create Bedrock directory structure +// File attachablesDir = new File(bedrockFolder, "attachables"); +// File modelsDir = new File(bedrockFolder, "models" + File.separator + "entity"); +// File texturesDir = new File(bedrockFolder, "textures" + File.separator + "entity"); +// +// attachablesDir.mkdirs(); +// modelsDir.mkdirs(); +// texturesDir.mkdirs(); +// +// // Copy textures first +// File javaTexturesDir = new File(freeMinecraftModelsAssets, "textures" + File.separator + "entity"); +// if (javaTexturesDir.exists()) { +// copyTexturesRecursively(javaTexturesDir, texturesDir); +// } +// +// // Convert Java models to Bedrock - each bone becomes separate item +// File javaModelsDir = new File(freeMinecraftModelsAssets, "models"); +// if (javaModelsDir.exists()) { +// convertJavaModelsToBedrock(javaModelsDir, attachablesDir, modelsDir, bedrockFolder); +// } +// +// } catch (Exception e) { +// Logger.warn("Failed to convert FreeMinecraftModels assets!"); +// e.printStackTrace(); +// } +// } +// +// private static void copyTexturesRecursively(File sourceDir, File targetDir) { +// if (!sourceDir.exists() || !sourceDir.isDirectory()) return; +// +// try { +// for (File file : sourceDir.listFiles()) { +// if (file.isDirectory()) { +// File newTargetDir = new File(targetDir, file.getName()); +// newTargetDir.mkdirs(); +// copyTexturesRecursively(file, newTargetDir); +// } else if (file.getName().endsWith(".png")) { +// File targetFile = new File(targetDir, file.getName()); +// Files.copy(file.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); +// } +// } +// } catch (Exception e) { +// Logger.warn("Failed to copy textures from " + sourceDir.getName()); +// e.printStackTrace(); +// } +// } +// +// private static void convertJavaModelsToBedrock(File javaModelsDir, File attachablesDir, File modelsDir, File bedrockFolder) { +// // Look for model folders (each model has its own folder with bone JSON files) +// for (File modelFolder : javaModelsDir.listFiles()) { +// if (!modelFolder.isDirectory()) continue; +// +// String modelName = modelFolder.getName(); +// +// // Skip equipment folder - that's for 1.21.4+ format +// if (modelName.equals("equipment")) continue; +// +// Logger.info("Converting model: " + modelName); +// +// try { +// // Process each bone file individually - each becomes a separate item +// convertModelBonesToBedrock(modelFolder, modelName, attachablesDir, modelsDir, bedrockFolder); +// +// } catch (Exception e) { +// Logger.warn("Failed to convert model: " + modelName); +// e.printStackTrace(); +// } +// } +// } +// +// private static void convertModelBonesToBedrock(File modelFolder, String modelName, File attachablesDir, File modelsDir, File bedrockFolder) { +// // Process each bone file as a separate item/geometry +// List boneFiles = new ArrayList<>(); +// collectBoneFiles(modelFolder, boneFiles); +// +// for (File boneFile : boneFiles) { +// String boneName = boneFile.getName().replace(".json", ""); +// +// // Skip the root bone +// if (boneName.equals("freeminecraftmodels_autogenerated_root")) continue; +// +// Logger.info("Converting bone: " + boneName + " from model: " + modelName); +// +// try { +// // Create unique identifiers for this bone +// String boneId = modelName + "_" + boneName; +// String itemId = "fmm_bone_" + boneId; +// +// // Convert bone to Bedrock geometry +// convertSingleBoneToBedrock(boneFile, boneName, modelName, modelsDir); +// +// // Create attachable for this bone +// generateBoneAttachable(itemId, boneName, modelName, attachablesDir); +// +// // Create behavior pack item for this bone +// generateBoneItem(itemId, boneName, modelName, bedrockFolder); +// +// } catch (Exception e) { +// Logger.warn("Failed to convert bone: " + boneName + " from model: " + modelName); +// e.printStackTrace(); +// } +// } +// } +// +// private static void collectBoneFiles(File folder, List boneFiles) { +// for (File file : folder.listFiles()) { +// if (file.isDirectory()) { +// collectBoneFiles(file, boneFiles); +// } else if (file.getName().endsWith(".json")) { +// boneFiles.add(file); +// } +// } +// } +// +// private static void convertSingleBoneToBedrock(File boneFile, String boneName, String modelName, File modelsDir) { +// try { +// // Read Java bone model +// JsonObject javaModel = JsonParser.parseReader(new FileReader(boneFile)).getAsJsonObject(); +// +// // Create Bedrock geometry +// JsonObject geometry = new JsonObject(); +// geometry.addProperty("format_version", "1.21.0"); +// +// JsonArray geometryArray = new JsonArray(); +// JsonObject geometryDef = new JsonObject(); +// +// JsonObject description = new JsonObject(); +// description.addProperty("identifier", "geometry.fmm_bone." + modelName + "_" + boneName); +// description.addProperty("texture_width", 64); +// description.addProperty("texture_height", 64); +// geometryDef.add("description", description); +// +// JsonArray bones = new JsonArray(); +// +// // Create root bone +// JsonObject rootBone = new JsonObject(); +// rootBone.addProperty("name", "root"); +// JsonArray pivot = new JsonArray(); +// pivot.add(0); pivot.add(0); pivot.add(0); +// rootBone.add("pivot", pivot); +// +// // Convert elements (cubes) to Bedrock format +// if (javaModel.has("elements")) { +// JsonArray elements = javaModel.getAsJsonArray("elements"); +// JsonArray cubes = new JsonArray(); +// +// for (int i = 0; i < elements.size(); i++) { +// JsonObject element = elements.get(i).getAsJsonObject(); +// JsonObject bedrockCube = convertJavaCubeToBedrock(element); +// if (bedrockCube != null) { +// cubes.add(bedrockCube); +// } +// } +// +// if (cubes.size() > 0) { +// rootBone.add("cubes", cubes); +// } +// } +// +// bones.add(rootBone); +// geometryDef.add("bones", bones); +// geometryArray.add(geometryDef); +// geometry.add("minecraft:geometry", geometryArray); +// +// // Write geometry file +// File geoFile = new File(modelsDir, "fmm_bone_" + modelName + "_" + boneName + ".geo.json"); +// try (FileWriter writer = new FileWriter(geoFile)) { +// new Gson().toJson(geometry, writer); +// } +// +// } catch (Exception e) { +// Logger.warn("Failed to convert bone: " + boneName); +// e.printStackTrace(); +// } +// } +// +// private static JsonObject convertJavaCubeToBedrock(JsonObject javaCube) { +// try { +// JsonObject bedrockCube = new JsonObject(); +// +// // Convert origin (from in Java) +// if (javaCube.has("from")) { +// JsonArray from = javaCube.getAsJsonArray("from"); +// JsonArray origin = new JsonArray(); +// origin.add(from.get(0)); +// origin.add(from.get(1)); +// origin.add(from.get(2)); +// bedrockCube.add("origin", origin); +// } +// +// // Convert size (to - from in Java) +// if (javaCube.has("from") && javaCube.has("to")) { +// JsonArray from = javaCube.getAsJsonArray("from"); +// JsonArray to = javaCube.getAsJsonArray("to"); +// JsonArray size = new JsonArray(); +// size.add(to.get(0).getAsDouble() - from.get(0).getAsDouble()); +// size.add(to.get(1).getAsDouble() - from.get(1).getAsDouble()); +// size.add(to.get(2).getAsDouble() - from.get(2).getAsDouble()); +// bedrockCube.add("size", size); +// } +// +// // Convert UV mapping - extract from faces +// if (javaCube.has("faces")) { +// JsonObject faces = javaCube.getAsJsonObject("faces"); +// // Use north face UV as default, or first available face +// JsonObject northFace = null; +// +// if (faces.has("north")) northFace = faces.getAsJsonObject("north"); +// else if (faces.has("south")) northFace = faces.getAsJsonObject("south"); +// else if (faces.has("east")) northFace = faces.getAsJsonObject("east"); +// else if (faces.has("west")) northFace = faces.getAsJsonObject("west"); +// else if (faces.has("up")) northFace = faces.getAsJsonObject("up"); +// else if (faces.has("down")) northFace = faces.getAsJsonObject("down"); +// +// if (northFace != null && northFace.has("uv")) { +// JsonArray javaUV = northFace.getAsJsonArray("uv"); +// JsonArray bedrockUV = new JsonArray(); +// bedrockUV.add(javaUV.get(0)); +// bedrockUV.add(javaUV.get(1)); +// bedrockCube.add("uv", bedrockUV); +// } else { +// // Default UV +// JsonArray uv = new JsonArray(); +// uv.add(0); uv.add(0); +// bedrockCube.add("uv", uv); +// } +// } +// +// return bedrockCube; +// +// } catch (Exception e) { +// Logger.warn("Failed to convert Java cube to Bedrock format"); +// return null; +// } +// } +// +// private static void generateBoneAttachable(String itemId, String boneName, String modelName, File attachablesDir) { +// try { +// JsonObject attachable = new JsonObject(); +// attachable.addProperty("format_version", "1.10.0"); +// +// JsonObject attachableData = new JsonObject(); +// JsonObject description = new JsonObject(); +// description.addProperty("identifier", "fmm:" + itemId); +// +// JsonObject materials = new JsonObject(); +// materials.addProperty("default", "entity_alphatest"); +// materials.addProperty("enchanted", "entity_alphatest_glint"); +// description.add("materials", materials); +// +// JsonObject textures = new JsonObject(); +// // Try to find appropriate texture path +// String texturePath = "textures/entity/" + modelName + "/texture"; +// textures.addProperty("default", texturePath); +// description.add("textures", textures); +// +// JsonObject geometry = new JsonObject(); +// geometry.addProperty("default", "geometry.fmm_bone." + modelName + "_" + boneName); +// description.add("geometry", geometry); +// +// // Configure for handheld/inventory item display +// JsonArray scripts = new JsonArray(); +// JsonObject animateScript = new JsonObject(); +// animateScript.addProperty("animate", "third_person_main_hand"); +// scripts.add(animateScript); +// description.add("scripts", scripts); +// +// attachableData.add("description", description); +// attachable.add("minecraft:attachable", attachableData); +// +// File attachableFile = new File(attachablesDir, itemId + ".json"); +// try (FileWriter writer = new FileWriter(attachableFile)) { +// new Gson().toJson(attachable, writer); +// } +// +// Logger.info("Created attachable: fmm:" + itemId); +// +// } catch (Exception e) { +// Logger.warn("Failed to generate attachable for " + itemId); +// e.printStackTrace(); +// } +// } +// +// private static void generateBoneItem(String itemId, String boneName, String modelName, File bedrockFolder) { +// try { +// // Create BP items folder +// File bpItemsDir = new File(bedrockFolder, "BP" + File.separator + "items"); +// bpItemsDir.mkdirs(); +// +// JsonObject item = new JsonObject(); +// item.addProperty("format_version", "1.21.0"); +// +// JsonObject minecraftItem = new JsonObject(); +// JsonObject description = new JsonObject(); +// description.addProperty("identifier", "fmm:" + itemId); +// description.addProperty("category", "Items"); +// minecraftItem.add("description", description); +// +// JsonObject components = new JsonObject(); +// +// // Set max stack size +// components.addProperty("minecraft:max_stack_size", 64); +// +// // Set icon - use leather horse armor for now +// components.addProperty("minecraft:icon", "leather_horse_armor"); +// +// // Set display name +// JsonObject displayName = new JsonObject(); +// displayName.addProperty("value", modelName + " " + boneName.replace("_", " ")); +// components.add("minecraft:display_name", displayName); +// +// // Set attachable for handheld items +// JsonObject attachable = new JsonObject(); +// attachable.addProperty("slot", "mainhand"); +// attachable.addProperty("attachable", "fmm:" + itemId); +// components.add("minecraft:attachable", attachable); +// +// minecraftItem.add("components", components); +// item.add("minecraft:item", minecraftItem); +// +// File itemFile = new File(bpItemsDir, itemId + ".json"); +// try (FileWriter writer = new FileWriter(itemFile)) { +// new Gson().toJson(item, writer); +// } +// +// Logger.info("Created custom bone item: fmm:" + itemId); +// +// } catch (Exception e) { +// Logger.warn("Failed to generate behavior pack item for " + itemId); +// e.printStackTrace(); +// } +// } + } + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index ebcd730..2b025a9 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.3.0 +version: 1.4.1 api-version: 1.14 author: MagmaGuy softdepend: @@ -18,10 +18,4 @@ commands: resourcepackmanager: aliases: - rspm - description: "Main command" - logify: - description: Posts current latest.log in mclo.gs, to make reporting bugs easier for admins. -permissions: - logify.*: - description: Lets admins run the /logify command, which sends the current latest server log to mclo.gs. - default: op \ No newline at end of file + description: "Main command" \ No newline at end of file From d29b88545250a1d6bffd9772b3d23cc38a9f6e8e Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sat, 26 Jul 2025 15:56:07 +0100 Subject: [PATCH 13/33] Resource Pack Manager 1.5.0: - [New] Full remake of core elements of the plugin! - [New] It is now possible to customize plugins that are supported, and even add support for new plugins via configuration files - [Fix] Fixed a lot of issues that would cause resource packs to fail to upload, fail to download or fail to load. --- pom.xml | 4 +- .../ResourcePackManager.java | 18 +- .../api/ResourcePackManagerAPI.java | 67 ++- .../autohost/AutoHost.java | 219 ++++++- .../config/BlueprintFolder.java | 2 +- .../config/DataConfig.java | 69 ++- .../CompatiblePluginConfig.java | 19 + .../CompatiblePluginConfigFields.java | 57 ++ .../premade/BackpackPlus.java | 13 + .../compatibleplugins/premade/BetterHUD.java | 15 + .../compatibleplugins/premade/EliteMobs.java | 14 + .../premade/FreeMinecraftModels.java | 14 + .../compatibleplugins/premade/ItemsAdder.java | 15 + .../premade/MMOInventory.java | 11 + .../premade/ModelEngine.java | 14 + .../compatibleplugins/premade/Nova.java | 13 + .../compatibleplugins/premade/Oraxen.java | 13 + .../premade/RealisticSurvival.java | 11 + .../premade/ResourcePackManager.java | 14 + .../premade/ValhallaMMO.java | 14 + .../compatibleplugins/premade/VaneCore.java | 12 + .../datacompliance/DataCompliance.java | 4 - .../listeners/ResourcePackGeneratedEvent.java | 4 +- .../resourcepackmanager/mixer/Mix.java | 538 +----------------- .../thirdparty/BackpackPlus.java | 15 - .../thirdparty/BetterHUD.java | 15 - .../thirdparty/EliteMobs.java | 15 - .../thirdparty/FreeMinecraftModels.java | 15 - .../thirdparty/GeneratorInterface.java | 11 +- .../thirdparty/ItemsAdder.java | 15 - .../thirdparty/MMOInventory.java | 13 - .../thirdparty/ModelEngine.java | 15 - .../resourcepackmanager/thirdparty/Nova.java | 15 - .../thirdparty/Oraxen.java | 15 - .../thirdparty/RealisticSurvival.java | 13 - .../thirdparty/ResourcePackManager.java | 15 - .../thirdparty/ThirdPartyResourcePack.java | 119 ++-- .../thirdparty/ValhallaMMO.java | 15 - .../thirdparty/VaneCore.java | 13 - src/main/resources/ReadMe.md | 55 +- src/main/resources/plugin.yml | 20 +- 41 files changed, 735 insertions(+), 833 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfig.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BackpackPlus.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/FreeMinecraftModels.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MMOInventory.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ModelEngine.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nova.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Oraxen.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/RealisticSurvival.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ResourcePackManager.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ValhallaMMO.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java diff --git a/pom.xml b/pom.xml index 21d1296..fb306aa 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.4.1 + 1.5.0 17 @@ -55,7 +55,7 @@ com.magmaguy MagmaCore - 1.15-SNAPSHOT + 1.18-SNAPSHOT compile diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 4da25dd..bb174db 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -1,19 +1,21 @@ package com.magmaguy.resourcepackmanager; -import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; -import com.magmaguy.resourcepackmanager.commands.ReloadCommand; -import com.magmaguy.resourcepackmanager.utils.VersionChecker; import com.magmaguy.freeminecraftmodels.bukkit.Metrics; import com.magmaguy.magmacore.MagmaCore; import com.magmaguy.magmacore.command.CommandManager; import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.listeners.ResourcePackGeneratedEvent; import com.magmaguy.resourcepackmanager.autohost.AutoHost; +import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import com.magmaguy.resourcepackmanager.config.BlueprintFolder; import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; -import com.magmaguy.resourcepackmanager.Listeners.ResourcePackGeneratedEvent; +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfig; import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; +import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; +import com.magmaguy.resourcepackmanager.utils.VersionChecker; import org.bukkit.Bukkit; import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; @@ -34,8 +36,9 @@ public void onEnable() { MagmaCore.onEnable(); plugin = this; - new DefaultConfig(); new DataConfig(); + new DefaultConfig(); + new CompatiblePluginConfig(); BlueprintFolder.initialize(); Mix.initialize(); if (DefaultConfig.isAutoHost()) @@ -49,17 +52,18 @@ public void onEnable() { AutoHost.initialize(); Metrics metrics = new Metrics(this, 22867); - VersionChecker.checkPluginVersion(); + MagmaCore.checkVersionUpdate("118574", "https://www.spigotmc.org/resources/resource-pack-manager.118574/"); } @Override - public void onLoad(){ + public void onLoad() { MagmaCore.createInstance(this); } @Override public void onDisable() { Logger.info("Disabling ResourcePackManager"); + ThirdPartyResourcePack.shutdown(); HandlerList.unregisterAll(this); AutoHost.shutdown(); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java index 84ed292..736a17a 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java @@ -10,30 +10,73 @@ public class ResourcePackManagerAPI { public static HashMap thirdPartyResourcePackHashMap = new HashMap<>(); /** - * Please read and follow the parameters carefully. - * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. - * @param path The absolute path to the resource pack file (zipped or folder), or the URL to the resource pack. Zipped, local resource packs are strongly recommended. - * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. - * @param distributes Whether the plugin can distribute the pack. Currently does nothing. - * @param zips Whether the resource pack linked to is zipped. - * @param local Whether the resource pack is local or not, meaning whether it appears in the directory of the server or is provided as a download link. Local is strongly recommended. + * Registers a resource pack with the ResourcePackManager. + * Either localPath or url must be provided (non-null), but not both. + * + * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. + * @param localPath The relative path to the resource pack file (zipped or folder) from the plugins directory, or null if using URL. + * @param url The URL to download the resource pack from, or null if using local path. + * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. + * @param distributes Whether the plugin can distribute the pack. Currently does nothing. + * @param zips Whether the resource pack is already zipped. If false, ResourcePackManager will zip it. * @param reloadCommand The reload command of the plugin adding a pack. Currently does nothing. */ public static void registerResourcePack(String pluginName, - String path, + String localPath, + String url, boolean encrypts, boolean distributes, boolean zips, - boolean local, String reloadCommand) { thirdPartyResourcePackHashMap.put(pluginName, - new ThirdPartyResourcePack(pluginName, path, encrypts, distributes, zips, local, reloadCommand)); + new ThirdPartyResourcePack(pluginName, localPath, url, encrypts, distributes, zips, reloadCommand)); + } + + /** + * Registers a local resource pack with the ResourcePackManager. + * + * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. + * @param localPath The relative path to the resource pack file (zipped or folder) from the plugins directory. + * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. + * @param distributes Whether the plugin can distribute the pack. Currently does nothing. + * @param zips Whether the resource pack is already zipped. If false, ResourcePackManager will zip it. + * @param reloadCommand The reload command of the plugin adding a pack. Currently does nothing. + */ + public static void registerLocalResourcePack(String pluginName, + String localPath, + boolean encrypts, + boolean distributes, + boolean zips, + String reloadCommand) { + thirdPartyResourcePackHashMap.put(pluginName, + new ThirdPartyResourcePack(pluginName, localPath, null, encrypts, distributes, zips, reloadCommand)); + } + + /** + * Registers a remote resource pack with the ResourcePackManager. + * The resource pack will be downloaded from the provided URL. + * + * @param pluginName The name of the plugin as it appears in the plugin list. Case-sensitive. + * @param url The URL to download the resource pack from. + * @param encrypts Whether the pack can be encrypted by the plugin. Currently does nothing. + * @param distributes Whether the plugin can distribute the pack. Currently does nothing. + * @param zips Whether the resource pack from the URL is already zipped. If false, ResourcePackManager will zip it. + * @param reloadCommand The reload command of the plugin adding a pack. Currently does nothing. + */ + public static void registerRemoteResourcePack(String pluginName, + String url, + boolean encrypts, + boolean distributes, + boolean zips, + String reloadCommand) { + thirdPartyResourcePackHashMap.put(pluginName, + new ThirdPartyResourcePack(pluginName, null, url, encrypts, distributes, zips, reloadCommand)); } /** * Reloads the plugin, thereby redoing everything necessary to merge and host the resource pack */ - public static void reloadResourcePack(){ + public static void reloadResourcePack() { ReloadCommand.reloadPlugin(Bukkit.getConsoleSender()); } -} +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 1c5807f..b50be98 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -1,10 +1,12 @@ package com.magmaguy.resourcepackmanager.autohost; -import com.magmaguy.resourcepackmanager.ResourcePackManager; -import com.magmaguy.resourcepackmanager.mixer.Mix; +import com.google.gson.Gson; +import com.google.gson.JsonObject; import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; +import com.magmaguy.resourcepackmanager.mixer.Mix; import lombok.Getter; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; @@ -23,11 +25,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.util.UUID; public class AutoHost { - private static final String finalURL = "http://magmaguy.com:50000/"; - private static final UUID pluginRSPUUID = UUID.randomUUID(); + private static final String finalURL = "http://magmaguy.com:50000/"; + private static boolean done = false; +// private static final String finalURL = "http://localhost:50000/"; + private static BukkitTask keepAlive = null; @Getter private static String rspUUID = null; @@ -37,7 +40,7 @@ private AutoHost() { } public static void sendResourcePack(Player player) { - if (rspUUID == null) return; + if (rspUUID == null || !done) return; Logger.info("Sending resource pack to " + player.getName()); player.setResourcePack(finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); } @@ -80,7 +83,10 @@ public void run() { private static void checkFileExistence() { initializeLink(); - if (rspUUID == null) return; + if (rspUUID == null) { + Logger.info("No resource pack found on the server! Uploading resource pack to the server..."); + return; + } if (!sendSHA1()) uploadFile(); } @@ -93,11 +99,45 @@ public static void initializeLink() { try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseString = EntityUtils.toString(response.getEntity()); - rspUUID = responseString.trim(); - DataConfig.setRspUUID(rspUUID); + int statusCode = response.getCode(); + + if (statusCode >= 200 && statusCode < 300) { + // Success - parse JSON response + try { + Gson gson = new Gson(); + JsonObject jsonResponse = gson.fromJson(responseString, JsonObject.class); + + // Check if response indicates success + if (jsonResponse.has("success") && jsonResponse.get("success").getAsBoolean()) { + rspUUID = jsonResponse.get("uuid").getAsString(); + DataConfig.setRspUUID(rspUUID); + Logger.info("Server initialized successfully: " + jsonResponse.get("message").getAsString()); + } else { + // Server returned error in success status code + Logger.warn("Server returned error in response: " + responseString); + rspUUID = null; + } + } catch (Exception e) { + // JSON parsing failed - validate if it looks like a UUID before using it + String trimmedResponse = responseString.trim(); + if (isValidUUID(trimmedResponse)) { + rspUUID = trimmedResponse; + DataConfig.setRspUUID(rspUUID); + Logger.info("Server initialized with UUID: " + rspUUID); + } else { + Logger.warn("Invalid response format from server: " + responseString); + rspUUID = null; + } + } + } else { + // Error - parse and log detailed error message + handleErrorResponse(responseString, statusCode, "initialization"); + rspUUID = null; + } } catch (Exception e) { Logger.warn("Failed to communicate with remote server!"); e.printStackTrace(); + rspUUID = null; } } catch (Exception e) { rspUUID = null; @@ -106,6 +146,23 @@ public static void initializeLink() { } } + // Helper method to validate UUID format + private static boolean isValidUUID(String uuidString) { + if (uuidString == null || uuidString.isEmpty()) { + return false; + } + + // Check if it looks like a UUID (basic validation) + // UUIDs are typically 36 characters with dashes or 32 characters without + String cleanUuid = uuidString.replaceAll("-", ""); + if (cleanUuid.length() != 32) { + return false; + } + + // Check if it contains only valid hex characters + return cleanUuid.matches("[0-9a-fA-F]+"); + } + public static void uploadFile() { Logger.info("Uploading resource!"); @@ -119,17 +176,28 @@ public static void uploadFile() { uploadFile.setEntity(builder.build()); try (CloseableHttpResponse response = httpClient.execute(uploadFile)) { + String responseString = EntityUtils.toString(response.getEntity()); + int statusCode = response.getCode(); + + if (statusCode >= 200 && statusCode < 300) { + Logger.info("Uploaded resource pack for automatic hosting! url: " + finalURL + "rsp_" + rspUUID); + done = true; + if (firstUpload) { + //Recover from a reload by sending the pack to online players + for (Player player : Bukkit.getOnlinePlayers()) + AutoHost.sendResourcePack(player); + } + firstUpload = false; + } else { + // Handle detailed error messages from server + handleErrorResponse(responseString, statusCode, "upload"); + return; + } } catch (Exception e) { - Logger.warn("Failed to communicate with remote server!"); + Logger.warn("Failed to communicate with remote server during upload!"); e.printStackTrace(); return; } - Logger.info("Uploaded resource pack for automatic hosting!"); - if (firstUpload) - //Recover from a reload by sending the pack to online players - for (Player player : Bukkit.getOnlinePlayers()) - AutoHost.sendResourcePack(player); - firstUpload = false; } catch (IOException e) { throw new RuntimeException(e); } @@ -146,21 +214,63 @@ private static boolean sendSHA1() { HttpEntity entity = builder.build(); httpPost.setEntity(entity); - // Convert the entity to a string for logging - ByteArrayOutputStream out = new ByteArrayOutputStream(); - entity.writeTo(out); - try (CloseableHttpResponse response = httpClient.execute(httpPost)) { String responseString = EntityUtils.toString(response.getEntity()); - return Boolean.valueOf(responseString); + int statusCode = response.getCode(); + + if (statusCode >= 200 && statusCode < 300) { + try { + // Parse JSON response + Gson gson = new Gson(); + JsonObject jsonResponse = gson.fromJson(responseString, JsonObject.class); + + // Check if response indicates success + if (jsonResponse.has("success") && jsonResponse.get("success").getAsBoolean()) { + boolean uploadNeeded = jsonResponse.get("uploadNeeded").getAsBoolean(); + + if (!uploadNeeded) { + Logger.info("Remote server already has this resource pack!"); + done = true; + if (firstUpload) { + //Recover from a reload by sending the pack to online players + for (Player player : Bukkit.getOnlinePlayers()) + AutoHost.sendResourcePack(player); + } + firstUpload = false; + } + return !uploadNeeded; // Return true if no upload needed + } else { + // Server returned error in success status code + Logger.warn("Server returned error in SHA1 response: " + responseString); + return false; + } + } catch (Exception e) { + // Fallback to boolean parsing (backward compatibility) + String trimmedResponse = responseString.trim(); + if (trimmedResponse.equals("true") || trimmedResponse.equals("false")) { + boolean result = Boolean.valueOf(trimmedResponse); + Logger.info("Remote server already has this resource pack! Response: " + responseString); + return result; + } else { + Logger.warn("Invalid SHA1 response format from server: " + responseString); + return false; + } + } + } else { + // Handle error response + handleErrorResponse(responseString, statusCode, "SHA1 check"); + return false; + } } catch (Exception e) { - Logger.warn("Failed to communicate with remote server!"); + Logger.warn("Failed to communicate with remote server during SHA1 check!"); e.printStackTrace(); + return false; } } catch (Exception e) { + Logger.warn("Failed to create HTTP client for SHA1 check!"); e.printStackTrace(); + return false; } - return false; } private static void sendStillAlive() throws IOException { @@ -172,8 +282,20 @@ private static void sendStillAlive() throws IOException { httpPost.setEntity(builder.build()); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + String responseString = EntityUtils.toString(response.getEntity()); + int statusCode = response.getCode(); + + if (statusCode >= 200 && statusCode < 300) { + // Success - optionally log the success message + // Logger.info("Still alive ping successful"); + } else { + // Handle error - this might indicate session expired + handleErrorResponse(responseString, statusCode, "still alive"); + // Reset UUID to trigger re-initialization + rspUUID = null; + } } catch (Exception e) { - Logger.warn("Failed to communicate with remote server!"); + Logger.warn("Failed to communicate with remote server during still alive ping!"); e.printStackTrace(); } } @@ -216,5 +338,54 @@ public static void dataComplianceRequest() throws IOException { public static void shutdown() { if (keepAlive != null) keepAlive.cancel(); + done = false; + } + + private static void handleErrorResponse(String responseString, int statusCode, String operation) { + try { + Gson gson = new Gson(); + JsonObject errorResponse = gson.fromJson(responseString, JsonObject.class); + + if (errorResponse.has("error")) { + JsonObject error = errorResponse.getAsJsonObject("error"); + String errorCode = error.get("code").getAsString(); + String errorMessage = error.get("message").getAsString(); + String errorType = error.get("type").getAsString(); + + Logger.warn("=== Resource Pack " + operation.toUpperCase() + " ERROR ==="); + Logger.warn("Error Code: " + errorCode); + Logger.warn("Error Type: " + errorType); + Logger.warn("Message: " + errorMessage); + Logger.warn("HTTP Status: " + statusCode); + Logger.warn("====================================="); + + // Handle specific error types + switch (errorCode) { + case "MISSING_REQUIRED_FILES": + Logger.warn("Your resource pack structure is incorrect!"); + Logger.warn("Make sure pack.png and pack.mcmeta are in the root of your zip file."); + break; + case "FILE_TOO_LARGE": + Logger.warn("Your resource pack is too large! Please reduce the file size."); + break; + case "INVALID_FILE_FORMAT": + Logger.warn("Your resource pack file is corrupted or not a valid zip file."); + break; + case "SESSION_NOT_FOUND": + Logger.warn("Server session expired. Will attempt to reinitialize..."); + rspUUID = null; // Trigger re-initialization + break; + case "SERVER_UNAVAILABLE": + Logger.warn("Remote server is temporarily unavailable. Will retry later."); + break; + } + } else { + // Fallback for non-JSON error responses + Logger.warn("Server error during " + operation + " (HTTP " + statusCode + "): " + responseString); + } + } catch (Exception e) { + // Fallback if JSON parsing fails + Logger.warn("Server error during " + operation + " (HTTP " + statusCode + "): " + responseString); + } } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java index 03966aa..3a8c9f4 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java @@ -1,8 +1,8 @@ package com.magmaguy.resourcepackmanager.config; -import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; +import com.magmaguy.resourcepackmanager.ResourcePackManager; import java.io.File; import java.io.IOException; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java index 919d8e0..d8b25a0 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DataConfig.java @@ -3,6 +3,8 @@ import com.magmaguy.magmacore.config.ConfigurationFile; import com.magmaguy.magmacore.util.Logger; +import java.util.UUID; + public class DataConfig extends ConfigurationFile { private static DataConfig instance = null; @@ -13,22 +15,81 @@ public DataConfig() { public static String getRspUUID() { String uuid = instance.getFileConfiguration().getString("uuid"); - if (uuid == null) uuid = ""; - return uuid; + + // If UUID exists but is invalid, clear it and return null + if (uuid != null && !uuid.isEmpty() && !isValidUUID(uuid)) { + Logger.warn("Invalid UUID found in config file: " + uuid + ". Deleting invalid UUID from file."); + instance.clearInvalidUUID(); + return null; + } + + // Return null for empty/null UUIDs instead of empty string + return (uuid == null || uuid.isEmpty()) ? "" : uuid; } public static void setRspUUID(String rspUUID) { + // Validate UUID if not null/empty + if (rspUUID != null && !rspUUID.isEmpty() && !isValidUUID(rspUUID)) { + throw new IllegalArgumentException("Invalid UUID format: " + rspUUID + + ". UUID must be in format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); + } + + // Store null as null, not empty string instance.getFileConfiguration().set("uuid", rspUUID); try { instance.getFileConfiguration().save(instance.file); + if (rspUUID != null && !rspUUID.isEmpty()) { + Logger.info("Successfully saved UUID: " + rspUUID); + } else { + Logger.info("Successfully cleared UUID from config."); + } } catch (Exception e) { Logger.warn("Failed to save uuid!"); e.printStackTrace(); } } + /** + * Clears invalid UUID from config file + */ + private void clearInvalidUUID() { + getFileConfiguration().set("uuid", null); + try { + getFileConfiguration().save(file); + Logger.info("Invalid UUID cleared from config file."); + } catch (Exception e) { + Logger.warn("Failed to clear invalid UUID from config!"); + e.printStackTrace(); + } + } + + /** + * Validates if a string is a proper UUID format + * @param uuidString The string to validate + * @return true if valid UUID format, false otherwise + */ + private static boolean isValidUUID(String uuidString) { + if (uuidString == null || uuidString.trim().isEmpty()) { + return false; + } + + try { + // Use Java's built-in UUID validation + UUID.fromString(uuidString.trim()); + return true; + } catch (IllegalArgumentException e) { + return false; + } + } + @Override public void initializeValues() { - + // Validate existing UUID on initialization and clear if invalid + String existingUUID = getFileConfiguration().getString("uuid"); + if (existingUUID != null && !existingUUID.isEmpty() && !isValidUUID(existingUUID)) { + Logger.warn("Invalid UUID found in config file during initialization: " + existingUUID); + Logger.warn("Deleting invalid UUID from file. A new one will be generated on next server connection."); + clearInvalidUUID(); + } } -} +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfig.java new file mode 100644 index 0000000..b1223fe --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfig.java @@ -0,0 +1,19 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins; + +import com.magmaguy.magmacore.config.CustomConfig; +import lombok.Getter; + +import java.util.HashMap; +import java.util.Map; + +public class CompatiblePluginConfig extends CustomConfig { + @Getter + private static Map compatiblePlugins = new HashMap<>(); + + public CompatiblePluginConfig() { + super("compatible_plugins", "com.magmaguy.resourcepackmanager.config.compatibleplugins.premade", CompatiblePluginConfigFields.class); + compatiblePlugins = new HashMap<>(); + for (String key : super.getCustomConfigFieldsHashMap().keySet()) + compatiblePlugins.put(key, (CompatiblePluginConfigFields) super.getCustomConfigFieldsHashMap().get(key)); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java new file mode 100644 index 0000000..2f6b2f2 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java @@ -0,0 +1,57 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins; + +import com.magmaguy.magmacore.config.CustomConfigFields; +import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; +import lombok.Getter; +import lombok.Setter; + +public class CompatiblePluginConfigFields extends CustomConfigFields { + + @Getter + @Setter + private String pluginName = "placeholder"; + @Getter + @Setter + private String url = null; + @Getter + @Setter + private String localPath = null; + @Getter + @Setter + private boolean encrypts = false; + @Getter + @Setter + private boolean distributes = false; + @Getter + @Setter + private boolean zips = true; + @Getter + @Setter + private String reloadCommand; + @Getter + @Setter + private boolean resourcePackUpdated = false; + @Getter + @Setter + private String mixerFilename; + + public CompatiblePluginConfigFields(String filename, boolean isEnabled) { + super(filename, isEnabled); + } + + @Override + public void processConfigFields() { + this.isEnabled = processBoolean("isEnabled", isEnabled, isEnabled, true); + this.pluginName = processString("pluginName", pluginName, pluginName, true); + this.url = processString("url", url, url, true); + this.encrypts = processBoolean("encrypts", encrypts, encrypts, true); + this.distributes = processBoolean("distributes", distributes, distributes, true); + this.zips = processBoolean("zips", zips, zips, true); + this.reloadCommand = processString("reloadCommand", reloadCommand, reloadCommand, true); + this.resourcePackUpdated = processBoolean("resourcePackUpdated", resourcePackUpdated, resourcePackUpdated, true); + this.mixerFilename = processString("mixerFilename", mixerFilename, mixerFilename, true); + this.localPath = processString("localPath", localPath, localPath, true); + + if (isEnabled) ThirdPartyResourcePack.initializeThirdPartyResourcePack(this); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BackpackPlus.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BackpackPlus.java new file mode 100644 index 0000000..d829860 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BackpackPlus.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class BackpackPlus extends CompatiblePluginConfigFields { + public BackpackPlus() { + super("backpack_plus", true); + setPluginName("BackpackPlus"); + setLocalPath("BackpackPlus" + File.separatorChar + "pack" + File.separatorChar + "resourcepack.zip"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java new file mode 100644 index 0000000..56235e9 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class BetterHUD extends CompatiblePluginConfigFields { + public BetterHUD() { + super("better_hud", true); + setPluginName("BetterHUD"); + setLocalPath("BackpackPlus" + File.separatorChar + "pack" + File.separatorChar + "resourcepack.zip"); + setEncrypts(true); + setReloadCommand("betterhud reload"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java new file mode 100644 index 0000000..3db7b9a --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class EliteMobs extends CompatiblePluginConfigFields { + public EliteMobs() { + super("elitemobs", true); + setPluginName("EliteMobs"); + setLocalPath("EliteMobs" + File.separatorChar + "exports" + File.separatorChar + "elitemobs_resource_pack.zip"); + setReloadCommand("elitemobs reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/FreeMinecraftModels.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/FreeMinecraftModels.java new file mode 100644 index 0000000..44ab1f0 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/FreeMinecraftModels.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class FreeMinecraftModels extends CompatiblePluginConfigFields { + public FreeMinecraftModels() { + super("free_minecraft_models", true); + setPluginName("FreeMinecraftModels"); + setLocalPath("FreeMinecraftModels" + File.separatorChar + "output" + File.separatorChar + "FreeMinecraftModels.zip"); + setReloadCommand("freeminecraftmodels reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java new file mode 100644 index 0000000..01b18d0 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java @@ -0,0 +1,15 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ItemsAdder extends CompatiblePluginConfigFields { + public ItemsAdder() { + super("items_adder", true); + setPluginName("ItemsAdder"); + setLocalPath("ItemsAdder" + File.separatorChar + "output" + File.separatorChar + "generated.zip"); + setEncrypts(true); + setDistributes(true); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MMOInventory.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MMOInventory.java new file mode 100644 index 0000000..d357c0d --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MMOInventory.java @@ -0,0 +1,11 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +public class MMOInventory extends CompatiblePluginConfigFields { + public MMOInventory() { + super("mmo_inventory", true); + setPluginName("MMOInventory"); + setUrl("https://www.dropbox.com/s/1lftxmzh0q4b5yu/mmoinv_rp_3.zip?dl=1"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ModelEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ModelEngine.java new file mode 100644 index 0000000..d16a585 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ModelEngine.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ModelEngine extends CompatiblePluginConfigFields { + public ModelEngine() { + super("model_engine", true); + setPluginName("ModelEngine"); + setLocalPath("ModelEngine" + File.separatorChar + "resource pack.zip"); + setReloadCommand("meg reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nova.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nova.java new file mode 100644 index 0000000..ce68b14 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nova.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class Nova extends CompatiblePluginConfigFields { + public Nova() { + super("nova", true); + setPluginName("Nova"); + setLocalPath("Nova" + File.separatorChar + "resource_pack" + File.separatorChar + "ResourcePack.zip"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Oraxen.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Oraxen.java new file mode 100644 index 0000000..68ca0a5 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Oraxen.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class Oraxen extends CompatiblePluginConfigFields { + public Oraxen() { + super("oraxen", true); + setPluginName("Oraxen"); + setLocalPath("Oraxen" + File.separatorChar + "pack" + File.separatorChar + "pack.zip"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/RealisticSurvival.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/RealisticSurvival.java new file mode 100644 index 0000000..774f6f6 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/RealisticSurvival.java @@ -0,0 +1,11 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +public class RealisticSurvival extends CompatiblePluginConfigFields { + public RealisticSurvival() { + super("realistic_survival", true); + setPluginName("RealisticSurvival"); + setUrl("https://www.dropbox.com/scl/fi/5ggc7t20bfxmrzsbb1kok/Realistic-Survival-RP-1.2.8-RELEASE.zip?rlkey=55llpn4zj146usfmm8okf1ier&dl=1"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ResourcePackManager.java new file mode 100644 index 0000000..b69b50a --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ResourcePackManager.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ResourcePackManager extends CompatiblePluginConfigFields { + public ResourcePackManager() { + super("resource_pack_manager", true); + setPluginName("ResourcePackManager"); + setLocalPath("ResourcePackManager" + File.separatorChar + "blueprint" + File.separatorChar + "blueprint.zip"); + setReloadCommand("rspm reload"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ValhallaMMO.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ValhallaMMO.java new file mode 100644 index 0000000..e4a506b --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ValhallaMMO.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class ValhallaMMO extends CompatiblePluginConfigFields { + public ValhallaMMO() { + super("valhalla_mmo", true); + setPluginName("ValhallaMMO"); + setLocalPath("ValhallaMMO" + File.separatorChar + "resourcepack"); + setZips(false); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java new file mode 100644 index 0000000..da73fa3 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java @@ -0,0 +1,12 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +public class VaneCore extends CompatiblePluginConfigFields { + public VaneCore() { + super("vane_core", true); + setPluginName("vane-core"); + setLocalPath("vane-resource-pack.zip"); + setDistributes(true); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java b/src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java deleted file mode 100644 index bdd04bb..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/datacompliance/DataCompliance.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.magmaguy.resourcepackmanager.datacompliance; - -public class DataCompliance { -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java index 663c0e0..07a5d6b 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java @@ -1,7 +1,7 @@ -package com.magmaguy.resourcepackmanager.Listeners; +package com.magmaguy.resourcepackmanager.listeners; -import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import com.magmaguy.freeminecraftmodels.events.ResourcePackGenerationEvent; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import org.bukkit.Bukkit; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 07eb578..092cd01 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -1,27 +1,29 @@ package com.magmaguy.resourcepackmanager.mixer; -import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.google.gson.*; import com.google.gson.stream.JsonReader; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; +import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.api.ResourcePackManagerAPI; import com.magmaguy.resourcepackmanager.config.DefaultConfig; -import com.magmaguy.resourcepackmanager.thirdparty.*; +import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; -import java.io.*; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.security.NoSuchAlgorithmException; -import java.util.*; -import java.util.stream.Stream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; - private static HashMap priorities; private static List resourcePacks; private static List orderedResourcePacks; @Getter @@ -59,23 +61,9 @@ private static boolean initializeDefaultPluginFolders() { private static void initializeThirdPartyResourcePacks() { orderedResourcePacks = new ArrayList<>(); - priorities = new HashMap<>(); // Use a mutable list so we can addAll() to it - List resourcePackManagers = new ArrayList<>(Arrays.asList( - new com.magmaguy.resourcepackmanager.thirdparty.ResourcePackManager(), - new EliteMobs(), - new FreeMinecraftModels(), - new ModelEngine(), - new ItemsAdder(), - new Nova(), - new Oraxen(), - new BetterHUD(), - new ValhallaMMO(), - new VaneCore(), - new BackpackPlus(), - new RealisticSurvival() - )); + List resourcePackManagers = new ArrayList<>(ThirdPartyResourcePack.thirdPartyResourcePacks.stream().toList()); // Add any API-registered packs resourcePackManagers.addAll(ResourcePackManagerAPI.thirdPartyResourcePackHashMap.values()); @@ -172,7 +160,7 @@ private static void cloneToOutputAndUnzip() { } catch (Exception e) { if (resourcePack == null) Logger.warn("Failed to extract resource pack! A file might be encrypted."); - else + else Logger.warn("Failed to extract resource pack " + resourcePack.getName() + " ! The file might be encrypted."); e.printStackTrace(); } @@ -191,7 +179,7 @@ private static void createOutputDefaultElements() { } List orderedFiles = new ArrayList<>(); - for (String filename : orderedResourcePacks){ + for (String filename : orderedResourcePacks) { orderedFiles.add(new File(getOutputFolder().getPath() + File.separatorChar + filename)); } @@ -211,18 +199,18 @@ private static void createOutputDefaultElements() { return; } - if (!DefaultConfig.getResourcePackRerouting().isEmpty() && !DefaultConfig.getResourcePackRerouting().isBlank()){ - try{ + if (!DefaultConfig.getResourcePackRerouting().isEmpty() && !DefaultConfig.getResourcePackRerouting().isBlank()) { + try { File rerouteFolder = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().getAbsolutePath() + File.separatorChar + DefaultConfig.getResourcePackRerouting()); if (!rerouteFolder.exists()) { Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that folder does not exist!"); - } else if (!rerouteFolder.isDirectory()){ + } else if (!rerouteFolder.isDirectory()) { Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that is a file and not a folder!"); } else if (!ZipFile.zip(getOutputResourcePackFolder(), rerouteFolder.getPath() + ".zip")) { Logger.warn("Failed to zip merged resource pack into reroute directory!"); return; } - } catch (Exception e){ + } catch (Exception e) { Logger.warn("Failed to reroute zipped file to " + DefaultConfig.getResourcePackRerouting()); } } @@ -388,497 +376,5 @@ private static JsonArray mergeJsonArrays(JsonArray array1, JsonArray array2) { return mergedArray; } -// -// @Getter -// private static File finalBedrockResourcePack; -// @Getter -// private static String finalBedrockSHA1; -// -// // Call this method in createOutputDefaultElements() after finalResourcePack is set -// private static void generateBedrockResourcePack() { -// if (finalResourcePack == null) { -// Logger.warn("Cannot generate Bedrock resource pack - Java resource pack was not created successfully!"); -// return; -// } -// -// Logger.info("Starting Bedrock resource pack generation..."); -// -// File bedrockOutputFolder = new File(getOutputFolder().getAbsolutePath() + File.separatorChar + resourcePackName + "_Bedrock"); -// -// // Clean up any existing Bedrock folder -// if (bedrockOutputFolder.exists()) { -// recursivelyDeleteDirectory(bedrockOutputFolder); -// } -// -// try { -// bedrockOutputFolder.mkdirs(); -// -// // Extract the Java resource pack to process it -// File tempJavaFolder = new File(getOutputFolder().getAbsolutePath() + File.separatorChar + "temp_java_extract"); -// if (tempJavaFolder.exists()) { -// recursivelyDeleteDirectory(tempJavaFolder); -// } -// tempJavaFolder.mkdirs(); -// -// ZipFile.unzip(finalResourcePack, tempJavaFolder); -// -// // Generate Bedrock pack contents -// generateBedrockManifest(bedrockOutputFolder); -// generateBehaviorPackManifest(bedrockOutputFolder); -// convertFreeMinecraftModelsAssets(tempJavaFolder, bedrockOutputFolder); -// -// // Create the Bedrock resource pack zip -// String bedrockPackName = resourcePackName + "_Bedrock.mcpack"; -// File bedrockZipFile = new File(getOutputFolder().getAbsolutePath() + File.separatorChar + bedrockPackName); -// -// if (ZipFile.zip(bedrockOutputFolder, bedrockZipFile.getAbsolutePath())) { -// finalBedrockResourcePack = bedrockZipFile; -// try { -// finalBedrockSHA1 = SHA1Generator.sha1CodeString(finalBedrockResourcePack); -// Logger.info("Successfully generated Bedrock resource pack: " + bedrockPackName); -// } catch (Exception e) { -// Logger.warn("Failed to generate SHA1 for Bedrock resource pack!"); -// } -// } else { -// Logger.warn("Failed to zip Bedrock resource pack!"); -// } -// -// // Clean up temporary folders -// recursivelyDeleteDirectory(tempJavaFolder); -// recursivelyDeleteDirectory(bedrockOutputFolder); -// -// } catch (Exception e) { -// Logger.warn("Failed to generate Bedrock resource pack!"); -// e.printStackTrace(); -// } -// } -// -// private static void generateBedrockManifest(File bedrockOutputFolder) { -// try { -// JsonObject manifest = new JsonObject(); -// manifest.addProperty("format_version", 2); -// -// // Header -// JsonObject header = new JsonObject(); -// header.addProperty("name", "FreeMinecraftModels Bedrock"); -// header.addProperty("description", "Bedrock Edition conversion of FreeMinecraftModels resource pack"); -// header.addProperty("uuid", UUID.randomUUID().toString()); -// JsonArray headerVersion = new JsonArray(); -// headerVersion.add(1); headerVersion.add(0); headerVersion.add(0); -// header.add("version", headerVersion); -// JsonArray minEngineVersion = new JsonArray(); -// minEngineVersion.add(1); minEngineVersion.add(21); minEngineVersion.add(0); -// header.add("min_engine_version", minEngineVersion); -// manifest.add("header", header); -// -// // Modules -// JsonArray modules = new JsonArray(); -// JsonObject resourceModule = new JsonObject(); -// resourceModule.addProperty("type", "resources"); -// resourceModule.addProperty("uuid", UUID.randomUUID().toString()); -// JsonArray moduleVersion = new JsonArray(); -// moduleVersion.add(1); moduleVersion.add(0); moduleVersion.add(0); -// resourceModule.add("version", moduleVersion); -// modules.add(resourceModule); -// manifest.add("modules", modules); -// -// // Write manifest.json -// File manifestFile = new File(bedrockOutputFolder, "manifest.json"); -// try (FileWriter writer = new FileWriter(manifestFile)) { -// new Gson().toJson(manifest, writer); -// } -// -// } catch (Exception e) { -// Logger.warn("Failed to generate Bedrock manifest!"); -// e.printStackTrace(); -// } -// } -// -// private static void generateBehaviorPackManifest(File bedrockOutputFolder) { -// try { -// JsonObject manifest = new JsonObject(); -// manifest.addProperty("format_version", 2); -// -// // Header -// JsonObject header = new JsonObject(); -// header.addProperty("name", "FreeMinecraftModels Bedrock Behavior"); -// header.addProperty("description", "Behavior pack for FreeMinecraftModels custom bone items"); -// header.addProperty("uuid", UUID.randomUUID().toString()); -// JsonArray headerVersion = new JsonArray(); -// headerVersion.add(1); headerVersion.add(0); headerVersion.add(0); -// header.add("version", headerVersion); -// JsonArray minEngineVersion = new JsonArray(); -// minEngineVersion.add(1); minEngineVersion.add(21); minEngineVersion.add(0); -// header.add("min_engine_version", minEngineVersion); -// manifest.add("header", header); -// -// // Modules -// JsonArray modules = new JsonArray(); -// JsonObject behaviorModule = new JsonObject(); -// behaviorModule.addProperty("type", "data"); -// behaviorModule.addProperty("uuid", UUID.randomUUID().toString()); -// JsonArray moduleVersion = new JsonArray(); -// moduleVersion.add(1); moduleVersion.add(0); moduleVersion.add(0); -// behaviorModule.add("version", moduleVersion); -// modules.add(behaviorModule); -// manifest.add("modules", modules); -// -// // Write BP manifest.json -// File bpFolder = new File(bedrockOutputFolder, "BP"); -// bpFolder.mkdirs(); -// File manifestFile = new File(bpFolder, "manifest.json"); -// try (FileWriter writer = new FileWriter(manifestFile)) { -// new Gson().toJson(manifest, writer); -// } -// -// } catch (Exception e) { -// Logger.warn("Failed to generate Behavior Pack manifest!"); -// e.printStackTrace(); -// } -// } -// -// private static void convertFreeMinecraftModelsAssets(File javaFolder, File bedrockFolder) { -// File freeMinecraftModelsAssets = new File(javaFolder, "assets" + File.separator + "freeminecraftmodels"); -// if (!freeMinecraftModelsAssets.exists()) { -// Logger.info("No FreeMinecraftModels assets found to convert"); -// return; -// } -// -// Logger.info("Converting FreeMinecraftModels assets to Bedrock format..."); -// -// try { -// // Create Bedrock directory structure -// File attachablesDir = new File(bedrockFolder, "attachables"); -// File modelsDir = new File(bedrockFolder, "models" + File.separator + "entity"); -// File texturesDir = new File(bedrockFolder, "textures" + File.separator + "entity"); -// -// attachablesDir.mkdirs(); -// modelsDir.mkdirs(); -// texturesDir.mkdirs(); -// -// // Copy textures first -// File javaTexturesDir = new File(freeMinecraftModelsAssets, "textures" + File.separator + "entity"); -// if (javaTexturesDir.exists()) { -// copyTexturesRecursively(javaTexturesDir, texturesDir); -// } -// -// // Convert Java models to Bedrock - each bone becomes separate item -// File javaModelsDir = new File(freeMinecraftModelsAssets, "models"); -// if (javaModelsDir.exists()) { -// convertJavaModelsToBedrock(javaModelsDir, attachablesDir, modelsDir, bedrockFolder); -// } -// -// } catch (Exception e) { -// Logger.warn("Failed to convert FreeMinecraftModels assets!"); -// e.printStackTrace(); -// } -// } -// -// private static void copyTexturesRecursively(File sourceDir, File targetDir) { -// if (!sourceDir.exists() || !sourceDir.isDirectory()) return; -// -// try { -// for (File file : sourceDir.listFiles()) { -// if (file.isDirectory()) { -// File newTargetDir = new File(targetDir, file.getName()); -// newTargetDir.mkdirs(); -// copyTexturesRecursively(file, newTargetDir); -// } else if (file.getName().endsWith(".png")) { -// File targetFile = new File(targetDir, file.getName()); -// Files.copy(file.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING); -// } -// } -// } catch (Exception e) { -// Logger.warn("Failed to copy textures from " + sourceDir.getName()); -// e.printStackTrace(); -// } -// } -// -// private static void convertJavaModelsToBedrock(File javaModelsDir, File attachablesDir, File modelsDir, File bedrockFolder) { -// // Look for model folders (each model has its own folder with bone JSON files) -// for (File modelFolder : javaModelsDir.listFiles()) { -// if (!modelFolder.isDirectory()) continue; -// -// String modelName = modelFolder.getName(); -// -// // Skip equipment folder - that's for 1.21.4+ format -// if (modelName.equals("equipment")) continue; -// -// Logger.info("Converting model: " + modelName); -// -// try { -// // Process each bone file individually - each becomes a separate item -// convertModelBonesToBedrock(modelFolder, modelName, attachablesDir, modelsDir, bedrockFolder); -// -// } catch (Exception e) { -// Logger.warn("Failed to convert model: " + modelName); -// e.printStackTrace(); -// } -// } -// } -// -// private static void convertModelBonesToBedrock(File modelFolder, String modelName, File attachablesDir, File modelsDir, File bedrockFolder) { -// // Process each bone file as a separate item/geometry -// List boneFiles = new ArrayList<>(); -// collectBoneFiles(modelFolder, boneFiles); -// -// for (File boneFile : boneFiles) { -// String boneName = boneFile.getName().replace(".json", ""); -// -// // Skip the root bone -// if (boneName.equals("freeminecraftmodels_autogenerated_root")) continue; -// -// Logger.info("Converting bone: " + boneName + " from model: " + modelName); -// -// try { -// // Create unique identifiers for this bone -// String boneId = modelName + "_" + boneName; -// String itemId = "fmm_bone_" + boneId; -// -// // Convert bone to Bedrock geometry -// convertSingleBoneToBedrock(boneFile, boneName, modelName, modelsDir); -// -// // Create attachable for this bone -// generateBoneAttachable(itemId, boneName, modelName, attachablesDir); -// -// // Create behavior pack item for this bone -// generateBoneItem(itemId, boneName, modelName, bedrockFolder); -// -// } catch (Exception e) { -// Logger.warn("Failed to convert bone: " + boneName + " from model: " + modelName); -// e.printStackTrace(); -// } -// } -// } -// -// private static void collectBoneFiles(File folder, List boneFiles) { -// for (File file : folder.listFiles()) { -// if (file.isDirectory()) { -// collectBoneFiles(file, boneFiles); -// } else if (file.getName().endsWith(".json")) { -// boneFiles.add(file); -// } -// } -// } -// -// private static void convertSingleBoneToBedrock(File boneFile, String boneName, String modelName, File modelsDir) { -// try { -// // Read Java bone model -// JsonObject javaModel = JsonParser.parseReader(new FileReader(boneFile)).getAsJsonObject(); -// -// // Create Bedrock geometry -// JsonObject geometry = new JsonObject(); -// geometry.addProperty("format_version", "1.21.0"); -// -// JsonArray geometryArray = new JsonArray(); -// JsonObject geometryDef = new JsonObject(); -// -// JsonObject description = new JsonObject(); -// description.addProperty("identifier", "geometry.fmm_bone." + modelName + "_" + boneName); -// description.addProperty("texture_width", 64); -// description.addProperty("texture_height", 64); -// geometryDef.add("description", description); -// -// JsonArray bones = new JsonArray(); -// -// // Create root bone -// JsonObject rootBone = new JsonObject(); -// rootBone.addProperty("name", "root"); -// JsonArray pivot = new JsonArray(); -// pivot.add(0); pivot.add(0); pivot.add(0); -// rootBone.add("pivot", pivot); -// -// // Convert elements (cubes) to Bedrock format -// if (javaModel.has("elements")) { -// JsonArray elements = javaModel.getAsJsonArray("elements"); -// JsonArray cubes = new JsonArray(); -// -// for (int i = 0; i < elements.size(); i++) { -// JsonObject element = elements.get(i).getAsJsonObject(); -// JsonObject bedrockCube = convertJavaCubeToBedrock(element); -// if (bedrockCube != null) { -// cubes.add(bedrockCube); -// } -// } -// -// if (cubes.size() > 0) { -// rootBone.add("cubes", cubes); -// } -// } -// -// bones.add(rootBone); -// geometryDef.add("bones", bones); -// geometryArray.add(geometryDef); -// geometry.add("minecraft:geometry", geometryArray); -// -// // Write geometry file -// File geoFile = new File(modelsDir, "fmm_bone_" + modelName + "_" + boneName + ".geo.json"); -// try (FileWriter writer = new FileWriter(geoFile)) { -// new Gson().toJson(geometry, writer); -// } -// -// } catch (Exception e) { -// Logger.warn("Failed to convert bone: " + boneName); -// e.printStackTrace(); -// } -// } -// -// private static JsonObject convertJavaCubeToBedrock(JsonObject javaCube) { -// try { -// JsonObject bedrockCube = new JsonObject(); -// -// // Convert origin (from in Java) -// if (javaCube.has("from")) { -// JsonArray from = javaCube.getAsJsonArray("from"); -// JsonArray origin = new JsonArray(); -// origin.add(from.get(0)); -// origin.add(from.get(1)); -// origin.add(from.get(2)); -// bedrockCube.add("origin", origin); -// } -// -// // Convert size (to - from in Java) -// if (javaCube.has("from") && javaCube.has("to")) { -// JsonArray from = javaCube.getAsJsonArray("from"); -// JsonArray to = javaCube.getAsJsonArray("to"); -// JsonArray size = new JsonArray(); -// size.add(to.get(0).getAsDouble() - from.get(0).getAsDouble()); -// size.add(to.get(1).getAsDouble() - from.get(1).getAsDouble()); -// size.add(to.get(2).getAsDouble() - from.get(2).getAsDouble()); -// bedrockCube.add("size", size); -// } -// -// // Convert UV mapping - extract from faces -// if (javaCube.has("faces")) { -// JsonObject faces = javaCube.getAsJsonObject("faces"); -// // Use north face UV as default, or first available face -// JsonObject northFace = null; -// -// if (faces.has("north")) northFace = faces.getAsJsonObject("north"); -// else if (faces.has("south")) northFace = faces.getAsJsonObject("south"); -// else if (faces.has("east")) northFace = faces.getAsJsonObject("east"); -// else if (faces.has("west")) northFace = faces.getAsJsonObject("west"); -// else if (faces.has("up")) northFace = faces.getAsJsonObject("up"); -// else if (faces.has("down")) northFace = faces.getAsJsonObject("down"); -// -// if (northFace != null && northFace.has("uv")) { -// JsonArray javaUV = northFace.getAsJsonArray("uv"); -// JsonArray bedrockUV = new JsonArray(); -// bedrockUV.add(javaUV.get(0)); -// bedrockUV.add(javaUV.get(1)); -// bedrockCube.add("uv", bedrockUV); -// } else { -// // Default UV -// JsonArray uv = new JsonArray(); -// uv.add(0); uv.add(0); -// bedrockCube.add("uv", uv); -// } -// } -// -// return bedrockCube; -// -// } catch (Exception e) { -// Logger.warn("Failed to convert Java cube to Bedrock format"); -// return null; -// } -// } -// -// private static void generateBoneAttachable(String itemId, String boneName, String modelName, File attachablesDir) { -// try { -// JsonObject attachable = new JsonObject(); -// attachable.addProperty("format_version", "1.10.0"); -// -// JsonObject attachableData = new JsonObject(); -// JsonObject description = new JsonObject(); -// description.addProperty("identifier", "fmm:" + itemId); -// -// JsonObject materials = new JsonObject(); -// materials.addProperty("default", "entity_alphatest"); -// materials.addProperty("enchanted", "entity_alphatest_glint"); -// description.add("materials", materials); -// -// JsonObject textures = new JsonObject(); -// // Try to find appropriate texture path -// String texturePath = "textures/entity/" + modelName + "/texture"; -// textures.addProperty("default", texturePath); -// description.add("textures", textures); -// -// JsonObject geometry = new JsonObject(); -// geometry.addProperty("default", "geometry.fmm_bone." + modelName + "_" + boneName); -// description.add("geometry", geometry); -// -// // Configure for handheld/inventory item display -// JsonArray scripts = new JsonArray(); -// JsonObject animateScript = new JsonObject(); -// animateScript.addProperty("animate", "third_person_main_hand"); -// scripts.add(animateScript); -// description.add("scripts", scripts); -// -// attachableData.add("description", description); -// attachable.add("minecraft:attachable", attachableData); -// -// File attachableFile = new File(attachablesDir, itemId + ".json"); -// try (FileWriter writer = new FileWriter(attachableFile)) { -// new Gson().toJson(attachable, writer); -// } -// -// Logger.info("Created attachable: fmm:" + itemId); -// -// } catch (Exception e) { -// Logger.warn("Failed to generate attachable for " + itemId); -// e.printStackTrace(); -// } -// } -// -// private static void generateBoneItem(String itemId, String boneName, String modelName, File bedrockFolder) { -// try { -// // Create BP items folder -// File bpItemsDir = new File(bedrockFolder, "BP" + File.separator + "items"); -// bpItemsDir.mkdirs(); -// -// JsonObject item = new JsonObject(); -// item.addProperty("format_version", "1.21.0"); -// -// JsonObject minecraftItem = new JsonObject(); -// JsonObject description = new JsonObject(); -// description.addProperty("identifier", "fmm:" + itemId); -// description.addProperty("category", "Items"); -// minecraftItem.add("description", description); -// -// JsonObject components = new JsonObject(); -// -// // Set max stack size -// components.addProperty("minecraft:max_stack_size", 64); -// -// // Set icon - use leather horse armor for now -// components.addProperty("minecraft:icon", "leather_horse_armor"); -// -// // Set display name -// JsonObject displayName = new JsonObject(); -// displayName.addProperty("value", modelName + " " + boneName.replace("_", " ")); -// components.add("minecraft:display_name", displayName); -// -// // Set attachable for handheld items -// JsonObject attachable = new JsonObject(); -// attachable.addProperty("slot", "mainhand"); -// attachable.addProperty("attachable", "fmm:" + itemId); -// components.add("minecraft:attachable", attachable); -// -// minecraftItem.add("components", components); -// item.add("minecraft:item", minecraftItem); -// -// File itemFile = new File(bpItemsDir, itemId + ".json"); -// try (FileWriter writer = new FileWriter(itemFile)) { -// new Gson().toJson(item, writer); -// } -// -// Logger.info("Created custom bone item: fmm:" + itemId); -// -// } catch (Exception e) { -// Logger.warn("Failed to generate behavior pack item for " + itemId); -// e.printStackTrace(); -// } -// } - } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java deleted file mode 100644 index 95f41e1..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BackpackPlus.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class BackpackPlus extends ThirdPartyResourcePack { - public BackpackPlus() { - super("BackpackPlus", - "BackpackPlus" +File.separatorChar+ "pack" +File.separatorChar + "resourcepack.zip", - false, - false, - true, - true, - ""); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java deleted file mode 100644 index b1b19d8..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/BetterHUD.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class BetterHUD extends ThirdPartyResourcePack { - public BetterHUD() { - super("BetterHUD", - "BetterHud" + File.separatorChar + "build.zip", - true, - false, - true, - true, - "betterhud reload"); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java deleted file mode 100644 index 84903f7..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/EliteMobs.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class EliteMobs extends ThirdPartyResourcePack { - public EliteMobs() { - super("EliteMobs", - "EliteMobs" + File.separatorChar + "exports" + File.separatorChar + "elitemobs_resource_pack.zip", - false, - false, - true, - true, - "elitemobs reload"); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java deleted file mode 100644 index 6b790a8..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/FreeMinecraftModels.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class FreeMinecraftModels extends ThirdPartyResourcePack { - public FreeMinecraftModels() { - super("FreeMinecraftModels", - "FreeMinecraftModels" + File.separatorChar + "output" + File.separatorChar + "FreeMinecraftModels.zip", - false, - false, - true, - true, - "freeminecraftmodels reload"); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java index a0c6e03..a3a3e38 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java @@ -1,8 +1,11 @@ package com.magmaguy.resourcepackmanager.thirdparty; public interface GeneratorInterface { - public void decrypt(); - public void unpublish(); - public void cloneResourcePackFile(); - public void reload(); + void decrypt(); + + void unpublish(); + + void cloneResourcePackFile(); + + void reload(); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java deleted file mode 100644 index a4b21e5..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ItemsAdder.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class ItemsAdder extends ThirdPartyResourcePack { - public ItemsAdder() { - super("ItemsAdder", - "ItemsAdder" + File.separatorChar + "output" + File.separatorChar + "generated.zip", - false, - false, - true, - true, - ""); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java deleted file mode 100644 index 3323975..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/MMOInventory.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -public class MMOInventory extends ThirdPartyResourcePack { - public MMOInventory() { - super("MMOInventory", - "https://www.dropbox.com/s/1lftxmzh0q4b5yu/mmoinv_rp_3.zip?dl=1", - false, - false, - true, - false, - ""); - } -} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java deleted file mode 100644 index 1760ba1..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ModelEngine.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class ModelEngine extends ThirdPartyResourcePack { - public ModelEngine() { - super("ModelEngine", - "ModelEngine" + File.separatorChar + "resource pack.zip", - false, - false, - true, - true, - "meg reload"); - } -} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java deleted file mode 100644 index 13a017c..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Nova.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class Nova extends ThirdPartyResourcePack { - public Nova() { - super("Nova", - "Nova" + File.separatorChar + "resource_pack" + File.separatorChar + "ResourcePack.zip", - false, - false, - true, - true, - ""); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java deleted file mode 100644 index b828c09..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/Oraxen.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class Oraxen extends ThirdPartyResourcePack { - public Oraxen() { - super("Oraxen", - "Oraxen" + File.separatorChar + "pack" + File.separatorChar + "pack.zip", - false, - false, - true, - true, - ""); - } -} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java deleted file mode 100644 index c69eb94..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/RealisticSurvival.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -public class RealisticSurvival extends ThirdPartyResourcePack { - public RealisticSurvival() { - super("RealisticSurvival", - "https://www.dropbox.com/scl/fi/5ggc7t20bfxmrzsbb1kok/Realistic-Survival-RP-1.2.8-RELEASE.zip?rlkey=55llpn4zj146usfmm8okf1ier&dl=1", - false, - false, - true, - false, - ""); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java deleted file mode 100644 index a15d979..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ResourcePackManager.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class ResourcePackManager extends ThirdPartyResourcePack { - public ResourcePackManager() { - super("ResourcePackManager", - "ResourcePackManager" + File.separatorChar + "blueprint" + File.separatorChar + "blueprint.zip", - false, - false, - true, - true, - ""); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 16f514e..d3fa686 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -1,9 +1,10 @@ package com.magmaguy.resourcepackmanager.thirdparty; -import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; +import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.config.DefaultConfig; +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; import org.apache.hc.client5.http.classic.methods.HttpGet; @@ -20,79 +21,124 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardCopyOption; +import java.util.HashSet; public class ThirdPartyResourcePack implements GeneratorInterface { + public static HashSet thirdPartyResourcePacks = new HashSet<>(); + @Getter private final String pluginName; @Getter + private final String mixerFilename; + private final String localPath; + private final String url; + @Getter private File file = null; - private String url; private boolean encrypts; private boolean distributes; private boolean zips; - private boolean local; private String reloadCommand; @Getter private boolean isEnabled; private String SHA1; - private boolean resourcePackUpdated = false; @Getter private File mixerResourcePack = null; @Getter - private String mixerFilename; - @Getter private int priority = -1; + @Getter + private boolean done = false; - public ThirdPartyResourcePack(String pluginName, String path, boolean encrypts, boolean distributes, boolean zips, boolean local, String reloadCommand) { + public ThirdPartyResourcePack(String pluginName, String localPath, String url, boolean encrypts, boolean distributes, boolean zips, String reloadCommand) { this.pluginName = pluginName; this.mixerFilename = pluginName + "_resource_pack.zip"; + this.url = url; + this.localPath = localPath; + isEnabled = Bukkit.getPluginManager().isPluginEnabled(pluginName); - if (isEnabled) - Logger.info("Initializing " + pluginName + "'s resource pack"); - else return; - this.local = local; - if (local) - this.file = new File(com.magmaguy.resourcepackmanager.ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + path); - else - this.url = path; - if (file != null && !file.exists()) { - Logger.warn("Found " + pluginName + " but could not find resource pack at location " + file.getPath() + " ! ResourcePackManager will not be able to merge the resource pack from this plugin."); + if (!isEnabled) { + done = true; + return; + } + + if (localPath != null && !processLocal(localPath)) { + done = true; + return; + } else if (localPath == null && url == null) { + Logger.warn("Plugin " + pluginName + " has no resource pack path specified! ResourcePackManager will not be able to merge the resource pack from this plugin."); isEnabled = false; + done = true; return; } + this.encrypts = encrypts; this.distributes = distributes; this.reloadCommand = reloadCommand; this.zips = zips; - if (!zips) { - ZipFile.zip(file, getTarget().toString()); - file = new File(getTarget().toUri()); - resourcePackUpdated = true; - mixerResourcePack = file; - } - if (isEnabled && local) SHA1 = getSHA1(file); + + if (!zips) zipThirdPartyPack(); + + if (localPath != null) SHA1 = getSHA1(file); + process(); + if (DefaultConfig.getPriorityOrder().contains(pluginName)) priority = DefaultConfig.getPriorityOrder().indexOf(pluginName); + + thirdPartyResourcePacks.add(this); + done = true; + } + + public static void shutdown() { + thirdPartyResourcePacks.clear(); + } + + public static void initializeThirdPartyResourcePack(CompatiblePluginConfigFields compatiblePluginConfigFields) { + new ThirdPartyResourcePack( + compatiblePluginConfigFields.getPluginName(), + compatiblePluginConfigFields.getLocalPath(), + compatiblePluginConfigFields.getUrl(), + compatiblePluginConfigFields.isEncrypts(), + compatiblePluginConfigFields.isDistributes(), + compatiblePluginConfigFields.isZips(), + compatiblePluginConfigFields.getReloadCommand()); + } + + private void zipThirdPartyPack() { + ZipFile.zip(file, getTarget().toString()); + file = new File(getTarget().toUri()); + mixerResourcePack = file; + } + + private boolean processLocal(String localPath) { + this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + localPath); + + if (!file.exists()) { + Logger.warn("Found " + pluginName + " but could not find resource pack at location " + file.getPath() + " ! ResourcePackManager will not be able to merge the resource pack from this plugin."); + isEnabled = false; + return false; + } + + return true; } public void process() { if (!isEnabled) return; - if (local && zips) { - if (mixerCloneExists()) { - if (getSHA1(new File(getTarget().toUri())).equals(SHA1)) { - mixerResourcePack = getTarget().toFile(); - return; - } else { - Logger.info("Clearing outdated resource pack in " + getTarget()); - getTarget().toFile().delete(); - } + + //Check if a copy already exists in the mixer folder and if it is up-to-date + if (localPath != null && mixerCloneExists()) { + if (getSHA1(new File(getTarget().toUri())).equals(SHA1)) { + mixerResourcePack = getTarget().toFile(); + return; + } else { + Logger.info("Clearing outdated resource pack in " + getTarget()); + getTarget().toFile().delete(); } } + if (encrypts) decrypt(); if (distributes) unpublish(); - if (zips) - cloneResourcePackFile(); + + cloneResourcePackFile(); } private String getSHA1(File file) { @@ -116,9 +162,8 @@ public void unpublish() { @Override public void cloneResourcePackFile() { - if (local) cloneLocalRSP(); + if (localPath != null) cloneLocalRSP(); else cloneRemoteRSP(); - resourcePackUpdated = true; } private void cloneLocalRSP() { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java deleted file mode 100644 index b7abf4d..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ValhallaMMO.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -import java.io.File; - -public class ValhallaMMO extends ThirdPartyResourcePack { - public ValhallaMMO() { - super("ValhallaMMO", - "ValhallaMMO" + File.separatorChar + "resourcepack", - false, - false, - false, - true, - ""); - } -} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java deleted file mode 100644 index 54b69bb..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/VaneCore.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -public class VaneCore extends ThirdPartyResourcePack { - public VaneCore() { - super("vane-core", - "vane-resource-pack.zip", - false, - true, - true, - true, - ""); - } -} \ No newline at end of file diff --git a/src/main/resources/ReadMe.md b/src/main/resources/ReadMe.md index b7ca72c..15939ec 100644 --- a/src/main/resources/ReadMe.md +++ b/src/main/resources/ReadMe.md @@ -1,50 +1,73 @@ ### ResourcePackManager Data Policy and Compliance -**ResourcePackManager**, developed by MagmaGuy for the Nightbreak game studio, includes an optional auto-host feature that temporarily hosts resource pack data on a remote server. +**ResourcePackManager**, developed by MagmaGuy for the Nightbreak game studio, includes an optional auto-host feature +that temporarily hosts resource pack data on a remote server. -As of this writing, the hosted data is fully anonymous and serves the sole purpose of simplifying the distribution of resource packs to clients of servers utilizing this service. Future versions of this document may be updated to reflect any changes in data policy and other related matters. +As of this writing, the hosted data is fully anonymous and serves the sole purpose of simplifying the distribution of +resource packs to clients of servers utilizing this service. Future versions of this document may be updated to reflect +any changes in data policy and other related matters. This system complies with: + - **Directive 2000/31/EC** of the European Parliament and of the Council of 8 June 2000 - **Regulation (EU) 2022/2065** of the European Parliament and of the Council of 19 October 2022 -For data hosting transparency and compliance with these and other European norms, it is possible to obtain all data related to a server via the `/resourcepackmanager data_compliance_request` command. This command packages a full copy of all files and data associated with the requesting server. +For data hosting transparency and compliance with these and other European norms, it is possible to obtain all data +related to a server via the `/resourcepackmanager data_compliance_request` command. This command packages a full copy of +all files and data associated with the requesting server. -To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\nightbreak.io` (replace `/at\ ` with `@`). However, due to the system's design, data is only retained for up to 24h after a server using ResourcePackManager shuts down, making email requests largely unnecessary. Nonetheless, the option remains available to ensure full compliance with European norms. +To request the takedown of your server's data, contact MagmaGuy at `magmaguy/at\nightbreak.io` (replace `/at\ ` with +`@`). However, due to the system's design, data is only retained for up to 24h after a server using ResourcePackManager +shuts down, making email requests largely unnecessary. Nonetheless, the option remains available to ensure full +compliance with European norms. ### Data Handling by ResourcePackManager and Nightbreak Servers 1. **Resource Pack Creation** - - ResourcePackManager aggregates resource packs on your server into a single zipped file containing all custom content. + - ResourcePackManager aggregates resource packs on your server into a single zipped file containing all custom + content. 2. **Initialization Request** - - An initialization request is sent to remote servers, creating a `.txt` file with a random UUID. This file can be obtained via the `/resourcepackmanager data_compliance_request`. + - An initialization request is sent to remote servers, creating a `.txt` file with a random UUID. This file can be + obtained via the `/resourcepackmanager data_compliance_request`. 3. **SHA1 Request** - - ResourcePackManager transmits the SHA1 code of your resource pack to the remote server, which is saved in the `.txt` file. + - ResourcePackManager transmits the SHA1 code of your resource pack to the remote server, which is saved in the + `.txt` file. 4. **File Transmission** - - The zipped resource pack file is sent to the remote server, assigned the same UUID as the `.txt` file. This file can be obtained through `/resourcepackmanager data_compliance_request` and verified to be identical to the original in your output folder, as it is not modified by the Nightbreak servers. + - The zipped resource pack file is sent to the remote server, assigned the same UUID as the `.txt` file. This file + can be obtained through `/resourcepackmanager data_compliance_request` and verified to be identical to the + original in your output folder, as it is not modified by the Nightbreak servers. 5. **"Still Alive" Ping** - - ResourcePackManager sends a "still alive" ping every 6 hours, transmitting the UUID to the server, which updates the timestamp in the `.txt` file. - - If no "still alive" ping is received for over 24 hours, all data associated with that UUID (the `.txt` file and the resource pack) is deleted from the Nightbreak servers. + - ResourcePackManager sends a "still alive" ping every 6 hours, transmitting the UUID to the server, which updates + the timestamp in the `.txt` file. + - If no "still alive" ping is received for over 24 hours, all data associated with that UUID (the `.txt` file + and the resource pack) is deleted from the Nightbreak servers. ### Data Policy -- **Pseudonymous Identification:** Nightbreak assigns a random UUID to your server's files each time the server reboots, ensuring no IP address or identifiable information is stored unless users manually add such information to their resource packs. +- **Pseudonymous Identification:** Nightbreak assigns a random UUID to your server's files each time the server reboots, + ensuring no IP address or identifiable information is stored unless users manually add such information to their + resource packs. - **No Download Logging:** Nightbreak does not log any data related to download requests by Minecraft clients. - **No Data Sales:** Data uploaded to Nightbreak is not, has never been, and will never be sold. -- **Compliance with Takedown Requests:** Nightbreak will comply with takedown requests from both server administrators and law enforcement agencies. -- **Automatic Data Removal:** All data associated with your server is automatically removed 24 hours after your server shuts down and is reuploaded on every restart for as long as ResourcePackManager is in use and using the auto-host feature. +- **Compliance with Takedown Requests:** Nightbreak will comply with takedown requests from both server administrators + and law enforcement agencies. +- **Automatic Data Removal:** All data associated with your server is automatically removed 24 hours after your server + shuts down and is reuploaded on every restart for as long as ResourcePackManager is in use and using the auto-host + feature. ### Terms of service -As of writing this, the hosting service is provided for free for all users of ResourcePackManager. +As of writing this, the hosting service is provided for free for all users of ResourcePackManager. -It is the user's responsibility to ensure that the data uploaded to servers is not illegal and complies with any Mojang TOS as defined in their EULA. +It is the user's responsibility to ensure that the data uploaded to servers is not illegal and complies with any Mojang +TOS as defined in their EULA. -Abusing the service to host material other than resource packages may result in a permanent denial of service for the offending IP. +Abusing the service to host material other than resource packages may result in a permanent denial of service for the +offending IP. The service may, at any time, cease or be modified in such a way that makes old versions unable to connect to it. diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 2b025a9..3982791 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,18 +1,18 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.4.1 +version: 1.5.0 api-version: 1.14 author: MagmaGuy softdepend: -- EliteMobs -- FreeMinecraftModels -- ModelEngine -- Nova -- Oraxen -- ItemsAdder -- BackpackPlus -- vane-core -- RealisticSurvival + - EliteMobs + - FreeMinecraftModels + - ModelEngine + - Nova + - Oraxen + - ItemsAdder + - BackpackPlus + - vane-core + - RealisticSurvival commands: resourcepackmanager: From 6e93ee06afd711ca539c17aa4d8b5139122a6abc Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sun, 3 Aug 2025 01:43:19 +0100 Subject: [PATCH 14/33] Resource Pack Manager 1.6.0: - [New] New using SSL for connecting to the remote server. This is a breaking change in the system, servers will have to update to 1.6.0 or later to use the Auto Host feature. - [New] Added compatibility with InfiniteVehicles - [New] ResourcePackManager now passively checks for file changes in all compatible directories, and automatically updates the resource pack as they change. - This means that if a resource pack updates during runtime, ResourcePackManager will also update automatically without requiring a reload. - This system has a 3 second buffer time to check for file stability, giving other plugins the time to zip their files correctly. This means it should work with both sync and async zipping of third party plugins, and it should no longer be possible to accidentally only grab a partially zipped file. - [Critical fixes] A lot of back end fixes to make the system more reliable and less remanding with the uploads. --- pom.xml | 2 +- .../ResourcePackManager.java | 12 ++- .../autohost/AutoHost.java | 80 +++++++++++-------- .../premade/InfiniteVehicles.java | 13 +++ .../listeners/ResourcePackGeneratedEvent.java | 14 ---- .../resourcepackmanager/mixer/Mix.java | 6 +- .../thirdparty/ThirdPartyResourcePack.java | 56 +++++++++++++ src/main/resources/plugin.yml | 2 +- 8 files changed, 128 insertions(+), 57 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/InfiniteVehicles.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java diff --git a/pom.xml b/pom.xml index fb306aa..c477c5a 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.5.0 + 1.6.0 17 diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index bb174db..24e3adb 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -4,7 +4,6 @@ import com.magmaguy.magmacore.MagmaCore; import com.magmaguy.magmacore.command.CommandManager; import com.magmaguy.magmacore.util.Logger; -import com.magmaguy.resourcepackmanager.listeners.ResourcePackGeneratedEvent; import com.magmaguy.resourcepackmanager.autohost.AutoHost; import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; import com.magmaguy.resourcepackmanager.commands.ReloadCommand; @@ -12,7 +11,6 @@ import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfig; -import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import com.magmaguy.resourcepackmanager.utils.VersionChecker; @@ -39,17 +37,17 @@ public void onEnable() { new DataConfig(); new DefaultConfig(); new CompatiblePluginConfig(); + BlueprintFolder.initialize(); - Mix.initialize(); + //This starts a watchdog to see if the resource packs change and updates the mixer if htey do. + ThirdPartyResourcePack.startResourcePackChangeWatchdog(); if (DefaultConfig.isAutoHost()) Bukkit.getPluginManager().registerEvents(new PlayerManager(), this); CommandManager commandManager = new CommandManager(this, "resourcepackmanager"); commandManager.registerCommand(new ReloadCommand()); commandManager.registerCommand(new DataComplianceRequestCommand()); - if (Bukkit.getPluginManager().isPluginEnabled("FreeMinecraftModels")) - Bukkit.getPluginManager().registerEvents(new ResourcePackGeneratedEvent(), this); Bukkit.getPluginManager().registerEvents(new VersionChecker.VersionCheckerEvents(), this); - AutoHost.initialize(); +// AutoHost.initialize(); Metrics metrics = new Metrics(this, 22867); MagmaCore.checkVersionUpdate("118574", "https://www.spigotmc.org/resources/resource-pack-manager.118574/"); @@ -64,7 +62,7 @@ public void onLoad() { public void onDisable() { Logger.info("Disabling ResourcePackManager"); ThirdPartyResourcePack.shutdown(); - HandlerList.unregisterAll(this); AutoHost.shutdown(); + HandlerList.unregisterAll(this); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index b50be98..6e83625 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -8,6 +8,7 @@ import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.mixer.Mix; import lombok.Getter; +import lombok.Setter; import org.apache.hc.client5.http.classic.methods.HttpPost; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; @@ -21,28 +22,34 @@ import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitTask; -import java.io.*; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; public class AutoHost { - private static final String finalURL = "http://magmaguy.com:50000/"; - private static boolean done = false; -// private static final String finalURL = "http://localhost:50000/"; + private static final String finalURL = "https://magmaguy.com/rsp/"; + @Setter + private static boolean done = false; +// private static final String finalURL = "https://localhost:50000/"; private static BukkitTask keepAlive = null; @Getter private static String rspUUID = null; + @Setter private static boolean firstUpload = true; private AutoHost() { } public static void sendResourcePack(Player player) { + Logger.info("Sending resource pack to " + player.getName() + ". rspUUID is null: " + (rspUUID == null) + ". done: " + done + ". firstUpload: " + firstUpload + "."); if (rspUUID == null || !done) return; Logger.info("Sending resource pack to " + player.getName()); - player.setResourcePack(finalURL + "rsp_" + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); + player.setResourcePack(finalURL + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); } public static void initialize() { @@ -50,35 +57,33 @@ public static void initialize() { if (Mix.getFinalResourcePack() == null) return; Logger.info("Starting autohost!"); firstUpload = true; - new BukkitRunnable() { + done = false; + rspUUID = null; + if (keepAlive != null) keepAlive.cancel(); + + keepAlive = new BukkitRunnable() { + int counter = 0; + @Override public void run() { - checkFileExistence(); - keepAlive = new BukkitRunnable() { - int counter = 0; - - @Override - public void run() { - if (rspUUID != null) { - counter = 0; - try { - sendStillAlive(); - } catch (Exception e) { - rspUUID = null; - Logger.warn("Failed to autohost resource pack!"); - e.printStackTrace(); - } - } else { - checkFileExistence(); - if (rspUUID == null && counter % 10 == 0) { - Logger.warn("Failed to connect to remote server to autohost the resource pack!"); - } - counter++; - } + if (rspUUID != null) { + counter = 0; + try { + sendStillAlive(); + } catch (Exception e) { + rspUUID = null; + Logger.warn("Failed to autohost resource pack!"); + e.printStackTrace(); + } + } else { + checkFileExistence(); + if (rspUUID == null && counter % 10 == 0) { + Logger.warn("Failed to connect to remote server to autohost the resource pack!"); } - }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 6 * 60 * 60 * 20L); + counter++; + } } - }.runTaskAsynchronously(ResourcePackManager.plugin); + }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 0, 6 * 60 * 60 * 20L); } private static void checkFileExistence() { @@ -88,6 +93,16 @@ private static void checkFileExistence() { return; } if (!sendSHA1()) uploadFile(); + else { + //Case if the remote server already has the resource pack + done = true; + if (firstUpload) { + //Recover from a reload by sending the pack to online players + for (Player player : Bukkit.getOnlinePlayers()) + AutoHost.sendResourcePack(player); + } + firstUpload = false; + } } public static void initializeLink() { @@ -180,7 +195,7 @@ public static void uploadFile() { int statusCode = response.getCode(); if (statusCode >= 200 && statusCode < 300) { - Logger.info("Uploaded resource pack for automatic hosting! url: " + finalURL + "rsp_" + rspUUID); + Logger.info("Uploaded resource pack for automatic hosting! url: " + finalURL + rspUUID); done = true; if (firstUpload) { //Recover from a reload by sending the pack to online players @@ -191,12 +206,10 @@ public static void uploadFile() { } else { // Handle detailed error messages from server handleErrorResponse(responseString, statusCode, "upload"); - return; } } catch (Exception e) { Logger.warn("Failed to communicate with remote server during upload!"); e.printStackTrace(); - return; } } catch (IOException e) { throw new RuntimeException(e); @@ -339,6 +352,7 @@ public static void dataComplianceRequest() throws IOException { public static void shutdown() { if (keepAlive != null) keepAlive.cancel(); done = false; + rspUUID = null; } private static void handleErrorResponse(String responseString, int statusCode, String operation) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/InfiniteVehicles.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/InfiniteVehicles.java new file mode 100644 index 0000000..18f2587 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/InfiniteVehicles.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class InfiniteVehicles extends CompatiblePluginConfigFields { + public InfiniteVehicles() { + super("infinite_vehicles", true); + setPluginName("InfiniteVehicles"); + setLocalPath("InfiniteVehicles" + File.separatorChar + "InfiniteModelPack.zip"); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java b/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java deleted file mode 100644 index 07a5d6b..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/listeners/ResourcePackGeneratedEvent.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.magmaguy.resourcepackmanager.listeners; - -import com.magmaguy.freeminecraftmodels.events.ResourcePackGenerationEvent; -import com.magmaguy.resourcepackmanager.commands.ReloadCommand; -import org.bukkit.Bukkit; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; - -public class ResourcePackGeneratedEvent implements Listener { - @EventHandler - public void onResourcePackGenerated(ResourcePackGenerationEvent event) { - ReloadCommand.reloadPlugin(Bukkit.getConsoleSender()); - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 092cd01..cd43a84 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -6,6 +6,7 @@ import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.api.ResourcePackManagerAPI; +import com.magmaguy.resourcepackmanager.autohost.AutoHost; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; @@ -37,11 +38,12 @@ public class Mix { private Mix() { } - public static void initialize() { + public static void mixResourcePacks() { if (!initializeDefaultPluginFolders()) return; initializeThirdPartyResourcePacks(); cloneToOutputAndUnzip(); createOutputDefaultElements(); + AutoHost.initialize(); } private static boolean initializeDefaultPluginFolders() { @@ -168,9 +170,11 @@ private static void cloneToOutputAndUnzip() { } private static void createOutputDefaultElements() { + //Clear old resource pack if (getOutputResourcePackFolder().exists()) { recursivelyDeleteDirectory(getOutputResourcePackFolder()); } + //Make sure new resource pack exists try { getOutputResourcePackFolder().mkdir(); } catch (Exception e) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index d3fa686..4883e8a 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -5,14 +5,18 @@ import com.magmaguy.resourcepackmanager.ResourcePackManager; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; +import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; +import lombok.Setter; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.HttpEntity; import org.bukkit.Bukkit; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; import java.io.BufferedInputStream; import java.io.File; @@ -22,6 +26,7 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.HashSet; +import java.util.Objects; public class ThirdPartyResourcePack implements GeneratorInterface { public static HashSet thirdPartyResourcePacks = new HashSet<>(); @@ -48,6 +53,11 @@ public class ThirdPartyResourcePack implements GeneratorInterface { @Getter private boolean done = false; + private int ticksWithoutChange = 0; + private boolean consideredStable = false; + @Setter + private boolean stableResourcePackSent = false; + public ThirdPartyResourcePack(String pluginName, String localPath, String url, boolean encrypts, boolean distributes, boolean zips, String reloadCommand) { this.pluginName = pluginName; this.mixerFilename = pluginName + "_resource_pack.zip"; @@ -88,8 +98,54 @@ public ThirdPartyResourcePack(String pluginName, String localPath, String url, b done = true; } + private static BukkitTask resourcePackChangeWatcher = null; + + public static void startResourcePackChangeWatchdog() { + resourcePackChangeWatcher = new BukkitRunnable() { + @Override + public void run() { + boolean readyToSend = true; + boolean stableAlreadySent = true; + for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { + if (!thirdPartyResourcePack.isEnabled || thirdPartyResourcePack.file == null) continue; + if (!Objects.equals(thirdPartyResourcePack.getSHA1(thirdPartyResourcePack.file), thirdPartyResourcePack.SHA1)) { + thirdPartyResourcePack.ticksWithoutChange = 0; + thirdPartyResourcePack.SHA1 = thirdPartyResourcePack.getSHA1(thirdPartyResourcePack.file); + if (thirdPartyResourcePack.consideredStable) { + thirdPartyResourcePack.consideredStable = false; + thirdPartyResourcePack.stableResourcePackSent = false; + Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has changed, considering it unstable."); + } + } + if (!thirdPartyResourcePack.stableResourcePackSent) stableAlreadySent = false; + if (!thirdPartyResourcePack.consideredStable) readyToSend = false; + if (thirdPartyResourcePack.consideredStable) continue; + thirdPartyResourcePack.ticksWithoutChange++; + if (thirdPartyResourcePack.ticksWithoutChange == 3) { + //Is nothing has changed for 1 second, then it should be considered to be stable + thirdPartyResourcePack.consideredStable = true; + Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has not changed for 3 seconds, considering it stable."); + } + } + + if (!stableAlreadySent && readyToSend) { + Logger.info("All resource packs are stable, sending resource pack to players."); + tagAsResourcePackSent(); + Mix.mixResourcePacks(); + } + } + }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 20, 20); + } + + public static void tagAsResourcePackSent(){ + for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { + thirdPartyResourcePack.stableResourcePackSent = true; + } + } + public static void shutdown() { thirdPartyResourcePacks.clear(); + resourcePackChangeWatcher.cancel(); } public static void initializeThirdPartyResourcePack(CompatiblePluginConfigFields compatiblePluginConfigFields) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3982791..c66e772 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.5.0 +version: 1.6.0 api-version: 1.14 author: MagmaGuy softdepend: From 95a4052e996c933116bd18ee6bf4b69d512d05d4 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sat, 6 Dec 2025 21:13:09 +0000 Subject: [PATCH 15/33] Resource Pack Manager 1.7.0: - [New] Added support for multiple resource packs on 1.20.3+ servers using addResourcePack API, allowing ResourcePackManager to coexist with other plugins that send resource packs. - [New] Added "cluster" mode for compatible plugins - allows processing multiple resource packs from subdirectories in a single plugin folder. - [New] Added compatibility with MegaBlockSurvivors. - [New] Updated EliteMobs compatibility to use the new cluster mode, now reading from the resource_pack folder instead of exports. - [Fix] Improved stability detection - resource packs now wait 10 seconds without changes before being considered stable (was 3 seconds). - [Fix] Added SHA1 comparison to detect unchanged resource packs, preventing unnecessary reprocessing on reload. - [Fix] Improved error handling for encrypted or corrupted resource packs - they are now gracefully skipped instead of causing errors. - [Fix] Fixed potential null pointer exceptions when processing mixer folder contents. - [Fix] Fixed issue where non-zip files and directories in the mixer folder could cause processing errors. --- pom.xml | 2 +- .../api/ResourcePackManagerAPI.java | 6 +- .../autohost/AutoHost.java | 18 +++- .../CompatiblePluginConfigFields.java | 4 + .../compatibleplugins/premade/EliteMobs.java | 4 +- .../premade/MegaBlockSurvivors.java | 14 +++ .../resourcepackmanager/mixer/Mix.java | 69 +++++++++++- .../thirdparty/ThirdPartyResourcePack.java | 100 ++++++++++++++++-- src/main/resources/plugin.yml | 2 +- 9 files changed, 201 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MegaBlockSurvivors.java diff --git a/pom.xml b/pom.xml index c477c5a..b9ee3e6 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.6.0 + 1.7.0 17 diff --git a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java index 736a17a..dc1821c 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java @@ -29,7 +29,7 @@ public static void registerResourcePack(String pluginName, boolean zips, String reloadCommand) { thirdPartyResourcePackHashMap.put(pluginName, - new ThirdPartyResourcePack(pluginName, localPath, url, encrypts, distributes, zips, reloadCommand)); + new ThirdPartyResourcePack(pluginName, localPath, url, encrypts, distributes, zips, false, reloadCommand)); } /** @@ -49,7 +49,7 @@ public static void registerLocalResourcePack(String pluginName, boolean zips, String reloadCommand) { thirdPartyResourcePackHashMap.put(pluginName, - new ThirdPartyResourcePack(pluginName, localPath, null, encrypts, distributes, zips, reloadCommand)); + new ThirdPartyResourcePack(pluginName, localPath, null, encrypts, distributes, zips, false, reloadCommand)); } /** @@ -70,7 +70,7 @@ public static void registerRemoteResourcePack(String pluginName, boolean zips, String reloadCommand) { thirdPartyResourcePackHashMap.put(pluginName, - new ThirdPartyResourcePack(pluginName, null, url, encrypts, distributes, zips, reloadCommand)); + new ThirdPartyResourcePack(pluginName, null, url, encrypts, distributes, zips, false, reloadCommand)); } /** diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 6e83625..8e03294 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -7,6 +7,7 @@ import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.mixer.Mix; +import com.magmaguy.resourcepackmanager.utils.ServerVersionHelper; import lombok.Getter; import lombok.Setter; import org.apache.hc.client5.http.classic.methods.HttpPost; @@ -29,9 +30,12 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardCopyOption; +import java.util.UUID; public class AutoHost { private static final String finalURL = "https://magmaguy.com/rsp/"; + // Consistent UUID for identifying ResourcePackManager's pack when using multiple resource packs + private static final UUID RESOURCE_PACK_UUID = UUID.fromString("a1b2c3d4-e5f6-4a5b-8c9d-0e1f2a3b4c5d"); @Setter private static boolean done = false; // private static final String finalURL = "https://localhost:50000/"; @@ -49,7 +53,19 @@ public static void sendResourcePack(Player player) { Logger.info("Sending resource pack to " + player.getName() + ". rspUUID is null: " + (rspUUID == null) + ". done: " + done + ". firstUpload: " + firstUpload + "."); if (rspUUID == null || !done) return; Logger.info("Sending resource pack to " + player.getName()); - player.setResourcePack(finalURL + rspUUID, Mix.getFinalSHA1Bytes(), DefaultConfig.getResourcePackPrompt(), DefaultConfig.isForceResourcePack()); + + String url = finalURL + rspUUID; + byte[] hash = Mix.getFinalSHA1Bytes(); + String prompt = DefaultConfig.getResourcePackPrompt(); + boolean force = DefaultConfig.isForceResourcePack(); + + if (ServerVersionHelper.supportsMultipleResourcePacks()) { + // 1.20.3+ supports multiple resource packs - use addResourcePack to coexist with other plugins + player.addResourcePack(RESOURCE_PACK_UUID, url, hash, prompt, force); + } else { + // Older versions - use setResourcePack (replaces any existing packs) + player.setResourcePack(url, hash, prompt, force); + } } public static void initialize() { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java index 2f6b2f2..0c06e79 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java @@ -34,6 +34,9 @@ public class CompatiblePluginConfigFields extends CustomConfigFields { @Getter @Setter private String mixerFilename; + @Getter + @Setter + private boolean cluster = false; public CompatiblePluginConfigFields(String filename, boolean isEnabled) { super(filename, isEnabled); @@ -51,6 +54,7 @@ public void processConfigFields() { this.resourcePackUpdated = processBoolean("resourcePackUpdated", resourcePackUpdated, resourcePackUpdated, true); this.mixerFilename = processString("mixerFilename", mixerFilename, mixerFilename, true); this.localPath = processString("localPath", localPath, localPath, true); + this.cluster = processBoolean("cluster", cluster, cluster, true); if (isEnabled) ThirdPartyResourcePack.initializeThirdPartyResourcePack(this); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java index 3db7b9a..27deed7 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/EliteMobs.java @@ -8,7 +8,9 @@ public class EliteMobs extends CompatiblePluginConfigFields { public EliteMobs() { super("elitemobs", true); setPluginName("EliteMobs"); - setLocalPath("EliteMobs" + File.separatorChar + "exports" + File.separatorChar + "elitemobs_resource_pack.zip"); + setLocalPath("EliteMobs" + File.separatorChar + "resource_pack"); setReloadCommand("elitemobs reload"); + setCluster(true); + setZips(false); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MegaBlockSurvivors.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MegaBlockSurvivors.java new file mode 100644 index 0000000..be92a15 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/MegaBlockSurvivors.java @@ -0,0 +1,14 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class MegaBlockSurvivors extends CompatiblePluginConfigFields { + public MegaBlockSurvivors() { + super("megablock_survivors", true); + setPluginName("MegaBlockSurvivors"); + setLocalPath("MegaBlockSurvivors" + File.separatorChar + "resourcepack"); + setZips(false); + } +} \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index cd43a84..c0a11fa 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -90,6 +90,10 @@ private static void initializeThirdPartyResourcePacks() { // Separate out truly custom files in the mixer folder for (File file : mixerFolder.listFiles()) { + // Skip directories that aren't zip files - these are likely from cluster processing + if (file.isDirectory()) continue; + // Skip non-zip files + if (!file.getName().endsWith(".zip")) continue; boolean isThirdParty = thirdPartyFiles.stream() .anyMatch(tp -> tp.getName().equals(file.getName())); if (!isThirdParty) { @@ -161,12 +165,62 @@ private static void cloneToOutputAndUnzip() { stripDirectoryMetadata(file); } catch (Exception e) { if (resourcePack == null) - Logger.warn("Failed to extract resource pack! A file might be encrypted."); + Logger.warn("Failed to extract resource pack! The file might be encrypted. This pack will be skipped."); else - Logger.warn("Failed to extract resource pack " + resourcePack.getName() + " ! The file might be encrypted."); - e.printStackTrace(); + Logger.warn("Failed to extract resource pack " + resourcePack.getName() + " - the file might be encrypted or the plugin distributes its own pack. This pack will be skipped."); } }); + + // Also copy any directories from mixer folder (from cluster processing) directly to output + copyClusterDirectoriesToOutput(); + } + + private static void copyClusterDirectoriesToOutput() { + if (mixerFolder == null || !mixerFolder.exists()) return; + File[] mixerContents = mixerFolder.listFiles(); + if (mixerContents == null) return; + + for (File file : mixerContents) { + // Only process directories (cluster content like 'assets') + if (!file.isDirectory()) continue; + // Skip the output folder if it somehow ends up here + if (file.getName().equals("output")) continue; + + // Copy the directory to a wrapper folder in output + // This ensures the structure is: output/cluster_assets/assets/... + File outputWrapper = new File(getOutputFolder().getPath() + File.separatorChar + "cluster_" + file.getName()); + if (!outputWrapper.exists()) outputWrapper.mkdir(); + + try { + recursivelyCopyDirectoryForCluster(file, new File(outputWrapper.getPath() + File.separatorChar + file.getName())); + // Track this for the merge process + if (!orderedResourcePacks.contains("cluster_" + file.getName())) { + orderedResourcePacks.add("cluster_" + file.getName()); + } + Logger.info("Copied cluster directory " + file.getName() + " to output folder"); + } catch (Exception e) { + Logger.warn("Failed to copy cluster directory " + file.getName() + " to output folder"); + e.printStackTrace(); + } + } + } + + private static void recursivelyCopyDirectoryForCluster(File source, File target) { + if (source.isDirectory()) { + if (!target.exists()) target.mkdir(); + File[] files = source.listFiles(); + if (files != null) { + for (File child : files) { + recursivelyCopyDirectoryForCluster(child, new File(target.getPath() + File.separatorChar + child.getName())); + } + } + } else { + try { + Files.copy(source.toPath(), target.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + Logger.warn("Failed to copy cluster file " + source.getPath()); + } + } } private static void createOutputDefaultElements() { @@ -189,12 +243,19 @@ private static void createOutputDefaultElements() { for (File file : orderedFiles) { if (file.getName().equals(resourcePackName + ".zip")) continue; + if (!file.exists()) { + // Pack likely failed to extract (possibly encrypted) - skip gracefully + Logger.info("Skipping " + file.getName() + " - pack was not extracted (may be encrypted or corrupted)"); + continue; + } if (!file.isDirectory()) { if (file.getName().endsWith(".zip")) continue; Logger.warn("Somehow a non-folder file made its way to the output folder! This isn't good. File: " + file.getAbsolutePath()); continue; } - for (File subFile : file.listFiles()) { + File[] subFiles = file.listFiles(); + if (subFiles == null) continue; + for (File subFile : subFiles) { recursivelyCopyDirectory(subFile, getOutputResourcePackFolder()); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 4883e8a..157a81c 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -42,6 +42,7 @@ public class ThirdPartyResourcePack implements GeneratorInterface { private boolean encrypts; private boolean distributes; private boolean zips; + private boolean cluster; private String reloadCommand; @Getter private boolean isEnabled; @@ -58,7 +59,7 @@ public class ThirdPartyResourcePack implements GeneratorInterface { @Setter private boolean stableResourcePackSent = false; - public ThirdPartyResourcePack(String pluginName, String localPath, String url, boolean encrypts, boolean distributes, boolean zips, String reloadCommand) { + public ThirdPartyResourcePack(String pluginName, String localPath, String url, boolean encrypts, boolean distributes, boolean zips, boolean cluster, String reloadCommand) { this.pluginName = pluginName; this.mixerFilename = pluginName + "_resource_pack.zip"; this.url = url; @@ -84,12 +85,29 @@ public ThirdPartyResourcePack(String pluginName, String localPath, String url, b this.distributes = distributes; this.reloadCommand = reloadCommand; this.zips = zips; + this.cluster = cluster; - if (!zips) zipThirdPartyPack(); + // If this is a cluster, process each resource pack in the folder + if (cluster) { + processCluster(); + } else if (!zips) { + zipThirdPartyPack(); + } + + if (localPath != null && !cluster) SHA1 = getSHA1(file); - if (localPath != null) SHA1 = getSHA1(file); + // Check if source file matches existing mixer file - if so, no changes occurred and it's stable + if (!cluster && localPath != null) { + File existingMixerFile = getTarget().toFile(); + if (existingMixerFile.exists()) { + String existingMixerSHA1 = getSHA1(existingMixerFile); + if (Objects.equals(SHA1, existingMixerSHA1)) { + consideredStable = true; + } + } + } - process(); + if (!cluster) process(); if (DefaultConfig.getPriorityOrder().contains(pluginName)) priority = DefaultConfig.getPriorityOrder().indexOf(pluginName); @@ -121,10 +139,10 @@ public void run() { if (!thirdPartyResourcePack.consideredStable) readyToSend = false; if (thirdPartyResourcePack.consideredStable) continue; thirdPartyResourcePack.ticksWithoutChange++; - if (thirdPartyResourcePack.ticksWithoutChange == 3) { - //Is nothing has changed for 1 second, then it should be considered to be stable + if (thirdPartyResourcePack.ticksWithoutChange == 10) { + //If nothing has changed for 10 seconds, then it should be considered to be stable thirdPartyResourcePack.consideredStable = true; - Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has not changed for 3 seconds, considering it stable."); + Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has not changed for 10 seconds, considering it stable."); } } @@ -156,6 +174,7 @@ public static void initializeThirdPartyResourcePack(CompatiblePluginConfigFields compatiblePluginConfigFields.isEncrypts(), compatiblePluginConfigFields.isDistributes(), compatiblePluginConfigFields.isZips(), + compatiblePluginConfigFields.isCluster(), compatiblePluginConfigFields.getReloadCommand()); } @@ -165,6 +184,73 @@ private void zipThirdPartyPack() { mixerResourcePack = file; } + private void processCluster() { + if (!file.isDirectory()) { + Logger.warn("Cluster path for " + pluginName + " is not a directory: " + file.getPath()); + isEnabled = false; + return; + } + + File[] clusterContents = file.listFiles(); + if (clusterContents == null || clusterContents.length == 0) { + Logger.warn("Cluster directory for " + pluginName + " is empty: " + file.getPath()); + isEnabled = false; + return; + } + + File mixerDir = new File(ResourcePackManager.plugin.getDataFolder().toString() + File.separatorChar + "mixer"); + if (!mixerDir.exists()) mixerDir.mkdir(); + + Logger.info("Processing cluster for " + pluginName + " with " + clusterContents.length + " resource packs"); + + for (File resourcePackFolder : clusterContents) { + if (!resourcePackFolder.isDirectory()) { + Logger.info("Skipping non-directory in cluster: " + resourcePackFolder.getName()); + continue; + } + + // Each folder in the cluster is a resource pack - copy its contents to mixer + File[] resourcePackContents = resourcePackFolder.listFiles(); + if (resourcePackContents == null) continue; + + for (File contentFolder : resourcePackContents) { + if (!contentFolder.isDirectory()) continue; + + File targetFolder = new File(mixerDir.getPath() + File.separatorChar + contentFolder.getName()); + try { + recursivelyCloneDirectory(contentFolder, targetFolder); + Logger.info("Copied " + contentFolder.getName() + " from " + resourcePackFolder.getName() + " to mixer folder"); + } catch (Exception e) { + Logger.warn("Failed to copy " + contentFolder.getPath() + " to mixer folder"); + e.printStackTrace(); + } + } + } + + // For clusters, we don't track a single mixerResourcePack since they get copied directly + // Set isEnabled to false so it doesn't try to process further as a single pack + isEnabled = false; + Logger.info("Finished processing cluster for " + pluginName); + } + + private void recursivelyCloneDirectory(File source, File target) { + if (source.isDirectory()) { + if (!target.exists()) target.mkdir(); + File[] files = source.listFiles(); + if (files != null) { + for (File child : files) { + recursivelyCloneDirectory(child, new File(target.getPath() + File.separatorChar + child.getName())); + } + } + } else { + try { + Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + Logger.warn("Failed to copy file " + source.getPath() + " to " + target.getPath()); + } + } + } + private boolean processLocal(String localPath) { this.file = new File(ResourcePackManager.plugin.getDataFolder().getParentFile().toPath().toString() + File.separatorChar + localPath); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index c66e772..62e6d0c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.6.0 +version: 1.7.0 api-version: 1.14 author: MagmaGuy softdepend: From f770621bdbf15c04093cb792c56574c4eb9db380 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Fri, 19 Dec 2025 20:20:11 +0000 Subject: [PATCH 16/33] Resource Pack Manager 1.7.1: - [New] Added ItemsAdder compatibility detection - OP players are now warned on join if ItemsAdder is present but not configured to work with ResourcePackManager. - [New] Added /rspm itemsadder configure command to automatically configure ItemsAdder for ResourcePackManager hosting (disables ItemsAdder hosting and file protections). - [New] Added /rspm itemsadder dismiss command to permanently dismiss the ItemsAdder warning. - [New] OP players are now notified when all resource packs are stable and the merged resource pack will be sent in 5 seconds. - [Fix] Fixed JSON file collision handling - JSON files that aren't designed for merging (custom models, blockstates, equipment layers, etc.) are now replaced instead of merged, preventing broken arrays and corrupted files. - [Fix] Fixed cluster mode file collision handling - files in cluster mode now properly use the collision resolution system instead of always overwriting. - [Fix] Fixed BetterHUD compatibility - was incorrectly pointing to BackpackPlus folder instead of BetterHUD folder, and was marked as encrypted when it isn't. - [Fix] Reduced resource pack stability detection from 10 seconds to 3 seconds for faster processing. - [Fix] Added 5 second delay before sending merged resource pack to give admins time to react if needed. --- pom.xml | 2 +- .../ResourcePackManager.java | 6 + .../compatibleplugins/premade/BetterHUD.java | 4 +- .../itemsadder/ItemsAdderCommand.java | 138 +++++++++++++++++ .../itemsadder/ItemsAdderDetector.java | 145 ++++++++++++++++++ .../itemsadder/ItemsAdderDismissedConfig.java | 71 +++++++++ .../itemsadder/ItemsAdderWarningListener.java | 90 +++++++++++ .../resourcepackmanager/mixer/Mix.java | 64 +++++++- .../thirdparty/ThirdPartyResourcePack.java | 41 ++++- .../utils/ServerVersionHelper.java | 43 ++++++ src/main/resources/plugin.yml | 2 +- 11 files changed, 589 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderCommand.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDetector.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDismissedConfig.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderWarningListener.java create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java diff --git a/pom.xml b/pom.xml index b9ee3e6..2eefa22 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.7.0 + 1.7.1 17 diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 24e3adb..315038a 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -8,6 +8,9 @@ import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; import com.magmaguy.resourcepackmanager.commands.ReloadCommand; import com.magmaguy.resourcepackmanager.config.BlueprintFolder; +import com.magmaguy.resourcepackmanager.itemsadder.ItemsAdderCommand; +import com.magmaguy.resourcepackmanager.itemsadder.ItemsAdderDismissedConfig; +import com.magmaguy.resourcepackmanager.itemsadder.ItemsAdderWarningListener; import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfig; @@ -37,6 +40,7 @@ public void onEnable() { new DataConfig(); new DefaultConfig(); new CompatiblePluginConfig(); + new ItemsAdderDismissedConfig(); BlueprintFolder.initialize(); //This starts a watchdog to see if the resource packs change and updates the mixer if htey do. @@ -46,7 +50,9 @@ public void onEnable() { CommandManager commandManager = new CommandManager(this, "resourcepackmanager"); commandManager.registerCommand(new ReloadCommand()); commandManager.registerCommand(new DataComplianceRequestCommand()); + commandManager.registerCommand(new ItemsAdderCommand()); Bukkit.getPluginManager().registerEvents(new VersionChecker.VersionCheckerEvents(), this); + Bukkit.getPluginManager().registerEvents(new ItemsAdderWarningListener(), this); // AutoHost.initialize(); Metrics metrics = new Metrics(this, 22867); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java index 56235e9..0005adb 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java @@ -8,8 +8,8 @@ public class BetterHUD extends CompatiblePluginConfigFields { public BetterHUD() { super("better_hud", true); setPluginName("BetterHUD"); - setLocalPath("BackpackPlus" + File.separatorChar + "pack" + File.separatorChar + "resourcepack.zip"); - setEncrypts(true); + setLocalPath("BetterHUD" + File.separatorChar + "build.zip"); + setEncrypts(false); setReloadCommand("betterhud reload"); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderCommand.java new file mode 100644 index 0000000..f3107c3 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderCommand.java @@ -0,0 +1,138 @@ +package com.magmaguy.resourcepackmanager.itemsadder; + +import com.magmaguy.magmacore.command.AdvancedCommand; +import com.magmaguy.magmacore.command.CommandData; +import com.magmaguy.magmacore.command.arguments.ListStringCommandArgument; +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.commands.ReloadCommand; +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import java.io.File; +import java.util.List; + +/** + * Command to handle ItemsAdder configuration and warning dismissal. + */ +public class ItemsAdderCommand extends AdvancedCommand { + + public ItemsAdderCommand() { + super(List.of("itemsadder")); + setDescription("Configure ItemsAdder integration"); + addArgument("action", new ListStringCommandArgument(List.of("configure", "dismiss"), "")); + setPermission("resourcepackmanager.*"); + setUsage("/rspm itemsadder "); + } + + @Override + public void execute(CommandData commandData) { + CommandSender sender = commandData.getCommandSender(); + + String action = commandData.getStringArgument("action"); + if (action == null || action.isEmpty()) { + Logger.sendMessage(sender, "&cUsage: /rspm itemsadder "); + return; + } + + switch (action.toLowerCase()) { + case "configure": + handleConfigure(sender); + break; + case "dismiss": + handleDismiss(sender); + break; + default: + Logger.sendMessage(sender, "&cUnknown action. Use: /rspm itemsadder "); + } + } + + /** + * Handle the configure action - modifies ItemsAdder config and reloads plugins. + */ + private void handleConfigure(CommandSender sender) { + if (!ItemsAdderDetector.isItemsAdderInstalled()) { + Logger.sendMessage(sender, "&cItemsAdder is not installed!"); + return; + } + + if (ItemsAdderDetector.isItemsAdderHosting()) { + Logger.sendMessage(sender, "&eItemsAdder is already configured to host its own resource pack."); + Logger.sendMessage(sender, "&7If you want ResourcePackManager to host instead, please manually disable ItemsAdder's hosting."); + return; + } + + File configFile = ItemsAdderDetector.getItemsAdderConfigFile(); + if (configFile == null || !configFile.exists()) { + Logger.sendMessage(sender, "&cCould not find ItemsAdder config.yml!"); + return; + } + + try { + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Set no-host enabled + config.set("resource-pack.hosting.no-host.enabled", true); + + // Disable all protections + config.set("resource-pack.zip.protect-file-from-unzip.protection_1", false); + config.set("resource-pack.zip.protect-file-from-unzip.protection_2", false); + config.set("resource-pack.zip.protect-file-from-unzip.protection_3", false); + + // Save the config + config.save(configFile); + + Logger.sendMessage(sender, "&aItemsAdder configuration updated successfully!"); + Logger.sendMessage(sender, "&7- Enabled no-host mode"); + Logger.sendMessage(sender, "&7- Disabled file protections"); + Logger.sendMessage(sender, ""); + Logger.sendMessage(sender, "&eReloading ItemsAdder..."); + + // Reload ItemsAdder first, then RSPM + new BukkitRunnable() { + @Override + public void run() { + // Reload ItemsAdder + try { + Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "iazip"); + Logger.sendMessage(sender, "&aItemsAdder reloaded!"); + } catch (Exception e) { + Logger.sendMessage(sender, "&cFailed to reload ItemsAdder: " + e.getMessage()); + Logger.sendMessage(sender, "&7Try running /iazip manually."); + } + + // Schedule RSPM reload after ItemsAdder has time to regenerate + new BukkitRunnable() { + @Override + public void run() { + Logger.sendMessage(sender, "&eReloading ResourcePackManager..."); + ReloadCommand.reloadPlugin(sender); + Logger.sendMessage(sender, "&aConfiguration complete! ResourcePackManager is now hosting the merged resource pack."); + } + }.runTaskLater(ResourcePackManager.plugin, 100L); // 5 seconds to let IA regenerate + } + }.runTaskLater(ResourcePackManager.plugin, 20L); // 1 second delay + + } catch (Exception e) { + Logger.sendMessage(sender, "&cFailed to update ItemsAdder config: " + e.getMessage()); + e.printStackTrace(); + } + } + + /** + * Handle the dismiss action - permanently dismisses the warning for the player. + */ + private void handleDismiss(CommandSender sender) { + if (!(sender instanceof Player player)) { + Logger.sendMessage(sender, "&cThis command can only be used by players!"); + return; + } + + ItemsAdderDismissedConfig.setDismissed(player.getUniqueId(), true); + Logger.sendMessage(sender, "&aItemsAdder configuration warning has been dismissed permanently."); + Logger.sendMessage(sender, "&7You can run &e/rspm itemsadder configure &7at any time to set it up."); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDetector.java b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDetector.java new file mode 100644 index 0000000..f4fd414 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDetector.java @@ -0,0 +1,145 @@ +package com.magmaguy.resourcepackmanager.itemsadder; + +import com.magmaguy.magmacore.util.Logger; +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; + +/** + * Detects ItemsAdder installation and checks its hosting configuration. + */ +public class ItemsAdderDetector { + + private ItemsAdderDetector() { + } + + /** + * Check if ItemsAdder plugin is installed and enabled. + * @return true if ItemsAdder is installed and enabled + */ + public static boolean isItemsAdderInstalled() { + return Bukkit.getPluginManager().isPluginEnabled("ItemsAdder"); + } + + /** + * Check if ItemsAdder is currently configured to host its own resource pack. + * This checks all built-in hosting methods: self-host, external-host, and lobfile. + * @return true if ItemsAdder is hosting via any method + */ + public static boolean isItemsAdderHosting() { + if (!isItemsAdderInstalled()) return false; + + File configFile = getItemsAdderConfigFile(); + if (configFile == null || !configFile.exists()) { + Logger.warn("Could not find ItemsAdder config.yml"); + return false; + } + + try { + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check self-host + boolean selfHostEnabled = config.getBoolean("resource-pack.hosting.self-host.enabled", false); + if (selfHostEnabled) { + Logger.info("ItemsAdder is configured with self-host enabled"); + return true; + } + + // Check external-host + String externalHostUrl = config.getString("resource-pack.hosting.external-host.url", ""); + if (externalHostUrl != null && !externalHostUrl.isEmpty() && !externalHostUrl.equals("http://example.com/resourcepack.zip")) { + Logger.info("ItemsAdder is configured with external-host URL: " + externalHostUrl); + return true; + } + + // Check lobfile hosting + boolean lobfileEnabled = config.getBoolean("resource-pack.hosting.lobfile.enabled", false); + if (lobfileEnabled) { + Logger.info("ItemsAdder is configured with lobfile hosting enabled"); + return true; + } + + // Check if no-host is enabled (this means ItemsAdder is NOT hosting) + boolean noHostEnabled = config.getBoolean("resource-pack.hosting.no-host.enabled", false); + if (noHostEnabled) { + Logger.info("ItemsAdder has no-host enabled - not hosting"); + return false; + } + + // Default: if nothing is explicitly enabled, ItemsAdder is not hosting + return false; + + } catch (Exception e) { + Logger.warn("Failed to read ItemsAdder config: " + e.getMessage()); + return false; + } + } + + /** + * Check if ItemsAdder has encryption/protection enabled. + * @return true if any protection is enabled + */ + public static boolean hasProtectionEnabled() { + if (!isItemsAdderInstalled()) return false; + + File configFile = getItemsAdderConfigFile(); + if (configFile == null || !configFile.exists()) return false; + + try { + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + boolean protection1 = config.getBoolean("resource-pack.zip.protect-file-from-unzip.protection_1", false); + boolean protection2 = config.getBoolean("resource-pack.zip.protect-file-from-unzip.protection_2", false); + boolean protection3 = config.getBoolean("resource-pack.zip.protect-file-from-unzip.protection_3", false); + + return protection1 || protection2 || protection3; + + } catch (Exception e) { + Logger.warn("Failed to check ItemsAdder protection settings: " + e.getMessage()); + return false; + } + } + + /** + * Get the ItemsAdder config.yml file. + * @return the config file, or null if not found + */ + public static File getItemsAdderConfigFile() { + File pluginsFolder = ResourcePackManager.plugin.getDataFolder().getParentFile(); + return new File(pluginsFolder, "ItemsAdder" + File.separatorChar + "config.yml"); + } + + /** + * Check if ItemsAdder needs configuration for ResourcePackManager to host. + * Returns true if ItemsAdder is installed but not set up for external hosting. + * @return true if ItemsAdder needs to be configured + */ + public static boolean needsConfiguration() { + if (!isItemsAdderInstalled()) return false; + + // If ItemsAdder is hosting via any method, don't warn + if (isItemsAdderHosting()) return false; + + // If no-host is already enabled, check if protections need to be disabled + File configFile = getItemsAdderConfigFile(); + if (configFile == null || !configFile.exists()) return false; + + try { + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + boolean noHostEnabled = config.getBoolean("resource-pack.hosting.no-host.enabled", false); + + // If no-host is enabled but protections are still on, needs configuration + if (noHostEnabled && hasProtectionEnabled()) { + return true; + } + + // If no-host is not enabled, needs configuration + return !noHostEnabled; + + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDismissedConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDismissedConfig.java new file mode 100644 index 0000000..3438fc8 --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderDismissedConfig.java @@ -0,0 +1,71 @@ +package com.magmaguy.resourcepackmanager.itemsadder; + +import com.magmaguy.magmacore.config.ConfigurationFile; +import com.magmaguy.magmacore.util.Logger; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Stores which players have dismissed the ItemsAdder configuration warning. + */ +public class ItemsAdderDismissedConfig extends ConfigurationFile { + + private static ItemsAdderDismissedConfig instance = null; + + public ItemsAdderDismissedConfig() { + super("itemsadder_dismissed.yml"); + instance = this; + } + + /** + * Check if a player has dismissed the warning. + * @param playerUUID the player's UUID + * @return true if the player has dismissed the warning + */ + public static boolean hasDismissed(UUID playerUUID) { + if (instance == null) return false; + + List dismissed = instance.getFileConfiguration().getStringList("dismissed"); + return dismissed.contains(playerUUID.toString()); + } + + /** + * Set whether a player has dismissed the warning. + * @param playerUUID the player's UUID + * @param dismissed true to dismiss, false to un-dismiss + */ + public static void setDismissed(UUID playerUUID, boolean dismissed) { + if (instance == null) return; + + List dismissedList = new ArrayList<>(instance.getFileConfiguration().getStringList("dismissed")); + + if (dismissed && !dismissedList.contains(playerUUID.toString())) { + dismissedList.add(playerUUID.toString()); + } else if (!dismissed) { + dismissedList.remove(playerUUID.toString()); + } + + instance.getFileConfiguration().set("dismissed", dismissedList); + try { + instance.getFileConfiguration().save(instance.file); + } catch (Exception e) { + Logger.warn("Failed to save ItemsAdder dismissed config!"); + e.printStackTrace(); + } + } + + @Override + public void initializeValues() { + // Ensure the dismissed list exists + if (!getFileConfiguration().contains("dismissed")) { + getFileConfiguration().set("dismissed", new ArrayList()); + try { + getFileConfiguration().save(file); + } catch (Exception e) { + Logger.warn("Failed to initialize ItemsAdder dismissed config!"); + } + } + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderWarningListener.java b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderWarningListener.java new file mode 100644 index 0000000..8ec6fad --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/itemsadder/ItemsAdderWarningListener.java @@ -0,0 +1,90 @@ +package com.magmaguy.resourcepackmanager.itemsadder; + +import com.magmaguy.magmacore.util.ChatColorConverter; +import com.magmaguy.resourcepackmanager.ResourcePackManager; +import net.md_5.bungee.api.chat.ClickEvent; +import net.md_5.bungee.api.chat.ComponentBuilder; +import net.md_5.bungee.api.chat.HoverEvent; +import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.chat.hover.content.Text; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.scheduler.BukkitRunnable; + +/** + * Listens for OP player joins and warns them if ItemsAdder needs configuration. + */ +public class ItemsAdderWarningListener implements Listener { + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + Player player = event.getPlayer(); + + // Only warn OP players + if (!player.isOp()) return; + + // Check if this player has dismissed the warning + if (ItemsAdderDismissedConfig.hasDismissed(player.getUniqueId())) return; + + // Check if ItemsAdder needs configuration + if (!ItemsAdderDetector.needsConfiguration()) return; + + // Delay the warning slightly to let the player fully join + new BukkitRunnable() { + @Override + public void run() { + if (!player.isOnline()) return; + + sendWarning(player); + } + }.runTaskLater(ResourcePackManager.plugin, 60L); // 3 seconds after join + } + + /** + * Send the warning title and chat message to the player. + */ + private void sendWarning(Player player) { + // Send title/subtitle + player.sendTitle( + ChatColorConverter.convert("&c&lItemsAdder Detected"), + ChatColorConverter.convert("&eResource pack not configured - check chat!"), + 10, 70, 20 + ); + + // Send chat message with explanation + player.sendMessage(""); + player.sendMessage(ChatColorConverter.convert("&8&m----------------------------------------")); + player.sendMessage(ChatColorConverter.convert("&c&lItemsAdder Configuration Warning")); + player.sendMessage(ChatColorConverter.convert("&8&m----------------------------------------")); + player.sendMessage(""); + player.sendMessage(ChatColorConverter.convert("&eItemsAdder has been detected but is not currently")); + player.sendMessage(ChatColorConverter.convert("&econfigured to let ResourcePackManager host the resource pack.")); + player.sendMessage(""); + player.sendMessage(ChatColorConverter.convert("&7For ResourcePackManager to merge and host the resource pack,")); + player.sendMessage(ChatColorConverter.convert("&7ItemsAdder needs to have hosting disabled and protections off.")); + player.sendMessage(""); + + // Create clickable buttons + // Option 1: Configure automatically + TextComponent configureButton = new TextComponent(ChatColorConverter.convert("&a&l[Configure Automatically]")); + configureButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/rspm itemsadder configure")); + configureButton.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new Text(ChatColorConverter.convert("&aClick to automatically configure ItemsAdder\n&7This will:\n&7- Enable no-host mode\n&7- Disable file protections\n&7- Reload both plugins")))); + + // Option 2: Dismiss permanently + TextComponent dismissButton = new TextComponent(ChatColorConverter.convert("&c&l[Dismiss Permanently]")); + dismissButton.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/rspm itemsadder dismiss")); + dismissButton.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, + new Text(ChatColorConverter.convert("&cClick to dismiss this warning permanently\n&7You won't see this warning again")))); + + // Send the buttons + TextComponent space = new TextComponent(" "); + player.spigot().sendMessage(configureButton, space, dismissButton); + + player.sendMessage(""); + player.sendMessage(ChatColorConverter.convert("&8&m----------------------------------------")); + player.sendMessage(""); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index c0a11fa..26bacbc 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -216,7 +216,12 @@ private static void recursivelyCopyDirectoryForCluster(File source, File target) } } else { try { - Files.copy(source.toPath(), target.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + // Check if target exists - if so, use resolveFileCollision to handle merging + if (target.exists()) { + resolveFileCollision(source, target); + } else { + Files.copy(source.toPath(), target.toPath()); + } } catch (IOException e) { Logger.warn("Failed to copy cluster file " + source.getPath()); } @@ -271,7 +276,7 @@ private static void createOutputDefaultElements() { Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that folder does not exist!"); } else if (!rerouteFolder.isDirectory()) { Logger.warn("Failed to reroute zipped file to " + rerouteFolder.getAbsolutePath() + " because that is a file and not a folder!"); - } else if (!ZipFile.zip(getOutputResourcePackFolder(), rerouteFolder.getPath() + ".zip")) { + } else if (!ZipFile.zip(getOutputResourcePackFolder(), rerouteFolder.getPath() + File.separatorChar + resourcePackName + ".zip")) { Logger.warn("Failed to zip merged resource pack into reroute directory!"); return; } @@ -347,11 +352,20 @@ private static void recursivelyCopyDirectory(File source, File target) { } } - private static void resolveFileCollision(File sourceFile, File targetFile) throws IOException { + public static void resolveFileCollision(File sourceFile, File targetFile) throws IOException { if (!targetFile.getName().endsWith(".json")) { - //If the file isn't .json then it can't be merged, only replaced (such as with .png). - //No further action is needed here since files are transferred over in priority order - Logger.info("Hard collision for file " + targetFile.getPath() + " detected! Auto-resolved based on highest priority."); + //If the file isn't .json then it can't be merged, only replaced (such as with .png, shaders, etc). + //Higher priority pack overwrites the file + Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + Logger.info("Hard collision for file " + targetFile.getPath() + " detected! Replaced with higher priority version."); + return; + } + + // Only merge JSON files that are designed to be merged (sounds.json, lang files) + // Model files, blockstates, etc. have fixed-size arrays that break when concatenated + if (!isMergeableJsonFile(targetFile)) { + Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + Logger.info("JSON collision for file " + targetFile.getPath() + " detected! Replaced with higher priority version (not a mergeable JSON type)."); return; } @@ -393,6 +407,44 @@ private static void resolveFileCollision(File sourceFile, File targetFile) throw Logger.info("File " + targetFile.getName() + " successfully auto-merged!"); } + /** + * Checks if a JSON file is designed to be merged (content can be combined). + * Files like sounds.json, lang files, atlases, fonts, and vanilla item model overrides can be merged. + * Custom model files, blockstates, equipment layers, etc. have fixed-size arrays that break when concatenated. + */ + private static boolean isMergeableJsonFile(File file) { + String path = file.getPath().replace("\\", "/"); + String fileName = file.getName(); + + // sounds.json files should be merged + if (fileName.equals("sounds.json")) { + return true; + } + + // Language files should be merged + if (path.contains("/lang/") || path.contains("/languages/")) { + return true; + } + + // Vanilla item model overrides should be merged (for custom model data) + if (path.contains("/minecraft/models/item/")) { + return true; + } + + // Atlas files should be merged (sources array) + if (path.contains("/atlases/")) { + return true; + } + + // Font files should be merged (providers array) + if (path.contains("/font/")) { + return true; + } + + // All other JSON files (custom models, blockstates, equipment layers, etc.) should not be merged + return false; + } + private static void stripDirectoryMetadata(File file) throws IOException { if (!file.isDirectory()) return; for (File listFile : file.listFiles()) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 157a81c..78bbcc0 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -3,9 +3,9 @@ import com.magmaguy.magmacore.util.Logger; import com.magmaguy.magmacore.util.ZipFile; import com.magmaguy.resourcepackmanager.ResourcePackManager; +import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; -import com.magmaguy.resourcepackmanager.mixer.Mix; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; import lombok.Setter; @@ -139,17 +139,24 @@ public void run() { if (!thirdPartyResourcePack.consideredStable) readyToSend = false; if (thirdPartyResourcePack.consideredStable) continue; thirdPartyResourcePack.ticksWithoutChange++; - if (thirdPartyResourcePack.ticksWithoutChange == 10) { - //If nothing has changed for 10 seconds, then it should be considered to be stable + if (thirdPartyResourcePack.ticksWithoutChange == 3) { + //If nothing has changed for 3 seconds, then it should be considered to be stable thirdPartyResourcePack.consideredStable = true; - Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has not changed for 10 seconds, considering it stable."); + Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has not changed for 3 seconds, considering it stable."); } } if (!stableAlreadySent && readyToSend) { - Logger.info("All resource packs are stable, sending resource pack to players."); + notifyResourcePackSending(); tagAsResourcePackSent(); - Mix.mixResourcePacks(); + // Delay 5 seconds before actually mixing and sending + new BukkitRunnable() { + @Override + public void run() { + Logger.info("Sending resource pack now."); + Mix.mixResourcePacks(); + } + }.runTaskLater(ResourcePackManager.plugin, 100L); } } }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 20, 20); @@ -161,6 +168,17 @@ public static void tagAsResourcePackSent(){ } } + private static void notifyResourcePackSending() { + String message = "&eAll resource packs are stable. Resource pack will be sent in 5 seconds."; + Logger.info("All resource packs are stable. Resource pack will be sent in 5 seconds."); + // Notify all online OPs + for (org.bukkit.entity.Player player : Bukkit.getOnlinePlayers()) { + if (player.isOp()) { + player.sendMessage(com.magmaguy.magmacore.util.ChatColorConverter.convert(message)); + } + } + } + public static void shutdown() { thirdPartyResourcePacks.clear(); resourcePackChangeWatcher.cancel(); @@ -244,7 +262,12 @@ private void recursivelyCloneDirectory(File source, File target) { } } else { try { - Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING); + // Check if target exists - if so, use Mix.resolveFileCollision to handle merging + if (target.exists()) { + Mix.resolveFileCollision(source, target); + } else { + Files.copy(source.toPath(), target.toPath()); + } } catch (Exception e) { Logger.warn("Failed to copy file " + source.getPath() + " to " + target.getPath()); } @@ -311,6 +334,10 @@ public void cloneResourcePackFile() { private void cloneLocalRSP() { try { if (zips) { + // Ensure mixer folder exists + File mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().toString(), "mixer"); + if (!mixerFolder.exists()) mixerFolder.mkdirs(); + Logger.info("Cloning resource pack from " + file.toPath()); mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java new file mode 100644 index 0000000..abaceda --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java @@ -0,0 +1,43 @@ +package com.magmaguy.resourcepackmanager.utils; + +import org.bukkit.Bukkit; + +public class ServerVersionHelper { + private static final int majorVersion; + private static final int minorVersion; + private static final boolean supportsMultipleResourcePacks; + + static { + String version = Bukkit.getBukkitVersion(); // e.g., "1.20.4-R0.1-SNAPSHOT" + String[] parts = version.split("-")[0].split("\\."); + majorVersion = Integer.parseInt(parts[0]); + minorVersion = Integer.parseInt(parts[1]); + + // addResourcePack was added in 1.20.3 + supportsMultipleResourcePacks = majorVersion > 1 || (majorVersion == 1 && minorVersion >= 20 && getRevision(version) >= 3); + } + + private static int getRevision(String version) { + String[] parts = version.split("-")[0].split("\\."); + if (parts.length >= 3) { + try { + return Integer.parseInt(parts[2]); + } catch (NumberFormatException e) { + return 0; + } + } + return 0; + } + + public static boolean supportsMultipleResourcePacks() { + return supportsMultipleResourcePacks; + } + + public static int getMajorVersion() { + return majorVersion; + } + + public static int getMinorVersion() { + return minorVersion; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 62e6d0c..96521d4 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.7.0 +version: 1.7.1 api-version: 1.14 author: MagmaGuy softdepend: From 5a93470a96c804ca9be1a2cfd3651de1947bc8c6 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sat, 17 Jan 2026 09:35:15 +0000 Subject: [PATCH 17/33] Resource Pack Manager 1.7.2: - [New] The mixer folder is now automatically created on startup, making it easier for users to add custom resource packs. - [Fix] Fixed server lag caused by resource pack mixing running on the main thread - mixing now runs asynchronously. - [Fix] Fixed zip extraction failing when the output directory didn't exist yet. - [Fix] Improved error logging when resource pack extraction fails - now shows detailed error messages. - [Fix] Merged resource packs now use ResourcePackManager's own pack.mcmeta, ensuring valid and compatible metadata. - [Fix] Custom core shaders are now removed from merged resource packs to prevent shader compilation errors caused by version-incompatible shaders. Co-Authored-By: Claude Opus 4.5 --- pom.xml | 4 +- .../ResourcePackManager.java | 6 ++ .../resourcepackmanager/mixer/Mix.java | 94 ++++++++++++++++++- .../thirdparty/ThirdPartyResourcePack.java | 2 +- src/main/resources/plugin.yml | 2 +- 5 files changed, 100 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index 2eefa22..b4811a0 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.7.1 + 1.7.2 17 @@ -55,7 +55,7 @@ com.magmaguy MagmaCore - 1.18-SNAPSHOT + 1.23-SNAPSHOT compile diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 315038a..3c29e75 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -21,6 +21,8 @@ import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; +import java.io.File; + public class ResourcePackManager extends JavaPlugin { public static JavaPlugin plugin; @@ -42,6 +44,10 @@ public void onEnable() { new CompatiblePluginConfig(); new ItemsAdderDismissedConfig(); + // Create mixer folder on startup so users can add custom resource packs + File mixerFolder = new File(getDataFolder(), "mixer"); + if (!mixerFolder.exists()) mixerFolder.mkdirs(); + BlueprintFolder.initialize(); //This starts a watchdog to see if the resource packs change and updates the mixer if htey do. ThirdPartyResourcePack.startResourcePackChangeWatchdog(); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 26bacbc..c7d3ca7 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -11,6 +11,7 @@ import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import com.magmaguy.resourcepackmanager.utils.SHA1Generator; import lombok.Getter; +import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.FileReader; @@ -38,6 +39,21 @@ public class Mix { private Mix() { } + /** + * Mixes resource packs asynchronously. Use this from the main thread. + */ + public static void mixResourcePacksAsync() { + new BukkitRunnable() { + @Override + public void run() { + mixResourcePacks(); + } + }.runTaskAsynchronously(ResourcePackManager.plugin); + } + + /** + * Mixes resource packs synchronously. Only call this from an async context to avoid blocking the main thread. + */ public static void mixResourcePacks() { if (!initializeDefaultPluginFolders()) return; initializeThirdPartyResourcePacks(); @@ -160,14 +176,18 @@ private static void cloneToOutputAndUnzip() { Logger.warn("A resource pack was null by the time it was meant to be unzipped!"); return; } - File file = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "output" + File.separatorChar + resourcePack.getName().replace(".zip", "")); - ZipFile.unzip(resourcePack, file); - stripDirectoryMetadata(file); + File outputDir = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "output" + File.separatorChar + resourcePack.getName().replace(".zip", "")); + // Pre-create the output directory to ensure getCanonicalPath() works correctly in the unzip security check + if (!outputDir.exists()) outputDir.mkdirs(); + ZipFile.unzip(resourcePack, outputDir); + stripDirectoryMetadata(outputDir); } catch (Exception e) { if (resourcePack == null) Logger.warn("Failed to extract resource pack! The file might be encrypted. This pack will be skipped."); - else + else { Logger.warn("Failed to extract resource pack " + resourcePack.getName() + " - the file might be encrypted or the plugin distributes its own pack. This pack will be skipped."); + Logger.warn("Error details: " + e.getMessage()); + } } }); @@ -264,6 +284,13 @@ private static void createOutputDefaultElements() { recursivelyCopyDirectory(subFile, getOutputResourcePackFolder()); } } + + // Copy ResourcePackManager's own pack.mcmeta to ensure valid metadata + copyPluginPackMcmeta(); + + // Remove incompatible custom shaders that can break the resource pack + removeIncompatibleShaders(); + if (!ZipFile.zip(getOutputResourcePackFolder(), getOutputResourcePackFolder().getPath() + ".zip")) { Logger.warn("Failed to zip merged resource pack!"); return; @@ -493,5 +520,64 @@ private static JsonArray mergeJsonArrays(JsonArray array1, JsonArray array2) { return mergedArray; } + /** + * Copies ResourcePackManager's own pack.mcmeta from resources to the output folder. + * This ensures the merged resource pack has a valid, compatible pack.mcmeta. + */ + private static void copyPluginPackMcmeta() { + try { + java.io.InputStream inputStream = ResourcePackManager.plugin.getResource("pack.mcmeta"); + if (inputStream == null) { + Logger.warn("Could not find pack.mcmeta in plugin resources!"); + return; + } + File targetFile = new File(getOutputResourcePackFolder().getPath() + File.separatorChar + "pack.mcmeta"); + Files.copy(inputStream, targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + inputStream.close(); + Logger.info("Copied ResourcePackManager pack.mcmeta to merged resource pack."); + } catch (IOException e) { + Logger.warn("Failed to copy pack.mcmeta to merged resource pack!"); + e.printStackTrace(); + } + } + + /** + * Removes custom shaders from the core shader folder that can cause compatibility issues. + * Custom shaders written for different Minecraft versions often have incompatible function names + * and variable declarations that cause shader compilation errors. + */ + private static void removeIncompatibleShaders() { + File coreShaderFolder = new File(getOutputResourcePackFolder().getPath() + + File.separatorChar + "assets" + + File.separatorChar + "minecraft" + + File.separatorChar + "shaders" + + File.separatorChar + "core"); + + if (!coreShaderFolder.exists()) { + return; // No custom core shaders, nothing to do + } + + File[] shaderFiles = coreShaderFolder.listFiles(); + if (shaderFiles == null) return; + + int removedCount = 0; + for (File shaderFile : shaderFiles) { + if (shaderFile.isFile() && (shaderFile.getName().endsWith(".fsh") || + shaderFile.getName().endsWith(".vsh") || + shaderFile.getName().endsWith(".glsl"))) { + try { + Files.delete(shaderFile.toPath()); + removedCount++; + } catch (IOException e) { + Logger.warn("Failed to remove incompatible shader: " + shaderFile.getName()); + } + } + } + + if (removedCount > 0) { + Logger.warn("Removed " + removedCount + " custom core shader(s) from merged resource pack to prevent compatibility issues."); + } + } + } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 78bbcc0..ee7f253 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -156,7 +156,7 @@ public void run() { Logger.info("Sending resource pack now."); Mix.mixResourcePacks(); } - }.runTaskLater(ResourcePackManager.plugin, 100L); + }.runTaskLaterAsynchronously(ResourcePackManager.plugin, 100L); } } }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 20, 20); diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 96521d4..dca1a4f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.7.1 +version: 1.7.2 api-version: 1.14 author: MagmaGuy softdepend: From ca88cf2c0a4ad6ad7dc4c04f680fd6a0bb1813d8 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Thu, 29 Jan 2026 06:40:09 +0000 Subject: [PATCH 18/33] Resource Pack Manager 1.7.2: - [New] Added collision log file (collision_log.txt) - all file collisions are now logged to a single file instead of flooding the console. - [New] Added HTTP timeout configuration for Auto Host requests - 30 second connect timeout, 60 second default socket timeout, and 5 minute timeout for file uploads to prevent hanging connections. - [New] The mixer folder is now automatically created on startup, making it easier for users to add custom resource packs. - [Fix] Fixed overlay directories from ItemsAdder and similar plugins being incorrectly merged - directories containing "overlay" or starting with "ia_overlay" are now skipped. - [Fix] Fixed problems related to merging shaders - [Fix] Reduced resource pack send delay from 5 seconds to 1 second for faster processing after stability is detected. - [Fix] Reduced console spam by removing verbose logging for routine operations (resource pack additions, cluster copies, pack.mcmeta copy, skipped packs). - [Fix] Fixed server lag caused by resource pack mixing running on the main thread - mixing now runs asynchronously. - [Fix] Fixed zip extraction failing when the output directory didn't exist yet. - [Fix] Improved error logging when resource pack extraction fails - now shows detailed error messages. - [Fix] Merged resource packs now use ResourcePackManager's own pack.mcmeta, ensuring valid and compatible metadata. - [Fix] Custom core shaders are now removed from merged resource packs to prevent shader compilation errors caused by version-incompatible shaders. --- .../autohost/AutoHost.java | 52 +++++++++- .../resourcepackmanager/mixer/Mix.java | 99 +++++++++++-------- .../thirdparty/ThirdPartyResourcePack.java | 8 +- 3 files changed, 110 insertions(+), 49 deletions(-) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 8e03294..7bf490e 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -11,13 +11,18 @@ import lombok.Getter; import lombok.Setter; import org.apache.hc.client5.http.classic.methods.HttpPost; +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.io.HttpClientConnectionManager; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; import org.apache.hc.core5.http.io.entity.EntityUtils; +import org.apache.hc.core5.util.Timeout; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; @@ -46,9 +51,46 @@ public class AutoHost { @Setter private static boolean firstUpload = true; + // Timeout settings for HTTP requests (in seconds) + private static final int DEFAULT_CONNECT_TIMEOUT = 30; + private static final int DEFAULT_SOCKET_TIMEOUT = 60; + private static final int UPLOAD_SOCKET_TIMEOUT = 300; // 5 minutes for file uploads + private AutoHost() { } + /** + * Creates an HTTP client with custom timeouts suitable for regular requests. + */ + private static CloseableHttpClient createHttpClient() { + return createHttpClient(DEFAULT_SOCKET_TIMEOUT); + } + + /** + * Creates an HTTP client with custom timeouts. + * @param socketTimeoutSeconds The socket (read) timeout in seconds + */ + private static CloseableHttpClient createHttpClient(int socketTimeoutSeconds) { + ConnectionConfig connectionConfig = ConnectionConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(DEFAULT_CONNECT_TIMEOUT)) + .setSocketTimeout(Timeout.ofSeconds(socketTimeoutSeconds)) + .build(); + + HttpClientConnectionManager connectionManager = PoolingHttpClientConnectionManagerBuilder.create() + .setDefaultConnectionConfig(connectionConfig) + .build(); + + RequestConfig requestConfig = RequestConfig.custom() + .setConnectionRequestTimeout(Timeout.ofSeconds(DEFAULT_CONNECT_TIMEOUT)) + .setResponseTimeout(Timeout.ofSeconds(socketTimeoutSeconds)) + .build(); + + return HttpClients.custom() + .setConnectionManager(connectionManager) + .setDefaultRequestConfig(requestConfig) + .build(); + } + public static void sendResourcePack(Player player) { Logger.info("Sending resource pack to " + player.getName() + ". rspUUID is null: " + (rspUUID == null) + ". done: " + done + ". firstUpload: " + firstUpload + "."); if (rspUUID == null || !done) return; @@ -122,7 +164,7 @@ private static void checkFileExistence() { } public static void initializeLink() { - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (CloseableHttpClient httpClient = createHttpClient()) { HttpPost httpPost = new HttpPost(finalURL + "initialize"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.addTextBody("uuid", DataConfig.getRspUUID(), ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8)); @@ -197,7 +239,7 @@ private static boolean isValidUUID(String uuidString) { public static void uploadFile() { Logger.info("Uploading resource!"); - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (CloseableHttpClient httpClient = createHttpClient(UPLOAD_SOCKET_TIMEOUT)) { HttpPost uploadFile = new HttpPost(finalURL + "upload"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -233,7 +275,7 @@ public static void uploadFile() { } private static boolean sendSHA1() { - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (CloseableHttpClient httpClient = createHttpClient()) { HttpPost httpPost = new HttpPost(finalURL + "sha1"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -303,7 +345,7 @@ private static boolean sendSHA1() { } private static void sendStillAlive() throws IOException { - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (CloseableHttpClient httpClient = createHttpClient()) { HttpPost httpPost = new HttpPost(finalURL + "still_alive"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); @@ -331,7 +373,7 @@ private static void sendStillAlive() throws IOException { } public static void dataComplianceRequest() throws IOException { - try (CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (CloseableHttpClient httpClient = createHttpClient()) { HttpPost httpPost = new HttpPost(finalURL + "data_compliance"); MultipartEntityBuilder builder = MultipartEntityBuilder.create(); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index c7d3ca7..9578a3c 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -35,6 +35,7 @@ public class Mix { @Getter private static byte[] finalSHA1Bytes; private static File mixerFolder; + private static List collisionLog; private Mix() { } @@ -55,10 +56,14 @@ public void run() { * Mixes resource packs synchronously. Only call this from an async context to avoid blocking the main thread. */ public static void mixResourcePacks() { + Logger.info("Starting resource pack mixing..."); + collisionLog = new ArrayList<>(); if (!initializeDefaultPluginFolders()) return; initializeThirdPartyResourcePacks(); cloneToOutputAndUnzip(); createOutputDefaultElements(); + writeCollisionLog(); + Logger.info("Resource pack mixing complete."); AutoHost.initialize(); } @@ -158,14 +163,12 @@ private static void initializeThirdPartyResourcePacks() { .forEach(packFile -> { resourcePacks.add(packFile); orderedResourcePacks.add(packFile.getName().replace(".zip", "")); - Logger.info("Added supported resource pack " + packFile.getName() + " without a priority!"); }); // Any leftover custom files without explicit priority customFiles.forEach(customFile -> { resourcePacks.add(customFile); orderedResourcePacks.add(customFile.getName().replace(".zip", "")); - Logger.info("Added custom resource pack " + customFile.getName() + " without a priority!"); }); } @@ -217,7 +220,6 @@ private static void copyClusterDirectoriesToOutput() { if (!orderedResourcePacks.contains("cluster_" + file.getName())) { orderedResourcePacks.add("cluster_" + file.getName()); } - Logger.info("Copied cluster directory " + file.getName() + " to output folder"); } catch (Exception e) { Logger.warn("Failed to copy cluster directory " + file.getName() + " to output folder"); e.printStackTrace(); @@ -227,6 +229,18 @@ private static void copyClusterDirectoriesToOutput() { private static void recursivelyCopyDirectoryForCluster(File source, File target) { if (source.isDirectory()) { + String sourceName = source.getName(); + + // Skip shaders folder entirely + if (sourceName.equals("shaders")) { + return; + } + + // Skip overlay directories + if (sourceName.startsWith("ia_overlay") || sourceName.contains("overlay")) { + return; + } + if (!target.exists()) target.mkdir(); File[] files = source.listFiles(); if (files != null) { @@ -270,7 +284,6 @@ private static void createOutputDefaultElements() { if (file.getName().equals(resourcePackName + ".zip")) continue; if (!file.exists()) { // Pack likely failed to extract (possibly encrypted) - skip gracefully - Logger.info("Skipping " + file.getName() + " - pack was not extracted (may be encrypted or corrupted)"); continue; } if (!file.isDirectory()) { @@ -360,7 +373,19 @@ private static void recursivelyDeleteDirectory(File directory) { private static void recursivelyCopyDirectory(File source, File target) { if (source.isDirectory()) { - target = new File(target.getAbsolutePath() + File.separatorChar + source.getName()); + String sourceName = source.getName(); + + // Skip shaders folder entirely - ItemsAdder has incomplete shaders that break MC 1.21.4+ + if (sourceName.equals("shaders")) { + return; + } + + // Skip overlay directories - they contain version-specific partial assets + if (sourceName.startsWith("ia_overlay") || sourceName.contains("overlay")) { + return; + } + + target = new File(target.getAbsolutePath() + File.separatorChar + sourceName); target.mkdir(); for (File file : source.listFiles()) { recursivelyCopyDirectory(file, target); @@ -384,7 +409,7 @@ public static void resolveFileCollision(File sourceFile, File targetFile) throws //If the file isn't .json then it can't be merged, only replaced (such as with .png, shaders, etc). //Higher priority pack overwrites the file Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); - Logger.info("Hard collision for file " + targetFile.getPath() + " detected! Replaced with higher priority version."); + logCollision("Replaced: " + targetFile.getPath()); return; } @@ -392,7 +417,7 @@ public static void resolveFileCollision(File sourceFile, File targetFile) throws // Model files, blockstates, etc. have fixed-size arrays that break when concatenated if (!isMergeableJsonFile(targetFile)) { Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); - Logger.info("JSON collision for file " + targetFile.getPath() + " detected! Replaced with higher priority version (not a mergeable JSON type)."); + logCollision("Replaced (non-mergeable JSON): " + targetFile.getPath()); return; } @@ -404,14 +429,13 @@ public static void resolveFileCollision(File sourceFile, File targetFile) throws try { json1 = JsonParser.parseReader(sourceFileReader).getAsJsonObject(); } catch (Exception e) { - Logger.warn("Malformed JSON for " + sourceFile.getAbsolutePath() + " !"); + Logger.warn("Malformed JSON: " + sourceFile.getAbsolutePath()); try { JsonReader jsonReader = new JsonReader(sourceFileReader); jsonReader.setStrictness(Strictness.LENIENT); json1 = JsonParser.parseReader(jsonReader).getAsJsonObject(); - Logger.info(JsonParser.parseReader(jsonReader).getAsString()); } catch (Exception ex) { - Logger.warn("Your JSON " + sourceFile.getAbsolutePath() + " is so broken even lenient won't let me read it!"); + Logger.warn("Unreadable JSON: " + sourceFile.getAbsolutePath()); } } JsonObject json2 = JsonParser.parseReader(targetFileReader).getAsJsonObject(); @@ -431,7 +455,7 @@ public static void resolveFileCollision(File sourceFile, File targetFile) throws targetFileWriter.close(); - Logger.info("File " + targetFile.getName() + " successfully auto-merged!"); + logCollision("Merged: " + targetFile.getPath()); } /** @@ -534,7 +558,6 @@ private static void copyPluginPackMcmeta() { File targetFile = new File(getOutputResourcePackFolder().getPath() + File.separatorChar + "pack.mcmeta"); Files.copy(inputStream, targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); inputStream.close(); - Logger.info("Copied ResourcePackManager pack.mcmeta to merged resource pack."); } catch (IOException e) { Logger.warn("Failed to copy pack.mcmeta to merged resource pack!"); e.printStackTrace(); @@ -542,40 +565,36 @@ private static void copyPluginPackMcmeta() { } /** - * Removes custom shaders from the core shader folder that can cause compatibility issues. - * Custom shaders written for different Minecraft versions often have incompatible function names - * and variable declarations that cause shader compilation errors. + * Shader handling removed - reverting to 1.7.1 behavior where shaders were + * copied/merged like any other files without special handling. */ private static void removeIncompatibleShaders() { - File coreShaderFolder = new File(getOutputResourcePackFolder().getPath() + - File.separatorChar + "assets" + - File.separatorChar + "minecraft" + - File.separatorChar + "shaders" + - File.separatorChar + "core"); - - if (!coreShaderFolder.exists()) { - return; // No custom core shaders, nothing to do - } - - File[] shaderFiles = coreShaderFolder.listFiles(); - if (shaderFiles == null) return; + // No-op: 1.7.1 had no shader handling and worked fine + } - int removedCount = 0; - for (File shaderFile : shaderFiles) { - if (shaderFile.isFile() && (shaderFile.getName().endsWith(".fsh") || - shaderFile.getName().endsWith(".vsh") || - shaderFile.getName().endsWith(".glsl"))) { - try { - Files.delete(shaderFile.toPath()); - removedCount++; - } catch (IOException e) { - Logger.warn("Failed to remove incompatible shader: " + shaderFile.getName()); - } + /** + * Writes the collision log to a file in the plugin's config folder. + * Only keeps the latest log, no history. + */ + private static void writeCollisionLog() { + if (collisionLog == null || collisionLog.isEmpty()) return; + + File logFile = new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "collision_log.txt"); + try (FileWriter writer = new FileWriter(logFile, false)) { + writer.write("Resource Pack Collision Log\n"); + writer.write("Generated: " + java.time.LocalDateTime.now() + "\n"); + writer.write("================================================\n\n"); + for (String entry : collisionLog) { + writer.write(entry + "\n"); } + } catch (IOException e) { + Logger.warn("Failed to write collision log file."); } + } - if (removedCount > 0) { - Logger.warn("Removed " + removedCount + " custom core shader(s) from merged resource pack to prevent compatibility issues."); + private static void logCollision(String message) { + if (collisionLog != null) { + collisionLog.add(message); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index ee7f253..a4c06ea 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -149,14 +149,14 @@ public void run() { if (!stableAlreadySent && readyToSend) { notifyResourcePackSending(); tagAsResourcePackSent(); - // Delay 5 seconds before actually mixing and sending + // Delay 1 second before actually mixing and sending new BukkitRunnable() { @Override public void run() { Logger.info("Sending resource pack now."); Mix.mixResourcePacks(); } - }.runTaskLaterAsynchronously(ResourcePackManager.plugin, 100L); + }.runTaskLaterAsynchronously(ResourcePackManager.plugin, 20L); } } }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 20, 20); @@ -169,8 +169,8 @@ public static void tagAsResourcePackSent(){ } private static void notifyResourcePackSending() { - String message = "&eAll resource packs are stable. Resource pack will be sent in 5 seconds."; - Logger.info("All resource packs are stable. Resource pack will be sent in 5 seconds."); + String message = "&eAll resource packs are stable. Resource pack will be sent in 1 second."; + Logger.info("All resource packs are stable. Resource pack will be sent in 1 second."); // Notify all online OPs for (org.bukkit.entity.Player player : Bukkit.getOnlinePlayers()) { if (player.isOp()) { From 74ad905048b2a9a69cb6b1ff0a44e0e082aad00a Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Mon, 2 Feb 2026 16:46:30 +0000 Subject: [PATCH 19/33] Resource Pack Manager 1.7.3: - [Fix] Stopped replacing pack.mcmeta, sorry about that. --- pom.xml | 2 +- .../resourcepackmanager/mixer/Mix.java | 34 ------------------- src/main/resources/plugin.yml | 2 +- 3 files changed, 2 insertions(+), 36 deletions(-) diff --git a/pom.xml b/pom.xml index b4811a0..61fa8b3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.7.2 + 1.7.3 17 diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 9578a3c..687999f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -298,12 +298,6 @@ private static void createOutputDefaultElements() { } } - // Copy ResourcePackManager's own pack.mcmeta to ensure valid metadata - copyPluginPackMcmeta(); - - // Remove incompatible custom shaders that can break the resource pack - removeIncompatibleShaders(); - if (!ZipFile.zip(getOutputResourcePackFolder(), getOutputResourcePackFolder().getPath() + ".zip")) { Logger.warn("Failed to zip merged resource pack!"); return; @@ -544,34 +538,6 @@ private static JsonArray mergeJsonArrays(JsonArray array1, JsonArray array2) { return mergedArray; } - /** - * Copies ResourcePackManager's own pack.mcmeta from resources to the output folder. - * This ensures the merged resource pack has a valid, compatible pack.mcmeta. - */ - private static void copyPluginPackMcmeta() { - try { - java.io.InputStream inputStream = ResourcePackManager.plugin.getResource("pack.mcmeta"); - if (inputStream == null) { - Logger.warn("Could not find pack.mcmeta in plugin resources!"); - return; - } - File targetFile = new File(getOutputResourcePackFolder().getPath() + File.separatorChar + "pack.mcmeta"); - Files.copy(inputStream, targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); - inputStream.close(); - } catch (IOException e) { - Logger.warn("Failed to copy pack.mcmeta to merged resource pack!"); - e.printStackTrace(); - } - } - - /** - * Shader handling removed - reverting to 1.7.1 behavior where shaders were - * copied/merged like any other files without special handling. - */ - private static void removeIncompatibleShaders() { - // No-op: 1.7.1 had no shader handling and worked fine - } - /** * Writes the collision log to a file in the plugin's config folder. * Only keeps the latest log, no history. diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index dca1a4f..fa42655 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.7.2 +version: 1.7.3 api-version: 1.14 author: MagmaGuy softdepend: From b7c2274ca6891a8ed7a33255c87944f0116d7679 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Wed, 11 Mar 2026 15:43:37 +0000 Subject: [PATCH 20/33] Resource Pack Manager 1.7.5: - [New] Added Nexo compatibility for automatic resource pack detection. - [New] Added soft dependencies for BetterHUD, InfiniteVehicles, MegaBlockSurvivors, MMOInventory, Nexo, and ValhallaMMO. - [New] Plugin initialization now uses MagmaCore's async/sync initialization system for better startup performance and progress tracking. - [Fix] Fixed overlay directories being incorrectly skipped during resource pack mixing - overlay directories are now properly merged. - [Fix] Fixed potential null pointer exception when cancelling the resource pack change watchdog during shutdown. - [Fix] Fixed potential crash when disabling the plugin during initialization by adding initialization state checks. - [Fix] Moved third-party resource pack initialization out of config field constructors into the main initialization flow for better control over startup order. - [Fix] Replaced FreeMinecraftModels Metrics dependency with direct bStats dependency. - [Fix] Updated MagmaCore to 1.28-SNAPSHOT. - [Fix] Updated Lombok to 1.18.42 and configured annotation processor path for maven-compiler-plugin. --- pom.xml | 21 ++-- .../ResourcePackManager.java | 100 +++++++++++++----- .../CompatiblePluginConfigFields.java | 2 - .../compatibleplugins/premade/Nexo.java | 13 +++ .../resourcepackmanager/mixer/Mix.java | 10 -- .../thirdparty/ThirdPartyResourcePack.java | 8 +- src/main/resources/plugin.yml | 10 +- 7 files changed, 114 insertions(+), 50 deletions(-) create mode 100644 src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nexo.java diff --git a/pom.xml b/pom.xml index 61fa8b3..a808316 100644 --- a/pom.xml +++ b/pom.xml @@ -5,12 +5,13 @@ com.magmaguy ResourcePackManager - 1.7.3 + 1.7.5 17 17 UTF-8 + 1.18.42 @@ -39,7 +40,7 @@ org.projectlombok lombok - 1.18.34 + ${lombok.version} provided @@ -55,15 +56,9 @@ com.magmaguy MagmaCore - 1.23-SNAPSHOT + 1.28-SNAPSHOT compile - - com.magmaguy - FreeMinecraftModels - 1.4.0 - provided - org.bstats @@ -79,9 +74,17 @@ maven-compiler-plugin + 3.13.0 17 17 + + + org.projectlombok + lombok + ${lombok.version} + + diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index 3c29e75..b2851bf 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -1,8 +1,10 @@ package com.magmaguy.resourcepackmanager; -import com.magmaguy.freeminecraftmodels.bukkit.Metrics; import com.magmaguy.magmacore.MagmaCore; import com.magmaguy.magmacore.command.CommandManager; +import com.magmaguy.magmacore.initialization.PluginInitializationConfig; +import com.magmaguy.magmacore.initialization.PluginInitializationContext; +import com.magmaguy.magmacore.initialization.PluginInitializationState; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.autohost.AutoHost; import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; @@ -14,9 +16,11 @@ import com.magmaguy.resourcepackmanager.config.DataConfig; import com.magmaguy.resourcepackmanager.config.DefaultConfig; import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfig; +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import com.magmaguy.resourcepackmanager.utils.VersionChecker; +import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.event.HandlerList; import org.bukkit.plugin.java.JavaPlugin; @@ -36,45 +40,89 @@ public void onEnable() { " |_|_\\|___/_| |_| |_\\__,_|_||_\\__,_\\__, \\___|_| \n" + " |___/ "); Bukkit.getLogger().info("ResourcePackManager v." + this.getDescription().getVersion()); - MagmaCore.onEnable(); - plugin = this; + MagmaCore.onEnable(this); + MagmaCore.startInitialization(this, + new PluginInitializationConfig("ResourcePackManager", null, 10), + this::asyncInitialization, + this::syncInitialization, + () -> Logger.info("ResourcePackManager fully initialized!"), + throwable -> throwable.printStackTrace()); + } + + @Override + public void onLoad() { + MagmaCore.createInstance(this); + } + + @Override + public void onDisable() { + MagmaCore.requestInitializationShutdown(this); + if (MagmaCore.getInitializationState(this.getName()) == PluginInitializationState.INITIALIZING) { + Logger.info("Disabling ResourcePackManager during initialization"); + ThirdPartyResourcePack.shutdown(); + AutoHost.shutdown(); + HandlerList.unregisterAll(this); + MagmaCore.shutdown(this); + return; + } + Logger.info("Disabling ResourcePackManager"); + ThirdPartyResourcePack.shutdown(); + AutoHost.shutdown(); + HandlerList.unregisterAll(this); + MagmaCore.shutdown(this); + } + + private void asyncInitialization(PluginInitializationContext initializationContext) { + initializationContext.step("Data Config"); new DataConfig(); + + initializationContext.step("Default Config"); new DefaultConfig(); - new CompatiblePluginConfig(); + + initializationContext.step("ItemsAdder Config"); new ItemsAdderDismissedConfig(); - // Create mixer folder on startup so users can add custom resource packs + initializationContext.step("Mixer Folder"); File mixerFolder = new File(getDataFolder(), "mixer"); - if (!mixerFolder.exists()) mixerFolder.mkdirs(); + if (!mixerFolder.exists()) { + mixerFolder.mkdirs(); + } + initializationContext.step("Blueprint Folder"); BlueprintFolder.initialize(); - //This starts a watchdog to see if the resource packs change and updates the mixer if htey do. + + initializationContext.step("Compatible Plugins"); + new CompatiblePluginConfig(); + + initializationContext.step("Pack Integrations"); + for (CompatiblePluginConfigFields compatiblePluginConfigFields : CompatiblePluginConfig.getCompatiblePlugins().values()) { + if (!compatiblePluginConfigFields.isEnabled()) continue; + ThirdPartyResourcePack.initializeThirdPartyResourcePack(compatiblePluginConfigFields); + } + } + + private void syncInitialization(PluginInitializationContext initializationContext) { + initializationContext.step("Change Watchdog"); ThirdPartyResourcePack.startResourcePackChangeWatchdog(); - if (DefaultConfig.isAutoHost()) + + initializationContext.step("Event Listeners"); + if (DefaultConfig.isAutoHost()) { Bukkit.getPluginManager().registerEvents(new PlayerManager(), this); + } + Bukkit.getPluginManager().registerEvents(new VersionChecker.VersionCheckerEvents(), this); + Bukkit.getPluginManager().registerEvents(new ItemsAdderWarningListener(), this); + + initializationContext.step("Commands"); CommandManager commandManager = new CommandManager(this, "resourcepackmanager"); commandManager.registerCommand(new ReloadCommand()); commandManager.registerCommand(new DataComplianceRequestCommand()); commandManager.registerCommand(new ItemsAdderCommand()); - Bukkit.getPluginManager().registerEvents(new VersionChecker.VersionCheckerEvents(), this); - Bukkit.getPluginManager().registerEvents(new ItemsAdderWarningListener(), this); -// AutoHost.initialize(); - - Metrics metrics = new Metrics(this, 22867); - MagmaCore.checkVersionUpdate("118574", "https://www.spigotmc.org/resources/resource-pack-manager.118574/"); - } - @Override - public void onLoad() { - MagmaCore.createInstance(this); - } + initializationContext.step("Metrics"); + new Metrics(this, 22867); - @Override - public void onDisable() { - Logger.info("Disabling ResourcePackManager"); - ThirdPartyResourcePack.shutdown(); - AutoHost.shutdown(); - HandlerList.unregisterAll(this); + initializationContext.step("Version Check"); + MagmaCore.checkVersionUpdate("118574", "https://www.spigotmc.org/resources/resource-pack-manager.118574/"); } -} \ No newline at end of file +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java index 0c06e79..fc4d25e 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java @@ -1,7 +1,6 @@ package com.magmaguy.resourcepackmanager.config.compatibleplugins; import com.magmaguy.magmacore.config.CustomConfigFields; -import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; import lombok.Getter; import lombok.Setter; @@ -56,6 +55,5 @@ public void processConfigFields() { this.localPath = processString("localPath", localPath, localPath, true); this.cluster = processBoolean("cluster", cluster, cluster, true); - if (isEnabled) ThirdPartyResourcePack.initializeThirdPartyResourcePack(this); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nexo.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nexo.java new file mode 100644 index 0000000..552fc7b --- /dev/null +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/Nexo.java @@ -0,0 +1,13 @@ +package com.magmaguy.resourcepackmanager.config.compatibleplugins.premade; + +import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; + +import java.io.File; + +public class Nexo extends CompatiblePluginConfigFields { + public Nexo() { + super("nexo", true); + setPluginName("Nexo"); + setLocalPath("Nexo" + File.separatorChar + "pack" + File.separatorChar + "pack.zip"); + } +} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 687999f..73310f2 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -236,11 +236,6 @@ private static void recursivelyCopyDirectoryForCluster(File source, File target) return; } - // Skip overlay directories - if (sourceName.startsWith("ia_overlay") || sourceName.contains("overlay")) { - return; - } - if (!target.exists()) target.mkdir(); File[] files = source.listFiles(); if (files != null) { @@ -374,11 +369,6 @@ private static void recursivelyCopyDirectory(File source, File target) { return; } - // Skip overlay directories - they contain version-specific partial assets - if (sourceName.startsWith("ia_overlay") || sourceName.contains("overlay")) { - return; - } - target = new File(target.getAbsolutePath() + File.separatorChar + sourceName); target.mkdir(); for (File file : source.listFiles()) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index a4c06ea..1a8eb4d 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -119,6 +119,9 @@ public ThirdPartyResourcePack(String pluginName, String localPath, String url, b private static BukkitTask resourcePackChangeWatcher = null; public static void startResourcePackChangeWatchdog() { + if (resourcePackChangeWatcher != null) { + resourcePackChangeWatcher.cancel(); + } resourcePackChangeWatcher = new BukkitRunnable() { @Override public void run() { @@ -181,7 +184,10 @@ private static void notifyResourcePackSending() { public static void shutdown() { thirdPartyResourcePacks.clear(); - resourcePackChangeWatcher.cancel(); + if (resourcePackChangeWatcher != null) { + resourcePackChangeWatcher.cancel(); + resourcePackChangeWatcher = null; + } } public static void initializeThirdPartyResourcePack(CompatiblePluginConfigFields compatiblePluginConfigFields) { diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index fa42655..853b21f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.7.3 +version: 1.7.5 api-version: 1.14 author: MagmaGuy softdepend: @@ -11,6 +11,12 @@ softdepend: - Oraxen - ItemsAdder - BackpackPlus + - BetterHUD + - InfiniteVehicles + - MegaBlockSurvivors + - MMOInventory + - Nexo + - ValhallaMMO - vane-core - RealisticSurvival @@ -18,4 +24,4 @@ commands: resourcepackmanager: aliases: - rspm - description: "Main command" \ No newline at end of file + description: "Main command" From 7decefe418a93e20530430f5325e46757041c2be Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sat, 14 Mar 2026 16:13:22 +0000 Subject: [PATCH 21/33] Fix NoSuchFileException during resource pack mixing by creating parent directories Add parent directory creation (mkdirs) before Files.copy() calls in recursivelyCopyDirectoryForCluster, recursivelyCopyDirectory, and recursivelyCloneDirectory to prevent NoSuchFileException when the target file's parent directory doesn't exist yet. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/com/magmaguy/resourcepackmanager/mixer/Mix.java | 5 ++++- .../thirdparty/ThirdPartyResourcePack.java | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 73310f2..acc75b7 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -249,6 +249,7 @@ private static void recursivelyCopyDirectoryForCluster(File source, File target) if (target.exists()) { resolveFileCollision(source, target); } else { + target.getParentFile().mkdirs(); Files.copy(source.toPath(), target.toPath()); } } catch (IOException e) { @@ -380,7 +381,9 @@ private static void recursivelyCopyDirectory(File source, File target) { resolveFileCollision(source, Path.of(target.getPath() + File.separatorChar + source.getName()).toFile()); return; } - Files.copy(source.toPath(), Path.of(target.getPath() + File.separatorChar + source.getName())); + Path targetPath = Path.of(target.getPath() + File.separatorChar + source.getName()); + targetPath.getParent().toFile().mkdirs(); + Files.copy(source.toPath(), targetPath); } catch (IOException e) { Logger.warn("Failed to copy file"); throw new RuntimeException(e); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 1a8eb4d..3cdcc89 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -272,6 +272,7 @@ private void recursivelyCloneDirectory(File source, File target) { if (target.exists()) { Mix.resolveFileCollision(source, target); } else { + target.getParentFile().mkdirs(); Files.copy(source.toPath(), target.toPath()); } } catch (Exception e) { From 7e202d135abb9c6e1d44cac355f1af29f11810e1 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sat, 14 Mar 2026 16:19:10 +0000 Subject: [PATCH 22/33] Migrate ResourcePackManager to NightbreakPluginBootstrap Replace direct MagmaCore.startInitialization() with NightbreakPluginBootstrap.startInitialization() and NightbreakPluginHooks. Define NightbreakPluginSpec with all capability flags set to false (no content packages, no preset modes, no import system). Wire standard commands via simplified registerStandardCommands overload with a no-op setup action. Keep RPM-specific commands (ReloadCommand, DataComplianceRequestCommand, ItemsAdderCommand) registered separately. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../ResourcePackManager.java | 43 ++++++++++++++++--- 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index b2851bf..bc9c2ef 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -5,6 +5,9 @@ import com.magmaguy.magmacore.initialization.PluginInitializationConfig; import com.magmaguy.magmacore.initialization.PluginInitializationContext; import com.magmaguy.magmacore.initialization.PluginInitializationState; +import com.magmaguy.magmacore.nightbreak.NightbreakPluginBootstrap; +import com.magmaguy.magmacore.nightbreak.NightbreakPluginHooks; +import com.magmaguy.magmacore.nightbreak.NightbreakPluginSpec; import com.magmaguy.magmacore.util.Logger; import com.magmaguy.resourcepackmanager.autohost.AutoHost; import com.magmaguy.resourcepackmanager.commands.DataComplianceRequestCommand; @@ -29,6 +32,12 @@ public class ResourcePackManager extends JavaPlugin { + public static final NightbreakPluginSpec NIGHTBREAK_PLUGIN_SPEC = new NightbreakPluginSpec( + "ResourcePackManager", "resourcepackmanager", "resourcepackmanager.*", + "resourcepackmanager.setup", "resourcepackmanager.initialize", + "", "Reloaded ResourcePackManager.", + false, false, false); + public static JavaPlugin plugin; @Override @@ -41,13 +50,30 @@ public void onEnable() { " |___/ "); Bukkit.getLogger().info("ResourcePackManager v." + this.getDescription().getVersion()); plugin = this; - MagmaCore.onEnable(this); - MagmaCore.startInitialization(this, + NightbreakPluginBootstrap.startInitialization(this, new PluginInitializationConfig("ResourcePackManager", null, 10), - this::asyncInitialization, - this::syncInitialization, - () -> Logger.info("ResourcePackManager fully initialized!"), - throwable -> throwable.printStackTrace()); + NIGHTBREAK_PLUGIN_SPEC, + new NightbreakPluginHooks() { + @Override + public void asyncInitialization(PluginInitializationContext initializationContext) { + ResourcePackManager.this.asyncInitialization(initializationContext); + } + + @Override + public void syncInitialization(PluginInitializationContext initializationContext) { + ResourcePackManager.this.syncInitialization(initializationContext); + } + + @Override + public void onInitializationSuccess() { + Logger.info("ResourcePackManager fully initialized!"); + } + + @Override + public void onInitializationFailure(Throwable throwable) { + throwable.printStackTrace(); + } + }); } @Override @@ -115,6 +141,11 @@ private void syncInitialization(PluginInitializationContext initializationContex initializationContext.step("Commands"); CommandManager commandManager = new CommandManager(this, "resourcepackmanager"); + NightbreakPluginBootstrap.registerStandardCommands(this, + commandManager, + NIGHTBREAK_PLUGIN_SPEC, + player -> Logger.sendMessage(player, "&eResourcePackManager has no setup menu. Edit config files in plugins/ResourcePackManager/ and use &6/resourcepackmanager reload&e."), + sender -> ReloadCommand.reloadPlugin(sender)); commandManager.registerCommand(new ReloadCommand()); commandManager.registerCommand(new DataComplianceRequestCommand()); commandManager.registerCommand(new ItemsAdderCommand()); From 90bc5e8a78012713da067cbf31fd93416c632eaa Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Wed, 18 Mar 2026 23:27:13 +0000 Subject: [PATCH 23/33] Save current state before Lua scripting abstraction refactor Co-Authored-By: Claude Opus 4.6 (1M context) --- pom.xml | 2 +- .../resourcepackmanager/mixer/Mix.java | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index a808316..8bc4cf6 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ com.magmaguy MagmaCore - 1.28-SNAPSHOT + 1.29-SNAPSHOT compile diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index acc75b7..8c9052d 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -418,6 +418,8 @@ public static void resolveFileCollision(File sourceFile, File targetFile) throws } catch (Exception e) { Logger.warn("Malformed JSON: " + sourceFile.getAbsolutePath()); try { + sourceFileReader.close(); + sourceFileReader = new FileReader(sourceFile); JsonReader jsonReader = new JsonReader(sourceFileReader); jsonReader.setStrictness(Strictness.LENIENT); json1 = JsonParser.parseReader(jsonReader).getAsJsonObject(); @@ -425,11 +427,41 @@ public static void resolveFileCollision(File sourceFile, File targetFile) throws Logger.warn("Unreadable JSON: " + sourceFile.getAbsolutePath()); } } - JsonObject json2 = JsonParser.parseReader(targetFileReader).getAsJsonObject(); + JsonObject json2 = null; + try { + json2 = JsonParser.parseReader(targetFileReader).getAsJsonObject(); + } catch (Exception e) { + Logger.warn("Malformed JSON: " + targetFile.getAbsolutePath()); + try { + targetFileReader.close(); + targetFileReader = new FileReader(targetFile); + JsonReader jsonReader = new JsonReader(targetFileReader); + jsonReader.setStrictness(Strictness.LENIENT); + json2 = JsonParser.parseReader(jsonReader).getAsJsonObject(); + } catch (Exception ex) { + Logger.warn("Unreadable JSON: " + targetFile.getAbsolutePath()); + } + } sourceFileReader.close(); targetFileReader.close(); + // If either JSON is unreadable, keep whichever one is valid, or skip + if (json1 == null && json2 == null) { + Logger.warn("Both JSON files unreadable during merge, skipping: " + targetFile.getPath()); + return; + } + if (json1 == null) { + // Target is already in place, nothing to merge + return; + } + if (json2 == null) { + // Overwrite with the source since target is broken + Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + logCollision("Replaced (unreadable target JSON): " + targetFile.getPath()); + return; + } + // Merge JSON objects JsonObject mergedJson = mergeJsonObjects(json1, json2); From de9717c9310c16820feb334ef25a1758911087a3 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Wed, 18 Mar 2026 23:59:26 +0000 Subject: [PATCH 24/33] Update ResourcePackManager to Magmacore 2.0.0-SNAPSHOT Co-Authored-By: Claude Opus 4.6 (1M context) --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 8bc4cf6..2a7e379 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ com.magmaguy MagmaCore - 1.29-SNAPSHOT + 2.0.0-SNAPSHOT compile From 57a9b3390c339e35fbda0d03d8d0f04bc8575a40 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Thu, 19 Mar 2026 14:52:45 +0000 Subject: [PATCH 25/33] fix: retry file copy on Windows lock when cloning resource packs On Windows, the source zip may still be locked by FMM when RSPM tries to copy it. Retries up to 5 times with increasing delay (500ms-2.5s). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../thirdparty/ThirdPartyResourcePack.java | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 3cdcc89..6e95a32 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -339,18 +339,30 @@ public void cloneResourcePackFile() { } private void cloneLocalRSP() { - try { - if (zips) { - // Ensure mixer folder exists - File mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().toString(), "mixer"); - if (!mixerFolder.exists()) mixerFolder.mkdirs(); + if (!zips) return; + File mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().toString(), "mixer"); + if (!mixerFolder.exists()) mixerFolder.mkdirs(); + // Retry up to 5 times with increasing delay — on Windows the source file + // may still be locked by the plugin that just finished writing it. + for (int attempt = 1; attempt <= 5; attempt++) { + try { Logger.info("Cloning resource pack from " + file.toPath()); mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); + return; + } catch (java.nio.file.FileSystemException e) { + if (attempt < 5) { + Logger.warn("Resource pack file is locked (attempt " + attempt + "/5), retrying in " + (attempt * 500) + "ms..."); + try { Thread.sleep(attempt * 500L); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); return; } + } else { + Logger.warn("Failed to clone resource pack from " + file.getPath() + " after 5 attempts — file is still locked by another process."); + e.printStackTrace(); + } + } catch (Exception e) { + Logger.warn("Failed to clone resource pack from " + file.getPath() + " to the mixer folder!"); + e.printStackTrace(); + return; } - } catch (Exception e) { - Logger.warn("Failed to clone resource pack from " + file.getPath() + " to the mixer folder!"); - e.printStackTrace(); } } From 6442471fb115d8de69303953e160831db2c7afc9 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Thu, 19 Mar 2026 14:53:48 +0000 Subject: [PATCH 26/33] fix: use Bukkit scheduler for file lock retry instead of Thread.sleep Co-Authored-By: Claude Opus 4.6 (1M context) --- .../thirdparty/ThirdPartyResourcePack.java | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 6e95a32..7066b1f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -342,27 +342,24 @@ private void cloneLocalRSP() { if (!zips) return; File mixerFolder = new File(ResourcePackManager.plugin.getDataFolder().toString(), "mixer"); if (!mixerFolder.exists()) mixerFolder.mkdirs(); + attemptClone(1); + } - // Retry up to 5 times with increasing delay — on Windows the source file - // may still be locked by the plugin that just finished writing it. - for (int attempt = 1; attempt <= 5; attempt++) { - try { - Logger.info("Cloning resource pack from " + file.toPath()); - mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); - return; - } catch (java.nio.file.FileSystemException e) { - if (attempt < 5) { - Logger.warn("Resource pack file is locked (attempt " + attempt + "/5), retrying in " + (attempt * 500) + "ms..."); - try { Thread.sleep(attempt * 500L); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); return; } - } else { - Logger.warn("Failed to clone resource pack from " + file.getPath() + " after 5 attempts — file is still locked by another process."); - e.printStackTrace(); - } - } catch (Exception e) { - Logger.warn("Failed to clone resource pack from " + file.getPath() + " to the mixer folder!"); + private void attemptClone(int attempt) { + try { + Logger.info("Cloning resource pack from " + file.toPath()); + mixerResourcePack = Files.copy(Path.of(file.getAbsolutePath()), Path.of(getTarget().toAbsolutePath().toString()), StandardCopyOption.REPLACE_EXISTING).toFile(); + } catch (java.nio.file.FileSystemException e) { + if (attempt < 5) { + Logger.warn("Resource pack file is locked (attempt " + attempt + "/5), retrying in " + (attempt * 10) + " ticks..."); + Bukkit.getScheduler().runTaskLaterAsynchronously(ResourcePackManager.plugin, () -> attemptClone(attempt + 1), attempt * 10L); + } else { + Logger.warn("Failed to clone resource pack from " + file.getPath() + " after 5 attempts — file is still locked by another process."); e.printStackTrace(); - return; } + } catch (Exception e) { + Logger.warn("Failed to clone resource pack from " + file.getPath() + " to the mixer folder!"); + e.printStackTrace(); } } From a62d4233d15520a11ff56aadb065624b2647edf6 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Thu, 26 Mar 2026 21:47:40 +0000 Subject: [PATCH 27/33] refactor+fix: major cleanup and merge logic overhaul Cleanup: - Remove dead code: GeneratorInterface, duplicate ChatColorConverter, VersionChecker (MagmaCore handles it) - Remove no-op methods: decrypt(), unpublish(), stripDirectoryMetadata() - Remove unused config fields: encrypts, distributes, resourcePackUpdated, mixerFilename - Fix bstats relocation typo (was pointing to freeminecraftmodels) - Fix /fmm usage string, elitemobs.versionnotification permission - Fix resource leaks: SHA1Generator, BlueprintFolder, Mix.resolveFileCollision - Deduplicate: 3 recursive-copy methods -> 1, isValidUUID, ReloadCommand.execute Merge logic fixes: - Fix priority ordering: unified sort replaces tangled 90-line loop - Fix merge direction: higher priority packs now win collisions correctly - Fix custom file priority matching: check both name.zip and name - Fix cluster packs: now zip and participate in priority ordering - Remove shader skip: was stripping ItemsAdder's custom armor shaders from overlays - Add pack.mcmeta merge: overlay entries from all packs are now combined - Add items/*.json merge (1.21.4+): range_dispatch entries and select cases - Sort models/item overrides by custom_model_data after merge - Respect sounds.json replace flag per sound event Co-Authored-By: Claude Opus 4.6 (1M context) --- pom.xml | 2 +- .../ResourcePackManager.java | 2 - .../api/ResourcePackManagerAPI.java | 6 +- .../autohost/AutoHost.java | 22 +- .../DataComplianceRequestCommand.java | 2 +- .../commands/ReloadCommand.java | 4 +- .../config/BlueprintFolder.java | 7 +- .../CompatiblePluginConfigFields.java | 16 - .../compatibleplugins/premade/BetterHUD.java | 1 - .../compatibleplugins/premade/ItemsAdder.java | 2 - .../compatibleplugins/premade/VaneCore.java | 1 - .../resourcepackmanager/mixer/Mix.java | 540 ++++++++++++------ .../thirdparty/GeneratorInterface.java | 11 - .../thirdparty/ThirdPartyResourcePack.java | 131 ++--- .../utils/ChatColorConverter.java | 24 - .../utils/SHA1Generator.java | 19 +- .../utils/ServerVersionHelper.java | 29 +- .../utils/VersionChecker.java | 106 ---- 18 files changed, 452 insertions(+), 473 deletions(-) delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java delete mode 100644 src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java diff --git a/pom.xml b/pom.xml index 2a7e379..f7cb550 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ org.bstats - com.magmaguy.freeminecraftmodels + com.magmaguy.resourcepackmanager.bstats diff --git a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java index bc9c2ef..d3c44f7 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/ResourcePackManager.java @@ -22,7 +22,6 @@ import com.magmaguy.resourcepackmanager.config.compatibleplugins.CompatiblePluginConfigFields; import com.magmaguy.resourcepackmanager.playermanager.PlayerManager; import com.magmaguy.resourcepackmanager.thirdparty.ThirdPartyResourcePack; -import com.magmaguy.resourcepackmanager.utils.VersionChecker; import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; import org.bukkit.event.HandlerList; @@ -136,7 +135,6 @@ private void syncInitialization(PluginInitializationContext initializationContex if (DefaultConfig.isAutoHost()) { Bukkit.getPluginManager().registerEvents(new PlayerManager(), this); } - Bukkit.getPluginManager().registerEvents(new VersionChecker.VersionCheckerEvents(), this); Bukkit.getPluginManager().registerEvents(new ItemsAdderWarningListener(), this); initializationContext.step("Commands"); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java index dc1821c..7695329 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/api/ResourcePackManagerAPI.java @@ -29,7 +29,7 @@ public static void registerResourcePack(String pluginName, boolean zips, String reloadCommand) { thirdPartyResourcePackHashMap.put(pluginName, - new ThirdPartyResourcePack(pluginName, localPath, url, encrypts, distributes, zips, false, reloadCommand)); + new ThirdPartyResourcePack(pluginName, localPath, url, zips, false, reloadCommand)); } /** @@ -49,7 +49,7 @@ public static void registerLocalResourcePack(String pluginName, boolean zips, String reloadCommand) { thirdPartyResourcePackHashMap.put(pluginName, - new ThirdPartyResourcePack(pluginName, localPath, null, encrypts, distributes, zips, false, reloadCommand)); + new ThirdPartyResourcePack(pluginName, localPath, null, zips, false, reloadCommand)); } /** @@ -70,7 +70,7 @@ public static void registerRemoteResourcePack(String pluginName, boolean zips, String reloadCommand) { thirdPartyResourcePackHashMap.put(pluginName, - new ThirdPartyResourcePack(pluginName, null, url, encrypts, distributes, zips, false, reloadCommand)); + new ThirdPartyResourcePack(pluginName, null, url, zips, false, reloadCommand)); } /** diff --git a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java index 7bf490e..fc2996a 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/autohost/AutoHost.java @@ -92,7 +92,6 @@ private static CloseableHttpClient createHttpClient(int socketTimeoutSeconds) { } public static void sendResourcePack(Player player) { - Logger.info("Sending resource pack to " + player.getName() + ". rspUUID is null: " + (rspUUID == null) + ". done: " + done + ". firstUpload: " + firstUpload + "."); if (rspUUID == null || !done) return; Logger.info("Sending resource pack to " + player.getName()); @@ -193,11 +192,12 @@ public static void initializeLink() { } catch (Exception e) { // JSON parsing failed - validate if it looks like a UUID before using it String trimmedResponse = responseString.trim(); - if (isValidUUID(trimmedResponse)) { + try { + UUID.fromString(trimmedResponse); rspUUID = trimmedResponse; DataConfig.setRspUUID(rspUUID); Logger.info("Server initialized with UUID: " + rspUUID); - } else { + } catch (IllegalArgumentException ignored) { Logger.warn("Invalid response format from server: " + responseString); rspUUID = null; } @@ -219,22 +219,6 @@ public static void initializeLink() { } } - // Helper method to validate UUID format - private static boolean isValidUUID(String uuidString) { - if (uuidString == null || uuidString.isEmpty()) { - return false; - } - - // Check if it looks like a UUID (basic validation) - // UUIDs are typically 36 characters with dashes or 32 characters without - String cleanUuid = uuidString.replaceAll("-", ""); - if (cleanUuid.length() != 32) { - return false; - } - - // Check if it contains only valid hex characters - return cleanUuid.matches("[0-9a-fA-F]+"); - } public static void uploadFile() { Logger.info("Uploading resource!"); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java index 6b4475c..a1c7fcc 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/DataComplianceRequestCommand.java @@ -15,7 +15,7 @@ public DataComplianceRequestCommand() { super(List.of("data_compliance_request")); setDescription("Downloads a copy of all data associated to this server from the autohoster"); setPermission("resourcepackmanager.*"); - setUsage("/fmm data_compliance_request"); + setUsage("/rspm data_compliance_request"); } @Override diff --git a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java index 5feb568..c40163f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/commands/ReloadCommand.java @@ -24,8 +24,6 @@ public static void reloadPlugin(CommandSender sender) { @Override public void execute(CommandData commandData) { - ResourcePackManager.plugin.onDisable(); - ResourcePackManager.plugin.onEnable(); - Logger.sendMessage(commandData.getCommandSender(), "Reloaded the plugin!"); + reloadPlugin(commandData.getCommandSender()); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java index 3a8c9f4..a30cd90 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/BlueprintFolder.java @@ -20,8 +20,7 @@ public static void initialize() { Logger.info("Copying image"); File imageFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.png"); if (!imageFile.exists()) { - try { - InputStream inputStream = ResourcePackManager.plugin.getResource("pack.png"); + try (InputStream inputStream = ResourcePackManager.plugin.getResource("pack.png")) { Files.copy(inputStream, imageFile.toPath()); } catch (IOException e) { e.printStackTrace(); @@ -30,10 +29,8 @@ public static void initialize() { Logger.info("Copying mcmeta"); File mcmetaFile = new File(blueprintDirectory.getAbsolutePath() + File.separatorChar + "pack.mcmeta"); if (!mcmetaFile.exists()) { - try { - InputStream inputStream = ResourcePackManager.plugin.getResource("pack.mcmeta"); + try (InputStream inputStream = ResourcePackManager.plugin.getResource("pack.mcmeta")) { Files.copy(inputStream, mcmetaFile.toPath()); - inputStream.close(); } catch (IOException e) { e.printStackTrace(); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java index fc4d25e..0d13720 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/CompatiblePluginConfigFields.java @@ -17,24 +17,12 @@ public class CompatiblePluginConfigFields extends CustomConfigFields { private String localPath = null; @Getter @Setter - private boolean encrypts = false; - @Getter - @Setter - private boolean distributes = false; - @Getter - @Setter private boolean zips = true; @Getter @Setter private String reloadCommand; @Getter @Setter - private boolean resourcePackUpdated = false; - @Getter - @Setter - private String mixerFilename; - @Getter - @Setter private boolean cluster = false; public CompatiblePluginConfigFields(String filename, boolean isEnabled) { @@ -46,12 +34,8 @@ public void processConfigFields() { this.isEnabled = processBoolean("isEnabled", isEnabled, isEnabled, true); this.pluginName = processString("pluginName", pluginName, pluginName, true); this.url = processString("url", url, url, true); - this.encrypts = processBoolean("encrypts", encrypts, encrypts, true); - this.distributes = processBoolean("distributes", distributes, distributes, true); this.zips = processBoolean("zips", zips, zips, true); this.reloadCommand = processString("reloadCommand", reloadCommand, reloadCommand, true); - this.resourcePackUpdated = processBoolean("resourcePackUpdated", resourcePackUpdated, resourcePackUpdated, true); - this.mixerFilename = processString("mixerFilename", mixerFilename, mixerFilename, true); this.localPath = processString("localPath", localPath, localPath, true); this.cluster = processBoolean("cluster", cluster, cluster, true); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java index 0005adb..a25d3fe 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/BetterHUD.java @@ -9,7 +9,6 @@ public BetterHUD() { super("better_hud", true); setPluginName("BetterHUD"); setLocalPath("BetterHUD" + File.separatorChar + "build.zip"); - setEncrypts(false); setReloadCommand("betterhud reload"); } } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java index 01b18d0..5629222 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/ItemsAdder.java @@ -9,7 +9,5 @@ public ItemsAdder() { super("items_adder", true); setPluginName("ItemsAdder"); setLocalPath("ItemsAdder" + File.separatorChar + "output" + File.separatorChar + "generated.zip"); - setEncrypts(true); - setDistributes(true); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java index da73fa3..0b445ce 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/compatibleplugins/premade/VaneCore.java @@ -7,6 +7,5 @@ public VaneCore() { super("vane_core", true); setPluginName("vane-core"); setLocalPath("vane-resource-pack.zip"); - setDistributes(true); } } \ No newline at end of file diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 8c9052d..bcab991 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -19,10 +19,7 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; +import java.util.*; public class Mix { private static final String resourcePackName = "ResourcePackManager_RSP"; @@ -82,94 +79,51 @@ private static boolean initializeDefaultPluginFolders() { } } + /** + * Builds the ordered list of resource packs to merge, sorted by configured priority. + * Packs are copied in this order during merging — higher priority packs go first, + * and their files are preserved when lower priority packs collide with them. + */ private static void initializeThirdPartyResourcePacks() { orderedResourcePacks = new ArrayList<>(); + resourcePacks = new ArrayList<>(); + List priorityOrder = DefaultConfig.getPriorityOrder(); - // Use a mutable list so we can addAll() to it - List resourcePackManagers = new ArrayList<>(ThirdPartyResourcePack.thirdPartyResourcePacks.stream().toList()); - - // Add any API-registered packs - resourcePackManagers.addAll(ResourcePackManagerAPI.thirdPartyResourcePackHashMap.values()); + // Collect all enabled packs from both the static set and API registrations + List allPacks = new ArrayList<>(ThirdPartyResourcePack.thirdPartyResourcePacks); + allPacks.addAll(ResourcePackManagerAPI.thirdPartyResourcePackHashMap.values()); - // Filter enabled packs into a working list - List tempList = new ArrayList<>(); - resourcePackManagers.stream() - .filter(ThirdPartyResourcePack::isEnabled) - .forEach(tempList::add); + // Build a unified map of (file -> priority) for sorting + Map filePriorities = new HashMap<>(); + Set registeredFilenames = new HashSet<>(); - resourcePacks = new ArrayList<>(); - List customFiles = new ArrayList<>(); - List thirdPartyFiles = new ArrayList<>(); - - // Collect actual pack files from each enabled pack - tempList.forEach(rsp -> { - File mixerPack = rsp.getMixerResourcePack(); - if (mixerPack != null) { - thirdPartyFiles.add(mixerPack); - } - }); - - // Separate out truly custom files in the mixer folder - for (File file : mixerFolder.listFiles()) { - // Skip directories that aren't zip files - these are likely from cluster processing - if (file.isDirectory()) continue; - // Skip non-zip files - if (!file.getName().endsWith(".zip")) continue; - boolean isThirdParty = thirdPartyFiles.stream() - .anyMatch(tp -> tp.getName().equals(file.getName())); - if (!isThirdParty) { - customFiles.add(file); - } + for (ThirdPartyResourcePack pack : allPacks) { + if (!pack.isEnabled() || pack.getMixerResourcePack() == null) continue; + registeredFilenames.add(pack.getMixerResourcePack().getName()); + filePriorities.put(pack.getMixerResourcePack(), + pack.getPriority() >= 0 ? pack.getPriority() : Integer.MAX_VALUE); } - // Add in order of configured priority - List priorityOrder = DefaultConfig.getPriorityOrder(); - for (int i = 0; i < priorityOrder.size(); i++) { - boolean foundAtThisPriority = false; - - Iterator it = tempList.iterator(); - while (it.hasNext()) { - ThirdPartyResourcePack pack = it.next(); - if (pack.getPriority() == i) { - File f = pack.getMixerResourcePack(); - if (f != null) { - resourcePacks.add(f); - orderedResourcePacks.add(f.getName().replace(".zip", "")); - it.remove(); - foundAtThisPriority = true; - break; - } - } - } - - if (!foundAtThisPriority) { - Iterator fileIt = customFiles.iterator(); - while (fileIt.hasNext()) { - File custom = fileIt.next(); - int prio = priorityOrder.indexOf(custom.getName()); - if (prio == i) { - resourcePacks.add(custom); - orderedResourcePacks.add(custom.getName().replace(".zip", "")); - fileIt.remove(); - } - } + // Add custom zip files from the mixer folder (user-provided packs not tied to a plugin) + File[] mixerContents = mixerFolder.listFiles(); + if (mixerContents != null) { + for (File file : mixerContents) { + if (file.isDirectory() || !file.getName().endsWith(".zip")) continue; + if (registeredFilenames.contains(file.getName())) continue; + // Check priority list by both filename and name without .zip + int prio = priorityOrder.indexOf(file.getName()); + if (prio < 0) prio = priorityOrder.indexOf(file.getName().replace(".zip", "")); + filePriorities.put(file, prio >= 0 ? prio : Integer.MAX_VALUE); } } - // Any remaining third-party packs without explicit priority - tempList.stream() - .map(ThirdPartyResourcePack::getMixerResourcePack) - .filter(Objects::nonNull) - .forEach(packFile -> { - resourcePacks.add(packFile); - orderedResourcePacks.add(packFile.getName().replace(".zip", "")); + // Sort all packs by priority (lower index = higher priority = copied first = wins collisions) + filePriorities.entrySet().stream() + .sorted(Map.Entry.comparingByValue()) + .forEach(entry -> { + resourcePacks.add(entry.getKey()); + orderedResourcePacks.add(entry.getKey().getName().replace(".zip", "")); }); - - // Any leftover custom files without explicit priority - customFiles.forEach(customFile -> { - resourcePacks.add(customFile); - orderedResourcePacks.add(customFile.getName().replace(".zip", "")); - }); } private static void cloneToOutputAndUnzip() { @@ -183,7 +137,6 @@ private static void cloneToOutputAndUnzip() { // Pre-create the output directory to ensure getCanonicalPath() works correctly in the unzip security check if (!outputDir.exists()) outputDir.mkdirs(); ZipFile.unzip(resourcePack, outputDir); - stripDirectoryMetadata(outputDir); } catch (Exception e) { if (resourcePack == null) Logger.warn("Failed to extract resource pack! The file might be encrypted. This pack will be skipped."); @@ -215,7 +168,7 @@ private static void copyClusterDirectoriesToOutput() { if (!outputWrapper.exists()) outputWrapper.mkdir(); try { - recursivelyCopyDirectoryForCluster(file, new File(outputWrapper.getPath() + File.separatorChar + file.getName())); + recursivelyCopyDirectory(file, outputWrapper); // Track this for the merge process if (!orderedResourcePacks.contains("cluster_" + file.getName())) { orderedResourcePacks.add("cluster_" + file.getName()); @@ -227,37 +180,6 @@ private static void copyClusterDirectoriesToOutput() { } } - private static void recursivelyCopyDirectoryForCluster(File source, File target) { - if (source.isDirectory()) { - String sourceName = source.getName(); - - // Skip shaders folder entirely - if (sourceName.equals("shaders")) { - return; - } - - if (!target.exists()) target.mkdir(); - File[] files = source.listFiles(); - if (files != null) { - for (File child : files) { - recursivelyCopyDirectoryForCluster(child, new File(target.getPath() + File.separatorChar + child.getName())); - } - } - } else { - try { - // Check if target exists - if so, use resolveFileCollision to handle merging - if (target.exists()) { - resolveFileCollision(source, target); - } else { - target.getParentFile().mkdirs(); - Files.copy(source.toPath(), target.toPath()); - } - } catch (IOException e) { - Logger.warn("Failed to copy cluster file " + source.getPath()); - } - } - } - private static void createOutputDefaultElements() { //Clear old resource pack if (getOutputResourcePackFolder().exists()) { @@ -342,7 +264,7 @@ private static File getOutputResourcePackFolder() { return new File(getOutputFolder().getAbsolutePath() + File.separatorChar + resourcePackName); } - private static void recursivelyDeleteDirectory(File directory) { + public static void recursivelyDeleteDirectory(File directory) { if (directory.isDirectory()) { for (File file : directory.listFiles()) { recursivelyDeleteDirectory(file); @@ -361,15 +283,10 @@ private static void recursivelyDeleteDirectory(File directory) { } } - private static void recursivelyCopyDirectory(File source, File target) { + public static void recursivelyCopyDirectory(File source, File target) { if (source.isDirectory()) { String sourceName = source.getName(); - // Skip shaders folder entirely - ItemsAdder has incomplete shaders that break MC 1.21.4+ - if (sourceName.equals("shaders")) { - return; - } - target = new File(target.getAbsolutePath() + File.separatorChar + sourceName); target.mkdir(); for (File file : source.listFiles()) { @@ -392,91 +309,76 @@ private static void recursivelyCopyDirectory(File source, File target) { } public static void resolveFileCollision(File sourceFile, File targetFile) throws IOException { - if (!targetFile.getName().endsWith(".json")) { - //If the file isn't .json then it can't be merged, only replaced (such as with .png, shaders, etc). - //Higher priority pack overwrites the file - Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); - logCollision("Replaced: " + targetFile.getPath()); + // pack.mcmeta needs overlay entries merged from all packs + if (targetFile.getName().equals("pack.mcmeta")) { + mergePackMcmeta(sourceFile, targetFile); return; } - // Only merge JSON files that are designed to be merged (sounds.json, lang files) - // Model files, blockstates, etc. have fixed-size arrays that break when concatenated - if (!isMergeableJsonFile(targetFile)) { - Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); - logCollision("Replaced (non-mergeable JSON): " + targetFile.getPath()); + if (!targetFile.getName().endsWith(".json")) { + // Non-JSON: higher priority pack already placed this file, keep it + logCollision("Kept (higher priority): " + targetFile.getPath()); return; } - FileReader sourceFileReader = new FileReader(sourceFile); - FileReader targetFileReader = new FileReader(targetFile); - - JsonObject json1 = null; - // Read JSON files - try { - json1 = JsonParser.parseReader(sourceFileReader).getAsJsonObject(); - } catch (Exception e) { - Logger.warn("Malformed JSON: " + sourceFile.getAbsolutePath()); - try { - sourceFileReader.close(); - sourceFileReader = new FileReader(sourceFile); - JsonReader jsonReader = new JsonReader(sourceFileReader); - jsonReader.setStrictness(Strictness.LENIENT); - json1 = JsonParser.parseReader(jsonReader).getAsJsonObject(); - } catch (Exception ex) { - Logger.warn("Unreadable JSON: " + sourceFile.getAbsolutePath()); - } - } - JsonObject json2 = null; - try { - json2 = JsonParser.parseReader(targetFileReader).getAsJsonObject(); - } catch (Exception e) { - Logger.warn("Malformed JSON: " + targetFile.getAbsolutePath()); - try { - targetFileReader.close(); - targetFileReader = new FileReader(targetFile); - JsonReader jsonReader = new JsonReader(targetFileReader); - jsonReader.setStrictness(Strictness.LENIENT); - json2 = JsonParser.parseReader(jsonReader).getAsJsonObject(); - } catch (Exception ex) { - Logger.warn("Unreadable JSON: " + targetFile.getAbsolutePath()); - } + if (!isMergeableJsonFile(targetFile)) { + // Non-mergeable JSON (models, blockstates, etc.): higher priority version takes precedence + logCollision("Kept (higher priority, non-mergeable JSON): " + targetFile.getPath()); + return; } - sourceFileReader.close(); - targetFileReader.close(); + JsonObject json1 = readJsonFile(sourceFile); + JsonObject json2 = readJsonFile(targetFile); - // If either JSON is unreadable, keep whichever one is valid, or skip if (json1 == null && json2 == null) { Logger.warn("Both JSON files unreadable during merge, skipping: " + targetFile.getPath()); return; } - if (json1 == null) { - // Target is already in place, nothing to merge - return; - } + if (json1 == null) return; if (json2 == null) { - // Overwrite with the source since target is broken Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); logCollision("Replaced (unreadable target JSON): " + targetFile.getPath()); return; } - // Merge JSON objects - JsonObject mergedJson = mergeJsonObjects(json1, json2); - - FileWriter targetFileWriter = new FileWriter(targetFile); + // Route to format-specific merge where needed + JsonObject mergedJson; + if (isItemsFile(targetFile)) { + mergedJson = mergeItemsModels(json1, json2); + } else if (targetFile.getName().equals("sounds.json")) { + mergedJson = mergeSoundsJson(json1, json2); + } else { + mergedJson = mergeJsonObjects(json1, json2); + } - // Write merged JSON to a file - try (FileWriter file = targetFileWriter) { - new Gson().toJson(mergedJson, file); + // Post-process: sort overrides in legacy item model files by custom_model_data + if (isLegacyItemModel(targetFile) && mergedJson.has("overrides")) { + sortModelOverrides(mergedJson); } - targetFileWriter.close(); + try (FileWriter writer = new FileWriter(targetFile)) { + new Gson().toJson(mergedJson, writer); + } logCollision("Merged: " + targetFile.getPath()); } + private static JsonObject readJsonFile(File file) { + try (FileReader reader = new FileReader(file)) { + return JsonParser.parseReader(reader).getAsJsonObject(); + } catch (Exception e) { + Logger.warn("Malformed JSON: " + file.getAbsolutePath()); + try (FileReader reader = new FileReader(file); + JsonReader jsonReader = new JsonReader(reader)) { + jsonReader.setStrictness(Strictness.LENIENT); + return JsonParser.parseReader(jsonReader).getAsJsonObject(); + } catch (Exception ex) { + Logger.warn("Unreadable JSON: " + file.getAbsolutePath()); + return null; + } + } + } + /** * Checks if a JSON file is designed to be merged (content can be combined). * Files like sounds.json, lang files, atlases, fonts, and vanilla item model overrides can be merged. @@ -511,16 +413,15 @@ private static boolean isMergeableJsonFile(File file) { return true; } + // 1.21.4+ item model definitions should be merged (range_dispatch entries, select cases) + if (path.contains("/items/")) { + return true; + } + // All other JSON files (custom models, blockstates, equipment layers, etc.) should not be merged return false; } - private static void stripDirectoryMetadata(File file) throws IOException { - if (!file.isDirectory()) return; - for (File listFile : file.listFiles()) - stripDirectoryMetadata(listFile); - } - public static JsonObject mergeJsonObjects(JsonObject json1, JsonObject json2) { JsonObject mergedJson = new JsonObject(); @@ -589,5 +490,272 @@ private static void logCollision(String message) { } } + private static void mergePackMcmeta(File sourceFile, File targetFile) throws IOException { + JsonObject source = readJsonFile(sourceFile); + JsonObject target = readJsonFile(targetFile); + + if (source == null) return; + if (target == null) { + Files.copy(sourceFile.toPath(), targetFile.toPath(), java.nio.file.StandardCopyOption.REPLACE_EXISTING); + return; + } + + // Take the highest pack_format + if (source.has("pack") && target.has("pack")) { + JsonObject sourcePack = source.getAsJsonObject("pack"); + JsonObject targetPack = target.getAsJsonObject("pack"); + if (sourcePack.has("pack_format") && targetPack.has("pack_format")) { + int sourceFormat = sourcePack.get("pack_format").getAsInt(); + int targetFormat = targetPack.get("pack_format").getAsInt(); + targetPack.addProperty("pack_format", Math.max(sourceFormat, targetFormat)); + } + } + + // Merge supported_formats to widest range + if (source.has("supported_formats") && target.has("supported_formats")) { + JsonArray sourceFormats = source.getAsJsonArray("supported_formats"); + JsonArray targetFormats = target.getAsJsonArray("supported_formats"); + if (sourceFormats.size() >= 2 && targetFormats.size() >= 2) { + int min = Math.min(sourceFormats.get(0).getAsInt(), targetFormats.get(0).getAsInt()); + int max = Math.max(sourceFormats.get(1).getAsInt(), targetFormats.get(1).getAsInt()); + JsonArray merged = new JsonArray(); + merged.add(min); + merged.add(max); + target.add("supported_formats", merged); + } + } else if (source.has("supported_formats") && !target.has("supported_formats")) { + target.add("supported_formats", source.get("supported_formats")); + } + + // Merge overlay entries from both packs + JsonArray mergedEntries = new JsonArray(); + + if (target.has("overlays")) { + JsonObject targetOverlays = target.getAsJsonObject("overlays"); + if (targetOverlays.has("entries")) { + mergedEntries.addAll(targetOverlays.getAsJsonArray("entries")); + } + } + if (source.has("overlays")) { + JsonObject sourceOverlays = source.getAsJsonObject("overlays"); + if (sourceOverlays.has("entries")) { + Set existingDirs = new HashSet<>(); + for (JsonElement e : mergedEntries) { + if (e.isJsonObject() && e.getAsJsonObject().has("directory")) { + existingDirs.add(e.getAsJsonObject().get("directory").getAsString()); + } + } + for (JsonElement e : sourceOverlays.getAsJsonArray("entries")) { + if (e.isJsonObject()) { + String dir = e.getAsJsonObject().has("directory") + ? e.getAsJsonObject().get("directory").getAsString() : ""; + if (!existingDirs.contains(dir)) { + mergedEntries.add(e); + } + } + } + } + } + + if (mergedEntries.size() > 0) { + JsonObject overlays = new JsonObject(); + overlays.add("entries", mergedEntries); + target.add("overlays", overlays); + } + + try (FileWriter writer = new FileWriter(targetFile)) { + new Gson().toJson(target, writer); + } + + logCollision("Merged pack.mcmeta: " + targetFile.getPath()); + } + + private static boolean isLegacyItemModel(File file) { + return file.getPath().replace("\\", "/").contains("/minecraft/models/item/"); + } + + private static boolean isItemsFile(File file) { + String path = file.getPath().replace("\\", "/"); + return path.contains("/items/") && !path.contains("/models/item/"); + } + + private static void sortModelOverrides(JsonObject modelJson) { + JsonArray overrides = modelJson.getAsJsonArray("overrides"); + if (overrides == null || overrides.size() <= 1) return; + + List sorted = new ArrayList<>(); + for (JsonElement e : overrides) sorted.add(e); + + sorted.sort((a, b) -> { + int cmdA = getCustomModelData(a); + int cmdB = getCustomModelData(b); + return Integer.compare(cmdA, cmdB); + }); + + JsonArray sortedArray = new JsonArray(); + for (JsonElement e : sorted) sortedArray.add(e); + modelJson.add("overrides", sortedArray); + } + + private static int getCustomModelData(JsonElement override) { + try { + return override.getAsJsonObject() + .getAsJsonObject("predicate") + .get("custom_model_data").getAsInt(); + } catch (Exception e) { + return Integer.MAX_VALUE; + } + } + + private static JsonObject mergeItemsModels(JsonObject source, JsonObject target) { + if (!source.has("model") || !target.has("model")) { + return mergeJsonObjects(source, target); + } + + JsonObject sourceModel = source.getAsJsonObject("model"); + JsonObject targetModel = target.getAsJsonObject("model"); + + String sourceType = sourceModel.has("type") ? sourceModel.get("type").getAsString().replace("minecraft:", "") : ""; + String targetType = targetModel.has("type") ? targetModel.get("type").getAsString().replace("minecraft:", "") : ""; + + if (sourceType.equals("range_dispatch") && targetType.equals("range_dispatch")) { + mergeRangeDispatchEntries(sourceModel, targetModel); + target.add("model", targetModel); + for (String key : source.keySet()) { + if (!key.equals("model") && !target.has(key)) { + target.add(key, source.get(key)); + } + } + return target; + } + + if (sourceType.equals("select") && targetType.equals("select")) { + String sourceProp = sourceModel.has("property") ? sourceModel.get("property").getAsString() : ""; + String targetProp = targetModel.has("property") ? targetModel.get("property").getAsString() : ""; + if (sourceProp.equals(targetProp)) { + mergeSelectCases(sourceModel, targetModel); + target.add("model", targetModel); + for (String key : source.keySet()) { + if (!key.equals("model") && !target.has(key)) { + target.add(key, source.get(key)); + } + } + return target; + } + } + + // Incompatible types: higher priority (target) wins + return mergeJsonObjects(source, target); + } + + private static void mergeRangeDispatchEntries(JsonObject sourceModel, JsonObject targetModel) { + JsonArray sourceEntries = sourceModel.has("entries") ? sourceModel.getAsJsonArray("entries") : new JsonArray(); + JsonArray targetEntries = targetModel.has("entries") ? targetModel.getAsJsonArray("entries") : new JsonArray(); + + // Collect all entries, target (higher priority) wins on threshold conflicts + Map entryMap = new LinkedHashMap<>(); + for (JsonElement e : sourceEntries) { + double threshold = e.getAsJsonObject().has("threshold") + ? e.getAsJsonObject().get("threshold").getAsDouble() : 0; + entryMap.put(threshold, e); + } + for (JsonElement e : targetEntries) { + double threshold = e.getAsJsonObject().has("threshold") + ? e.getAsJsonObject().get("threshold").getAsDouble() : 0; + entryMap.put(threshold, e); + } + + List> sorted = new ArrayList<>(entryMap.entrySet()); + sorted.sort(Comparator.comparingDouble(Map.Entry::getKey)); + + JsonArray merged = new JsonArray(); + for (Map.Entry entry : sorted) { + merged.add(entry.getValue()); + } + + targetModel.add("entries", merged); + } + + private static void mergeSelectCases(JsonObject sourceModel, JsonObject targetModel) { + JsonArray sourceCases = sourceModel.has("cases") ? sourceModel.getAsJsonArray("cases") : new JsonArray(); + JsonArray targetCases = targetModel.has("cases") ? targetModel.getAsJsonArray("cases") : new JsonArray(); + + Map caseMap = new LinkedHashMap<>(); + for (JsonElement e : sourceCases) { + String when = e.getAsJsonObject().has("when") + ? e.getAsJsonObject().get("when").getAsString() : ""; + caseMap.put(when, e); + } + for (JsonElement e : targetCases) { + String when = e.getAsJsonObject().has("when") + ? e.getAsJsonObject().get("when").getAsString() : ""; + caseMap.put(when, e); + } + + JsonArray merged = new JsonArray(); + for (JsonElement e : caseMap.values()) { + merged.add(e); + } + + targetModel.add("cases", merged); + } + + private static JsonObject mergeSoundsJson(JsonObject source, JsonObject target) { + JsonObject merged = new JsonObject(); + + // Start with all source (lower priority) events + for (String key : source.keySet()) { + merged.add(key, source.get(key)); + } + + // Apply target (higher priority) events + for (String key : target.keySet()) { + JsonElement targetEvent = target.get(key); + if (!merged.has(key)) { + merged.add(key, targetEvent); + continue; + } + + if (targetEvent.isJsonObject()) { + JsonObject targetObj = targetEvent.getAsJsonObject(); + boolean replace = targetObj.has("replace") && targetObj.get("replace").getAsBoolean(); + + if (replace) { + merged.add(key, targetEvent); + } else { + JsonObject sourceObj = merged.get(key).isJsonObject() + ? merged.get(key).getAsJsonObject() : new JsonObject(); + JsonObject mergedEvent = new JsonObject(); + + JsonArray mergedSounds = new JsonArray(); + if (sourceObj.has("sounds")) { + mergedSounds.addAll(sourceObj.getAsJsonArray("sounds")); + } + if (targetObj.has("sounds")) { + mergedSounds.addAll(targetObj.getAsJsonArray("sounds")); + } + mergedEvent.add("sounds", mergedSounds); + + for (String prop : sourceObj.keySet()) { + if (!prop.equals("sounds") && !prop.equals("replace")) { + mergedEvent.add(prop, sourceObj.get(prop)); + } + } + for (String prop : targetObj.keySet()) { + if (!prop.equals("sounds") && !prop.equals("replace")) { + mergedEvent.add(prop, targetObj.get(prop)); + } + } + + merged.add(key, mergedEvent); + } + } else { + merged.add(key, targetEvent); + } + } + + return merged; + } + } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java deleted file mode 100644 index a3a3e38..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/GeneratorInterface.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.magmaguy.resourcepackmanager.thirdparty; - -public interface GeneratorInterface { - void decrypt(); - - void unpublish(); - - void cloneResourcePackFile(); - - void reload(); -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 7066b1f..0685215 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -28,7 +28,7 @@ import java.util.HashSet; import java.util.Objects; -public class ThirdPartyResourcePack implements GeneratorInterface { +public class ThirdPartyResourcePack { public static HashSet thirdPartyResourcePacks = new HashSet<>(); @Getter @@ -39,8 +39,6 @@ public class ThirdPartyResourcePack implements GeneratorInterface { private final String url; @Getter private File file = null; - private boolean encrypts; - private boolean distributes; private boolean zips; private boolean cluster; private String reloadCommand; @@ -59,7 +57,7 @@ public class ThirdPartyResourcePack implements GeneratorInterface { @Setter private boolean stableResourcePackSent = false; - public ThirdPartyResourcePack(String pluginName, String localPath, String url, boolean encrypts, boolean distributes, boolean zips, boolean cluster, String reloadCommand) { + public ThirdPartyResourcePack(String pluginName, String localPath, String url, boolean zips, boolean cluster, String reloadCommand) { this.pluginName = pluginName; this.mixerFilename = pluginName + "_resource_pack.zip"; this.url = url; @@ -81,8 +79,6 @@ public ThirdPartyResourcePack(String pluginName, String localPath, String url, b return; } - this.encrypts = encrypts; - this.distributes = distributes; this.reloadCommand = reloadCommand; this.zips = zips; this.cluster = cluster; @@ -118,13 +114,55 @@ public ThirdPartyResourcePack(String pluginName, String localPath, String url, b private static BukkitTask resourcePackChangeWatcher = null; + /** + * Checks whether a monitored plugin has finished its Magmacore initialization. + * Uses System properties published by each plugin's shaded Magmacore instance. + */ + private static boolean isPluginInitialized(String pluginName) { + String state = System.getProperty("magmacore.init." + pluginName); + // If no state is published, the plugin doesn't use Magmacore — treat as ready + if (state == null) return true; + return "INITIALIZED".equals(state); + } + public static void startResourcePackChangeWatchdog() { if (resourcePackChangeWatcher != null) { resourcePackChangeWatcher.cancel(); } resourcePackChangeWatcher = new BukkitRunnable() { + private boolean allPluginsReady = false; + @Override public void run() { + // Phase 1: Wait for all monitored plugins to finish initializing + if (!allPluginsReady) { + for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { + if (!thirdPartyResourcePack.isEnabled) continue; + if (!isPluginInitialized(thirdPartyResourcePack.pluginName)) { + return; // Still waiting — check again next tick + } + } + allPluginsReady = true; + Logger.info("All monitored plugins are initialized. Starting resource pack stability checks."); + } + + // Check if any monitored plugin has gone back to initializing (reload detected) + for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { + if (!thirdPartyResourcePack.isEnabled) continue; + if (!isPluginInitialized(thirdPartyResourcePack.pluginName)) { + Logger.info("Plugin " + thirdPartyResourcePack.pluginName + " is reloading. Pausing resource pack processing."); + allPluginsReady = false; + // Reset stability for all packs since a reload may change them + for (ThirdPartyResourcePack pack : thirdPartyResourcePacks) { + pack.consideredStable = false; + pack.stableResourcePackSent = false; + pack.ticksWithoutChange = 0; + } + return; + } + } + + // Phase 2: SHA1 stability checks (same logic, but no arbitrary extra delay) boolean readyToSend = true; boolean stableAlreadySent = true; for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { @@ -143,7 +181,6 @@ public void run() { if (thirdPartyResourcePack.consideredStable) continue; thirdPartyResourcePack.ticksWithoutChange++; if (thirdPartyResourcePack.ticksWithoutChange == 3) { - //If nothing has changed for 3 seconds, then it should be considered to be stable thirdPartyResourcePack.consideredStable = true; Logger.info("Resource pack for " + thirdPartyResourcePack.pluginName + " has not changed for 3 seconds, considering it stable."); } @@ -152,14 +189,8 @@ public void run() { if (!stableAlreadySent && readyToSend) { notifyResourcePackSending(); tagAsResourcePackSent(); - // Delay 1 second before actually mixing and sending - new BukkitRunnable() { - @Override - public void run() { - Logger.info("Sending resource pack now."); - Mix.mixResourcePacks(); - } - }.runTaskLaterAsynchronously(ResourcePackManager.plugin, 20L); + Logger.info("Sending resource pack now."); + Bukkit.getScheduler().runTaskAsynchronously(ResourcePackManager.plugin, Mix::mixResourcePacks); } } }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 20, 20); @@ -172,8 +203,8 @@ public static void tagAsResourcePackSent(){ } private static void notifyResourcePackSending() { - String message = "&eAll resource packs are stable. Resource pack will be sent in 1 second."; - Logger.info("All resource packs are stable. Resource pack will be sent in 1 second."); + String message = "&eAll resource packs are stable. Mixing and sending now."; + Logger.info("All resource packs are stable. Mixing and sending now."); // Notify all online OPs for (org.bukkit.entity.Player player : Bukkit.getOnlinePlayers()) { if (player.isOp()) { @@ -195,8 +226,6 @@ public static void initializeThirdPartyResourcePack(CompatiblePluginConfigFields compatiblePluginConfigFields.getPluginName(), compatiblePluginConfigFields.getLocalPath(), compatiblePluginConfigFields.getUrl(), - compatiblePluginConfigFields.isEncrypts(), - compatiblePluginConfigFields.isDistributes(), compatiblePluginConfigFields.isZips(), compatiblePluginConfigFields.isCluster(), compatiblePluginConfigFields.getReloadCommand()); @@ -225,6 +254,11 @@ private void processCluster() { File mixerDir = new File(ResourcePackManager.plugin.getDataFolder().toString() + File.separatorChar + "mixer"); if (!mixerDir.exists()) mixerDir.mkdir(); + // Merge all cluster sub-packs into a temporary directory + File clusterTemp = new File(mixerDir.getPath() + File.separatorChar + pluginName + "_cluster_temp"); + if (clusterTemp.exists()) Mix.recursivelyDeleteDirectory(clusterTemp); + clusterTemp.mkdir(); + Logger.info("Processing cluster for " + pluginName + " with " + clusterContents.length + " resource packs"); for (File resourcePackFolder : clusterContents) { @@ -233,52 +267,34 @@ private void processCluster() { continue; } - // Each folder in the cluster is a resource pack - copy its contents to mixer File[] resourcePackContents = resourcePackFolder.listFiles(); if (resourcePackContents == null) continue; for (File contentFolder : resourcePackContents) { if (!contentFolder.isDirectory()) continue; - File targetFolder = new File(mixerDir.getPath() + File.separatorChar + contentFolder.getName()); try { - recursivelyCloneDirectory(contentFolder, targetFolder); - Logger.info("Copied " + contentFolder.getName() + " from " + resourcePackFolder.getName() + " to mixer folder"); + Mix.recursivelyCopyDirectory(contentFolder, clusterTemp); } catch (Exception e) { - Logger.warn("Failed to copy " + contentFolder.getPath() + " to mixer folder"); + Logger.warn("Failed to copy " + contentFolder.getPath() + " to cluster temp"); e.printStackTrace(); } } } - // For clusters, we don't track a single mixerResourcePack since they get copied directly - // Set isEnabled to false so it doesn't try to process further as a single pack - isEnabled = false; - Logger.info("Finished processing cluster for " + pluginName); - } - - private void recursivelyCloneDirectory(File source, File target) { - if (source.isDirectory()) { - if (!target.exists()) target.mkdir(); - File[] files = source.listFiles(); - if (files != null) { - for (File child : files) { - recursivelyCloneDirectory(child, new File(target.getPath() + File.separatorChar + child.getName())); - } - } + // Zip the merged cluster content so it participates in priority ordering like any other pack + File targetZip = getTarget().toFile(); + if (ZipFile.zip(clusterTemp, targetZip.getAbsolutePath())) { + mixerResourcePack = targetZip; + Logger.info("Created merged cluster pack: " + targetZip.getAbsolutePath()); } else { - try { - // Check if target exists - if so, use Mix.resolveFileCollision to handle merging - if (target.exists()) { - Mix.resolveFileCollision(source, target); - } else { - target.getParentFile().mkdirs(); - Files.copy(source.toPath(), target.toPath()); - } - } catch (Exception e) { - Logger.warn("Failed to copy file " + source.getPath() + " to " + target.getPath()); - } + Logger.warn("Failed to zip merged cluster for " + pluginName); + isEnabled = false; } + + // Clean up temp directory + Mix.recursivelyDeleteDirectory(clusterTemp); + Logger.info("Finished processing cluster for " + pluginName); } private boolean processLocal(String localPath) { @@ -307,9 +323,6 @@ public void process() { } } - if (encrypts) decrypt(); - if (distributes) unpublish(); - cloneResourcePackFile(); } @@ -322,17 +335,6 @@ private String getSHA1(File file) { } } - @Override - public void decrypt() { - //Implementation depends on extended classes - } - - @Override - public void unpublish() { - //Implementation depends on extended classes - } - - @Override public void cloneResourcePackFile() { if (localPath != null) cloneLocalRSP(); else cloneRemoteRSP(); @@ -409,7 +411,6 @@ public void cloneRemoteRSP() { } } - @Override public void reload() { Bukkit.dispatchCommand(Bukkit.getConsoleSender(), reloadCommand); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java deleted file mode 100644 index cfcae6d..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/ChatColorConverter.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.magmaguy.resourcepackmanager.utils; - -import org.bukkit.ChatColor; - -import java.util.ArrayList; -import java.util.List; - -public class ChatColorConverter { - private ChatColorConverter() { - } - - public static String convert(String string) { - if (string == null) return ""; - return ChatColor.translateAlternateColorCodes('&', string); - } - - public static List convert(List list) { - if (list == null) return new ArrayList<>(); - List convertedList = new ArrayList<>(); - for (Object value : list) - convertedList.add(convert(value + "")); - return convertedList; - } -} diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java index 5cd8649..d6b21a0 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/SHA1Generator.java @@ -16,10 +16,8 @@ public static String sha1CodeString(File file) throws IOException, NoSuchAlgorit try (FileInputStream fileInputStream = new FileInputStream(file); DigestInputStream digestInputStream = new DigestInputStream(fileInputStream, MessageDigest.getInstance("SHA-1"))) { byte[] bytes = new byte[1024]; - MessageDigest digest = null; - //read all file content - while (digestInputStream.read(bytes) > 0) digest = digestInputStream.getMessageDigest(); - byte[] resultByteArry = digest.digest(); + while (digestInputStream.read(bytes) > 0) ; + byte[] resultByteArry = digestInputStream.getMessageDigest().digest(); return bytesToHexString(resultByteArry); } } @@ -37,13 +35,12 @@ public static String sha1CodeString(String text) { } public static byte[] sha1CodeByteArray(File file) throws IOException, NoSuchAlgorithmException { - FileInputStream fileInputStream = new FileInputStream(file); - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - DigestInputStream digestInputStream = new DigestInputStream(fileInputStream, digest); - byte[] bytes = new byte[1024]; - // read all file content - while (digestInputStream.read(bytes) > 0) digest = digestInputStream.getMessageDigest(); - return digest.digest(); + try (FileInputStream fileInputStream = new FileInputStream(file); + DigestInputStream digestInputStream = new DigestInputStream(fileInputStream, MessageDigest.getInstance("SHA-1"))) { + byte[] bytes = new byte[1024]; + while (digestInputStream.read(bytes) > 0) ; + return digestInputStream.getMessageDigest().digest(); + } } public static String bytesToHexString(byte[] bytes) { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java index abaceda..c42a214 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/utils/ServerVersionHelper.java @@ -8,25 +8,22 @@ public class ServerVersionHelper { private static final boolean supportsMultipleResourcePacks; static { - String version = Bukkit.getBukkitVersion(); // e.g., "1.20.4-R0.1-SNAPSHOT" + String version = Bukkit.getBukkitVersion(); // e.g., "1.20.4-R0.1-SNAPSHOT" or "26.1-R0.1-SNAPSHOT" String[] parts = version.split("-")[0].split("\\."); - majorVersion = Integer.parseInt(parts[0]); - minorVersion = Integer.parseInt(parts[1]); - // addResourcePack was added in 1.20.3 - supportsMultipleResourcePacks = majorVersion > 1 || (majorVersion == 1 && minorVersion >= 20 && getRevision(version) >= 3); - } - - private static int getRevision(String version) { - String[] parts = version.split("-")[0].split("\\."); - if (parts.length >= 3) { - try { - return Integer.parseInt(parts[2]); - } catch (NumberFormatException e) { - return 0; - } + if (parts[0].equals("1")) { + // Legacy format: 1.MAJOR.MINOR + majorVersion = Integer.parseInt(parts[1]); + minorVersion = parts.length >= 3 ? Integer.parseInt(parts[2]) : 0; + } else { + // New year.drop format: MAJOR.MINOR (e.g. 26.1) + majorVersion = Integer.parseInt(parts[0]); + minorVersion = parts.length >= 2 ? Integer.parseInt(parts[1]) : 0; } - return 0; + + // addResourcePack was added in 1.20.3 (majorVersion=20, minorVersion>=3) + // All versions >= 26 support it + supportsMultipleResourcePacks = majorVersion > 20 || (majorVersion == 20 && minorVersion >= 3); } public static boolean supportsMultipleResourcePacks() { diff --git a/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java b/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java deleted file mode 100644 index 5341e96..0000000 --- a/src/main/java/com/magmaguy/resourcepackmanager/utils/VersionChecker.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.magmaguy.resourcepackmanager.utils; - -import com.magmaguy.magmacore.util.Logger; -import com.magmaguy.resourcepackmanager.ResourcePackManager; -import org.bukkit.event.EventHandler; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.scheduler.BukkitRunnable; - -import java.io.IOException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Scanner; - -public class VersionChecker { - private static boolean pluginIsUpToDate = true; - - private VersionChecker() { - } - - public static void checkPluginVersion() { - new BukkitRunnable() { - @Override - public void run() { - String currentVersion = ResourcePackManager.plugin.getDescription().getVersion(); - boolean snapshot = false; - if (currentVersion.contains("SNAPSHOT")) { - snapshot = true; - currentVersion = currentVersion.split("-")[0]; - } - String publicVersion = ""; - - try { - Logger.info("Latest public release is " + VersionChecker.readStringFromURL("https://api.spigotmc.org/legacy/update.php?resource=118574")); - Logger.info("Your version is " + ResourcePackManager.plugin.getDescription().getVersion()); - publicVersion = VersionChecker.readStringFromURL("https://api.spigotmc.org/legacy/update.php?resource=118574"); - } catch (IOException e) { - Logger.warn("Couldn't check latest version"); - return; - } - - if (Double.parseDouble(currentVersion.split("\\.")[0]) < Double.parseDouble(publicVersion.split("\\.")[0])) { - outOfDateHandler(); - return; - } - - if (Double.parseDouble(currentVersion.split("\\.")[0]) == Double.parseDouble(publicVersion.split("\\.")[0])) { - - if (Double.parseDouble(currentVersion.split("\\.")[1]) < Double.parseDouble(publicVersion.split("\\.")[1])) { - outOfDateHandler(); - return; - } - - if (Double.parseDouble(currentVersion.split("\\.")[1]) == Double.parseDouble(publicVersion.split("\\.")[1])) { - if (Double.parseDouble(currentVersion.split("\\.")[2]) < Double.parseDouble(publicVersion.split("\\.")[2])) { - outOfDateHandler(); - return; - } - } - } - - if (!snapshot) - Logger.info("You are running the latest version!"); - else - Logger.info("You are running a snapshot version! You can check for updates in the #releases channel on the Nightbreak Discord!"); - - pluginIsUpToDate = true; - } - }.runTaskAsynchronously(ResourcePackManager.plugin); - } - - private static String readStringFromURL(String url) throws IOException { - - try (Scanner scanner = new Scanner(new URL(url).openStream(), - StandardCharsets.UTF_8)) { - scanner.useDelimiter("\\A"); - return scanner.hasNext() ? scanner.next() : ""; - } - - } - - private static void outOfDateHandler() { - Logger.warn("A newer version of this plugin is available for download!"); - pluginIsUpToDate = false; - } - - public static class VersionCheckerEvents implements Listener { - @EventHandler - public void onPlayerLogin(PlayerJoinEvent event) { - - if (!event.getPlayer().hasPermission("elitemobs.versionnotification")) return; - - new BukkitRunnable() { - @Override - public void run() { - if (!event.getPlayer().isOnline()) return; - if (!pluginIsUpToDate) - event.getPlayer().sendMessage(com.magmaguy.magmacore.util.ChatColorConverter.convert("&cYour version of ResourcePackManager is outdated." + - " &aYou can download the latest version from &3&n&ohttps://www.spigotmc.org/resources/resource-pack-manager.118574/")); - } - }.runTaskLater(ResourcePackManager.plugin, 20L * 3); - - } - } - -} From 862515d1b5826457d0f78e506c0a9eea60e7dae5 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Thu, 26 Mar 2026 22:18:46 +0000 Subject: [PATCH 28/33] fix: skip SHA1 watchdog check for cluster packs (source is a directory, not hashable) Co-Authored-By: Claude Opus 4.6 (1M context) --- .../resourcepackmanager/thirdparty/ThirdPartyResourcePack.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 0685215..4374d66 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -167,6 +167,8 @@ public void run() { boolean stableAlreadySent = true; for (ThirdPartyResourcePack thirdPartyResourcePack : thirdPartyResourcePacks) { if (!thirdPartyResourcePack.isEnabled || thirdPartyResourcePack.file == null) continue; + // Cluster packs have a directory as their source file — can't hash, skip SHA1 check + if (thirdPartyResourcePack.cluster) continue; if (!Objects.equals(thirdPartyResourcePack.getSHA1(thirdPartyResourcePack.file), thirdPartyResourcePack.SHA1)) { thirdPartyResourcePack.ticksWithoutChange = 0; thirdPartyResourcePack.SHA1 = thirdPartyResourcePack.getSHA1(thirdPartyResourcePack.file); From bbc7c023c2344dfba2bd854ed1f699b0a93d2250 Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sun, 29 Mar 2026 15:50:53 +0100 Subject: [PATCH 29/33] fix: preserve non-standard top-level keys in pack.mcmeta during merge Keys like "sodium" (with ignored_shaders) from source packs were silently dropped because mergePackMcmeta only handled pack, supported_formats, and overlays explicitly. Now copies any unhandled top-level keys from source to target if not already present. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/com/magmaguy/resourcepackmanager/mixer/Mix.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index bcab991..82ac714 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -563,6 +563,13 @@ private static void mergePackMcmeta(File sourceFile, File targetFile) throws IOE target.add("overlays", overlays); } + // Preserve any non-standard top-level keys from source (e.g. "sodium" with ignored_shaders) + for (String key : source.keySet()) { + if (!target.has(key)) { + target.add(key, source.get(key)); + } + } + try (FileWriter writer = new FileWriter(targetFile)) { new Gson().toJson(target, writer); } From c87376ea9b6ec74ed1175534e69793b4e311563a Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sun, 29 Mar 2026 18:59:35 +0100 Subject: [PATCH 30/33] =?UTF-8?q?=20=20Resource=20Pack=20Manager=201.7.6:?= =?UTF-8?q?=20=20=20-=20[Fix]=20Fixed=20non-standard=20top-level=20keys=20?= =?UTF-8?q?in=20pack.mcmeta=20(e.g.=20Sodium's=20ignored=5Fshaders)=20bein?= =?UTF-8?q?g=20silently=20dropped=20during=20resource=20pack=20merging.=20?= =?UTF-8?q?=20=20-=20[Fix]=20Fixed=20overlay=20entries=20missing=20min=5Ff?= =?UTF-8?q?ormat/max=5Fformat=20fields=20causing=20"resource=20reload=20fa?= =?UTF-8?q?iled"=20on=201.21.9+=20clients=20=E2=80=94=20RSPM=20now=20norma?= =?UTF-8?q?lizes=20overlay=20entries=20from=20third-party=20packs=20that?= =?UTF-8?q?=20haven't=20updated=20to=20the=20new=20format.=20=20=20-=20[Fi?= =?UTF-8?q?x]=20Fixed=20NoSuchFileException=20during=20resource=20pack=20m?= =?UTF-8?q?ixing=20by=20creating=20parent=20directories.=20=20=20-=20[Fix]?= =?UTF-8?q?=20Fixed=20SHA1=20watchdog=20check=20crashing=20for=20cluster?= =?UTF-8?q?=20packs=20where=20the=20source=20is=20a=20directory.=20=20=20-?= =?UTF-8?q?=20[Fix]=20Fixed=20file=20copy=20failures=20on=20Windows=20caus?= =?UTF-8?q?ed=20by=20file=20locks=20=E2=80=94=20now=20retries=20with=20Buk?= =?UTF-8?q?kit=20scheduler.=20=20=20-=20[Fix]=20Fixed=20major=20merge=20lo?= =?UTF-8?q?gic=20issues=20with=20resource=20pack=20mixing,=20including=20i?= =?UTF-8?q?mproved=20handling=20of=201.21.4+=20item=20model=20definitions,?= =?UTF-8?q?=20sounds.json=20merging,=20atlas=20merging,=20and=20legacy=20m?= =?UTF-8?q?odel=20overrides.=20=20=20-=20[New]=20Migrated=20to=20Nightbrea?= =?UTF-8?q?kPluginBootstrap=20and=20Magmacore=202.0.0-SNAPSHOT.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 2 +- .../resourcepackmanager/mixer/Mix.java | 46 +++++++++++++++++++ src/main/resources/plugin.yml | 2 +- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index f7cb550..4e69ce3 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.magmaguy ResourcePackManager - 1.7.5 + 1.7.6 17 diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index 82ac714..b9e373f 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -558,6 +558,17 @@ private static void mergePackMcmeta(File sourceFile, File targetFile) throws IOE } if (mergedEntries.size() > 0) { + // Defensive normalization: ensure overlay entries have min_format/max_format fields. + // Starting with resource pack format 65 (Minecraft 1.21.9+), overlay entries MUST include + // min_format and max_format as separate fields — the old "formats" field alone is no longer + // sufficient. If an overlay's format range covers 65+, the client rejects entries missing + // these fields with: "declares support for version newer than 64, but is missing mandatory + // fields min_format and max_format". + // This is NOT an RSPM bug — the source packs (e.g. ModelEngine) are generating overlay entries + // without these fields. Ideally those packs should fix their own pack.mcmeta output. + // We patch it here because RSPM gets the bug reports when the merged pack fails to load. + normalizeOverlayEntries(mergedEntries); + JsonObject overlays = new JsonObject(); overlays.add("entries", mergedEntries); target.add("overlays", overlays); @@ -577,6 +588,41 @@ private static void mergePackMcmeta(File sourceFile, File targetFile) throws IOE logCollision("Merged pack.mcmeta: " + targetFile.getPath()); } + /** + * Patches overlay entries that are missing min_format/max_format fields. + * See comment at call site for full rationale — this works around third-party packs + * that haven't updated their pack.mcmeta to the 1.21.9+ overlay format. + */ + private static void normalizeOverlayEntries(JsonArray entries) { + for (JsonElement element : entries) { + if (!element.isJsonObject()) continue; + JsonObject entry = element.getAsJsonObject(); + if (entry.has("min_format") && entry.has("max_format")) continue; + if (!entry.has("formats")) continue; + + int min, max; + JsonElement formats = entry.get("formats"); + if (formats.isJsonArray()) { + JsonArray arr = formats.getAsJsonArray(); + if (arr.size() < 2) continue; + min = arr.get(0).getAsInt(); + max = arr.get(1).getAsInt(); + } else if (formats.isJsonObject()) { + JsonObject obj = formats.getAsJsonObject(); + if (!obj.has("min_inclusive") || !obj.has("max_inclusive")) continue; + min = obj.get("min_inclusive").getAsInt(); + max = obj.get("max_inclusive").getAsInt(); + } else if (formats.isJsonPrimitive()) { + min = max = formats.getAsInt(); + } else { + continue; + } + + if (!entry.has("min_format")) entry.addProperty("min_format", min); + if (!entry.has("max_format")) entry.addProperty("max_format", max); + } + } + private static boolean isLegacyItemModel(File file) { return file.getPath().replace("\\", "/").contains("/minecraft/models/item/"); } diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 853b21f..e12c4aa 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,6 +1,6 @@ name: ResourcePackManager main: com.magmaguy.resourcepackmanager.ResourcePackManager -version: 1.7.5 +version: 1.7.6 api-version: 1.14 author: MagmaGuy softdepend: From 033ced1423b80ab944566de60fb667fb55f5894f Mon Sep 17 00:00:00 2001 From: MagmaGuy Date: Sun, 29 Mar 2026 19:00:18 +0100 Subject: [PATCH 31/33] =?UTF-8?q?Resource=20Pack=20Manager=201.7.6:=20-=20?= =?UTF-8?q?[Fix]=20Fixed=20non-standard=20top-level=20keys=20in=20pack.mcm?= =?UTF-8?q?eta=20(e.g.=20Sodium's=20ignored=5Fshaders)=20being=20silently?= =?UTF-8?q?=20dropped=20during=20resource=20pack=20merging.=20-=20[Fix]=20?= =?UTF-8?q?Fixed=20overlay=20entries=20missing=20min=5Fformat/max=5Fformat?= =?UTF-8?q?=20fields=20causing=20"resource=20reload=20failed"=20on=201.21.?= =?UTF-8?q?9+=20clients=20=E2=80=94=20RSPM=20now=20normalizes=20overlay=20?= =?UTF-8?q?entries=20from=20third-party=20packs=20that=20haven't=20updated?= =?UTF-8?q?=20to=20the=20new=20format.=20-=20[Fix]=20Fixed=20NoSuchFileExc?= =?UTF-8?q?eption=20during=20resource=20pack=20mixing=20by=20creating=20pa?= =?UTF-8?q?rent=20directories.=20-=20[Fix]=20Fixed=20SHA1=20watchdog=20che?= =?UTF-8?q?ck=20crashing=20for=20cluster=20packs=20where=20the=20source=20?= =?UTF-8?q?is=20a=20directory.=20-=20[Fix]=20Fixed=20file=20copy=20failure?= =?UTF-8?q?s=20on=20Windows=20caused=20by=20file=20locks=20=E2=80=94=20now?= =?UTF-8?q?=20retries=20with=20Bukkit=20scheduler.=20-=20[Fix]=20Fixed=20m?= =?UTF-8?q?ajor=20merge=20logic=20issues=20with=20resource=20pack=20mixing?= =?UTF-8?q?,=20including=20improved=20handling=20of=201.21.4+=20item=20mod?= =?UTF-8?q?el=20definitions,=20sounds.json=20merging,=20atlas=20merging,?= =?UTF-8?q?=20and=20legacy=20model=20overrides.=20-=20[New]=20Migrated=20t?= =?UTF-8?q?o=20NightbreakPluginBootstrap=20and=20Magmacore=202.0.0-SNAPSHO?= =?UTF-8?q?T.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 (1M context) --- .gitignore | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 5ff6309..d2a93b5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,10 +4,7 @@ target/ !**/src/test/**/target/ ### IntelliJ IDEA ### -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ +.idea/ *.iws *.iml *.ipr @@ -35,4 +32,14 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store + +### Claude / AI ### +.claude/ +codex-build.log + +### Docs ### +docs/plans/ + +### Generated ### +dependency-reduced-pom.xml \ No newline at end of file From 7fe8ca7fe43186df9fce12155677b6ef1a6cf107 Mon Sep 17 00:00:00 2001 From: ImTheLeviDR Date: Mon, 6 Apr 2026 08:20:03 +0200 Subject: [PATCH 32/33] Implemented autoMixOnStartup config --- .../resourcepackmanager/config/DefaultConfig.java | 7 +++++++ .../thirdparty/ThirdPartyResourcePack.java | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java index 466c341..b5c9050 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/config/DefaultConfig.java @@ -15,6 +15,8 @@ public class DefaultConfig extends ConfigurationFile { @Getter private static boolean forceResourcePack; @Getter + private static boolean autoMixOnStartup; + @Getter private static String resourcePackPrompt; @Getter private static String resourcePackRerouting; @@ -54,6 +56,11 @@ public void initializeValues() { forceResourcePack = ConfigurationEngine.setBoolean( List.of("Sets whether the resource pack use will be forced to clients"), fileConfiguration, "forceResourcePack", false); + autoMixOnStartup = ConfigurationEngine.setBoolean( + List.of( + "Enable automatic resource pack mixing when the server starts.", + "If disabled, ResourcePackManager will wait for a resource pack change or manual reload before mixing."), + fileConfiguration, "autoMixOnStartup", true); resourcePackPrompt = ConfigurationEngine.setString( List.of("Sets whether the resource pack use will be forced to clients"), fileConfiguration, "resourcePackPrompt", "Use recommended resource pack?"); diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 4374d66..022c985 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -113,6 +113,7 @@ public ThirdPartyResourcePack(String pluginName, String localPath, String url, b } private static BukkitTask resourcePackChangeWatcher = null; + private static boolean initialStartup = true; /** * Checks whether a monitored plugin has finished its Magmacore initialization. @@ -189,8 +190,16 @@ public void run() { } if (!stableAlreadySent && readyToSend) { + if (!DefaultConfig.isAutoMixOnStartup() && initialStartup) { + Logger.info("Automatic resource pack mixing on startup is disabled. Waiting for resource pack changes or manual reload before mixing."); + tagAsResourcePackSent(); + initialStartup = false; + return; + } + notifyResourcePackSending(); tagAsResourcePackSent(); + initialStartup = false; Logger.info("Sending resource pack now."); Bukkit.getScheduler().runTaskAsynchronously(ResourcePackManager.plugin, Mix::mixResourcePacks); } From cec5de3730b76724a2a6a0a33b254dd22bdd5cc8 Mon Sep 17 00:00:00 2001 From: ImTheLeviDR Date: Mon, 6 Apr 2026 08:25:06 +0200 Subject: [PATCH 33/33] Send the last resource pack if no new mix on server start --- .../resourcepackmanager/mixer/Mix.java | 18 ++++++++++++++++++ .../thirdparty/ThirdPartyResourcePack.java | 9 +++++++++ 2 files changed, 27 insertions(+) diff --git a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java index b9e373f..364d11d 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/mixer/Mix.java @@ -256,6 +256,24 @@ private static void createOutputDefaultElements() { // generateBedrockResourcePack(); } + public static boolean loadExistingFinalResourcePack() { + if (finalResourcePack != null) return true; + File possiblePack = new File(getOutputFolder().getAbsolutePath() + File.separatorChar + resourcePackName + ".zip"); + if (!possiblePack.exists()) return false; + try { + finalResourcePack = possiblePack; + finalSHA1 = SHA1Generator.sha1CodeString(finalResourcePack); + finalSHA1Bytes = SHA1Generator.sha1CodeByteArray(finalResourcePack); + return true; + } catch (Exception e) { + Logger.warn("Failed to load existing merged resource pack from disk."); + finalResourcePack = null; + finalSHA1 = null; + finalSHA1Bytes = null; + return false; + } + } + private static File getOutputFolder() { return new File(ResourcePackManager.plugin.getDataFolder().getAbsolutePath() + File.separatorChar + "output"); } diff --git a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java index 022c985..65d5d39 100644 --- a/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java +++ b/src/main/java/com/magmaguy/resourcepackmanager/thirdparty/ThirdPartyResourcePack.java @@ -205,6 +205,15 @@ public void run() { } } }.runTaskTimerAsynchronously(ResourcePackManager.plugin, 20, 20); + + if (!DefaultConfig.isAutoMixOnStartup()) { + if (Mix.loadExistingFinalResourcePack()) { + Logger.info("autoMixOnStartup is disabled, reusing the last merged resource pack from disk."); + AutoHost.initialize(); + } else { + Logger.info("autoMixOnStartup is disabled and no existing merged resource pack was found. Waiting for changes."); + } + } } public static void tagAsResourcePackSent(){