소스 검색

Profile and Sacks, Production ready

Jan 1 주 전
부모
커밋
84ea943142
31개의 변경된 파일861개의 추가작업 그리고 588개의 파일을 삭제
  1. 1 6
      pom.xml
  2. 2 6
      src/main/java/me/lethunderhawk/custom/item/CustomItemModule.java
  3. 2 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityContext.java
  4. 16 9
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityDefinition.java
  5. 2 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityTrigger.java
  6. 31 16
      src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinitionLoader.java
  7. 0 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/handling/AbilityHandler.java
  8. 7 0
      src/main/java/me/lethunderhawk/custom/item/abstraction/handling/ResolvedParams.java
  9. 2 2
      src/main/java/me/lethunderhawk/custom/item/abstraction/runtime/AbilityDispatchService.java
  10. 48 15
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/SackAbility.java
  11. 14 1
      src/main/java/me/lethunderhawk/custom/item/listener/CustomItemListener.java
  12. 117 22
      src/main/java/me/lethunderhawk/custom/recipes/CraftingTableRecipe.java
  13. 8 1
      src/main/java/me/lethunderhawk/custom/recipes/CustomRecipe.java
  14. 43 17
      src/main/java/me/lethunderhawk/custom/recipes/CustomRecipeModule.java
  15. 85 19
      src/main/java/me/lethunderhawk/custom/recipes/RecipeManager.java
  16. 29 4
      src/main/java/me/lethunderhawk/custom/recipes/abstraction/AbstractRecipe.java
  17. 6 1
      src/main/java/me/lethunderhawk/custom/recipes/command/RecipeEditorCommand.java
  18. 42 17
      src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeBrowserGUI.java
  19. 68 30
      src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeEditorGUI.java
  20. 19 23
      src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeViewerGUI.java
  21. 22 0
      src/main/java/me/lethunderhawk/custom/recipes/serialization/RecipeSerializer.java
  22. 17 9
      src/main/java/me/lethunderhawk/profile/ProfileModule.java
  23. 3 18
      src/main/java/me/lethunderhawk/profile/command/ProfileCommand.java
  24. 13 1
      src/main/java/me/lethunderhawk/profile/data/sacks/Sack.java
  25. 0 10
      src/main/java/me/lethunderhawk/profile/data/sacks/foraging/ForagingSack.java
  26. 0 203
      src/main/java/me/lethunderhawk/profile/data/sacks/foraging/ForagingSackGUI.java
  27. 258 0
      src/main/java/me/lethunderhawk/profile/data/sacks/gui/SackGUI.java
  28. 0 10
      src/main/java/me/lethunderhawk/profile/data/sacks/mining/MiningSack.java
  29. 0 143
      src/main/java/me/lethunderhawk/profile/data/sacks/mining/MiningSackGUI.java
  30. 1 1
      src/main/java/me/lethunderhawk/profile/gui/StatsGUI.java
  31. 5 1
      src/main/resources/plugin.yml

+ 1 - 6
pom.xml

@@ -96,11 +96,6 @@
             <version>2.11.7</version>
             <scope>provided</scope>
         </dependency>
-        <dependency>
-            <groupId>net.dmulloy2</groupId>
-            <artifactId>ProtocolLib</artifactId>
-            <version>5.4.0</version>
-        </dependency>
         <dependency>
             <groupId>com.sk89q.worldedit</groupId>
             <artifactId>worldedit-bukkit</artifactId>
@@ -110,7 +105,7 @@
         <dependency>
             <groupId>me.lethunderhawk</groupId>
             <artifactId>FluxAPI</artifactId>
-            <version>1.2.3</version>
+            <version>1.2.4</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>

+ 2 - 6
src/main/java/me/lethunderhawk/custom/item/CustomItemModule.java

@@ -14,7 +14,6 @@ import me.lethunderhawk.custom.item.listener.CustomItemListener;
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.main.BazaarFlux;
-import me.lethunderhawk.profile.data.sacks.SackType;
 import org.bukkit.command.CommandSender;
 import org.bukkit.event.HandlerList;
 import org.bukkit.plugin.java.JavaPlugin;
@@ -36,8 +35,7 @@ public class CustomItemModule extends FluxAPIModule {
         abilityRegistry.register("claim_tool", new ClaimToolAbility());
         abilityRegistry.register("hyperion", new HyperionAbility());
         abilityRegistry.register("regenerating_block", new RegeneratingBlockAbility());
-        abilityRegistry.register("mining_sack", new SackAbility(SackType.MINING));
-        abilityRegistry.register("foraging_sack", new SackAbility(SackType.FORAGING));
+        abilityRegistry.register("sack", new SackAbility());
     }
 
     @Override
