소스 검색

Implementation of Sacks, start of custom Recipes, more dynamic approach

Jan 2 주 전
부모
커밋
27c48c3bde
59개의 변경된 파일1432개의 추가작업 그리고 203개의 파일을 삭제
  1. 1 1
      pom.xml
  2. 2 2
      src/main/java/me/lethunderhawk/clans/ClanModule.java
  3. 7 1
      src/main/java/me/lethunderhawk/clans/command/ClanCommand.java
  4. 8 8
      src/main/java/me/lethunderhawk/clans/gui/AdminMenuGUI.java
  5. 9 9
      src/main/java/me/lethunderhawk/clans/gui/ClaimSettingsGUI.java
  6. 10 10
      src/main/java/me/lethunderhawk/clans/gui/ClanMenu.java
  7. 8 9
      src/main/java/me/lethunderhawk/clans/gui/ClanSettingsGUI.java
  8. 2 2
      src/main/java/me/lethunderhawk/clans/gui/MemberListGUI.java
  9. 5 2
      src/main/java/me/lethunderhawk/custom/item/CustomItemModule.java
  10. 1 3
      src/main/java/me/lethunderhawk/custom/item/abstraction/visual/ItemRenderer.java
  11. 6 1
      src/main/java/me/lethunderhawk/custom/item/command/CustomItemCommand.java
  12. 33 0
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/SackAbility.java
  13. 71 0
      src/main/java/me/lethunderhawk/custom/recipes/CraftingTableRecipe.java
  14. 18 0
      src/main/java/me/lethunderhawk/custom/recipes/CustomRecipe.java
  15. 66 0
      src/main/java/me/lethunderhawk/custom/recipes/CustomRecipeModule.java
  16. 89 0
      src/main/java/me/lethunderhawk/custom/recipes/RecipeManager.java
  17. 8 0
      src/main/java/me/lethunderhawk/custom/recipes/RecipeType.java
  18. 33 0
      src/main/java/me/lethunderhawk/custom/recipes/abstraction/AbstractRecipe.java
  19. 31 0
      src/main/java/me/lethunderhawk/custom/recipes/command/RecipeEditorCommand.java
  20. 4 3
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/EnchantedVariantRecipeManager.java
  21. 3 3
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/EnchantedCobblestone.java
  22. 3 3
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/EnchantedRedstone.java
  23. 1 1
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/abstraction/DisableEnchantedItemPlacingListener.java
  24. 1 1
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/abstraction/EnchantedItemRegistry.java
  25. 3 3
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/abstraction/EnchantedVariant.java
  26. 1 1
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/recipe/EnchantedItemRecipe.java
  27. 1 1
      src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/recipe/StackSizeIngredient.java
  28. 72 0
      src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeBrowserGUI.java
  29. 114 0
      src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeEditorGUI.java
  30. 65 0
      src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeViewerGUI.java
  31. 2 2
      src/main/java/me/lethunderhawk/dungeon/DungeonModule.java
  32. 6 4
      src/main/java/me/lethunderhawk/dungeon/command/DungeonCommand.java
  33. 2 3
      src/main/java/me/lethunderhawk/dungeon/gui/DungeonGUI.java
  34. 2 2
      src/main/java/me/lethunderhawk/economy/EconomyModule.java
  35. 7 1
      src/main/java/me/lethunderhawk/economy/command/EcoCommand.java
  36. 9 7
      src/main/java/me/lethunderhawk/main/BazaarFlux.java
  37. 15 15
      src/main/java/me/lethunderhawk/minion/MinionModule.java
  38. 7 1
      src/main/java/me/lethunderhawk/minion/command/MinionCommand.java
  39. 8 9
      src/main/java/me/lethunderhawk/minion/ui/MinionMenu.java
  40. 7 7
      src/main/java/me/lethunderhawk/minion/ui/minionList/MinionLevelListMenu.java
  41. 8 8
      src/main/java/me/lethunderhawk/minion/ui/minionList/MinionListMenu.java
  42. 2 2
      src/main/java/me/lethunderhawk/npc/NPCRegistrator.java
  43. 63 7
      src/main/java/me/lethunderhawk/profile/ProfileModule.java
  44. 18 6
      src/main/java/me/lethunderhawk/profile/command/ProfileCommand.java
  45. 99 0
      src/main/java/me/lethunderhawk/profile/data/sacks/Sack.java
  46. 76 0
      src/main/java/me/lethunderhawk/profile/data/sacks/SackContent.java
  47. 6 0
      src/main/java/me/lethunderhawk/profile/data/sacks/SackType.java
  48. 10 0
      src/main/java/me/lethunderhawk/profile/data/sacks/foraging/ForagingSack.java
  49. 203 0
      src/main/java/me/lethunderhawk/profile/data/sacks/foraging/ForagingSackGUI.java
  50. 10 0
      src/main/java/me/lethunderhawk/profile/data/sacks/mining/MiningSack.java
  51. 143 0
      src/main/java/me/lethunderhawk/profile/data/sacks/mining/MiningSackGUI.java
  52. 8 9
      src/main/java/me/lethunderhawk/profile/gui/StatsGUI.java
  53. 0 46
      src/main/java/me/lethunderhawk/profile/test/BazaarFluxStats.java
  54. 2 2
      src/main/java/me/lethunderhawk/tradeplugin/TradeModule.java
  55. 2 3
      src/main/java/me/lethunderhawk/tradeplugin/input/player/NumberInputGUI.java
  56. 24 0
      src/main/java/me/lethunderhawk/util/StringUtil.java
  57. 2 2
      src/main/java/me/lethunderhawk/world/WorldModule.java
  58. 8 3
      src/main/java/me/lethunderhawk/world/command/WorldCommand.java
  59. 7 0
      src/main/resources/plugin.yml

+ 1 - 1
pom.xml

@@ -110,7 +110,7 @@
         <dependency>
             <groupId>me.lethunderhawk</groupId>
             <artifactId>FluxAPI</artifactId>
-            <version>1.2.2</version>
+            <version>1.2.3</version>
             <scope>provided</scope>
         </dependency>
     </dependencies>

+ 2 - 2
src/main/java/me/lethunderhawk/clans/ClanModule.java

@@ -5,12 +5,12 @@ import me.lethunderhawk.clans.claim.ClaimManager;
 import me.lethunderhawk.clans.command.ClanCommand;
 import me.lethunderhawk.clans.placeholder.ClanPlaceHolder;
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.main.BazaarFlux;
 import org.bukkit.event.HandlerList;
 import org.bukkit.plugin.java.JavaPlugin;
 
