From 213b9ce3210756890d85900eb8b0752fa344daf4 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Mon, 28 Apr 2025 09:05:23 +0200
Subject: [PATCH 01/16] updates codex dependency
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 316b2ca..f45913a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,7 +16,7 @@
16
- 1.1.0-R0.6-SNAPSHOT
+ 1.1.0-R1
From 1e1140974f8328650c92b4cdc4f0c4a375dd016c Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Mon, 28 Apr 2025 09:05:39 +0200
Subject: [PATCH 02/16] fixed queue service for new icon settings and result
section
---
.../api/events/QueueItemFinishedEvent.java | 20 ++----
.../api/events/services/QueueService.java | 65 +++++++++----------
.../editors/professions/RecipeEditorCfg.java | 2 +-
.../fusion/commands/FusionEditorCommand.java | 2 +-
.../data/professions/ProfessionResults.java | 28 +++++---
.../fusion/data/queue/CraftingQueue.java | 5 +-
6 files changed, 61 insertions(+), 61 deletions(-)
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 623c4ce..19552a6 100644
--- a/src/main/java/studio/magemonkey/fusion/api/events/QueueItemFinishedEvent.java
+++ b/src/main/java/studio/magemonkey/fusion/api/events/QueueItemFinishedEvent.java
@@ -3,10 +3,12 @@
import lombok.Getter;
import lombok.Setter;
import org.bukkit.entity.Player;
-import org.bukkit.inventory.ItemStack;
import studio.magemonkey.fusion.cfg.ProfessionsCfg;
import studio.magemonkey.fusion.data.queue.CraftingQueue;
import studio.magemonkey.fusion.data.queue.QueueItem;
+import studio.magemonkey.fusion.data.recipes.RecipeItem;
+
+import java.util.List;
@Getter
public class QueueItemFinishedEvent extends FusionEvent {
@@ -23,12 +25,7 @@ public class QueueItemFinishedEvent extends FusionEvent {
* The result item
*/
@Setter
- private ItemStack resultItem;
- /**
- * The amount of the result item
- */
- @Setter
- private int resultAmount;
+ private List resultItems;
/**
* Constructor for the QueueItemFinishedEvent
@@ -37,19 +34,16 @@ public class QueueItemFinishedEvent extends FusionEvent {
* @param player The player that finished the item
* @param queue The crafting queue
* @param queueItem The queue item
- * @param resultItem The result item
- * @param resultAmount The amount of the result item
+ * @param resultItems The result items
*/
public QueueItemFinishedEvent(String professionName,
Player player,
CraftingQueue queue,
QueueItem queueItem,
- ItemStack resultItem,
- int resultAmount) {
+ List resultItems) {
super(professionName, ProfessionsCfg.getTable(professionName), player);
this.queue = queue;
this.queueItem = queueItem;
- this.resultItem = resultItem;
- this.resultAmount = resultAmount;
+ this.resultItems = resultItems;
}
}
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 41f0bfa..f76a24b 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
@@ -17,6 +17,7 @@
import studio.magemonkey.fusion.data.queue.CraftingQueue;
import studio.magemonkey.fusion.data.queue.QueueItem;
import studio.magemonkey.fusion.data.recipes.CraftingTable;
+import studio.magemonkey.fusion.data.recipes.RecipeItem;
import studio.magemonkey.fusion.util.PlayerUtil;
import java.util.*;
@@ -25,10 +26,11 @@ public class QueueService {
/**
* Call the QueueItemAddedEvent.
+ *
* @param player The player that adds the item to the queue.
- * @param table The crafting table (profession) the player is using.
- * @param queue The crafting queue the player is using.
- * @param item The queue item that is added to the queue.
+ * @param table The crafting table (profession) the player is using.
+ * @param queue The crafting queue the player is using.
+ * @param item The queue item that is added to the queue.
*/
public void addQueueItem(Player player, CraftingTable table, CraftingQueue queue, QueueItem item) {
QueueItemAddedEvent event = new QueueItemAddedEvent(table.getName(), player, queue, item);
@@ -41,12 +43,13 @@ public void addQueueItem(Player player, CraftingTable table, CraftingQueue queue
/**
* Call the QueueItemCanceledEvent.
- * @param player The player that cancels the item in the queue.
- * @param table The crafting table (profession) the player is using.
- * @param queue The crafting queue the player is using.
- * @param item The queue item that is canceled.
- * @param finished If the item is finished.
- * @param refunded If the item ingredients will be refunded.
+ *
+ * @param player The player that cancels the item in the queue.
+ * @param table The crafting table (profession) the player is using.
+ * @param queue The crafting queue the player is using.
+ * @param item The queue item that is canceled.
+ * @param finished If the item is finished.
+ * @param refunded If the item ingredients will be refunded.
* @param refundItems The items that will be refunded in case `refunded=true`.
*/
public void cancelQueueItem(Player player,
@@ -104,21 +107,20 @@ public void cancelQueueItem(Player player,
/**
* Call the QueueItemFinishedEvent.
- * @param player The player that finishes the item in the queue.
- * @param table The crafting table (profession) the player is using.
- * @param queue The crafting queue the player is using.
- * @param item The queue item that is finished.
- * @param resultItem The result item of the queue item.
- * @param resultAmount The amount of the result item.
+ *
+ * @param player The player that finishes the item in the queue.
+ * @param table The crafting table (profession) the player is using.
+ * @param queue The crafting queue the player is using.
+ * @param item The queue item that is finished.
+ * @param resultItems The result items of the queue item.
*/
public void finishQueueItem(Player player,
CraftingTable table,
CraftingQueue queue,
QueueItem item,
- ItemStack resultItem,
- int resultAmount) {
+ List resultItems) {
QueueItemFinishedEvent event =
- new QueueItemFinishedEvent(table.getName(), player, queue, item, resultItem, resultAmount);
+ new QueueItemFinishedEvent(table.getName(), player, queue, item, resultItems);
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
if (event.getFusionPlayer().hasRecipeLimitReached(event.getQueueItem().getRecipe())) {
@@ -139,11 +141,9 @@ public void finishQueueItem(Player player,
return;
}
// Items if no commands exist
- if(!item.getRecipe().getResults().hasCommandsOrItems()) {
- ItemStack result =
- event.getQueueItem().getRecipe().getDivinityRecipeMeta() == null ? event.getResultItem()
+ if (!item.getRecipe().getResults().hasCommandsOrItems()) {
+ ItemStack result = event.getQueueItem().getRecipe().getDivinityRecipeMeta() == null ? event.getQueueItem().getRecipe().getSettings().getRecipeItem().getItemStack()
: event.getQueueItem().getRecipe().getDivinityRecipeMeta().generateItem();
- result.setAmount(event.getResultAmount());
// If there is no space in the inventory, drop the items
Collection notAdded = player.getInventory().addItem(result).values();
if (!notAdded.isEmpty()) {
@@ -153,24 +153,19 @@ public void finishQueueItem(Player player,
}
}
} else {
- if(!item.getRecipe().getResults().getCommands().isEmpty()) {
+ if (!item.getRecipe().getResults().getCommands().isEmpty()) {
// If there are commands, we need to delay the item giving
DelayedCommand.invoke(Fusion.getInstance(), player, item.getRecipe().getResults().getCommands());
}
- if(!item.getRecipe().getResults().getItems().isEmpty()) {
+ if (!item.getRecipe().getResults().getItems().isEmpty()) {
// If there are items, we need to delay the item giving
- for (String itemString : item.getRecipe().getResults().getItems()) {
- try {
- ItemStack itemStack = CodexEngine.get().getItemManager().getItemType(itemString).create();
- if (itemStack != null) {
- Collection remainings = player.getInventory().addItem(itemStack).values();
- if(!remainings.isEmpty()) {
- remainings.forEach(_item -> player.getWorld().dropItemNaturally(player.getLocation(), _item));
- }
+ for (RecipeItem resultItem : resultItems) {
+ ItemStack itemStack = resultItem.getItemStack();
+ if (itemStack != null) {
+ Collection remainings = player.getInventory().addItem(itemStack).values();
+ if (!remainings.isEmpty()) {
+ remainings.forEach(_item -> player.getWorld().dropItemNaturally(player.getLocation(), _item));
}
- } catch (MissingItemException | MissingProviderException e) {
- Fusion.getInstance().getLogger().warning("Failed to give item: " + itemString);
- player.sendMessage("§cFailed to give you a specific item from the recipe. Please contact an admin.");
}
}
}
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 244f64f..3a15a10 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
@@ -169,7 +169,7 @@ public ItemStack getSubIcon(Recipe recipe, String icon) {
} else if (lore.get(i).contains(MessageUtil.getReplacement("items"))) {
lore.remove(i);
int newLines = 1;
- for (String line : recipe.getResults().getItems()) {
+ for (String line : recipe.getResults().getItemNames()) {
lore.add(i - 1 + newLines,
config.getString("subEditor.icons.items.itemPrefix", "&7- &a$- ")
.replace(MessageUtil.getReplacement("item"),
diff --git a/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java b/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java
index dbd3172..f7b3482 100644
--- a/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java
+++ b/src/main/java/studio/magemonkey/fusion/commands/FusionEditorCommand.java
@@ -1096,7 +1096,7 @@ private void addRecipeItem(ProfessionEditor professionEditor, String[] args) {
.getRecipeItemEditor()
.getRecipe()
.getResults()
- .getItems()
+ .getItemNames()
.add(itemName + ":" + amount);
professionEditor.getRecipeEditor().getRecipeItemEditor().reload(true);
} catch (NumberFormatException e) {
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 be60228..2fe2b08 100644
--- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionResults.java
+++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionResults.java
@@ -8,6 +8,7 @@
import studio.magemonkey.codex.api.DelayedCommand;
import studio.magemonkey.codex.util.DeserializationWorker;
import studio.magemonkey.codex.util.SerializationBuilder;
+import studio.magemonkey.fusion.data.recipes.RecipeItem;
import java.util.*;
import java.util.stream.Collectors;
@@ -21,19 +22,24 @@ public class ProfessionResults implements ConfigurationSerializable {
// Rewards
private long professionExp;
private int vanillaExp;
- private List
items = new LinkedList<>();
+ private List items = new LinkedList<>();
+ private List itemNames = new LinkedList<>();
private List commands = new LinkedList<>();
public ProfessionResults(String profession,
long professionExp,
int vanillaExp,
- List items,
+ List itemNames,
List commands) {
this.profession = profession;
this.professionExp = professionExp;
this.vanillaExp = vanillaExp;
- this.items = items;
+ this.itemNames = itemNames;
this.commands = commands;
+
+ for (String itemName : itemNames) {
+ this.items.add(RecipeItem.fromConfig(itemName));
+ }
}
public ProfessionResults(String profession, ConfigurationSection config) {
@@ -41,7 +47,10 @@ public ProfessionResults(String profession, ConfigurationSection config) {
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.items = config.getList("rewards.items", new LinkedList<>()).stream().map(Object::toString).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));
+ }
}
public ProfessionResults(String profession, DeserializationWorker dw) {
@@ -72,7 +81,10 @@ public ProfessionResults(String profession, DeserializationWorker dw) {
List items = (List) resultsSection.getOrDefault("items", new ArrayList<>());
if (items != null) {
- items.forEach(item -> { this.items.add(item.toString()); });
+ items.forEach(item -> {
+ this.itemNames.add(item.toString());
+ this.items.add(RecipeItem.fromConfig(item));
+ });
}
}
}
@@ -83,7 +95,7 @@ public ProfessionResults(String profession, DeserializationWorker dw) {
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("items", new ArrayList<>(this.items));
+ resultMap.put("items", new ArrayList<>(this.itemNames));
return SerializationBuilder.start(4).append("results", resultMap).build();
}
@@ -96,11 +108,11 @@ public static ProfessionResults copy(ProfessionResults results) {
return new ProfessionResults(results.getProfession(),
results.getProfessionExp(),
results.getVanillaExp(),
- new ArrayList<>(results.getItems()),
+ new ArrayList<>(results.getItemNames()),
cmds);
}
public boolean hasCommandsOrItems() {
- return professionExp > 0 || vanillaExp > 0 || !commands.isEmpty() || !items.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 d871dbf..eccda48 100644
--- a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java
+++ b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java
@@ -102,15 +102,14 @@ public void finishAllRecipes() {
public void finishRecipe(QueueItem item) {
if (item.isDone()) {
// TODO consider getting the item back from CodexEngine in case the icon is modified
- RecipeItem recipeItem = item.getRecipe().getSettings().getRecipeItem();
+ List resultItems = item.getRecipe().getResults().getItems();
FusionAPI.getEventServices()
.getQueueService()
.finishQueueItem(player,
ProfessionsCfg.getTable(profession),
this,
item,
- recipeItem.getItemStack(),
- recipeItem.getAmount());
+ resultItems);
}
}
From db4b27ad79f5e2bbd8070afe97164c5660e4ab46 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Mon, 28 Apr 2025 16:19:56 +0200
Subject: [PATCH 03/16] removed extensive vanilla lore for ingredients due to
many bugs
---
.../fusion/cfg/CraftingRequirementsCfg.java | 118 ------------------
.../professions/CalculatedProfession.java | 21 +---
.../fusion/data/recipes/CalculatedRecipe.java | 45 +------
.../resources/lang/CraftingRequirements.yml | 76 -----------
4 files changed, 2 insertions(+), 258 deletions(-)
diff --git a/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java
index 836bfb7..938f177 100644
--- a/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java
+++ b/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java
@@ -117,124 +117,6 @@ private static String getCustomHighlight(String path) {
return config.getString(path + ".ingredients.highlightCustomItem", "");
}
- public static boolean hasExtensionEnabled(String path) {
- return config.getBoolean(path + ".ingredients.extensions.enabled", true);
- }
-
- public static boolean hasOnlyVanillaExtension(String path) {
- return config.getBoolean(path + ".ingredients.extensions.onlyVanilla", false);
- }
-
- public static List getExtensionLoreLine(String path, List provided, List required) {
- List entries = new ArrayList<>();
- if (!config.getBoolean(path + ".ingredients.extensions.lore.enabled")) return entries;
- if (required.isEmpty()) return entries;
- for (String lore : required) {
- boolean fulfilled = provided.contains(lore);
- String line =
- config.getString(path + ".ingredients.extensions.lore." + (fulfilled ? "true" : "false"),
- " $");
- line = line.replace("$", getFulfilled()).replace("$", getUnfulfilled());
- line = line.replace("$", ChatUT.serialize(Component.text(lore)));
- entries.add(ChatUT.hexString(line));
- }
- for (String lore : provided) {
- if (!required.contains(lore)) {
- String line = config.getString(path + ".ingredients.extensions.lore.false",
- " $ &7($&7)");
- line = line.replace("$", getUnfulfilled());
- line = line.replace("$", ChatUT.serialize(Component.text(lore)));
- entries.add(ChatUT.hexString(line));
- }
- }
- return entries;
- }
-
- public static List getExtensionEnchantmentLine(String path,
- Map provided,
- Map required) {
- List entries = new ArrayList<>();
- if (!config.getBoolean(path + ".ingredients.extensions.enchantments.enabled")) return entries;
- if (required.isEmpty()) return entries;
- for (Map.Entry entry : required.entrySet()) {
- boolean fulfilled = provided.containsKey(entry.getKey()) && Objects.equals(provided.get(entry.getKey()),
- entry.getValue());
- String line =
- config.getString(path + ".ingredients.extensions.enchantments." + (fulfilled ? "true" : "false"),
- fulfilled ? " &8• &7Enchant &9$ &7(&a$&7) &7($&7)"
- : " &8• &7Enchant &9$ &7(&a$&7) &7($&7)");
- line = line.replace("$", String.valueOf(entry.getValue()));
- line = line.replace("$", getFulfilled()).replace("$", getUnfulfilled());
- line = line.replace("$",
- ChatUT.serialize(Component.translatable(entry.getKey().getTranslationKey())));
- entries.add(ChatUT.hexString(line));
- }
- for (Map.Entry entry : provided.entrySet()) {
- if (!required.containsKey(entry.getKey())) {
- String line = config.getString(path + ".ingredients.extensions.enchantments.false",
- " &8• &7Enchant &9$ &7(&a$&7) &7($&7)");
- line = line.replace("$", "0");
- line = line.replace("$", getUnfulfilled());
- line = line.replace("$",
- ChatUT.serialize(Component.translatable(entry.getKey().getTranslationKey())));
- entries.add(ChatUT.hexString(line));
- }
- }
- return entries;
- }
-
- public static List getExtensionFlagLine(String path, Set provided, Set required) {
- List entries = new ArrayList<>();
- if (!config.getBoolean(path + ".ingredients.extensions.flags.enabled")) return entries;
- if (required.isEmpty()) return entries;
- for (ItemFlag flag : required) {
- boolean fulfilled = provided.contains(flag);
- String line = config.getString(path + ".ingredients.extensions.flags." + (fulfilled ? "true" : "false"),
- fulfilled ? " &8• &7Flag &9$ &7(&a$&7) &7($&7)"
- : " &8• &7Flag &9$ &7(&a$&7) &7($&7)");
- line = line.replace("$", getFulfilled());
- line = line.replace("$", getFulfilled()).replace("$", getUnfulfilled());
- line = line.replace("$", flag.toString().toLowerCase());
- entries.add(ChatUT.hexString(line));
- }
- return entries;
- }
-
- public static String getExtensionUnbreakableLine(String path, boolean provided, boolean required) {
- if (!config.getBoolean(path + ".ingredients.extensions.unbreakable.enabled")) return null;
- boolean fulfilled = provided == required;
- String line = config.getString(path + ".ingredients.extensions.unbreakable." + (fulfilled ? "true" : "false"),
- fulfilled ? " &8• &7Unbreakable &7(&a$&7) &7($&7)"
- : " &8• &7Unbreakable &7(&a$&7) &7($&7)");
- line = line.replace("$", String.valueOf(required));
- line = line.replace("$", getFulfilled()).replace("$", getUnfulfilled());
- return ChatUT.hexString(line);
- }
-
- public static String getExtensionDurabilityLine(String path, int provided, int required) {
- if (!config.getBoolean(path + ".ingredients.extensions.durability.enabled")) return null;
- boolean fulfilled = provided == required;
- String line = config.getString(path + ".ingredients.extensions.durability." + (fulfilled ? "true" : "false"),
- fulfilled ? " &8• &7Durability &7(&a$&7) &7($&7)"
- : " &8• &7Durability &7(&a$&7) &7($&7)");
- line = line.replace("$", String.valueOf(required));
- line = line.replace("$", getFulfilled()).replace("$", getUnfulfilled());
- return ChatUT.hexString(line);
- }
-
- public static String getExtensionCustomModelDataLine(String path, int provided, int required) {
- if (!config.getBoolean(path + ".ingredients.extensions.customModelData.enabled")) return null;
- boolean fulfilled = provided == required;
- String line =
- config.getString(path + ".ingredients.extensions.customModelData." + (fulfilled ? "true" : "false"),
- fulfilled ? " &8• &7Custom Model &7(&a$&7) &7($&7)"
- : " &8• &7Custom Model &7(&a$&7) &7($&7)");
- line = line.replace("$", String.valueOf(required));
- line = line.replace("$", getFulfilled()).replace("$", getUnfulfilled());
- return ChatUT.hexString(line);
- }
-
-
/* Recipe-related crafting requirements */
public static String getCanCraft(boolean fulfilled) {
return ChatUT.hexString(config.getString("recipes.canCraft." + (fulfilled ? "true" : "false"),
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 09da1d5..be5ea15 100644
--- a/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java
+++ b/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java
@@ -85,18 +85,15 @@ public static CalculatedProfession create(ProfessionConditions conditions,
List> eqItems = Recipe.getItems(items);
Collection localPattern = new HashSet<>(conditions.getRequiredItems());
- boolean isExtensionEnabled = CraftingRequirementsCfg.hasExtensionEnabled("professions");
- boolean isVanillaOnly = CraftingRequirementsCfg.hasOnlyVanillaExtension("professions");
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;
- List extensionLines = new ArrayList<>();
for (Pair entry : eqItems) {
ItemStack item = entry.getKey().clone();
- if (CalculatedRecipe.isSimilar("professions", recipeItemStackOne, item, extensionLines)) {
+ if (CalculatedRecipe.isSimilar(recipeItemStackOne, item)) {
eqEntry = entry;
break;
}
@@ -110,14 +107,6 @@ public static CalculatedProfession create(ProfessionConditions conditions,
recipeItem,
eqAmount,
patternAmount)).append('\n');
- if (isExtensionEnabled) {
- if ((isVanillaOnly && !(recipeItem instanceof RecipeEconomyItem)) || (!isVanillaOnly
- && (recipeItem instanceof RecipeEconomyItem))) {
- for (String extension : extensionLines) {
- lore.append(extension).append('\n');
- }
- }
- }
continue;
}
if (eqAmount == patternAmount) {
@@ -130,14 +119,6 @@ public static CalculatedProfession create(ProfessionConditions conditions,
it.remove();
lore.append(CraftingRequirementsCfg.getIngredientLine("recipes", recipeItem, eqAmount, patternAmount))
.append('\n');
- if (isExtensionEnabled) {
- if ((isVanillaOnly && !(recipeItem instanceof RecipeEconomyItem)) || (!isVanillaOnly
- && (recipeItem instanceof RecipeEconomyItem))) {
- for (String extension : extensionLines) {
- lore.append(extension).append('\n');
- }
- }
- }
}
String canJoinLine = CraftingRequirementsCfg.getCanJoin(canJoin);
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 30a206b..720a364 100644
--- a/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java
+++ b/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java
@@ -147,18 +147,15 @@ public static CalculatedRecipe create(Recipe recipe,
Collection localPattern = new LinkedHashSet<>(recipe.getConditions().getRequiredItems());
- boolean isExtensionEnabled = CraftingRequirementsCfg.hasExtensionEnabled("recipes");
- boolean isVanillaOnly = CraftingRequirementsCfg.hasOnlyVanillaExtension("recipes");
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;
- List extensionLines = new ArrayList<>();
for (Pair entry : eqItems) {
ItemStack item = entry.getKey().clone();
- if (CalculatedRecipe.isSimilar("recipes", recipeItemStackOne, item, extensionLines)) {
+ if (CalculatedRecipe.isSimilar(recipeItemStackOne, item)) {
eqEntry = entry;
break;
}
@@ -172,14 +169,6 @@ public static CalculatedRecipe create(Recipe recipe,
recipeItem,
eqAmount,
patternAmount)).append('\n');
- if (isExtensionEnabled) {
- if ((isVanillaOnly && !(recipeItem instanceof RecipeEconomyItem)) || (!isVanillaOnly
- && (recipeItem instanceof RecipeEconomyItem))) {
- for (String extension : extensionLines) {
- lore.append(extension).append('\n');
- }
- }
- }
continue;
}
if (eqAmount == patternAmount) {
@@ -192,14 +181,6 @@ public static CalculatedRecipe create(Recipe recipe,
it.remove();
lore.append(CraftingRequirementsCfg.getIngredientLine("recipes", recipeItem, eqAmount, patternAmount))
.append('\n');
- if (isExtensionEnabled) {
- if ((isVanillaOnly && !(recipeItem instanceof RecipeEconomyItem)) || (!isVanillaOnly
- && (recipeItem instanceof RecipeEconomyItem))) {
- for (String extension : extensionLines) {
- lore.append(extension).append('\n');
- }
- }
- }
}
String canCraftLine = CraftingRequirementsCfg.getCanCraft(canCraft);
@@ -268,14 +249,6 @@ public boolean equals(Object o) {
}
public static boolean isSimilar(ItemStack is1, ItemStack is2) {
- return isSimilar("recipes", is1, is2);
- }
-
- public static boolean isSimilar(String path, ItemStack is1, ItemStack is2) {
- return isSimilar(path, is1, is2, new ArrayList<>());
- }
-
- public static boolean isSimilar(String path, ItemStack is1, ItemStack is2, List checkingLines) {
//More relaxed comparison
if (is1.getType() != is2.getType())
return false;
@@ -314,7 +287,6 @@ public static boolean isSimilar(String path, ItemStack is1, ItemStack is2, List<
}
}
}
- checkingLines.addAll(CraftingRequirementsCfg.getExtensionLoreLine(path, lore2, lore1));
}
// Check for enchantments
@@ -329,7 +301,6 @@ public static boolean isSimilar(String path, ItemStack is1, ItemStack is2, List<
if (!ench2.containsKey(entry.getKey()) || !ench2.get(entry.getKey()).equals(entry.getValue()))
isValid = false;
}
- checkingLines.addAll(CraftingRequirementsCfg.getExtensionEnchantmentLine(path, ench2, ench1));
} else {
if (im1.hasEnchants()) {
Map ench1 = im1.getEnchants();
@@ -340,7 +311,6 @@ public static boolean isSimilar(String path, ItemStack is1, ItemStack is2, List<
if (!ench2.containsKey(entry.getKey()) || !ench2.get(entry.getKey()).equals(entry.getValue()))
isValid = false;
}
- checkingLines.addAll(CraftingRequirementsCfg.getExtensionEnchantmentLine(path, ench2, ench1));
}
}
// Check for flags
@@ -351,31 +321,19 @@ public static boolean isSimilar(String path, ItemStack is1, ItemStack is2, List<
if (!im2.getItemFlags().contains(flag))
isValid = false;
}
- checkingLines.addAll(CraftingRequirementsCfg.getExtensionFlagLine(path,
- im2.getItemFlags(),
- im1.getItemFlags()));
}
// Check for custom model data
if (im1.hasCustomModelData() && im2.hasCustomModelData()) {
if (im1.getCustomModelData() != im2.getCustomModelData())
isValid = false;
- checkingLines.add(CraftingRequirementsCfg.getExtensionCustomModelDataLine(path,
- im2.getCustomModelData(),
- im1.getCustomModelData()));
} else if (im1.hasCustomModelData() || im2.hasCustomModelData()) {
isValid = false;
- checkingLines.add(CraftingRequirementsCfg.getExtensionCustomModelDataLine(path,
- im2.hasCustomModelData() ? im2.getCustomModelData() : 0,
- im1.hasCustomModelData() ? im1.getCustomModelData() : 0));
}
// Check if unbreakable
if (im1.isUnbreakable()) {
if (im2.isUnbreakable())
isValid = false;
- checkingLines.add(CraftingRequirementsCfg.getExtensionUnbreakableLine(path,
- im2.isUnbreakable(),
- im1.isUnbreakable()));
}
// Check for durability if instanceof Damageable
if (im1 instanceof Damageable dmg && dmg.getDamage() > 0) {
@@ -383,7 +341,6 @@ public static boolean isSimilar(String path, ItemStack is1, ItemStack is2, List<
int damage2 = im2 instanceof Damageable dmg2 ? dmg2.getDamage() : 0;
if (damage1 != damage2)
isValid = false;
- checkingLines.add(CraftingRequirementsCfg.getExtensionDurabilityLine(path, damage2, damage1));
}
// If all those checks failed, try to check once more through the native item meta check
diff --git a/src/main/resources/lang/CraftingRequirements.yml b/src/main/resources/lang/CraftingRequirements.yml
index 47f3e20..d19c298 100644
--- a/src/main/resources/lang/CraftingRequirements.yml
+++ b/src/main/resources/lang/CraftingRequirements.yml
@@ -33,44 +33,6 @@ recipes:
# Setting this to something like '&d*' would make the ingredient line into '&d*- Iron Ore <...>'
# If you want to leave it, simply make it blank
highlightCustomItem: "&d*"
-
- # The vanilla extension that can be displayed in the ingredients
- extensions:
- # Rather those extensions are enabled
- enabled: true
- # Rather those extensions will be shown on vanilla items only. If set to 'false', items from Divinity will be extended too
- onlyVanilla: true
- # The lore that needs to be displayed in the ingredients
- # Since this is kind of redundant to display twice, you will only see how it should look like.
- lore:
- enabled: true
- 'true': " $ &7($&7)"
- 'false': " $ &7($&7)"
- # The enchantments that are displayed in the ingredients
- enchantments:
- enabled: true
- 'true': " &8• &7Enchant &9$ &7(&a$&7) &7($&7)"
- 'false': " &8• &7Enchant &9$ &7(&c$&7) &7($&7)"
- # The flags that are displayed in the ingredients
- flags:
- enabled: false
- 'true': " &8• &7Flag &9$ &7(&a$&7) &7($&7)"
- 'false': " &8• &7Flag &9$ &7(&a$&7) &7($&7)"
- # The unbreakable that is displayed in the ingredients
- unbreakable:
- enabled: false
- 'true': " &8• &7Unbreakable &7(&a$&7) &7($&7)"
- 'false': " &8• &7Unbreakable &7(&c$&7) &7($&7)"
- # The durability that is displayed in the ingredients (only if existent)
- durability:
- enabled: true
- 'true': " &8• &7Durability &7(&a$&7) &7($&7)"
- 'false': " &8• &7Durability &7(&a$&7) &7($&7)"
- # The custom model data that is displayed in the ingredients
- customModelData:
- enabled: false
- 'true': " &8• &7Custom Model &7(&a$&7) &7($&7)"
- 'false': " &8• &7Custom Model &7(&a$&7) &7($&7)"
# The money that is displayed in the crafting requirements
money:
'true': "&6- &eMoney: &7(&a$$&8/&7$$&7)"
@@ -131,44 +93,6 @@ professions:
# Setting this to something like '&d*' would make the ingredient line into '&d*- Iron Ore <...>'
# If you want to leave it, simply make it blank
highlightCustomItem: "&d*"
-
- # The vanilla extension that can be displayed in the ingredients
- extensions:
- # Rather those extensions are enabled
- enabled: true
- # Rather those extensions will be shown on vanilla items only. If set to 'false', items from Divinity will be extended too
- onlyVanilla: true
- # The lore that needs to be displayed in the ingredients
- # Since this is kind of redundant to display twice, you will only see how it should look like.
- lore:
- enabled: true
- 'true': " $ &7($&7)"
- 'false': " $ &7($&7)"
- # The enchantments that are displayed in the ingredients
- enchantments:
- enabled: true
- 'true': " &8• &7Enchant &9$ &7(&a$&7) &7($&7)"
- 'false': " &8• &7Enchant &9$ &7(&c$&7) &7($&7)"
- # The flags that are displayed in the ingredients
- flags:
- enabled: false
- 'true': " &8• &7Flag &9$ &7(&a$&7) &7($&7)"
- 'false': " &8• &7Flag &9$ &7(&a$&7) &7($&7)"
- # The unbreakable that is displayed in the ingredients
- unbreakable:
- enabled: false
- 'true': " &8• &7Unbreakable &7(&a$&7) &7($&7)"
- 'false': " &8• &7Unbreakable &7(&c$&7) &7($&7)"
- # The durability that is displayed in the ingredients (only if existent)
- durability:
- enabled: true
- 'true': " &8• &7Durability &7(&a$&7) &7($&7)"
- 'false': " &8• &7Durability &7(&a$&7) &7($&7)"
- # The custom model data that is displayed in the ingredients
- customModelData:
- enabled: false
- 'true': " &8• &7Custom Model &7(&a$&7) &7($&7)"
- 'false': " &8• &7Custom Model &7(&a$&7) &7($&7)"
# The money that is displayed in the crafting requirements
money:
'true': "&6- &eMoney: &7(&a$$&8/&7$$&7)"
From cd327e67010bc1578fd6cd1ed8f9001bb06747ab Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 1 Jun 2025 16:32:32 +0200
Subject: [PATCH 04/16] added singletone pattern for static instances of the
api (they were null for some times)
---
.../studio/magemonkey/fusion/api/FusionAPI.java | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java b/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java
index 8578e92..fb5330a 100644
--- a/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java
+++ b/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java
@@ -10,11 +10,8 @@ public class FusionAPI {
@Getter
private static final JavaPlugin instance = Fusion.getInstance();
- @Getter
private static ProfessionManager professionManager;
- @Getter
private static PlayerManager playerManager;
- @Getter
private static EventServices eventServices;
public static void init() {
@@ -24,4 +21,15 @@ public static void init() {
FusionAPI.getInstance().getLogger().info("FusionAPI has been initialized.");
}
+ public static ProfessionManager getProfessionManager() {
+ return professionManager != null ? professionManager : new ProfessionManager();
+ }
+
+ public static PlayerManager getPlayerManager() {
+ return playerManager != null ? playerManager : new PlayerManager();
+ }
+
+ public static EventServices getEventServices() {
+ return eventServices != null ? eventServices : new EventServices();
+ }
}
From 10486299db5fb4cc68abb65dfff3d0d8cd87064b Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Thu, 26 Jun 2025 22:08:11 +0200
Subject: [PATCH 05/16] improved performance in guis through inv and recipe
hashing
---
.../java/studio/magemonkey/fusion/Fusion.java | 2 +
.../cfg/migrations/ProfessionMigration.java | 1 +
.../fusion/commands/CommandMechanics.java | 7 +-
.../fusion/data/player/FusionPlayer.java | 17 +-
.../professions/pattern/InventoryPattern.java | 5 +-
.../fusion/data/recipes/CalculatedRecipe.java | 356 ++++++-------
.../fusion/gui/ProfessionGuiRegistry.java | 9 +-
.../magemonkey/fusion/gui/RecipeGui.java | 477 +++++++++++-------
.../gui/recipe/IngredientFingerprint.java | 98 ++++
.../gui/recipe/InventoryFingerprint.java | 84 +++
.../fusion/gui/recipe/RecipeCacheKey.java | 38 ++
.../gui/recipe/RecipeGuiEventRouter.java | 115 +++++
12 files changed, 825 insertions(+), 384 deletions(-)
create mode 100644 src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java
create mode 100644 src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
create mode 100644 src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
create mode 100644 src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
diff --git a/src/main/java/studio/magemonkey/fusion/Fusion.java b/src/main/java/studio/magemonkey/fusion/Fusion.java
index 12a47b5..6572bad 100644
--- a/src/main/java/studio/magemonkey/fusion/Fusion.java
+++ b/src/main/java/studio/magemonkey/fusion/Fusion.java
@@ -28,6 +28,7 @@
import studio.magemonkey.fusion.data.player.PlayerLoader;
import studio.magemonkey.fusion.data.recipes.*;
import studio.magemonkey.fusion.gui.BrowseGUI;
+import studio.magemonkey.fusion.gui.recipe.RecipeGuiEventRouter;
import studio.magemonkey.fusion.util.ExperienceManager;
import studio.magemonkey.fusion.util.LevelFunction;
@@ -127,6 +128,7 @@ public void onEnable() {
this.getCommand("craft").setExecutor(new Commands());
this.getCommand("fusion-editor").setExecutor(new FusionEditorCommand());
getServer().getPluginManager().registerEvents(this, this);
+ Bukkit.getPluginManager().registerEvents(new RecipeGuiEventRouter(), this);
runQueueTask();
if (hookManager.isHooked(HookType.PlaceholderAPI)) {
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..ac09c13 100644
--- a/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java
+++ b/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java
@@ -2,6 +2,7 @@
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.event.entity.CreatureSpawnEvent;
import org.jetbrains.annotations.Nullable;
import studio.magemonkey.codex.api.items.PrefixHelper;
import studio.magemonkey.fusion.Fusion;
diff --git a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java
index b7fc45f..9117541 100644
--- a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java
+++ b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java
@@ -73,9 +73,9 @@ public static void useProfession(CommandSender sender, String[] args) {
openGui(target, eq, category);
CodexEngine.get().getMessageUtil().sendMessage("fusion.useConfirmOther",
sender,
- new MessageData("craftingInventory", eq),
- new MessageData("sender", sender),
- new MessageData("target", target));
+ new MessageData("craftingInventory", eq.getProfession()),
+ new MessageData("sender", sender.getName()),
+ new MessageData("target", target.getName()));
} else {
if (sender instanceof Player player) {
if (!Utils.hasCraftingUsePermission(sender, eq.getProfession())) {
@@ -314,6 +314,7 @@ public static void reloadPlugin(CommandSender sender) {
Fusion.getInstance().closeAll();
Fusion.getInstance().reloadConfig();
Fusion.getInstance().reloadLang();
+ ProfessionGuiRegistry.getLatestRecipeGui().clear();
CodexEngine.get()
.getMessageUtil()
.sendMessage("fusion.reload", sender, new MessageData("sender", sender));
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 3352eb9..08d88e9 100644
--- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java
+++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java
@@ -29,8 +29,6 @@ public class FusionPlayer {
private Map cachedQueues = new TreeMap<>();
private Map cachedRecipeLimits = new TreeMap<>();
- private final Map cachedGuis = new TreeMap<>();
-
@Getter
@Setter
private boolean autoCrafting;
@@ -50,18 +48,10 @@ public Player getPlayer() {
}
public CraftingQueue getQueue(String profession, Category category) {
- if (!cachedQueues.containsKey(profession)) {
- cachedQueues.put(profession, new CraftingQueue(getPlayer(), profession, category));
- }
- return cachedQueues.get(profession);
- }
-
- public void cacheGui(String id, RecipeGui gui) {
- if (cachedGuis.containsKey(id)) {
- cachedGuis.get(id).open(getPlayer());
- return;
+ if (!cachedQueues.containsKey(profession + "." + category.getName())) {
+ cachedQueues.put(profession + "." + category.getName(), new CraftingQueue(getPlayer(), profession, category));
}
- cachedGuis.put(id, gui);
+ return cachedQueues.get(profession + "." + category.getName());
}
public PlayerRecipeLimit getRecipeLimit(Recipe recipe) {
@@ -361,7 +351,6 @@ public void save() {
SQLManager.queues().saveCraftingQueue(queue);
}
SQLManager.recipeLimits().saveRecipeLimits(uuid, cachedRecipeLimits);
- cachedGuis.clear();
cachedQueues.clear();
cachedRecipeLimits.clear();
}
diff --git a/src/main/java/studio/magemonkey/fusion/data/professions/pattern/InventoryPattern.java b/src/main/java/studio/magemonkey/fusion/data/professions/pattern/InventoryPattern.java
index d0406d0..9b537ad 100644
--- a/src/main/java/studio/magemonkey/fusion/data/professions/pattern/InventoryPattern.java
+++ b/src/main/java/studio/magemonkey/fusion/data/professions/pattern/InventoryPattern.java
@@ -11,6 +11,7 @@
import studio.magemonkey.codex.legacy.item.ItemBuilder;
import studio.magemonkey.codex.util.DeserializationWorker;
import studio.magemonkey.codex.util.SerializationBuilder;
+import studio.magemonkey.fusion.data.recipes.RecipeItem;
import java.util.AbstractMap.SimpleEntry;
import java.util.*;
@@ -43,14 +44,14 @@ public InventoryPattern(Map map) {
continue;
Map section = itemsTemp.getSection(entry);
- this.items.put(entry.charAt(0), new ItemBuilder(section).build());
+ this.items.put(entry.charAt(0), RecipeItem.fromConfig(section).getItemStack());
if (section.containsKey("closeonclick") && (boolean) section.get("closeonclick")) {
closeOnClickSlots.add(entry.charAt(0));
}
}
if (dw.getSection("items.queue-items.-") != null)
- this.items.put('-', new ItemBuilder(dw.getSection("items.queue-items.-")).build());
+ this.items.put('-', RecipeItem.fromConfig(dw.getSection("items.queue-items.-")).getItemStack());
final DeserializationWorker commandsTemp =
DeserializationWorker.start(dw.getSection("commands", new HashMap<>(2)));
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 720a364..88d7f8b 100644
--- a/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java
+++ b/src/main/java/studio/magemonkey/fusion/data/recipes/CalculatedRecipe.java
@@ -4,14 +4,10 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
-import org.apache.commons.lang3.tuple.Pair;
import org.bukkit.Bukkit;
-import org.bukkit.Material;
-import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta;
import studio.magemonkey.codex.CodexEngine;
@@ -19,123 +15,159 @@
import studio.magemonkey.fusion.cfg.CraftingRequirementsCfg;
import studio.magemonkey.fusion.data.player.PlayerLoader;
import studio.magemonkey.fusion.data.player.PlayerRecipeLimit;
+import studio.magemonkey.fusion.gui.recipe.IngredientFingerprint;
import studio.magemonkey.fusion.util.ExperienceManager;
import studio.magemonkey.fusion.util.InvalidPatternItemException;
import studio.magemonkey.fusion.util.Utils;
import java.util.*;
+/**
+ * A “calculated” recipe icon + canCraft flag. We build lore lines (ingredients, money, xp, etc.)
+ * once per relevant change, and store the final Icon + canCraft in a single object.
+ */
@Getter
public class CalculatedRecipe {
private final Recipe recipe;
-
private final ItemStack icon;
- private final boolean canCraft;
+ private final boolean canCraft;
- CalculatedRecipe(Recipe recipe, ItemStack icon, boolean canCraft) {
+ public CalculatedRecipe(Recipe recipe, ItemStack icon, boolean canCraft) {
this.recipe = recipe;
this.icon = icon;
this.canCraft = canCraft;
}
+ /**
+ * Builds a CalculatedRecipe for the given Recipe and a snapshot of the player’s inventory counts.
+ *
+ * @param recipe The Recipe to evaluate
+ * @param invCounts A Map of everything in the player’s inventory
+ * @param player The player who is crafting
+ * @param craftingTable The CraftingTable (for level checks)
+ * @return a new CalculatedRecipe containing:
+ * - recipe (underlying Recipe)
+ * - icon (cloned ItemStack with assembled lore)
+ * - canCraft (true if player meets all requirements)
+ * @throws InvalidPatternItemException if building fails
+ */
public static CalculatedRecipe create(Recipe recipe,
- Collection items,
+ Map invCounts,
Player player,
CraftingTable craftingTable) throws InvalidPatternItemException {
try {
StringBuilder lore = new StringBuilder(512);
- // TODO Make sure this icon is always applied. Also on Divinity Item Meta existent
- ItemStack iconResult = recipe.getSettings().getRecipeItem().getItemStack();
- List resultLore = iconResult.getItemMeta().getLore();
-
- /*
- TODO This part is natively provided through the settings section soon
- if (!recipe.getSettings().isEnableLore()) {
- if ((resultLore != null) && !resultLore.isEmpty()) {
- resultLore.forEach((str) -> lore.append(str).append('\n'));
- lore.append('\n');
- }
- } else if (recipe.getSettings().getLore() != null && !recipe.getSettings().getLore().isEmpty()) {
- recipe.getSettings().getLore().forEach((str) -> lore.append(ChatUT.hexString(str)).append('\n'));
- lore.append('\n');
- }*/
+ // Base icon (without lore)
+ ItemStack iconResult = recipe.getSettings().getRecipeItem().getItemStack();
+ ItemMeta baseMeta = iconResult.getItemMeta();
+ List resultLore = (baseMeta == null) ? Collections.emptyList() : baseMeta.getLore();
+
+ // (Optional custom lore logic omitted)
+
+ // 1) “Requirement” header
String requirementLine = CraftingRequirementsCfg.getCraftingRequirementLine("recipes");
- if (!requirementLine.isEmpty())
+ if (!requirementLine.isEmpty()) {
lore.append(requirementLine).append('\n');
+ }
boolean canCraft = true;
-
- String recipePermissionLine;
+ // 2) Permission (learned) check
+ String recipePermissionLine = null;
if (!Utils.hasCraftingPermission(player, recipe.getName())) {
canCraft = false;
}
- recipePermissionLine = CraftingRequirementsCfg.getLearned("recipes",
- Utils.hasCraftingPermission(player, recipe.getName()));
+ recipePermissionLine = CraftingRequirementsCfg.getLearned(
+ "recipes",
+ Utils.hasCraftingPermission(player, recipe.getName())
+ );
+ // 3) Money cost
String moneyLine = null;
if (recipe.getConditions().getMoneyCost() != 0) {
- if (CodexEngine.get().getVault() == null || !CodexEngine.get()
- .getVault()
- .canPay(player, recipe.getConditions().getMoneyCost())) {
+ if (CodexEngine.get().getVault() == null ||
+ !CodexEngine.get().getVault().canPay(player, recipe.getConditions().getMoneyCost())) {
canCraft = false;
}
- moneyLine = CraftingRequirementsCfg.getMoney("recipes",
- CodexEngine.get().getVault() == null ? 0
- : CodexEngine.get().getVault().getBalance(player),
- recipe.getConditions().getMoneyCost());
+ double balance = (CodexEngine.get().getVault() == null)
+ ? 0.0
+ : CodexEngine.get().getVault().getBalance(player);
+ moneyLine = CraftingRequirementsCfg.getMoney(
+ "recipes",
+ (int) balance,
+ recipe.getConditions().getMoneyCost()
+ );
}
+ // 4) XP cost
String expLine = null;
if (recipe.getConditions().getExpCost() != 0) {
- if (ExperienceManager.getTotalExperience(player) < recipe.getConditions().getExpCost()) {
+ int totalExp = ExperienceManager.getTotalExperience(player);
+ if (totalExp < recipe.getConditions().getExpCost()) {
canCraft = false;
}
- expLine = CraftingRequirementsCfg.getExp("recipes",
- ExperienceManager.getTotalExperience(player),
- recipe.getConditions().getExpCost());
+ expLine = CraftingRequirementsCfg.getExp(
+ "recipes",
+ totalExp,
+ recipe.getConditions().getExpCost()
+ );
}
+ // 5) Profession level
String levelsLine = null;
if (recipe.getConditions().getProfessionLevel() != 0) {
- if (recipe.getTable().getLevelFunction().getLevel(player) < recipe.getConditions()
- .getProfessionLevel()) {
+ int profLevel = recipe.getTable().getLevelFunction().getLevel(player);
+ if (profLevel < recipe.getConditions().getProfessionLevel()) {
canCraft = false;
}
- levelsLine = CraftingRequirementsCfg.getProfessionLevel("recipes",
- recipe.getTable().getLevelFunction().getLevel(player),
- recipe.getConditions().getProfessionLevel());
+ levelsLine = CraftingRequirementsCfg.getProfessionLevel(
+ "recipes",
+ profLevel,
+ recipe.getConditions().getProfessionLevel()
+ );
}
+ // 6) Mastery
String masteryLine = null;
if (recipe.getConditions().isMastery()) {
- if (!PlayerLoader.getPlayer(player).hasMastered(craftingTable.getName())) {
+ boolean hasMastery = PlayerLoader.getPlayer(player)
+ .hasMastered(craftingTable.getName());
+ if (!hasMastery) {
canCraft = false;
}
- masteryLine = CraftingRequirementsCfg.getMastery("recipes",
- PlayerLoader.getPlayer(player).hasMastered(craftingTable.getName()),
- recipe.getConditions().isMastery());
+ masteryLine = CraftingRequirementsCfg.getMastery(
+ "recipes",
+ hasMastery,
+ recipe.getConditions().isMastery()
+ );
}
+ // 7) Crafting limit
String limitLine = null;
if (recipe.getCraftingLimit() > 0) {
PlayerRecipeLimit limit = PlayerLoader.getPlayer(player).getRecipeLimit(recipe);
- if (limit.getLimit() > 0) {
- if (limit.getCooldownTimestamp() > 0 && !limit.hasCooldown()) {
- limit.resetLimit();
- Bukkit.getConsoleSender()
- .sendMessage(
- "§aResetting limit for " + player.getName() + " on " + recipe.getRecipePath());
- }
+ if (limit.getLimit() > 0 &&
+ limit.getCooldownTimestamp() > 0 &&
+ !limit.hasCooldown()) {
+ limit.resetLimit();
+ Bukkit.getConsoleSender().sendMessage(
+ "§aResetting limit for " + player.getName() + " on " + recipe.getRecipePath()
+ );
}
if (limit.getLimit() >= recipe.getCraftingLimit()) {
canCraft = false;
}
- limitLine = CraftingRequirementsCfg.getLimit("recipes", limit.getLimit(), recipe.getCraftingLimit());
+ limitLine = CraftingRequirementsCfg.getLimit(
+ "recipes",
+ limit.getLimit(),
+ recipe.getCraftingLimit()
+ );
}
- List> conditionLines = recipe.getConditions().getConditionLines(player);
+ // 8) Custom condition lines
+ List> conditionLines =
+ recipe.getConditions().getConditionLines(player);
for (Map.Entry entry : conditionLines) {
if (!entry.getKey()) {
canCraft = false;
@@ -143,115 +175,94 @@ public static CalculatedRecipe create(Recipe recipe,
}
}
- List> eqItems = Recipe.getItems(items);
-
-
- Collection localPattern = new LinkedHashSet<>(recipe.getConditions().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;
- for (Pair entry : eqItems) {
- ItemStack item = entry.getKey().clone();
- if (CalculatedRecipe.isSimilar(recipeItemStackOne, item)) {
- eqEntry = entry;
- break;
- }
- }
+ //
+ // ─── 9) Ingredient check using invCounts ───
+ //
+ Collection localPattern = new LinkedHashSet<>(
+ recipe.getConditions().getRequiredItems()
+ );
+
+ for (Iterator it = localPattern.iterator(); it.hasNext();) {
+ RecipeItem required = it.next();
+
+ // We only compare a single “unit” for matching:
+ ItemStack single = required.getItemStack().clone();
+ single.setAmount(1);
- int eqAmount = eqEntry != null ? eqEntry.getValue() : 0;
- int patternAmount = recipeItem.getAmount();
- if (eqAmount < patternAmount) {
+ IngredientFingerprint reqKey = IngredientFingerprint.of(single);
+ int have = invCounts.getOrDefault(reqKey, 0);
+ int need = required.getAmount();
+
+ if (have < need) {
canCraft = false;
- lore.append(CraftingRequirementsCfg.getIngredientLine("recipes",
- recipeItem,
- eqAmount,
- patternAmount)).append('\n');
+ lore.append(
+ CraftingRequirementsCfg.getIngredientLine("recipes", required, have, need)
+ ).append('\n');
continue;
}
- if (eqAmount == patternAmount) {
- eqItems.remove(eqEntry);
- }
- int rest = eqAmount - patternAmount;
- if (rest > 0 && eqEntry != null) {
- eqItems.add(Pair.of(eqEntry.getKey(), rest));
- }
- it.remove();
- lore.append(CraftingRequirementsCfg.getIngredientLine("recipes", recipeItem, eqAmount, patternAmount))
- .append('\n');
- }
- String canCraftLine = CraftingRequirementsCfg.getCanCraft(canCraft);
+ // Subtract used quantity so overlapping items are handled correctly
+ invCounts.put(reqKey, have - need);
- lore.append('\n');
- if (moneyLine != null) {
- lore.append(moneyLine).append('\n');
- }
- if (levelsLine != null) {
- lore.append(levelsLine).append('\n');
- }
- if (expLine != null) {
- lore.append(expLine).append('\n');
- }
- if (masteryLine != null) {
- lore.append(masteryLine).append('\n');
- }
- if (limitLine != null) {
- lore.append(limitLine).append('\n');
+ lore.append(
+ CraftingRequirementsCfg.getIngredientLine("recipes", required, have, need)
+ ).append('\n');
}
+ // 10) Append summary lines
+ String canCraftLine = CraftingRequirementsCfg.getCanCraft(canCraft);
+ lore.append('\n');
+ if (moneyLine != null) lore.append(moneyLine).append('\n');
+ if (levelsLine != null) lore.append(levelsLine).append('\n');
+ if (expLine != null) lore.append(expLine).append('\n');
+ if (masteryLine != null) lore.append(masteryLine).append('\n');
+ if (limitLine != null) lore.append(limitLine).append('\n');
if (!conditionLines.isEmpty()) {
- for (Map.Entry entry : conditionLines) {
- lore.append('\n').append(entry.getValue());
+ for (Map.Entry e : conditionLines) {
+ lore.append('\n').append(e.getValue());
}
}
-
- if (recipePermissionLine != null) {
- lore.append('\n').append(recipePermissionLine);
- }
-
+ lore.append('\n').append(recipePermissionLine);
lore.append('\n').append(canCraftLine);
- ItemStack icon = iconResult.clone();
- ItemMeta itemMeta = icon.getItemMeta();
- itemMeta.setLore(Arrays.asList(StringUtils.split(lore.toString(), '\n')));
- icon.setItemMeta(itemMeta);
+ // Build final icon + lore
+ ItemStack icon = iconResult.clone();
+ ItemMeta im = icon.getItemMeta();
+ im.setLore(Arrays.asList(StringUtils.split(lore.toString(), '\n')));
+ icon.setItemMeta(im);
return new CalculatedRecipe(recipe, icon, canCraft);
} catch (Exception e) {
- Fusion.getInstance()
- .error("The recipe-item seems not to be recognized. Please check your setup on the following recipe '"
- + recipe.getName());
- Fusion.getInstance().error("Result: " + recipe.getSettings().getIconNamespace());
- Fusion.getInstance().error("Pattern Items: ");
- for (Object patternItem : recipe.getConditions().getRequiredItemNames()) {
- Fusion.getInstance().error("- " + patternItem);
- }
- Fusion.getInstance().error("Error on creating CalculatedRecipe: " + e.getMessage());
- e.printStackTrace();
+ Fusion.getInstance().error(
+ "Error creating CalculatedRecipe for '" + recipe.getName() + "': " + e.getMessage()
+ );
throw new InvalidPatternItemException(e);
}
}
@Override
public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
-
- if (!(o instanceof CalculatedRecipe that)) {
- return false;
- }
+ if (this == o) return true;
+ if (!(o instanceof CalculatedRecipe that)) return false;
+ return new EqualsBuilder()
+ .append(this.recipe, that.recipe)
+ .append(this.icon, that.icon)
+ .isEquals();
+ }
- return new EqualsBuilder().append(this.recipe, that.recipe).append(this.icon, that.icon).isEquals();
+ @Override
+ public int hashCode() {
+ return new HashCodeBuilder(17, 37)
+ .append(this.recipe)
+ .append(this.icon)
+ .toHashCode();
}
+ /**
+ * Unchanged “isSimilar” from before—compares two ItemStacks in a relaxed manner.
+ */
public static boolean isSimilar(ItemStack is1, ItemStack is2) {
- //More relaxed comparison
- if (is1.getType() != is2.getType())
- return false;
+ if (is1.getType() != is2.getType()) return false;
ItemMeta im1 = is1.getItemMeta();
ItemMeta im2 = is2.getItemMeta();
@@ -262,8 +273,7 @@ public static boolean isSimilar(ItemStack is1, ItemStack is2) {
if (im1.hasDisplayName()) {
String displayName1 = im1.getDisplayName().trim();
String displayName2 = im2.hasDisplayName() ? im2.getDisplayName().trim() : "";
- if (!displayName1.equals(displayName2))
- return false;
+ if (!displayName1.equals(displayName2)) return false;
} else if (!im1.hasDisplayName() && im2.hasDisplayName()) {
return false;
}
@@ -291,65 +301,55 @@ public static boolean isSimilar(ItemStack is1, ItemStack is2) {
// Check for enchantments
if (im1 instanceof EnchantmentStorageMeta storage1) {
- EnchantmentStorageMeta storage2 = (EnchantmentStorageMeta) im2;
- Map ench1 = storage1.getStoredEnchants();
- Map ench2 = storage2.getStoredEnchants();
-
- if (ench1.size() != ench2.size())
- isValid = false;
- for (Map.Entry entry : ench1.entrySet()) {
- if (!ench2.containsKey(entry.getKey()) || !ench2.get(entry.getKey()).equals(entry.getValue()))
+ EnchantmentStorageMeta storage2 = (EnchantmentStorageMeta) im2;
+ Map ench1 = storage1.getStoredEnchants();
+ Map ench2 = storage2.getStoredEnchants();
+
+ if (ench1.size() != ench2.size()) isValid = false;
+ for (Map.Entry entry : ench1.entrySet()) {
+ if (!ench2.containsKey(entry.getKey()) ||
+ !ench2.get(entry.getKey()).equals(entry.getValue())) {
isValid = false;
+ }
}
} else {
if (im1.hasEnchants()) {
- Map ench1 = im1.getEnchants();
- Map ench2 = im2.getEnchants();
- if (ench1.size() != ench2.size())
- isValid = false;
- for (Map.Entry entry : ench1.entrySet()) {
- if (!ench2.containsKey(entry.getKey()) || !ench2.get(entry.getKey()).equals(entry.getValue()))
+ Map ench1 = im1.getEnchants();
+ Map ench2 = im2.getEnchants();
+ if (ench1.size() != ench2.size()) isValid = false;
+ for (Map.Entry entry : ench1.entrySet()) {
+ if (!ench2.containsKey(entry.getKey()) ||
+ !ench2.get(entry.getKey()).equals(entry.getValue())) {
isValid = false;
+ }
}
}
}
+
// Check for flags
if (!im1.getItemFlags().isEmpty()) {
- if (im1.getItemFlags().size() != im2.getItemFlags().size())
- isValid = false;
+ if (im1.getItemFlags().size() != im2.getItemFlags().size()) isValid = false;
for (ItemFlag flag : im1.getItemFlags()) {
- if (!im2.getItemFlags().contains(flag))
- isValid = false;
+ if (!im2.getItemFlags().contains(flag)) isValid = false;
}
}
// Check for custom model data
if (im1.hasCustomModelData() && im2.hasCustomModelData()) {
- if (im1.getCustomModelData() != im2.getCustomModelData())
- isValid = false;
+ if (im1.getCustomModelData() != im2.getCustomModelData()) isValid = false;
} else if (im1.hasCustomModelData() || im2.hasCustomModelData()) {
isValid = false;
- }
+ }
+
// Check if unbreakable
if (im1.isUnbreakable()) {
- if (im2.isUnbreakable())
- isValid = false;
+ if (im2.isUnbreakable()) isValid = false;
}
+
// Check for durability if instanceof Damageable
- if (im1 instanceof Damageable dmg && dmg.getDamage() > 0) {
- int damage1 = dmg.getDamage();
- int damage2 = im2 instanceof Damageable dmg2 ? dmg2.getDamage() : 0;
- if (damage1 != damage2)
- isValid = false;
- }
+ // TODO
- // If all those checks failed, try to check once more through the native item meta check
- // This is useful for custom items like from Divinity, etc.
+ // Final fallback to Bukkit’s built‐in check
return isValid || is1.isSimilar(is2);
}
-
- @Override
- public int hashCode() {
- return new HashCodeBuilder(17, 37).append(this.recipe).append(this.icon).toHashCode();
- }
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java b/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
index 311cfea..7b9937e 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
@@ -1,6 +1,7 @@
package studio.magemonkey.fusion.gui;
import lombok.Getter;
+import lombok.Setter;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import studio.magemonkey.codex.CodexEngine;
@@ -19,6 +20,9 @@ public class ProfessionGuiRegistry {
private final Map categoryGuis = new TreeMap<>();
private final Map recipeGuis = new TreeMap<>();
+ @Getter
+ public static final Map latestRecipeGui = new TreeMap<>();
+
public ProfessionGuiRegistry(String profession) {
this.profession = profession;
}
@@ -29,8 +33,9 @@ public void open(Player player) {
categoryGuis.put(player.getUniqueId(), new CategoryGui(player, table));
categoryGuis.get(player.getUniqueId()).open(player);
} else {
- recipeGuis.put(player.getUniqueId(),
- new RecipeGui(player, table, new Category("master", "PAPER", table.getRecipePattern(), 1)));
+ RecipeGui gui = new RecipeGui(player, table, new Category("master", "PAPER", table.getRecipePattern(), 1));
+
+ recipeGuis.put(player.getUniqueId(), gui);
recipeGuis.get(player.getUniqueId()).open(player);
}
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
index 12f1997..3082c6e 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
@@ -12,13 +12,10 @@
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
-import org.bukkit.event.entity.EntityPickupItemEvent;
import org.bukkit.event.inventory.*;
+import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerDropItemEvent;
-import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.PlayerInventory;
@@ -47,9 +44,12 @@
import studio.magemonkey.fusion.gui.slot.Slot;
import studio.magemonkey.fusion.util.ChatUT;
import studio.magemonkey.fusion.util.ExperienceManager;
-import studio.magemonkey.fusion.util.InvalidPatternItemException;
import studio.magemonkey.fusion.util.PlayerUtil;
+import studio.magemonkey.fusion.gui.recipe.IngredientFingerprint;
+import studio.magemonkey.fusion.gui.recipe.InventoryFingerprint;
+import studio.magemonkey.fusion.gui.recipe.RecipeCacheKey;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
@Getter
@@ -75,6 +75,8 @@ public class RecipeGui implements Listener {
private int prevQueuePage;
private int nextQueuePage;
private CraftingQueue queue;
+ private int lastQueueSecond = -1; // <<< track last‐seen wall‐clock second
+ private int lastQueueSize = 0; // <<< track last‐seen queue size
/* Manual Crafting Mode */
private BukkitTask craftingTask;
@@ -95,6 +97,17 @@ public class RecipeGui implements Listener {
private final ArrayList blockedSlots = new ArrayList<>(20);
private final ArrayList queuedSlots = new ArrayList<>(20);
+ // Caches all previously built CalculatedRecipe objects:
+ private static final Map recipeCache = new ConcurrentHashMap<>();
+
+ // Last‐seen “inventory fingerprint” so we know if we truly need to recalc:
+ private byte[] lastInventoryHash = new byte[0];
+ private int lastSeenLevel = -1;
+ private double lastSeenMoney = -1.0;
+
+ // Last page counts so we skip redraw unless page or queuePage also changed:
+ private int lastPageCount = -1, lastQueuePageCount = -1;
+
public RecipeGui(Player player, CraftingTable table, Category category) {
this.player = player;
this.table = table;
@@ -235,125 +248,217 @@ public void initialize() {
public void reloadRecipes() {
if (!player.isOnline()) return;
+
try {
- setPattern();
-
- /* Default setup */
- ItemStack fill = table.getFillItem();
- Collection allRecipes = new ArrayList<>(category.getRecipes());
- allRecipes.removeIf(r -> r.isHidden(player));
- int pageSize = resultSlots.size();
- int allRecipeCount = allRecipes.size();
- int i = 0;
- int page = this.page;
-
- int fullPages = allRecipeCount / pageSize;
- int rest = allRecipeCount % pageSize;
+ //
+ // ─── 1) Compute new “fingerprint” of the player’s current inventory + level + money ───
+ //
+ byte[] newHash = InventoryFingerprint.fingerprint(player);
+ int newLevel = table.getLevelFunction().getLevel(player);
+ double newMoney = (CodexEngine.get().getVault() == null)
+ ? 0.0
+ : CodexEngine.get().getVault().getBalance(player);
+
+ boolean invChanged = !Arrays.equals(newHash, lastInventoryHash);
+ boolean levelChanged = (newLevel != lastSeenLevel);
+ boolean moneyChanged = (newMoney != lastSeenMoney);
+
+ lastInventoryHash = newHash;
+ lastSeenLevel = newLevel;
+ lastSeenMoney = newMoney;
+
+ //
+ // ─── 2) Re-obtain the recipe list & calculate total pages ───
+ //
+ setPattern(); // (exactly as before)
+ ItemStack fill = table.getFillItem();
+
+ Collection allRecipesCollection = new ArrayList<>(category.getRecipes());
+ allRecipesCollection.removeIf(r -> r.isHidden(player));
+
+ int pageSize = resultSlots.size();
+ int totalItems = allRecipesCollection.size();
+ int page = this.page;
+
+ int fullPages = (pageSize == 0) ? 0 : totalItems / pageSize;
+ int rest = (pageSize == 0) ? 0 : totalItems % pageSize;
int pages = (rest == 0) ? fullPages : (fullPages + 1);
- if (player.isOnline() && page >= pages) {
- if (page > 0) {
- this.page = pages - 1;
- }
- // Add a check to prevent infinite recursion
- if (this.page != page) { // Only reload if page has changed
+ if (page >= pages && pages > 0) {
+ // Clamp page if out-of-range
+ this.page = pages - 1;
+ if (this.page != page) {
+ // Avoid infinite recursion
this.reloadRecipes();
}
return;
}
- Collection playerItems = PlayerUtil.getPlayerItems(this.player);
- CalculatedRecipe[] calculatedRecipes =
- new CalculatedRecipe[(page < pages) ? pageSize : ((rest == 0) ? pageSize : rest)];
- Recipe[] allRecipesArray = allRecipes.toArray(new Recipe[allRecipeCount]);
-
- Integer[] slots = resultSlots.toArray(new Integer[0]);
- for (Integer slot : slots) {
- if (slot != null) this.inventory.setItem(slot, null);
+ //
+ // ─── 3) Compute total queue pages (if craftingQueue is enabled) ───
+ //
+ List allQueuedItems = (Cfg.craftingQueue && queue != null)
+ ? new ArrayList<>(queue.getQueue())
+ : Collections.emptyList();
+ int queueSize = allQueuedItems.size();
+ int queuePageSize = queuedSlots.size();
+ int fullQueuePages = (queuePageSize == 0) ? 0 : queueSize / queuePageSize;
+ int restQueue = (queuePageSize == 0) ? 0 : queueSize % queuePageSize;
+ int queuePages = (restQueue == 0) ? fullQueuePages : (fullQueuePages + 1);
+
+ if (queuePage >= queuePages && queuePages > 0) {
+ this.queuePage = queuePages - 1;
+ this.reloadRecipes();
+ return;
}
- /* Additionally, when crafting_queue: true */
- if (Cfg.craftingQueue) {
+ //
+ // ─── 4) Bail out early if nothing changed _and_ no unfinished queued items ───
+ //
+ boolean hasUnfinishedQueue = (Cfg.craftingQueue && queue != null && !queue.getQueuedItems().isEmpty());
+ boolean queueSizeChanged = (queueSize != lastQueueSize); // <<< check if queue length changed
+ lastQueueSize = queueSize; // <<< update lastQueueSize
+
+ if (!invChanged && !levelChanged && !moneyChanged
+ && lastPageCount == page
+ && lastQueuePageCount == queuePage
+ && !hasUnfinishedQueue
+ && !queueSizeChanged) // <<< also require queue size unchanged
+ {
+ return;
+ }
+ lastPageCount = page;
+ lastQueuePageCount = queuePage;
+
+ //
+ // ─── 5) Build a single Map of the player’s entire inventory ───
+ //
+ Map invCounts = new HashMap<>();
+ for (ItemStack is : player.getInventory().getContents()) {
+ if (is == null || is.getType() == Material.AIR) continue;
+ IngredientFingerprint fp = IngredientFingerprint.of(is);
+ invCounts.merge(fp, is.getAmount(), Integer::sum);
+ }
- // Clear all queue slots
- Integer[] _queuedSlots = queuedSlots.toArray(new Integer[0]);
- for (int slot : _queuedSlots) {
- this.inventory.setItem(slot, ProfessionsCfg.getQueueSlot(table.getName()));
+ //
+ // ─── 6) Clear out any “result” slots from the previous page ───
+ //
+ Integer[] resultSlotArray = resultSlots.toArray(new Integer[0]);
+ for (Integer slotIndex : resultSlotArray) {
+ if (slotIndex != null) {
+ inventory.setItem(slotIndex, null);
+ }
+ }
+ recipes.clear();
+
+ //
+ // ─── 7) Re-populate this page’s recipe icons, using a cache key to avoid repeated recalculation ───
+ //
+ Recipe[] allRecipesArray = allRecipesCollection.toArray(new Recipe[0]);
+ int startIndex = page * pageSize;
+ int endIndex = Math.min(startIndex + pageSize, totalItems);
+
+ for (int i = startIndex, idx = 0; i < endIndex; i++, idx++) {
+ Recipe recipe = allRecipesArray[i];
+ int slotIndex = resultSlotArray[idx];
+ RecipeCacheKey cacheKey = new RecipeCacheKey(
+ recipe.getRecipePath(),
+ newHash,
+ newLevel,
+ newMoney
+ );
+
+ CalculatedRecipe calc;
+ if (recipeCache.containsKey(cacheKey)) {
+ calc = recipeCache.get(cacheKey);
+ } else {
+ CalculatedRecipe fresh = CalculatedRecipe.create(
+ recipe,
+ new HashMap<>(invCounts),
+ player,
+ table
+ );
+ recipeCache.put(cacheKey, fresh);
+ calc = fresh;
}
- this.queue.getQueuedItems().clear();
- Collection allQueuedItems = queue.getQueue();
- int queueAllItemsCount = allQueuedItems.size();
- if (!allQueuedItems.isEmpty()) {
- int queuePageSize = queuedSlots.size();
- if (queuePageSize > 0) {
- int j = 0;
- int queuePage = this.queuePage;
-
- int queueFullPages = queueAllItemsCount / queuePageSize;
- int queueRest = queueAllItemsCount % queuePageSize;
- int queuePages = (queueRest == 0) ? queueFullPages : (queueFullPages + 1);
- if (queuePage >= queuePages) {
- if (queuePage > 0)
- this.queuePage = queuePages - 1;
- this.reloadRecipes();
- return;
- }
-
- QueueItem[] queuedItems = new QueueItem[queuePageSize];
- QueueItem[] allQueueItemsArray = allQueuedItems.toArray(new QueueItem[queueAllItemsCount]);
- Integer[] queuedSlots = this.queuedSlots.toArray(new Integer[0]);
+ recipes.put(slotIndex, calc);
+ inventory.setItem(slotIndex, calc.getIcon().clone());
+ }
- for (int k = (queuePage * queuePageSize), e = queuedSlots.length;
- (k < allQueueItemsArray.length) && (j < e);
- k++, j++) {
- QueueItem queueItem = allQueueItemsArray[k];
- int slot = queuedSlots[j];
- this.queue.getQueuedItems().put(slot, queuedItems[j] = queueItem);
- this.queue.getQueuedItems().get(slot).updateIcon();
+ //
+ // ─── 8) Fill anything not set yet with the “fill” background ───
+ //
+ for (int k = 0; k < inventory.getSize(); k++) {
+ ItemStack it = inventory.getItem(k);
+ if (it == null || it.getType() == Material.AIR) {
+ inventory.setItem(k, fill.clone());
+ }
+ }
- this.inventory.setItem(slot, queuedItems[j].getIcon().clone());
+ //
+ // ─── 9) If crafting-queue mode is enabled, clear + rebuild queued slots
+ // ─ only when the wall-clock second or queueSize changed ─
+ //
+ if (Cfg.craftingQueue && queue != null) {
+ int nowSec = (int) (System.currentTimeMillis() / 1000L);
+ if (nowSec != lastQueueSecond || queueSizeChanged) { // <<< MODIFIED
+ lastQueueSecond = nowSec; // <<< MODIFIED
+
+ // 9a) Clear all queue slots to the “empty queue” icon
+ Integer[] queuedIndices = queuedSlots.toArray(new Integer[0]);
+ for (int qIndex : queuedIndices) {
+ inventory.setItem(qIndex, ProfessionsCfg.getQueueSlot(table.getName()));
+ }
+ this.queue.getQueuedItems().clear();
+
+ // 9b) Place each queued item onto its slot for the current queuePage
+ 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]);
+ Integer[] qSlots = queuedIndices;
+
+ for (int q = qStart; q < qEnd && j < qSlots.length; q++, j++) {
+ QueueItem qi = allQueueItemsArray[q];
+ int slot = qSlots[j];
+ this.queue.getQueuedItems().put(slot, qi);
+ qi.updateIcon();
+ inventory.setItem(slot, qi.getIcon().clone());
}
}
}
+ // (Otherwise, same second / queueSize, so skip rebuilding this block.)
}
+
+ //
+ // ─── 10) Finally, update “arrows” / “fill” / etc. exactly as before ───
+ //
updateBlockedSlots(new MessageData[]{
- new MessageData("level", table.getLevelFunction().getLevel(player)),
+ new MessageData("level", table.getLevelFunction().getLevel(player)),
new MessageData("category", category),
- new MessageData("gui", getName()),
- new MessageData("player", player.getName()),
+ new MessageData("gui", getName()),
+ new MessageData("player", player.getName()),
new MessageData("bal",
- CodexEngine.get().getVault() == null ? 0
+ CodexEngine.get().getVault() == null
+ ? 0
: CodexEngine.get().getVault().getBalance(player))
});
- for (int k = (page * pageSize), e = Math.min(slots.length, calculatedRecipes.length);
- (k < allRecipesArray.length) && (i < e);
- k++, i++) {
- Recipe recipe = allRecipesArray[k];
- int slot = slots[i];
- try {
- CalculatedRecipe calculatedRecipe = CalculatedRecipe.create(recipe, playerItems, this.player, table);
- this.recipes.put(slot, calculatedRecipes[i] = calculatedRecipe);
- this.inventory.setItem(slot, calculatedRecipe.getIcon().clone());
- } catch (InvalidPatternItemException ignored) {
- }
- }
-
- for (int k = 0; k < inventory.getSize(); k++) {
- if (inventory.getItem(k) != null && inventory.getItem(k).getType() != Material.AIR)
- continue;
- inventory.setItem(k, fill);
- }
this.isLoaded = true;
- } catch (
- Exception e) {
+ }
+ catch (Exception e) {
+ // On any exception, clear the inventory and close it to avoid partial states
this.inventory.clear();
Bukkit.getScheduler().runTask(Fusion.getInstance(), this.player::closeInventory);
- throw new RuntimeException("Exception was thrown when reloading recipes for: " + this.player.getName(),
- e);
- } finally {
- if (Cfg.craftingQueue && !queue.getQueuedItems().isEmpty()) {
+ throw new RuntimeException(
+ "Exception was thrown when reloading recipes for: " + this.player.getName(), e
+ );
+ }
+ finally {
+ // If queue-mode is on and there are unfinished items, re-schedule another reload in 1 second
+ if (Cfg.craftingQueue && queue != null && !queue.getQueuedItems().isEmpty()) {
boolean requiresUpdate = false;
for (Map.Entry entry : queue.getQueuedItems().entrySet()) {
if (!entry.getValue().isDone()) {
@@ -364,7 +469,7 @@ public void reloadRecipes() {
if (requiresUpdate) {
Bukkit.getScheduler().runTaskLater(Fusion.getInstance(), this::reloadRecipes, 20L);
}
- isLoaded = true;
+ this.isLoaded = true;
}
}
}
@@ -473,7 +578,8 @@ public Slot getSlot(int i) {
}
public void open(Player player) {
- if(!isLoaded)
+ ProfessionGuiRegistry.getLatestRecipeGui().put(player.getUniqueId(), this);
+ if (!isLoaded)
reloadRecipes();
player.openInventory(inventory);
}
@@ -523,7 +629,7 @@ private boolean craft(int slot, boolean addToCursor) {
return false;
}
CalculatedRecipe calculatedRecipe = this.recipes.get(slot);
- Recipe recipe = calculatedRecipe.getRecipe();
+ Recipe recipe = calculatedRecipe.getRecipe();
if (craftingRecipe != null && craftingRecipe.equals(recipe)) {
cancel(true);
return false;
@@ -535,16 +641,16 @@ private boolean craft(int slot, boolean addToCursor) {
RecipeItem recipeResult = recipe.getSettings().getRecipeItem();
ItemStack resultItem = recipeResult.getItemStack();
- //Add "Crafted by"
+ // Add "Crafted by" lore if the player has permission
if (player.hasPermission("fusion.craftedby." + recipe.getName())) {
ItemMeta meta = resultItem.getItemMeta();
-
List lore = (meta != null && meta.hasLore()) ? meta.getLore() : new ArrayList<>();
lore.add(ChatColor.WHITE + " - " + ChatColor.YELLOW + "Crafted by: " + ChatColor.WHITE + player.getName());
meta.setLore(lore);
resultItem.setItemMeta(meta);
}
+ // If adding directly to cursor, ensure enough room
if (addToCursor) {
ItemStack cursor = this.player.getItemOnCursor();
if (resultItem.isSimilar(cursor)) {
@@ -556,56 +662,88 @@ private boolean craft(int slot, boolean addToCursor) {
}
}
- Collection itemsToTake = recipe.getItemsToTake();
- Collection taken = new ArrayList<>(itemsToTake.size());
- PlayerInventory inventory = this.player.getInventory();
-
- for (Iterator iterator = itemsToTake.iterator(); iterator.hasNext(); ) {
- ItemStack toTake = iterator.next();
- for (ItemStack entry : PlayerUtil.getPlayerItems(player)) {
- ItemStack item = entry.clone();
- entry = entry.clone();
- item = item.clone();
- entry.setAmount(toTake.getAmount());
+ //
+ // ─── 1) Build a local copy of the ingredient list ───
+ //
+ List requiredItems = new ArrayList<>(recipe.getItemsToTake());
+ // Track exactly what we remove, so we can refund on failure
+ List removedSoFar = new ArrayList<>();
+
+ PlayerInventory inv = this.player.getInventory();
+ boolean missingSomething = false;
+
+ //
+ // ─── 2) For each required ingredient, manually drain across all matching slots ───
+ //
+ for (ItemStack required : requiredItems) {
+ int need = required.getAmount();
+ ItemStack neededTemplate = required.clone(); // same material+meta
+
+ // 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;
+ }
- if (CalculatedRecipe.isSimilar(toTake, item)) {
- toTake = entry;
- break;
+ 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);
+ } else {
+ inv.setItem(slotIndex, slotStack);
}
- }
- HashMap notRemoved = inventory.removeItem(toTake);
- if (notRemoved.isEmpty()) {
- taken.add(toTake);
- iterator.remove();
- continue;
+ // Track exactly what we removed
+ ItemStack actuallyTaken = neededTemplate.clone();
+ actuallyTaken.setAmount(take);
+ removedSoFar.add(actuallyTaken);
+
+ need -= take;
}
- for (ItemStack itemStack : taken) {
- HashMap notAdded = inventory.addItem(itemStack);
- if (notAdded.isEmpty()) {
- break;
- }
- for (ItemStack stack : notAdded.values()) {
- this.player.getWorld().dropItemNaturally(this.player.getLocation(), stack);
+
+ if (need > 0) {
+ // Could not find enough of “required” across all slots
+ missingSomething = true;
+
+ // ─── Roll back everything we already removed ───
+ for (ItemStack alreadyRemoved : removedSoFar) {
+ Map overflow = inv.addItem(alreadyRemoved.clone());
+ for (ItemStack drop : overflow.values()) {
+ this.player.getWorld().dropItemNaturally(this.player.getLocation(), drop);
+ }
}
+ break;
}
- break;
}
- refund.addAll(taken);
-
- if (!itemsToTake.isEmpty()) {
- CodexEngine.get()
- .getMessageUtil()
+ if (missingSomething) {
+ // At least one ingredient was short → inform player and abort
+ CodexEngine.get().getMessageUtil()
.sendMessage("fusion.error.insufficientItems", player, new MessageData("recipe", recipe));
cancel(true);
return false;
}
+
+ // All ingredients were successfully removed; add those to refund list
+ refund.addAll(removedSoFar);
+
+ //
+ // ─── 3) Proceed with cooldown / boss‐bar / giving the result ───
+ //
if (!Cfg.craftingQueue) {
double modifier = Fusion.getInstance().getPlayerCooldown(player);
int cooldown = modifier == 0d
? recipe.getCraftingTime()
- : (int) Math.round(recipe.getCraftingTime() - (recipe.getCraftingTime() * modifier));
+ : (int) Math.round(
+ recipe.getCraftingTime() - (recipe.getCraftingTime() * modifier)
+ );
+
showBossBar(this.player, recipe.getSettings().getRecipeItem().getItemStack(), cooldown);
if (cooldown != 0) {
@@ -617,6 +755,7 @@ private boolean craft(int slot, boolean addToCursor) {
craftingRecipe = recipe;
craftingTask = Fusion.getInstance().runTaskLater(cooldown, () -> {
craftingSuccess = true;
+
if (recipe.getResults().getCommands().isEmpty()) {
if (addToCursor) {
ItemStack cursor = this.player.getItemOnCursor();
@@ -637,7 +776,7 @@ private boolean craft(int slot, boolean addToCursor) {
} else {
boolean fits = calcWillFit(resultItem);
if (fits) {
- HashMap notAdded = inventory.addItem(resultItem);
+ HashMap notAdded = inv.addItem(resultItem);
if (!notAdded.isEmpty()) {
for (ItemStack stack : notAdded.values()) {
this.player.getWorld().dropItemNaturally(this.player.getLocation(), stack);
@@ -729,7 +868,7 @@ public void run() {
}.runTaskTimer(Fusion.getInstance(), 1L, 1L);
}
- private void cancel(boolean refund) {
+ private void cancel(boolean refundAll) {
if (!Cfg.craftingQueue) {
if (craftingTask == null) return;
craftingRecipe = null;
@@ -745,26 +884,27 @@ private void cancel(boolean refund) {
}
if (player.getOpenInventory().getCursor() != null
- && player.getOpenInventory().getCursor().getType() == Material.BARRIER)
+ && player.getOpenInventory().getCursor().getType() == Material.BARRIER) {
if (previousCursor != null) {
player.getOpenInventory().setCursor(previousCursor);
previousCursor = null;
- } else
+ } else {
player.getOpenInventory().setCursor(new ItemStack(Material.AIR));
+ }
+ }
if (craftingTask != null)
craftingTask.cancel();
craftingTask = null;
- if (!refund || craftingSuccess)
+ if (!refundAll || craftingSuccess)
return;
-
PlayerInventory inventory = player.getInventory();
Collection notAdded = inventory.addItem(this.refund.toArray(new ItemStack[0])).values();
if (!notAdded.isEmpty()) {
for (ItemStack item : notAdded) {
- player.getLocation().getWorld().dropItemNaturally(player.getLocation(), item);
+ player.getLocation().getWorld().dropItem(player.getLocation(), item);
}
}
this.refund.clear();
@@ -788,7 +928,7 @@ public void click(InventoryClickEvent event) {
Character c = pattern.getSlot(event.getRawSlot());
executeCommands(c, event.getWhoClicked());
- //Close on click
+ // Close on click
if (pattern.getCloseOnClickSlots().contains(c)) {
Bukkit.getScheduler().runTask(Fusion.getInstance(), () -> event.getWhoClicked().closeInventory());
}
@@ -818,8 +958,7 @@ public void click(InventoryClickEvent event) {
if (slots[event.getRawSlot()].equals(Slot.BASE_RESULT_SLOT)) {
event.setCancelled(true);
event.setResult(Event.Result.DENY);
- Fusion.getInstance().runSync(() ->
- {
+ Fusion.getInstance().runSync(() -> {
this.reloadRecipes();
this.craft(event.getRawSlot(), false);
this.reloadRecipesTask();
@@ -850,7 +989,6 @@ public void click(InventoryClickEvent event) {
return;
}
if (event.getCursor().getType() != Material.AIR) {
-
if (Slot.SPECIAL_CRAFTING_SLOT.canHoldItem(event.getCursor()) == null) {
event.setResult(Event.Result.DENY);
return;
@@ -859,15 +997,16 @@ public void click(InventoryClickEvent event) {
this.reloadRecipesTask();
}
- private void close(Player p, Inventory inv) {
+ public void close(Player p, Inventory inv) {
if (inv == null) {
return;
}
Inventory pInventory = p.getInventory();
if (inv.equals(this.inventory)) {
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)
- || this.slots[i].equals(Slot.QUEUED_SLOT)) {
+ if (this.slots[i].equals(Slot.BLOCKED_SLOT) ||
+ this.slots[i].equals(Slot.BASE_RESULT_SLOT) ||
+ this.slots[i].equals(Slot.QUEUED_SLOT)) {
continue;
}
ItemStack it = inv.getItem(i);
@@ -884,20 +1023,10 @@ private void close(Player p, Inventory inv) {
}
}
- @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
- public void onClick(InventoryClickEvent event) {
- if (event.getInventory() != getInventory()) return;
- if (event.getRawSlot() < 0) {
- return;
- }
- click(event);
- }
-
/*
Event to prevent the player from dragging items into the crafting slots
while doing manual crafting
*/
- @EventHandler(ignoreCancelled = true)
public void onDrag(InventoryDragEvent e) {
if (!(e.getWhoClicked() instanceof Player)) {
return;
@@ -907,13 +1036,14 @@ public void onDrag(InventoryDragEvent e) {
e.setCancelled(true);
if (e.getRawSlots()
.stream()
- .anyMatch(i -> (i < this.slots.length) && (!Objects.equals(this.slots[i],
- Slot.SPECIAL_CRAFTING_SLOT)))) {
+ .anyMatch(i -> (i < this.slots.length) &&
+ (!Objects.equals(this.slots[i], Slot.SPECIAL_CRAFTING_SLOT)))) {
e.setResult(Event.Result.DENY);
return;
}
- if (e.getNewItems().values().stream().anyMatch(i -> Slot.SPECIAL_CRAFTING_SLOT.canHoldItem(i) == null)) {
+ if (e.getNewItems().values().stream().anyMatch(i ->
+ Slot.SPECIAL_CRAFTING_SLOT.canHoldItem(i) == null)) {
e.setResult(Event.Result.DENY);
}
reloadRecipesTask();
@@ -924,7 +1054,6 @@ public void onDrag(InventoryDragEvent e) {
Event to prevent the player from dropping items into the crafting slots
while doing manual crafting
*/
- @EventHandler
public void drop(PlayerDropItemEvent event) {
Player player = event.getPlayer();
if (this.getInventory().getViewers().contains(player) && !Cfg.craftingQueue) {
@@ -939,26 +1068,4 @@ public void drop(PlayerDropItemEvent event) {
}
}
}
-
- // Events to close the players inventory
- @EventHandler(ignoreCancelled = true)
- public void onClose(InventoryCloseEvent e) {
- if (e.getPlayer() instanceof Player) {
- e.getInventory();
- close((Player) e.getPlayer(), e.getInventory());
- }
- }
-
- @EventHandler(ignoreCancelled = true)
- public void onClose(EntityPickupItemEvent e) {
- if (!(e.getEntity() instanceof Player)) {
- return;
- }
- reloadRecipesTask();
- }
-
- @EventHandler(ignoreCancelled = true)
- public void onExit(PlayerQuitEvent e) {
- close(e.getPlayer(), inventory);
- }
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java
new file mode 100644
index 0000000..d8c0aa7
--- /dev/null
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java
@@ -0,0 +1,98 @@
+package studio.magemonkey.fusion.gui.recipe;
+
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.*;
+
+/**
+ * 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)
+ */
+public class IngredientFingerprint {
+ 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;
+
+ public IngredientFingerprint(Material type,
+ int customModelData,
+ String displayName,
+ List lore,
+ Map enchantments,
+ boolean unbreakable,
+ int durability) {
+ this.type = type;
+ this.customModelData = customModelData;
+ this.displayName = (displayName == null ? "" : displayName);
+ this.lore = (lore == null ? Collections.emptyList() : new ArrayList<>(lore));
+ this.enchantments = (enchantments == null ? Collections.emptyMap() : new HashMap<>(enchantments));
+ this.unbreakable = unbreakable;
+ this.durability = durability;
+ }
+
+ /** Build an IngredientFingerprint by examining a live ItemStack. */
+ public static IngredientFingerprint of(ItemStack is) {
+ Material mat = is.getType();
+ ItemMeta meta = is.getItemMeta();
+
+ int cmd = 0;
+ String name = "";
+ List loreList = Collections.emptyList();
+ Map enchantsMap = Collections.emptyMap();
+ boolean unbreak = false;
+ int dmg = 0;
+
+ if (meta != null) {
+ if (meta.hasCustomModelData()) {
+ cmd = meta.getCustomModelData();
+ }
+ if (meta.hasDisplayName()) {
+ name = meta.getDisplayName();
+ }
+ if (meta.hasLore()) {
+ loreList = new ArrayList<>(Objects.requireNonNull(meta.getLore()));
+ }
+ Map raw = meta.getEnchants();
+ if (!raw.isEmpty()) {
+ enchantsMap = new HashMap<>(raw);
+ }
+ unbreak = meta.isUnbreakable();
+ if (meta instanceof org.bukkit.inventory.meta.Damageable dmeta) {
+ dmg = dmeta.getDamage();
+ }
+ }
+
+ return new IngredientFingerprint(mat, cmd, name, loreList, enchantsMap, unbreak, dmg);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof IngredientFingerprint that)) return false;
+ return customModelData == that.customModelData &&
+ unbreakable == that.unbreakable &&
+ durability == that.durability &&
+ type == that.type &&
+ Objects.equals(displayName, that.displayName) &&
+ Objects.equals(lore, that.lore) &&
+ Objects.equals(enchantments, that.enchantments);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, customModelData, displayName, lore, enchantments, unbreakable, durability);
+ }
+}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
new file mode 100644
index 0000000..a6601fc
--- /dev/null
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
@@ -0,0 +1,84 @@
+package studio.magemonkey.fusion.gui.recipe;
+
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.nio.ByteBuffer;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Objects;
+
+/**
+ * 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
+ *
+ * If MD5 is not available, we fall back to a simple int‐hash of slot hashCodes.
+ */
+public class InventoryFingerprint {
+ public static byte[] fingerprint(Player p) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ for (ItemStack is : p.getInventory().getContents()) {
+ if (is == null || is.getType() == Material.AIR) {
+ md.update((byte) 0);
+ continue;
+ }
+ ItemMeta im = is.getItemMeta();
+ // Material
+ md.update((byte) is.getType().ordinal());
+ // Amount (4 bytes)
+ md.update(ByteBuffer.allocate(4).putInt(is.getAmount()).array());
+ // customModelData
+ if (im != null && im.hasCustomModelData()) {
+ md.update(ByteBuffer.allocate(4).putInt(im.getCustomModelData()).array());
+ }
+ // displayName
+ if (im != null && im.hasDisplayName()) {
+ byte[] nameBytes = im.getDisplayName().getBytes(java.nio.charset.StandardCharsets.UTF_8);
+ md.update(nameBytes);
+ }
+ // lore
+ if (im != null && im.hasLore()) {
+ for (String line : Objects.requireNonNull(im.getLore())) {
+ byte[] lineBytes = line.getBytes(java.nio.charset.StandardCharsets.UTF_8);
+ md.update(lineBytes);
+ }
+ }
+ // enchantments
+ if (im != null && im.hasEnchants()) {
+ im.getEnchants().forEach((ench, lvl) -> {
+ md.update(ByteBuffer.allocate(4).putInt(ench.getKey().hashCode()).array());
+ md.update(ByteBuffer.allocate(4).putInt(lvl).array());
+ });
+ }
+ // unbreakable + durability
+ if (im != null) {
+ if (im.isUnbreakable()) {
+ md.update((byte) 1);
+ }
+ if (im instanceof org.bukkit.inventory.meta.Damageable dmeta && dmeta.getDamage() > 0) {
+ md.update(ByteBuffer.allocate(4).putInt(dmeta.getDamage()).array());
+ }
+ }
+ }
+ return md.digest();
+ } catch (NoSuchAlgorithmException e) {
+ // Fallback: compute a 4‐byte hash code of all slot hashCodes
+ int h = 7;
+ for (ItemStack is : p.getInventory().getContents()) {
+ h = h * 31 + ((is == null) ? 0 : is.hashCode());
+ }
+ return ByteBuffer.allocate(4).putInt(h).array();
+ }
+ }
+}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
new file mode 100644
index 0000000..237c60a
--- /dev/null
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
@@ -0,0 +1,38 @@
+package studio.magemonkey.fusion.gui.recipe;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Key used for caching a CalculatedRecipe. Uniquely identifies a player’s view of one recipe.
+ */
+public class RecipeCacheKey {
+ private final String recipeId;
+ private final byte[] inventoryHash;
+ private final int playerLevel;
+ private final double playerMoney;
+
+ public RecipeCacheKey(String recipeId, byte[] inventoryHash, int playerLevel, double playerMoney) {
+ this.recipeId = recipeId;
+ this.inventoryHash = Arrays.copyOf(inventoryHash, inventoryHash.length);
+ this.playerLevel = playerLevel;
+ this.playerMoney = playerMoney;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RecipeCacheKey that)) return false;
+ return playerLevel == that.playerLevel &&
+ Double.compare(that.playerMoney, playerMoney) == 0 &&
+ Objects.equals(recipeId, that.recipeId) &&
+ Arrays.equals(inventoryHash, that.inventoryHash);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(recipeId, playerLevel, playerMoney);
+ result = 31 * result + Arrays.hashCode(inventoryHash);
+ return result;
+ }
+}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
new file mode 100644
index 0000000..281b800
--- /dev/null
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
@@ -0,0 +1,115 @@
+package studio.magemonkey.fusion.gui.recipe;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityPickupItemEvent;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.bukkit.event.inventory.InventoryDragEvent;
+import org.bukkit.event.player.PlayerDropItemEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.inventory.Inventory;
+import studio.magemonkey.fusion.data.player.FusionPlayer;
+import studio.magemonkey.fusion.data.player.PlayerLoader;
+import studio.magemonkey.fusion.gui.ProfessionGuiRegistry;
+import studio.magemonkey.fusion.gui.RecipeGui;
+
+/**
+ * Centralized listener for all RecipeGui‐related events.
+ * For each incoming event, we look up the player’s FusionPlayer and its cachedGuis.
+ * If an event’s Inventory matches one of the cached RecipeGui inventories, we forward
+ * to that RecipeGui’s click/drag/close/drop logic.
+ */
+public class RecipeGuiEventRouter implements Listener {
+
+ /**
+ * Look up, for a given Player, which RecipeGui (if any) has this exact Inventory open.
+ * We fetch that player’s FusionPlayer via PlayerLoader.getPlayer(Player).
+ */
+ private RecipeGui findGuiFor(Player player, Inventory inv) {
+ if(!ProfessionGuiRegistry.getLatestRecipeGui().containsKey(player.getUniqueId()))
+ return null;
+ RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(player.getUniqueId());
+ if (gui.getInventory().equals(inv)) {
+ return gui;
+ }
+ return null;
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST)
+ public void onInventoryClick(InventoryClickEvent event) {
+ if (!(event.getWhoClicked() instanceof Player p)) return;
+ Inventory inv = event.getInventory();
+ RecipeGui gui = findGuiFor(p, inv);
+ if (gui == null) return;
+
+ // Only forward if the clicked inventory is *exactly* the GUI’s inventory
+ if (!inv.equals(gui.getInventory())) return;
+ if (event.getRawSlot() < 0) return;
+
+ // Delegate to RecipeGui.click(...)
+ gui.click(event);
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onInventoryDrag(InventoryDragEvent event) {
+ if (!(event.getWhoClicked() instanceof Player p)) return;
+ Inventory inv = event.getInventory();
+ RecipeGui gui = findGuiFor(p, inv);
+ if (gui == null) return;
+
+ // Only route if the drag is happening inside this GUI’s inventory
+ if (!inv.equals(gui.getInventory())) return;
+
+ // Delegate to RecipeGui’s drag logic
+ gui.onDrag(event);
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onInventoryClose(InventoryCloseEvent event) {
+ if (!(event.getPlayer() instanceof Player p)) return;
+ Inventory inv = event.getInventory();
+ RecipeGui gui = findGuiFor(p, inv);
+ if (gui == null) return;
+
+ // If the player closes this GUI, perform cleanup
+ gui.close(p, inv);
+
+ // Also remove it from the player’s cache so it won’t be routed again
+ // TODO remove from cache if needed
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onPlayerDrop(PlayerDropItemEvent event) {
+ Player p = event.getPlayer();
+ RecipeGui gui = findGuiFor(p, p.getOpenInventory().getTopInventory());
+ if (gui == null) return;
+
+ // If the player drops an item while their RecipeGui is open, route to its drop logic
+ gui.drop(event);
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onItemPickup(EntityPickupItemEvent event) {
+ if (!(event.getEntity() instanceof Player p)) return;
+ RecipeGui gui = findGuiFor(p, p.getOpenInventory().getTopInventory());
+ if (gui == null) return;
+
+ // If the player picks up anything while the GUI is open, trigger a reload
+ gui.reloadRecipesTask();
+ }
+
+ @EventHandler(ignoreCancelled = true)
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ 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;
+ gui.close(p, gui.getInventory());
+ }
+}
From dbf0c48b75a2da76b2f3cd673532c499b4dd0a19 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sat, 12 Jul 2025 09:42:19 +0200
Subject: [PATCH 06/16] improved reloading to reset caches
---
src/main/java/studio/magemonkey/fusion/Fusion.java | 1 +
.../studio/magemonkey/fusion/commands/CommandMechanics.java | 4 +++-
.../studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java | 5 +++++
src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java | 4 ++++
4 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/main/java/studio/magemonkey/fusion/Fusion.java b/src/main/java/studio/magemonkey/fusion/Fusion.java
index 6572bad..c673fdf 100644
--- a/src/main/java/studio/magemonkey/fusion/Fusion.java
+++ b/src/main/java/studio/magemonkey/fusion/Fusion.java
@@ -28,6 +28,7 @@
import studio.magemonkey.fusion.data.player.PlayerLoader;
import studio.magemonkey.fusion.data.recipes.*;
import studio.magemonkey.fusion.gui.BrowseGUI;
+import studio.magemonkey.fusion.gui.ProfessionGuiRegistry;
import studio.magemonkey.fusion.gui.recipe.RecipeGuiEventRouter;
import studio.magemonkey.fusion.util.ExperienceManager;
import studio.magemonkey.fusion.util.LevelFunction;
diff --git a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java
index 9117541..e713420 100644
--- a/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java
+++ b/src/main/java/studio/magemonkey/fusion/commands/CommandMechanics.java
@@ -21,6 +21,7 @@
import studio.magemonkey.fusion.data.recipes.RecipeItem;
import studio.magemonkey.fusion.gui.BrowseGUI;
import studio.magemonkey.fusion.gui.ProfessionGuiRegistry;
+import studio.magemonkey.fusion.gui.RecipeGui;
import studio.magemonkey.fusion.gui.show.ShowRecipesGui;
import studio.magemonkey.fusion.util.Utils;
@@ -314,7 +315,8 @@ public static void reloadPlugin(CommandSender sender) {
Fusion.getInstance().closeAll();
Fusion.getInstance().reloadConfig();
Fusion.getInstance().reloadLang();
- ProfessionGuiRegistry.getLatestRecipeGui().clear();
+ ProfessionGuiRegistry.clearLatestRecipeGui();
+ RecipeGui.resetRecipeHashes();
CodexEngine.get()
.getMessageUtil()
.sendMessage("fusion.reload", sender, new MessageData("sender", sender));
diff --git a/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java b/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
index 7b9937e..fc611d9 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
@@ -76,4 +76,9 @@ public void closeAll() {
toClose.forEach(HumanEntity::closeInventory);
}
+
+ public static void clearLatestRecipeGui() {
+
+ latestRecipeGui.clear();
+ }
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
index 3082c6e..0afc49f 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
@@ -1068,4 +1068,8 @@ public void drop(PlayerDropItemEvent event) {
}
}
}
+
+ public static void resetRecipeHashes() {
+ recipeCache.clear();
+ }
}
From 690d5e9064d514f9ee2eb1cc0cff9665579691d4 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 20 Jul 2025 16:06:31 +0200
Subject: [PATCH 07/16] Update
src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../magemonkey/fusion/gui/recipe/InventoryFingerprint.java | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
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 a6601fc..35b9c81 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
@@ -37,10 +37,12 @@ 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());
+ intBuffer.clear();
+ md.update(intBuffer.putInt(is.getAmount()).array());
// customModelData
if (im != null && im.hasCustomModelData()) {
- md.update(ByteBuffer.allocate(4).putInt(im.getCustomModelData()).array());
+ intBuffer.clear();
+ md.update(intBuffer.putInt(im.getCustomModelData()).array());
}
// displayName
if (im != null && im.hasDisplayName()) {
From d249ab5fba867ba83ace01ad4a296af967e087b0 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 20 Jul 2025 16:07:16 +0200
Subject: [PATCH 08/16] Update
src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
.../java/studio/magemonkey/fusion/gui/RecipeGui.java | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
index 0afc49f..77b81d4 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
@@ -97,8 +97,15 @@ public class RecipeGui implements Listener {
private final ArrayList blockedSlots = new ArrayList<>(20);
private final ArrayList queuedSlots = new ArrayList<>(20);
- // Caches all previously built CalculatedRecipe objects:
- private static final Map recipeCache = new ConcurrentHashMap<>();
+ // Caches all previously built CalculatedRecipe objects with a size limit:
+ private static final Map recipeCache = Collections.synchronizedMap(
+ new LinkedHashMap(100, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > 100; // Limit cache size to 100 entries
+ }
+ }
+ );
// Last‐seen “inventory fingerprint” so we know if we truly need to recalc:
private byte[] lastInventoryHash = new byte[0];
From 76032ff881f3ea469c8903af4a107bdbdb431a05 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 20 Jul 2025 16:08:01 +0200
Subject: [PATCH 09/16] Update
src/main/java/studio/magemonkey/fusion/api/FusionAPI.java
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---
src/main/java/studio/magemonkey/fusion/api/FusionAPI.java | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java b/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java
index fb5330a..40c328b 100644
--- a/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java
+++ b/src/main/java/studio/magemonkey/fusion/api/FusionAPI.java
@@ -22,7 +22,10 @@ public static void init() {
}
public static ProfessionManager getProfessionManager() {
- return professionManager != null ? professionManager : new ProfessionManager();
+ if (professionManager == null) {
+ professionManager = new ProfessionManager();
+ }
+ return professionManager;
}
public static PlayerManager getPlayerManager() {
From e208b0da6f85ff183e615bc41bec71e9bcfb7c8a Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 20 Jul 2025 16:08:29 +0200
Subject: [PATCH 10/16] change requests from copilot
---
src/main/java/studio/magemonkey/fusion/Fusion.java | 1 -
.../fusion/api/events/services/QueueService.java | 7 ++++---
.../fusion/cfg/CraftingRequirementsCfg.java | 4 ----
.../fusion/cfg/migrations/ProfessionMigration.java | 1 -
.../magemonkey/fusion/data/player/FusionPlayer.java | 1 -
.../data/professions/CalculatedProfession.java | 5 ++++-
.../fusion/data/professions/ProfessionSettings.java | 1 -
.../magemonkey/fusion/gui/ProfessionGuiRegistry.java | 1 -
.../java/studio/magemonkey/fusion/gui/RecipeGui.java | 12 +++++++-----
.../magemonkey/fusion/gui/recipe/RecipeCacheKey.java | 9 +++++++++
.../fusion/gui/recipe/RecipeGuiEventRouter.java | 3 ---
11 files changed, 24 insertions(+), 21 deletions(-)
diff --git a/src/main/java/studio/magemonkey/fusion/Fusion.java b/src/main/java/studio/magemonkey/fusion/Fusion.java
index c673fdf..6572bad 100644
--- a/src/main/java/studio/magemonkey/fusion/Fusion.java
+++ b/src/main/java/studio/magemonkey/fusion/Fusion.java
@@ -28,7 +28,6 @@
import studio.magemonkey.fusion.data.player.PlayerLoader;
import studio.magemonkey.fusion.data.recipes.*;
import studio.magemonkey.fusion.gui.BrowseGUI;
-import studio.magemonkey.fusion.gui.ProfessionGuiRegistry;
import studio.magemonkey.fusion.gui.recipe.RecipeGuiEventRouter;
import studio.magemonkey.fusion.util.ExperienceManager;
import studio.magemonkey.fusion.util.LevelFunction;
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 f76a24b..7738217 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
@@ -5,8 +5,6 @@
import org.bukkit.inventory.ItemStack;
import studio.magemonkey.codex.CodexEngine;
import studio.magemonkey.codex.api.DelayedCommand;
-import studio.magemonkey.codex.api.items.exception.MissingItemException;
-import studio.magemonkey.codex.api.items.exception.MissingProviderException;
import studio.magemonkey.codex.util.messages.MessageData;
import studio.magemonkey.fusion.Fusion;
import studio.magemonkey.fusion.api.FusionAPI;
@@ -20,7 +18,10 @@
import studio.magemonkey.fusion.data.recipes.RecipeItem;
import studio.magemonkey.fusion.util.PlayerUtil;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
public class QueueService {
diff --git a/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java b/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java
index 938f177..c956f41 100644
--- a/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java
+++ b/src/main/java/studio/magemonkey/fusion/cfg/CraftingRequirementsCfg.java
@@ -1,8 +1,6 @@
package studio.magemonkey.fusion.cfg;
import net.kyori.adventure.text.Component;
-import org.bukkit.enchantments.Enchantment;
-import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import studio.magemonkey.codex.compat.VersionManager;
import studio.magemonkey.codex.util.messages.MessageUtil;
@@ -10,8 +8,6 @@
import studio.magemonkey.fusion.data.recipes.RecipeItem;
import studio.magemonkey.fusion.util.ChatUT;
-import java.util.*;
-
public class CraftingRequirementsCfg {
private static YamlParser config;
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 ac09c13..fbdb883 100644
--- a/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java
+++ b/src/main/java/studio/magemonkey/fusion/cfg/migrations/ProfessionMigration.java
@@ -2,7 +2,6 @@
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
-import org.bukkit.event.entity.CreatureSpawnEvent;
import org.jetbrains.annotations.Nullable;
import studio.magemonkey.codex.api.items.PrefixHelper;
import studio.magemonkey.fusion.Fusion;
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 08d88e9..cec424d 100644
--- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java
+++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java
@@ -12,7 +12,6 @@
import studio.magemonkey.fusion.data.queue.QueueItem;
import studio.magemonkey.fusion.data.recipes.CraftingTable;
import studio.magemonkey.fusion.data.recipes.Recipe;
-import studio.magemonkey.fusion.gui.RecipeGui;
import java.util.Collection;
import java.util.Map;
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 be5ea15..dfc5df7 100644
--- a/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java
+++ b/src/main/java/studio/magemonkey/fusion/data/professions/CalculatedProfession.java
@@ -9,7 +9,10 @@
import studio.magemonkey.codex.CodexEngine;
import studio.magemonkey.fusion.Fusion;
import studio.magemonkey.fusion.cfg.CraftingRequirementsCfg;
-import studio.magemonkey.fusion.data.recipes.*;
+import studio.magemonkey.fusion.data.recipes.CalculatedRecipe;
+import studio.magemonkey.fusion.data.recipes.CraftingTable;
+import studio.magemonkey.fusion.data.recipes.Recipe;
+import studio.magemonkey.fusion.data.recipes.RecipeItem;
import studio.magemonkey.fusion.util.ExperienceManager;
import studio.magemonkey.fusion.util.InvalidPatternItemException;
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 f4431b1..d190baa 100644
--- a/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java
+++ b/src/main/java/studio/magemonkey/fusion/data/professions/ProfessionSettings.java
@@ -2,7 +2,6 @@
import lombok.Getter;
import lombok.Setter;
-import org.bukkit.Bukkit;
import org.bukkit.Color;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
diff --git a/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java b/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
index fc611d9..4d92521 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/ProfessionGuiRegistry.java
@@ -1,7 +1,6 @@
package studio.magemonkey.fusion.gui;
import lombok.Getter;
-import lombok.Setter;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import studio.magemonkey.codex.CodexEngine;
diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
index 0afc49f..37fc502 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
@@ -13,7 +13,9 @@
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.Listener;
-import org.bukkit.event.inventory.*;
+import org.bukkit.event.inventory.InventoryAction;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryDragEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerDropItemEvent;
import org.bukkit.inventory.Inventory;
@@ -41,16 +43,16 @@
import studio.magemonkey.fusion.data.recipes.CraftingTable;
import studio.magemonkey.fusion.data.recipes.Recipe;
import studio.magemonkey.fusion.data.recipes.RecipeItem;
+import studio.magemonkey.fusion.gui.recipe.IngredientFingerprint;
+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.util.ChatUT;
import studio.magemonkey.fusion.util.ExperienceManager;
import studio.magemonkey.fusion.util.PlayerUtil;
-import studio.magemonkey.fusion.gui.recipe.IngredientFingerprint;
-import studio.magemonkey.fusion.gui.recipe.InventoryFingerprint;
-import studio.magemonkey.fusion.gui.recipe.RecipeCacheKey;
-import java.util.concurrent.ConcurrentHashMap;
import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
@Getter
public class RecipeGui implements Listener {
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 237c60a..154d1b9 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
@@ -12,6 +12,15 @@ public class RecipeCacheKey {
private final int playerLevel;
private final double playerMoney;
+ // TODO add:
+ /*
+ * - Vanilla Exp
+ * - Conditions.McMMO Map
+ * - Conditions.Fabled Map
+ * - Conditions.Aura Map
+ * - Conditions.ProfessionLevels Map
+ */
+
public RecipeCacheKey(String recipeId, byte[] inventoryHash, int playerLevel, double playerMoney) {
this.recipeId = recipeId;
this.inventoryHash = Arrays.copyOf(inventoryHash, inventoryHash.length);
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 281b800..2f9435f 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
@@ -76,9 +76,6 @@ public void onInventoryClose(InventoryCloseEvent event) {
// If the player closes this GUI, perform cleanup
gui.close(p, inv);
-
- // Also remove it from the player’s cache so it won’t be routed again
- // TODO remove from cache if needed
}
@EventHandler(ignoreCancelled = true)
From 3cffc6768b446f49277c8c515eee78bd2f644c10 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 20 Jul 2025 16:12:20 +0200
Subject: [PATCH 11/16] fixed merge conflicts
---
.../magemonkey/fusion/gui/recipe/InventoryFingerprint.java | 7 -------
1 file changed, 7 deletions(-)
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 35b9c81..d2b0688 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
@@ -37,13 +37,6 @@ public static byte[] fingerprint(Player p) {
// Material
md.update((byte) is.getType().ordinal());
// Amount (4 bytes)
- intBuffer.clear();
- md.update(intBuffer.putInt(is.getAmount()).array());
- // customModelData
- if (im != null && im.hasCustomModelData()) {
- intBuffer.clear();
- md.update(intBuffer.putInt(im.getCustomModelData()).array());
- }
// displayName
if (im != null && im.hasDisplayName()) {
byte[] nameBytes = im.getDisplayName().getBytes(java.nio.charset.StandardCharsets.UTF_8);
From 3b671469cf0f23a48b2023bcbee5e0a6dee7a87a Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 20 Jul 2025 17:46:54 +0200
Subject: [PATCH 12/16] updated dependencies
---
pom.xml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/pom.xml b/pom.xml
index 2e9cf95..05210d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -59,7 +59,7 @@
studio.magemonkey
divinity
- 1.0.2-R0.22-SNAPSHOT
+ 1.0.2-R0.47-SNAPSHOT
studio.magemonkey
@@ -100,7 +100,7 @@
com.nexomc
nexo
- 0.10.0-dev.27
+ 1.7.0-dev.4
provided
From e116405c1c10d8db5ca10451952bce6f1d985e12 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 20 Jul 2025 17:55:00 +0200
Subject: [PATCH 13/16] updated dependencies
---
pom.xml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pom.xml b/pom.xml
index 05210d8..008863c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -55,6 +55,7 @@
studio.magemonkey
sapphire
1.0.1-R0.2-SNAPSHOT
+ provided
studio.magemonkey
From 36f293bc9da072c04a70c191bb90e00d5a40ddc7 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Thu, 24 Jul 2025 23:49:57 +0200
Subject: [PATCH 14/16] removed md
---
FORCE_COMMANDS.md | 96 -----------------------------------------------
1 file changed, 96 deletions(-)
delete mode 100644 FORCE_COMMANDS.md
diff --git a/FORCE_COMMANDS.md b/FORCE_COMMANDS.md
deleted file mode 100644
index 67a34b7..0000000
--- a/FORCE_COMMANDS.md
+++ /dev/null
@@ -1,96 +0,0 @@
-# Force Commands for Fusion
-
-This document describes the administrator force commands implemented to support issue #58.
-
-## Overview
-
-Force commands are admin-only commands that bypass normal profession restrictions and requirements. They are designed to help administrators manage player professions programmatically, especially for NPC interactions and server automation.
-
-## Permission
-
-All force commands require the `fusion.admin.force` permission.
-
-## Commands
-
-### `/fusion forcejoin `
-Forces a player to join a profession without checking requirements or costs.
-
-**Usage:**
-- `player`: Target player name (must be online)
-- `profession`: Name of the profession to join
-
-**Example:**
-```
-/fusion forcejoin Steve blacksmith
-```
-
-### `/fusion forceleave `
-Forces a player to leave a profession without confirmation prompts.
-
-**Usage:**
-- `player`: Target player name (must be online)
-- `profession`: Name of the profession to leave
-
-**Example:**
-```
-/fusion forceleave Steve blacksmith
-```
-
-### `/fusion forcestats `
-Shows profession statistics for any player.
-
-**Usage:**
-- `player`: Target player name (must be online)
-
-**Example:**
-```
-/fusion forcestats Steve
-```
-
-### `/fusion forcemaster `
-Forces a player to master a profession without level or fee requirements.
-
-**Usage:**
-- `player`: Target player name (must be online)
-- `profession`: Name of the profession to master
-
-**Example:**
-```
-/fusion forcemaster Steve blacksmith
-```
-
-### `/fusion forceshow `
-Forces the ingredient usage GUI to open for a player based on their held item.
-
-**Usage:**
-- `player`: Target player name (must be online)
-
-**Example:**
-```
-/fusion forceshow Steve
-```
-
-## Tab Completion
-
-All force commands support tab completion for:
-- Command names when typing the first argument
-- Online player names for the player argument
-- Available profession names for profession arguments
-
-## Error Handling
-
-The commands include comprehensive error handling for:
-- Missing permissions
-- Invalid syntax/argument count
-- Player not found/offline
-- Invalid profession names
-- Player already has/doesn't have profession
-- Player already mastered profession
-- No item in hand (for forceshow)
-
-## Implementation Notes
-
-- Commands bypass all normal restrictions including costs, requirements, and confirmations
-- Uses the same underlying API as regular commands but with forced parameters
-- Maintains consistency with existing command patterns and error messages
-- Includes proper permission checks to prevent unauthorized usage
\ No newline at end of file
From 3e4f920070de3a857ad6988577ffcecb64fd33a4 Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 27 Jul 2025 11:21:48 +0200
Subject: [PATCH 15/16] fixed crafting time to be skipped completly when
`Cfg.updateQueueOffline=true` (offline updates still dont work, but online
time works now)
---
.../fusion/data/player/FusionPlayer.java | 2 +
.../fusion/data/queue/CraftingQueue.java | 46 +++++++++++++++-
.../fusion/data/queue/QueueItem.java | 52 +++++++++++++------
.../magemonkey/fusion/gui/RecipeGui.java | 12 ++---
4 files changed, 88 insertions(+), 24 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 cec424d..80c97e9 100644
--- a/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java
+++ b/src/main/java/studio/magemonkey/fusion/data/player/FusionPlayer.java
@@ -49,6 +49,7 @@ public Player getPlayer() {
public CraftingQueue getQueue(String profession, Category category) {
if (!cachedQueues.containsKey(profession + "." + category.getName())) {
cachedQueues.put(profession + "." + category.getName(), new CraftingQueue(getPlayer(), profession, category));
+ Bukkit.getConsoleSender().sendMessage("Created new crafting queue for profession " + profession + " and category " + category.getName() + " for player " + getPlayer().getName());
}
return cachedQueues.get(profession + "." + category.getName());
}
@@ -348,6 +349,7 @@ 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();
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 eccda48..6ebbc9d 100644
--- a/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java
+++ b/src/main/java/studio/magemonkey/fusion/data/queue/CraftingQueue.java
@@ -9,6 +9,7 @@
import studio.magemonkey.codex.util.messages.MessageData;
import studio.magemonkey.fusion.Fusion;
import studio.magemonkey.fusion.api.FusionAPI;
+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.PlayerLoader;
@@ -39,9 +40,50 @@ public CraftingQueue(Player player, String profession, Category category) {
this.profession = profession;
this.category = category;
this.queuedItems = new HashMap<>(20);
- queue.addAll(SQLManager.queues().getQueueItems(player.getUniqueId(), profession, category));
- queue.forEach(entry -> entry.setCraftinQueue(this));
+ // Load items from the database
+ List loaded = SQLManager.queues().getQueueItems(player.getUniqueId(), profession, category);
+ queue.addAll(loaded);
+
+ /*
+ * If offline progression is enabled, distribute the offline time across the
+ * 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 (Cfg.updateQueueOffline && !queue.isEmpty()) {
+ long now = System.currentTimeMillis();
+ // find the first unfinished item
+ QueueItem current = queue.stream()
+ .filter(item -> !item.isDone())
+ .findFirst()
+ .orElse(null);
+ if (current != null) {
+ int offlineSeconds = (int) ((now - current.getTimestamp()) / 1000L);
+ // apply offline progress sequentially
+ for (QueueItem item : queue) {
+ if (offlineSeconds <= 0) {
+ break;
+ }
+ if (item.isDone()) {
+ continue;
+ }
+ int remaining = item.getRecipe().getCraftingTime() - item.getSavedSeconds();
+ int apply = Math.min(offlineSeconds, remaining);
+ item.progressOffline(apply);
+ offlineSeconds -= apply;
+ }
+ }
+ // normalize timestamps after applying offline progress
+ queue.forEach(item -> item.setTimestamp(now));
+ }
+
+ // Assign the queue and update the icons
+ queue.forEach(entry -> {
+ entry.setCraftinQueue(this);
+ entry.updateIcon();
+ });
+
+ // Start the queue update task
queueTask = new BukkitRunnable() {
@Override
public void run() {
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 086b164..76b5afa 100644
--- a/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java
+++ b/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java
@@ -3,6 +3,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
+import lombok.Setter;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import studio.magemonkey.fusion.cfg.Cfg;
@@ -19,6 +20,7 @@ public class QueueItem {
private Category category;
private @NonNull Recipe recipe;
private ItemStack icon;
+ @Setter
private long timestamp;
private boolean done;
private int savedSeconds;
@@ -41,17 +43,7 @@ public QueueItem(int id,
this.recipe = recipe;
this.timestamp = timestamp;
this.savedSeconds = savedSeconds;
- this.visualRemainingItemTime = (recipe.getCraftingTime() - savedSeconds);
- // If the queue item shall not be working when player is offline, just instantly override the timestamp
- if (Cfg.updateQueueOffline) {
- int diff = (int) ((System.currentTimeMillis() - timestamp) / 1000);
- if (diff + savedSeconds > recipe.getCraftingTime()) {
- this.savedSeconds = recipe.getCraftingTime();
- this.done = true;
- } else {
- this.savedSeconds += diff;
- }
- }
+ this.visualRemainingItemTime = recipe.getCraftingTime() - savedSeconds;
this.timestamp = System.currentTimeMillis();
}
@@ -66,16 +58,27 @@ public void update() {
int reconstructedCooldown = this.visualRemainingItemTime + savedSeconds;
if (visualRemainingItemTime == recipe.getCraftingTime() + 1) return;
+
if (reconstructedCooldown <= recipe.getCraftingTime()) {
if (!isRunning) {
+ // Start the item
isRunning = true;
+ this.timestamp = System.currentTimeMillis();
return;
}
- this.savedSeconds++;
- this.done = savedSeconds >= recipe.getCraftingTime();
- this.icon = ProfessionsCfg.getQueueItem(profession, this);
- if (this.savedSeconds > 0)
- this.visualRemainingItemTime--;
+ // Advance progress
+ savedSeconds++;
+ this.timestamp = System.currentTimeMillis();
+ // Check if finished
+ if (savedSeconds >= recipe.getCraftingTime()) {
+ done = true;
+ // Mark finish time to prevent future overcounting
+ this.timestamp = System.currentTimeMillis();
+ }
+ icon = ProfessionsCfg.getQueueItem(profession, this);
+ if (savedSeconds > 0) {
+ visualRemainingItemTime--;
+ }
}
} else {
this.icon = ProfessionsCfg.getQueueItem(profession, this);
@@ -89,4 +92,21 @@ public void updateIcon() {
public String getRecipePath() {
return recipe.getRecipePath();
}
+
+ public void progressOffline(int offlineSeconds) {
+ if (done || offlineSeconds <= 0) {
+ return;
+ }
+ int remaining = recipe.getCraftingTime() - savedSeconds;
+ if (offlineSeconds >= remaining) {
+ // item has finished offline
+ savedSeconds = recipe.getCraftingTime();
+ done = true;
+ } else {
+ // item partially progressed offline
+ savedSeconds += offlineSeconds;
+ }
+ // update the remaining time for the UI
+ visualRemainingItemTime = recipe.getCraftingTime() - savedSeconds;
+ }
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
index 8617b1c..e243c29 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/RecipeGui.java
@@ -101,12 +101,12 @@ public class RecipeGui implements Listener {
// Caches all previously built CalculatedRecipe objects with a size limit:
private static final Map recipeCache = Collections.synchronizedMap(
- new LinkedHashMap(100, 0.75f, true) {
- @Override
- protected boolean removeEldestEntry(Map.Entry eldest) {
- return size() > 100; // Limit cache size to 100 entries
+ new LinkedHashMap<>(100, 0.75f, true) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > 100; // Limit cache size to 100 entries
+ }
}
- }
);
// Last‐seen “inventory fingerprint” so we know if we truly need to recalc:
@@ -132,7 +132,7 @@ public RecipeGui(Player player, CraftingTable table, Category category) {
}
setPattern();
if (Cfg.craftingQueue && pattern != null) {
- this.queue = PlayerLoader.getPlayer(player).getQueue(table.getName(), this.category);
+ this.queue = FusionAPI.getPlayerManager().getPlayer(player).getQueue(table.getName(), this.category);
}
Fusion.registerListener(this);
initialize();
From 8179a2c15b23d94efe74932e196a2e45d203b3ac Mon Sep 17 00:00:00 2001
From: MaksyKun <77341370+MaksyKun@users.noreply.github.com>
Date: Sun, 27 Jul 2025 11:33:18 +0200
Subject: [PATCH 16/16] fix approach of offline updates from queue items
---
.../studio/magemonkey/fusion/data/queue/QueueItem.java | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
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 76b5afa..ba3c94f 100644
--- a/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java
+++ b/src/main/java/studio/magemonkey/fusion/data/queue/QueueItem.java
@@ -44,7 +44,13 @@ public QueueItem(int id,
this.timestamp = timestamp;
this.savedSeconds = savedSeconds;
this.visualRemainingItemTime = recipe.getCraftingTime() - savedSeconds;
- this.timestamp = System.currentTimeMillis();
+ }
+
+ public QueueItem(int id,
+ String profession,
+ Category category,
+ @NotNull Recipe recipe) {
+ this(id, profession, category, recipe, System.currentTimeMillis(), 0);
}
public void setCraftinQueue(CraftingQueue craftingQueue) {