@@ -58,9 +56,7 @@ public class CustomItemModule extends FluxAPIModule {
 
         // --- Load definitions from disk ---
         Map<String, ItemDefinition> definitions = loader.loadAll(itemsDir);
-
-        CustomItemRegistry itemRegistry = new CustomItemRegistry()
-                .fromRegistry(definitions);
+        CustomItemRegistry itemRegistry = new CustomItemRegistry().fromRegistry(definitions);
 
         // --- Reload listener ---
 

+ 2 - 1
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityContext.java

@@ -7,5 +7,6 @@ import org.bukkit.event.Event;
 public record AbilityContext(
         Player player,
         ItemInstance itemInstance,
-        Event event
+        Event event,
+        AbilityTrigger trigger
 ) {}

+ 16 - 9
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityDefinition.java

@@ -8,34 +8,40 @@ import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 public class AbilityDefinition implements Serializable {
 
-    private final AbilityTrigger trigger;
+    private final Set<AbilityTrigger> triggers;
     private final String handlerId;
     private final Map<String, String> params;
     private final String name;
     private final String description;
 
     public AbilityDefinition(
-            AbilityTrigger trigger,
+            Set<AbilityTrigger> triggers,
             String handlerId,
             String name,
             String description,
             Map<String, String> params
     ) {
-        this.trigger = trigger;
+        this.triggers = triggers;
         this.handlerId = handlerId;
+        this.name = name;
         this.description = description;
         this.params = Map.copyOf(params);
-        this.name = name;
     }
-    public String getName(){
-        return this.name;
+
+    public boolean matchesTrigger(AbilityTrigger trigger) {
+        return triggers.stream().anyMatch(trigger::isSubTypeOf);
     }
 
-    public AbilityTrigger trigger() {
-        return trigger;
+    public Map<String,String> params(){
+        return params;
+    }
+
+    public String getName(){
+        return this.name;
     }
 
     public List<Component> getLore(String firstLine) {
@@ -71,9 +77,10 @@ public class AbilityDefinition implements Serializable {
 
     public List<Component> renderLore() {
         List<Component> lore = new ArrayList<>();
-        String abilityName = "Ability: " + getName() + " [" + trigger().getDisplayName() + "]";
+        String abilityName = "Ability: " + getName() + " [" + ((AbilityTrigger) triggers.toArray()[0]).getDisplayName() + "]";
         lore.add(Component.text(abilityName, NamedTextColor.GOLD));
         lore.addAll(getLore(abilityName));
+
         return lore;
     }
 }

+ 2 - 1
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityTrigger.java

@@ -14,7 +14,8 @@ public enum AbilityTrigger {
 
     ON_HIT("ON HIT", null),
     ON_INTERACT("ON INTERACT", null),
-    ON_SNEAK("ON SNEAK", null);
+    ON_SNEAK("ON SNEAK", null),
+    ON_PICKUP("ON ITEM PICKUP", null);
 
     private final String displayName;
     private final AbilityTrigger parent;

+ 31 - 16
src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinitionLoader.java

@@ -127,23 +127,38 @@ public final class ItemDefinitionLoader {
                     );
                 }
 
-                String triggerRaw =
-                        abilityCfg.getString("trigger");
+                Set<AbilityTrigger> triggers = new HashSet<>();
+                List<String> triggerList = abilityCfg.getStringList("triggers");
+
+                if (!triggerList.isEmpty()) {
+
+                    for (String raw : triggerList) {
+                        try {
+                            triggers.add(AbilityTrigger.valueOf(raw.toUpperCase()));
+                        } catch (IllegalArgumentException ex) {
+                            throw new IllegalStateException(
+                                    "Invalid ability trigger '" + raw + "' in item " + id
+                            );
+                        }
+                    }
+                } else {
 
-                if (triggerRaw == null) {
-                    throw new IllegalStateException(
-                            "Missing ability trigger in item " + id
-                    );
-                }
+                    // --- Legacy format: trigger: A
+                    String singleTrigger = abilityCfg.getString("trigger");
 
-                AbilityTrigger trigger;
-                try {
-                    trigger = AbilityTrigger.valueOf(triggerRaw.toUpperCase());
-                } catch (IllegalArgumentException ex) {
-                    throw new IllegalStateException(
-                            "Invalid ability trigger '" + triggerRaw +
-                                    "' in item " + id
-                    );
+                    if (singleTrigger == null) {
+                        throw new IllegalStateException(
+                                "Missing ability trigger(s) in item " + id
+                        );
+                    }
+
+                    try {
+                        triggers.add(AbilityTrigger.valueOf(singleTrigger.toUpperCase()));
+                    } catch (IllegalArgumentException ex) {
+                        throw new IllegalStateException(
+                                "Invalid ability trigger '" + singleTrigger + "' in item " + id
+                        );
+                    }
                 }
 
                 String handler_id =
@@ -181,7 +196,7 @@ public final class ItemDefinitionLoader {
                 }
 
                 abilities.add(new AbilityDefinition(
-                        trigger,
+                        triggers,
                         handler_id,
                         abilityName,
                         abilityDescription,

+ 0 - 1
src/main/java/me/lethunderhawk/custom/item/abstraction/handling/AbilityHandler.java

@@ -3,7 +3,6 @@ package me.lethunderhawk.custom.item.abstraction.handling;
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
 
 public interface AbilityHandler {
-
     void execute(
             AbilityContext context,
             ResolvedParams params

+ 7 - 0
src/main/java/me/lethunderhawk/custom/item/abstraction/handling/ResolvedParams.java

@@ -139,5 +139,12 @@ public final class ResolvedParams {
         return defaultValue;
     }
 
+    public String getString(String key) {
+        Object val = values.get(key);
+        if (val instanceof String str) {
+            return str;
+        }
+        throw new IllegalArgumentException("Value for key '" + key + "' is not a String");
+    }
 }
 

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/abstraction/runtime/AbilityDispatchService.java

@@ -39,7 +39,7 @@ public final class AbilityDispatchService {
         ItemDefinition def = itemRegistry.get(instance.itemId());
 
         for (AbilityDefinition ability : def.abilities()) {
-            if (trigger.isSubTypeOf(ability.trigger())) {
+            if (ability.matchesTrigger(trigger)) {
                 String cooldownKey = player.getUniqueId() + ":" + ability.handlerId();
 
                 long lastUsed = cooldowns.getOrDefault(cooldownKey, 0L);
@@ -54,7 +54,7 @@ public final class AbilityDispatchService {
 
                 AbilityHandler handler = abilityRegistry.get(ability.handlerId());
                 handler.execute(
-                        new AbilityContext(player, instance, event),
+                        new AbilityContext(player, instance, event, trigger),
                         ResolvedParams.resolve(ability, def, instance)
                 );
 

+ 48 - 15
src/main/java/me/lethunderhawk/custom/item/concrete/ability/SackAbility.java

@@ -1,32 +1,65 @@
 package me.lethunderhawk.custom.item.concrete.ability;
 
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
+import me.lethunderhawk.custom.item.abstraction.ability.AbilityTrigger;
 import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
-import me.lethunderhawk.fluxapi.util.gui.InventoryManager;
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.profile.ProfileManager;
 import me.lethunderhawk.profile.data.sacks.Sack;
 import me.lethunderhawk.profile.data.sacks.SackType;
-import me.lethunderhawk.profile.data.sacks.foraging.ForagingSackGUI;
-import me.lethunderhawk.profile.data.sacks.mining.MiningSackGUI;
+import me.lethunderhawk.profile.data.sacks.gui.SackGUI;
+import org.bukkit.Sound;
 import org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerAttemptPickupItemEvent;
 import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.inventory.ItemStack;
 
 public class SackAbility implements AbilityHandler {
-    private final SackType type;
-    public SackAbility(SackType sackType) {
-        this.type = sackType;
-    }
 
     @Override
     public void execute(AbilityContext context, ResolvedParams params) {
-        Player p = context.player();
-        PlayerInteractEvent event = ((PlayerInteractEvent) context.event());
-        event.setCancelled(true);
-        if(Sack.getRegisteredType(type) != null){
-            if(type.equals(SackType.MINING)){
-                InventoryManager.openFor(p, new MiningSackGUI(p));
-            }else if(type.equals(SackType.FORAGING)){
-                InventoryManager.openFor(p, new ForagingSackGUI(p, 0));
+
+        Player player = context.player();
+
+        String profileId = params.getString("profile_id");
+        SackType type = SackType.valueOf(params.getString("sack_type"));
+
+        ProfileManager profileManager = FluxService.get(ProfileManager.class);
+
+        Sack sack = profileManager
+                .getSubProfile(player.getUniqueId(), profileId, Sack.class);
+
+        if (context.trigger().isSubTypeOf(AbilityTrigger.RIGHT_CLICK)) {
+
+            PlayerInteractEvent event = (PlayerInteractEvent) context.event();
+            event.setCancelled(true);
+            player.playSound(player.getLocation(), Sound.ITEM_BUNDLE_DROP_CONTENTS, 1f, 1f);
+
+            new SackGUI(player, sack).openWithListener(player);
+        }
+
+        if (context.trigger().isSubTypeOf(AbilityTrigger.ON_PICKUP) &&
+                context.event() instanceof PlayerAttemptPickupItemEvent pickupEvent) {
+
+            if(pickupEvent.isCancelled()) return;
+            ItemStack stack = pickupEvent.getItem().getItemStack();
+
+            if (!Sack.getRegisteredTypes()
+                    .get(type)
+                    .containsKey(stack.getType())) {
+
+                return;
+            }
+
+            pickupEvent.setCancelled(true);
+
+            int remaining = sack.addItemStack(stack);
+            player.playSound(player.getLocation(), Sound.ITEM_BUNDLE_INSERT, 1f, 1f);
+            if (remaining <= 0) {
+                pickupEvent.getItem().remove();
+            } else {
+                stack.setAmount(remaining);
             }
         }
     }

+ 14 - 1
src/main/java/me/lethunderhawk/custom/item/listener/CustomItemListener.java

@@ -6,8 +6,10 @@ import me.lethunderhawk.custom.item.abstraction.runtime.AbilityDispatchService;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerAttemptPickupItemEvent;
 import org.bukkit.event.player.PlayerInteractEvent;
 import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.inventory.ItemStack;
 
 /**
  * Handles events for custom items
@@ -24,7 +26,18 @@ public class CustomItemListener implements Listener {
         Player player = event.getPlayer();
         new MigrationService().migratePlayer(player);
     }
-
+    @EventHandler
+    public void onItemPickUp(PlayerAttemptPickupItemEvent event) {
+        Player player = event.getPlayer();
+        for (ItemStack stack : player.getInventory().getContents()) {
+            dispatchService.dispatch(
+                    player,
+                    stack,
+                    AbilityTrigger.ON_PICKUP,
+                    event
+            );
+        }
+    }
 
     @EventHandler
     public void onAction(PlayerInteractEvent event) {

+ 117 - 22
src/main/java/me/lethunderhawk/custom/recipes/CraftingTableRecipe.java

@@ -6,66 +6,161 @@ import org.bukkit.inventory.CraftingInventory;
 import org.bukkit.inventory.Inventory;
 import org.bukkit.inventory.ItemStack;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Custom 3×3 crafting recipe.
+ *
+ * <p>The class stores a **copy** of the required matrix so the recipe
+ * can never be mutated by the crafting inventory. All ingredient
+ * consumption happens on the {@link CraftingInventory} that is passed
+ * to {@link #craft(Inventory)}.
+ */
 public class CraftingTableRecipe extends AbstractRecipe {
 
-    private final ItemStack[] matrix;
+    /** Expected ingredient layout (size 9). Each entry is a *clone* of the
+     *  ItemStack supplied by the plugin author – the recipe never mutates
+     *  these objects. */
+    private ItemStack[] matrix;
 
     public CraftingTableRecipe(NamespacedKey key, ItemStack[] matrix, ItemStack result) {
         super(key, result, RecipeType.CRAFTING);
-        this.matrix = matrix;
+        // Defensive copy – we never want to modify the original array or its
+        // ItemStack objects.
+        this.matrix = new ItemStack[matrix.length];
+        for (int i = 0; i < matrix.length; i++) {
+            ItemStack src = matrix[i];
+            this.matrix[i] = (src == null) ? null : src.clone();
+        }
     }
 
     @Override
     public boolean matches(Inventory inventory) {
-
         if (!(inventory instanceof CraftingInventory crafting))
             return false;
 
-        ItemStack[] invMatrix = crafting.getMatrix();
-
+        ItemStack[] invMatrix = crafting.getMatrix(); // copy of the 3×3 grid
         for (int i = 0; i < matrix.length; i++) {
-
             ItemStack expected = matrix[i];
             ItemStack actual = invMatrix[i];
 
             if (expected == null && actual == null) continue;
-
             if (expected == null || actual == null) return false;
-
-            if (expected.getType() != actual.getType()) return false;
-
+            if (!actual.isSimilar(expected)) return false;
             if (actual.getAmount() < expected.getAmount()) return false;
         }
-
         return true;
     }
 
+    /**
+     * Consumes the required items from the crafting inventory and returns
+     * a fresh copy of the result.
+     *
+     * <p>The method is deliberately defensive – it validates the inventory
+     * type, never touches the recipe’s own {@code matrix}, and writes the
+     * updated matrix back to the {@link CraftingInventory}.
+     */
     @Override
     public ItemStack craft(Inventory inventory) {
+        if (!(inventory instanceof CraftingInventory crafting))
+            throw new IllegalArgumentException("Inventory must be a CraftingInventory");
 
-        CraftingInventory crafting = (CraftingInventory) inventory;
+        // Clone the result so the original stored in the recipe stays untouched.
+        ItemStack resultStack = result.clone();
+
+        // Work on a mutable copy of the current crafting grid.
         ItemStack[] invMatrix = crafting.getMatrix();
 
+        // Consume each ingredient slot.
         for (int i = 0; i < matrix.length; i++) {
+            ItemStack required = matrix[i];
+            if (required == null) continue; // empty slot in the recipe
+
+            ItemStack current = invMatrix[i];
+            if (current == null) continue; // should never happen after matches()
+
+            int remaining = current.getAmount() - required.getAmount();
+            if (remaining > 0) {
+                current.setAmount(remaining);
+                invMatrix[i] = current;
+            } else {
+                // Slot emptied – set to null so the UI clears it.
+                invMatrix[i] = null;
+            }
+        }
 
-            ItemStack expected = matrix[i];
-            ItemStack actual = invMatrix[i];
+        // Write the updated matrix back to the crafting inventory.
+        crafting.setMatrix(invMatrix);
+        return resultStack;
+    }
 
-            if (expected == null || actual == null) continue;
+    @Override
+    public void setResult(ItemStack result) {
+        this.result = result;
+    }
 
-            actual.setAmount(actual.getAmount() - expected.getAmount());
+    @Override
+    public void setCraftMats(ItemStack[] matrix) {
+        this.matrix = matrix;
+    }
 
-            if (actual.getAmount() <= 0)
-                invMatrix[i] = null;
+    /** Exposes the *template* matrix for external helpers (e.g. max‑craft calculation). */
+    public ItemStack[] getTemplateMatrix() {
+        // Return a defensive copy – callers must not modify the internal array.
+        ItemStack[] copy = new ItemStack[matrix.length];
+        for (int i = 0; i < matrix.length; i++) {
+            copy[i] = (matrix[i] == null) ? null : matrix[i].clone();
         }
+        return copy;
+    }
 
-        crafting.setMatrix(invMatrix);
+    public int getMaxCrafts(CraftingInventory inv) {
+
+        int max = Integer.MAX_VALUE;
+        ItemStack[] invMatrix = inv.getMatrix();
+
+        for (int i = 0; i < matrix.length; i++) {
+
+            ItemStack expected = matrix[i];
+            if (expected == null) continue;
+
+            ItemStack actual = invMatrix[i];
+            if (actual == null) return 0;
+
+            int crafts = actual.getAmount() / expected.getAmount();
+            max = Math.min(max, crafts);
+        }
 
-        return result.clone();
+        return max;
     }
+    @Override
+    public Map<String, Object> serialize() {
+
+        Map<String, Object> map = super.serialize();
 
-    public ItemStack[] getMatrix() {
-        return matrix;
+        List<ItemStack> matrixList = new ArrayList<>(Arrays.asList(matrix));
+
+        map.put("matrix", matrixList);
+
+        return map;
     }
+    public static CraftingTableRecipe deserialize(Map<String, Object> map) {
+
+        NamespacedKey key = NamespacedKey.fromString((String) map.get("key"));
 
+        ItemStack result = (ItemStack) map.get("result");
+
+        List<ItemStack> matrixList = (List<ItemStack>) map.get("matrix");
+
+        ItemStack[] matrix = new ItemStack[9];
+
+        for (int i = 0; i < matrixList.size(); i++) {
+            matrix[i] = matrixList.get(i);
+        }
+
+        return new CraftingTableRecipe(key, matrix, result);
+    }
 }

+ 8 - 1
src/main/java/me/lethunderhawk/custom/recipes/CustomRecipe.java

@@ -1,10 +1,11 @@
 package me.lethunderhawk.custom.recipes;
 
 import org.bukkit.NamespacedKey;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
 import org.bukkit.inventory.Inventory;
 import org.bukkit.inventory.ItemStack;
 
-public interface CustomRecipe {
+public interface CustomRecipe extends ConfigurationSerializable {
 
     NamespacedKey getKey();
 
@@ -15,4 +16,10 @@ public interface CustomRecipe {
     ItemStack craft(Inventory inventory);
 
     RecipeType getRecipeType();
+
+    void setResult(ItemStack result);
+
+    void setCraftMats(ItemStack[] matrix);
+
+    void setNameSpace(NamespacedKey namespacedKey);
 }

+ 43 - 17
src/main/java/me/lethunderhawk/custom/recipes/CustomRecipeModule.java

@@ -3,10 +3,11 @@ package me.lethunderhawk.custom.recipes;
 import me.lethunderhawk.custom.recipes.command.RecipeEditorCommand;
 import me.lethunderhawk.custom.recipes.enchantedVariant.EnchantedVariantRecipeManager;
 import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.util.config.ConfigLoader;
 import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
-import me.lethunderhawk.main.BazaarFlux;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
 import org.bukkit.event.HandlerList;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.plugin.java.JavaPlugin;
@@ -26,29 +27,51 @@ public class CustomRecipeModule extends FluxAPIModule {
 
     @Override
     public void onEnable() {
-        this.enchantedVariantRecipeManager = new EnchantedVariantRecipeManager(FluxService.get(BazaarFlux.class));
+        ConfigurationSerialization.registerClass(CraftingTableRecipe.class);
+
         this.recipeManager = new RecipeManager(plugin);
+
         FluxService.register(RecipeManager.class, recipeManager);
-        addTestRecipes();
 
-        plugin.getServer().getPluginManager().registerEvents(this.enchantedVariantRecipeManager, this.plugin);
-        plugin.getServer().getPluginManager().registerEvents(this.recipeManager, this.plugin);
+        recipeManager.loadRecipes(FluxService.get(ConfigLoader.class));
+
+        //addTestRecipes();
+        //registerEnchantedVariantCrafts();
+
+        plugin.getServer().getPluginManager().registerEvents(recipeManager, plugin);
+
         RecipeEditorCommand command = new RecipeEditorCommand(this);
         plugin.getCommand("customrecipe").setExecutor(command);
         plugin.getCommand("customrecipe").setExecutor(command);
     }
 
+    private void registerEnchantedVariantCrafts() {
+        ItemStack[] matrix = new ItemStack[9];
+        ItemStack baseMaterial = new ItemStack(Material.COBBLESTONE);
+        baseMaterial.setAmount(32);
+        matrix[0] = baseMaterial;
+        matrix[1] = baseMaterial;
+        matrix[2] = baseMaterial;
+        matrix[3] = baseMaterial;
+        matrix[4] = baseMaterial;
+
+        recipeManager.registerRecipe(
+                new CraftingTableRecipe(new NamespacedKey(plugin, "enchanted_cobblestone"),
+                        matrix, new ItemStack(Material.RED_CONCRETE))
+        );
+    }
+
     private void addTestRecipes() {
         ItemStack[] matrix = new ItemStack[9];
-        matrix[0] = new ItemStack(Material.STICK);
-        matrix[1] = new ItemStack(Material.STICK);
-        matrix[2] = new ItemStack(Material.STICK);
-        matrix[3] = new ItemStack(Material.STICK);
-        matrix[4] = new ItemStack(Material.STICK);
-        matrix[5] = new ItemStack(Material.STICK);
-        matrix[6] = new ItemStack(Material.STICK);
-        matrix[7] = new ItemStack(Material.STICK);
-        matrix[8] = new ItemStack(Material.STICK);
+        matrix[0] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[1] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[2] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[3] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[4] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[5] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[6] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[7] = new ItemStack(Material.WHITE_CONCRETE);
+        matrix[8] = new ItemStack(Material.WHITE_CONCRETE);
 
         recipeManager.registerRecipe(
                 new CraftingTableRecipe(new NamespacedKey(plugin, "test_recipe"),
@@ -58,9 +81,12 @@ public class CustomRecipeModule extends FluxAPIModule {
 
     @Override
     public void onDisable() {
-        if(this.enchantedVariantRecipeManager != null && this.recipeManager != null) {
-            HandlerList.unregisterAll(this.enchantedVariantRecipeManager);
-            HandlerList.unregisterAll(this.recipeManager);
+
+        if(recipeManager != null) {
+
+            recipeManager.saveRecipes(FluxService.get(ConfigLoader.class));
+
+            HandlerList.unregisterAll(recipeManager);
         }
     }
 }

+ 85 - 19
src/main/java/me/lethunderhawk/custom/recipes/RecipeManager.java

@@ -1,16 +1,24 @@
 package me.lethunderhawk.custom.recipes;
 
 import me.lethunderhawk.custom.recipes.abstraction.AbstractRecipe;
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.util.config.ConfigLoader;
+import me.lethunderhawk.main.BazaarFlux;
 import org.bukkit.Bukkit;
+import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
-import org.bukkit.event.inventory.CraftItemEvent;
+import org.bukkit.event.inventory.InventoryClickEvent;
 import org.bukkit.event.inventory.PrepareItemCraftEvent;
 import org.bukkit.inventory.CraftingInventory;
 import org.bukkit.inventory.ItemStack;
-import org.bukkit.plugin.Plugin;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -19,11 +27,10 @@ import java.util.Map;
 
 public class RecipeManager implements Listener {
 
-    private final Plugin plugin;
-
+    private final JavaPlugin plugin;
     private final Map<NamespacedKey, CustomRecipe> recipes = new HashMap<>();
 
-    public RecipeManager(Plugin plugin) {
+    public RecipeManager(JavaPlugin plugin) {
         this.plugin = plugin;
         Bukkit.getPluginManager().registerEvents(this, plugin);
     }
@@ -32,6 +39,10 @@ public class RecipeManager implements Listener {
         recipes.put(recipe.getKey(), recipe);
     }
 
+    public static CustomRecipe createDummyCraftingRecipe(@NotNull String namespace){
+        return new CraftingTableRecipe(new NamespacedKey(FluxService.get(BazaarFlux.class), namespace), new ItemStack[9], new ItemStack(Material.GREEN_CONCRETE));
+    }
+
     public void unregisterRecipe(NamespacedKey key) {
         recipes.remove(key);
     }
@@ -45,6 +56,10 @@ public class RecipeManager implements Listener {
                 .filter(r -> r instanceof AbstractRecipe && r.getRecipeType() == type)
                 .toList();
     }
+
+    /* --------------------------------------------------------------------- */
+    /* 1️⃣ Prepare the crafting result – identical to your original version.   */
+    /* --------------------------------------------------------------------- */
     @EventHandler
     public void onPrepareCraft(PrepareItemCraftEvent event) {
 
@@ -53,37 +68,88 @@ public class RecipeManager implements Listener {
         for (CustomRecipe recipe : recipes.values()) {
 
             if (recipe.matches(inv)) {
-
-                inv.setResult(recipe.getResult());
+                inv.setResult(recipe.getResult().clone());
                 return;
-
             }
 
         }
 
-        inv.setResult(null);
+        // DO NOTHING
     }
+
+    /* --------------------------------------------------------------------- */
+    /* 2️⃣ Handle the actual crafting (normal click OR shift‑click).           */
+    /* --------------------------------------------------------------------- */
     @EventHandler
-    public void onCraft(CraftItemEvent event) {
+    public void onResultClick(InventoryClickEvent event) {
 
-        CraftingInventory inv = event.getInventory();
+        if (!(event.getWhoClicked() instanceof Player player)) return;
+
+        if (!(event.getInventory() instanceof CraftingInventory inv)) return;
+
+        if (event.getClickedInventory() != event.getView().getTopInventory()) return;
+
+        if (event.getSlot() != 0) return;
 
         for (CustomRecipe recipe : recipes.values()) {
 
-            if (recipe.matches(inv)) {
+            if (!recipe.matches(inv)) continue;
 
-                event.setCancelled(true);
+            event.setCancelled(true);
 
-                ItemStack result = recipe.craft(inv);
+            ItemStack result = recipe.craft(inv);
 
-                Player player = (Player) event.getWhoClicked();
+            if(event.getCursor().isSimilar(result) && event.getCursor().getAmount() < 64) {
+                event.getCursor().add(result.getAmount());
+            }else if(event.getCursor().getType() == Material.AIR && !event.isShiftClick()) {
+                event.setCursor(result);
+            }else{
                 player.getInventory().addItem(result);
+            }
 
-                player.updateInventory();
 
-                return;
-            }
+            player.updateInventory();
+            return;
+        }
+    }
+    public void saveRecipes(ConfigLoader loader) {
 
+        YamlConfiguration cfg = new YamlConfiguration();
+
+        ConfigurationSection section = cfg.createSection("recipes");
+
+        for (CustomRecipe recipe : recipes.values()) {
+            section.set(recipe.getKey().toString(), recipe);
+        }
+
+        loader.saveConfig(cfg, "recipes");
+    }
+    public void loadRecipes(ConfigLoader loader) {
+
+        YamlConfiguration cfg = loader.loadConfig("recipes");
+
+        ConfigurationSection section = cfg.getConfigurationSection("recipes");
+
+        if (section == null) {
+            section = cfg.createSection("recipes");
+            loader.saveConfig(cfg, "recipes");
+            return;
         }
+
+        recipes.clear();
+
+        for (String key : section.getKeys(false)) {
+
+            Object obj = section.get(key);
+
+            if (obj instanceof CustomRecipe recipe) {
+                recipes.put(recipe.getKey(), recipe);
+            }
+        }
+    }
+
+    public @Nullable CustomRecipe getRecipeByNameSpace(String s) {
+        return recipes.get(recipes.keySet().stream().filter(recipe -> recipe.getKey().equalsIgnoreCase(s))
+                .findFirst().orElse(null));
     }
-}
+}

+ 29 - 4
src/main/java/me/lethunderhawk/custom/recipes/abstraction/AbstractRecipe.java

@@ -5,17 +5,25 @@ import me.lethunderhawk.custom.recipes.RecipeType;
 import org.bukkit.NamespacedKey;
 import org.bukkit.inventory.ItemStack;
 
+import java.util.HashMap;
+import java.util.Map;
+
 public abstract class AbstractRecipe implements CustomRecipe {
 
-    protected final NamespacedKey key;
-    protected final ItemStack result;
-    protected final RecipeType type;
+    protected NamespacedKey key;
+    protected ItemStack result;
+    protected RecipeType type;
 
     public AbstractRecipe(NamespacedKey key, ItemStack result, RecipeType type) {
         this.key = key;
         this.result = result;
         this.type = type;
     }
+    public AbstractRecipe(Map<String, Object> map) {
+        this.key = (NamespacedKey) map.get("key");
+        this.result = (ItemStack) map.get("result");
+        this.type = (RecipeType) map.get("type");
+    }
 
     @Override
     public NamespacedKey getKey() {
@@ -24,10 +32,27 @@ public abstract class AbstractRecipe implements CustomRecipe {
 
     @Override
     public ItemStack getResult() {
-        return result.clone();
+        return result;
     }
+
     @Override
     public RecipeType getRecipeType() {
         return type;
     }
+    @Override
+    public void setNameSpace(NamespacedKey namespacedKey) {
+        this.key = namespacedKey;
+    }
+    @Override
+    public Map<String, Object> serialize() {
+
+        Map<String, Object> map = new HashMap<>();
+
+        map.put("key", key.toString());
+        map.put("type", type.name());
+        map.put("result", result);
+
+        return map;
+    }
+
 }

+ 6 - 1
src/main/java/me/lethunderhawk/custom/recipes/command/RecipeEditorCommand.java

@@ -21,10 +21,15 @@ public class RecipeEditorCommand extends CustomCommand {
     @Override
     public void createCommands() {
         registerCommand("gui", "displays a neat UI", this::showGUI);
+        registerCommand("restart", "Restart the module", this::reload);
+    }
+
+    private void reload(CommandSender sender, String[] strings) {
+        module.reload(sender, strings);
     }
 
     private void showGUI(CommandSender sender, String[] strings) {
-        if(sender.hasPermission("customrecipe.gui") && sender instanceof Player player) {
+        if(sender instanceof Player player) {
             InventoryManager.openFor(player, new RecipeBrowserGUI(player));
         }
     }

+ 42 - 17
src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeBrowserGUI.java

@@ -4,7 +4,10 @@ import me.lethunderhawk.custom.recipes.CustomRecipe;
 import me.lethunderhawk.custom.recipes.RecipeManager;
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
-import me.lethunderhawk.fluxapi.util.gui.InventoryManager;
+import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Material;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.meta.ItemMeta;
@@ -16,6 +19,7 @@ public class RecipeBrowserGUI extends InventoryGUI {
 
     private RecipeManager manager;
     private int page = 0;
+    private Player player;
 
     public RecipeBrowserGUI(Player player) {
         super("Recipe Browser", 54, player);
@@ -23,6 +27,7 @@ public class RecipeBrowserGUI extends InventoryGUI {
 
     @Override
     public void performAdditionalComputationOnPlayer(Player player) {
+        this.player = player;
         this.manager = FluxService.get(RecipeManager.class);
     }
 
@@ -36,37 +41,57 @@ public class RecipeBrowserGUI extends InventoryGUI {
         for (int i = 0; i < 28 && start + i < recipes.size(); i++) {
 
             CustomRecipe recipe = recipes.get(start + i);
-
-            ItemStack icon = recipe.getResult().clone();
+            ItemStack icon;
+            if(recipe.getResult() == null){
+                icon = new ItemStack(Material.BARRIER);
+            }else{
+                icon = recipe.getResult().clone();
+            }
             ItemMeta meta = icon.getItemMeta();
 
             meta.setDisplayName("§e" + recipe.getKey().getKey());
 
-            meta.setLore(List.of(
-                    "§7Type: " + recipe.getRecipeType(),
-                    "",
-                    "§eClick to view",
-                    "§eShift Click to edit"
-            ));
+            if(player.hasPermission("customrecipe.edit")){
+                meta.setLore(List.of(
+                        "§7Type: " + recipe.getRecipeType(),
+                        "",
+                        "§eClick to view",
+                        "§eRight click to edit"
+                ));
+            }else{
+                meta.setLore(List.of(
+                        "§7Type: " + recipe.getRecipeType(),
+                        "",
+                        "§eClick to view"
+                ));
+            }
+
 
             icon.setItemMeta(meta);
 
             setItemWithClickAction(10 + i + (i / 7) * 2, icon, (player, clickType) -> {
-
-                if (clickType.isShiftClick()) {
-                    InventoryManager.openFor(player, new RecipeEditorGUI(player, manager, recipe));
-                } else {
-                    InventoryManager.openFor(player, new RecipeViewerGUI(player, recipe));;
+                if (clickType.isLeftClick()) {
+                    openNext(player, new RecipeViewerGUI(player, recipe));
+                } else if(clickType.isRightClick() && player.hasPermission("customrecipe.edit")) {
+                    openNext(player, new RecipeEditorGUI(player, manager, recipe));
                 }
-
             });
 
         }
-
+        if(player.hasPermission("customrecipe.edit")) {
+            ItemStack addItem = new ItemOptions(Material.GOLD_INGOT)
+                    .setName(Component.text("Add a new recipe", NamedTextColor.YELLOW))
+                    .buildItemStack();
+            setItemWithClickAction(53, addItem, (player, clickType) -> {
+                CustomRecipe dummyRecipe = RecipeManager.createDummyCraftingRecipe("dummy_recipe");
+                FluxService.get(RecipeManager.class).registerRecipe(dummyRecipe);
+                openNext(player, new RecipeEditorGUI(player, manager, dummyRecipe));
+            });
+        }
     }
 
     @Override
     public void update() {
-
+        buildContent();
     }
 }

+ 68 - 30
src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeEditorGUI.java

@@ -5,67 +5,88 @@ import me.lethunderhawk.custom.recipes.CustomRecipe;
 import me.lethunderhawk.custom.recipes.RecipeManager;
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
+import me.lethunderhawk.fluxapi.util.gui.input.SignMenuFactory;
 import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
+import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
 import me.lethunderhawk.main.BazaarFlux;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Bukkit;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
 
-import java.util.UUID;
+import java.util.List;
 
-public class RecipeEditorGUI extends InventoryGUI {
+public class RecipeEditorGUI extends InventoryGUI implements InventoryGUI.AutoCloseHandler {
 
     private final RecipeManager manager;
     private final CustomRecipe editingRecipe;
 
-    public RecipeEditorGUI(Player player, RecipeManager manager, CustomRecipe recipe) {
+    public RecipeEditorGUI(Player player, RecipeManager manager, @NotNull CustomRecipe recipe) {
 
-        super("Recipe Editor", 45, player);
+        super("Recipe Editor", 54, player);
 
         this.manager = manager;
         this.editingRecipe = recipe;
-
     }
 
     private void loadRecipe() {
 
-        if (!(editingRecipe instanceof CraftingTableRecipe crafting))
+        if (!(editingRecipe instanceof CraftingTableRecipe crafting)){
+            Bukkit.getLogger().info("Recipe editor recipe was not loaded! Invalid recipe type!");
             return;
+        }
 
-        ItemStack[] matrix = crafting.getMatrix();
+        ItemStack[] matrix = crafting.getTemplateMatrix();
 
         int[] slots = {
                 10,11,12,
                 19,20,21,
                 28,29,30
         };
-
+        setEditable(slots);
         for (int i = 0; i < 9; i++) {
-
-            if (matrix[i] != null)
+            if (matrix[i] != null){
                 setItem(slots[i], matrix[i]);
+            }else{
+                setItem(slots[i], null);
+            }
         }
-
         setItem(25, crafting.getResult());
-
+        setEditable(25);
     }
 
     private void drawButtons() {
 
-        setItemWithClickAction(40, new ItemOptions(Material.GREEN_WOOL)
-                .buildItemStack(), (player, type) -> save(player));
-
-        setItemWithClickAction(41,
+        setItemWithClickAction(51,
+                new ItemOptions(Material.NAME_TAG)
+                        .setName(Component.text("Rename", NamedTextColor.GREEN))
+                        .setLore(LoreDesigner.createLore("<dark_gray>Rename the namespace of this configuration: <br><gray>" + editingRecipe.getKey().getKey(), "Save this configuration as-is.l", NamedTextColor.DARK_GRAY))
+                        .buildItemStack(),
+                (player, type) -> rename(player));
+
+        setItemWithClickAction(52,
+                new ItemOptions(Material.GREEN_WOOL)
+                        .setName(Component.text("Save", NamedTextColor.GREEN))
+                        .setLore(LoreDesigner.createLore("Save this configuration as-is", "Save this configuration as-is.l", NamedTextColor.DARK_GRAY))
+                        .buildItemStack(),
+                (player, type) -> save(player));
+
+        setItemWithClickAction(53,
                 new ItemOptions(Material.RED_WOOL)
-                        .buildItemStack(), (player, type) -> delete(player));
-
-        setCloseButton(42);
+                        .setName(Component.text("Delete", NamedTextColor.RED))
+                        .setLore(LoreDesigner.createLore("Delete this recipe.", "Delete this recipe. ad", NamedTextColor.DARK_GRAY))
+                        .buildItemStack(),
+                (player, type) -> delete(player));
 
+        setBackButton(48);
+        setCloseButton(49);
     }
 
     private void save(Player player) {
-
         ItemStack[] matrix = new ItemStack[9];
 
         int[] slots = {
@@ -75,31 +96,43 @@ public class RecipeEditorGUI extends InventoryGUI {
         };
 
         for (int i = 0; i < 9; i++) {
-
             matrix[i] = getInventory().getItem(slots[i]);
-
         }
 
         ItemStack result = getInventory().getItem(25);
 
-        NamespacedKey key = new NamespacedKey(FluxService.get(BazaarFlux.class),
-                "recipe_" + UUID.randomUUID());
-
-        CraftingTableRecipe recipe =
-                new CraftingTableRecipe(key, matrix, result);
+        editingRecipe.setCraftMats(matrix);
+        editingRecipe.setResult(result);
 
-        manager.registerRecipe(recipe);
+        manager.registerRecipe(editingRecipe);
 
         player.sendMessage("§aRecipe saved.");
-
+        openPrevious(player);
     }
 
     private void delete(Player player) {
         manager.unregisterRecipe(editingRecipe.getKey());
-
         player.sendMessage("§cRecipe deleted.");
+        openPrevious(player);
     }
 
+    private void rename(Player player){
+        save(player);
+        new SignMenuFactory(FluxService.get(BazaarFlux.class)).newMenu(List.of(
+            editingRecipe.getKey().getKey(),
+                "^^^^^^^^^^^^^^",
+                "Edit this line"
+        )).reopenIfFail(true).response((p, s) -> {
+            if(s[0].length() <= 3 || manager.getRecipeByNameSpace(s[0]) != null){
+                return false;
+            }
+            manager.unregisterRecipe(editingRecipe.getKey());
+            editingRecipe.setNameSpace(new NamespacedKey(FluxService.get(BazaarFlux.class), s[0]));
+            manager.registerRecipe(editingRecipe);
+            p.sendMessage(Component.text("Recipe namespace renamed to " + editingRecipe.getKey().getKey() + ".", NamedTextColor.GREEN));
+            return true;
+        }).open(player);
+    }
     @Override
     public void buildContent() {
         fillGlassPaneBackground();
@@ -111,4 +144,9 @@ public class RecipeEditorGUI extends InventoryGUI {
     public void update() {
 
     }
+
+    @Override
+    public void onClosedByPlayer(Player player) {
+        save(player);
+    }
 }

+ 19 - 23
src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeViewerGUI.java

@@ -7,14 +7,14 @@ import org.bukkit.Material;
 import org.bukkit.entity.Player;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.inventory.meta.ItemMeta;
+import org.jetbrains.annotations.NotNull;
 
 public class RecipeViewerGUI extends InventoryGUI {
 
     private final CustomRecipe recipe;
 
-    public RecipeViewerGUI(Player player, CustomRecipe recipe) {
-
-        super("Recipe Viewer", 45, player);
+    public RecipeViewerGUI(Player player, @NotNull CustomRecipe recipe) {
+        super("Recipe Viewer", 54, player);
         this.recipe = recipe;
     }
 
@@ -31,35 +31,31 @@ public class RecipeViewerGUI extends InventoryGUI {
 
     @Override
     public void buildContent() {
-
-    }
-
-    @Override
-    public void update() {
+        fillGlassPaneBackground();
         if (recipe instanceof CraftingTableRecipe crafting) {
 
-            ItemStack[] matrix = crafting.getMatrix();
-
-            int[] slots = {
-                    10,11,12,
-                    19,20,21,
-                    28,29,30
-            };
+            ItemStack[] matrix = crafting.getTemplateMatrix();
+            int[] slots = {10,11,12,19,20,21,28,29,30};
 
             for (int i = 0; i < 9; i++) {
-
                 if (matrix[i] != null) {
-
-                    setItem(slots[i], matrix[i]);
-
+                    setItem(slots[i], matrix[i].clone());
+                }else{
+                    setItem(slots[i], null);
                 }
-
             }
-
         }
-
         setItem(23, createArrow());
+        if(recipe.getResult() == null){
+            setItem(25, new ItemStack(Material.AIR));
+        }else setItem(25, recipe.getResult().clone());
+        setBackButton(48);
+        setCloseButton(49);
+
+    }
+
+    @Override
+    public void update() {
 
-        setItem(25, recipe.getResult());
     }
 }

+ 22 - 0
src/main/java/me/lethunderhawk/custom/recipes/serialization/RecipeSerializer.java

@@ -0,0 +1,22 @@
+package me.lethunderhawk.custom.recipes.serialization;
+
+import me.lethunderhawk.custom.recipes.CraftingTableRecipe;
+import me.lethunderhawk.custom.recipes.CustomRecipe;
+import me.lethunderhawk.custom.recipes.RecipeType;
+
+import java.util.Map;
+
+public class RecipeSerializer {
+
+    public static CustomRecipe deserialize(Map<String, Object> map) {
+
+        RecipeType type = RecipeType.valueOf((String) map.get("type"));
+
+        return switch (type) {
+
+            case CRAFTING -> CraftingTableRecipe.deserialize(map);
+
+            default -> throw new IllegalStateException("Unknown recipe type " + type);
+        };
+    }
+}

+ 17 - 9
src/main/java/me/lethunderhawk/profile/ProfileModule.java

@@ -6,8 +6,6 @@ import me.lethunderhawk.profile.command.ProfileCommand;
 import me.lethunderhawk.profile.data.sacks.Sack;
 import me.lethunderhawk.profile.data.sacks.SackContent;
 import me.lethunderhawk.profile.data.sacks.SackType;
-import me.lethunderhawk.profile.data.sacks.foraging.ForagingSack;
-import me.lethunderhawk.profile.data.sacks.mining.MiningSack;
 import org.bukkit.Material;
 import org.bukkit.configuration.serialization.ConfigurationSerialization;
 import org.bukkit.plugin.java.JavaPlugin;
@@ -30,14 +28,14 @@ public class ProfileModule extends FluxAPIModule {
 
         FluxProfile.registerSubProfile(
                 "mining_sack",
-                MiningSack.class,
-                uuid -> new MiningSack()
+                Sack.class,
+                uuid -> new Sack(SackType.MINING)
         );
 
         FluxProfile.registerSubProfile(
                 "foraging_sack",
-                ForagingSack.class,
-                uuid -> new ForagingSack()
+                Sack.class,
+                uuid -> new Sack(SackType.FORAGING)
         );
 
         ProfileCommand command = new ProfileCommand(this);
@@ -78,9 +76,19 @@ public class ProfileModule extends FluxAPIModule {
     }
 
     private void registerMiningSackTypes() {
-        Sack.registerType(SackType.MINING, Material.COBBLESTONE);
-        Sack.registerType(SackType.MINING, Material.STONE);
-        Sack.registerType(SackType.MINING, Material.GOLD_INGOT);
+        Sack.registerType(SackType.MINING, Material.COBBLESTONE, 1024);
+        Sack.registerType(SackType.MINING, Material.STONE, 1024);
+        Sack.registerType(SackType.MINING, Material.SANDSTONE);
+        Sack.registerType(SackType.MINING, Material.RED_SANDSTONE);
+        Sack.registerType(SackType.MINING, Material.BLACKSTONE);
+        Sack.registerType(SackType.MINING, Material.REDSTONE);
+        Sack.registerType(SackType.MINING, Material.RAW_COPPER);
+        Sack.registerType(SackType.MINING, Material.RAW_GOLD);
+        Sack.registerType(SackType.MINING, Material.RAW_IRON);
+        Sack.registerType(SackType.MINING, Material.EMERALD);
+        Sack.registerType(SackType.MINING, Material.DIAMOND, 128);
+        Sack.registerType(SackType.MINING, Material.QUARTZ);
+        Sack.registerType(SackType.MINING, Material.ANCIENT_DEBRIS, 16);
     }
 
     @Override

+ 3 - 18
src/main/java/me/lethunderhawk/profile/command/ProfileCommand.java

@@ -1,13 +1,9 @@
 package me.lethunderhawk.profile.command;
 
-import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.profile.ProfileManager;
-import me.lethunderhawk.fluxapi.profile.representation.FluxProfile;
 import me.lethunderhawk.fluxapi.util.command.CommandNode;
 import me.lethunderhawk.fluxapi.util.command.CustomCommand;
 import me.lethunderhawk.fluxapi.util.gui.InventoryManager;
 import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
-import me.lethunderhawk.profile.data.sacks.mining.MiningSackGUI;
 import me.lethunderhawk.profile.gui.StatsGUI;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
@@ -24,22 +20,11 @@ public class ProfileCommand extends CustomCommand {
 
     @Override
     public void createCommands() {
-        registerCommand("show", "shows your profile", this::showProfile);
-        registerCommand("sacks", "shows your sacks", this::showSacks);
+        registerCommand("reload", "Reloads the profile module", this::reload);
     }
 
-    private void showSacks(CommandSender sender, String[] strings) {
-        if(!(sender instanceof Player p)) return;
-        MiningSackGUI sacks = new MiningSackGUI(p);
-        InventoryManager.openFor(p, sacks);
-    }
-
-    private void addTest(CommandSender sender, String[] strings) {
-        if(!(sender instanceof Player p)) return;
-        FluxProfile profile = FluxService.get(ProfileManager.class).getProfile(p.getUniqueId());
-
-        /*BazaarFluxStats stats = profile.getSubProfile( "stats", BazaarFluxStats.class);
-        stats.addKill();*/
+    private void reload(CommandSender sender, String[] strings) {
+        module.reload(sender, strings);
     }
 
     private void showProfile(CommandSender sender, String[] strings) {

+ 13 - 1
src/main/java/me/lethunderhawk/profile/data/sacks/Sack.java

@@ -3,11 +3,12 @@ package me.lethunderhawk.profile.data.sacks;
 import me.lethunderhawk.fluxapi.profile.AnnotatedProfileCategory;
 import me.lethunderhawk.fluxapi.profile.AnnotatedSerializable;
 import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
 
 import java.util.HashMap;
 import java.util.Map;
 
-public abstract class Sack extends AnnotatedProfileCategory {
+public class Sack extends AnnotatedProfileCategory {
     @AnnotatedSerializable(prefix = "sackType")
     private final SackType sackType;
 
@@ -38,6 +39,10 @@ public abstract class Sack extends AnnotatedProfileCategory {
         return registeredMap.getOrDefault(sackType, null);
     }
 
+    public static Map<SackType, Map<Material, SackContent>> getRegisteredTypes() {
+        return new HashMap<>(registeredMap);
+    }
+
     public Map<Material, SackContent> getAssociatedSackMap(){
         return getRegisteredType(sackType);
     }
@@ -55,6 +60,13 @@ public abstract class Sack extends AnnotatedProfileCategory {
         return addItem(material, 1);
     }
 
+    public int addItemStack(ItemStack itemStack) {
+        if(itemStack.getItemMeta().hasLore()) {
+            return 0;
+        }
+        return addItem(itemStack.getType(), itemStack.getAmount());
+    }
+
     public int addItem(Material material, int amount) {
         if(getAssociatedSackMap() == null || !getAssociatedSackMap().containsKey(material)){
             return amount;

+ 0 - 10
src/main/java/me/lethunderhawk/profile/data/sacks/foraging/ForagingSack.java

@@ -1,10 +0,0 @@
-package me.lethunderhawk.profile.data.sacks.foraging;
-
-import me.lethunderhawk.profile.data.sacks.Sack;
-import me.lethunderhawk.profile.data.sacks.SackType;
-
-public class ForagingSack extends Sack {
-    public ForagingSack() {
-        super(SackType.FORAGING);
-    }
-}

+ 0 - 203
src/main/java/me/lethunderhawk/profile/data/sacks/foraging/ForagingSackGUI.java

@@ -1,203 +0,0 @@
-package me.lethunderhawk.profile.data.sacks.foraging;
-
-import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.profile.ProfileManager;
-import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
-import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
-import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
-import me.lethunderhawk.profile.data.sacks.Sack;
-import me.lethunderhawk.util.StringUtil;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.Material;
-import org.bukkit.entity.Player;
-import org.bukkit.event.inventory.ClickType;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.PlayerInventory;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-public class ForagingSackGUI extends InventoryGUI {
-    private int currentPage = 0;
-    private List<Material> materials;
-    private Sack sack;
-    private Player player;
-
-    public ForagingSackGUI(Player p, int page) {
-        super("Your Foraging Sack", calculateSize(p), p);
-        this.currentPage = page;
-    }
-    private static int calculateSize(Player p) {
-        // Always minimum 4 rows (36)
-        return 54;
-    }
-    @Override
-    public void performAdditionalComputationOnPlayer(Player player) {
-        sack = FluxService.get(ProfileManager.class)
-                .getProfile(player.getUniqueId())
-                .getSubProfile("foraging_sack", ForagingSack.class);
-
-        this.player = player;
-        this.materials = new ArrayList<>(sack.getAssociatedSackMap().keySet());
-    }
-
-    private int getItemsPerPage() {
-        int rows = getInventory().getSize() / 9;
-        int usableRows = rows - 2; // remove top & bottom border
-        return usableRows * 7;
-    }
-
-    private void addSackItem(int slot, Material material) {
-
-        String materialName = StringUtil.toCamelCase(material.name());
-
-        List<Component> lore = LoreDesigner.createLore(
-                "<dark_gray>Foraging Sack </dark_gray>" +
-                        "<br><br>" +
-                        "Stored: " +
-                        (sack.getMaterialAmount(material) == 0
-                                ? "<dark_gray>0</dark_gray>"
-                                : "<yellow>" + sack.getMaterialAmount(material) + "</yellow>")
-                        + "/" + sack.getMaxMaterialAmount(material) +
-                        "<br><br><aqua>Right-Click for stack!</aqua>" +
-                        "<br><yellow>Click to pickup!</yellow>",
-                "This is a string long enough to have everything inside of it",
-                NamedTextColor.GRAY
-        );
-
-        setItemWithClickAction(slot,
-                new ItemOptions(material)
-                        .setName(Component.text(materialName, NamedTextColor.GREEN))
-                        .setLore(lore)
-                        .buildItemStack(),
-                (p, type) -> doSackAction(type, material)
-        );
-    }
-
-    private void doSackAction(ClickType type, Material material) {
-
-        int amountToTake = 1;
-
-        if (type == ClickType.RIGHT || type == ClickType.SHIFT_RIGHT) {
-            amountToTake = 64;
-        }
-
-        int removed = sack.takeOutItem(material, amountToTake);
-
-        if (removed > 0) {
-            ItemStack stack = new ItemStack(material, removed);
-            HashMap<Integer, ItemStack> leftover = player.getInventory().addItem(stack);
-
-            // If inventory full → return items back into sack
-            if (!leftover.isEmpty()) {
-                int notStored = leftover.values().stream()
-                        .mapToInt(ItemStack::getAmount)
-                        .sum();
-                sack.addItem(material, notStored);
-            }
-        }
-
-        player.updateInventory();
-        update();
-    }
-
-    @Override
-    public void buildContent() {
-
-        getInventory().clear();
-        fillGlassPaneBackground();
-
-        int itemsPerPage = getItemsPerPage();
-        int startIndex = currentPage * itemsPerPage;
-        int endIndex = Math.min(startIndex + itemsPerPage, materials.size());
-
-        int slot = 10;
-
-        for (int index = startIndex; index < endIndex; index++) {
-
-            if (slot % 9 == 8) {
-                slot += 2;
-            }
-
-            addSackItem(slot, materials.get(index));
-            slot++;
-        }
-
-        setCloseButton(getInventory().getSize() - 5);
-        setAddAllItemsItem();
-
-        addPaginationButtons();
-    }
-    private void addPaginationButtons() {
-
-        int itemsPerPage = getItemsPerPage();
-        int totalPages = (int) Math.ceil((double) materials.size() / itemsPerPage);
-
-        if (currentPage > 0) {
-            setItemWithClickAction(
-                    getInventory().getSize() - 9,
-                    new ItemStack(Material.ARROW),
-                    (p, t) -> openPage(currentPage - 1)
-            );
-        }
-
-        if (currentPage < totalPages - 1) {
-            setItemWithClickAction(
-                    getInventory().getSize() - 1,
-                    new ItemStack(Material.ARROW),
-                    (p, t) -> openPage(currentPage + 1)
-            );
-        }
-    }
-    private void openPage(int page) {
-        ForagingSackGUI newGui = new ForagingSackGUI(player, page);
-        //newGui.performAdditionalComputationOnPlayer(player);  // Recompute the materials for the new page
-        openNext(player, newGui); // Open the new GUI
-        newGui.update(); // Ensure the content is displayed immediately.
-    }
-
-
-    private void setAddAllItemsItem() {
-        setItemWithClickAction(getInventory().getSize() - 4,
-                new ItemOptions(Material.CAULDRON)
-                        .setName(Component.text("Add all items into the Sack", NamedTextColor.GREEN))
-                        .setLore(LoreDesigner.createLore(
-                                "<br>Allows you to put all items from your inventory into the sack",
-                                "This is an example width reference",
-                                NamedTextColor.DARK_GRAY)
-                        )
-                        .buildItemStack(), this::putAllItemsIn);
-    }
-
-    private void putAllItemsIn(Player player, ClickType type) {
-
-        PlayerInventory inv = player.getInventory();
-
-        for (ItemStack stack : inv.getContents()) {
-
-            if (stack == null) continue;
-            if (stack.getType() == Material.AIR) continue;
-
-            Material material = stack.getType();
-            int amount = stack.getAmount();
-
-            // Try adding to sack
-            int leftover = sack.addItem(material, amount);
-
-            int successfullyAdded = amount - leftover;
-
-            if (successfullyAdded > 0) {
-                stack.setAmount(leftover); // leave leftover in inventory
-            }
-        }
-        player.updateInventory();
-        update();
-    }
-
-    @Override
-    public void update() {
-        buildContent();
-    }
-}

+ 258 - 0
src/main/java/me/lethunderhawk/profile/data/sacks/gui/SackGUI.java

@@ -0,0 +1,258 @@
+package me.lethunderhawk.profile.data.sacks.gui;
+
+import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
+import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
+import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
+import me.lethunderhawk.profile.data.sacks.Sack;
+import me.lethunderhawk.util.StringUtil;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class SackGUI extends InventoryGUI {
+
+    private final Sack sack;
+    private Player player;
+
+    private int currentPage = 0;
+    int maxPage;
+    private List<Material> materials;
+
+    private List<Integer> contentSlots;
+
+    public SackGUI(@NotNull Player p, @NotNull Sack sack) {
+        super("Your " + sack.getType().name().toLowerCase() + " Sack", 36, p);
+        this.sack = sack;
+
+    }
+
+    @Override
+    public void performAdditionalComputationOnPlayer(Player player) {
+        this.player = player;
+    }
+
+    @Override
+    public void buildContent() {
+
+        fillGlassPaneBackground();
+
+        materials = new ArrayList<>(sack.getAssociatedSackMap().keySet());
+
+        contentSlots = computeContentSlots();
+        int itemsPerPage = contentSlots.size();
+        maxPage = (materials.size() - 1) / itemsPerPage;
+
+        renderPage();
+        addNavigationButtons();
+        setCloseButton(getInventory().getSize() - 5);
+        setAddAllItemsItem();
+    }
+
+    /**
+     * Computes all interior slots dynamically (1 slot border).
+     */
+    private List<Integer> computeContentSlots() {
+
+        List<Integer> slots = new ArrayList<>();
+
+        int rows = getInventory().getSize() / 9;
+
+        for (int row = 1; row < rows - 1; row++) {
+            for (int col = 1; col < 8; col++) {
+
+                int slot = row * 9 + col;
+                slots.add(slot);
+
+            }
+        }
+
+        return slots;
+    }
+
+    private void renderPage() {
+
+        int itemsPerPage = contentSlots.size();
+        int start = currentPage * itemsPerPage;
+
+        for (int i = 0; i < itemsPerPage; i++) {
+
+            int materialIndex = start + i;
+
+            if (materialIndex >= materials.size()) continue;
+
+            Material material = materials.get(materialIndex);
+
+            addSackItem(material, contentSlots.get(i));
+        }
+    }
+
+    private void addSackItem(Material material, int slot) {
+
+        String materialName = StringUtil.toCamelCase(material.name());
+
+        List<Component> lore = LoreDesigner.createLore(
+                "<dark_gray>" + sack.getType().name().toUpperCase() + " SACK </dark_gray>" +
+                        "<br><br>Stored: " +
+                        (sack.getMaterialAmount(material) == 0
+                                ? "<dark_gray>0</dark_gray>"
+                                : "<yellow>" + sack.getMaterialAmount(material) + "</yellow>")
+                        + "/" + sack.getMaxMaterialAmount(material) +
+                        "<br><br><aqua>Right-Click for stack!</aqua>" +
+                        "<br><yellow>Click to pickup!</yellow>",
+                "This is an example width reference",
+                NamedTextColor.GRAY
+        );
+
+        setItemWithClickAction(slot,
+                new ItemOptions(material)
+                        .setName(Component.text(materialName, NamedTextColor.GREEN))
+                        .setLore(lore)
+                        .buildItemStack(),
+                (p, type) -> doSackAction(type, material)
+        );
+    }
+
+    private void addNavigationButtons() {
+        if (currentPage > 0) {
+            setItemWithClickAction(getInventory().getSize() - 9,
+                    new ItemOptions(Material.ARROW)
+                            .setName(Component.text("Previous Page", NamedTextColor.YELLOW))
+                            .setLore(List.of(Component.text("Current Page: " + (currentPage + 1) + "/" + (maxPage + 1), NamedTextColor.GRAY)))
+                            .buildItemStack(),
+                    (p, type) -> {
+                        previousPage();
+                    });
+        }else{
+            setItem(getInventory().getSize() - 9, getGlassPaneStack());
+        }
+
+        if (currentPage < maxPage) {
+            setItemWithClickAction(getInventory().getSize() -1,
+                    new ItemOptions(Material.ARROW)
+                            .setName(Component.text("Next Page", NamedTextColor.YELLOW))
+                            .setLore(List.of(Component.text("Current Page " + (currentPage + 1) + "/" + (maxPage + 1), NamedTextColor.GRAY)))
+                            .buildItemStack(),
+                    (p, type) -> {
+                        nextPage();
+                    });
+        }else{
+            setItem(getInventory().getSize() -1, getGlassPaneStack());
+        }
+    }
+    private ItemStack getGlassPaneStack(){
+        ItemStack background = new ItemStack(Material.GRAY_STAINED_GLASS_PANE);
+        ItemMeta meta = background.getItemMeta();
+        meta.displayName(Component.text(" "));
+        meta.setHideTooltip(true);
+        background.setItemMeta(meta);
+        return background;
+    }
+
+    private void previousPage() {
+        if(currentPage > 0) {
+            currentPage--;
+            update();
+        }
+    }
+
+    private void nextPage() {
+        if(currentPage < maxPage) {
+            currentPage++;
+            update();
+        }
+    }
+
+    private void doSackAction(ClickType type, Material material) {
+
+        int amountToTake = 1;
+
+        if (type == ClickType.RIGHT || type == ClickType.SHIFT_RIGHT) {
+            amountToTake = 64;
+        }
+
+        int removed = sack.takeOutItem(material, amountToTake);
+
+        if (removed > 0) {
+            player.playSound(player.getLocation(), Sound.ITEM_BUNDLE_REMOVE_ONE, 1f, 1f);
+            ItemStack stack = new ItemStack(material, removed);
+
+            HashMap<Integer, ItemStack> leftover = player.getInventory().addItem(stack);
+
+            if (!leftover.isEmpty()) {
+
+                int notStored = leftover.values().stream()
+                        .mapToInt(ItemStack::getAmount)
+                        .sum();
+
+                sack.addItem(material, notStored);
+            }
+        }
+
+        player.updateInventory();
+        update();
+    }
+
+    private void setAddAllItemsItem() {
+
+        setItemWithClickAction(getInventory().getSize() - 4,
+                new ItemOptions(Material.CAULDRON)
+                        .setName(Component.text("Add all items into the Sack", NamedTextColor.GREEN))
+                        .setLore(LoreDesigner.createLore(
+                                "<br>Allows you to put all items from your inventory into the sack",
+                                "This is another example width reference",
+                                NamedTextColor.DARK_GRAY))
+                        .buildItemStack(),
+                this::putAllItemsIn);
+    }
+
+    private void putAllItemsIn(Player player, ClickType type) {
+
+        PlayerInventory inv = player.getInventory();
+
+        for (ItemStack stack : inv.getContents()) {
+
+            if (stack == null || stack.getType() == Material.AIR) continue;
+
+            Material material = stack.getType();
+            int amount = stack.getAmount();
+
+            int leftover = sack.addItem(material, amount);
+
+            int successfullyAdded = amount - leftover;
+
+            if (successfullyAdded > 0) {
+                stack.setAmount(leftover);
+            }
+        }
+        player.playSound(player.getLocation(), Sound.ITEM_BUNDLE_INSERT, 1f, 1f);
+        player.updateInventory();
+        update();
+    }
+
+    @Override
+    public void update() {
+        clearInventory();
+        fillGlassPaneBackground();
+        renderPage();
+        addNavigationButtons();
+        setCloseButton(getInventory().getSize() - 5);
+        setAddAllItemsItem();
+    }
+
+    private void clearInventory() {
+        for(int i = 0; i < getInventory().getSize(); ++i) {
+            this.getInventory().setItem(i,null);
+        }
+    }
+}

+ 0 - 10
src/main/java/me/lethunderhawk/profile/data/sacks/mining/MiningSack.java

@@ -1,10 +0,0 @@
-package me.lethunderhawk.profile.data.sacks.mining;
-
-import me.lethunderhawk.profile.data.sacks.Sack;
-import me.lethunderhawk.profile.data.sacks.SackType;
-
-public class MiningSack extends Sack {
-    public MiningSack() {
-        super(SackType.MINING);
-    }
-}

+ 0 - 143
src/main/java/me/lethunderhawk/profile/data/sacks/mining/MiningSackGUI.java

@@ -1,143 +0,0 @@
-package me.lethunderhawk.profile.data.sacks.mining;
-
-import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.profile.ProfileManager;
-import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
-import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
-import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
-import me.lethunderhawk.profile.data.sacks.Sack;
-import me.lethunderhawk.util.StringUtil;
-import net.kyori.adventure.text.Component;
-import net.kyori.adventure.text.format.NamedTextColor;
-import org.bukkit.Material;
-import org.bukkit.entity.Player;
-import org.bukkit.event.inventory.ClickType;
-import org.bukkit.inventory.ItemStack;
-import org.bukkit.inventory.PlayerInventory;
-
-import java.util.HashMap;
-import java.util.List;
-
-public class MiningSackGUI extends InventoryGUI {
-    private int i = 10;
-    private Sack sack;
-    private Player player;
-
-    public MiningSackGUI(Player p) {
-        super("Your Mining Sack", 36, p);
-        if (this.sack == null) {
-            throw new IllegalStateException("Sack profile not available.");
-        }
-    }
-    @Override
-    public void performAdditionalComputationOnPlayer(Player player){
-        sack = FluxService.get(ProfileManager.class).getProfile(player.getUniqueId()).getSubProfile("mining_sack", MiningSack.class);
-        this.player = player;
-    }
-
-    private void addSackItem(Material material) {
-        String materialName = StringUtil.toCamelCase(material.name());
-        String finalName = materialName;
-        List<Component> lore = LoreDesigner.createLore("<dark_gray>Mining Sack </dark_gray>" +
-                "<br>" +
-                "<br>" +
-                "Stored: " +
-                (sack.getMaterialAmount(material) == 0 ? "<dark_gray>0</dark_gray>" : "<yellow>" + sack.getMaterialAmount(material) + "</yellow>")
-                + "/"
-                + sack.getMaxMaterialAmount(material)
-                + "<br><br><aqua>Right-Click for stack!</aqua>"
-                + "<br><yellow>Click to pickup!</yellow>",
-                "This is a string long enough to have everything inside of it", NamedTextColor.GRAY);
-        setItemWithClickAction(i, new ItemOptions(material)
-                .setName(Component.text(finalName, NamedTextColor.GREEN))
-                .setLore(lore)
-                .buildItemStack(), (p, type) -> {
-            doSackAction(type, material);
-        });
-        i++;
-    }
-
-    private void doSackAction(ClickType type, Material material) {
-
-        int amountToTake = 1;
-
-        if (type == ClickType.RIGHT || type == ClickType.SHIFT_RIGHT) {
-            amountToTake = 64;
-        }
-
-        int removed = sack.takeOutItem(material, amountToTake);
-
-        if (removed > 0) {
-            ItemStack stack = new ItemStack(material, removed);
-            HashMap<Integer, ItemStack> leftover = player.getInventory().addItem(stack);
-
-            // If inventory full → return items back into sack
-            if (!leftover.isEmpty()) {
-                int notStored = leftover.values().stream()
-                        .mapToInt(ItemStack::getAmount)
-                        .sum();
-                sack.addItem(material, notStored);
-            }
-        }
-
-        player.updateInventory();
-        update();
-    }
-
-    @Override
-    public void buildContent() {
-        fillGlassPaneBackground();
-
-        i = 10;
-        for (Material material : sack.getAssociatedSackMap().keySet()) {
-            addSackItem(material);
-        }
-        setCloseButton(31);
-        setAddAllItemsItem();
-    }
-
-    private void setAddAllItemsItem() {
-        setItemWithClickAction(32,
-                new ItemOptions(Material.CAULDRON)
-                        .setName(Component.text("Add all items into the Sack", NamedTextColor.GREEN))
-                        .setLore(LoreDesigner.createLore(
-                                "<br>Allows you to put all items from your inventory into the sack",
-                                "This is an example width reference",
-                                NamedTextColor.DARK_GRAY)
-                        )
-                        .buildItemStack(), this::putAllItemsIn);
-    }
-
-    private void putAllItemsIn(Player player, ClickType type) {
-
-        PlayerInventory inv = player.getInventory();
-
-        for (ItemStack stack : inv.getContents()) {
-
-            if (stack == null) continue;
-            if (stack.getType() == Material.AIR) continue;
-
-            Material material = stack.getType();
-            int amount = stack.getAmount();
-
-            // Try adding to sack
-            int leftover = sack.addItem(material, amount);
-
-            int successfullyAdded = amount - leftover;
-
-            if (successfullyAdded > 0) {
-                stack.setAmount(leftover); // leave leftover in inventory
-            }
-        }
-        player.updateInventory();
-        update();
-    }
-
-    @Override
-    public void update() {
-        i = 10;
-        for (Material material : sack.getAssociatedSackMap().keySet()) {
-            addSackItem(material);
-        }
-    }
-}

+ 1 - 1
src/main/java/me/lethunderhawk/profile/gui/StatsGUI.java

@@ -32,7 +32,7 @@ public class StatsGUI extends InventoryGUI {
     }
 
     private ItemStack createPlayerHead() {
-        return CustomHeadCreator.createCustomHead(player, Component.text("This is you!", NamedTextColor.YELLOW),
+        return CustomHeadCreator.createCustomHead(player, Component.text("Your profile!", NamedTextColor.YELLOW),
                 LoreDesigner.createLore(
                         "This shows your data, there is a lot to be added soon!", "This shows your data,", NamedTextColor.GRAY
                 ));

+ 5 - 1
src/main/resources/plugin.yml

@@ -73,4 +73,8 @@ permissions:
     description: Allows accepting a trade
 
   customrecipe.gui:
-    description: Allows editing of recipes
+    description: Allows display of recipes
+
+  customrecipe.edit:
+    description: Allows editing of custom recipes
+    default: op