-public class ClanModule extends BazaarFluxModule {
+public class ClanModule extends FluxAPIModule {
 
     public ClanModule(JavaPlugin plugin) {
         super(plugin);

+ 7 - 1
src/main/java/me/lethunderhawk/clans/command/ClanCommand.java

@@ -27,9 +27,15 @@ public class ClanCommand extends CustomCommand {
     private final ClanManager manager;
 
     public ClanCommand(ClanManager manager) {
-        super(new CommandNode("clan", "Main clan command", null), FluxService.get(ClanModule.class));
+        super(FluxService.get(ClanModule.class));
         this.manager = manager;
     }
+
+    @Override
+    public void setRootCommand() {
+        rootCommand = new CommandNode("clan", "Main clan command", null);
+    }
+
     @Override
     public void createCommands() {
         registerCommand("list", "List all clans", this::handleList);

+ 8 - 8
src/main/java/me/lethunderhawk/clans/gui/AdminMenuGUI.java

@@ -23,14 +23,6 @@ public class AdminMenuGUI extends InventoryGUI {
     public AdminMenuGUI(Player player) {
         super("Admin Panel - Edit all clans", 36);
         this.player = player;
-        buildGUI();
-    }
-
-    private void buildGUI() {
-        fillGlassPaneBackground();
-        setBackButton(30);
-        setCloseButton(31);
-        fillClansMenuWithClans();
     }
 
     private void fillClansMenuWithClans() {
@@ -76,6 +68,14 @@ public class AdminMenuGUI extends InventoryGUI {
     }
 
 
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+        setBackButton(30);
+        setCloseButton(31);
+        fillClansMenuWithClans();
+    }
+
     @Override
     public void update() {
 

+ 9 - 9
src/main/java/me/lethunderhawk/clans/gui/ClaimSettingsGUI.java

@@ -28,14 +28,6 @@ public class ClaimSettingsGUI extends InventoryGUI {
         super("Claim Settings", 36);
         this.player = player;
         this.clan = clan;
-        buildGUI();
-    }
-
-    private void buildGUI() {
-        fillGlassPaneBackground();
-        setBackButton(30);
-        setCloseButton(31);
-        buildItems();
     }
 
     private void buildItems() {
@@ -96,8 +88,16 @@ public class ClaimSettingsGUI extends InventoryGUI {
         return item;
     }
 
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+        setBackButton(30);
+        setCloseButton(31);
+        buildItems();
+    }
+
     @Override
     public void update() {
-        buildGUI();
+        buildContent();
     }
 }

+ 10 - 10
src/main/java/me/lethunderhawk/clans/gui/ClanMenu.java

@@ -21,16 +21,6 @@ public class ClanMenu extends InventoryGUI {
         super("Clan Menu - " + clan.getName(), 36);
         this.clan = clan;
         this.player = player;
-        buildMenu();
-    }
-
-    private void buildMenu() {
-        fillBackground(Material.GRAY_STAINED_GLASS_PANE, " ");
-        setCloseButton(31);
-        buildSettings();
-        buildMemberList();
-        buildClaimList();
-        buildAdminList();
     }
 
 
@@ -115,6 +105,16 @@ public class ClanMenu extends InventoryGUI {
     }
 
 
+    @Override
+    public void buildContent() {
+        fillBackground(Material.GRAY_STAINED_GLASS_PANE, " ");
+        setCloseButton(31);
+        buildSettings();
+        buildMemberList();
+        buildClaimList();
+        buildAdminList();
+    }
+
     @Override
     public void update() {
 

+ 8 - 9
src/main/java/me/lethunderhawk/clans/gui/ClanSettingsGUI.java

@@ -23,15 +23,6 @@ public class ClanSettingsGUI extends InventoryGUI {
         super("Clan Settings", 36);
         this.player = player;
         this.settings = settings;
-
-        buildGUI();
-    }
-
-    private void buildGUI() {
-        fillGlassPaneBackground();
-        setBackButton(30);
-        setCloseButton(31);
-        buildItems();
     }
 
     private void buildItems() {
@@ -174,6 +165,14 @@ public class ClanSettingsGUI extends InventoryGUI {
         return item;
     }
 
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+        setBackButton(30);
+        setCloseButton(31);
+        buildItems();
+    }
+
     @Override
     public void update() {
         buildItems();

+ 2 - 2
src/main/java/me/lethunderhawk/clans/gui/MemberListGUI.java

@@ -9,10 +9,10 @@ public class MemberListGUI extends InventoryGUI {
     public MemberListGUI(Player player) {
         super("Member List", 36);
         this.player = player;
-        buildGUI();
     }
 
-    private void buildGUI() {
+    @Override
+    public void buildContent() {
         fillGlassPaneBackground();
         setBackButton(30);
         setCloseButton(31);

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

@@ -12,8 +12,9 @@ import me.lethunderhawk.custom.item.command.CustomItemCommand;
 import me.lethunderhawk.custom.item.concrete.ability.*;
 import me.lethunderhawk.custom.item.listener.CustomItemListener;
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+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;
@@ -21,7 +22,7 @@ import org.bukkit.plugin.java.JavaPlugin;
 import java.io.File;
 import java.util.Map;
 
-public class CustomItemModule extends BazaarFluxModule {
+public class CustomItemModule extends FluxAPIModule {
     private CustomItemListener listener;
 
     public CustomItemModule(JavaPlugin plugin) {
@@ -35,6 +36,8 @@ public class CustomItemModule extends BazaarFluxModule {
         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));
     }
 
     @Override

+ 1 - 3
src/main/java/me/lethunderhawk/custom/item/abstraction/visual/ItemRenderer.java

@@ -1,10 +1,10 @@
 package me.lethunderhawk.custom.item.abstraction.visual;
 
-import me.lethunderhawk.fluxapi.util.CustomHeadCreator;
 import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinition;
 import me.lethunderhawk.custom.item.abstraction.definition.ItemVisualDefinition;
 import me.lethunderhawk.custom.item.abstraction.instance.ItemInstance;
 import me.lethunderhawk.custom.item.abstraction.instance.ItemPersistentData;
+import me.lethunderhawk.fluxapi.util.CustomHeadCreator;
 import me.lethunderhawk.fluxapi.util.UnItalic;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
@@ -49,7 +49,6 @@ public final class ItemRenderer {
         // --- Flags ---
         meta.removeItemFlags(ItemFlag.values());
         meta.addItemFlags(visual.itemFlags());
-
         // --- Persist instance ---
         ItemPersistentData.write(meta, instance);
 
@@ -62,7 +61,6 @@ public final class ItemRenderer {
         ItemStack stack = visual.isHead()
                 ? CustomHeadCreator.createCustomHead(visual.headTexture().get())
                 : new ItemStack(visual.material());
-
         renderInto(stack, instance);
         return stack;
     }

+ 6 - 1
src/main/java/me/lethunderhawk/custom/item/command/CustomItemCommand.java

@@ -14,7 +14,7 @@ import org.bukkit.entity.Player;
 public class CustomItemCommand extends CustomCommand {
 
     public CustomItemCommand(CustomItemModule customItemModule) {
-        super(new CommandNode("customItems", "Main Custom Item Command", null), customItemModule);
+        super(customItemModule);
     }
 
     private void getAllItems(CommandSender sender, String[] strings) {
@@ -23,6 +23,11 @@ public class CustomItemCommand extends CustomCommand {
         getAllItems(p);
     }
 
+    @Override
+    public void setRootCommand() {
+        rootCommand = new CommandNode("customItems", "Main Custom Item Command", null);
+    }
+
     @Override
     public void createCommands() {
         rootCommand.registerSubCommand("getAll", "Get all custom items", this::getAllItems);

+ 33 - 0
src/main/java/me/lethunderhawk/custom/item/concrete/ability/SackAbility.java

@@ -0,0 +1,33 @@
+package me.lethunderhawk.custom.item.concrete.ability;
+
+import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
+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.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 org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerInteractEvent;
+
+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));
+            }
+        }
+    }
+}

+ 71 - 0
src/main/java/me/lethunderhawk/custom/recipes/CraftingTableRecipe.java

@@ -0,0 +1,71 @@
+package me.lethunderhawk.custom.recipes;
+
+import me.lethunderhawk.custom.recipes.abstraction.AbstractRecipe;
+import org.bukkit.NamespacedKey;
+import org.bukkit.inventory.CraftingInventory;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+public class CraftingTableRecipe extends AbstractRecipe {
+
+    private final ItemStack[] matrix;
+
+    public CraftingTableRecipe(NamespacedKey key, ItemStack[] matrix, ItemStack result) {
+        super(key, result, RecipeType.CRAFTING);
+        this.matrix = matrix;
+    }
+
+    @Override
+    public boolean matches(Inventory inventory) {
+
+        if (!(inventory instanceof CraftingInventory crafting))
+            return false;
+
+        ItemStack[] invMatrix = crafting.getMatrix();
+
+        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.getAmount() < expected.getAmount()) return false;
+        }
+
+        return true;
+    }
+
+    @Override
+    public ItemStack craft(Inventory inventory) {
+
+        CraftingInventory crafting = (CraftingInventory) inventory;
+        ItemStack[] invMatrix = crafting.getMatrix();
+
+        for (int i = 0; i < matrix.length; i++) {
+
+            ItemStack expected = matrix[i];
+            ItemStack actual = invMatrix[i];
+
+            if (expected == null || actual == null) continue;
+
+            actual.setAmount(actual.getAmount() - expected.getAmount());
+
+            if (actual.getAmount() <= 0)
+                invMatrix[i] = null;
+        }
+
+        crafting.setMatrix(invMatrix);
+
+        return result.clone();
+    }
+
+    public ItemStack[] getMatrix() {
+        return matrix;
+    }
+
+}

+ 18 - 0
src/main/java/me/lethunderhawk/custom/recipes/CustomRecipe.java

@@ -0,0 +1,18 @@
+package me.lethunderhawk.custom.recipes;
+
+import org.bukkit.NamespacedKey;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+public interface CustomRecipe {
+
+    NamespacedKey getKey();
+
+    ItemStack getResult();
+
+    boolean matches(Inventory inventory);
+
+    ItemStack craft(Inventory inventory);
+
+    RecipeType getRecipeType();
+}

+ 66 - 0
src/main/java/me/lethunderhawk/custom/recipes/CustomRecipeModule.java

@@ -0,0 +1,66 @@
+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.interfaces.FluxAPIModule;
+import me.lethunderhawk.main.BazaarFlux;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.event.HandlerList;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class CustomRecipeModule extends FluxAPIModule {
+    private EnchantedVariantRecipeManager enchantedVariantRecipeManager;
+    private RecipeManager recipeManager;
+
+    public CustomRecipeModule(JavaPlugin plugin) {
+        super(plugin);
+    }
+
+    @Override
+    public String getPrefix() {
+        return "[Custom Recipe]";
+    }
+
+    @Override
+    public void onEnable() {
+        this.enchantedVariantRecipeManager = new EnchantedVariantRecipeManager(FluxService.get(BazaarFlux.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);
+        RecipeEditorCommand command = new RecipeEditorCommand(this);
+        plugin.getCommand("customrecipe").setExecutor(command);
+        plugin.getCommand("customrecipe").setExecutor(command);
+    }
+
+    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);
+
+        recipeManager.registerRecipe(
+                new CraftingTableRecipe(new NamespacedKey(plugin, "test_recipe"),
+                        matrix, new ItemStack(Material.GREEN_CONCRETE))
+        );
+    }
+
+    @Override
+    public void onDisable() {
+        if(this.enchantedVariantRecipeManager != null && this.recipeManager != null) {
+            HandlerList.unregisterAll(this.enchantedVariantRecipeManager);
+            HandlerList.unregisterAll(this.recipeManager);
+        }
+    }
+}

+ 89 - 0
src/main/java/me/lethunderhawk/custom/recipes/RecipeManager.java

@@ -0,0 +1,89 @@
+package me.lethunderhawk.custom.recipes;
+
+import me.lethunderhawk.custom.recipes.abstraction.AbstractRecipe;
+import org.bukkit.Bukkit;
+import org.bukkit.NamespacedKey;
+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.PrepareItemCraftEvent;
+import org.bukkit.inventory.CraftingInventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.plugin.Plugin;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class RecipeManager implements Listener {
+
+    private final Plugin plugin;
+
+    private final Map<NamespacedKey, CustomRecipe> recipes = new HashMap<>();
+
+    public RecipeManager(Plugin plugin) {
+        this.plugin = plugin;
+        Bukkit.getPluginManager().registerEvents(this, plugin);
+    }
+
+    public void registerRecipe(CustomRecipe recipe) {
+        recipes.put(recipe.getKey(), recipe);
+    }
+
+    public void unregisterRecipe(NamespacedKey key) {
+        recipes.remove(key);
+    }
+
+    public Collection<CustomRecipe> getRecipes() {
+        return recipes.values();
+    }
+
+    public List<CustomRecipe> getRecipesByType(RecipeType type) {
+        return recipes.values().stream()
+                .filter(r -> r instanceof AbstractRecipe && r.getRecipeType() == type)
+                .toList();
+    }
+    @EventHandler
+    public void onPrepareCraft(PrepareItemCraftEvent event) {
+
+        CraftingInventory inv = event.getInventory();
+
+        for (CustomRecipe recipe : recipes.values()) {
+
+            if (recipe.matches(inv)) {
+
+                inv.setResult(recipe.getResult());
+                return;
+
+            }
+
+        }
+
+        inv.setResult(null);
+    }
+    @EventHandler
+    public void onCraft(CraftItemEvent event) {
+
+        CraftingInventory inv = event.getInventory();
+
+        for (CustomRecipe recipe : recipes.values()) {
+
+            if (recipe.matches(inv)) {
+
+                event.setCancelled(true);
+
+                ItemStack result = recipe.craft(inv);
+
+                Player player = (Player) event.getWhoClicked();
+                player.getInventory().addItem(result);
+
+                player.updateInventory();
+
+                return;
+            }
+
+        }
+    }
+}

+ 8 - 0
src/main/java/me/lethunderhawk/custom/recipes/RecipeType.java

@@ -0,0 +1,8 @@
+package me.lethunderhawk.custom.recipes;
+
+public enum RecipeType {
+    CRAFTING,
+    FURNACE,
+    AUTO_SMELTER,
+    CUSTOM
+}

+ 33 - 0
src/main/java/me/lethunderhawk/custom/recipes/abstraction/AbstractRecipe.java

@@ -0,0 +1,33 @@
+package me.lethunderhawk.custom.recipes.abstraction;
+
+import me.lethunderhawk.custom.recipes.CustomRecipe;
+import me.lethunderhawk.custom.recipes.RecipeType;
+import org.bukkit.NamespacedKey;
+import org.bukkit.inventory.ItemStack;
+
+public abstract class AbstractRecipe implements CustomRecipe {
+
+    protected final NamespacedKey key;
+    protected final ItemStack result;
+    protected final RecipeType type;
+
+    public AbstractRecipe(NamespacedKey key, ItemStack result, RecipeType type) {
+        this.key = key;
+        this.result = result;
+        this.type = type;
+    }
+
+    @Override
+    public NamespacedKey getKey() {
+        return key;
+    }
+
+    @Override
+    public ItemStack getResult() {
+        return result.clone();
+    }
+    @Override
+    public RecipeType getRecipeType() {
+        return type;
+    }
+}

+ 31 - 0
src/main/java/me/lethunderhawk/custom/recipes/command/RecipeEditorCommand.java

@@ -0,0 +1,31 @@
+package me.lethunderhawk.custom.recipes.command;
+
+import me.lethunderhawk.custom.recipes.gui.RecipeBrowserGUI;
+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 org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+public class RecipeEditorCommand extends CustomCommand {
+    public RecipeEditorCommand(FluxAPIModule module) {
+        super(module);
+    }
+
+    @Override
+    public void setRootCommand() {
+        rootCommand = new CommandNode("customrecipe", "base cmd", null);
+    }
+
+    @Override
+    public void createCommands() {
+        registerCommand("gui", "displays a neat UI", this::showGUI);
+    }
+
+    private void showGUI(CommandSender sender, String[] strings) {
+        if(sender.hasPermission("customrecipe.gui") && sender instanceof Player player) {
+            InventoryManager.openFor(player, new RecipeBrowserGUI(player));
+        }
+    }
+}

+ 4 - 3
src/main/java/me/lethunderhawk/minion/enchantedVariant/recipe/RecipeManager.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/EnchantedVariantRecipeManager.java

@@ -1,5 +1,6 @@
-package me.lethunderhawk.minion.enchantedVariant.recipe;
+package me.lethunderhawk.custom.recipes.enchantedVariant;
 
+import me.lethunderhawk.custom.recipes.enchantedVariant.recipe.EnchantedItemRecipe;
 import org.bukkit.Bukkit;
 import org.bukkit.Material;
 import org.bukkit.NamespacedKey;
@@ -15,11 +16,11 @@ import org.bukkit.plugin.Plugin;
 import java.util.HashMap;
 import java.util.Map;
 
-public class RecipeManager implements Listener {
+public class EnchantedVariantRecipeManager implements Listener {
     private final Plugin plugin;
     private final Map<Material, ItemStack> registeredRecipes = new HashMap<>();
 
-    public RecipeManager(Plugin plugin) {
+    public EnchantedVariantRecipeManager(Plugin plugin) {
         this.plugin = plugin;
         Bukkit.getPluginManager().registerEvents(this, plugin);
     }

+ 3 - 3
src/main/java/me/lethunderhawk/minion/enchantedVariant/item/EnchantedCobblestone.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/EnchantedCobblestone.java

@@ -1,6 +1,6 @@
-package me.lethunderhawk.minion.enchantedVariant.item;
+package me.lethunderhawk.custom.recipes.enchantedVariant.item;
 
-import me.lethunderhawk.minion.enchantedVariant.item.abstraction.EnchantedItem;
+import me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction.EnchantedVariant;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.Material;
@@ -9,7 +9,7 @@ import org.bukkit.inventory.meta.ItemMeta;
 
 import java.util.List;
 
-public class EnchantedCobblestone extends EnchantedItem {
+public class EnchantedCobblestone extends EnchantedVariant {
 
     public EnchantedCobblestone() {
         super("enchanted_cobblestone", Component.text("Enchanted Cobblestone", NamedTextColor.GREEN),

+ 3 - 3
src/main/java/me/lethunderhawk/minion/enchantedVariant/item/EnchantedRedstone.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/EnchantedRedstone.java

@@ -1,6 +1,6 @@
-package me.lethunderhawk.minion.enchantedVariant.item;
+package me.lethunderhawk.custom.recipes.enchantedVariant.item;
 
-import me.lethunderhawk.minion.enchantedVariant.item.abstraction.EnchantedItem;
+import me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction.EnchantedVariant;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.Material;
@@ -9,7 +9,7 @@ import org.bukkit.inventory.meta.ItemMeta;
 
 import java.util.List;
 
-public class EnchantedRedstone extends EnchantedItem {
+public class EnchantedRedstone extends EnchantedVariant {
 
     public EnchantedRedstone() {
         super("enchanted_redstone", Component.text("Enchanted Redstone", NamedTextColor.GREEN),

+ 1 - 1
src/main/java/me/lethunderhawk/minion/enchantedVariant/item/abstraction/DisableEnchantedItemPlacingListener.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/abstraction/DisableEnchantedItemPlacingListener.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.minion.enchantedVariant.item.abstraction;
+package me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction;
 
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;

+ 1 - 1
src/main/java/me/lethunderhawk/minion/enchantedVariant/item/abstraction/EnchantedItemRegistry.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/abstraction/EnchantedItemRegistry.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.minion.enchantedVariant.item.abstraction;
+package me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction;
 
 import org.bukkit.NamespacedKey;
 import org.bukkit.inventory.ItemStack;

+ 3 - 3
src/main/java/me/lethunderhawk/minion/enchantedVariant/item/abstraction/EnchantedItem.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/item/abstraction/EnchantedVariant.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.minion.enchantedVariant.item.abstraction;
+package me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction;
 
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.main.BazaarFlux;
@@ -16,7 +16,7 @@ import java.util.List;
 /**
  * Abstract base class for all enchanted items
  */
-public abstract class EnchantedItem {
+public abstract class EnchantedVariant {
     protected final Component displayName;
     protected final List<Component> lore;
     protected final Material baseMaterial;
@@ -26,7 +26,7 @@ public abstract class EnchantedItem {
     private final String itemId;
     private final ItemStack itemStack;
 
-    public EnchantedItem(String itemId, Component displayName, List<Component> lore, Material baseMaterial) {
+    public EnchantedVariant(String itemId, Component displayName, List<Component> lore, Material baseMaterial) {
         this.displayName = displayName;
         this.lore = lore;
         this.baseMaterial = baseMaterial;

+ 1 - 1
src/main/java/me/lethunderhawk/minion/enchantedVariant/recipe/EnchantedItemRecipe.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/recipe/EnchantedItemRecipe.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.minion.enchantedVariant.recipe;
+package me.lethunderhawk.custom.recipes.enchantedVariant.recipe;
 
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.main.BazaarFlux;

+ 1 - 1
src/main/java/me/lethunderhawk/minion/enchantedVariant/recipe/StackSizeIngredient.java → src/main/java/me/lethunderhawk/custom/recipes/enchantedVariant/recipe/StackSizeIngredient.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.minion.enchantedVariant.recipe;
+package me.lethunderhawk.custom.recipes.enchantedVariant.recipe;
 
 import org.bukkit.Material;
 import org.bukkit.inventory.ItemStack;

+ 72 - 0
src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeBrowserGUI.java

@@ -0,0 +1,72 @@
+package me.lethunderhawk.custom.recipes.gui;
+
+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 org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class RecipeBrowserGUI extends InventoryGUI {
+
+    private RecipeManager manager;
+    private int page = 0;
+
+    public RecipeBrowserGUI(Player player) {
+        super("Recipe Browser", 54, player);
+    }
+
+    @Override
+    public void performAdditionalComputationOnPlayer(Player player) {
+        this.manager = FluxService.get(RecipeManager.class);
+    }
+
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+        List<CustomRecipe> recipes = new ArrayList<>(manager.getRecipes());
+
+        int start = page * 28;
+
+        for (int i = 0; i < 28 && start + i < recipes.size(); i++) {
+
+            CustomRecipe recipe = recipes.get(start + i);
+
+            ItemStack 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"
+            ));
+
+            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));;
+                }
+
+            });
+
+        }
+
+    }
+
+    @Override
+    public void update() {
+
+    }
+}

+ 114 - 0
src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeEditorGUI.java

@@ -0,0 +1,114 @@
+package me.lethunderhawk.custom.recipes.gui;
+
+import me.lethunderhawk.custom.recipes.CraftingTableRecipe;
+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.itemdesign.ItemOptions;
+import me.lethunderhawk.main.BazaarFlux;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.UUID;
+
+public class RecipeEditorGUI extends InventoryGUI {
+
+    private final RecipeManager manager;
+    private final CustomRecipe editingRecipe;
+
+    public RecipeEditorGUI(Player player, RecipeManager manager, CustomRecipe recipe) {
+
+        super("Recipe Editor", 45, player);
+
+        this.manager = manager;
+        this.editingRecipe = recipe;
+
+    }
+
+    private void loadRecipe() {
+
+        if (!(editingRecipe instanceof CraftingTableRecipe crafting))
+            return;
+
+        ItemStack[] matrix = crafting.getMatrix();
+
+        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(25, crafting.getResult());
+
+    }
+
+    private void drawButtons() {
+
+        setItemWithClickAction(40, new ItemOptions(Material.GREEN_WOOL)
+                .buildItemStack(), (player, type) -> save(player));
+
+        setItemWithClickAction(41,
+                new ItemOptions(Material.RED_WOOL)
+                        .buildItemStack(), (player, type) -> delete(player));
+
+        setCloseButton(42);
+
+    }
+
+    private void save(Player player) {
+
+        ItemStack[] matrix = new ItemStack[9];
+
+        int[] slots = {
+                10,11,12,
+                19,20,21,
+                28,29,30
+        };
+
+        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);
+
+        manager.registerRecipe(recipe);
+
+        player.sendMessage("§aRecipe saved.");
+
+    }
+
+    private void delete(Player player) {
+        manager.unregisterRecipe(editingRecipe.getKey());
+
+        player.sendMessage("§cRecipe deleted.");
+    }
+
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+        loadRecipe();
+        drawButtons();
+    }
+
+    @Override
+    public void update() {
+
+    }
+}

+ 65 - 0
src/main/java/me/lethunderhawk/custom/recipes/gui/RecipeViewerGUI.java

@@ -0,0 +1,65 @@
+package me.lethunderhawk.custom.recipes.gui;
+
+import me.lethunderhawk.custom.recipes.CraftingTableRecipe;
+import me.lethunderhawk.custom.recipes.CustomRecipe;
+import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class RecipeViewerGUI extends InventoryGUI {
+
+    private final CustomRecipe recipe;
+
+    public RecipeViewerGUI(Player player, CustomRecipe recipe) {
+
+        super("Recipe Viewer", 45, player);
+        this.recipe = recipe;
+    }
+
+    private ItemStack createArrow() {
+
+        ItemStack arrow = new ItemStack(Material.ARROW);
+
+        ItemMeta meta = arrow.getItemMeta();
+        meta.setDisplayName("§eCrafts Into");
+        arrow.setItemMeta(meta);
+
+        return arrow;
+    }
+
+    @Override
+    public void buildContent() {
+
+    }
+
+    @Override
+    public void update() {
+        if (recipe instanceof CraftingTableRecipe crafting) {
+
+            ItemStack[] matrix = crafting.getMatrix();
+
+            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(23, createArrow());
+
+        setItem(25, recipe.getResult());
+    }
+}

+ 2 - 2
src/main/java/me/lethunderhawk/dungeon/DungeonModule.java

@@ -3,11 +3,11 @@ package me.lethunderhawk.dungeon;
 import me.lethunderhawk.dungeon.command.DungeonCommand;
 import me.lethunderhawk.dungeon.manager.DungeonManager;
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.main.BazaarFlux;
 import org.bukkit.plugin.java.JavaPlugin;
 
-public class DungeonModule extends BazaarFluxModule {
+public class DungeonModule extends FluxAPIModule {
 
     public DungeonModule(JavaPlugin plugin) {
         super(plugin);

+ 6 - 4
src/main/java/me/lethunderhawk/dungeon/command/DungeonCommand.java

@@ -14,10 +14,12 @@ import org.bukkit.entity.Player;
 public class DungeonCommand extends CustomCommand {
 
     public DungeonCommand() {
-        super(
-                new CommandNode("dungeon", "Main Dungeon command", null),
-                FluxService.get(DungeonModule.class)
-        );
+        super(FluxService.get(DungeonModule.class));
+    }
+
+    @Override
+    public void setRootCommand() {
+        rootCommand = new CommandNode("dungeon", "Main Dungeon command", null);
     }
 
     @Override

+ 2 - 3
src/main/java/me/lethunderhawk/dungeon/gui/DungeonGUI.java

@@ -6,13 +6,12 @@ public class DungeonGUI extends InventoryGUI {
 
     public DungeonGUI() {
         super("Dungeon Browser", 36);
-        buildItems();
     }
 
-    private void buildItems() {
+    @Override
+    public void buildContent() {
         fillGlassPaneBackground();
         setCloseButton(31);
-
     }
 
     @Override

+ 2 - 2
src/main/java/me/lethunderhawk/economy/EconomyModule.java

@@ -7,11 +7,11 @@ import me.lethunderhawk.economy.currency.EconomyManager;
 import me.lethunderhawk.economy.listener.PlayerJoinListener;
 import me.lethunderhawk.scoreboard.ScoreboardManager;
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.tradeplugin.api.TradePlaceholder;
 import org.bukkit.plugin.java.JavaPlugin;
 
-public class EconomyModule extends BazaarFluxModule {
+public class EconomyModule extends FluxAPIModule {
 
     private EconomyAPI economyAPI;
     private ScoreboardManager scoreboardManager;

+ 7 - 1
src/main/java/me/lethunderhawk/economy/command/EcoCommand.java

@@ -19,9 +19,15 @@ public class EcoCommand extends CustomCommand {
     private final EconomyModule module;
 
     public EcoCommand(EconomyModule module) {
-        super(new CommandNode("eco", "Main economy command", null), module);
+        super(module);
         this.module = module;
     }
+
+    @Override
+    public void setRootCommand() {
+        rootCommand = new CommandNode("eco", "Main economy command", null);
+    }
+
     @Override
     public void createCommands() {
         CommandNode set = registerCommand("set", "Set the balance of a player", this::setEco);

+ 9 - 7
src/main/java/me/lethunderhawk/main/BazaarFlux.java

@@ -2,10 +2,11 @@ package me.lethunderhawk.main;
 
 import me.lethunderhawk.clans.ClanModule;
 import me.lethunderhawk.custom.item.CustomItemModule;
+import me.lethunderhawk.custom.recipes.CustomRecipeModule;
 import me.lethunderhawk.economy.EconomyModule;
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.gui.InventoryManager;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.npc.NPCRegistrator;
 import me.lethunderhawk.profile.ProfileModule;
 import me.lethunderhawk.tradeplugin.TradeModule;
@@ -17,7 +18,7 @@ import java.util.List;
 
 public class BazaarFlux extends JavaPlugin{
 
-    private final List<BazaarFluxModule> bazaarFluxModules = new ArrayList<>();
+    private final List<FluxAPIModule> FluxAPIModules = new ArrayList<>();
     @Override
     public void onEnable() {
         FluxService.register(BazaarFlux.class, this);
@@ -28,26 +29,27 @@ public class BazaarFlux extends JavaPlugin{
                 new TradeModule(this),
                 new ClanModule(this),
                 new EconomyModule(this),
+                new CustomRecipeModule(this),
                 //new MinionModule(this),
                 //new DungeonModule(this),
                 new WorldModule(this),
                 new ProfileModule(this),
                 new NPCRegistrator(this));
     }
-    private void registerAllModules(BazaarFluxModule... modules){
-        for(BazaarFluxModule module : modules){
-            bazaarFluxModules.add(module);
+    private void registerAllModules(FluxAPIModule... modules){
+        for(FluxAPIModule module : modules){
+            FluxAPIModules.add(module);
             registerModule(module);
         }
     }
-    private void registerModule(BazaarFluxModule module){
+    private void registerModule(FluxAPIModule module){
         FluxService.registerModule(module.getClass(), module);
         module.onEnable();
     }
 
     @Override
     public void onDisable(){
-        for(BazaarFluxModule module : bazaarFluxModules){
+        for(FluxAPIModule module : FluxAPIModules){
             module.onDisable();
             FluxService.unregisterModule(module.getClass());
         }

+ 15 - 15
src/main/java/me/lethunderhawk/minion/MinionModule.java

@@ -1,15 +1,14 @@
 package me.lethunderhawk.minion;
 
+import me.lethunderhawk.custom.recipes.enchantedVariant.EnchantedVariantRecipeManager;
+import me.lethunderhawk.custom.recipes.enchantedVariant.item.EnchantedCobblestone;
+import me.lethunderhawk.custom.recipes.enchantedVariant.item.EnchantedRedstone;
+import me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction.DisableEnchantedItemPlacingListener;
+import me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction.EnchantedVariant;
+import me.lethunderhawk.custom.recipes.enchantedVariant.item.abstraction.EnchantedItemRegistry;
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
-import me.lethunderhawk.main.BazaarFlux;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.minion.command.MinionCommand;
-import me.lethunderhawk.minion.enchantedVariant.item.EnchantedCobblestone;
-import me.lethunderhawk.minion.enchantedVariant.item.EnchantedRedstone;
-import me.lethunderhawk.minion.enchantedVariant.item.abstraction.DisableEnchantedItemPlacingListener;
-import me.lethunderhawk.minion.enchantedVariant.item.abstraction.EnchantedItem;
-import me.lethunderhawk.minion.enchantedVariant.item.abstraction.EnchantedItemRegistry;
-import me.lethunderhawk.minion.enchantedVariant.recipe.RecipeManager;
 import me.lethunderhawk.minion.factory.MinionFactory;
 import me.lethunderhawk.minion.impl.type.CobbleMinionType;
 import me.lethunderhawk.minion.impl.type.RedstoneMinionType;
@@ -26,13 +25,13 @@ import org.bukkit.plugin.java.JavaPlugin;
 import java.util.ArrayList;
 import java.util.List;
 
-public class MinionModule extends BazaarFluxModule {
+public class MinionModule extends FluxAPIModule {
 
     private List<ItemStack> allMinions;
     private MinionListener minionListener;
     private MinionFactory factory;
     private MinionStorage storage;
-    private RecipeManager recipeManager;
+    private EnchantedVariantRecipeManager enchantedVariantRecipeManager;
 
     public MinionModule(JavaPlugin plugin) {
         super(plugin);
@@ -63,21 +62,22 @@ public class MinionModule extends BazaarFluxModule {
 
     private void customRecipes() {
 
-        this.recipeManager = new RecipeManager(FluxService.get(BazaarFlux.class));
+        this.enchantedVariantRecipeManager = FluxService.get(EnchantedVariantRecipeManager.class);
+
         plugin.getServer().getPluginManager().registerEvents(new DisableEnchantedItemPlacingListener(), plugin);
 
         registerRecipes();
         clearRecipes();
     }
     private void registerRecipes() {
-        EnchantedItem enchantedCobblestone = new EnchantedCobblestone();
-        recipeManager.registerRecipe(
+        EnchantedVariant enchantedCobblestone = new EnchantedCobblestone();
+        enchantedVariantRecipeManager.registerRecipe(
                 Material.COBBLESTONE,
                 enchantedCobblestone.getItemStack()
         );
         EnchantedItemRegistry.registerNoPlaceItem(enchantedCobblestone.getItemIdKey());
-        EnchantedItem enchantedRedstone = new EnchantedRedstone();
-        recipeManager.registerRecipe(
+        EnchantedVariant enchantedRedstone = new EnchantedRedstone();
+        enchantedVariantRecipeManager.registerRecipe(
                 Material.REDSTONE,
                 enchantedRedstone.getItemStack()
         );

+ 7 - 1
src/main/java/me/lethunderhawk/minion/command/MinionCommand.java

@@ -16,9 +16,15 @@ public class MinionCommand extends CustomCommand {
     private final MinionModule module;
 
     public MinionCommand(MinionModule minionModule) {
-        super(new CommandNode("clan", "Main clan command", null), minionModule);
+        super(minionModule);
         this.module = minionModule;
     }
+
+    @Override
+    public void setRootCommand() {
+        rootCommand = new CommandNode("minion", "Main minion command", null);
+    }
+
     @Override
     public void createCommands() {
         registerCommand("all", "get all minions", this::getAll);

+ 8 - 9
src/main/java/me/lethunderhawk/minion/ui/MinionMenu.java

@@ -27,10 +27,9 @@ public class MinionMenu extends InventoryGUI implements InventoryGUI.AutoCloseHa
     public MinionMenu(PlacedMinion minion) {
         super(minion.getType().getName() + " " + minion.getRomanLevel(), 54);
         this.minion = minion;
-        buildContents();
     }
-
-    private void buildContents() {
+    @Override
+    public void buildContent() {
         fillGlassPaneBackground();
 
         ItemStack minionItem = minion.getHelmetItem().clone();
@@ -247,7 +246,7 @@ public class MinionMenu extends InventoryGUI implements InventoryGUI.AutoCloseHa
     }
 
     private void updateDisplay(Player player) {
-        buildContents();
+        buildContent();
         player.updateInventory();
     }
 
@@ -259,7 +258,7 @@ public class MinionMenu extends InventoryGUI implements InventoryGUI.AutoCloseHa
 
     @Override
     public void update() {
-        buildContents();
+        buildContent();
     }
 
     public UUID getMinionId() {
@@ -273,10 +272,10 @@ public class MinionMenu extends InventoryGUI implements InventoryGUI.AutoCloseHa
         public MinionStorageMenu(PlacedMinion minion) {
             super(minion.getType().getName() + " Storage", 54);
             this.minion = minion;
-            buildContents();
         }
 
-        private void buildContents() {
+        @Override
+        public void buildContent() {
             fillBackground(Material.GRAY_STAINED_GLASS_PANE, " ");
 
 //            List<ItemStack> storage = minion.getStorageContents();
@@ -307,10 +306,10 @@ public class MinionMenu extends InventoryGUI implements InventoryGUI.AutoCloseHa
         public MinionSettingsMenu(PlacedMinion minion) {
             super(minion.getType().getName() + " Settings", 27);
             this.minion = minion;
-            buildContents();
         }
 
-        private void buildContents() {
+        @Override
+        public void buildContent() {
             fillBackground(Material.GRAY_STAINED_GLASS_PANE, " ");
 
 

+ 7 - 7
src/main/java/me/lethunderhawk/minion/ui/minionList/MinionLevelListMenu.java

@@ -16,10 +16,14 @@ public class MinionLevelListMenu extends InventoryGUI {
     public MinionLevelListMenu(String typeId) {
         super(typeId + " Minions", 27);
         this.typeId = typeId;
-        buildLevelList(typeId);
     }
 
-    private void buildLevelList(String typeId) {
+    private void getMinionItem(Player player, ItemStack minionItem) {
+        player.getInventory().addItem(minionItem);
+    }
+
+    @Override
+    public void buildContent() {
         fillBackground(Material.GRAY_STAINED_GLASS_PANE, " ");
         MinionLevelTable table = MinionRegistry.get(typeId).getLevelTable();
         int slot = 0;
@@ -35,12 +39,8 @@ public class MinionLevelListMenu extends InventoryGUI {
                 (player, clickType) -> openPrevious(player));
     }
 
-    private void getMinionItem(Player player, ItemStack minionItem) {
-        player.getInventory().addItem(minionItem);
-    }
-
     @Override
     public void update() {
-        buildLevelList(this.typeId);
+
     }
 }

+ 8 - 8
src/main/java/me/lethunderhawk/minion/ui/minionList/MinionListMenu.java

@@ -15,10 +15,16 @@ public class MinionListMenu extends InventoryGUI {
 
     public MinionListMenu() {
         super("Minion List", 27);
-        buildMenu();
     }
 
-    private void buildMenu() {
+    private void openBiggerMinionListMenu(Player player, String typeId) {
+        MinionLevelListMenu menu = new MinionLevelListMenu(typeId);
+        openNext(player, menu);
+    }
+
+
+    @Override
+    public void buildContent() {
         fillBackground(Material.GRAY_STAINED_GLASS_PANE, " ");
         Map<String, MinionType> types = MinionRegistry.getAllRegisteredTypes();
         int slot = 0;
@@ -32,12 +38,6 @@ public class MinionListMenu extends InventoryGUI {
         }
     }
 
-    private void openBiggerMinionListMenu(Player player, String typeId) {
-        MinionLevelListMenu menu = new MinionLevelListMenu(typeId);
-        openNext(player, menu);
-    }
-
-
     @Override
     public void update() {
 

+ 2 - 2
src/main/java/me/lethunderhawk/npc/NPCRegistrator.java

@@ -1,11 +1,11 @@
 package me.lethunderhawk.npc;
 
 import me.lethunderhawk.fluxapi.npc.registry.NPCRegistry;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.npc.types.IslandNPC;
 import org.bukkit.plugin.java.JavaPlugin;
 
-public class NPCRegistrator extends BazaarFluxModule {
+public class NPCRegistrator extends FluxAPIModule {
     public NPCRegistrator(JavaPlugin plugin) {
         super(plugin);
     }

+ 63 - 7
src/main/java/me/lethunderhawk/profile/ProfileModule.java

@@ -1,12 +1,18 @@
 package me.lethunderhawk.profile;
 
 import me.lethunderhawk.fluxapi.profile.representation.FluxProfile;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.profile.command.ProfileCommand;
-import me.lethunderhawk.profile.test.BazaarFluxStats;
+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;
 
-public class ProfileModule extends BazaarFluxModule {
+public class ProfileModule extends FluxAPIModule {
     public ProfileModule(JavaPlugin plugin) {
         super(plugin);
     }
@@ -18,13 +24,63 @@ public class ProfileModule extends BazaarFluxModule {
 
     @Override
     public void onEnable() {
+        ConfigurationSerialization.registerClass(SackContent.class);
+        registerMiningSackTypes();
+        registerForagingSackTypes();
+
+        FluxProfile.registerSubProfile(
+                "mining_sack",
+                MiningSack.class,
+                uuid -> new MiningSack()
+        );
+
         FluxProfile.registerSubProfile(
-                "stats",
-                BazaarFluxStats.class,
-                uuid -> new BazaarFluxStats()
+                "foraging_sack",
+                ForagingSack.class,
+                uuid -> new ForagingSack()
         );
 
-        plugin.getCommand("profile").setExecutor(new ProfileCommand(this));
+        ProfileCommand command = new ProfileCommand(this);
+        plugin.getCommand("profile").setExecutor(command);
+        plugin.getCommand("profile").setTabCompleter(command);
+    }
+
+    private void registerForagingSackTypes() {
+        registerLogs();
+        registerLeaves();
+        Sack.registerType(SackType.FORAGING, Material.STICK, 1024);
+    }
+
+    private void registerLeaves() {
+        Sack.registerType(SackType.FORAGING, Material.OAK_LEAVES);
+        Sack.registerType(SackType.FORAGING, Material.SPRUCE_LEAVES);
+        Sack.registerType(SackType.FORAGING, Material.BIRCH_LOG);
+        Sack.registerType(SackType.FORAGING, Material.JUNGLE_LEAVES);
+        Sack.registerType(SackType.FORAGING, Material.ACACIA_LEAVES);
+        Sack.registerType(SackType.FORAGING, Material.DARK_OAK_LEAVES);
+        Sack.registerType(SackType.FORAGING, Material.MANGROVE_LEAVES);
+        Sack.registerType(SackType.FORAGING, Material.CHERRY_LEAVES);
+        Sack.registerType(SackType.FORAGING, Material.PALE_OAK_LEAVES);
+    }
+
+    private void registerLogs() {
+        Sack.registerType(SackType.FORAGING, Material.OAK_LOG);
+        Sack.registerType(SackType.FORAGING, Material.SPRUCE_LOG);
+        Sack.registerType(SackType.FORAGING, Material.BIRCH_LOG);
+        Sack.registerType(SackType.FORAGING, Material.JUNGLE_LOG);
+        Sack.registerType(SackType.FORAGING, Material.ACACIA_LOG);
+        Sack.registerType(SackType.FORAGING, Material.DARK_OAK_LOG);
+        Sack.registerType(SackType.FORAGING, Material.MANGROVE_LOG);
+        Sack.registerType(SackType.FORAGING, Material.CHERRY_LOG);
+        Sack.registerType(SackType.FORAGING, Material.PALE_OAK_LOG);
+        Sack.registerType(SackType.FORAGING, Material.CRIMSON_STEM);
+        Sack.registerType(SackType.FORAGING, Material.WARPED_STEM);
+    }
+
+    private void registerMiningSackTypes() {
+        Sack.registerType(SackType.MINING, Material.COBBLESTONE);
+        Sack.registerType(SackType.MINING, Material.STONE);
+        Sack.registerType(SackType.MINING, Material.GOLD_INGOT);
     }
 
     @Override

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

@@ -6,28 +6,40 @@ 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.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
+import me.lethunderhawk.profile.data.sacks.mining.MiningSackGUI;
 import me.lethunderhawk.profile.gui.StatsGUI;
-import me.lethunderhawk.profile.test.BazaarFluxStats;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 
 public class ProfileCommand extends CustomCommand {
-    public ProfileCommand(BazaarFluxModule module) {
-        super(new CommandNode("profile", "base command", null), module);
+    public ProfileCommand(FluxAPIModule module) {
+        super(module);
+    }
+
+    @Override
+    public void setRootCommand() {
+        this.rootCommand = new CommandNode("profile", "base command", this::showProfile);
     }
 
     @Override
     public void createCommands() {
         registerCommand("show", "shows your profile", this::showProfile);
+        registerCommand("sacks", "shows your sacks", this::showSacks);
+    }
+
+    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();
+        /*BazaarFluxStats stats = profile.getSubProfile( "stats", BazaarFluxStats.class);
+        stats.addKill();*/
     }
 
     private void showProfile(CommandSender sender, String[] strings) {

+ 99 - 0
src/main/java/me/lethunderhawk/profile/data/sacks/Sack.java

@@ -0,0 +1,99 @@
+package me.lethunderhawk.profile.data.sacks;
+
+import me.lethunderhawk.fluxapi.profile.AnnotatedProfileCategory;
+import me.lethunderhawk.fluxapi.profile.AnnotatedSerializable;
+import org.bukkit.Material;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public abstract class Sack extends AnnotatedProfileCategory {
+    @AnnotatedSerializable(prefix = "sackType")
+    private final SackType sackType;
+
+    @AnnotatedSerializable(prefix = "items")
+    private Map<Material, SackContent> items = new HashMap<>();
+
+    private static final Map<SackType, Map<Material, SackContent>> registeredMap = new HashMap<>();
+
+    public static void registerType(SackType type, Material material){
+        if (!registeredMap.containsKey(type)) {
+            registeredMap.put(type, new HashMap<>());
+        }
+        if(!registeredMap.get(type).containsKey(material)){
+            registeredMap.get(type).put(material, createDefaultCapacitySackItem(material));
+        }
+    }
+
+    public static void registerType(SackType type, Material material, int maxAmount){
+        if (!registeredMap.containsKey(type)) {
+            registeredMap.put(type, new HashMap<>());
+        }
+        if(!registeredMap.get(type).containsKey(material)){
+            registeredMap.get(type).put(material, createSackContent(material, maxAmount));
+        }
+    }
+
+    public static Map<Material, SackContent> getRegisteredType(SackType sackType){
+        return registeredMap.getOrDefault(sackType, null);
+    }
+
+    public Map<Material, SackContent> getAssociatedSackMap(){
+        return getRegisteredType(sackType);
+    }
+
+    public Sack(SackType type){
+        this.sackType = type;
+        items.putAll(registeredMap.get(type));
+    }
+
+    public SackType getType(){
+        return sackType;
+    }
+
+    public int addItem(Material material) {
+        return addItem(material, 1);
+    }
+
+    public int addItem(Material material, int amount) {
+        if(getAssociatedSackMap() == null || !getAssociatedSackMap().containsKey(material)){
+            return amount;
+        }
+        items.putIfAbsent(material, createDefaultCapacitySackItem(material));
+        return items.get(material).addAmount(amount);
+    }
+
+    public int takeOutItem(Material material) {
+        return takeOutItem(material, 1);
+    }
+
+    public int takeOutItem(Material material, int amount) {
+        if(getAssociatedSackMap() == null || !getAssociatedSackMap().containsKey(material)){
+            return amount;
+        }
+        items.putIfAbsent(material, createDefaultCapacitySackItem(material));
+        return items.get(material).removeAmount(amount);
+    }
+
+    public int getMaxMaterialAmount(Material material) {
+        if(getAssociatedSackMap() == null) return 0;
+        if (!getAssociatedSackMap().containsKey(material)) {
+            getAssociatedSackMap().putIfAbsent(material, createDefaultCapacitySackItem(material));
+        }
+        return getAssociatedSackMap().get(material).getMaxAmount();
+    }
+
+    public int getMaterialAmount(Material material) {
+        if (!items.containsKey(material)) {
+            items.put(material, createDefaultCapacitySackItem(material));
+        }
+        return items.get(material).getAmount();
+    }
+    private static SackContent createSackContent(Material material, int maxAmount) {
+        return new SackContent(material, 0, maxAmount);
+    }
+
+    private static SackContent createDefaultCapacitySackItem(Material material){
+        return new SackContent(material, 0, 512);
+    }
+}

+ 76 - 0
src/main/java/me/lethunderhawk/profile/data/sacks/SackContent.java

@@ -0,0 +1,76 @@
+package me.lethunderhawk.profile.data.sacks;
+
+import me.lethunderhawk.fluxapi.profile.AnnotatedProfileCategory;
+import me.lethunderhawk.fluxapi.profile.AnnotatedSerializable;
+import org.bukkit.Material;
+
+import java.util.Map;
+
+public class SackContent extends AnnotatedProfileCategory {
+    @AnnotatedSerializable(prefix = "material")
+    private final String material;
+    @AnnotatedSerializable(prefix = "amount")
+    private int amount;
+    @AnnotatedSerializable(prefix = "maxAmount")
+    private int maxAmount;
+
+    /**
+     *
+     * @param addAmount The amount to add to the SackItem
+     * @return The Leftover / overflow not being able to put into the SackItem
+     */
+    public int addAmount(int addAmount) {
+
+        int spaceLeft = maxAmount - this.amount;
+
+        if (addAmount > spaceLeft) {
+            this.amount = maxAmount;
+            return addAmount - spaceLeft; // leftover
+        }
+
+        this.amount += addAmount;
+        return 0; // no leftover
+    }
+
+    /**
+     *
+     * @param removeAmount The amount to remove from the SackItem
+     * @return The amount actually removed
+     */
+    public int removeAmount(int removeAmount) {
+
+        if (removeAmount >= this.amount) {
+            int removed = this.amount;
+            this.amount = 0;
+            return removed;
+        }
+
+        this.amount -= removeAmount;
+        return removeAmount;
+    }
+
+
+    public SackContent(Material material, int amount, int maxAmount) {
+        this.material = material.name();
+        this.amount = amount;
+        this.maxAmount = maxAmount;
+    }
+
+    public SackContent(Map<String, Object> map) {
+        this.material = (String) map.get("material");
+        this.amount = (int) map.get("amount");
+        this.maxAmount = (int) map.get("maxAmount");
+    }
+
+    public Material getMaterial() {
+        return Material.valueOf(this.material);
+    }
+
+    public int getMaxAmount() {
+        return maxAmount;
+    }
+
+    public int getAmount() {
+        return amount;
+    }
+}

+ 6 - 0
src/main/java/me/lethunderhawk/profile/data/sacks/SackType.java

@@ -0,0 +1,6 @@
+package me.lethunderhawk.profile.data.sacks;
+
+public enum SackType {
+    MINING,
+    FORAGING
+}

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

@@ -0,0 +1,10 @@
+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);
+    }
+}

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

@@ -0,0 +1,203 @@
+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();
+    }
+}

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

@@ -0,0 +1,10 @@
+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);
+    }
+}

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

@@ -0,0 +1,143 @@
+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);
+        }
+    }
+}

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

@@ -16,16 +16,7 @@ public class StatsGUI extends InventoryGUI {
     public StatsGUI(Player player) {
         super("Stats", 45);
         this.player = player;
-        buildGUI();
     }
-
-    private void buildGUI() {
-        fillGlassPaneBackground();
-        ItemStack item = createPlayerHead();
-
-        setItemWithClickAction(4, item, this::displayEasterEgg);
-    }
-
     private void displayEasterEgg(Player player, ClickType type) {
         setItem(34, createEasterEgg());
     }
@@ -47,6 +38,14 @@ public class StatsGUI extends InventoryGUI {
                 ));
     }
 
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+        ItemStack item = createPlayerHead();
+
+        setItemWithClickAction(4, item, this::displayEasterEgg);
+    }
+
     @Override
     public void update() {
 

+ 0 - 46
src/main/java/me/lethunderhawk/profile/test/BazaarFluxStats.java

@@ -1,46 +0,0 @@
-package me.lethunderhawk.profile.test;
-
-import org.bukkit.configuration.serialization.ConfigurationSerializable;
-import org.jspecify.annotations.NonNull;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class BazaarFluxStats implements ConfigurationSerializable {
-
-    private int kills;
-    private int deaths;
-    private transient Runnable changeListener;
-
-    public BazaarFluxStats() {
-    }
-
-    public BazaarFluxStats(Map<String, Object> map) {
-        this.kills = (int) map.get("kills");
-        this.deaths = (int) map.get("deaths");
-    }
-
-    public void addKill() {
-        this.kills++;
-        notifyChange();
-    }
-
-    private void notifyChange() {
-        if (changeListener != null) {
-            changeListener.run();
-        }
-    }
-
-    @Override
-    public @NonNull Map<String, Object> serialize() {
-        Map<String, Object> map = new HashMap<>();
-        map.put("kills", kills);
-        map.put("deaths", deaths);
-        return map;
-    }
-
-    public static BazaarFluxStats deserialize(Map<String, Object> map) {
-        return new BazaarFluxStats(map);
-    }
-
-}

+ 2 - 2
src/main/java/me/lethunderhawk/tradeplugin/TradeModule.java

@@ -1,6 +1,6 @@
 package me.lethunderhawk.tradeplugin;
 
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.tradeplugin.command.TradeAcceptCommand;
 import me.lethunderhawk.tradeplugin.command.TradeCommand;
 import me.lethunderhawk.tradeplugin.listener.PlayerInteractListener;
@@ -9,7 +9,7 @@ import me.lethunderhawk.tradeplugin.trade.TradeManager;
 import me.lethunderhawk.tradeplugin.trade.TradeRequestManager;
 import org.bukkit.plugin.java.JavaPlugin;
 
-public class TradeModule extends BazaarFluxModule {
+public class TradeModule extends FluxAPIModule {
 
     private static TradeManager tradeManager;
     private static TradeRequestManager requestManager;

+ 2 - 3
src/main/java/me/lethunderhawk/tradeplugin/input/player/NumberInputGUI.java

@@ -40,10 +40,9 @@ public class NumberInputGUI extends InventoryGUI implements InventoryGUI.AutoClo
         this.currentValue = Math.max(minValue, Math.min(maxValue, defaultValue));
         this.callback = callback;
 
-        buildContents();
     }
-
-    private void buildContents() {
+    @Override
+    public void buildContent() {
         update();
 
         // confirm button

+ 24 - 0
src/main/java/me/lethunderhawk/util/StringUtil.java

@@ -0,0 +1,24 @@
+package me.lethunderhawk.util;
+
+public class StringUtil {
+    public static String toCamelCase(String text, String regex){
+        String[] words = text.split(regex);
+        StringBuilder builder = new StringBuilder();
+        for (String s : words) {
+            String word = s;
+            word = word.isEmpty() ? word : Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase();
+            builder.append(word + " ");
+        }
+        return builder.toString();
+    }
+    public static String toCamelCase(String text){
+        String[] words = text.split("[\\W_]+");
+        StringBuilder builder = new StringBuilder();
+        for (String s : words) {
+            String word = s;
+            word = word.isEmpty() ? word : Character.toUpperCase(word.charAt(0)) + word.substring(1).toLowerCase();
+            builder.append(word + " ");
+        }
+        return builder.toString();
+    }
+}

+ 2 - 2
src/main/java/me/lethunderhawk/world/WorldModule.java

@@ -1,7 +1,7 @@
 package me.lethunderhawk.world;
 
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.main.BazaarFlux;
 import me.lethunderhawk.world.command.WorldCommand;
 import me.lethunderhawk.world.island.IslandBootstrapListener;
@@ -12,7 +12,7 @@ import org.bukkit.plugin.java.JavaPlugin;
 import java.io.File;
 import java.util.Objects;
 
-public class WorldModule extends BazaarFluxModule {
+public class WorldModule extends FluxAPIModule {
 
     private IslandBootstrapListener bootstrapListener;
 

+ 8 - 3
src/main/java/me/lethunderhawk/world/command/WorldCommand.java

@@ -3,7 +3,7 @@ package me.lethunderhawk.world.command;
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.command.CommandNode;
 import me.lethunderhawk.fluxapi.util.command.CustomCommand;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.main.BazaarFlux;
 import me.lethunderhawk.world.manager.NestedWorldStorageManager;
 import net.kyori.adventure.text.Component;
@@ -21,11 +21,16 @@ import java.util.function.BiConsumer;
 public class WorldCommand extends CustomCommand {
     private Map<String, Location> locations = new HashMap<>();
 
-    public WorldCommand(BazaarFluxModule module) {
-        super(new CommandNode("warp", "Hop between worlds!", null), module);
+    public WorldCommand(FluxAPIModule module) {
+        super(module);
         populateLocationMap();
     }
 
+    @Override
+    public void setRootCommand() {
+        rootCommand = new CommandNode("warp", "Hop between worlds!", null);
+    }
+
     private void populateLocationMap() {
         locations.clear();
         initMatchingWorldLocationFromSpawn("hub", "world");

+ 7 - 0
src/main/resources/plugin.yml

@@ -45,6 +45,10 @@ commands:
     description: Shows your profile
     usage: /profile
 
+  customrecipe:
+    description: Shows recipes
+    usage: /customrecipe
+
 permissions:
   minion.commands:
     description: Allows use of Minion commands
@@ -67,3 +71,6 @@ permissions:
 
   trade.acceptTrade:
     description: Allows accepting a trade
+
+  customrecipe.gui:
+    description: Allows editing of recipes