From cf435580368107d6cc88ebbb2e620f262ad66612 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 17 Aug 2025 09:55:31 +0200 Subject: [PATCH 01/34] added * for itemflag hiding in settings item --- pom.xml | 2 +- .../data/professions/ProfessionSettings.java | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 9d6f4d9..77878d8 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 16 - 1.1.1-R0.3-SNAPSHOT + 1.1.1-R0.7-SNAPSHOT diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java index d190baa..dc14c68 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java @@ -92,8 +92,12 @@ public ProfessionSettings(String profession, ConfigurationSection config) { List flagsList = config.getStringList("settings.icon.optionals.flags"); if(!flagsList.isEmpty()) { flags = new HashSet<>(); - for (String flag : flagsList) { - flags.add(ItemFlag.valueOf(flag)); + if(flagsList.contains("*")) { + flags.addAll(Arrays.asList(ItemFlag.values())); + } else { + for (String flag : flagsList) { + flags.add(ItemFlag.valueOf(flag)); + } } } color = config.getString("settings.icon.optionals.color"); @@ -147,8 +151,12 @@ public ProfessionSettings(String profession, DeserializationWorker dw) { if (optionalIconSettings.get("flags") != null) { flags = new HashSet<>(); List flagsList = (List) optionalIconSettings.get("flags"); - for (String flag : flagsList) { - flags.add(ItemFlag.valueOf(flag)); + if(flagsList.contains("*")) { + flags.addAll(Arrays.asList(ItemFlag.values())); + } else { + for (String flag : flagsList) { + flags.add(ItemFlag.valueOf(flag)); + } } } if (optionalIconSettings.get("color") != null) From b7cef4d8135db297caeee12f4b01db12a705217c Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 17 Aug 2025 09:55:31 +0200 Subject: [PATCH 02/34] added * for itemflag hiding in settings item --- pom.xml | 2 +- .../data/professions/ProfessionSettings.java | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index 211386f..0049314 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 16 - 1.1.1-R0.3-SNAPSHOT + 1.1.1-R0.7-SNAPSHOT diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java index d190baa..dc14c68 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java @@ -92,8 +92,12 @@ public ProfessionSettings(String profession, ConfigurationSection config) { List flagsList = config.getStringList("settings.icon.optionals.flags"); if(!flagsList.isEmpty()) { flags = new HashSet<>(); - for (String flag : flagsList) { - flags.add(ItemFlag.valueOf(flag)); + if(flagsList.contains("*")) { + flags.addAll(Arrays.asList(ItemFlag.values())); + } else { + for (String flag : flagsList) { + flags.add(ItemFlag.valueOf(flag)); + } } } color = config.getString("settings.icon.optionals.color"); @@ -147,8 +151,12 @@ public ProfessionSettings(String profession, DeserializationWorker dw) { if (optionalIconSettings.get("flags") != null) { flags = new HashSet<>(); List flagsList = (List) optionalIconSettings.get("flags"); - for (String flag : flagsList) { - flags.add(ItemFlag.valueOf(flag)); + if(flagsList.contains("*")) { + flags.addAll(Arrays.asList(ItemFlag.values())); + } else { + for (String flag : flagsList) { + flags.add(ItemFlag.valueOf(flag)); + } } } if (optionalIconSettings.get("color") != null) From edcfa80c5726f3439dc1a73267ee0beb386c746f Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 17 Aug 2025 11:29:37 +0200 Subject: [PATCH 03/34] added mechanic to disable bukkit recipes --- .../fusion/cfg/BukkitRecipeWrapper.java | 63 +++++++++++++++++++ .../studio/magemonkey/fusion/cfg/Cfg.java | 10 +++ 2 files changed, 73 insertions(+) create mode 100644 src/main/java/studio/magemonkey/fusion/cfg/BukkitRecipeWrapper.java diff --git a/src/main/java/studio/magemonkey/fusion/cfg/BukkitRecipeWrapper.java b/src/main/java/studio/magemonkey/fusion/cfg/BukkitRecipeWrapper.java new file mode 100644 index 0000000..5080b8c --- /dev/null +++ b/src/main/java/studio/magemonkey/fusion/cfg/BukkitRecipeWrapper.java @@ -0,0 +1,63 @@ +package studio.magemonkey.fusion.cfg; + +import org.bukkit.Bukkit; +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.Recipe; +import studio.magemonkey.fusion.Fusion; + +import java.util.ArrayList; +import java.util.List; + +public class BukkitRecipeWrapper { + + public static List getRecipeKeysForMaterials(List material) { + + List entries = new ArrayList<>(); + + for (Material mat : material) { + if (mat.isAir()) continue; + ItemStack stack = new ItemStack(mat); + var recipes = Bukkit.getRecipesFor(stack); + if (recipes.isEmpty()) { + Fusion.getInstance().error("No recipes found for material: " + mat); + continue; + } + for(Recipe recipe : recipes) { + if (recipe instanceof Keyed keyed) { + NamespacedKey key = keyed.getKey(); + entries.add(key); + } else { + Bukkit.getLogger().info("Recipe has no key (likely a custom non-keyed recipe)."); + } + } + } + return entries; + } + + public static void enableBukkitRecipes(List recipes) { + for (NamespacedKey recipe : recipes) { + Recipe bukkitRecipe = Fusion.getInstance().getServer().getRecipe(recipe); + if (bukkitRecipe != null) { + Fusion.getInstance().getServer().addRecipe(bukkitRecipe); + Bukkit.getLogger().info("Enabled Bukkit recipe with key: " + recipe); + } else { + Fusion.getInstance().error("Bukkit recipe with key " + recipe + " not found."); + } + } + } + + public static void disableBukkitRecipes(List recipes) { + for (NamespacedKey recipe : recipes) { + Recipe bukkitRecipe = Fusion.getInstance().getServer().getRecipe(recipe); + if (bukkitRecipe != null) { + Fusion.getInstance().getServer().removeRecipe(recipe); + Bukkit.getLogger().info("Disabled Bukkit recipe with key: " + recipe); + } else { + Fusion.getInstance().error("Bukkit recipe with key " + recipe + " not found."); + } + } + } +} diff --git a/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java b/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java index f9d6377..b82d1b9 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java @@ -1,5 +1,7 @@ package studio.magemonkey.fusion.cfg; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; @@ -8,8 +10,10 @@ import java.io.File; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public final class Cfg { public static String recursive = "floor(n+300^(n/7)^2)"; @@ -28,6 +32,7 @@ public final class Cfg { public static String finishMessage = "&aYou have crafting items ready for pickup! ($)"; + public static List disabledVanillaRecipes = new ArrayList<>(); // No usage inside of Cfg, just used for default values. The actual values are stored in SQLManager.class private static final DatabaseType storageType = DatabaseType.LOCAL; @@ -89,6 +94,7 @@ private static void addDefs(FileConfiguration cfg) { if (!cfg.isSet("storage.password")) cfg.set("storage.password", storagePassword); if (!cfg.isSet("useCustomFormula")) cfg.set("useCustomFormula", useCustomFormula); + if (!cfg.isSet("disabled_vanilla_recipes")) cfg.set("disabled_vanilla_recipes", disabledVanillaRecipes); } public static void init() { @@ -111,6 +117,8 @@ public static void init() { hideRecipesLimitReached = cfg.getBoolean("hideRecipesDefault.recipeLimitReached"); useCustomFormula = cfg.getBoolean("useCustomFormula"); + List materials = cfg.getStringList("disabled_vanilla_recipes").stream().map(x -> Material.valueOf(x.toUpperCase())).toList(); + disabledVanillaRecipes = BukkitRecipeWrapper.getRecipeKeysForMaterials(materials); migrateOldTypes(cfg); } @@ -124,6 +132,8 @@ private static void reload(FileConfiguration cfg, File file) { cfg.save(file); // Load the config again cfg.load(file); + + BukkitRecipeWrapper.disableBukkitRecipes(disabledVanillaRecipes); } catch (Exception e) { Fusion.getInstance().getLogger().warning("Can't load config file: " + file + ":" + e.getMessage()); } From 26adbf5c32a0e348972a453db10f6a434cc0f2f7 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:58:52 +0200 Subject: [PATCH 04/34] added condition line next to requirement line to define what will be taken and what is just a condition at all --- .../fusion/cfg/CraftingRequirementsCfg.java | 4 ++++ .../fusion/data/recipes/CalculatedRecipe.java | 11 ++++++++--- src/main/resources/lang/CraftingRequirements.yml | 4 ++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java index aef918d..e593a34 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java @@ -29,6 +29,10 @@ public static String getCraftingRequirementLine(String path) { return ChatUT.hexString(config.getString(path + ".requirementLine", "&7Crafting Requirements")); } + public static String getCraftingConditionLine(String path) { + return ChatUT.hexString(config.getString(path + ".conditionLine", "&7Crafting Conditions")); + } + public static String getBossBarTitle(ItemStack item) { String itemName = item.getItemMeta() != null && item.getItemMeta().hasDisplayName() ? item.getItemMeta().getDisplayName() : ChatUT.serialize(Component.translatable(item.getTranslationKey())); diff --git a/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java b/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java index 12828b1..c63dda9 100644 --- a/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java +++ b/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java @@ -219,6 +219,10 @@ public static CalculatedRecipe create(Recipe recipe, if (masteryLine != null) lore.append(masteryLine).append('\n'); if (limitLine != null) lore.append(limitLine).append('\n'); if (!conditionLines.isEmpty()) { + String conditionLine = CraftingRequirementsCfg.getCraftingConditionLine("recipes"); + if (!conditionLine.isEmpty()) { + lore.append(conditionLine).append('\n'); + } for (Map.Entry e : conditionLines) { lore.append('\n').append(e.getValue()); } @@ -327,13 +331,14 @@ public static boolean isSimilar(ItemStack is1, ItemStack is2) { } } - // Check for flags - if (!im1.getItemFlags().isEmpty()) { + // TODO make sure this works with Divinity's specific update logic. Until then, skip it. + // Divinity Logic -> https://github.com/magemonkeystudio/divinity/blob/dev/src/main/java/studio/magemonkey/divinity/manager/listener/object/ItemUpdaterListener.java#L95 + /*if (!im1.getItemFlags().isEmpty()) { if (im1.getItemFlags().size() != im2.getItemFlags().size()) isValid = false; for (ItemFlag flag : im1.getItemFlags()) { if (!im2.getItemFlags().contains(flag)) isValid = false; } - } + }*/ // Check for custom model data if (im1.hasCustomModelData() && im2.hasCustomModelData()) { diff --git a/src/main/resources/lang/CraftingRequirements.yml b/src/main/resources/lang/CraftingRequirements.yml index d19c298..8ef2529 100644 --- a/src/main/resources/lang/CraftingRequirements.yml +++ b/src/main/resources/lang/CraftingRequirements.yml @@ -4,6 +4,8 @@ unfulfilled: "&c&l✗&r" recipes: # The line that is displayed above the crafting requirements requirementLine: "&7Crafting Requirements" + # The line that is displayed above the crafting conditions + conditionLine: "&7Crafting Conditions" # The bossbar that will be shown when manual crafting is enabled bossbar: '&5Crafting $...' # The message that is displayed when the player has learned the recipe @@ -66,6 +68,8 @@ recipes: professions: # The line that is displayed above the crafting requirements requirementLine: "&7Crafting Requirements" + # The line that is displayed above the crafting conditions + conditionLine: "&7Crafting Conditions" # The message that is displayed when the player has unlocked the profession learned: 'true': "&aYou have unlocked this profession" From 8b693f9499bb507b5a7b015f6563e22d43edb70f Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:59:03 +0200 Subject: [PATCH 05/34] added auto-joining --- .../studio/magemonkey/fusion/cfg/Cfg.java | 17 ++++++++++ .../fusion/commands/CommandMechanics.java | 23 +++++++------ .../magemonkey/fusion/commands/Commands.java | 32 +++++++++++-------- .../professions/ProfessionConditions.java | 2 +- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java b/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java index b82d1b9..e76f3e4 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/Cfg.java @@ -2,14 +2,20 @@ import org.bukkit.Material; import org.bukkit.NamespacedKey; +import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.entity.Player; import studio.magemonkey.fusion.Fusion; import studio.magemonkey.fusion.cfg.sql.DatabaseType; +import studio.magemonkey.fusion.commands.CommandMechanics; +import studio.magemonkey.fusion.data.player.FusionPlayer; +import studio.magemonkey.fusion.data.player.PlayerLoader; +import studio.magemonkey.fusion.gui.BrowseGUI; import java.io.File; import java.io.IOException; +import java.sql.Array; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -33,6 +39,7 @@ public final class Cfg { public static String finishMessage = "&aYou have crafting items ready for pickup! ($)"; public static List disabledVanillaRecipes = new ArrayList<>(); + public static List autoJoinProfessions = new ArrayList<>(); // No usage inside of Cfg, just used for default values. The actual values are stored in SQLManager.class private static final DatabaseType storageType = DatabaseType.LOCAL; @@ -95,6 +102,7 @@ private static void addDefs(FileConfiguration cfg) { if (!cfg.isSet("useCustomFormula")) cfg.set("useCustomFormula", useCustomFormula); if (!cfg.isSet("disabled_vanilla_recipes")) cfg.set("disabled_vanilla_recipes", disabledVanillaRecipes); + if (!cfg.isSet("auto_join_professions")) cfg.set("auto_join_professions", autoJoinProfessions); } public static void init() { @@ -119,6 +127,7 @@ public static void init() { useCustomFormula = cfg.getBoolean("useCustomFormula"); List materials = cfg.getStringList("disabled_vanilla_recipes").stream().map(x -> Material.valueOf(x.toUpperCase())).toList(); disabledVanillaRecipes = BukkitRecipeWrapper.getRecipeKeysForMaterials(materials); + autoJoinProfessions = cfg.getStringList("auto_join_professions"); migrateOldTypes(cfg); } @@ -175,4 +184,12 @@ public static boolean setDatabaseType(DatabaseType type) { return false; } } + + public static void autoJoinProfessions(Player player) { + FusionPlayer fp = PlayerLoader.getPlayer(player); + for (String professionId : autoJoinProfessions) { + if(fp.hasProfession(professionId) && fp.hasJoined(professionId)) continue; + BrowseGUI.joinProfession(player, ProfessionsCfg.getGuiMap().get(professionId)); + } + } } diff --git a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java index 8e47eee..b1413b5 100644 --- a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java +++ b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java @@ -37,8 +37,8 @@ public static void useProfession(CommandSender sender, String[] args) { return; } String[] professionArgs = args[1].split(":"); - String profession = professionArgs[0]; - Category category = null; + String profession = professionArgs[0]; + Category category = null; ProfessionGuiRegistry eq = ProfessionsCfg.getGuiMap().get(profession); @@ -111,8 +111,8 @@ public static void useProfession(CommandSender sender, String[] args) { public static void masterProfession(CommandSender sender, String[] args) { if (sender instanceof Player player) { - String guiName = args[1]; - CraftingTable table = ProfessionsCfg.getTable(guiName); + String guiName = args[1]; + CraftingTable table = ProfessionsCfg.getTable(guiName); if (table == null) { CodexEngine.get().getMessageUtil().sendMessage("fusion.notACrafting", sender, @@ -175,8 +175,7 @@ public static void forgetProfession(CommandSender sender, new MessageData("sender", sender), new MessageData("craftingTable", table)); - Bukkit.getScheduler().runTaskLater(Fusion.getInstance(), - () -> confirmation.remove(player.getUniqueId().toString()), 15 * 20L); + Bukkit.getScheduler().runTaskLater(Fusion.getInstance(), () -> confirmation.remove(player.getUniqueId().toString()), 15 * 20L); } else { CodexEngine.get() @@ -357,7 +356,7 @@ public static void setProfessionExp(CommandSender sender, String[] args) { } try { - long exp = Long.parseLong(args[4]); + long exp = Long.parseLong(args[4]); long expBefore = FusionAPI.getPlayerManager().getPlayer(player).getExperience(profession); switch (args[1].toLowerCase()) { case "add" -> FusionAPI.getEventServices() @@ -406,7 +405,7 @@ public static void setProfessionLevel(CommandSender sender, String[] args) { } try { int levelBefore = FusionAPI.getPlayerManager().getPlayer(player).getLevel(profession); - int levelAfter = Integer.parseInt(args[4]); + int levelAfter = Integer.parseInt(args[4]); if (levelAfter <= 0) levelBefore = 1; long expBefore = (long) ProfessionsCfg.getTable(profession) @@ -550,8 +549,8 @@ public static void forceLeaveProfession(@NotNull CommandSender sender, String[] return; } - String professionName = args[2]; - CraftingTable table = ProfessionsCfg.getTable(professionName); + String professionName = args[2]; + CraftingTable table = ProfessionsCfg.getTable(professionName); if (table == null) { CodexEngine.get().getMessageUtil().sendMessage("fusion.notACrafting", sender, @@ -641,8 +640,8 @@ public static void forceMaster(@NotNull CommandSender sender, String[] args) { return; } - String professionName = args[2]; - CraftingTable table = ProfessionsCfg.getTable(professionName); + String professionName = args[2]; + CraftingTable table = ProfessionsCfg.getTable(professionName); if (table == null) { CodexEngine.get().getMessageUtil().sendMessage("fusion.notACrafting", sender, diff --git a/src/main/java/studio/magemonkey/fusion/commands/Commands.java b/src/main/java/studio/magemonkey/fusion/commands/Commands.java index 09698c0..50c5dc1 100644 --- a/src/main/java/studio/magemonkey/fusion/commands/Commands.java +++ b/src/main/java/studio/magemonkey/fusion/commands/Commands.java @@ -121,17 +121,21 @@ public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { + if(!(sender instanceof Player player)) + return List.of(); + List entries = new ArrayList<>(); List professions = new ArrayList<>(); - if (sender instanceof Player) { - professions = new ArrayList<>(PlayerLoader.getPlayer(((Player) sender).getUniqueId()).getProfessions()); - } + professions = new ArrayList<>(PlayerLoader.getPlayer((player).getUniqueId()).getProfessions()); if (args.length == 1) { - if ("browse".startsWith(args[0])) entries.add("browse"); + if (sender.hasPermission("fusion.browse") + && "browse".startsWith(args[0])) entries.add("browse"); + if (confirmation.containsKey(player.getUniqueId().toString()) + && "confirm".startsWith(args[0])) entries.add("confirm"); if ("stats".startsWith(args[0])) entries.add("stats"); - if ("confirm".startsWith(args[0])) entries.add("confirm"); if ("use".startsWith(args[0])) entries.add("use"); - if ("master".startsWith(args[0])) entries.add("master"); + if (sender.hasPermission("fusion.master") && + "master".startsWith(args[0])) entries.add("master"); if ("forget".startsWith(args[0])) entries.add("forget"); if ("join".startsWith(args[0])) entries.add("join"); if (sender.hasPermission("fusion.admin.use") && "storage".startsWith(args[0])) @@ -154,8 +158,8 @@ public List onTabComplete(@NotNull CommandSender sender, } } else if (args.length == 2) { if (args[0].equalsIgnoreCase("use")) { - for (String name : professions.stream().map(Profession::getName).toList()) { - if (name.startsWith(args[1])) entries.add(name); + for (String profession : professions.stream().map(Profession::getName).toList()) { + if (ProfessionsCfg.getGuiMap().containsKey(profession) && profession.startsWith(args[1])) entries.add(profession); } if (sender.hasPermission("fusion.craft.use.categories") && args[1].contains(":")) { @@ -198,14 +202,14 @@ else if (sender.hasPermission("fusion.admin.force") && (args[0].equalsIgnoreCase("forcejoin") || args[0].equalsIgnoreCase("forceleave") || args[0].equalsIgnoreCase("forcestats") || args[0].equalsIgnoreCase("forcemaster") || args[0].equalsIgnoreCase("forceshow"))) { - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.getName().startsWith(args[1])) entries.add(player.getName()); + for (Player _player : Bukkit.getOnlinePlayers()) { + if (_player.getName().startsWith(args[1])) entries.add(_player.getName()); } } } else if (args.length == 3) { if (sender.hasPermission("fusion.admin.use") && args[0].equalsIgnoreCase("use")) { - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.getName().startsWith(args[2])) entries.add(player.getName()); + for (Player _player : Bukkit.getOnlinePlayers()) { + if (_player.getName().startsWith(args[2])) entries.add(_player.getName()); } } else if ((args[0].equalsIgnoreCase("exp") || args[0].equalsIgnoreCase("level")) && sender.hasPermission( "fusion.admin")) { @@ -224,8 +228,8 @@ else if (sender.hasPermission("fusion.admin.force") && } else if (args.length == 4) { if ((args[0].equalsIgnoreCase("exp") || args[0].equalsIgnoreCase("level")) && sender.hasPermission( "fusion.admin")) { - for (Player player : Bukkit.getOnlinePlayers()) { - if (player.getName().startsWith(args[3])) entries.add(player.getName()); + for (Player _player : Bukkit.getOnlinePlayers()) { + if (_player.getName().startsWith(args[3])) entries.add(_player.getName()); } } } diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionConditions.java b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionConditions.java index 827b3b7..409ce59 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionConditions.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionConditions.java @@ -282,7 +282,7 @@ public boolean isValid(FusionPlayer player) { new MessageData("condition.name", null), }; - if (player.hasProfession(profession)) { + if (player.hasProfession(profession) && player.hasJoined(profession)) { _player.playSound(_player.getLocation(), Sound.BLOCK_ANVIL_PLACE, 1f, 1f); CodexEngine.get().getMessageUtil().sendMessage("fusion.browse.alreadyUnlocked", _player, data); return false; From 95c0b8a16299ef38735b785e996995e06757f940 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Fri, 5 Sep 2025 11:59:20 +0200 Subject: [PATCH 06/34] fixed some event services for joining/leaving professions --- src/main/java/studio/magemonkey/fusion/Fusion.java | 3 +++ .../api/events/services/ProfessionService.java | 13 +++++++++++-- .../fusion/data/professions/Profession.java | 2 +- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/Fusion.java b/src/main/java/studio/magemonkey/fusion/Fusion.java index 6572bad..69b0198 100644 --- a/src/main/java/studio/magemonkey/fusion/Fusion.java +++ b/src/main/java/studio/magemonkey/fusion/Fusion.java @@ -190,6 +190,9 @@ private void runQueueTask() { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { PlayerLoader.loadPlayer(event.getPlayer()); + if(!Cfg.autoJoinProfessions.isEmpty()) { + Cfg.autoJoinProfessions(event.getPlayer()); + } if (Cfg.craftingQueue) { notifyForQueue(event.getPlayer()); } diff --git a/src/main/java/studio/magemonkey/fusion/api/events/services/ProfessionService.java b/src/main/java/studio/magemonkey/fusion/api/events/services/ProfessionService.java index f8ca68c..95d6a29 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/services/ProfessionService.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/services/ProfessionService.java @@ -8,12 +8,16 @@ import studio.magemonkey.fusion.Fusion; import studio.magemonkey.fusion.api.FusionAPI; import studio.magemonkey.fusion.api.events.*; +import studio.magemonkey.fusion.cfg.ProfessionsCfg; import studio.magemonkey.fusion.data.player.FusionPlayer; import studio.magemonkey.fusion.data.professions.Profession; import studio.magemonkey.fusion.data.recipes.CraftingTable; +import studio.magemonkey.fusion.util.ChatUT; import studio.magemonkey.fusion.util.ExperienceManager; import studio.magemonkey.fusion.util.PlayerUtil; +import java.util.Objects; + public class ProfessionService { /** @@ -27,8 +31,11 @@ public void joinProfession(String professionName, Player player, double moneyCos ProfessionJoinEvent event = new ProfessionJoinEvent(professionName, player); Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { - event.getFusionPlayer() - .addProfession(new Profession(-1, player.getUniqueId(), professionName, 0, false, true)); + if(!event.getFusionPlayer().hasProfession(professionName)) { + event.getFusionPlayer().addProfession(new Profession(-1, player.getUniqueId(), professionName, 0, false, true)); + } else { + Objects.requireNonNull(event.getFusionPlayer().getProfession(professionName)).setJoined(true); + } if (moneyCost > 0) CodexEngine.get().getVault().take(player, moneyCost); if (expCost > 0) @@ -36,6 +43,7 @@ public void joinProfession(String professionName, Player player, double moneyCos MessageData[] data = { new MessageData("profession", professionName), + new MessageData("inventoryName", ChatUT.hexString(ProfessionsCfg.getTable(professionName).getInventoryName())), new MessageData("costs.money", moneyCost), new MessageData("costs.experience", expCost), new MessageData("unlocked", event.getFusionPlayer().getJoinedProfessions().size()), @@ -58,6 +66,7 @@ public void leaveProfession(CraftingTable table, Player player) { ProfessionLeaveEvent event = new ProfessionLeaveEvent(table.getName(), player); Bukkit.getPluginManager().callEvent(event); if (!event.isCancelled()) { + event.getFusionPlayer().setJoined(table, false); event.getFusionPlayer().removeProfession(table); CodexEngine.get().getMessageUtil().sendMessage("fusion.forgotten", player, diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/Profession.java b/src/main/java/studio/magemonkey/fusion/data/professions/Profession.java index 905de12..3ddb3c3 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/Profession.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/Profession.java @@ -61,7 +61,7 @@ public void update() { } public int getLevel() { - return ProfessionsCfg.getTable(name).getLevelFunction().getLevel(exp); + return ProfessionsCfg.getGuiMap().containsKey(name) ? ProfessionsCfg.getTable(name).getLevelFunction().getLevel(exp) : 0; } public void setLevel(int level) { From 7c156d418721d3c2443ab71dc3c6b91a0f703137 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 7 Sep 2025 09:17:45 +0200 Subject: [PATCH 07/34] removed debug --- .../java/studio/magemonkey/fusion/data/player/FusionPlayer.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java index 80c97e9..bd8b9cd 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java @@ -349,7 +349,6 @@ public void save() { } for (CraftingQueue queue : cachedQueues.values()) { SQLManager.queues().saveCraftingQueue(queue); - Bukkit.getConsoleSender().sendMessage("Saved queue for profession " + queue.getProfession() + " and category " + queue.getCategory().getName()); } SQLManager.recipeLimits().saveRecipeLimits(uuid, cachedRecipeLimits); cachedQueues.clear(); From cca604c5ddd3c026d245b6a28f684cec48312505 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 7 Sep 2025 09:18:01 +0200 Subject: [PATCH 08/34] fixed ingredient hash on craft method --- .../studio/magemonkey/fusion/gui/RecipeGui.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java index e243c29..02dfcda 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java +++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java @@ -686,21 +686,18 @@ private boolean craft(int slot, boolean addToCursor) { // for (ItemStack required : requiredItems) { int need = required.getAmount(); - ItemStack neededTemplate = required.clone(); // same material+meta + IngredientFingerprint neededFingerprint = IngredientFingerprint.of(required); - // Iterate through every inventory slot to match via isSimilar() for (int slotIndex = 0; slotIndex < inv.getSize() && need > 0; slotIndex++) { ItemStack slotStack = inv.getItem(slotIndex); if (slotStack == null || slotStack.getType() == Material.AIR) continue; - // Use CalculatedRecipe.isSimilar() to match custom NBT/lore - if (!CalculatedRecipe.isSimilar(neededTemplate, slotStack)) { - continue; - } + IngredientFingerprint slotFingerprint = IngredientFingerprint.of(slotStack); + if (!neededFingerprint.equals(slotFingerprint)) continue; int available = slotStack.getAmount(); int take = Math.min(available, need); - // Subtract “take” from that slot + slotStack.setAmount(available - take); if (slotStack.getAmount() <= 0) { inv.setItem(slotIndex, null); @@ -708,8 +705,7 @@ private boolean craft(int slot, boolean addToCursor) { inv.setItem(slotIndex, slotStack); } - // Track exactly what we removed - ItemStack actuallyTaken = neededTemplate.clone(); + ItemStack actuallyTaken = required.clone(); actuallyTaken.setAmount(take); removedSoFar.add(actuallyTaken); From 4bb2545c0121feefe835bcb77bc04740e0ee8e1d Mon Sep 17 00:00:00 2001 From: Travja Date: Tue, 9 Sep 2025 01:02:11 -0600 Subject: [PATCH 09/34] Formatting --- .../fusion/api/ProfessionManager.java | 2 +- .../fusion/api/events/FusionEvent.java | 4 +- .../api/events/ProfessionGainXpEvent.java | 4 +- .../api/events/ProfessionJoinEvent.java | 2 +- .../api/events/ProfessionLeaveEvent.java | 2 +- .../api/events/ProfessionLevelUpEvent.java | 6 +- .../api/events/ProfessionMasteryEvent.java | 4 +- .../api/events/QueueItemAddedEvent.java | 6 +- .../api/events/QueueItemFinishedEvent.java | 14 +-- .../api/events/QueueItemRemovedEvent.java | 12 +-- .../api/events/services/QueueService.java | 10 +- .../magemonkey/fusion/cfg/ShowRecipesCfg.java | 6 +- .../fusion/cfg/editors/EditorRegistry.java | 2 +- .../editors/professions/RecipeEditorCfg.java | 2 +- .../professions/RecipeIconEditorCfg.java | 25 ++--- .../cfg/migrations/ProfessionMigration.java | 8 +- .../cfg/sql/tables/FusionQueuesSQL.java | 2 +- .../fusion/commands/FusionEditorCommand.java | 96 ++++++++++--------- .../professions/CalculatedProfession.java | 4 +- .../data/professions/ProfessionResults.java | 28 ++++-- .../fusion/data/queue/CraftingQueue.java | 2 +- .../fusion/data/queue/QueueItem.java | 1 - .../fusion/data/recipes/CraftingTable.java | 13 +-- .../editors/professions/ProfessionEditor.java | 2 +- .../professions/recipes/RecipeIconEditor.java | 36 ++++--- .../gui/recipe/IngredientFingerprint.java | 46 ++++----- .../gui/recipe/InventoryFingerprint.java | 18 ++-- .../fusion/gui/recipe/RecipeCacheKey.java | 12 +-- .../gui/recipe/RecipeGuiEventRouter.java | 8 +- .../magemonkey/fusion/gui/slot/Slot.java | 2 +- .../fusion/commands/ForceCommandsTest.java | 37 +++---- 31 files changed, 226 insertions(+), 190 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/api/ProfessionManager.java b/src/main/java/studio/magemonkey/fusion/api/ProfessionManager.java index 801fbe1..4428a1e 100644 --- a/src/main/java/studio/magemonkey/fusion/api/ProfessionManager.java +++ b/src/main/java/studio/magemonkey/fusion/api/ProfessionManager.java @@ -48,7 +48,7 @@ public ProfessionGuiRegistry getProfessionGui(String profession) { * This object handles all players guis individually by caching them in a map. * * @param profession The name of the profession - * @param player The Player object + * @param player The Player object * @return The profession gui object */ public ProfessionGuiRegistry openProfessionGui(String profession, Player player) { diff --git a/src/main/java/studio/magemonkey/fusion/api/events/FusionEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/FusionEvent.java index 183aa00..4462a21 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/FusionEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/FusionEvent.java @@ -37,8 +37,8 @@ public class FusionEvent extends Event implements Cancellable { * Constructor for the FusionEvent * * @param professionName The name of the profession - * @param craftingTable The crafting table - * @param player The player + * @param craftingTable The crafting table + * @param player The player */ public FusionEvent(String professionName, CraftingTable craftingTable, Player player) { this.professionName = professionName; diff --git a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionGainXpEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionGainXpEvent.java index 9cd3429..d460e5d 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionGainXpEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionGainXpEvent.java @@ -18,8 +18,8 @@ public class ProfessionGainXpEvent extends FusionEvent { * Constructor for the ProfessionGainXpEvent * * @param professionName The name of the profession - * @param player The player that gained the experience - * @param gainedExp The amount of experience gained + * @param player The player that gained the experience + * @param gainedExp The amount of experience gained */ public ProfessionGainXpEvent(String professionName, Player player, long gainedExp) { super(professionName, ProfessionsCfg.getTable(professionName), player); diff --git a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionJoinEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionJoinEvent.java index faf9673..dc2aea3 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionJoinEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionJoinEvent.java @@ -10,7 +10,7 @@ public class ProfessionJoinEvent extends FusionEvent { * Constructor for the ProfessionJoinEvent * * @param professionName The name of the profession - * @param player The player that joined the profession + * @param player The player that joined the profession */ public ProfessionJoinEvent(String professionName, Player player) { super(professionName, ProfessionsCfg.getTable(professionName), player); diff --git a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLeaveEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLeaveEvent.java index cea6091..1d71e7b 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLeaveEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLeaveEvent.java @@ -10,7 +10,7 @@ public class ProfessionLeaveEvent extends FusionEvent { * Constructor for the ProfessionLeaveEvent * * @param professionName The name of the profession - * @param player The player that left the profession + * @param player The player that left the profession */ public ProfessionLeaveEvent(String professionName, Player player) { super(professionName, ProfessionsCfg.getTable(professionName), player); diff --git a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLevelUpEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLevelUpEvent.java index f74cfb2..930bf26 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLevelUpEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionLevelUpEvent.java @@ -23,9 +23,9 @@ public class ProfessionLevelUpEvent extends FusionEvent { * Constructor for the ProfessionLevelUpEvent * * @param professionName The name of the profession - * @param player The player that leveled up - * @param previousLevel The previous level of the profession - * @param newLevel The new level of the profession + * @param player The player that leveled up + * @param previousLevel The previous level of the profession + * @param newLevel The new level of the profession */ public ProfessionLevelUpEvent(String professionName, Player player, int previousLevel, int newLevel) { super(professionName, ProfessionsCfg.getTable(professionName), player); diff --git a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionMasteryEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionMasteryEvent.java index d616306..4f8ae9e 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/ProfessionMasteryEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/ProfessionMasteryEvent.java @@ -16,8 +16,8 @@ public class ProfessionMasteryEvent extends FusionEvent { * Constructor for the ProfessionMasteryEvent * * @param professionName The name of the profession - * @param player The player that has mastered the profession - * @param hasMastered Whether the player has mastered the profession + * @param player The player that has mastered the profession + * @param hasMastered Whether the player has mastered the profession */ public ProfessionMasteryEvent(String professionName, Player player, boolean hasMastered) { super(professionName, ProfessionsCfg.getTable(professionName), player); diff --git a/src/main/java/studio/magemonkey/fusion/api/events/QueueItemAddedEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/QueueItemAddedEvent.java index 8643ae3..de96dd7 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/QueueItemAddedEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/QueueItemAddedEvent.java @@ -22,9 +22,9 @@ public class QueueItemAddedEvent extends FusionEvent { * Constructor for the QueueItemAddedEvent * * @param professionName The name of the profession - * @param player The player that added the item to the queue - * @param queue The crafting queue - * @param queueItem The queue item + * @param player The player that added the item to the queue + * @param queue The crafting queue + * @param queueItem The queue item */ public QueueItemAddedEvent(String professionName, Player player, CraftingQueue queue, QueueItem queueItem) { super(professionName, ProfessionsCfg.getTable(professionName), player); diff --git a/src/main/java/studio/magemonkey/fusion/api/events/QueueItemFinishedEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/QueueItemFinishedEvent.java index 19552a6..04db1e3 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/QueueItemFinishedEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/QueueItemFinishedEvent.java @@ -16,25 +16,25 @@ public class QueueItemFinishedEvent extends FusionEvent { /** * The crafting queue */ - private final CraftingQueue queue; + private final CraftingQueue queue; /** * The queue item */ - private final QueueItem queueItem; + private final QueueItem queueItem; /** * The result item */ @Setter - private List resultItems; + private List resultItems; /** * Constructor for the QueueItemFinishedEvent * * @param professionName The name of the profession - * @param player The player that finished the item - * @param queue The crafting queue - * @param queueItem The queue item - * @param resultItems The result items + * @param player The player that finished the item + * @param queue The crafting queue + * @param queueItem The queue item + * @param resultItems The result items */ public QueueItemFinishedEvent(String professionName, Player player, diff --git a/src/main/java/studio/magemonkey/fusion/api/events/QueueItemRemovedEvent.java b/src/main/java/studio/magemonkey/fusion/api/events/QueueItemRemovedEvent.java index d78df7f..f5b102b 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/QueueItemRemovedEvent.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/QueueItemRemovedEvent.java @@ -39,12 +39,12 @@ public class QueueItemRemovedEvent extends FusionEvent { * Constructor for the QueueItemRemovedEvent * * @param professionName The name of the profession - * @param player The player that removed the item from the queue - * @param queue The crafting queue - * @param queueItem The queue item - * @param finished Whether the item was finished - * @param refunded Whether the item was refunded - * @param refundedItems The refunded items in case `refunded` is `true` + * @param player The player that removed the item from the queue + * @param queue The crafting queue + * @param queueItem The queue item + * @param finished Whether the item was finished + * @param refunded Whether the item was refunded + * @param refundedItems The refunded items in case `refunded` is `true` */ public QueueItemRemovedEvent(String professionName, Player player, diff --git a/src/main/java/studio/magemonkey/fusion/api/events/services/QueueService.java b/src/main/java/studio/magemonkey/fusion/api/events/services/QueueService.java index 7738217..7321d62 100644 --- a/src/main/java/studio/magemonkey/fusion/api/events/services/QueueService.java +++ b/src/main/java/studio/magemonkey/fusion/api/events/services/QueueService.java @@ -143,7 +143,12 @@ public void finishQueueItem(Player player, } // Items if no commands exist if (!item.getRecipe().getResults().hasCommandsOrItems()) { - ItemStack result = event.getQueueItem().getRecipe().getDivinityRecipeMeta() == null ? event.getQueueItem().getRecipe().getSettings().getRecipeItem().getItemStack() + ItemStack result = + event.getQueueItem().getRecipe().getDivinityRecipeMeta() == null ? event.getQueueItem() + .getRecipe() + .getSettings() + .getRecipeItem() + .getItemStack() : event.getQueueItem().getRecipe().getDivinityRecipeMeta().generateItem(); // If there is no space in the inventory, drop the items Collection notAdded = player.getInventory().addItem(result).values(); @@ -165,7 +170,8 @@ public void finishQueueItem(Player player, if (itemStack != null) { Collection remainings = player.getInventory().addItem(itemStack).values(); if (!remainings.isEmpty()) { - remainings.forEach(_item -> player.getWorld().dropItemNaturally(player.getLocation(), _item)); + remainings.forEach(_item -> player.getWorld() + .dropItemNaturally(player.getLocation(), _item)); } } } diff --git a/src/main/java/studio/magemonkey/fusion/cfg/ShowRecipesCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/ShowRecipesCfg.java index e49c40c..990d505 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/ShowRecipesCfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/ShowRecipesCfg.java @@ -96,10 +96,10 @@ private static void readData() { } public static ItemStack getRecipeIcon(Recipe recipe, RecipeItem ingredient) { - String itemName = Utils.getItemName(recipe.getSettings().getRecipeItem().getItemStack()); - String name = ChatUT.hexString(config.getString("recipeItem.name", "&7$") + String itemName = Utils.getItemName(recipe.getSettings().getRecipeItem().getItemStack()); + String name = ChatUT.hexString(config.getString("recipeItem.name", "&7$") .replace(MessageUtil.getReplacement("name"), itemName)); - List lore = config.getStringList("recipeItem.lore"); + List lore = config.getStringList("recipeItem.lore"); lore.replaceAll(s -> ChatUT.hexString(s.replace(MessageUtil.getReplacement("ingredient"), Utils.getItemName(ingredient.getItemStack())) .replace(MessageUtil.getReplacement("profession"), recipe.getTable().getInventoryName()) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/editors/EditorRegistry.java b/src/main/java/studio/magemonkey/fusion/cfg/editors/EditorRegistry.java index 8056c4f..e9c5427 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/editors/EditorRegistry.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/editors/EditorRegistry.java @@ -39,7 +39,7 @@ public class EditorRegistry { @Getter private static RecipeEditorCfg recipeEditorCfg; @Getter - private static RecipeIconEditorCfg recipeIconEditorCfg; + private static RecipeIconEditorCfg recipeIconEditorCfg; @Getter private static CategoryEditorCfg categoryEditorCfg; @Getter diff --git a/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeEditorCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeEditorCfg.java index 3a15a10..86dbb27 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeEditorCfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeEditorCfg.java @@ -166,7 +166,7 @@ public ItemStack getSubIcon(Recipe recipe, String icon) { } i += newLines; continue; - } else if (lore.get(i).contains(MessageUtil.getReplacement("items"))) { + } else if (lore.get(i).contains(MessageUtil.getReplacement("items"))) { lore.remove(i); int newLines = 1; for (String line : recipe.getResults().getItemNames()) { diff --git a/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeIconEditorCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeIconEditorCfg.java index 71f9437..650c1e9 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeIconEditorCfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/editors/professions/RecipeIconEditorCfg.java @@ -38,12 +38,13 @@ public Map getIcons(Recipe recipe) { } public ItemStack getIcon(Recipe recipe, String icon) { - Material material = Material.valueOf(config.getString("icons." + icon + ".material", "STONE").toUpperCase()); - int amount = config.getInt("icons." + icon + ".amount", 1); - int durability = config.getInt("icons." + icon + ".durability", 0); - boolean unbreakable = config.getBoolean("icon." + icon + ".unbreakable", false); - String name = config.getString("icons." + icon + ".name", "&cInvalid Item: &4" + icon); - List lore = config.getStringList("icons." + icon + ".lore"); + Material material = + Material.valueOf(config.getString("icons." + icon + ".material", "STONE").toUpperCase()); + int amount = config.getInt("icons." + icon + ".amount", 1); + int durability = config.getInt("icons." + icon + ".durability", 0); + boolean unbreakable = config.getBoolean("icon." + icon + ".unbreakable", false); + String name = config.getString("icons." + icon + ".name", "&cInvalid Item: &4" + icon); + List lore = config.getStringList("icons." + icon + ".lore"); ProfessionSettings settings = recipe.getSettings(); for (int i = 0; i < lore.size(); i++) { @@ -105,14 +106,16 @@ public ItemStack getIcon(Recipe recipe, String icon) { } lore.set(i, ChatUT.hexString(lore.get(i) .replace(MessageUtil.getReplacement("name"), settings.getName() != null ? settings.getName() : "") - .replace(MessageUtil.getReplacement("customModelData"), String.valueOf(settings.getCustomModelData())) + .replace(MessageUtil.getReplacement("customModelData"), + String.valueOf(settings.getCustomModelData())) .replace(MessageUtil.getReplacement("unbreakable"), String.valueOf(settings.isUnbreakable())) - .replace(MessageUtil.getReplacement("color"), settings.getColor() != null ? settings.getColor() : "") + .replace(MessageUtil.getReplacement("color"), + settings.getColor() != null ? settings.getColor() : "") .replace(MessageUtil.getReplacement("cancelDrop"), String.valueOf(settings.isCancelDrop())))); } - Map enchants = config.getEnchantmentSection("icons." + icon + ".enchants"); - List flags = config.getItemFlags("icons." + icon + ".flags"); - ItemFlag[] itemFlags = flags.toArray(new ItemFlag[0]); + Map enchants = config.getEnchantmentSection("icons." + icon + ".enchants"); + List flags = config.getItemFlags("icons." + icon + ".flags"); + ItemFlag[] itemFlags = flags.toArray(new ItemFlag[0]); return ItemBuilder.newItem(material) .amount(amount) .durability(durability) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java b/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java index fbdb883..09f011d 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java @@ -125,14 +125,14 @@ public static void migrate(FileConfiguration config, String toVersion) { migrations.put("1.3", (config) -> { List> recipes = config.getMapList("recipes"); for (Map recipe : recipes) { - Map results = (Map) recipe.get("results"); + Map results = (Map) recipe.get("results"); Map settings = (Map) recipe.get("settings"); - if(results == null) continue; - if(settings == null) + if (results == null) continue; + if (settings == null) settings = new LinkedHashMap<>(); String namespace = (String) results.get("item"); - if(namespace == null) continue; + if (namespace == null) continue; Map iconSettings = new LinkedHashMap<>(); iconSettings.put("item", namespace); diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java index 4d863aa..9af7774 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java @@ -114,7 +114,7 @@ public List getQueueItems(UUID uuid, String profession, Category cate try (ResultSet result = select.executeQuery()) { while (result.next()) { String recipeStr = result.getString("RecipePath").split("\\.")[2]; - Recipe recipe = category.getRecipe(recipeStr); + Recipe recipe = category.getRecipe(recipeStr); if (recipe == null) { Fusion.getInstance() diff --git a/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java b/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java index f7b3482..e62d5f2 100644 --- a/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java +++ b/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java @@ -228,7 +228,7 @@ public List onTabComplete(@NotNull CommandSender sender, } if (editor instanceof ProfessionEditor) { ProfessionEditor professionEditor = (ProfessionEditor) editor; - EditorCriteria criteria = editorCriteria.get(player.getUniqueId()); + EditorCriteria criteria = editorCriteria.get(player.getUniqueId()); switch (criteria) { case Profession_Edit_Name: case Pattern_Edit_Name: @@ -329,7 +329,12 @@ public List onTabComplete(@NotNull CommandSender sender, case RecipeIcon_Edit_Name: if (args.length == 1) { entries.add(""); - entries.add(professionEditor.getRecipeEditor().getRecipeItemEditor().getRecipeIconEditor().getRecipe().getSettings().getName()); + entries.add(professionEditor.getRecipeEditor() + .getRecipeItemEditor() + .getRecipeIconEditor() + .getRecipe() + .getSettings() + .getName()); } break; case RecipeIcon_Edit_Color: @@ -342,8 +347,8 @@ public List onTabComplete(@NotNull CommandSender sender, break; } } else if (editor instanceof BrowseEditor) { - BrowseEditor browseEditor = (BrowseEditor) editor; - EditorCriteria criteria = editorCriteria.get(player.getUniqueId()); + BrowseEditor browseEditor = (BrowseEditor) editor; + EditorCriteria criteria = editorCriteria.get(player.getUniqueId()); switch (criteria) { case Browse_Edit_Name: if (args.length == 1) { @@ -544,8 +549,8 @@ private void updateProfessionName(ProfessionEditor professionEditor, String[] ar professionNameBuilder.append(arg).append(" "); } String professionName = professionNameBuilder.toString().trim(); - String oldName = professionEditor.getTable().getName(); - Player player = professionEditor.getPlayer(); + String oldName = professionEditor.getTable().getName(); + Player player = professionEditor.getPlayer(); professionEditor.getTable().setInventoryName(professionName); CodexEngine.get() .getMessageUtil() @@ -557,9 +562,9 @@ private void updateProfessionName(ProfessionEditor professionEditor, String[] ar } private void updateProfessionIcon(ProfessionEditor professionEditor, String[] args) { - String icon = args[0]; + String icon = args[0]; String oldIcon = professionEditor.getTable().getIconItem().getID(); - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); if (!isValidItem(icon)) { CodexEngine.get().getMessageUtil().sendMessage("editor.invalidItem", player, new MessageData("item", icon)); return; @@ -592,7 +597,7 @@ private void updateCategory(ProfessionEditor professionEditor, String[] args, Ed String categoryName = args[0]; String categoryIcon = args[1]; - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); if (!isValidItem(categoryIcon)) { CodexEngine.get() .getMessageUtil() @@ -686,7 +691,7 @@ private void updatePatternItemName(Editor editor, String[] args) { private void updatePatternItem(Editor editor, String[] args) { if (editor instanceof ProfessionEditor) { ProfessionEditor professionEditor = (ProfessionEditor) editor; - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); if (args.length < 2) { CodexEngine.get() .getMessageUtil() @@ -697,7 +702,7 @@ private void updatePatternItem(Editor editor, String[] args) { } try { Material material = Material.valueOf(args[0].toUpperCase()); - int amount = Integer.parseInt(args[1]); + int amount = Integer.parseInt(args[1]); professionEditor.getPatternItemsEditor().getPatternItemEditor().getBuilder().material(material); professionEditor.getPatternItemsEditor().getPatternItemEditor().getBuilder().amount(amount); @@ -718,7 +723,7 @@ private void updatePatternItem(Editor editor, String[] args) { } } else if (editor instanceof BrowseEditor) { BrowseEditor browseEditor = (BrowseEditor) editor; - Player player = browseEditor.getPlayer(); + Player player = browseEditor.getPlayer(); if (args.length < 2) { CodexEngine.get() .getMessageUtil() @@ -729,7 +734,7 @@ private void updatePatternItem(Editor editor, String[] args) { } try { Material material = Material.valueOf(args[0].toUpperCase()); - int amount = Integer.parseInt(args[1]); + int amount = Integer.parseInt(args[1]); browseEditor.getPatternItemsEditor().getPatternItemEditor().getBuilder().material(material); browseEditor.getPatternItemsEditor().getPatternItemEditor().getBuilder().amount(amount); @@ -754,7 +759,7 @@ private void updatePatternItem(Editor editor, String[] args) { private void addPatternItemLore(Editor editor, String[] args) { if (editor instanceof ProfessionEditor) { ProfessionEditor professionEditor = (ProfessionEditor) editor; - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); if (args.length < 1) { CodexEngine.get() .getMessageUtil() @@ -778,7 +783,7 @@ private void addPatternItemLore(Editor editor, String[] args) { new MessageData("lore", ChatUT.hexString(builder.toString()))); } else if (editor instanceof BrowseEditor) { BrowseEditor browseEditor = (BrowseEditor) editor; - Player player = browseEditor.getPlayer(); + Player player = browseEditor.getPlayer(); if (args.length < 1) { CodexEngine.get() .getMessageUtil() @@ -803,7 +808,7 @@ private void addPatternItemLore(Editor editor, String[] args) { private void addPatternItemCommand(Editor editor, String[] args) { if (editor instanceof ProfessionEditor) { ProfessionEditor professionEditor = (ProfessionEditor) editor; - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); if (args.length < 3) { CodexEngine.get() .getMessageUtil() @@ -813,7 +818,7 @@ private void addPatternItemCommand(Editor editor, String[] args) { StringBuilder commandBuilder = new StringBuilder(); try { CommandType commandType = CommandType.valueOf(args[0].toUpperCase()); - int delay = Integer.parseInt(args[1]); + int delay = Integer.parseInt(args[1]); commandBuilder = new StringBuilder(); for (int i = 2; i < args.length; i++) { commandBuilder.append(args[i]); @@ -838,7 +843,7 @@ private void addPatternItemCommand(Editor editor, String[] args) { } } else if (editor instanceof BrowseEditor) { BrowseEditor browseEditor = (BrowseEditor) editor; - Player player = browseEditor.getPlayer(); + Player player = browseEditor.getPlayer(); if (args.length < 3) { CodexEngine.get() .getMessageUtil() @@ -848,7 +853,7 @@ private void addPatternItemCommand(Editor editor, String[] args) { StringBuilder commandBuilder = new StringBuilder(); try { CommandType commandType = CommandType.valueOf(args[0].toUpperCase()); - int delay = Integer.parseInt(args[1]); + int delay = Integer.parseInt(args[1]); commandBuilder = new StringBuilder(); for (int i = 2; i < args.length; i++) { commandBuilder.append(args[i]); @@ -877,7 +882,7 @@ private void addPatternItemCommand(Editor editor, String[] args) { private void addPatternEnchants(Editor editor, String[] args) { if (editor instanceof ProfessionEditor) { ProfessionEditor professionEditor = (ProfessionEditor) editor; - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); if (args.length < 1) { CodexEngine.get() .getMessageUtil() @@ -910,7 +915,7 @@ private void addPatternEnchants(Editor editor, String[] args) { professionEditor.getPatternItemsEditor().getPatternItemEditor().reload(true); } else if (editor instanceof BrowseEditor) { BrowseEditor browseEditor = (BrowseEditor) editor; - Player player = browseEditor.getPlayer(); + Player player = browseEditor.getPlayer(); if (args.length < 1) { CodexEngine.get() .getMessageUtil() @@ -947,7 +952,7 @@ private void addPatternEnchants(Editor editor, String[] args) { public void addPatternFlags(Editor editor, String[] args) { if (editor instanceof ProfessionEditor) { ProfessionEditor professionEditor = (ProfessionEditor) editor; - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); if (args.length < 1) { CodexEngine.get() .getMessageUtil() @@ -975,7 +980,7 @@ public void addPatternFlags(Editor editor, String[] args) { } } else if (editor instanceof BrowseEditor) { BrowseEditor browseEditor = (BrowseEditor) editor; - Player player = browseEditor.getPlayer(); + Player player = browseEditor.getPlayer(); if (args.length < 1) { CodexEngine.get() .getMessageUtil() @@ -1016,8 +1021,8 @@ private void updateRecipeName(ProfessionEditor professionEditor, String[] args) } String recipeName = args[0]; - Player player = professionEditor.getPlayer(); - String oldName = professionEditor.getRecipeEditor().getRecipeItemEditor().getRecipeName(); + Player player = professionEditor.getPlayer(); + String oldName = professionEditor.getRecipeEditor().getRecipeItemEditor().getRecipeName(); for (Recipe recipe : professionEditor.getTable().getRecipes().values()) { if (recipe.getName().equalsIgnoreCase(recipeName)) { CodexEngine.get() @@ -1047,7 +1052,7 @@ private void addRecipeCommand(ProfessionEditor professionEditor, String[] args) StringBuilder commandBuilder = new StringBuilder(); try { CommandType commandType = CommandType.valueOf(args[0].toUpperCase()); - int delay = Integer.parseInt(args[1]); + int delay = Integer.parseInt(args[1]); commandBuilder = new StringBuilder(); for (int i = 2; i < args.length; i++) { commandBuilder.append(args[i]); @@ -1085,7 +1090,7 @@ private void addRecipeItem(ProfessionEditor professionEditor, String[] args) { } try { String itemName = args[0]; - int amount = Integer.parseInt(args[1]); + int amount = Integer.parseInt(args[1]); if (!isValidItem(itemName)) { CodexEngine.get() .getMessageUtil() @@ -1119,8 +1124,8 @@ private void addNewRecipe(ProfessionEditor professionEditor, String[] args) { } try { String recipeName = args[0]; - String itemName = args[1]; - int amount = Integer.parseInt(args[2]); + String itemName = args[1]; + int amount = Integer.parseInt(args[2]); for (Recipe recipe : professionEditor.getTable().getRecipes().values()) { if (recipe.getName().equalsIgnoreCase(recipeName)) { CodexEngine.get() @@ -1188,7 +1193,7 @@ private void updateRecipeItems(ProfessionEditor professionEditor, String[] args, } try { String itemName = args[0]; - int amount = Integer.parseInt(args[1]); + int amount = Integer.parseInt(args[1]); if (!isValidItem(itemName)) { CodexEngine.get() .getMessageUtil() @@ -1316,9 +1321,9 @@ private void addRecipeConditions(ProfessionEditor professionEditor, String[] arg new MessageData("syntax", " ")); return; } - String conditionKey = args[0]; + String conditionKey = args[0]; String conditionValue = args[1]; - int level = Integer.parseInt(args[2]); + int level = Integer.parseInt(args[2]); switch (conditionKey) { case "professions": @@ -1509,9 +1514,9 @@ private void updateBrowseName(BrowseEditor browseEditor, String[] args) { for (String arg : args) { builder.append(arg).append(" "); } - String name = builder.toString().trim(); + String name = builder.toString().trim(); String oldName = browseEditor.getName(); - Player player = browseEditor.getPlayer(); + Player player = browseEditor.getPlayer(); browseEditor.setName(name); CodexEngine.get() .getMessageUtil() @@ -1524,7 +1529,7 @@ private void updateBrowseName(BrowseEditor browseEditor, String[] args) { private void addNewProfession(BrowseEditor browseEditor, String[] args) { String professionName = args[0]; - Player player = browseEditor.getPlayer(); + Player player = browseEditor.getPlayer(); if (!ProfessionsCfg.getMap().containsKey(professionName)) { CodexEngine.get() .getMessageUtil() @@ -1562,7 +1567,7 @@ private void addBrowseIngredient(BrowseEditor browseEditor, String[] args) { } try { String itemName = args[0]; - int amount = Integer.parseInt(args[1]); + int amount = Integer.parseInt(args[1]); if (!isValidItem(itemName)) { CodexEngine.get() .getMessageUtil() @@ -1570,7 +1575,7 @@ private void addBrowseIngredient(BrowseEditor browseEditor, String[] args) { return; } - int i = 0; + int i = 0; boolean found = false; for (RecipeItem ingredient : browseEditor.getBrowseProfessionsEditor() .getBrowseProfessionEditor() @@ -1622,9 +1627,9 @@ private void addBrowseConditions(BrowseEditor browseEditor, String[] args) { new MessageData("syntax", " ")); return; } - String conditionKey = args[0]; + String conditionKey = args[0]; String conditionValue = args[1]; - int level = Integer.parseInt(args[2]); + int level = Integer.parseInt(args[2]); switch (conditionKey) { case "professions": @@ -1812,7 +1817,7 @@ public void updateRecipeIconName(ProfessionEditor professionEditor, String[] arg } String iconName = args[0]; - Player player = professionEditor.getPlayer(); + Player player = professionEditor.getPlayer(); professionEditor.getRecipeEditor().getRecipeItemEditor().getRecipe().getSettings().setName(iconName); CodexEngine.get() .getMessageUtil() @@ -1836,7 +1841,7 @@ public void addRecipeIconLore(ProfessionEditor professionEditor, String[] args) for (String arg : args) { loreBuilder.append(arg).append(" "); } - String lore = loreBuilder.toString().trim(); + String lore = loreBuilder.toString().trim(); Player player = professionEditor.getPlayer(); professionEditor.getRecipeEditor().getRecipeItemEditor().getRecipe().getSettings().getLore().add(lore); CodexEngine.get() @@ -1897,8 +1902,9 @@ public void updateRecipeIconColor(ProfessionEditor professionEditor, String[] ar Player player = professionEditor.getPlayer(); try { - String[] data = args[0].split(","); - Color color = Color.fromRGB(Integer.parseInt(data[0]), Integer.parseInt(data[1]), Integer.parseInt(data[2])); + String[] data = args[0].split(","); + Color color = + Color.fromRGB(Integer.parseInt(data[0]), Integer.parseInt(data[1]), Integer.parseInt(data[2])); professionEditor.getRecipeEditor().getRecipeItemEditor().getRecipe().getSettings().setColor(args[0]); CodexEngine.get() .getMessageUtil() @@ -1924,7 +1930,7 @@ public void addRecipeIconCommand(ProfessionEditor professionEditor, String[] arg StringBuilder commandBuilder = new StringBuilder(); try { CommandType commandType = CommandType.valueOf(args[0].toUpperCase()); - int delay = Integer.parseInt(args[1]); + int delay = Integer.parseInt(args[1]); commandBuilder = new StringBuilder(); for (int i = 2; i < args.length; i++) { commandBuilder.append(args[i]); @@ -1962,7 +1968,7 @@ public void addRecipeIconEnchants(ProfessionEditor professionEditor, String[] ar } try { Enchantment enchantment = Enchantment.getByKey(NamespacedKey.minecraft(args[0].toLowerCase())); - int level = Integer.parseInt(args[1]); + int level = Integer.parseInt(args[1]); if (enchantment == null) { CodexEngine.get() .getMessageUtil() diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java b/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java index dfc5df7..a022eec 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java @@ -87,13 +87,13 @@ public static CalculatedProfession create(ProfessionConditions conditions, List> eqItems = Recipe.getItems(items); - Collection localPattern = new HashSet<>(conditions.getRequiredItems()); + Collection localPattern = new HashSet<>(conditions.getRequiredItems()); for (Iterator it = localPattern.iterator(); it.hasNext(); ) { RecipeItem recipeItem = it.next(); ItemStack recipeItemStack = recipeItem.getItemStack(); ItemStack recipeItemStackOne = recipeItemStack.clone(); recipeItemStackOne.setAmount(1); - Pair eqEntry = null; + Pair eqEntry = null; for (Pair entry : eqItems) { ItemStack item = entry.getKey().clone(); if (CalculatedRecipe.isSimilar(recipeItemStackOne, item)) { diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionResults.java b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionResults.java index 2fe2b08..c71b128 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionResults.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionResults.java @@ -20,11 +20,11 @@ public class ProfessionResults implements ConfigurationSerializable { private final String profession; // Rewards - private long professionExp; - private int vanillaExp; - private List items = new LinkedList<>(); - private List itemNames = new LinkedList<>(); - private List commands = new LinkedList<>(); + private long professionExp; + private int vanillaExp; + private List items = new LinkedList<>(); + private List itemNames = new LinkedList<>(); + private List commands = new LinkedList<>(); public ProfessionResults(String profession, long professionExp, @@ -46,8 +46,14 @@ public ProfessionResults(String profession, ConfigurationSection config) { this.profession = profession; this.professionExp = config.getLong("rewards.professionExp"); this.vanillaExp = config.getInt("rewards.vanillaExp"); - this.commands = config.getList("rewards.commands", new LinkedList<>()).stream().map(entry -> new DelayedCommand()).collect(Collectors.toList()); - this.itemNames = config.getList("rewards.items", new LinkedList<>()).stream().map(Object::toString).collect(Collectors.toList()); + this.commands = config.getList("rewards.commands", new LinkedList<>()) + .stream() + .map(entry -> new DelayedCommand()) + .collect(Collectors.toList()); + this.itemNames = config.getList("rewards.items", new LinkedList<>()) + .stream() + .map(Object::toString) + .collect(Collectors.toList()); for (String itemName : itemNames) { this.items.add(RecipeItem.fromConfig(itemName)); } @@ -72,7 +78,8 @@ public ProfessionResults(String profession, DeserializationWorker dw) { this.vanillaExp = 0; } - List> commands = (List>) resultsSection.getOrDefault("commands", new ArrayList<>()); + List> commands = + (List>) resultsSection.getOrDefault("commands", new ArrayList<>()); if (commands != null) { for (Map command : commands) { this.commands.add(new DelayedCommand(command)); @@ -94,7 +101,8 @@ public ProfessionResults(String profession, DeserializationWorker dw) { Map resultMap = new HashMap<>(); resultMap.put("professionExp", this.professionExp); resultMap.put("vanillaExp", this.vanillaExp); - resultMap.put("commands", new ArrayList<>(this.commands.stream().map(DelayedCommand::serialize).collect(Collectors.toList()))); + resultMap.put("commands", + new ArrayList<>(this.commands.stream().map(DelayedCommand::serialize).collect(Collectors.toList()))); resultMap.put("items", new ArrayList<>(this.itemNames)); return SerializationBuilder.start(4).append("results", resultMap).build(); } @@ -113,6 +121,6 @@ public static ProfessionResults copy(ProfessionResults results) { } public boolean hasCommandsOrItems() { - return professionExp > 0 || vanillaExp > 0 || !commands.isEmpty() || !itemNames.isEmpty(); + return professionExp > 0 || vanillaExp > 0 || !commands.isEmpty() || !itemNames.isEmpty(); } } diff --git a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java index 6ebbc9d..a7d1c53 100644 --- a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java +++ b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java @@ -68,7 +68,7 @@ public CraftingQueue(Player player, String profession, Category category) { continue; } int remaining = item.getRecipe().getCraftingTime() - item.getSavedSeconds(); - int apply = Math.min(offlineSeconds, remaining); + int apply = Math.min(offlineSeconds, remaining); item.progressOffline(apply); offlineSeconds -= apply; } diff --git a/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java b/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java index ba3c94f..b0b2871 100644 --- a/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java +++ b/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java @@ -6,7 +6,6 @@ import lombok.Setter; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; -import studio.magemonkey.fusion.cfg.Cfg; import studio.magemonkey.fusion.cfg.ProfessionsCfg; import studio.magemonkey.fusion.data.professions.pattern.Category; import studio.magemonkey.fusion.data.recipes.Recipe; diff --git a/src/main/java/studio/magemonkey/fusion/data/recipes/CraftingTable.java b/src/main/java/studio/magemonkey/fusion/data/recipes/CraftingTable.java index a74e7ae..42cc65d 100644 --- a/src/main/java/studio/magemonkey/fusion/data/recipes/CraftingTable.java +++ b/src/main/java/studio/magemonkey/fusion/data/recipes/CraftingTable.java @@ -138,10 +138,10 @@ public CraftingTable(Map map) throws MissingProviderException, M for (Map recipeData : recipesSection) { long rStart = System.currentTimeMillis(); try { - Map settings = (Map) recipeData.get("settings"); - Map iconSettings = (Map) settings.get("icon"); + Map settings = (Map) recipeData.get("settings"); + Map iconSettings = (Map) settings.get("icon"); - String itemResult = (String) iconSettings.get("item"); + String itemResult = (String) iconSettings.get("item"); // TODO First contact with ResultItem / Icon if (itemResult.startsWith("DIVINITY_item_generator")) { buildDivinityResultItem(recipeData, itemResult); @@ -255,11 +255,12 @@ private void buildDivinityResultItem(Map recipeData, String itemResult) { } String recipeName = (String) recipeData.get("name"); - int i = 0; + int i = 0; for (Map.Entry> nameEntry : names.entrySet()) { for (String name : nameEntry.getValue()) { - DivinityRecipeMeta meta = new DivinityRecipeMeta(recipeName, entry, level, amount, nameEntry.getKey(), name); - Recipe recipe = new Recipe(this, (Map) recipeData, meta); + DivinityRecipeMeta meta = + new DivinityRecipeMeta(recipeName, entry, level, amount, nameEntry.getKey(), name); + Recipe recipe = new Recipe(this, (Map) recipeData, meta); recipe.setName(recipe.getName() + "::" + i); recipes.put(recipe.getName(), recipe); if (category != null) { diff --git a/src/main/java/studio/magemonkey/fusion/gui/editors/professions/ProfessionEditor.java b/src/main/java/studio/magemonkey/fusion/gui/editors/professions/ProfessionEditor.java index 3a77727..3f4f6b8 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/editors/professions/ProfessionEditor.java +++ b/src/main/java/studio/magemonkey/fusion/gui/editors/professions/ProfessionEditor.java @@ -31,7 +31,7 @@ public class ProfessionEditor extends Editor implements Listener { private PatternItemsEditor categoryPatternItemEditor; private PatternEditor categoryPatternEditor; private CategoryEditor categoryEditor; - private RecipeEditor recipeEditor; + private RecipeEditor recipeEditor; public ProfessionEditor(Player player, String profession) { super(null, EditorRegistry.getProfessionEditorCfg().getTitle(profession), 45); diff --git a/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeIconEditor.java b/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeIconEditor.java index 6674f44..a466071 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeIconEditor.java +++ b/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeIconEditor.java @@ -57,19 +57,21 @@ public void onInventoryClick(InventoryClickEvent event) { switch (event.getSlot()) { case 10 -> { - if(event.isLeftClick()) { + if (event.isLeftClick()) { FusionEditorCommand.suggestUsage(player, EditorCriteria.RecipeIcon_Edit_Name, "/fusion-editor " + recipe.getSettings().getName()); - } else if(event.isRightClick()){ + } else if (event.isRightClick()) { recipe.getSettings().setName(null); hasChanges = true; } } case 11 -> { if (event.isLeftClick()) { - FusionEditorCommand.suggestUsage(player, EditorCriteria.RecipeIcon_Edit_Lore, "/fusion-editor "); - } else if(event.isRightClick()){ + FusionEditorCommand.suggestUsage(player, + EditorCriteria.RecipeIcon_Edit_Lore, + "/fusion-editor "); + } else if (event.isRightClick()) { if (recipe.getSettings().getLore().isEmpty()) { return; } @@ -86,18 +88,22 @@ public void onInventoryClick(InventoryClickEvent event) { recipe.getSettings().setCustomModelData(recipe.getSettings().getCustomModelData() + amount); } else if (event.isRightClick()) { if (recipe.getSettings().getCustomModelData() == -1) return; - recipe.getSettings().setCustomModelData(Math.max(recipe.getSettings().getCustomModelData() - amount, -1)); + recipe.getSettings() + .setCustomModelData(Math.max(recipe.getSettings().getCustomModelData() - amount, -1)); } hasChanges = true; } case 16 -> { if (event.isLeftClick()) { - FusionEditorCommand.suggestUsage(player, EditorCriteria.RecipeIcon_Add_Commands, "/fusion-editor "); - } else if(event.isRightClick()){ + FusionEditorCommand.suggestUsage(player, + EditorCriteria.RecipeIcon_Add_Commands, + "/fusion-editor "); + } else if (event.isRightClick()) { if (recipe.getSettings().getCommandsOnClick().isEmpty()) { return; } - DelayedCommand command = new ArrayList<>(recipe.getSettings().getCommandsOnClick()).get(recipe.getSettings().getCommandsOnClick().size() - 1); + DelayedCommand command = new ArrayList<>(recipe.getSettings().getCommandsOnClick()).get( + recipe.getSettings().getCommandsOnClick().size() - 1); recipe.getSettings().getCommandsOnClick().remove(command); hasChanges = true; } @@ -108,11 +114,11 @@ public void onInventoryClick(InventoryClickEvent event) { return; } case 29 -> { - if(event.isLeftClick()) { + if (event.isLeftClick()) { FusionEditorCommand.suggestUsage(player, EditorCriteria.RecipeIcon_Edit_Color, "/fusion-editor " + recipe.getSettings().getColor()); - } else if(event.isRightClick()){ + } else if (event.isRightClick()) { recipe.getSettings().setColor(null); hasChanges = true; } @@ -132,20 +138,24 @@ public void onInventoryClick(InventoryClickEvent event) { } // Get last entry and remove it Enchantment lastEnchantment = - new ArrayList<>(recipe.getSettings().getEnchantments().keySet()).get(recipe.getSettings().getEnchantments().size() - 1); + new ArrayList<>(recipe.getSettings().getEnchantments().keySet()).get( + recipe.getSettings().getEnchantments().size() - 1); recipe.getSettings().getEnchantments().remove(lastEnchantment); hasChanges = true; } } case 33 -> { if (event.isLeftClick()) { - FusionEditorCommand.suggestUsage(player, EditorCriteria.RecipeIcon_Add_Flags, "/fusion-editor "); + FusionEditorCommand.suggestUsage(player, + EditorCriteria.RecipeIcon_Add_Flags, + "/fusion-editor "); } else if (event.isRightClick()) { if (recipe.getSettings().getFlags().isEmpty()) { return; } // Remove the last flag from the set - ItemFlag lastFlag = new ArrayList<>(recipe.getSettings().getFlags()).get(recipe.getSettings().getFlags().size() - 1); + ItemFlag lastFlag = new ArrayList<>(recipe.getSettings().getFlags()).get( + recipe.getSettings().getFlags().size() - 1); recipe.getSettings().getFlags().remove(lastFlag); hasChanges = true; } diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java index d8c0aa7..ff9de7e 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java +++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java @@ -8,24 +8,24 @@ /** * Immutable fingerprint for an ItemStack that matches CalculatedRecipe.isSimilar(...) logic. - * + *

* We compare: - * - Material - * - customModelData (if present) - * - displayName (if present) - * - lore lines (if present) - * - all enchantments (if present) - * - unbreakable flag - * - durability (if Damageable) + * - Material + * - customModelData (if present) + * - displayName (if present) + * - lore lines (if present) + * - all enchantments (if present) + * - unbreakable flag + * - durability (if Damageable) */ public class IngredientFingerprint { - private final Material type; - private final int customModelData; - private final String displayName; - private final List lore; + private final Material type; + private final int customModelData; + private final String displayName; + private final List lore; private final Map enchantments; - private final boolean unbreakable; - private final int durability; + private final boolean unbreakable; + private final int durability; public IngredientFingerprint(Material type, int customModelData, @@ -43,17 +43,19 @@ public IngredientFingerprint(Material type, this.durability = durability; } - /** Build an IngredientFingerprint by examining a live ItemStack. */ + /** + * Build an IngredientFingerprint by examining a live ItemStack. + */ public static IngredientFingerprint of(ItemStack is) { - Material mat = is.getType(); + Material mat = is.getType(); ItemMeta meta = is.getItemMeta(); - int cmd = 0; - String name = ""; - List loreList = Collections.emptyList(); + int cmd = 0; + String name = ""; + List loreList = Collections.emptyList(); Map enchantsMap = Collections.emptyMap(); - boolean unbreak = false; - int dmg = 0; + boolean unbreak = false; + int dmg = 0; if (meta != null) { if (meta.hasCustomModelData()) { @@ -65,7 +67,7 @@ public static IngredientFingerprint of(ItemStack is) { if (meta.hasLore()) { loreList = new ArrayList<>(Objects.requireNonNull(meta.getLore())); } - Map raw = meta.getEnchants(); + Map raw = meta.getEnchants(); if (!raw.isEmpty()) { enchantsMap = new HashMap<>(raw); } diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java index d2b0688..1418129 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java +++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java @@ -13,15 +13,15 @@ /** * Helper to build a small MD5 fingerprint of an entire PlayerInventory. * We incorporate: - * - Material ordinal - * - amount - * - customModelData - * - displayName - * - lore lines - * - enchantments - * - unbreakable - * - durability if Damageable - * + * - Material ordinal + * - amount + * - customModelData + * - displayName + * - lore lines + * - enchantments + * - unbreakable + * - durability if Damageable + *

* If MD5 is not available, we fall back to a simple int‐hash of slot hashCodes. */ public class InventoryFingerprint { diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java index 154d1b9..49fc7c1 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java +++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java @@ -9,16 +9,16 @@ public class RecipeCacheKey { private final String recipeId; private final byte[] inventoryHash; - private final int playerLevel; + private final int playerLevel; private final double playerMoney; // TODO add: /* - * - Vanilla Exp - * - Conditions.McMMO Map - * - Conditions.Fabled Map - * - Conditions.Aura Map - * - Conditions.ProfessionLevels Map + * - Vanilla Exp + * - Conditions.McMMO Map + * - Conditions.Fabled Map + * - Conditions.Aura Map + * - Conditions.ProfessionLevels Map */ public RecipeCacheKey(String recipeId, byte[] inventoryHash, int playerLevel, double playerMoney) { diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java index 2f9435f..ef9204a 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java +++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java @@ -29,7 +29,7 @@ public class RecipeGuiEventRouter implements Listener { * We fetch that player’s FusionPlayer via PlayerLoader.getPlayer(Player). */ private RecipeGui findGuiFor(Player player, Inventory inv) { - if(!ProfessionGuiRegistry.getLatestRecipeGui().containsKey(player.getUniqueId())) + if (!ProfessionGuiRegistry.getLatestRecipeGui().containsKey(player.getUniqueId())) return null; RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(player.getUniqueId()); if (gui.getInventory().equals(inv)) { @@ -80,7 +80,7 @@ public void onInventoryClose(InventoryCloseEvent event) { @EventHandler(ignoreCancelled = true) public void onPlayerDrop(PlayerDropItemEvent event) { - Player p = event.getPlayer(); + Player p = event.getPlayer(); RecipeGui gui = findGuiFor(p, p.getOpenInventory().getTopInventory()); if (gui == null) return; @@ -100,13 +100,13 @@ public void onItemPickup(EntityPickupItemEvent event) { @EventHandler(ignoreCancelled = true) public void onPlayerQuit(PlayerQuitEvent event) { - Player p = event.getPlayer(); + Player p = event.getPlayer(); FusionPlayer fp = PlayerLoader.getPlayer(p); if (fp == null) return; // On quit, close and remove *all* open RecipeGuis for that player RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(p.getUniqueId()); - if(gui == null) return; + if (gui == null) return; gui.close(p, gui.getInventory()); } } diff --git a/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java b/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java index e220850..8503e36 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java +++ b/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java @@ -93,7 +93,7 @@ public ItemStack canHoldItem(ItemStack item) { /** * -- GETTER -- - * Returns base slot type. + * Returns base slot type. * */ protected final SlotType slotType; diff --git a/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java b/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java index 8d586c1..0503766 100644 --- a/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java +++ b/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java @@ -2,7 +2,8 @@ import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; /** * Simple test to verify force command methods exist and have the expected signatures. @@ -16,31 +17,31 @@ public void testForceCommandMethodsExist() { // Verify that all force command methods exist as public static methods try { // Check forceJoinProfession method exists - CommandMechanics.class.getDeclaredMethod("forceJoinProfession", - org.bukkit.command.CommandSender.class, String[].class); - + CommandMechanics.class.getDeclaredMethod("forceJoinProfession", + org.bukkit.command.CommandSender.class, String[].class); + // Check forceLeaveProfession method exists - CommandMechanics.class.getDeclaredMethod("forceLeaveProfession", - org.bukkit.command.CommandSender.class, String[].class); - + CommandMechanics.class.getDeclaredMethod("forceLeaveProfession", + org.bukkit.command.CommandSender.class, String[].class); + // Check forceStats method exists - CommandMechanics.class.getDeclaredMethod("forceStats", - org.bukkit.command.CommandSender.class, String[].class); - + CommandMechanics.class.getDeclaredMethod("forceStats", + org.bukkit.command.CommandSender.class, String[].class); + // Check forceMaster method exists - CommandMechanics.class.getDeclaredMethod("forceMaster", - org.bukkit.command.CommandSender.class, String[].class); - + CommandMechanics.class.getDeclaredMethod("forceMaster", + org.bukkit.command.CommandSender.class, String[].class); + // Check forceShow method exists - CommandMechanics.class.getDeclaredMethod("forceShow", - org.bukkit.command.CommandSender.class, String[].class); - + CommandMechanics.class.getDeclaredMethod("forceShow", + org.bukkit.command.CommandSender.class, String[].class); + } catch (NoSuchMethodException e) { fail("Force command method not found: " + e.getMessage()); } } - @Test + @Test public void testCommandArgumentValidation() { // This test documents expected argument patterns for force commands // forcejoin = 3 args @@ -48,7 +49,7 @@ public void testCommandArgumentValidation() { // forcestats = 2 args // forcemaster = 3 args // forceshow = 2 args - + // Test would verify argument length checking but requires mocking the full environment assertTrue(true, "Force commands require 2-3 arguments as documented"); } From b418ce3774da9ef0750e0c69c556696fcd8aa20c Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 14 Sep 2025 18:54:02 +0200 Subject: [PATCH 10/34] fixed categories not being openable when registered in the gui --- .../java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java | 1 - .../java/studio/magemonkey/fusion/commands/Commands.java | 3 +-- .../studio/magemonkey/fusion/data/player/FusionPlayer.java | 1 + .../fusion/data/professions/ProfessionSettings.java | 2 +- src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java | 5 ++++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java index e91be64..d1f70ee 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java @@ -121,7 +121,6 @@ private static void loadProfessions(File root) { cfgs.put(ct.getName(), cfg); files.put(ct.getName(), file); injectProfessionLevelConfig(ct, file); - } catch (Exception e) { e.printStackTrace(); Fusion.getInstance().getLogger().warning("Can't load crafting table: " + e.getMessage()); diff --git a/src/main/java/studio/magemonkey/fusion/commands/Commands.java b/src/main/java/studio/magemonkey/fusion/commands/Commands.java index 50c5dc1..9af8c83 100644 --- a/src/main/java/studio/magemonkey/fusion/commands/Commands.java +++ b/src/main/java/studio/magemonkey/fusion/commands/Commands.java @@ -125,8 +125,7 @@ public List onTabComplete(@NotNull CommandSender sender, return List.of(); List entries = new ArrayList<>(); - List professions = new ArrayList<>(); - professions = new ArrayList<>(PlayerLoader.getPlayer((player).getUniqueId()).getProfessions()); + List professions = new ArrayList<>(PlayerLoader.getPlayer((player).getUniqueId()).getProfessions()); if (args.length == 1) { if (sender.hasPermission("fusion.browse") && "browse".startsWith(args[0])) entries.add("browse"); diff --git a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java index bd8b9cd..465418a 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java @@ -3,6 +3,7 @@ import lombok.Getter; import lombok.Setter; import org.bukkit.Bukkit; +import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import studio.magemonkey.fusion.cfg.sql.SQLManager; diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java index dc14c68..7a4cba6 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java @@ -274,7 +274,7 @@ private void generateIcon(String namespace) { meta = potionMeta; } builder = builder.data(meta); - this.recipeItem = new RecipeCustomItem(builder, iconReference.getAmount(), false); + this.recipeItem = new RecipeCustomItem(builder, 1, false); } } diff --git a/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java b/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java index 1f1a423..55924e8 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java +++ b/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java @@ -36,6 +36,7 @@ public class CategoryGui implements Listener { private final CraftingTable table; private Inventory inventory; + private final Map allCategoriesMap = new HashMap<>(); private final Map categories = new HashMap<>(); private int page = 0; private int nextPage = -1; @@ -103,6 +104,8 @@ public void reloadCategories() { : CodexEngine.get().getVault().getBalance(player)) }); + allCategories.forEach((category) -> allCategoriesMap.putIfAbsent(category.getName(), new RecipeGui(player, table, category))); + for (int k = (page * pageSize), e = Math.min(slots.length, allCategoryArray.length); (k < allCategoryArray.length) && (i < e); k++, i++) { @@ -255,7 +258,7 @@ private void nextPage() { public void open(Player player, Category category) { if (category == null) open(player); else { - for (RecipeGui gui : categories.values()) { + for (RecipeGui gui : allCategoriesMap.values()) { if (gui.getCategory().equals(category)) { gui.open(player); return; From d18c7967d261b7c01b72f9a2ac31927944c03034 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:38:57 +0200 Subject: [PATCH 11/34] remove latest gui on leave --- .../magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java index ef9204a..c5de114 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java +++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java @@ -108,5 +108,6 @@ public void onPlayerQuit(PlayerQuitEvent event) { RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(p.getUniqueId()); if (gui == null) return; gui.close(p, gui.getInventory()); + ProfessionGuiRegistry.getLatestRecipeGui().remove(p.getUniqueId()); } } From 02e07a61d946b865faefd21e534da9b72f718771 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:39:28 +0200 Subject: [PATCH 12/34] caching money to reduce lags through db-connections --- .../magemonkey/fusion/gui/RecipeGui.java | 6 +- .../magemonkey/fusion/hook/VaultHook.java | 67 +++++++++++++++++++ 2 files changed, 69 insertions(+), 4 deletions(-) create mode 100644 src/main/java/studio/magemonkey/fusion/hook/VaultHook.java diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java index 02dfcda..81d107c 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java +++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java @@ -47,12 +47,12 @@ import studio.magemonkey.fusion.gui.recipe.InventoryFingerprint; import studio.magemonkey.fusion.gui.recipe.RecipeCacheKey; import studio.magemonkey.fusion.gui.slot.Slot; +import studio.magemonkey.fusion.hook.VaultHook; import studio.magemonkey.fusion.util.ChatUT; import studio.magemonkey.fusion.util.ExperienceManager; import studio.magemonkey.fusion.util.PlayerUtil; import java.util.*; -import java.util.concurrent.ConcurrentHashMap; @Getter public class RecipeGui implements Listener { @@ -264,9 +264,7 @@ public void reloadRecipes() { // byte[] newHash = InventoryFingerprint.fingerprint(player); int newLevel = table.getLevelFunction().getLevel(player); - double newMoney = (CodexEngine.get().getVault() == null) - ? 0.0 - : CodexEngine.get().getVault().getBalance(player); + double newMoney = VaultHook.getBalance(player); boolean invChanged = !Arrays.equals(newHash, lastInventoryHash); boolean levelChanged = (newLevel != lastSeenLevel); diff --git a/src/main/java/studio/magemonkey/fusion/hook/VaultHook.java b/src/main/java/studio/magemonkey/fusion/hook/VaultHook.java new file mode 100644 index 0000000..029c31c --- /dev/null +++ b/src/main/java/studio/magemonkey/fusion/hook/VaultHook.java @@ -0,0 +1,67 @@ +package studio.magemonkey.fusion.hook; + +import org.apache.commons.lang3.tuple.Pair; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; +import studio.magemonkey.codex.CodexEngine; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; + +public class VaultHook { + + private static BukkitTask task; + private static Map> storedBalances = new HashMap<>(); + private static int thresholdSeconds = 10; + + public static double getBalance(Player player) { + if (storedBalances.containsKey(player.getUniqueId())) { + Pair balanceData = storedBalances.get(player.getUniqueId()); + double balance = balanceData.getLeft(); + LocalDateTime timestamp = balanceData.getRight(); + if (timestamp.plusSeconds(thresholdSeconds).isBefore(LocalDateTime.now())) { + double updatedBalance = CodexEngine.get().getVault() != null ? CodexEngine.get().getVault().getBalance(player) : 0.0; + storedBalances.put(player.getUniqueId(), Pair.of(updatedBalance, LocalDateTime.now())); + return updatedBalance; + } else { + return balance; + } + } else { + double balance = CodexEngine.get().getVault() != null ? CodexEngine.get().getVault().getBalance(player) : 0.0; + storedBalances.put(player.getUniqueId(), Pair.of(balance, LocalDateTime.now())); + return balance; + } + } + + public static void startMoneyUpdateTask() { + // Schedule a repeating task to update all stored balances every thresholdSeconds + task = Bukkit.getScheduler().runTaskTimerAsynchronously(CodexEngine.get(), () -> { + for (UUID uuid : storedBalances.keySet()) { + Player player = Bukkit.getPlayer(uuid); + if (player != null && player.isOnline()) { + getBalanceAsync(player, balance -> { + storedBalances.put(uuid, Pair.of(balance, LocalDateTime.now())); + }); + } + } + }, thresholdSeconds * 20L, thresholdSeconds * 20L); + } + + public static void cancelMoneyUpdateTask() { + if (task != null) { + task.cancel(); + task = null; + } + } + + private static void getBalanceAsync(Player player, Consumer moneyConsumer) { + Bukkit.getScheduler().runTaskAsynchronously(CodexEngine.get(), () -> { + double balance = CodexEngine.get().getVault() != null ? CodexEngine.get().getVault().getBalance(player) : 0.0; + moneyConsumer.accept(balance); + }); + } +} From b9c341ce9e2b324948f4a7f3b5da8ea8365ae30d Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Wed, 17 Sep 2025 11:20:43 +0200 Subject: [PATCH 13/34] added debug for issue handling with queue data loss on rejoining --- .../studio/magemonkey/fusion/data/queue/CraftingQueue.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java index a7d1c53..68117a3 100644 --- a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java +++ b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java @@ -12,6 +12,7 @@ import studio.magemonkey.fusion.cfg.Cfg; import studio.magemonkey.fusion.cfg.ProfessionsCfg; import studio.magemonkey.fusion.cfg.sql.SQLManager; +import studio.magemonkey.fusion.data.player.FusionPlayer; import studio.magemonkey.fusion.data.player.PlayerLoader; import studio.magemonkey.fusion.data.professions.pattern.Category; import studio.magemonkey.fusion.data.recipes.Recipe; @@ -50,6 +51,10 @@ public CraftingQueue(Player player, String profession, Category category) { * queue sequentially. All items are saved with the same timestamp when * saved, so use the first item's timestamp to calculate the offline duration. */ + if(!queue.isEmpty()) { + Fusion.getInstance().getLogger().warning("[Debug] Loaded " + queue.size() + " items for " + player.getName() + " in " + profession + " - " + category.getName() + ": ItemPaths:" + queue.stream().map(item -> item.getRecipe().getRecipePath()).toList()); + } + if (Cfg.updateQueueOffline && !queue.isEmpty()) { long now = System.currentTimeMillis(); // find the first unfinished item From 9aadc3e2814c8ba9981a513d39b8a94f107be620 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:38:07 +0200 Subject: [PATCH 14/34] fixed queue data loss with properly configured primary keys --- .../java/studio/magemonkey/fusion/Fusion.java | 4 +-- .../cfg/sql/tables/FusionQueuesSQL.java | 30 +++++-------------- 2 files changed, 9 insertions(+), 25 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/Fusion.java b/src/main/java/studio/magemonkey/fusion/Fusion.java index 69b0198..59eb78d 100644 --- a/src/main/java/studio/magemonkey/fusion/Fusion.java +++ b/src/main/java/studio/magemonkey/fusion/Fusion.java @@ -127,8 +127,8 @@ public void onEnable() { LevelFunction.generate(200); this.getCommand("craft").setExecutor(new Commands()); this.getCommand("fusion-editor").setExecutor(new FusionEditorCommand()); - getServer().getPluginManager().registerEvents(this, this); - Bukkit.getPluginManager().registerEvents(new RecipeGuiEventRouter(), this); + registerListener(this); + registerListener(new RecipeGuiEventRouter()); runQueueTask(); if (hookManager.isHooked(HookType.PlaceholderAPI)) { diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java index 9af7774..8c2050b 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java @@ -22,7 +22,7 @@ public class FusionQueuesSQL { public FusionQueuesSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" - + "Id long," + + "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY," + "UUID varchar(36), " + "RecipePath varchar(100)," + "CraftingTime numeric," @@ -37,33 +37,17 @@ public FusionQueuesSQL() { } } - public long getNextId() { - try (PreparedStatement select = SQLManager.connection().prepareStatement("SELECT Count(Id) FROM " + Table)) { - ResultSet result = select.executeQuery(); - if (result.next()) { - return result.getLong(1); - } - } catch (SQLException e) { - Fusion.getInstance() - .getLogger() - .warning("[SQL:FusionQueuesSQL:getNextId] Something went wrong with the sql-connection: " - + e.getMessage()); - } - return 0; - } - public boolean setQueueItem(UUID uuid, QueueItem item) { if (item == null) return false; if (item.getId() == -1) { try (PreparedStatement insert = SQLManager.connection() .prepareStatement("INSERT INTO " + Table - + "(Id, UUID, RecipePath, Timestamp, CraftingTime, SavedSeconds) VALUES (?,?,?,?,?,?)")) { - insert.setLong(1, getNextId()); - insert.setString(2, uuid.toString()); - insert.setString(3, item.getRecipePath()); - insert.setLong(4, item.getTimestamp()); - insert.setLong(5, item.getRecipe().getCraftingTime()); - insert.setLong(6, item.getSavedSeconds()); + + "(UUID, RecipePath, Timestamp, CraftingTime, SavedSeconds) VALUES (?,?,?,?,?)")) { + insert.setString(1, uuid.toString()); + insert.setString(2, item.getRecipePath()); + insert.setLong(3, item.getTimestamp()); + insert.setLong(4, item.getRecipe().getCraftingTime()); + insert.setLong(5, item.getSavedSeconds()); insert.execute(); return true; } catch (SQLException e) { From 362ff2a5074532c3d477b8c8276a565a564383c0 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Wed, 17 Sep 2025 23:38:32 +0200 Subject: [PATCH 15/34] removed debug --- .../studio/magemonkey/fusion/data/queue/CraftingQueue.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java index 68117a3..def10b6 100644 --- a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java +++ b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java @@ -51,10 +51,6 @@ public CraftingQueue(Player player, String profession, Category category) { * queue sequentially. All items are saved with the same timestamp when * saved, so use the first item's timestamp to calculate the offline duration. */ - if(!queue.isEmpty()) { - Fusion.getInstance().getLogger().warning("[Debug] Loaded " + queue.size() + " items for " + player.getName() + " in " + profession + " - " + category.getName() + ": ItemPaths:" + queue.stream().map(item -> item.getRecipe().getRecipePath()).toList()); - } - if (Cfg.updateQueueOffline && !queue.isEmpty()) { long now = System.currentTimeMillis(); // find the first unfinished item From 2f6666a9c071c0fdcb88c6766f38272133e53b9e Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Thu, 18 Sep 2025 23:04:16 +0200 Subject: [PATCH 16/34] fixed skull meta on queued items --- .../magemonkey/fusion/cfg/ProfessionsCfg.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java index d1f70ee..ec69b80 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/ProfessionsCfg.java @@ -1,13 +1,16 @@ package studio.magemonkey.fusion.cfg; import lombok.Getter; +import net.kyori.adventure.text.Component; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.NotNull; import studio.magemonkey.codex.legacy.item.ItemBuilder; +import studio.magemonkey.codex.legacy.item.SkullBuilder; import studio.magemonkey.fusion.Fusion; import studio.magemonkey.fusion.cfg.migrations.ProfessionMigration; import studio.magemonkey.fusion.data.professions.pattern.Category; @@ -15,6 +18,7 @@ import studio.magemonkey.fusion.data.recipes.CraftingTable; import studio.magemonkey.fusion.gui.ProfessionGuiRegistry; import studio.magemonkey.fusion.hook.NexoHook; +import studio.magemonkey.fusion.util.ChatUT; import studio.magemonkey.fusion.util.Utils; import java.io.File; @@ -464,10 +468,17 @@ public static ItemStack getQueueItem(String key, QueueItem item) { .warning("Profession '" + key + "' has an unknown material: " + materialString); return new ItemStack(Material.AIR); } + if(material != result.getType()) result.setType(material); + ItemMeta meta = result.getItemMeta(); + if(meta != null) { + List lore = cfg.getStringList(path + ".lore"); + lore.replaceAll(s -> ChatUT.hexString(s.replace("%time%", Utils.getFormattedTime(item.getVisualRemainingItemTime())))); + meta.setLore(lore); + result.setItemMeta(meta); + } + - List lore = cfg.getStringList(path + ".lore"); - lore.replaceAll(s -> s.replace("%time%", Utils.getFormattedTime(item.getVisualRemainingItemTime()))); - return ItemBuilder.newItem(result).material(material).lore(lore).build(); + return result; } public static void closeAll() { From a9a4900bf8aaf964f7eca1153ceb6ab0b70b2843 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 17 Aug 2025 09:55:31 +0200 Subject: [PATCH 17/34] added * for itemflag hiding in settings item --- pom.xml | 2 +- .../data/professions/ProfessionSettings.java | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index acc692b..d24977c 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 16 - 1.1.1-R0.3-SNAPSHOT + 1.1.1-R0.7-SNAPSHOT diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java index d190baa..dc14c68 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java @@ -92,8 +92,12 @@ public ProfessionSettings(String profession, ConfigurationSection config) { List flagsList = config.getStringList("settings.icon.optionals.flags"); if(!flagsList.isEmpty()) { flags = new HashSet<>(); - for (String flag : flagsList) { - flags.add(ItemFlag.valueOf(flag)); + if(flagsList.contains("*")) { + flags.addAll(Arrays.asList(ItemFlag.values())); + } else { + for (String flag : flagsList) { + flags.add(ItemFlag.valueOf(flag)); + } } } color = config.getString("settings.icon.optionals.color"); @@ -147,8 +151,12 @@ public ProfessionSettings(String profession, DeserializationWorker dw) { if (optionalIconSettings.get("flags") != null) { flags = new HashSet<>(); List flagsList = (List) optionalIconSettings.get("flags"); - for (String flag : flagsList) { - flags.add(ItemFlag.valueOf(flag)); + if(flagsList.contains("*")) { + flags.addAll(Arrays.asList(ItemFlag.values())); + } else { + for (String flag : flagsList) { + flags.add(ItemFlag.valueOf(flag)); + } } } if (optionalIconSettings.get("color") != null) From 411fec38182de8c68ccd3aef3eff0d40d7278d90 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Wed, 24 Sep 2025 21:57:52 +0200 Subject: [PATCH 18/34] fixed recipe gui reacting on player inventory instead of top inv --- src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java b/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java index 55924e8..1649e0b 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java +++ b/src/main/java/studio/magemonkey/fusion/gui/CategoryGui.java @@ -325,7 +325,7 @@ public void click(InventoryClickEvent event) { @EventHandler public void onClick(InventoryClickEvent event) { - if (event.getInventory() != getInventory()) return; + if (event.getClickedInventory() != getInventory()) return; event.setCancelled(true); event.setResult(Event.Result.DENY); From b51de93dee3045a21f9f3e71dbb5c38c09fb28cd Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sat, 27 Sep 2025 22:05:00 +0200 Subject: [PATCH 19/34] improved queue updating for performance --- .../magemonkey/fusion/gui/RecipeGui.java | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java index 81d107c..a7f32bb 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java +++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java @@ -474,7 +474,7 @@ public void reloadRecipes() { } } if (requiresUpdate) { - Bukkit.getScheduler().runTaskLater(Fusion.getInstance(), this::reloadRecipes, 20L); + Bukkit.getScheduler().runTaskLater(Fusion.getInstance(), this::updateQueuedSlots, 20L); } this.isLoaded = true; } @@ -485,6 +485,57 @@ public void reloadRecipesTask() { Bukkit.getScheduler().runTaskLater(Fusion.getInstance(), this::reloadRecipes, 1L); } + // Updates only the queued-slot icons/progress without rebuilding the whole GUI. + private void updateQueuedSlots() { + if (!player.isOnline()) return; + if (!Cfg.craftingQueue || queue == null || queuedSlots.isEmpty()) return; + + // Run the actual inventory updates on the main server thread + Bukkit.getScheduler().runTask(Fusion.getInstance(), () -> { + List allQueuedItems = new ArrayList<>(queue.getQueue()); + int queueSize = allQueuedItems.size(); + int queuePageSize = queuedSlots.size(); + + Integer[] queuedIndices = queuedSlots.toArray(new Integer[0]); + + // Reset all queued slots to the empty queue icon + for (int qIndex : queuedIndices) { + inventory.setItem(qIndex, ProfessionsCfg.getQueueSlot(table.getName())); + } + + // Clear the internal mapping and repopulate for current page only + this.queue.getQueuedItems().clear(); + + if (!allQueuedItems.isEmpty() && queuePageSize > 0) { + int j = 0; + int qStart = queuePage * queuePageSize; + int qEnd = Math.min(qStart + queuePageSize, queueSize); + QueueItem[] allQueueItemsArray = allQueuedItems.toArray(new QueueItem[0]); + + for (int q = qStart; q < qEnd && j < queuedIndices.length; q++, j++) { + QueueItem qi = allQueueItemsArray[q]; + int slot = queuedIndices[j]; + this.queue.getQueuedItems().put(slot, qi); + qi.updateIcon(); + inventory.setItem(slot, qi.getIcon().clone()); + } + } + + // Decide whether we need another update next second (any unfinished item) + boolean requiresUpdate = false; + for (QueueItem qi : allQueuedItems) { + if (!qi.isDone()) { + requiresUpdate = true; + break; + } + } + + if (requiresUpdate) { + Bukkit.getScheduler().runTaskLater(Fusion.getInstance(), this::updateQueuedSlots, 20L); + } + }); + } + private boolean validatePageCount() { if (this.page <= 0) { this.reloadRecipesTask(); From 7e238132805d9d9a836e3c914cb5998ed3f7c9a6 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Tue, 7 Oct 2025 22:35:57 +0200 Subject: [PATCH 20/34] sync fixes and inv checks --- .../studio/magemonkey/fusion/gui/RecipeGui.java | 5 +++-- .../gui/editors/pattern/PatternItemEditor.java | 2 +- .../professions/recipes/RecipeItemEditor.java | 2 +- .../fusion/gui/recipe/RecipeGuiEventRouter.java | 14 +++++++++++--- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java index a7f32bb..8a53608 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java +++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java @@ -864,7 +864,7 @@ private boolean craft(int slot, boolean addToCursor) { // Restart the crafting sequence if auto-crafting is enabled if (PlayerLoader.getPlayer(player).isAutoCrafting() && !this.recipes.isEmpty()) { reloadRecipesTask(); - boolean success = craft(slot, addToCursor); //Call this method again recursively + boolean success = craft(slot, addToCursor); // Call this method again recursively if (!success) CodexEngine.get().getMessageUtil().sendMessage("fusion.autoCancelled", player); } @@ -1056,7 +1056,7 @@ public void close(Player p, Inventory inv) { return; } Inventory pInventory = p.getInventory(); - if (inv.equals(this.inventory)) { + if (inv.equals(this.inventory) && !Cfg.craftingQueue) { for (int i = 0; i < this.slots.length; i++) { if (this.slots[i].equals(Slot.BLOCKED_SLOT) || this.slots[i].equals(Slot.BASE_RESULT_SLOT) || @@ -1075,6 +1075,7 @@ public void close(Player p, Inventory inv) { cancel(true); inv.clear(); } + ProfessionGuiRegistry.getLatestRecipeGui().remove(p.getUniqueId()); } /* diff --git a/src/main/java/studio/magemonkey/fusion/gui/editors/pattern/PatternItemEditor.java b/src/main/java/studio/magemonkey/fusion/gui/editors/pattern/PatternItemEditor.java index 3ae835c..c372d10 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/editors/pattern/PatternItemEditor.java +++ b/src/main/java/studio/magemonkey/fusion/gui/editors/pattern/PatternItemEditor.java @@ -91,7 +91,7 @@ private void initialize() { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (event.getInventory() != getInventory()) return; + if (event.getClickedInventory() != getInventory()) return; event.setCancelled(true); Player player = (Player) event.getWhoClicked(); boolean hasChanges = false; diff --git a/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeItemEditor.java b/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeItemEditor.java index ab52500..7731441 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeItemEditor.java +++ b/src/main/java/studio/magemonkey/fusion/gui/editors/professions/recipes/RecipeItemEditor.java @@ -63,7 +63,7 @@ public void initialize() { @EventHandler public void onInventoryClick(InventoryClickEvent event) { - if (event.getInventory() != getInventory()) return; + if (event.getClickedInventory() != getInventory()) return; event.setCancelled(true); Player player = (Player) event.getWhoClicked(); boolean hasChanges = false; diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java index c5de114..11baae1 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java +++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java @@ -8,6 +8,7 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.player.PlayerChangedWorldEvent; import org.bukkit.event.player.PlayerDropItemEvent; import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.Inventory; @@ -41,9 +42,9 @@ private RecipeGui findGuiFor(Player player, Inventory inv) { @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) public void onInventoryClick(InventoryClickEvent event) { if (!(event.getWhoClicked() instanceof Player p)) return; - Inventory inv = event.getInventory(); + Inventory inv = event.getClickedInventory(); RecipeGui gui = findGuiFor(p, inv); - if (gui == null) return; + if (gui == null || inv == null) return; // Only forward if the clicked inventory is *exactly* the GUI’s inventory if (!inv.equals(gui.getInventory())) return; @@ -108,6 +109,13 @@ public void onPlayerQuit(PlayerQuitEvent event) { RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(p.getUniqueId()); if (gui == null) return; gui.close(p, gui.getInventory()); - ProfessionGuiRegistry.getLatestRecipeGui().remove(p.getUniqueId()); + } + + @EventHandler(ignoreCancelled = true) + public void onPlayerChangedWorld(PlayerChangedWorldEvent event) { + Player p = event.getPlayer(); + RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(p.getUniqueId()); + if (gui == null) return; + gui.close(p, gui.getInventory()); } } From dbdd99216462221d3a1af14a62610cf5ea627d5e Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Wed, 8 Oct 2025 17:45:35 +0200 Subject: [PATCH 21/34] implemented lock mechanism to prevent the plugin to load crafting queues before the other server node got saved async --- pom.xml | 1 + .../cfg/sql/tables/FusionPlayersSQL.java | 64 ++++++++++++++++++- .../fusion/data/player/FusionPlayer.java | 28 +++++--- .../fusion/data/player/PlayerLoader.java | 2 + 4 files changed, 84 insertions(+), 11 deletions(-) diff --git a/pom.xml b/pom.xml index d24977c..e128aee 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ 16 1.1.1-R0.7-SNAPSHOT + 1.0.4-R0.63-SNAPSHOT diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java index d1edc64..32ad38e 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java @@ -17,8 +17,17 @@ public FusionPlayersSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" + "UUID varchar(36), " - + "AutoCrafting boolean)")) { + + "AutoCrafting boolean, " + + "Locked boolean)")) { create.execute(); + + boolean lockedColumnAdded = alterIfLockedNotExistent(); + if (lockedColumnAdded) { + Fusion.getInstance() + .getLogger() + .info("[SQL:FusionPlayersSQL:FusionPlayersSQL] Added 'Locked' column to 'fusion_players' table."); + } + } catch (SQLException e) { Fusion.getInstance() .getLogger() @@ -42,6 +51,37 @@ public void setAutoCrafting(UUID uuid, boolean autoCrafting) { } } + public void setLocked(UUID uuid, boolean locked) { + addPlayer(uuid); + try (PreparedStatement update = SQLManager.connection() + .prepareStatement("UPDATE " + Table + " SET Locked=? WHERE UUID=?")) { + update.setBoolean(1, locked); + update.setString(2, uuid.toString()); + update.execute(); + } catch (SQLException e) { + Fusion.getInstance() + .getLogger() + .warning("[SQL:FusionPlayersSQL:setLocked] Something went wrong with the sql-connection: " + + e.getMessage()); + } + } + + public boolean isLocked(UUID uuid) { + try (PreparedStatement select = SQLManager.connection() + .prepareStatement("SELECT Locked FROM " + Table + " WHERE UUID=?")) { + select.setString(1, uuid.toString()); + ResultSet result = select.executeQuery(); + if (result.next()) + return result.getBoolean("Locked"); + } catch (SQLException e) { + Fusion.getInstance() + .getLogger() + .warning("[SQL:FusionPlayersSQL:isLocked] Something went wrong with the sql-connection: " + + e.getMessage()); + } + return false; + } + public void addPlayer(UUID uuid) { if (hasPlayer(uuid)) return; @@ -89,4 +129,26 @@ public boolean isAutoCrafting(UUID uuid) { } return false; } + + public boolean alterIfLockedNotExistent() { + try (PreparedStatement select = SQLManager.connection() + .prepareStatement("SELECT Locked FROM " + Table + " LIMIT 1")) { + ResultSet result = select.executeQuery(); + if (result.next()) + return false; + } catch (SQLException e) { + // Column does not exist, we need to add it + try (PreparedStatement alter = SQLManager.connection() + .prepareStatement("ALTER TABLE " + Table + " ADD COLUMN Locked boolean DEFAULT false")) { + alter.execute(); + return true; + } catch (SQLException ex) { + Fusion.getInstance() + .getLogger() + .warning("[SQL:FusionPlayersSQL:alterIfLockedNotExistent] Something went wrong with the sql-connection: " + + ex.getMessage()); + } + } + return false; + } } diff --git a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java index 465418a..a3ccaea 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java @@ -6,6 +6,7 @@ import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; +import studio.magemonkey.fusion.Fusion; import studio.magemonkey.fusion.cfg.sql.SQLManager; import studio.magemonkey.fusion.data.professions.Profession; import studio.magemonkey.fusion.data.professions.pattern.Category; @@ -18,6 +19,7 @@ import java.util.Map; import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @SuppressWarnings("unused") @@ -344,15 +346,21 @@ public int getFinishedSize() { } public void save() { - SQLManager.players().setAutoCrafting(uuid, autoCrafting); - for (Profession profession : professions.values()) { - SQLManager.professions().setProfession(uuid, profession); - } - for (CraftingQueue queue : cachedQueues.values()) { - SQLManager.queues().saveCraftingQueue(queue); - } - SQLManager.recipeLimits().saveRecipeLimits(uuid, cachedRecipeLimits); - cachedQueues.clear(); - cachedRecipeLimits.clear(); + SQLManager.players().setLocked(uuid, true); + + Bukkit.getScheduler().runTaskAsynchronously(Fusion.getInstance(), () -> { + SQLManager.players().setAutoCrafting(uuid, autoCrafting); + for (Profession profession : professions.values()) { + SQLManager.professions().setProfession(uuid, profession); + } + for (CraftingQueue queue : cachedQueues.values()) { + SQLManager.queues().saveCraftingQueue(queue); + } + SQLManager.recipeLimits().saveRecipeLimits(uuid, cachedRecipeLimits); + cachedQueues.clear(); + cachedRecipeLimits.clear(); + + SQLManager.players().setLocked(uuid, false); + }); } } diff --git a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java index 44422af..d6d96a1 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java @@ -1,6 +1,7 @@ package studio.magemonkey.fusion.data.player; import org.bukkit.entity.Player; +import studio.magemonkey.fusion.cfg.sql.SQLManager; import java.util.Map; import java.util.TreeMap; @@ -12,6 +13,7 @@ public class PlayerLoader { public static FusionPlayer getPlayer(UUID uuid) { if (!cachedPlayers.containsKey(uuid)) { + if(!SQLManager.players().isLocked(uuid)) return null; cachedPlayers.put(uuid, new FusionPlayer(uuid)); } return cachedPlayers.get(uuid); From 86f481a9a555e2cda1860679ac74118cefe0e084 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Wed, 8 Oct 2025 20:06:56 +0200 Subject: [PATCH 22/34] changing some mistakes --- .../fusion/cfg/sql/tables/FusionPlayersSQL.java | 3 ++- .../magemonkey/fusion/data/player/FusionPlayer.java | 8 ++++++++ .../magemonkey/fusion/data/player/PlayerLoader.java | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java index 32ad38e..2400de1 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java @@ -86,9 +86,10 @@ public void addPlayer(UUID uuid) { if (hasPlayer(uuid)) return; try (PreparedStatement insert = SQLManager.connection() - .prepareStatement("INSERT INTO " + Table + "(UUID, AutoCrafting) VALUES(?,?)")) { + .prepareStatement("INSERT INTO " + Table + "(UUID, AutoCrafting, Locked) VALUES(?,?,?)")) { insert.setString(1, uuid.toString()); insert.setBoolean(2, false); + insert.setBoolean(3, false); insert.execute(); } catch (SQLException e) { Fusion.getInstance() diff --git a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java index a3ccaea..21921eb 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java @@ -360,6 +360,14 @@ public void save() { cachedQueues.clear(); cachedRecipeLimits.clear(); + /* + In case of race conditions we wait a bit before unlocking the player. Not required but just to be safe. + try { + Thread.sleep(250); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + */ SQLManager.players().setLocked(uuid, false); }); } diff --git a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java index d6d96a1..a3d9d98 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java @@ -13,7 +13,7 @@ public class PlayerLoader { public static FusionPlayer getPlayer(UUID uuid) { if (!cachedPlayers.containsKey(uuid)) { - if(!SQLManager.players().isLocked(uuid)) return null; + if(SQLManager.players().isLocked(uuid)) return null; cachedPlayers.put(uuid, new FusionPlayer(uuid)); } return cachedPlayers.get(uuid); From 0f354969b287d40ec0a3040bfd216974c54941eb Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 12 Oct 2025 18:08:16 +0200 Subject: [PATCH 23/34] fixed some issues related to limits --- .../cfg/sql/tables/FusionRecipeLimitsSQL.java | 93 +++++++++++++------ .../fusion/data/queue/CraftingQueue.java | 29 ------ 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java index 584720c..62388ee 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java @@ -71,35 +71,76 @@ public Map getRecipeLimits(UUID uuid) { } public void saveRecipeLimits(UUID uuid, Map recipeLimits) { - try (PreparedStatement delete = SQLManager.connection() - .prepareStatement("DELETE FROM " + Table + " WHERE UUID = ?")) { - delete.setString(1, uuid.toString()); - delete.execute(); - } catch (SQLException e) { - Fusion.getInstance() - .getLogger() - .warning( - "[SQL:FusionRecipeLimitsSQL:saveRecipeLimits] Something went wrong with the sql-connection: " - + e.getMessage()); + // Get all current limits from DB + Map currentLimits = getRecipeLimits(uuid); + + // Remove limits that are no longer present in the provided map + for (String recipePath : currentLimits.keySet()) { + if (!recipeLimits.containsKey(recipePath)) { + PlayerRecipeLimit limit = currentLimits.get(recipePath); + // Limits mit Timestamp = -1 nicht löschen, außer explizit entfernt + if (limit.getCooldownTimestamp() != -1) { + try (PreparedStatement delete = SQLManager.connection().prepareStatement( + "DELETE FROM " + Table + " WHERE UUID = ? AND RecipePath = ?")) { + delete.setString(1, uuid.toString()); + delete.setString(2, recipePath); + delete.execute(); + } catch (SQLException e) { + Fusion.getInstance().getLogger().warning( + "[SQL:FusionRecipeLimitsSQL:saveRecipeLimits] Error at deletion: " + e.getMessage()); + } + } + } } - try (PreparedStatement insert = SQLManager.connection() - .prepareStatement( + + // Update or Insert limits + for (Map.Entry entry : recipeLimits.entrySet()) { + String recipePath = entry.getKey(); + PlayerRecipeLimit limit = entry.getValue(); + if (limit.getLimit() <= 0) { + // Falls Limit <= 0, löschen (außer Timestamp = -1) + if (limit.getCooldownTimestamp() != -1) { + try (PreparedStatement delete = SQLManager.connection().prepareStatement( + "DELETE FROM " + Table + " WHERE UUID = ? AND RecipePath = ?")) { + delete.setString(1, uuid.toString()); + delete.setString(2, recipePath); + delete.execute(); + } catch (SQLException e) { + Fusion.getInstance().getLogger().warning( + "[SQL:FusionRecipeLimitsSQL:saveRecipeLimits] Error at deletion: " + e.getMessage()); + } + } + continue; + } + // Check if the limit already exists + if (currentLimits.containsKey(recipePath)) { + // Update + try (PreparedStatement update = SQLManager.connection().prepareStatement( + "UPDATE " + Table + " SET Amount = ?, Timestamp = ? WHERE UUID = ? AND RecipePath = ?")) { + update.setInt(1, limit.getLimit()); + update.setLong(2, limit.getCooldownTimestamp()); + update.setString(3, uuid.toString()); + update.setString(4, recipePath); + update.execute(); + } catch (SQLException e) { + Fusion.getInstance().getLogger().warning( + "[SQL:FusionRecipeLimitsSQL:saveRecipeLimits] Error at update: " + e.getMessage()); + } + } else { + // Insert + try (PreparedStatement insert = SQLManager.connection().prepareStatement( "INSERT INTO " + Table + "(Id, UUID, RecipePath, Amount, Timestamp) VALUES(?,?,?,?,?)")) { - for (Map.Entry entry : recipeLimits.entrySet()) { - if (entry.getValue().getLimit() <= 0) continue; - insert.setLong(1, getNextId()); - insert.setString(2, uuid.toString()); - insert.setString(3, entry.getKey()); - insert.setInt(4, entry.getValue().getLimit()); - insert.setLong(5, entry.getValue().getCooldownTimestamp()); - insert.execute(); + insert.setLong(1, getNextId()); + insert.setString(2, uuid.toString()); + insert.setString(3, recipePath); + insert.setInt(4, limit.getLimit()); + insert.setLong(5, limit.getCooldownTimestamp()); + insert.execute(); + } catch (SQLException e) { + Fusion.getInstance().getLogger().warning( + "[SQL:FusionRecipeLimitsSQL:saveRecipeLimits] Error at insert: " + e.getMessage()); + } } - } catch (SQLException e) { - Fusion.getInstance() - .getLogger() - .warning( - "[SQL:FusionRecipeLimitsSQL:saveRecipeLimits] Something went wrong with the sql-connection: " - + e.getMessage()); } } } diff --git a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java index def10b6..79e8132 100644 --- a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java +++ b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java @@ -12,12 +12,9 @@ import studio.magemonkey.fusion.cfg.Cfg; import studio.magemonkey.fusion.cfg.ProfessionsCfg; import studio.magemonkey.fusion.cfg.sql.SQLManager; -import studio.magemonkey.fusion.data.player.FusionPlayer; -import studio.magemonkey.fusion.data.player.PlayerLoader; import studio.magemonkey.fusion.data.professions.pattern.Category; import studio.magemonkey.fusion.data.recipes.Recipe; import studio.magemonkey.fusion.data.recipes.RecipeItem; -import studio.magemonkey.fusion.util.PlayerUtil; import java.util.ArrayList; import java.util.HashMap; @@ -104,32 +101,6 @@ public void run() { } public void addRecipe(Recipe recipe) { - int[] limits = PlayerLoader.getPlayer(player.getUniqueId()).getQueueSizes(profession, category); - int categoryLimit = - PlayerUtil.getPermOption(player, "fusion.queue." + profession + "." + category.getName() + ".limit"); - int professionLimit = PlayerUtil.getPermOption(player, "fusion.queue." + profession + ".limit"); - int limit = PlayerUtil.getPermOption(player, "fusion.queue.limit"); - - if (categoryLimit > 0 && limits[0] >= categoryLimit) { - CodexEngine.get().getMessageUtil().sendMessage("fusion.queue.fullCategory", - player, - new MessageData("limit", categoryLimit), - new MessageData("category", category.getName()), - new MessageData("profession", profession)); - return; - } else if (professionLimit > 0 && limits[1] >= professionLimit) { - CodexEngine.get().getMessageUtil().sendMessage("fusion.queue.fullProfession", - player, - new MessageData("limit", professionLimit), - new MessageData("profession", profession)); - return; - } else if (limit > 0 && limits[2] >= limit) { - CodexEngine.get() - .getMessageUtil() - .sendMessage("fusion.queue.fullGlobal", player, new MessageData("limit", limit)); - return; - } - QueueItem item = new QueueItem(-1, profession, category, recipe, System.currentTimeMillis(), 0); FusionAPI.getEventServices() .getQueueService() From da2b2ae7e9fcf574fcf651fcce0e4d4c79b0c5b4 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 12 Oct 2025 18:08:29 +0200 Subject: [PATCH 24/34] preparations for future enhancements --- .../magemonkey/fusion/gui/RecipeGui.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java index 8a53608..91f91bd 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java +++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java @@ -53,6 +53,7 @@ import studio.magemonkey.fusion.util.PlayerUtil; import java.util.*; +import java.util.stream.Collectors; @Getter public class RecipeGui implements Listener { @@ -447,6 +448,9 @@ public void reloadRecipes() { new MessageData("category", category), new MessageData("gui", getName()), new MessageData("player", player.getName()), + new MessageData("queue_done", queue != null ? queue.getQueue().stream().filter(QueueItem::isDone).toList().size() : 0), + new MessageData("queue_size", queue != null ? queue.getQueue().size() : 0), + new MessageData("queue_time", queue != null ? queue.getVisualRemainingTotalTime() : 0), new MessageData("bal", CodexEngine.get().getVault() == null ? 0 @@ -679,6 +683,34 @@ private boolean canCraft(CalculatedRecipe calculatedRecipe, int slot) { .sendMessage("fusion.error.noFunds", player, new MessageData("recipe", recipe)); return false; } + + // Check queue limits + int[] limits = PlayerLoader.getPlayer(player.getUniqueId()).getQueueSizes(table.getName(), category); + int categoryLimit = + PlayerUtil.getPermOption(player, "fusion.queue." + table.getName() + "." + category.getName() + ".limit"); + int professionLimit = PlayerUtil.getPermOption(player, "fusion.queue." + table.getName() + ".limit"); + int limit = PlayerUtil.getPermOption(player, "fusion.queue.limit"); + + if (categoryLimit > 0 && limits[0] >= categoryLimit) { + CodexEngine.get().getMessageUtil().sendMessage("fusion.queue.fullCategory", + player, + new MessageData("limit", categoryLimit), + new MessageData("category", category.getName()), + new MessageData("profession", table.getName())); + return false; + } else if (professionLimit > 0 && limits[1] >= professionLimit) { + CodexEngine.get().getMessageUtil().sendMessage("fusion.queue.fullProfession", + player, + new MessageData("limit", professionLimit), + new MessageData("profession", table.getName())); + return false; + } else if (limit > 0 && limits[2] >= limit) { + CodexEngine.get() + .getMessageUtil() + .sendMessage("fusion.queue.fullGlobal", player, new MessageData("limit", limit)); + return false; + } + return true; } From 3b7f68ba6e3f48e4a6acd426300c13470f37fdd6 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Mon, 13 Oct 2025 15:54:02 +0200 Subject: [PATCH 25/34] fixed hashes not updating on changes of itemstack amount --- .../magemonkey/fusion/gui/recipe/InventoryFingerprint.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java index 1418129..8c67870 100644 --- a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java +++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java @@ -37,6 +37,7 @@ public static byte[] fingerprint(Player p) { // Material md.update((byte) is.getType().ordinal()); // Amount (4 bytes) + md.update(ByteBuffer.allocate(4).putInt(is.getAmount()).array()); // displayName if (im != null && im.hasDisplayName()) { byte[] nameBytes = im.getDisplayName().getBytes(java.nio.charset.StandardCharsets.UTF_8); From 6ba8401deae069e4ef24f8cbacfdedc9f40c8c33 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 19 Oct 2025 16:17:03 +0200 Subject: [PATCH 26/34] further sql fixes --- .../cfg/sql/tables/FusionProfessionsSQL.java | 30 +++++-------------- .../cfg/sql/tables/FusionRecipeLimitsSQL.java | 28 ++++------------- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java index 6c1c0a3..e182e92 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java @@ -18,7 +18,7 @@ public class FusionProfessionsSQL { public FusionProfessionsSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" - + "Id numeric, " + + "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, " + "UUID varchar(36), " + "Profession varchar(100)," + "Experience numeric," @@ -34,21 +34,6 @@ public FusionProfessionsSQL() { } } - public long getNextId() { - try (PreparedStatement select = SQLManager.connection().prepareStatement("SELECT COUNT(*) FROM " + Table)) { - ResultSet result = select.executeQuery(); - if (result.next()) { - return result.getLong(1); - } - } catch (SQLException e) { - Fusion.getInstance() - .getLogger() - .warning("[SQL:FusionProfessionsSQL:getNextId] Something went wrong with the sql-connection: " - + e.getMessage()); - } - return 0; - } - public void setProfession(UUID uuid, Profession profession) { if (hasProfession(uuid, profession.getName())) { updateProfession(profession); @@ -60,13 +45,12 @@ public void setProfession(UUID uuid, Profession profession) { public void addProfession(Profession profession) { try (PreparedStatement insert = SQLManager.connection() .prepareStatement("INSERT INTO " + Table - + "(Id, UUID, Profession, Experience, Mastered, Joined) VALUES(?,?,?,?,?,?)")) { - insert.setLong(1, getNextId()); - insert.setString(2, profession.getUuid().toString()); - insert.setString(3, profession.getName()); - insert.setDouble(4, profession.getExp()); - insert.setBoolean(5, profession.isMastered()); - insert.setBoolean(6, profession.isJoined()); + + "(UUID, Profession, Experience, Mastered, Joined) VALUES(?,?,?,?,?)")) { + insert.setString(1, profession.getUuid().toString()); + insert.setString(2, profession.getName()); + insert.setDouble(3, profession.getExp()); + insert.setBoolean(4, profession.isMastered()); + insert.setBoolean(5, profession.isJoined()); insert.execute(); } catch (SQLException e) { Fusion.getInstance() diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java index 62388ee..98bfef7 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java @@ -18,7 +18,7 @@ public class FusionRecipeLimitsSQL { public FusionRecipeLimitsSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" - + "Id long," + + "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY," + "UUID varchar(36), " + "RecipePath varchar(100)," + "Amount numeric," @@ -33,21 +33,6 @@ public FusionRecipeLimitsSQL() { } } - public long getNextId() { - try (PreparedStatement select = SQLManager.connection().prepareStatement("SELECT Count(Id) FROM " + Table)) { - ResultSet result = select.executeQuery(); - if (result.next()) { - return result.getLong(1); - } - } catch (SQLException e) { - Fusion.getInstance() - .getLogger() - .warning("[SQL:FusionRecipeLimitsSQL:getNextId] Something went wrong with the sql-connection: " - + e.getMessage()); - } - return 0; - } - public Map getRecipeLimits(UUID uuid) { Map limits = new HashMap<>(); try (PreparedStatement select = SQLManager.connection() @@ -129,12 +114,11 @@ public void saveRecipeLimits(UUID uuid, Map recipeLim } else { // Insert try (PreparedStatement insert = SQLManager.connection().prepareStatement( - "INSERT INTO " + Table + "(Id, UUID, RecipePath, Amount, Timestamp) VALUES(?,?,?,?,?)")) { - insert.setLong(1, getNextId()); - insert.setString(2, uuid.toString()); - insert.setString(3, recipePath); - insert.setInt(4, limit.getLimit()); - insert.setLong(5, limit.getCooldownTimestamp()); + "INSERT INTO " + Table + "(UUID, RecipePath, Amount, Timestamp) VALUES(?,?,?,?)")) { + insert.setString(1, uuid.toString()); + insert.setString(2, recipePath); + insert.setInt(3, limit.getLimit()); + insert.setLong(4, limit.getCooldownTimestamp()); insert.execute(); } catch (SQLException e) { Fusion.getInstance().getLogger().warning( From 3c0954b7301f9e57978b4eddac27cb8fd8e2ad61 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Thu, 20 Nov 2025 21:17:23 +0100 Subject: [PATCH 27/34] re-implemented a `includeOriginalLore` setting --- .../data/professions/ProfessionSettings.java | 20 ++++++++++++++++++- .../fusion/data/recipes/CalculatedRecipe.java | 8 +++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java index 7a4cba6..6936448 100644 --- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java +++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java @@ -32,6 +32,7 @@ public class ProfessionSettings implements ConfigurationSerializable { // Icon related fields private RecipeItem recipeItem; private String iconNamespace; + private boolean includeOriginalLore; // Optional item fields private String name; @@ -50,12 +51,13 @@ public class ProfessionSettings implements ConfigurationSerializable { private Boolean hideRecipeLimitReached; public ProfessionSettings(String profession, Boolean hideNoPermission, - Boolean hideRecipeLimitReached, String iconNamespace, String name, int customModelData, List lore, boolean unbreakable, Map enchantments, Set flags, String color, boolean cancelDrop, List commandsOnClick) { + Boolean hideRecipeLimitReached, String iconNamespace, boolean includeOriginalLore, String name, int customModelData, List lore, boolean unbreakable, Map enchantments, Set flags, String color, boolean cancelDrop, List commandsOnClick) { this.profession = profession; this.hideNoPermission = hideNoPermission; this.hideRecipeLimitReached = hideRecipeLimitReached; this.iconNamespace = iconNamespace; + this.includeOriginalLore = includeOriginalLore; this.name = name; this.customModelData = customModelData; this.lore = lore; @@ -77,6 +79,8 @@ public ProfessionSettings(String profession, ConfigurationSection config) { // Setup of the icon String iconNamespace = config.getString("settings.icon.item"); + includeOriginalLore = config.getBoolean("settings.icon.includeOriginalLore", true); + if (config.isSet("settings.icon.optionals") && !config.getConfigurationSection("settings.icon.optionals").getKeys(false).isEmpty()) { name = config.getString("settings.icon.optionals.name"); customModelData = config.getInt("settings.icon.optionals.customModelData", -1); @@ -129,6 +133,7 @@ public ProfessionSettings(String profession, DeserializationWorker dw) { return; } String iconNamespace = (String) iconSettings.get("item"); + includeOriginalLore = iconSettings.get("includeOriginalLore") != null && (boolean) iconSettings.get("includeOriginalLore"); Map optionalIconSettings = (Map) iconSettings.get("optionals"); if (optionalIconSettings != null && !optionalIconSettings.isEmpty()) { if(optionalIconSettings.get("name") != null) @@ -188,6 +193,7 @@ public ProfessionSettings(String profession, DeserializationWorker dw) { Map iconSettings = new HashMap<>(3); iconSettings.put("item", iconNamespace); + iconSettings.put("includeOriginalLore", includeOriginalLore); Map optionalIconSettings = new HashMap<>(10); if (name != null) optionalIconSettings.put("name", name); if (customModelData >= 0) optionalIconSettings.put("customModelData", customModelData); @@ -223,6 +229,7 @@ public static ProfessionSettings copy(ProfessionSettings results) { results.hideNoPermission, results.hideRecipeLimitReached, results.iconNamespace, + results.includeOriginalLore, results.name, results.customModelData, results.lore, @@ -248,6 +255,17 @@ private void generateIcon(String namespace) { builder.name(name); if (customModelData >= 0) meta.setCustomModelData(customModelData); + + if(includeOriginalLore) { + List existingLore = meta.getLore(); + if (existingLore != null) { + if (lore == null) { + lore = new ArrayList<>(); + } + lore.addAll(0, existingLore); + } + } + if (lore != null) builder = builder.lore(lore); if (enchantments != null) diff --git a/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java b/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java index c63dda9..f32f5b2 100644 --- a/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java +++ b/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java @@ -64,7 +64,13 @@ public static CalculatedRecipe create(Recipe recipe, ItemMeta baseMeta = iconResult.getItemMeta(); List resultLore = (baseMeta == null) ? Collections.emptyList() : baseMeta.getLore(); - // (Optional custom lore logic omitted) + // Append resultLore if exists + if (resultLore != null && !resultLore.isEmpty()) { + for (String line : resultLore) { + lore.append(line).append('\n'); + } + lore.append(" ").append('\n'); + } // 1) “Requirement” header String requirementLine = CraftingRequirementsCfg.getCraftingRequirementLine("recipes"); From fe00e98f698bb84fdcadadd0cd78114f86ef8c00 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 25 Jan 2026 22:44:18 +0100 Subject: [PATCH 28/34] copilot-instruction --- .github/copilot-instructions.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..e69de29 From d9d33fedc32c4e0edda7b5c02f3e7c0d2d18de92 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 25 Jan 2026 23:25:02 +0100 Subject: [PATCH 29/34] fixed craft show on empty set --- .../magemonkey/fusion/commands/CommandMechanics.java | 7 +++++++ src/main/resources/lang/lang_en.yml | 2 ++ 2 files changed, 9 insertions(+) diff --git a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java index b1413b5..b2a851f 100644 --- a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java +++ b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java @@ -467,6 +467,13 @@ private static void showIngredientUsage(Player player) { } } + if(recipeUsage.isEmpty()) { + CodexEngine.get().getMessageUtil().sendMessage("fusion.show.noUsage", + player, + new MessageData("item", item), + new MessageData("sender", player)); + return; + } new ShowRecipesGui(player, recipeUsage).open(player); } diff --git a/src/main/resources/lang/lang_en.yml b/src/main/resources/lang/lang_en.yml index c7e4916..dcf3716 100644 --- a/src/main/resources/lang/lang_en.yml +++ b/src/main/resources/lang/lang_en.yml @@ -48,6 +48,8 @@ fusion: fullCategory: "&cYou have reached the category maximum of your queue size. ($ of profession $)" finished: "&aYou have crafting items ready for pickup! ($)" cancelled: "&cYour crafting queue has been cancelled." + show: + noUsage: "&cThere is no usage for this ingredient." help: |2- You typed: &7$&r Valid syntax: From 3ab88720d3194f77758b4fdefb95809fd72b1aed Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 15 Feb 2026 14:50:22 +0100 Subject: [PATCH 30/34] changed typo mistake in sql swapping --- .../studio/magemonkey/fusion/commands/CommandMechanics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java index b2a851f..886576e 100644 --- a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java +++ b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java @@ -208,7 +208,7 @@ public static void setStorage(CommandSender sender, String[] args) { String storage = args[1]; DatabaseType type = DatabaseType.valueOf(Objects.requireNonNull(Cfg.getConfig()) - .getString("storage.type", "LOCALE") + .getString("storage.type", "LOCAL") .toUpperCase()); switch (storage.toLowerCase()) { case "local": From 33a84bdcecb11c4d0d5cd5d211873509c6d8d612 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:05:25 +0100 Subject: [PATCH 31/34] initializing auto increment per DatabaseType --- .../magemonkey/fusion/cfg/sql/SQLManager.java | 46 +++++++++++++++---- .../cfg/sql/tables/FusionProfessionsSQL.java | 3 +- .../cfg/sql/tables/FusionQueuesSQL.java | 3 +- .../cfg/sql/tables/FusionRecipeLimitsSQL.java | 3 +- 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java index 3e3bb7b..55b2357 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java @@ -29,6 +29,9 @@ public class SQLManager { private static String user; private static String password; + // Track the currently selected database type so we can produce dialect-specific SQL when needed + private static DatabaseType currentType; + public static void init() { FileConfiguration cfg = Cfg.getConfig(); DatabaseType type = DatabaseType.valueOf(cfg.getString("storage.type", "LOCAL").toUpperCase()); @@ -38,6 +41,9 @@ public static void init() { user = cfg.getString("storage.user", "root"); password = cfg.getString("storage.password", "password"); + // store the selected type + currentType = type; + Fusion.getInstance().getLogger().info("Initializing SQLManager with type: " + type); switch (type) { @@ -155,10 +161,9 @@ public static void swapToLocal() { statement.execute("DROP TABLE IF EXISTS fusion_professions"); statement.execute("DROP TABLE IF EXISTS fusion_queues"); statement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36), AutoCrafting boolean)"); - statement.execute( - "CREATE TABLE IF NOT EXISTS fusion_professions(Id long, UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)"); - statement.execute( - "CREATE TABLE IF NOT EXISTS fusion_queues(Id long, UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)"); + // Use SQLite-compatible id column definition + statement.execute("CREATE TABLE IF NOT EXISTS fusion_professions(" + getIdColumn(DatabaseType.LOCAL) + " UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)"); + statement.execute("CREATE TABLE IF NOT EXISTS fusion_queues(" + getIdColumn(DatabaseType.LOCAL) + " UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)"); } catch (SQLException e) { Fusion.getInstance().getLogger().severe("Error while dropping tables: " + e.getMessage()); @@ -207,7 +212,7 @@ public static void swapToLocal() { try (Connection sqliteConnection = getSQLiteConnection(); PreparedStatement insertStatement = sqliteConnection.prepareStatement( - "INSERT INTO fusion_queues (Id, UUID, RecipePath, Timestamp, CraftingTime, SavedSeconds) VALUES (?, ?, ?, ?, ?, ?)")) { + "INSERT INTO fusion_queues (Id, UUID, RecipePath, Timestamp, CraftingTime, SavedSeconds) VALUES (?, ?, ?, ?, ?, ?)") ) { insertQueue(resultQueues, insertStatement); } catch (SQLException e) { Fusion.getInstance() @@ -236,10 +241,9 @@ public static void swapToSql() { // Delete all content of the current database and recreate tables sqlStatement.execute("DROP TABLE IF EXISTS fusion_players, fusion_professions, fusion_queues"); sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36), AutoCrafting boolean)"); - sqlStatement.execute( - "CREATE TABLE IF NOT EXISTS fusion_professions(Id long, UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)"); - sqlStatement.execute( - "CREATE TABLE IF NOT EXISTS fusion_queues(Id long, UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)"); + // Use MySQL-compatible id column definition + sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_professions(" + getIdColumn(DatabaseType.MYSQL) + " UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)"); + sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_queues(" + getIdColumn(DatabaseType.MYSQL) + " UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)"); // Get all data from the local database try (Connection sqliteConnection = getSQLiteConnection(); @@ -327,4 +331,28 @@ private static void insertProfession(ResultSet resultProfessions, PreparedStatem insertStatement.executeUpdate(); } } + + /** + * Returns a dialect-specific id column definition including the trailing comma. + * For SQLITE (LOCAL) this returns: "Id INTEGER PRIMARY KEY AUTOINCREMENT," + * For MYSQL/MARIADB this returns: "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY," + */ + public static String getIdColumn(DatabaseType type) { + if (type == DatabaseType.LOCAL) { + return "Id INTEGER PRIMARY KEY AUTOINCREMENT,"; + } + // default to MySQL/MariaDB style + return "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,"; + } + + /** + * Returns the id column definition for the currently configured database type. + */ + public static String getIdColumn() { + return getIdColumn(currentType == null ? DatabaseType.MYSQL : currentType); + } + + public static DatabaseType getDatabaseType() { + return currentType; + } } diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java index e182e92..d5e8dc7 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionProfessionsSQL.java @@ -18,8 +18,7 @@ public class FusionProfessionsSQL { public FusionProfessionsSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" - + "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, " - + "UUID varchar(36), " + + SQLManager.getIdColumn() + " UUID varchar(36), " + "Profession varchar(100)," + "Experience numeric," + "Mastered boolean," diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java index 8c2050b..a92e615 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionQueuesSQL.java @@ -22,8 +22,7 @@ public class FusionQueuesSQL { public FusionQueuesSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" - + "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY," - + "UUID varchar(36), " + + SQLManager.getIdColumn() + " UUID varchar(36), " + "RecipePath varchar(100)," + "CraftingTime numeric," + "SavedSeconds numeric," diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java index 98bfef7..426c9a7 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionRecipeLimitsSQL.java @@ -18,8 +18,7 @@ public class FusionRecipeLimitsSQL { public FusionRecipeLimitsSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" - + "Id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY," - + "UUID varchar(36), " + + SQLManager.getIdColumn() + " UUID varchar(36), " + "RecipePath varchar(100)," + "Amount numeric," + "Timestamp BIGINT)")) { From f2b47121b6d009410286d48e2606bc311f8e4417 Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:34:49 +0100 Subject: [PATCH 32/34] adressed npe potential from locking --- .../magemonkey/fusion/cfg/sql/SQLManager.java | 37 ++++++++++--- .../cfg/sql/tables/FusionPlayersSQL.java | 32 +++++++---- .../fusion/data/player/FusionPlayer.java | 12 ++++- .../fusion/data/player/PlayerLoader.java | 54 ++++++++++++++++++- 4 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java index 55b2357..6ea29b8 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java @@ -160,7 +160,7 @@ public static void swapToLocal() { statement.execute("DROP TABLE IF EXISTS fusion_players"); statement.execute("DROP TABLE IF EXISTS fusion_professions"); statement.execute("DROP TABLE IF EXISTS fusion_queues"); - statement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36), AutoCrafting boolean)"); + statement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36) PRIMARY KEY, AutoCrafting boolean DEFAULT false, Locked boolean DEFAULT false)"); // Use SQLite-compatible id column definition statement.execute("CREATE TABLE IF NOT EXISTS fusion_professions(" + getIdColumn(DatabaseType.LOCAL) + " UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)"); statement.execute("CREATE TABLE IF NOT EXISTS fusion_queues(" + getIdColumn(DatabaseType.LOCAL) + " UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)"); @@ -178,10 +178,16 @@ public static void swapToLocal() { try (Connection sqliteConnection = getSQLiteConnection(); PreparedStatement insertStatement = sqliteConnection.prepareStatement( - "INSERT INTO fusion_players (UUID, AutoCrafting) VALUES (?, ?)")) { + "INSERT INTO fusion_players (UUID, AutoCrafting, Locked) VALUES (?, ?, ?)") ) { while (resultPlayers.next()) { insertStatement.setString(1, resultPlayers.getString("UUID")); insertStatement.setBoolean(2, resultPlayers.getBoolean("AutoCrafting")); + // If source DB doesn't have Locked column, getBoolean will return false; that's acceptable + try { + insertStatement.setBoolean(3, resultPlayers.getBoolean("Locked")); + } catch (SQLException ignored) { + insertStatement.setBoolean(3, false); + } insertStatement.executeUpdate(); } } catch (SQLException e) { @@ -197,7 +203,7 @@ public static void swapToLocal() { try (Connection sqliteConnection = getSQLiteConnection(); PreparedStatement insertStatement = sqliteConnection.prepareStatement( - "INSERT INTO fusion_professions (Id, UUID, Profession, Experience, Mastered, Joined) VALUES (?, ?, ?, ?, ?, ?)")) { + "INSERT INTO fusion_professions (Id, UUID, Profession, Experience, Mastered, Joined) VALUES (?, ?, ?, ?, ?, ?)") ) { insertProfession(resultProfessions, insertStatement); } catch (SQLException e) { Fusion.getInstance() @@ -240,7 +246,7 @@ public static void swapToSql() { // Delete all content of the current database and recreate tables sqlStatement.execute("DROP TABLE IF EXISTS fusion_players, fusion_professions, fusion_queues"); - sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36), AutoCrafting boolean)"); + sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_players(UUID varchar(36) PRIMARY KEY, AutoCrafting boolean DEFAULT false, Locked boolean DEFAULT false)"); // Use MySQL-compatible id column definition sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_professions(" + getIdColumn(DatabaseType.MYSQL) + " UUID varchar(36), Profession varchar(100), Experience numeric, Mastered boolean, Joined boolean)"); sqlStatement.execute("CREATE TABLE IF NOT EXISTS fusion_queues(" + getIdColumn(DatabaseType.MYSQL) + " UUID varchar(36), RecipePath varchar(100), Timestamp BIGINT, CraftingTime numeric, SavedSeconds numeric)"); @@ -251,7 +257,20 @@ public static void swapToSql() { // Retrieve data from local database ResultSet resultPlayers = localStatement.executeQuery("SELECT * FROM fusion_players"); - insertPlayers(sqlConnection, resultPlayers); + // Ensure we transfer Locked value if present + try (PreparedStatement insert = sqlConnection.prepareStatement( + "INSERT INTO fusion_players (UUID, AutoCrafting, Locked) VALUES (?, ?, ?)") ) { + while (resultPlayers.next()) { + insert.setString(1, resultPlayers.getString("UUID")); + insert.setBoolean(2, resultPlayers.getBoolean("AutoCrafting")); + try { + insert.setBoolean(3, resultPlayers.getBoolean("Locked")); + } catch (SQLException ignored) { + insert.setBoolean(3, false); + } + insert.executeUpdate(); + } + } ResultSet resultProfessions = localStatement.executeQuery("SELECT * FROM fusion_professions"); insertProfessions(sqlConnection, resultProfessions); @@ -281,11 +300,16 @@ public static void swapToSql() { } private static void insertPlayers(Connection connection, ResultSet resultSet) throws SQLException { - String insertQuery = "INSERT INTO fusion_players (UUID, AutoCrafting) VALUES (?, ?)"; + String insertQuery = "INSERT INTO fusion_players (UUID, AutoCrafting, Locked) VALUES (?, ?, ?)"; try (PreparedStatement preparedStatement = connection.prepareStatement(insertQuery)) { while (resultSet.next()) { preparedStatement.setString(1, resultSet.getString("UUID")); preparedStatement.setBoolean(2, resultSet.getBoolean("AutoCrafting")); + try { + preparedStatement.setBoolean(3, resultSet.getBoolean("Locked")); + } catch (SQLException ignored) { + preparedStatement.setBoolean(3, false); + } preparedStatement.executeUpdate(); } } @@ -356,3 +380,4 @@ public static DatabaseType getDatabaseType() { return currentType; } } + diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java index 2400de1..7c8508b 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java @@ -2,6 +2,7 @@ import studio.magemonkey.fusion.Fusion; import studio.magemonkey.fusion.cfg.sql.SQLManager; +import studio.magemonkey.fusion.cfg.sql.DatabaseType; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -16,9 +17,9 @@ public class FusionPlayersSQL { public FusionPlayersSQL() { try (PreparedStatement create = SQLManager.connection() .prepareStatement("CREATE TABLE IF NOT EXISTS " + Table + "(" - + "UUID varchar(36), " - + "AutoCrafting boolean, " - + "Locked boolean)")) { + + "UUID varchar(36) PRIMARY KEY, " + + "AutoCrafting boolean DEFAULT false, " + + "Locked boolean DEFAULT false)")) { create.execute(); boolean lockedColumnAdded = alterIfLockedNotExistent(); @@ -27,7 +28,7 @@ public FusionPlayersSQL() { .getLogger() .info("[SQL:FusionPlayersSQL:FusionPlayersSQL] Added 'Locked' column to 'fusion_players' table."); } - + } catch (SQLException e) { Fusion.getInstance() .getLogger() @@ -83,19 +84,30 @@ public boolean isLocked(UUID uuid) { } public void addPlayer(UUID uuid) { - if (hasPlayer(uuid)) - return; - try (PreparedStatement insert = SQLManager.connection() - .prepareStatement("INSERT INTO " + Table + "(UUID, AutoCrafting, Locked) VALUES(?,?,?)")) { + // Use a dialect-aware insert that ignores duplicates to avoid race conditions across nodes + DatabaseType dbType = SQLManager.getDatabaseType(); + String sql; + if (dbType == DatabaseType.LOCAL) { + // SQLite: INSERT OR IGNORE + sql = "INSERT OR IGNORE INTO " + Table + "(UUID, AutoCrafting, Locked) VALUES(?,?,?)"; + } else { + // MySQL/MariaDB: use ON DUPLICATE KEY UPDATE as a no-op + sql = "INSERT INTO " + Table + "(UUID, AutoCrafting, Locked) VALUES(?,?,?) ON DUPLICATE KEY UPDATE UUID=UUID"; + } + + try (PreparedStatement insert = SQLManager.connection().prepareStatement(sql)) { insert.setString(1, uuid.toString()); insert.setBoolean(2, false); insert.setBoolean(3, false); insert.execute(); } catch (SQLException e) { + // If we still hit a duplicate key exception, ignore it safely + if (e.getSQLState() != null && (e.getSQLState().startsWith("23") || e.getMessage().toLowerCase().contains("duplicate"))) { + return; + } Fusion.getInstance() .getLogger() - .warning("[SQL:FusionPlayersSQL:addPlayer] Something went wrong with the sql-connection: " - + e.getMessage()); + .warning("[SQL:FusionPlayersSQL:addPlayer] Something went wrong with the sql-connection: " + e.getMessage()); } } diff --git a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java index 21921eb..3874147 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java @@ -3,7 +3,6 @@ import lombok.Getter; import lombok.Setter; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import studio.magemonkey.fusion.Fusion; @@ -19,7 +18,6 @@ import java.util.Map; import java.util.TreeMap; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @SuppressWarnings("unused") @@ -35,8 +33,15 @@ public class FusionPlayer { @Setter private boolean autoCrafting; + // Track whether this player is currently locked for saving (in-memory mirror of DB lock) + @Getter + @Setter + private volatile boolean locked; + public FusionPlayer(UUID uuid) { this.uuid = uuid; + // initialize locked state from DB to reflect current status + this.locked = SQLManager.players().isLocked(uuid); autoCrafting = SQLManager.players().isAutoCrafting(uuid); for (Profession profession : SQLManager.professions().getProfessions(uuid)) { professions.put(profession.getName(), profession); @@ -346,7 +351,9 @@ public int getFinishedSize() { } public void save() { + // set DB lock and in-memory lock SQLManager.players().setLocked(uuid, true); + this.locked = true; Bukkit.getScheduler().runTaskAsynchronously(Fusion.getInstance(), () -> { SQLManager.players().setAutoCrafting(uuid, autoCrafting); @@ -369,6 +376,7 @@ public void save() { } */ SQLManager.players().setLocked(uuid, false); + this.locked = false; }); } } diff --git a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java index a3d9d98..7940dc1 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java @@ -1,6 +1,7 @@ package studio.magemonkey.fusion.data.player; import org.bukkit.entity.Player; +import studio.magemonkey.fusion.Fusion; import studio.magemonkey.fusion.cfg.sql.SQLManager; import java.util.Map; @@ -12,9 +13,21 @@ public class PlayerLoader { private static final Map cachedPlayers = new TreeMap<>(); public static FusionPlayer getPlayer(UUID uuid) { + // Always return a FusionPlayer instance. Previously this returned null when the player was marked as + // locked in the database (during async save), which caused NullPointerExceptions at many call sites. + // Returning a FusionPlayer ensures call sites remain stable; the locking is handled at the persistence layer. if (!cachedPlayers.containsKey(uuid)) { - if(SQLManager.players().isLocked(uuid)) return null; + boolean locked = SQLManager.players().isLocked(uuid); + if (locked) { + Fusion.getInstance().getLogger().info("Player " + uuid + " is currently locked for saving; returning FusionPlayer instance."); + } cachedPlayers.put(uuid, new FusionPlayer(uuid)); + } else { + // If we already have a cached player, refresh its in-memory locked state from the DB so + // that cross-node changes to the lock are reflected when other nodes fetch the same player. + FusionPlayer fp = cachedPlayers.get(uuid); + boolean locked = SQLManager.players().isLocked(uuid); + fp.setLocked(locked); } return cachedPlayers.get(uuid); } @@ -23,6 +36,45 @@ public static FusionPlayer getPlayer(Player player) { return getPlayer(player.getUniqueId()); } + /** + * Returns the FusionPlayer if it is not currently locked for saving, otherwise returns null. + * This provides an explicit, safe way for call sites that cannot proceed during a save window. + */ + public static FusionPlayer getPlayerIfReady(UUID uuid) { + FusionPlayer fp = getPlayer(uuid); + if (fp == null) return null; // defensive, but getPlayer never returns null + if (fp.isLocked()) return null; + return fp; + } + + public static FusionPlayer getPlayerIfReady(Player player) { + return getPlayerIfReady(player.getUniqueId()); + } + + /** + * Blocks (polling) until the player's DB lock is cleared or until timeoutMs is reached. + * Returns the FusionPlayer if the lock cleared within the timeout, otherwise returns null. + * Note: Blocking the main server thread is dangerous; call this from an async thread or keep timeout small. + */ + public static FusionPlayer getPlayerBlocking(UUID uuid, long timeoutMs) { + long start = System.currentTimeMillis(); + while (System.currentTimeMillis() - start < timeoutMs) { + FusionPlayer fp = getPlayer(uuid); + if (fp != null && !fp.isLocked()) return fp; + try { + Thread.sleep(50); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + return null; + } + + public static FusionPlayer getPlayerBlocking(Player player, long timeoutMs) { + return getPlayerBlocking(player.getUniqueId(), timeoutMs); + } + public static void loadPlayer(Player player) { cachedPlayers.put(player.getUniqueId(), new FusionPlayer(player.getUniqueId())); } From 4377c021248cd7d851651c3a40056b2daa2697ce Mon Sep 17 00:00:00 2001 From: MaksyKun <77341370+MaksyKun@users.noreply.github.com> Date: Sun, 15 Feb 2026 15:38:19 +0100 Subject: [PATCH 33/34] adressed async cache clearing improvement --- .../magemonkey/fusion/data/player/FusionPlayer.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java index 3874147..3835d46 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java @@ -355,17 +355,20 @@ public void save() { SQLManager.players().setLocked(uuid, true); this.locked = true; + Map queuesToSave = new TreeMap<>(cachedQueues); + Map recipeLimitsToSave = new TreeMap<>(cachedRecipeLimits); + cachedQueues.clear(); + cachedRecipeLimits.clear(); + Bukkit.getScheduler().runTaskAsynchronously(Fusion.getInstance(), () -> { SQLManager.players().setAutoCrafting(uuid, autoCrafting); for (Profession profession : professions.values()) { SQLManager.professions().setProfession(uuid, profession); } - for (CraftingQueue queue : cachedQueues.values()) { + for (CraftingQueue queue : queuesToSave.values()) { SQLManager.queues().saveCraftingQueue(queue); } - SQLManager.recipeLimits().saveRecipeLimits(uuid, cachedRecipeLimits); - cachedQueues.clear(); - cachedRecipeLimits.clear(); + SQLManager.recipeLimits().saveRecipeLimits(uuid, recipeLimitsToSave); /* In case of race conditions we wait a bit before unlocking the player. Not required but just to be safe. From 041c16ed42963ac3ba1b93b571f6c0a7cdb1a185 Mon Sep 17 00:00:00 2001 From: Trav Date: Mon, 16 Feb 2026 20:20:53 -0700 Subject: [PATCH 34/34] fix: Enhance saving lock mechanism and prevent data loss - Clear all player locks on startup in SQLManager to prevent permanent locks after a crash. - Modify FusionPlayer.save() to only clear caches when explicitly requested (e.g., during unload), preventing data loss during periodic saves. - Fix race conditions in Fusion.onPlayerJoin by waiting for pending save locks asynchronously before loading player data. - Optimize PlayerLoader.getPlayer by removing frequent database queries for lock status, relying on initial load instead. - Fix redundant save and reload logic in PlayerManager.savePlayer to ensure consistency. --- .../java/studio/magemonkey/fusion/Fusion.java | 19 +++++++++++------- .../magemonkey/fusion/api/PlayerManager.java | 4 +--- .../magemonkey/fusion/cfg/sql/SQLManager.java | 1 + .../cfg/sql/tables/FusionPlayersSQL.java | 13 ++++++++++++ .../fusion/data/player/FusionPlayer.java | 11 ++++++++-- .../fusion/data/player/PlayerLoader.java | 20 ++++++------------- 6 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/main/java/studio/magemonkey/fusion/Fusion.java b/src/main/java/studio/magemonkey/fusion/Fusion.java index 59eb78d..d948e3b 100644 --- a/src/main/java/studio/magemonkey/fusion/Fusion.java +++ b/src/main/java/studio/magemonkey/fusion/Fusion.java @@ -189,13 +189,18 @@ private void runQueueTask() { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { - PlayerLoader.loadPlayer(event.getPlayer()); - if(!Cfg.autoJoinProfessions.isEmpty()) { - Cfg.autoJoinProfessions(event.getPlayer()); - } - if (Cfg.craftingQueue) { - notifyForQueue(event.getPlayer()); - } + Player player = event.getPlayer(); + Bukkit.getScheduler().runTaskAsynchronously(this, () -> { + PlayerLoader.getPlayerBlocking(player, 5000); // Wait up to 5s for any pending saves to finish + Bukkit.getScheduler().runTask(this, () -> { + if(!Cfg.autoJoinProfessions.isEmpty()) { + Cfg.autoJoinProfessions(player); + } + if (Cfg.craftingQueue) { + notifyForQueue(player); + } + }); + }); } @EventHandler diff --git a/src/main/java/studio/magemonkey/fusion/api/PlayerManager.java b/src/main/java/studio/magemonkey/fusion/api/PlayerManager.java index 6d2c1d9..f9df2d0 100644 --- a/src/main/java/studio/magemonkey/fusion/api/PlayerManager.java +++ b/src/main/java/studio/magemonkey/fusion/api/PlayerManager.java @@ -43,7 +43,7 @@ public FusionPlayer getPlayer(UUID uuid) { /** * Save the player data of a player. - * This will save the player data to the database and reload the player. + * This will save the player data to the database. * * @param player The Player object of the player. */ @@ -51,8 +51,6 @@ public void savePlayer(Player player) { FusionPlayer fusionPlayer = getPlayer(player); if (fusionPlayer != null) { fusionPlayer.save(); - PlayerLoader.unloadPlayer(player); - PlayerLoader.loadPlayer(player); } else { FusionAPI.getInstance() .getLogger() diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java index 6ea29b8..47c26f6 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/SQLManager.java @@ -62,6 +62,7 @@ public static void init() { Fusion.getInstance().getLogger().severe("Failed to initialize the Connection."); } else { fusionPlayersSQL = new FusionPlayersSQL(); + fusionPlayersSQL.clearAllLocks(); fusionProfessionsSQL = new FusionProfessionsSQL(); fusionQueuesSQL = new FusionQueuesSQL(); fusionRecipeLimitsSQL = new FusionRecipeLimitsSQL(); diff --git a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java index 7c8508b..69539b2 100644 --- a/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java +++ b/src/main/java/studio/magemonkey/fusion/cfg/sql/tables/FusionPlayersSQL.java @@ -67,6 +67,19 @@ public void setLocked(UUID uuid, boolean locked) { } } + public void clearAllLocks() { + try (PreparedStatement update = SQLManager.connection() + .prepareStatement("UPDATE " + Table + " SET Locked=?")) { + update.setBoolean(1, false); + update.execute(); + } catch (SQLException e) { + Fusion.getInstance() + .getLogger() + .warning("[SQL:FusionPlayersSQL:clearAllLocks] Something went wrong with the sql-connection: " + + e.getMessage()); + } + } + public boolean isLocked(UUID uuid) { try (PreparedStatement select = SQLManager.connection() .prepareStatement("SELECT Locked FROM " + Table + " WHERE UUID=?")) { diff --git a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java index 3835d46..0d9ca4c 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java @@ -351,14 +351,21 @@ public int getFinishedSize() { } public void save() { + save(false); + } + + public void save(boolean clearCaches) { // set DB lock and in-memory lock SQLManager.players().setLocked(uuid, true); this.locked = true; Map queuesToSave = new TreeMap<>(cachedQueues); Map recipeLimitsToSave = new TreeMap<>(cachedRecipeLimits); - cachedQueues.clear(); - cachedRecipeLimits.clear(); + + if (clearCaches) { + cachedQueues.clear(); + cachedRecipeLimits.clear(); + } Bukkit.getScheduler().runTaskAsynchronously(Fusion.getInstance(), () -> { SQLManager.players().setAutoCrafting(uuid, autoCrafting); diff --git a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java index 7940dc1..8c21d38 100644 --- a/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java +++ b/src/main/java/studio/magemonkey/fusion/data/player/PlayerLoader.java @@ -17,17 +17,7 @@ public static FusionPlayer getPlayer(UUID uuid) { // locked in the database (during async save), which caused NullPointerExceptions at many call sites. // Returning a FusionPlayer ensures call sites remain stable; the locking is handled at the persistence layer. if (!cachedPlayers.containsKey(uuid)) { - boolean locked = SQLManager.players().isLocked(uuid); - if (locked) { - Fusion.getInstance().getLogger().info("Player " + uuid + " is currently locked for saving; returning FusionPlayer instance."); - } cachedPlayers.put(uuid, new FusionPlayer(uuid)); - } else { - // If we already have a cached player, refresh its in-memory locked state from the DB so - // that cross-node changes to the lock are reflected when other nodes fetch the same player. - FusionPlayer fp = cachedPlayers.get(uuid); - boolean locked = SQLManager.players().isLocked(uuid); - fp.setLocked(locked); } return cachedPlayers.get(uuid); } @@ -59,8 +49,10 @@ public static FusionPlayer getPlayerIfReady(Player player) { public static FusionPlayer getPlayerBlocking(UUID uuid, long timeoutMs) { long start = System.currentTimeMillis(); while (System.currentTimeMillis() - start < timeoutMs) { - FusionPlayer fp = getPlayer(uuid); - if (fp != null && !fp.isLocked()) return fp; + boolean locked = SQLManager.players().isLocked(uuid); + if (!locked) { + return getPlayer(uuid); + } try { Thread.sleep(50); } catch (InterruptedException e) { @@ -82,14 +74,14 @@ public static void loadPlayer(Player player) { public static void unloadPlayer(Player player) { if (cachedPlayers.containsKey(player.getUniqueId())) { FusionPlayer fusionPlayer = cachedPlayers.get(player.getUniqueId()); - fusionPlayer.save(); + fusionPlayer.save(true); cachedPlayers.remove(player.getUniqueId()); } } public static void clearCache() { for (FusionPlayer fusionPlayer : cachedPlayers.values()) - fusionPlayer.save(); + fusionPlayer.save(true); cachedPlayers.clear(); } }