");
} else if (event.isRightClick()) {
if (recipe.getSettings().getFlags().isEmpty()) {
return;
}
// Remove the last flag from the set
- ItemFlag lastFlag = new ArrayList<>(recipe.getSettings().getFlags()).get(recipe.getSettings().getFlags().size() - 1);
+ ItemFlag lastFlag = new ArrayList<>(recipe.getSettings().getFlags()).get(
+ recipe.getSettings().getFlags().size() - 1);
recipe.getSettings().getFlags().remove(lastFlag);
hasChanges = true;
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java
index d8c0aa7..ff9de7e 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/IngredientFingerprint.java
@@ -8,24 +8,24 @@
/**
* Immutable fingerprint for an ItemStack that matches CalculatedRecipe.isSimilar(...) logic.
- *
+ *
* We compare:
- * - Material
- * - customModelData (if present)
- * - displayName (if present)
- * - lore lines (if present)
- * - all enchantments (if present)
- * - unbreakable flag
- * - durability (if Damageable)
+ * - Material
+ * - customModelData (if present)
+ * - displayName (if present)
+ * - lore lines (if present)
+ * - all enchantments (if present)
+ * - unbreakable flag
+ * - durability (if Damageable)
*/
public class IngredientFingerprint {
- private final Material type;
- private final int customModelData;
- private final String displayName;
- private final List lore;
+ private final Material type;
+ private final int customModelData;
+ private final String displayName;
+ private final List lore;
private final Map enchantments;
- private final boolean unbreakable;
- private final int durability;
+ private final boolean unbreakable;
+ private final int durability;
public IngredientFingerprint(Material type,
int customModelData,
@@ -43,17 +43,19 @@ public IngredientFingerprint(Material type,
this.durability = durability;
}
- /** Build an IngredientFingerprint by examining a live ItemStack. */
+ /**
+ * Build an IngredientFingerprint by examining a live ItemStack.
+ */
public static IngredientFingerprint of(ItemStack is) {
- Material mat = is.getType();
+ Material mat = is.getType();
ItemMeta meta = is.getItemMeta();
- int cmd = 0;
- String name = "";
- List loreList = Collections.emptyList();
+ int cmd = 0;
+ String name = "";
+ List loreList = Collections.emptyList();
Map enchantsMap = Collections.emptyMap();
- boolean unbreak = false;
- int dmg = 0;
+ boolean unbreak = false;
+ int dmg = 0;
if (meta != null) {
if (meta.hasCustomModelData()) {
@@ -65,7 +67,7 @@ public static IngredientFingerprint of(ItemStack is) {
if (meta.hasLore()) {
loreList = new ArrayList<>(Objects.requireNonNull(meta.getLore()));
}
- Map raw = meta.getEnchants();
+ Map raw = meta.getEnchants();
if (!raw.isEmpty()) {
enchantsMap = new HashMap<>(raw);
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
index d2b0688..1418129 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/InventoryFingerprint.java
@@ -13,15 +13,15 @@
/**
* Helper to build a small MD5 fingerprint of an entire PlayerInventory.
* We incorporate:
- * - Material ordinal
- * - amount
- * - customModelData
- * - displayName
- * - lore lines
- * - enchantments
- * - unbreakable
- * - durability if Damageable
- *
+ * - Material ordinal
+ * - amount
+ * - customModelData
+ * - displayName
+ * - lore lines
+ * - enchantments
+ * - unbreakable
+ * - durability if Damageable
+ *
* If MD5 is not available, we fall back to a simple int‐hash of slot hashCodes.
*/
public class InventoryFingerprint {
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
index 154d1b9..49fc7c1 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeCacheKey.java
@@ -9,16 +9,16 @@
public class RecipeCacheKey {
private final String recipeId;
private final byte[] inventoryHash;
- private final int playerLevel;
+ private final int playerLevel;
private final double playerMoney;
// TODO add:
/*
- * - Vanilla Exp
- * - Conditions.McMMO Map
- * - Conditions.Fabled Map
- * - Conditions.Aura Map
- * - Conditions.ProfessionLevels Map
+ * - Vanilla Exp
+ * - Conditions.McMMO Map
+ * - Conditions.Fabled Map
+ * - Conditions.Aura Map
+ * - Conditions.ProfessionLevels Map
*/
public RecipeCacheKey(String recipeId, byte[] inventoryHash, int playerLevel, double playerMoney) {
diff --git a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
index 2f9435f..c5de114 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/recipe/RecipeGuiEventRouter.java
@@ -29,7 +29,7 @@ public class RecipeGuiEventRouter implements Listener {
* We fetch that player’s FusionPlayer via PlayerLoader.getPlayer(Player).
*/
private RecipeGui findGuiFor(Player player, Inventory inv) {
- if(!ProfessionGuiRegistry.getLatestRecipeGui().containsKey(player.getUniqueId()))
+ if (!ProfessionGuiRegistry.getLatestRecipeGui().containsKey(player.getUniqueId()))
return null;
RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(player.getUniqueId());
if (gui.getInventory().equals(inv)) {
@@ -80,7 +80,7 @@ public void onInventoryClose(InventoryCloseEvent event) {
@EventHandler(ignoreCancelled = true)
public void onPlayerDrop(PlayerDropItemEvent event) {
- Player p = event.getPlayer();
+ Player p = event.getPlayer();
RecipeGui gui = findGuiFor(p, p.getOpenInventory().getTopInventory());
if (gui == null) return;
@@ -100,13 +100,14 @@ public void onItemPickup(EntityPickupItemEvent event) {
@EventHandler(ignoreCancelled = true)
public void onPlayerQuit(PlayerQuitEvent event) {
- Player p = event.getPlayer();
+ Player p = event.getPlayer();
FusionPlayer fp = PlayerLoader.getPlayer(p);
if (fp == null) return;
// On quit, close and remove *all* open RecipeGuis for that player
RecipeGui gui = ProfessionGuiRegistry.getLatestRecipeGui().get(p.getUniqueId());
- if(gui == null) return;
+ if (gui == null) return;
gui.close(p, gui.getInventory());
+ ProfessionGuiRegistry.getLatestRecipeGui().remove(p.getUniqueId());
}
}
diff --git a/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java b/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java
index e220850..8503e36 100644
--- a/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java
+++ b/src/main/java/studio/magemonkey/fusion/gui/slot/Slot.java
@@ -93,7 +93,7 @@ public ItemStack canHoldItem(ItemStack item) {
/**
* -- GETTER --
- * Returns base slot type.
+ * Returns base slot type.
*
*/
protected final SlotType slotType;
diff --git a/src/main/java/studio/magemonkey/fusion/hook/VaultHook.java b/src/main/java/studio/magemonkey/fusion/hook/VaultHook.java
new file mode 100644
index 0000000..029c31c
--- /dev/null
+++ b/src/main/java/studio/magemonkey/fusion/hook/VaultHook.java
@@ -0,0 +1,67 @@
+package studio.magemonkey.fusion.hook;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitTask;
+import studio.magemonkey.codex.CodexEngine;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Consumer;
+
+public class VaultHook {
+
+ private static BukkitTask task;
+ private static Map> storedBalances = new HashMap<>();
+ private static int thresholdSeconds = 10;
+
+ public static double getBalance(Player player) {
+ if (storedBalances.containsKey(player.getUniqueId())) {
+ Pair balanceData = storedBalances.get(player.getUniqueId());
+ double balance = balanceData.getLeft();
+ LocalDateTime timestamp = balanceData.getRight();
+ if (timestamp.plusSeconds(thresholdSeconds).isBefore(LocalDateTime.now())) {
+ double updatedBalance = CodexEngine.get().getVault() != null ? CodexEngine.get().getVault().getBalance(player) : 0.0;
+ storedBalances.put(player.getUniqueId(), Pair.of(updatedBalance, LocalDateTime.now()));
+ return updatedBalance;
+ } else {
+ return balance;
+ }
+ } else {
+ double balance = CodexEngine.get().getVault() != null ? CodexEngine.get().getVault().getBalance(player) : 0.0;
+ storedBalances.put(player.getUniqueId(), Pair.of(balance, LocalDateTime.now()));
+ return balance;
+ }
+ }
+
+ public static void startMoneyUpdateTask() {
+ // Schedule a repeating task to update all stored balances every thresholdSeconds
+ task = Bukkit.getScheduler().runTaskTimerAsynchronously(CodexEngine.get(), () -> {
+ for (UUID uuid : storedBalances.keySet()) {
+ Player player = Bukkit.getPlayer(uuid);
+ if (player != null && player.isOnline()) {
+ getBalanceAsync(player, balance -> {
+ storedBalances.put(uuid, Pair.of(balance, LocalDateTime.now()));
+ });
+ }
+ }
+ }, thresholdSeconds * 20L, thresholdSeconds * 20L);
+ }
+
+ public static void cancelMoneyUpdateTask() {
+ if (task != null) {
+ task.cancel();
+ task = null;
+ }
+ }
+
+ private static void getBalanceAsync(Player player, Consumer moneyConsumer) {
+ Bukkit.getScheduler().runTaskAsynchronously(CodexEngine.get(), () -> {
+ double balance = CodexEngine.get().getVault() != null ? CodexEngine.get().getVault().getBalance(player) : 0.0;
+ moneyConsumer.accept(balance);
+ });
+ }
+}
diff --git a/src/main/resources/lang/CraftingRequirements.yml b/src/main/resources/lang/CraftingRequirements.yml
index d19c298..8ef2529 100644
--- a/src/main/resources/lang/CraftingRequirements.yml
+++ b/src/main/resources/lang/CraftingRequirements.yml
@@ -4,6 +4,8 @@ unfulfilled: "&c&l✗&r"
recipes:
# The line that is displayed above the crafting requirements
requirementLine: "&7Crafting Requirements"
+ # The line that is displayed above the crafting conditions
+ conditionLine: "&7Crafting Conditions"
# The bossbar that will be shown when manual crafting is enabled
bossbar: '&5Crafting $- ...'
# The message that is displayed when the player has learned the recipe
@@ -66,6 +68,8 @@ recipes:
professions:
# The line that is displayed above the crafting requirements
requirementLine: "&7Crafting Requirements"
+ # The line that is displayed above the crafting conditions
+ conditionLine: "&7Crafting Conditions"
# The message that is displayed when the player has unlocked the profession
learned:
'true': "&aYou have unlocked this profession"
diff --git a/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java b/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java
index 8d586c1..0503766 100644
--- a/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java
+++ b/src/test/java/studio/magemonkey/fusion/commands/ForceCommandsTest.java
@@ -2,7 +2,8 @@
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.fail;
/**
* Simple test to verify force command methods exist and have the expected signatures.
@@ -16,31 +17,31 @@ public void testForceCommandMethodsExist() {
// Verify that all force command methods exist as public static methods
try {
// Check forceJoinProfession method exists
- CommandMechanics.class.getDeclaredMethod("forceJoinProfession",
- org.bukkit.command.CommandSender.class, String[].class);
-
+ CommandMechanics.class.getDeclaredMethod("forceJoinProfession",
+ org.bukkit.command.CommandSender.class, String[].class);
+
// Check forceLeaveProfession method exists
- CommandMechanics.class.getDeclaredMethod("forceLeaveProfession",
- org.bukkit.command.CommandSender.class, String[].class);
-
+ CommandMechanics.class.getDeclaredMethod("forceLeaveProfession",
+ org.bukkit.command.CommandSender.class, String[].class);
+
// Check forceStats method exists
- CommandMechanics.class.getDeclaredMethod("forceStats",
- org.bukkit.command.CommandSender.class, String[].class);
-
+ CommandMechanics.class.getDeclaredMethod("forceStats",
+ org.bukkit.command.CommandSender.class, String[].class);
+
// Check forceMaster method exists
- CommandMechanics.class.getDeclaredMethod("forceMaster",
- org.bukkit.command.CommandSender.class, String[].class);
-
+ CommandMechanics.class.getDeclaredMethod("forceMaster",
+ org.bukkit.command.CommandSender.class, String[].class);
+
// Check forceShow method exists
- CommandMechanics.class.getDeclaredMethod("forceShow",
- org.bukkit.command.CommandSender.class, String[].class);
-
+ CommandMechanics.class.getDeclaredMethod("forceShow",
+ org.bukkit.command.CommandSender.class, String[].class);
+
} catch (NoSuchMethodException e) {
fail("Force command method not found: " + e.getMessage());
}
}
- @Test
+ @Test
public void testCommandArgumentValidation() {
// This test documents expected argument patterns for force commands
// forcejoin = 3 args
@@ -48,7 +49,7 @@ public void testCommandArgumentValidation() {
// forcestats = 2 args
// forcemaster = 3 args
// forceshow = 2 args
-
+
// Test would verify argument length checking but requires mocking the full environment
assertTrue(true, "Force commands require 2-3 arguments as documented");
}