Explorar o código

Allows scripting of abilities in data folder. Ability Handlers support js files in script folder. Custom mobs renaming will correctly change their name in nametag. Start of custom enchantments.

Jan hai 2 semanas
pai
achega
5494dd2ccd
Modificáronse 53 ficheiros con 1304 adicións e 220 borrados
  1. 5 0
      pom.xml
  2. 1 2
      src/main/java/me/lethunderhawk/clans/command/ClanCommand.java
  3. 2 3
      src/main/java/me/lethunderhawk/custom/block/RegeneratingBlockListener.java
  4. 7 5
      src/main/java/me/lethunderhawk/custom/block/registry/BlockRegistry.java
  5. 14 0
      src/main/java/me/lethunderhawk/custom/enchantment/CustomEnchantment.java
  6. 105 0
      src/main/java/me/lethunderhawk/custom/enchantment/EnchantmentListener.java
  7. 34 0
      src/main/java/me/lethunderhawk/custom/enchantment/EnchantmentModule.java
  8. 16 0
      src/main/java/me/lethunderhawk/custom/enchantment/EnchantmentRegistry.java
  9. 34 0
      src/main/java/me/lethunderhawk/custom/enchantment/MessageProvider.java
  10. 94 0
      src/main/java/me/lethunderhawk/custom/enchantment/command/GiveCustomBookCommand.java
  11. 43 0
      src/main/java/me/lethunderhawk/custom/enchantment/tools/ReplenishEnchantment.java
  12. 2 3
      src/main/java/me/lethunderhawk/custom/entity/CustomEntityCommand.java
  13. 30 14
      src/main/java/me/lethunderhawk/custom/entity/CustomEntityListener.java
  14. 23 1
      src/main/java/me/lethunderhawk/custom/entity/CustomEntityModule.java
  15. 42 6
      src/main/java/me/lethunderhawk/custom/entity/CustomMob.java
  16. 35 0
      src/main/java/me/lethunderhawk/custom/entity/CustomMobRegistry.java
  17. 7 0
      src/main/java/me/lethunderhawk/custom/entity/EntityUtil.java
  18. 6 6
      src/main/java/me/lethunderhawk/custom/entity/TrackedMobsRegistry.java
  19. 41 48
      src/main/java/me/lethunderhawk/custom/item/CustomItemModule.java
  20. 13 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityDefinition.java
  21. 73 0
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityScript.java
  22. 6 0
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityTrigger.java
  23. 1 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/CustomItemTrigger.java
  24. 5 0
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/FishingRodTrigger.java
  25. 8 4
      src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinition.java
  26. 109 33
      src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinitionLoader.java
  27. 40 16
      src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemVisualDefinition.java
  28. 3 3
      src/main/java/me/lethunderhawk/custom/item/abstraction/instance/ItemInstance.java
  29. 6 7
      src/main/java/me/lethunderhawk/custom/item/abstraction/migration/MigrationService.java
  30. 14 9
      src/main/java/me/lethunderhawk/custom/item/abstraction/registry/CustomItemRegistry.java
  31. 10 7
      src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerHandlerRegistry.java
  32. 6 5
      src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerRegistry.java
  33. 6 9
      src/main/java/me/lethunderhawk/custom/item/abstraction/runtime/AbilityDispatchService.java
  34. 21 11
      src/main/java/me/lethunderhawk/custom/item/abstraction/visual/ItemRenderer.java
  35. 41 8
      src/main/java/me/lethunderhawk/custom/item/command/CustomItemCommand.java
  36. 4 7
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/RegeneratingBlockTrigger.java
  37. 142 0
      src/main/java/me/lethunderhawk/custom/item/gui/CustomItemEditor.java
  38. 77 0
      src/main/java/me/lethunderhawk/custom/item/gui/CustomItemListViewer.java
  39. 4 6
      src/main/java/me/lethunderhawk/custom/item/listener/CustomItemListener.java
  40. 50 0
      src/main/java/me/lethunderhawk/custom/item/listener/CustomItemTriggerEvent.java
  41. 0 1
      src/main/java/me/lethunderhawk/custom/recipes/CustomRecipeModule.java
  42. 0 1
      src/main/resources/config.yml
  43. 1 1
      src/main/resources/custom/items/claim_tool.yml
  44. 21 0
      src/main/resources/custom/items/combat_sack.yml
  45. 21 0
      src/main/resources/custom/items/farming_sack.yml
  46. 21 0
      src/main/resources/custom/items/fishing_sack.yml
  47. 21 0
      src/main/resources/custom/items/foraging_sack.yml
  48. 21 0
      src/main/resources/custom/items/mining_sack.yml
  49. 2 2
      src/main/resources/custom/items/regen_block.yml
  50. 14 0
      src/main/resources/custom/items/snowballShooter.yml
  51. 0 0
      src/main/resources/custom/mobs/custom_drops.yml
  52. 0 0
      src/main/resources/custom/mobs/custom_mobs.yml
  53. 2 0
      src/main/resources/plugin.yml

+ 5 - 0
pom.xml

@@ -83,6 +83,11 @@
             <version>1.21.11-R0.1-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.openjdk.nashorn</groupId>
+            <artifactId>nashorn-core</artifactId>
+            <version>15.7</version>
+        </dependency>
         <dependency>
             <groupId>me.clip</groupId>
             <artifactId>placeholderapi</artifactId>

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

@@ -67,8 +67,7 @@ public class ClanCommand extends CustomCommand {
 
     private void getClaimTool(CommandSender sender, String[] strings) {
         if(!(sender instanceof Player p)) return;
-        CustomItemRegistry registry = FluxService.get(CustomItemRegistry.class);
-        p.getInventory().addItem(new ItemInstance(registry.get("claim_tool")).buildItemStack());
+        p.getInventory().addItem(new ItemInstance(CustomItemRegistry.get("claim_tool")).buildItemStack());
         module.sendText(p, Component.text("The tool magically appears in your inventory! How convenient!"));
     }
 

+ 2 - 3
src/main/java/me/lethunderhawk/custom/block/RegeneratingBlockListener.java

@@ -1,7 +1,6 @@
 package me.lethunderhawk.custom.block;
 
 import me.lethunderhawk.custom.block.registry.BlockRegistry;
-import me.lethunderhawk.fluxapi.FluxService;
 import org.bukkit.block.Block;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
@@ -12,7 +11,7 @@ public class RegeneratingBlockListener implements Listener {
     @EventHandler(ignoreCancelled = true)
     public void onBlockBreak(BlockBreakEvent event) {
         Block block = event.getBlock();
-        RegeneratingBlock data = FluxService.get(BlockRegistry.class).getBlocks().get(BlockRegistry.BlockKey.from(block));
+        RegeneratingBlock data = BlockRegistry.getBlocks().get(BlockRegistry.BlockKey.from(block));
         if (data == null) return;
 
         data.onDestroyed();
@@ -21,7 +20,7 @@ public class RegeneratingBlockListener implements Listener {
     @EventHandler(ignoreCancelled = true)
     public void onBlockPlace(BlockPlaceEvent event) {
         Block block = event.getBlockPlaced();
-        RegeneratingBlock data = FluxService.get(BlockRegistry.class).getBlocks().get(BlockRegistry.BlockKey.from(block));
+        RegeneratingBlock data = BlockRegistry.getBlocks().get(BlockRegistry.BlockKey.from(block));
         if (data == null) return;
 
         data.onPlaced();

+ 7 - 5
src/main/java/me/lethunderhawk/custom/block/registry/BlockRegistry.java

@@ -10,7 +10,9 @@ import java.util.Map;
 
 public class BlockRegistry{
 
-    private final Map<BlockKey, RegeneratingBlock> blocks = new HashMap<>();
+    private BlockRegistry(){}
+
+    private static final Map<BlockKey, RegeneratingBlock> blocks = new HashMap<>();
 
     /* =====================================================
        REGISTRATION
@@ -20,7 +22,7 @@ public class BlockRegistry{
      * @param block the block to register
      * @param regenTimeSeconds the time needed before the block should regenerate
      */
-    public void register(Block block, long regenTimeSeconds) {
+    public static void register(Block block, long regenTimeSeconds) {
         BlockKey key = BlockKey.from(block);
         blocks.put(key, new RegeneratingBlock(
                 key,
@@ -29,12 +31,12 @@ public class BlockRegistry{
         ));
     }
 
-    public void unregister(Block clicked) {
+    public static void unregister(Block clicked) {
         BlockKey key = BlockKey.from(clicked);
         blocks.remove(key);
     }
 
-    public boolean contains(Block clicked) {
+    public static boolean contains(Block clicked) {
         return blocks.containsKey(BlockKey.from(clicked));
     }
 
@@ -42,7 +44,7 @@ public class BlockRegistry{
        EVENTS
        ===================================================== */
 
-    public Map<BlockKey, RegeneratingBlock> getBlocks() {
+    public static Map<BlockKey, RegeneratingBlock> getBlocks() {
         return new HashMap<>(blocks);
     }
 

+ 14 - 0
src/main/java/me/lethunderhawk/custom/enchantment/CustomEnchantment.java

@@ -0,0 +1,14 @@
+package me.lethunderhawk.custom.enchantment;
+
+public abstract class CustomEnchantment {
+    private final String name;
+    private final int maxLevel;
+    public CustomEnchantment(String name, int maxLevel) {
+        this.name = name.replace(" ", "").toLowerCase();
+        this.maxLevel = maxLevel;
+    }
+    public String getName() { return name; }
+    public int getMaxLevel() { return maxLevel; }
+    public abstract boolean canEnchantItem(org.bukkit.inventory.ItemStack item);
+    public abstract void applyEffect(org.bukkit.entity.Player player, org.bukkit.inventory.ItemStack item, int level);
+}

+ 105 - 0
src/main/java/me/lethunderhawk/custom/enchantment/EnchantmentListener.java

@@ -0,0 +1,105 @@
+package me.lethunderhawk.custom.enchantment;
+
+import me.lethunderhawk.custom.enchantment.tools.ReplenishEnchantment;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.inventory.PrepareAnvilEvent;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class EnchantmentListener implements Listener {
+    @EventHandler
+    public void onBlockBreak(BlockBreakEvent event) {
+        Player player = event.getPlayer();
+        ItemStack tool = player.getInventory().getItemInMainHand();
+        if (tool == null || !tool.hasItemMeta()) return;
+        ItemMeta meta = tool.getItemMeta();
+        if (meta == null || meta.getLore() == null) return;
+        for (String lore : meta.getLore()) {
+            for (CustomEnchantment ench : EnchantmentRegistry.getEnchantments()) {
+                if (lore.startsWith("customenchants:" + ench.getName() + ":")) {
+                    int level = getLevelFromLore(lore);
+                    if (ench instanceof ReplenishEnchantment) {
+                        ((ReplenishEnchantment) ench).autoReplant(event);
+                    }
+                }
+            }
+        }
+    }
+    @EventHandler
+    public void onAnvilCombine(PrepareAnvilEvent event) {
+        ItemStack left = event.getInventory().getItem(0);
+        ItemStack right = event.getInventory().getItem(1);
+        if (left == null || right == null) return;
+        if (right.getType() != Material.ENCHANTED_BOOK || !right.hasItemMeta()) return;
+        ItemMeta bookMeta = right.getItemMeta();
+        if (bookMeta == null || bookMeta.getLore() == null) return;
+        for (String lore : bookMeta.getLore()) {
+            for (CustomEnchantment ench : EnchantmentRegistry.getEnchantments()) {
+                if (lore.startsWith("customenchants:" + ench.getName() + ":")) {
+                    int level = getLevelFromLore(lore);
+                    if (left.getType() == Material.ENCHANTED_BOOK && left.hasItemMeta()) {
+                        ItemMeta leftMeta = left.getItemMeta();
+                        if (leftMeta != null && leftMeta.getLore() != null) {
+                            for (String leftLore : leftMeta.getLore()) {
+                                if (leftLore.equals(lore) && ench.getMaxLevel() > level) {
+                                    ItemStack result = new ItemStack(Material.ENCHANTED_BOOK);
+                                    ItemMeta resultMeta = result.getItemMeta();
+                                    String displayName = MessageProvider.get(ench.getName().toLowerCase(), "name", ench.getName());
+                                    String format = MessageProvider.get("book", "format", "{name} {level}");
+                                    String description = MessageProvider.get(ench.getName().toLowerCase(), "description", "");
+                                    String formattedName = format.replace("{name}", displayName).replace("{level}", String.valueOf(level + 1));
+                                    resultMeta.setDisplayName(formattedName);
+                                    java.util.List<String> loreList = new java.util.ArrayList<>();
+                                    loreList.add("customenchants:" + ench.getName() + ":" + (level + 1));
+                                    for (String line : description.split("\n")) {
+                                        if (!line.trim().isEmpty()) loreList.add(line);
+                                    }
+                                    resultMeta.setLore(loreList);
+                                    result.setItemMeta(resultMeta);
+                                    event.setResult(result);
+                                    return;
+                                }
+                            }
+                        }
+                    }
+                    if (ench.canEnchantItem(left)) {
+                        ItemStack result = left.clone();
+                        ItemMeta resultMeta = result.getItemMeta();
+                        java.util.List<String> loreList = resultMeta.hasLore() ? new java.util.ArrayList<>(resultMeta.getLore()) : new java.util.ArrayList<>();
+                        boolean already = false;
+                        String enchantIdLore = "customenchants:" + ench.getName() + ":" + level;
+                        String displayLore = MessageProvider.get(ench.getName().toLowerCase(), "name", ench.getName()) + " " + level;
+                        loreList.removeIf(l -> l.startsWith("customenchants:" + ench.getName() + ":"));
+                        for (String l : loreList) {
+                            if (l.equals(displayLore) || l.startsWith("customenchants:" + ench.getName() + ":")) {
+                                already = true;
+                                break;
+                            }
+                        }
+                        if (!already) {
+                            loreList.add(displayLore);
+                            loreList.add(enchantIdLore);
+                            resultMeta.setLore(loreList);
+                            result.setItemMeta(resultMeta);
+                            event.setResult(result);
+                        }
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    private int getLevelFromLore(String lore) {
+        try {
+            String[] parts = lore.split(":");
+            return Integer.parseInt(parts[2]);
+        } catch (Exception e) {
+            return 1;
+        }
+    }
+}

+ 34 - 0
src/main/java/me/lethunderhawk/custom/enchantment/EnchantmentModule.java

@@ -0,0 +1,34 @@
+package me.lethunderhawk.custom.enchantment;
+
+import me.lethunderhawk.custom.enchantment.command.GiveCustomBookCommand;
+import me.lethunderhawk.custom.enchantment.tools.ReplenishEnchantment;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
+import org.bukkit.Bukkit;
+import org.bukkit.event.HandlerList;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class EnchantmentModule extends FluxAPIModule {
+    private EnchantmentListener listener;
+
+    public EnchantmentModule(JavaPlugin plugin) {
+        super(plugin);
+    }
+
+    @Override
+    public String getPrefix() {
+        return "[Enchantment]";
+    }
+
+    @Override
+    public void onEnable() {
+        EnchantmentRegistry.add(new ReplenishEnchantment());
+        this.listener = new EnchantmentListener();
+        registerCommand("givecustombook", new GiveCustomBookCommand(this));
+        Bukkit.getPluginManager().registerEvents(listener, plugin);
+    }
+
+    @Override
+    public void onDisable() {
+        HandlerList.unregisterAll(listener);
+    }
+}

+ 16 - 0
src/main/java/me/lethunderhawk/custom/enchantment/EnchantmentRegistry.java

@@ -0,0 +1,16 @@
+package me.lethunderhawk.custom.enchantment;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class EnchantmentRegistry {
+    private static final List<CustomEnchantment> enchantments = new ArrayList<>();
+
+    public static void add(CustomEnchantment enchantment) {
+        enchantments.add(enchantment);
+    }
+
+    public static List<CustomEnchantment> getEnchantments() {
+        return enchantments;
+    }
+}

+ 34 - 0
src/main/java/me/lethunderhawk/custom/enchantment/MessageProvider.java

@@ -0,0 +1,34 @@
+package me.lethunderhawk.custom.enchantment;
+
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+
+public class MessageProvider {
+    private static YamlConfiguration messages;
+
+    static {
+        try (InputStream in = MessageProvider.class.getClassLoader().getResourceAsStream("/custom/enchantment/messages.yml");
+             InputStreamReader reader = new InputStreamReader(in, StandardCharsets.UTF_8)) {
+            messages = YamlConfiguration.loadConfiguration(reader);
+        } catch (Exception e) {
+            //e.printStackTrace();
+            messages = new YamlConfiguration();
+        }
+    }
+
+    public static String get(String key) {
+        return messages.getString("en." + key, "§c[Missing message: " + key + "]");
+    }
+
+    public static String get(String section, String key) {
+        return messages.getString("en." + section + "." + key,
+                "§c[Missing message: " + section + "." + key + "]");
+    }
+
+    public static String get(String section, String key, String fallback) {
+        return messages.getString("en." + section + "." + key, fallback);
+    }
+}

+ 94 - 0
src/main/java/me/lethunderhawk/custom/enchantment/command/GiveCustomBookCommand.java

@@ -0,0 +1,94 @@
+package me.lethunderhawk.custom.enchantment.command;
+
+import me.lethunderhawk.custom.enchantment.CustomEnchantment;
+import me.lethunderhawk.custom.enchantment.EnchantmentRegistry;
+import me.lethunderhawk.custom.enchantment.MessageProvider;
+import me.lethunderhawk.fluxapi.util.command.CommandNode;
+import me.lethunderhawk.fluxapi.util.command.CustomCommand;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
+import org.bukkit.Material;
+import org.bukkit.command.CommandSender;
+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 GiveCustomBookCommand extends CustomCommand {
+    public GiveCustomBookCommand(FluxAPIModule module) {
+        super(module);
+    }
+
+
+
+    @Override
+    public CommandNode getRootCommand() {
+        return new CommandNode("givecustombook", "gives you a custom book", this::giveBook);
+    }
+
+    private void giveBook(CommandSender sender, String[] args) {
+        if (args.length < 1) {
+            sender.sendMessage(MessageProvider.get("usage"));
+            return;
+        }
+        String enchantName = args[0].toLowerCase();
+        int level = 1;
+        String targetName = null;
+        Player targetPlayer = null;
+        if (args.length > 2) {
+            try { level = Integer.parseInt(args[1]); } catch (Exception ignored) {}
+            targetName = args[2];
+        } else if (args.length == 2) {
+            try {
+                level = Integer.parseInt(args[1]);
+            } catch (NumberFormatException e) {
+                targetName = args[1];
+            }
+        }
+        if (targetName != null) {
+            targetPlayer = org.bukkit.Bukkit.getPlayerExact(targetName);
+            if (targetPlayer == null) {
+                sender.sendMessage("§cJugador no encontrado: " + targetName);
+                return;
+            }
+        } else if (sender instanceof Player) {
+            targetPlayer = (Player) sender;
+        } else {
+            sender.sendMessage(MessageProvider.get("only_players"));
+            return;
+        }
+        CustomEnchantment enchant = null;
+        for (CustomEnchantment ench : EnchantmentRegistry.getEnchantments()) {
+            if (ench.getName().equalsIgnoreCase(enchantName)) {
+                enchant = ench;
+                break;
+            }
+        }
+        if (enchant == null) {
+            sender.sendMessage(MessageProvider.get("not_found"));
+            return;
+        }
+        String displayName = MessageProvider.get(enchant.getName().toLowerCase(), "name", "§b" + enchant.getName());
+        String description = MessageProvider.get(enchant.getName().toLowerCase(), "description", "§7No description available.");
+        String format = MessageProvider.get("book", "format", "{name} {level}");
+        List<String> bookLore = new ArrayList<>();
+        bookLore.add("customenchants:" + enchant.getName() + ":" + level);
+        for (String line : description.split("\n")) {
+            if (!line.trim().isEmpty()) bookLore.add(line);
+        }
+        displayName = format.replace("{name}", displayName).replace("{level}", String.valueOf(level));
+        ItemStack book = new ItemStack(Material.ENCHANTED_BOOK);
+        ItemMeta meta = book.getItemMeta();
+        meta.setDisplayName(displayName);
+        meta.setLore(bookLore);
+        book.setItemMeta(meta);
+        targetPlayer.getInventory().addItem(book);
+        sender.sendMessage(MessageProvider.get("given").replace("{name}", displayName).replace("{level}", String.valueOf(level)) + " a " + targetPlayer.getName());
+    }
+
+    @Override
+    public void createCommands() {
+
+    }
+}

+ 43 - 0
src/main/java/me/lethunderhawk/custom/enchantment/tools/ReplenishEnchantment.java

@@ -0,0 +1,43 @@
+package me.lethunderhawk.custom.enchantment.tools;
+
+import me.lethunderhawk.custom.enchantment.CustomEnchantment;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.inventory.ItemStack;
+
+public class ReplenishEnchantment extends CustomEnchantment {
+    public ReplenishEnchantment() {
+        super("replenish", 1);
+    }
+    @Override
+    public boolean canEnchantItem(ItemStack item) {
+        if (item == null) return false;
+        return item.getType().toString().endsWith("_HOE");
+    }
+    @Override
+    public void applyEffect(org.bukkit.entity.Player player, ItemStack item, int level) {}
+    public void autoReplant(BlockBreakEvent event) {
+        if (event == null || event.getBlock() == null) return;
+        Block block = event.getBlock();
+        Material crop = block.getType();
+        if (isCrop(crop)) {
+            block.setType(crop);
+        }
+    }
+    private boolean isCrop(Material mat) {
+        switch (mat) {
+            case WHEAT:
+            case CARROTS:
+            case POTATOES:
+            case BEETROOTS:
+            case NETHER_WART:
+            case COCOA:
+            case MELON_STEM:
+            case PUMPKIN_STEM:
+                return true;
+            default:
+                return false;
+        }
+    }
+}

+ 2 - 3
src/main/java/me/lethunderhawk/custom/entity/CustomEntityCommand.java

@@ -7,7 +7,6 @@ import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.LivingEntity;
 
-import java.util.Arrays;
 import java.util.List;
 
 public class CustomEntityCommand extends CustomCommand {
@@ -39,7 +38,7 @@ public class CustomEntityCommand extends CustomCommand {
     private void spawn(CommandSender sender, String[] strings) {
         if(!(sender instanceof LivingEntity living)) return;
         try{
-            CustomEntity entity = CustomEntity.valueOf(strings[0]);
+            CustomMob entity = CustomMobRegistry.getCustomMob(strings[0]);
             entity.spawn(living.getLocation());
 
         }catch (Exception e){
@@ -48,7 +47,7 @@ public class CustomEntityCommand extends CustomCommand {
     }
 
     private List<String> getAllCustomMobs(CommandSender sender, String[] strings) {
-        return Arrays.stream(CustomEntity.values()).map(CustomEntity::name).toList();
+        return CustomMobRegistry.getRegisteredMobs().keySet().stream().toList();
     }
 
     private void edit(CommandSender sender, String[] strings) {

+ 30 - 14
src/main/java/me/lethunderhawk/custom/entity/CustomEntityListener.java

@@ -1,13 +1,16 @@
 package me.lethunderhawk.custom.entity;
 
 import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent;
+import io.papermc.paper.event.player.PlayerNameEntityEvent;
 import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
+import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
 import org.bukkit.Location;
 import org.bukkit.attribute.Attribute;
 import org.bukkit.attribute.AttributeInstance;
 import org.bukkit.entity.ArmorStand;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.LivingEntity;
+import org.bukkit.entity.Mannequin;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
 import org.bukkit.event.entity.EntityDamageEvent;
@@ -20,23 +23,37 @@ public class CustomEntityListener implements Listener {
     private final DecimalFormat formatter = new DecimalFormat("#.##");
     @EventHandler
     public void onEntitySpawn(EntitySpawnEvent event){
-        boolean isCustomEntity = false;
-        for(CustomEntity entity : CustomEntity.values()){
-            if(entity.getType().equals(event.getEntity().getType())){
-                if(Math.random() * 101 > entity.getSpawnChance()) continue;
-                CustomEntityRegistry.add(event.getEntity(), entity);
-                entity.writeIntoEntity((LivingEntity) event.getEntity());
-                isCustomEntity = true;
-                break;
+        Entity entity = event.getEntity();
+        for(CustomMob customMob : CustomMobRegistry.getRegisteredMobs().values()){
+            if(customMob.getType().equals(entity.getType())){
+                if(Math.random() * 101 > customMob.getSpawnChance()) continue;
+                TrackedMobsRegistry.add(entity, customMob);
+                customMob.writeIntoEntity((LivingEntity) entity);
+                return;
             }
         }
-        if(!isCustomEntity && event.getEntity() instanceof LivingEntity living && !(event.getEntity() instanceof ArmorStand)){
+
+        if(EntityUtil.hasBaseName(entity)){
+            return;
+        }
+
+        if(entity instanceof LivingEntity living && !(entity instanceof ArmorStand) && !(entity instanceof Mannequin)){
             if(living.getAttribute(Attribute.MAX_HEALTH) instanceof AttributeInstance instance){
                 EntityUtil.updateHealthInName(living, instance.getBaseValue());
             }
         }
     }
 
+    @EventHandler
+    public void onRename(PlayerNameEntityEvent event){
+        LivingEntity entity = event.getEntity();
+        if(event.getName() != null){
+            EntityUtil.writeIntoBaseName(entity, PlainTextComponentSerializer.plainText().serialize(event.getName()));
+            EntityUtil.updateHealthInName(entity, entity.getHealth());
+            event.setCancelled(true);
+        }
+    }
+
     @EventHandler
     public void onEntityDamage(EntityDamageEvent event){
         Entity entity = event.getEntity();
@@ -45,7 +62,6 @@ public class CustomEntityListener implements Listener {
         if(health > damage){
             health -= damage;
         }
-
         EntityUtil.updateHealthInName(living, health);
         spawnDecayingDamageIndicator(living, damage);
     }
@@ -56,16 +72,16 @@ public class CustomEntityListener implements Listener {
         EntityUtil.clearBaseName(le);
 
 
-        if(!CustomEntityRegistry.contains(event.getEntity())) return;
+        if(!TrackedMobsRegistry.contains(event.getEntity())) return;
 
         event.setDroppedExp(0);
         event.getDrops().clear();
-        CustomEntityRegistry.remove(event.getEntity()).tryDropLoot(event.getEntity().getLocation());
+        TrackedMobsRegistry.remove(event.getEntity()).tryDropLoot(event.getEntity().getLocation());
     }
 
     @EventHandler
     public void onEntityUnload(EntityRemoveFromWorldEvent event){
-        CustomEntityRegistry.remove(event.getEntity());
+        TrackedMobsRegistry.remove(event.getEntity());
     }
 
     private void spawnDecayingDamageIndicator(LivingEntity entity, double damage){
@@ -78,7 +94,7 @@ public class CustomEntityListener implements Listener {
             armorStand.setSmall(true);
             armorStand.setCustomNameVisible(true);
             armorStand.customName(LoreDesigner.createSingle("<red>" + formatter.format(damage)));
-            CustomEntityRegistry.getIndicators().put(armorStand, 30);
+            TrackedMobsRegistry.getIndicators().put(armorStand, 30);
         });
     }
 

+ 23 - 1
src/main/java/me/lethunderhawk/custom/entity/CustomEntityModule.java

@@ -1,8 +1,14 @@
 package me.lethunderhawk.custom.entity;
 
 import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
+import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Material;
+import org.bukkit.entity.EntityType;
 import org.bukkit.plugin.java.JavaPlugin;
 
+import java.util.List;
+
 public class CustomEntityModule extends FluxAPIModule {
 
     public CustomEntityModule(JavaPlugin plugin) {
@@ -17,12 +23,28 @@ public class CustomEntityModule extends FluxAPIModule {
     @Override
     public void onEnable() {
         registerListener(new CustomEntityListener());
+        CustomMobRegistry.registerCustomMob("test_zombie",
+                new CustomMob("<blue>Zombie?", 1, 0, EntityType.SQUID, null, null, null)
+                        .withFireImmunity()
+                        .withPassenger(
+                                new CustomMob("<red>IDK", 1, 0, EntityType.ZOMBIE, null, null, null).withFireImmunity().withDrownImmunity()
+                        ));
+        CustomMobRegistry.registerCustomMob("test_guardian", new CustomMob("<blue>Sea Creature?", 1, 100, EntityType.GUARDIAN,
+                List.of(
+                        new LootItem(new ItemOptions(Material.PRISMARINE_SHARD)
+                                .setName(Component.text("HOLY SHARD!"))
+                                .buildItemStack(), 20),
+                        new LootItem(new ItemOptions(Material.GREEN_DYE)
+                                .setName(Component.text("Legendary Dye"))
+                                .buildItemStack(), 0.3)
+                ), null, null));
+
         registerCommand("custommobs", new CustomEntityCommand(this));
     }
 
     @Override
     public void onDisable() {
-        CustomEntityRegistry.getIndicators().forEach((entity, indicator) -> {
+        TrackedMobsRegistry.getIndicators().forEach((entity, indicator) -> {
             entity.remove();
         });
         unregisterAllListeners();

+ 42 - 6
src/main/java/me/lethunderhawk/custom/entity/CustomEntity.java → src/main/java/me/lethunderhawk/custom/entity/CustomMob.java

@@ -3,28 +3,29 @@ package me.lethunderhawk.custom.entity;
 import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
 import org.bukkit.Location;
 import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeInstance;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.EntityType;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.inventory.EntityEquipment;
 import org.bukkit.inventory.ItemStack;
 
-import java.util.ArrayList;
 import java.util.List;
 
-public enum CustomEntity {
-    CUSTOM_ZOMBIE("<blue>Zombie?", 15, 20, EntityType.ZOMBIE, null, null, null);
+public class CustomMob {
 
     private String name;
     private double maxHealth;
     private double spawnChance;
     private double currentHealth;
     private EntityType type;
-    private List<LootItem> lootTable = new ArrayList<>();
+    private List<LootItem> lootTable;
     private ItemStack mainHandItem;
     private ItemStack[] armor;
-
-    CustomEntity(String name, double maxHealth, double spawnChance, EntityType type, List<LootItem> lootTable, ItemStack mainHandItem, ItemStack[] armor) {
+    private LivingEntity passenger;
+    private CustomMob customPassenger;
+    private boolean fireImmune = false, drownImmunity = false;
+    CustomMob(String name, double maxHealth, double spawnChance, EntityType type, List<LootItem> lootTable, ItemStack mainHandItem, ItemStack[] armor) {
         this.name = name;
         this.maxHealth = maxHealth;
         this.spawnChance = spawnChance;
@@ -34,6 +35,24 @@ public enum CustomEntity {
         this.armor = armor;
         this.currentHealth = maxHealth;
     }
+    public CustomMob withFireImmunity(){
+        this.fireImmune = true;
+        return this;
+    }
+    public CustomMob withDrownImmunity(){
+        this.drownImmunity = true;
+        return this;
+    }
+
+    public CustomMob withPassenger(CustomMob passenger) {
+        this.customPassenger = passenger;
+        return this;
+    }
+
+    public CustomMob withPassenger(LivingEntity passenger) {
+        this.passenger = passenger;
+        return this;
+    }
 
     public LivingEntity writeIntoEntity(LivingEntity entity) {
         if(!entity.getType().equals(type)) return entity;
@@ -49,6 +68,23 @@ public enum CustomEntity {
 
         EntityUtil.updateHealthInName(entity, maxHealth);
 
+
+        if(fireImmune
+                && entity.getAttribute(Attribute.BURNING_TIME) instanceof AttributeInstance fire_attribute){
+            fire_attribute.setBaseValue(0);
+        }
+        if(drownImmunity){
+            entity.setMaximumAir(0);
+        }
+
+        if(passenger != null) {
+            entity.addPassenger(passenger);
+        }
+        if(customPassenger != null) {
+            entity.addPassenger(customPassenger.spawn(entity.getEyeLocation()));
+        }
+        entity.setCanPickupItems(false);
+
         EntityEquipment equipment = entity.getEquipment();
         if(armor != null && equipment != null){
             equipment.setArmorContents(armor);

+ 35 - 0
src/main/java/me/lethunderhawk/custom/entity/CustomMobRegistry.java

@@ -0,0 +1,35 @@
+package me.lethunderhawk.custom.entity;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CustomMobRegistry {
+    private static Map<String, CustomMob> customMobs = new HashMap<>();
+    private CustomMobRegistry(){}
+
+    public static void registerCustomMob(String name, CustomMob mob){
+        customMobs.put(name, mob);
+    }
+    public static void registerCustomMobs(CustomMob... mob){
+        for(CustomMob entity : mob){
+            customMobs.put(entity.getName(), entity);
+        }
+    }
+
+    public static CustomMob unregisterCustomMob(String name, CustomMob mob){
+        return customMobs.remove(name);
+    }
+
+    public static boolean isRegisteredCustomMob(String name){
+        return customMobs.containsKey(name);
+    }
+
+    public static CustomMob getCustomMob(String name){
+        return customMobs.get(name);
+    }
+
+    public static Map<String, CustomMob> getRegisteredMobs() {
+        return new HashMap<>(customMobs);
+    }
+
+}

+ 7 - 0
src/main/java/me/lethunderhawk/custom/entity/EntityUtil.java

@@ -6,8 +6,10 @@ import me.lethunderhawk.main.BazaarFlux;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
 import org.bukkit.NamespacedKey;
+import org.bukkit.entity.Entity;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.persistence.PersistentDataType;
+import org.jetbrains.annotations.NotNull;
 
 public final class EntityUtil {
     private EntityUtil() {}
@@ -54,4 +56,9 @@ public final class EntityUtil {
         var pdc = entity.getPersistentDataContainer();
         pdc.set(KEY_BASE_NAME, PersistentDataType.STRING, name);
     }
+
+    public static boolean hasBaseName(@NotNull Entity entity) {
+        var pdc = entity.getPersistentDataContainer();
+         return pdc.has(KEY_BASE_NAME, PersistentDataType.STRING);
+    }
 }

+ 6 - 6
src/main/java/me/lethunderhawk/custom/entity/CustomEntityRegistry.java → src/main/java/me/lethunderhawk/custom/entity/TrackedMobsRegistry.java

@@ -8,8 +8,8 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.*;
 
-public class CustomEntityRegistry {
-    private static Map<Entity, CustomEntity> entities = new HashMap<>();
+public class TrackedMobsRegistry {
+    private static Map<Entity, CustomMob> entities = new HashMap<>();
     private static Map<Entity, Integer> indicators = new HashMap<>();
 
     static {
@@ -33,19 +33,19 @@ public class CustomEntityRegistry {
         }.runTaskTimer(FluxService.get(BazaarFlux.class), 0L, 1L);
     }
 
-    public static CustomEntity remove(@NotNull Entity entity) {
+    public static CustomMob remove(@NotNull Entity entity) {
         return entities.remove(entity);
     }
 
-    public static void add(@NotNull Entity entity, @NotNull CustomEntity customEntity) {
-        entities.put(entity, customEntity);
+    public static void add(@NotNull Entity entity, @NotNull CustomMob customMob) {
+        entities.put(entity, customMob);
     }
 
     public static boolean contains(@NotNull Entity entity) {
         return entities.containsKey(entity);
     }
 
-    public static CustomEntity get(Entity entity) {
+    public static CustomMob get(Entity entity) {
         return entities.get(entity);
     }
 

+ 41 - 48
src/main/java/me/lethunderhawk/custom/item/CustomItemModule.java

@@ -1,14 +1,10 @@
 package me.lethunderhawk.custom.item;
 
 import me.lethunderhawk.custom.block.RegeneratingBlockListener;
-import me.lethunderhawk.custom.block.registry.BlockRegistry;
-import me.lethunderhawk.custom.item.abstraction.definition.DefaultItemDefinitionValidator;
-import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinition;
+import me.lethunderhawk.custom.item.abstraction.ability.AbilityScript;
 import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinitionLoader;
-import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinitionValidator;
-import me.lethunderhawk.custom.item.abstraction.registry.TriggerHandlerRegistry;
 import me.lethunderhawk.custom.item.abstraction.registry.CustomItemRegistry;
-import me.lethunderhawk.custom.item.abstraction.runtime.AbilityDispatchService;
+import me.lethunderhawk.custom.item.abstraction.registry.TriggerHandlerRegistry;
 import me.lethunderhawk.custom.item.command.CustomItemCommand;
 import me.lethunderhawk.custom.item.concrete.ability.*;
 import me.lethunderhawk.custom.item.listener.CustomItemListener;
@@ -16,12 +12,11 @@ import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.main.FluxAPI;
 import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.fluxapi.util.itemdesign.ItemData;
-import me.lethunderhawk.main.BazaarFlux;
-import org.bukkit.command.CommandSender;
 import org.bukkit.plugin.java.JavaPlugin;
 
 import java.io.File;
-import java.util.Map;
+import java.nio.file.Files;
+import java.util.List;
 
 public class CustomItemModule extends FluxAPIModule {
 
@@ -30,50 +25,54 @@ public class CustomItemModule extends FluxAPIModule {
     }
 
 
-    private static void registerAbilities(TriggerHandlerRegistry triggerHandlerRegistry) {
-        triggerHandlerRegistry.register("test_handler", new TestTriggerHandler());
-        triggerHandlerRegistry.register("rolling_dice", new RollingDiceTrigger());
-        triggerHandlerRegistry.register("claim_tool", new ClaimToolTrigger());
-        triggerHandlerRegistry.register("hyperion", new HyperionTrigger());
-        triggerHandlerRegistry.register("regenerating_block", new RegeneratingBlockTrigger());
-        triggerHandlerRegistry.register("sack", new SackTrigger());
-        triggerHandlerRegistry.register("snowball", new SnowBallShooterTrigger());
+    private void registerAbilities() {
+        TriggerHandlerRegistry.registerIfAbsent("test_handler", new TestTriggerHandler());
+        TriggerHandlerRegistry.registerIfAbsent("rolling_dice", new RollingDiceTrigger());
+        TriggerHandlerRegistry.registerIfAbsent("claim_tool", new ClaimToolTrigger());
+        TriggerHandlerRegistry.registerIfAbsent("hyperion", new HyperionTrigger());
+        TriggerHandlerRegistry.registerIfAbsent("regenerating_block", new RegeneratingBlockTrigger());
+        TriggerHandlerRegistry.registerIfAbsent("sack", new SackTrigger());
+        TriggerHandlerRegistry.registerIfAbsent("snowball", new SnowBallShooterTrigger());
+    }
+
+    private void registerScriptAbilities() {
+        File scriptsDir = new File(plugin.getDataFolder(), "custom/scripts");
+        if(scriptsDir.mkdirs()){
+            plugin.getLogger().info(getPrefix() + " Successfully created scripts directory.");
+        }
+        File[] script_files = scriptsDir.listFiles();
+        if(script_files != null){
+            for(File file : script_files) {
+                try{
+                    List<String> lines = Files.readAllLines(file.toPath());
+                    String one_line = String.join("\n", lines);
+                    AbilityScript script = new AbilityScript(one_line);
+                    TriggerHandlerRegistry.register(file.getName(), script);
+                }catch (Exception e){
+                    plugin.getLogger().warning(getPrefix() + " Error reading script inside file: " + file.getName());
+                }
+            }
+        }
     }
 
     @Override
     public String getPrefix() {
-        return "[CustomItem]";
+        return "[CustomItemModule]";
     }
 
     @Override
     public void onEnable() {
-        TriggerHandlerRegistry triggerHandlerRegistry = new TriggerHandlerRegistry();
-        registerAbilities(triggerHandlerRegistry);
-
-        ItemData.init(FluxService.get(FluxAPI.class));
-
-        ItemDefinitionValidator validator = new DefaultItemDefinitionValidator();
-        ItemDefinitionLoader loader = new ItemDefinitionLoader(validator);
+        File itemsDir = new File(plugin.getDataFolder(), "custom/items");
+        ItemDefinitionLoader.init(itemsDir);
 
-        BazaarFlux bazaarFlux = FluxService.get(BazaarFlux.class);
-        File itemsDir = new File(bazaarFlux.getDataFolder(), "custom/items");
+        registerAbilities();
+        registerScriptAbilities();
 
-        // --- Load definitions from disk ---
-        Map<String, ItemDefinition> definitions = loader.loadAll(itemsDir);
-        CustomItemRegistry itemRegistry = new CustomItemRegistry().fromRegistry(definitions);
-
-        registerListener(new CustomItemListener(
-                new AbilityDispatchService(itemRegistry, triggerHandlerRegistry)
-        ));
+        ItemData.init(FluxService.get(FluxAPI.class));
 
-        // --- FluxService ---
-        FluxService.register(CustomItemRegistry.class, itemRegistry);
-        FluxService.register(ItemDefinitionLoader.class, loader);
-        FluxService.register(ItemDefinitionValidator.class, validator);
-        FluxService.register(TriggerHandlerRegistry.class, triggerHandlerRegistry);
+        CustomItemRegistry.loadAllFromDefinitions(ItemDefinitionLoader.loadAll());
 
-        BlockRegistry blockRegistry = new BlockRegistry();
-        FluxService.register(BlockRegistry.class, blockRegistry);
+        registerListener(new CustomItemListener());
 
         registerListener(new RegeneratingBlockListener());
 
@@ -82,14 +81,8 @@ public class CustomItemModule extends FluxAPIModule {
 
     @Override
     public void onDisable() {
+        ItemDefinitionLoader.saveAll(CustomItemRegistry.getAll());
         unregisterAllListeners();
     }
 
-    public void reload(CommandSender sender, String[] strings) {
-        if (sender.hasPermission("customItem.reload")) {
-            onDisable();
-            onEnable();
-        }
-    }
-
 }

+ 13 - 1
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityDefinition.java

@@ -77,14 +77,26 @@ public class AbilityDefinition implements Serializable {
     public List<Component> renderLore() {
         if(!show_in_lore) return new ArrayList<>();
         List<Component> lore = new ArrayList<>();
+        if(triggers.isEmpty()) return lore;
         String abilityName = "Ability: " + getName() + " [" + (triggers.getFirst()).getDisplayName() + "]";
         lore.add(Component.text(abilityName, NamedTextColor.GOLD));
         lore.addAll(getLore(abilityName));
-
         return lore;
     }
     public AbilityDefinition setShowInLore(boolean value){
         show_in_lore = value;
         return this;
     }
+
+    public List<CustomItemTrigger> getTriggers() {
+        return triggers;
+    }
+
+    public String getDescriptionWithoutParams() {
+        return description;
+    }
+
+    public boolean isShownInLore() {
+        return show_in_lore;
+    }
 }

+ 73 - 0
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityScript.java

@@ -0,0 +1,73 @@
+package me.lethunderhawk.custom.item.abstraction.ability;
+
+import me.clip.placeholderapi.PlaceholderAPI;
+import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.openjdk.nashorn.api.scripting.NashornScriptEngineFactory;
+
+import javax.script.Bindings;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
+public class AbilityScript implements TriggerHandler {
+    private final Map<String, Object> storage = new HashMap<>();
+    private final String executableString;
+    private static ScriptEngineManager manager = new ScriptEngineManager();
+    private static ScriptEngine engine;
+    static {
+        final ScriptEngineFactory factory;
+        engine = new NashornScriptEngineFactory().getScriptEngine(
+                AbilityScript.class.getClassLoader()
+        );
+    }
+
+    public AbilityScript(String toExecute) {
+        this.executableString = toExecute;
+    }
+
+    @Override
+    public void execute(AbilityContext context, ResolvedParams params) {
+        String line = executableString;
+        Player player = context.player();
+
+        if(line.isEmpty()){
+            player.sendMessage(Component.text("No javascript found."));
+            return;
+        }
+        if(Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")){
+            line = PlaceholderAPI.setPlaceholders(player, line);
+            line = PlaceholderAPI.setBracketPlaceholders(player, line);
+        }
+
+        final Bindings bindings = engine.createBindings();
+
+
+        bindings.clear();
+        bindings.put("player", player);
+        bindings.put("context", context);
+        bindings.put("params", params);
+
+        bindings.put("storage", storage);
+        bindings.put("store", (BiConsumer<String, Object>) storage::put);
+        bindings.put("get", (Function<String, Object>) storage::get);
+        bindings.put("containsKey", (Function<String, Boolean>) storage::containsKey);
+
+        bindings.put("msg", (BiConsumer<Player, String>) CommandSender::sendMessage);
+        try{
+            engine.eval(line, bindings);
+            //player.sendMessage(result == null ? Component.text("No result.", NamedTextColor.RED) : Component.text("Got (" + result.getClass().getSimpleName() + "): " + result.toString()));
+        }catch (Exception e){
+            e.printStackTrace();
+            player.sendMessage("Error executing " + line + ": \n" + e.getMessage());
+        }
+    }
+}

+ 6 - 0
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityTrigger.java

@@ -33,10 +33,16 @@ public enum AbilityTrigger implements CustomItemTrigger{
         return displayName;
     }
 
+    @Override
+    public String getTriggerName() {
+        return this.name();
+    }
+
     @Override
     public AbilityTrigger getParent() {
         return parent;
     }
+
     static {
         TriggerRegistry.registerAll(values());
     }

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

@@ -10,7 +10,7 @@ package me.lethunderhawk.custom.item.abstraction.ability;
 public interface CustomItemTrigger {
 
     String getDisplayName();
-
+    String getTriggerName();
     CustomItemTrigger getParent();
 
     /**

+ 5 - 0
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/FishingRodTrigger.java

@@ -26,6 +26,11 @@ public enum FishingRodTrigger implements CustomItemTrigger{
         return displayName;
     }
 
+    @Override
+    public String getTriggerName() {
+        return this.name();
+    }
+
     public FishingRodTrigger getParent() {
         return parent;
     }

+ 8 - 4
src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinition.java

@@ -10,7 +10,7 @@ import java.util.Objects;
 public final class ItemDefinition implements Serializable {
 
     private final String id;
-    private final int version;
+    private int version;
 
     private final ItemVisualDefinition visual;
     private final Map<String, Double> stats;
@@ -30,15 +30,15 @@ public final class ItemDefinition implements Serializable {
         this.abilities = List.copyOf(abilities);
     }
 
-    public String id() {
+    public String getId() {
         return id;
     }
 
-    public int version() {
+    public int getVersion() {
         return version;
     }
 
-    public ItemVisualDefinition visual() {
+    public ItemVisualDefinition getVisual() {
         return visual;
     }
 
@@ -49,4 +49,8 @@ public final class ItemDefinition implements Serializable {
     public List<AbilityDefinition> abilities() {
         return abilities;
     }
+
+    public void setVersion(int version) {
+        this.version = version;
+    }
 }

+ 109 - 33
src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinitionLoader.java

@@ -1,49 +1,62 @@
 package me.lethunderhawk.custom.item.abstraction.definition;
 
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityDefinition;
-import me.lethunderhawk.custom.item.abstraction.ability.AbilityTrigger;
 import me.lethunderhawk.custom.item.abstraction.ability.CustomItemTrigger;
 import me.lethunderhawk.custom.item.abstraction.registry.TriggerRegistry;
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
 import me.lethunderhawk.main.BazaarFlux;
-import net.kyori.adventure.text.Component;
 import org.bukkit.Material;
 import org.bukkit.configuration.ConfigurationSection;
 import org.bukkit.configuration.file.YamlConfiguration;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.*;
 
+
 public final class ItemDefinitionLoader {
 
     private final ItemDefinitionValidator validator;
+    public static File folder;
+
 
-    public ItemDefinitionLoader(ItemDefinitionValidator validator) {
+    private ItemDefinitionLoader(ItemDefinitionValidator validator) {
         this.validator = validator;
     }
 
 
+
+    public static void init(File folderPath){
+        folder = folderPath;
+    }
+
+    public static Map<String, ItemDefinition> loadAll(){
+        if(folder == null || !folder.exists()) return null;
+        return loadAll(folder);
+    }
     /**
      * @param directory to load the files from
      * @return A {@link Map} of the item_id and the corresponding ItemDefinition
      */
+    public static Map<String, ItemDefinition> loadAll(File directory) {
 
-
-    public Map<String, ItemDefinition> loadAll(File directory) {
         Map<String, ItemDefinition> definitions = new HashMap<>();
 
         for (File file : Objects.requireNonNull(directory.listFiles())) {
             ItemDefinition def = load(file);
-            if (definitions.containsKey(def.id())) {
-                throw new IllegalStateException("Duplicate item id: " + def.id());
+            if (def == null) continue;
+
+            if (definitions.containsKey(def.getId())) {
+                throw new IllegalStateException("Duplicate item id: " + def.getId());
             }
-            definitions.put(def.id(), def);
+            definitions.put(def.getId(), def);
         }
         return Map.copyOf(definitions);
     }
 
-    private ItemDefinition load(File file) {
+    private static ItemDefinition load(File file) {
+        if(file.isDirectory()) return null;
+
         YamlConfiguration cfg = YamlConfiguration.loadConfiguration(file);
 
         // ---------- BASIC METADATA ----------
@@ -75,13 +88,16 @@ public final class ItemDefinitionLoader {
                     "Missing 'name' in item " + id
             );
         }
+
         Material realMaterial = Material.getMaterial(material);
-        List<Component> lore =
-                LoreDesigner.createLore(cfg.getString("lore"), "This is a reasonable lore");
+
+        String lore =
+                cfg.getString("lore");
 
         String headTexture =
                 cfg.getString("head.texture", null);
 
+        assert realMaterial != null;
         ItemVisualDefinition visual = new ItemVisualDefinition(
                 realMaterial,
                 name,
@@ -133,7 +149,6 @@ public final class ItemDefinitionLoader {
                 List<String> triggerList = abilityCfg.getStringList("triggers");
 
                 if (!triggerList.isEmpty()) {
-
                     for (String raw : triggerList) {
                         try {
                             TriggerRegistry.parse(raw.toUpperCase()).ifPresent(triggers::add);
@@ -143,24 +158,6 @@ public final class ItemDefinitionLoader {
                             );
                         }
                     }
-                } else {
-
-                    // --- Legacy format: trigger: A
-                    String singleTrigger = abilityCfg.getString("trigger");
-
-                    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 =
@@ -169,7 +166,8 @@ public final class ItemDefinitionLoader {
                         abilityCfg.getString("name");
                 String abilityDescription =
                         abilityCfg.getString("description");
-                boolean show_in_lore = abilityCfg.getBoolean("show_in_lore", true);
+                boolean show_in_lore =
+                        abilityCfg.getBoolean("show_in_lore", true);
 
                 if (handler_id == null) {
                     throw new IllegalStateException(
@@ -220,8 +218,86 @@ public final class ItemDefinitionLoader {
 
         // ---------- VALIDATE ----------
 
-        validator.validate(definition);
+        //validator.validate(definition);
 
         return definition;
     }
+
+    public static void save(File file, ItemDefinition def) {
+        YamlConfiguration cfg = new YamlConfiguration();
+
+        // ---------- BASIC METADATA ----------
+
+        cfg.set("id", def.getId());
+        cfg.set("version", def.getVersion());
+
+        // ---------- VISUAL DEFINITION ----------
+
+        ItemVisualDefinition visual = def.getVisual();
+
+        cfg.set("material", visual.getMaterial().name());
+        cfg.set("name", visual.getDisplayNameTemplate());
+
+
+        if (visual.getLoreTemplate() != null && !visual.getLoreTemplate().isEmpty()) {
+            String loreLines = visual.getLoreTemplate();
+            cfg.set("lore", loreLines);
+        }
+
+        if (visual.getHeadTexture().isPresent()) {
+            cfg.set("head.texture", visual.getHeadTexture().get());
+        }
+
+        // ---------- STATS ----------
+
+        if (!def.stats().isEmpty()) {
+            for (Map.Entry<String, Double> entry : def.stats().entrySet()) {
+                cfg.set("stats." + entry.getKey(), entry.getValue());
+            }
+        }
+
+        // ---------- ABILITIES ----------
+
+        if (!def.abilities().isEmpty()) {
+
+            for (AbilityDefinition ability : def.abilities()) {
+
+                String path = "abilities." + ability.getName().toLowerCase().replace(" ", "_");
+
+                List<String> triggers = ability.getTriggers().stream()
+                        .map(trigger -> trigger.getTriggerName().toUpperCase().replace(" ", "_"))
+                        .toList();
+
+                cfg.set(path + ".triggers", triggers);
+
+                cfg.set(path + ".handler_id", ability.handlerId());
+                cfg.set(path + ".name", ability.getName());
+                cfg.set(path + ".description", ability.getDescriptionWithoutParams());
+                cfg.set(path + ".show_in_lore", ability.isShownInLore());
+
+                // Params
+                if (ability.getParams() != null && !ability.getParams().isEmpty()) {
+                    for (Map.Entry<String, String> param : ability.getParams().entrySet()) {
+                        cfg.set(path + ".params." + param.getKey(), param.getValue());
+                    }
+                }
+
+            }
+        }
+
+        // ---------- SAVE FILE ----------
+
+        try {
+            cfg.save(file + "/"+ def.getId() + ".yml");
+        } catch (IOException e) {
+            throw new RuntimeException("Failed to save item definition to " + file.getName(), e);
+        }
+    }
+
+    public static void saveAll(Iterable<? extends ItemDefinition> all) {
+        for(ItemDefinition def : all) {
+            save(folder, def);
+        }
+        TriggerRegistry.register(null, "ad");
+    }
 }

+ 40 - 16
src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemVisualDefinition.java

@@ -12,46 +12,70 @@ import java.util.Optional;
 
 public final class ItemVisualDefinition implements Serializable {
 
-    private final Material material;
-    private final Optional<String> headTexture;
+    private Material material;
+    private Optional<String> headTexture;
 
-    private final String displayName;
-    private final List<Component> loreTemplate;
+    private String displayNameTemplate;
+    private String loreTemplate;
 
     public ItemVisualDefinition(
-            Material material,
-            String displayName,
-            List<Component> loreTemplate,
+            @NotNull Material material,
+            String displayNameTemplate,
+            String loreTemplate,
             String headTexture
     ) {
         this.material = material;
-        this.displayName = displayName;
-        this.loreTemplate = List.copyOf(loreTemplate);
+        this.displayNameTemplate = displayNameTemplate;
+        this.loreTemplate = loreTemplate;
         this.headTexture = Optional.ofNullable(headTexture);
     }
 
-    public Material material() {
+    public Material getMaterial() {
         return material;
     }
 
-    public Optional<String> headTexture() {
+    public Optional<String> getHeadTexture() {
         return headTexture;
     }
 
-    public Component displayName() {
-        return LoreDesigner.createSingle(displayName);
+    public Component getDisplayName() {
+        return LoreDesigner.createSingle(displayNameTemplate);
     }
 
-    public List<Component> loreTemplate() {
-        return loreTemplate;
+    public List<Component> createLoreFromTemplate() {
+        return LoreDesigner.createLore(loreTemplate);
     }
 
     public boolean isHead() {
-        return headTexture.isPresent();
+        return material == Material.PLAYER_HEAD;
     }
 
     public @NotNull ItemFlag itemFlags() {
         return ItemFlag.HIDE_ATTRIBUTES;
     }
+
+    public String getDisplayNameTemplate() {
+        return displayNameTemplate;
+    }
+
+    public String getLoreTemplate() {
+        return loreTemplate;
+    }
+
+    public void setMaterial(Material material) {
+        this.material = material;
+    }
+
+    public void setHeadTexture(String headTexture) {
+        this.headTexture = Optional.ofNullable(headTexture);
+    }
+
+    public void setDisplayNameTemplate(String displayNameTemplate) {
+        this.displayNameTemplate = displayNameTemplate;
+    }
+
+    public void setLoreTemplate(String loreTemplate) {
+        this.loreTemplate = loreTemplate;
+    }
 }
 

+ 3 - 3
src/main/java/me/lethunderhawk/custom/item/abstraction/instance/ItemInstance.java

@@ -20,10 +20,10 @@ public final class ItemInstance implements Serializable {
 
     public ItemInstance(ItemDefinition definition) {
         this.definition = Objects.requireNonNull(definition);
-        this.itemId = definition.id();
-        this.version = definition.version();
+        this.itemId = definition.getId();
+        this.version = definition.getVersion();
     }
-    public ItemInstance(String itemId, int version) {
+    private ItemInstance(String itemId, int version) {
         this.itemId = itemId;
         this.version = version;
         this.definition = null;

+ 6 - 7
src/main/java/me/lethunderhawk/custom/item/abstraction/migration/MigrationService.java

@@ -1,6 +1,5 @@
 package me.lethunderhawk.custom.item.abstraction.migration;
 
-import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinition;
 import me.lethunderhawk.custom.item.abstraction.instance.ItemInstance;
 import me.lethunderhawk.custom.item.abstraction.instance.ItemInstanceReader;
@@ -21,26 +20,26 @@ public final class MigrationService {
         ItemInstance current = ItemInstanceReader.read(original);
         if (current == null) return original;
 
-        ItemDefinition latest = FluxService.get(CustomItemRegistry.class).get(current.itemId());
+        ItemDefinition latest = CustomItemRegistry.get(current.itemId());
         if (latest == null) {
 
             return original;
         }
-        if (current.version() == latest.version()) {
+        if (current.version() == latest.getVersion()) {
             return original;
         }
 
         // --- Ensure correct material ---
         ItemStack stack = original;
-        if (!latest.visual().isHead()
-                && stack.getType() != latest.visual().material()) {
-            stack = stack.withType(latest.visual().material());
+        if (!latest.getVisual().isHead()
+                && stack.getType() != latest.getVisual().getMaterial()) {
+            stack = stack.withType(latest.getVisual().getMaterial());
         }
 
         // --- Create migrated instance ---
         ItemInstance migrated = new ItemInstance(latest);
         migrated.data().putAll(current.data());
-        migrated.setVersion(latest.version());
+        migrated.setVersion(latest.getVersion());
 
         // --- Render ---
         ItemRenderer.renderInto(stack, migrated);

+ 14 - 9
src/main/java/me/lethunderhawk/custom/item/abstraction/registry/CustomItemRegistry.java

@@ -4,30 +4,35 @@ import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinition;
 import me.lethunderhawk.main.BazaarFlux;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 
 public class CustomItemRegistry {
 
-    public CustomItemRegistry() {}
+    private CustomItemRegistry() {}
 
-    private Map<String, ItemDefinition> definitions = new HashMap<>();
+    private static Map<String, ItemDefinition> definitions = new HashMap<>();
 
-    public CustomItemRegistry fromRegistry(Map<String, ItemDefinition> definitionsMap) {
-        this.definitions = new HashMap<>(definitionsMap);
-        return this;
+    /**
+     * Use with Caution, will override all existing definitions!
+     * @param definitionsMap The Map with all item ids and their definition
+     */
+    public static void loadAllFromDefinitions(Map<String, ItemDefinition> definitionsMap) {
+        definitions = new HashMap<>(definitionsMap);
     }
-    public void add(ItemDefinition definition) {
-        definitions.put(definition.id(), definition);
+
+    public static void add(ItemDefinition definition) {
+        definitions.put(definition.getId(), definition);
     }
 
-    public ItemDefinition get(String itemId) {
+    public static ItemDefinition get(String itemId) {
         ItemDefinition definition = definitions.get(itemId);
         if(definition == null) FluxService.get(BazaarFlux.class).getLogger().warning("Item " + itemId + " not found");
         return definition;
     }
 
-    public Iterable<? extends ItemDefinition> getAll() {
+    public static Collection<ItemDefinition> getAll() {
         return definitions.values();
     }
 }

+ 10 - 7
src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerHandlerRegistry.java

@@ -7,16 +7,15 @@ import java.util.Map;
 
 public final class TriggerHandlerRegistry {
 
-    private final Map<String, TriggerHandler> handlers = new HashMap<>();
+    private TriggerHandlerRegistry() {}
 
-    public void register(String id, TriggerHandler handler) {
-        if (exists(id)) {
-            throw new IllegalStateException("Duplicate ability handler: " + id);
-        }
+    private static final Map<String, TriggerHandler> handlers = new HashMap<>();
+
+    public static void register(String id, TriggerHandler handler) {
         handlers.put(id, handler);
     }
 
-    public TriggerHandler get(String id) {
+    public static TriggerHandler get(String id) {
         TriggerHandler handler = handlers.get(id);
         if (handler == null) {
             throw new IllegalStateException("Missing ability handler: " + id);
@@ -24,7 +23,11 @@ public final class TriggerHandlerRegistry {
         return handler;
     }
 
-    public boolean exists(String id) {
+    public static boolean exists(String id) {
         return handlers.containsKey(id);
     }
+
+    public static void registerIfAbsent(String id, TriggerHandler handler) {
+        if(!exists(id)) handlers.put(id, handler);
+    }
 }

+ 6 - 5
src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerRegistry.java

@@ -16,14 +16,15 @@ public final class TriggerRegistry {
     }
 
     public static void register(CustomItemTrigger t, String... aliases) {
-        BY_KEY.put(t.getClass().getSimpleName().toLowerCase() + ":" + ((Enum<?>) t).name().toLowerCase(), t);
-        BY_KEY.put(((Enum<?>) t).name().toLowerCase(), t);                      // enum name key
-        BY_KEY.put(t.getDisplayName().toLowerCase(), t);                        // display name key
-        for (String a : aliases) BY_KEY.put(a.toLowerCase(), t);                // any aliases
+        if(t == null) return;
+        BY_KEY.put(t.getClass().getSimpleName().toLowerCase() + ":" + ((Enum<?>) t).name().toUpperCase(), t);
+        BY_KEY.put(((Enum<?>) t).name().toUpperCase(), t);                      // enum name key
+        BY_KEY.put(t.getDisplayName().toUpperCase(), t);                        // display name key
+        for (String a : aliases) BY_KEY.put(a.toUpperCase(), t);                // any aliases
     }
 
     public static Optional<CustomItemTrigger> parse(String token) {
         if (token == null) return Optional.empty();
-        return Optional.ofNullable(BY_KEY.get(token.trim().toLowerCase()));
+        return Optional.ofNullable(BY_KEY.get(token.trim().toUpperCase()));
     }
 }

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

@@ -18,16 +18,12 @@ import java.util.HashMap;
 import java.util.Map;
 
 public final class AbilityDispatchService {
+    private static final Map<String, Long> cooldowns = new HashMap<>();
 
-    private final CustomItemRegistry itemRegistry;
-    private final TriggerHandlerRegistry triggerHandlerRegistry;
-    private final Map<String, Long> cooldowns = new HashMap<>();
-    public AbilityDispatchService(CustomItemRegistry itemRegistry, TriggerHandlerRegistry triggerHandlerRegistry) {
-        this.itemRegistry = itemRegistry;
-        this.triggerHandlerRegistry = triggerHandlerRegistry;
+    private AbilityDispatchService() {
     }
 
-    public void dispatch(
+    public static void dispatch(
             Player player,
             ItemStack stack,
             CustomItemTrigger trigger,
@@ -36,8 +32,9 @@ public final class AbilityDispatchService {
         ItemInstance instance = ItemInstanceReader.read(stack);
         if (instance == null) return;
 
-        ItemDefinition def = itemRegistry.get(instance.itemId());
+        ItemDefinition def = CustomItemRegistry.get(instance.itemId());
 
+        if(def == null) return;
         for (AbilityDefinition ability : def.abilities()) {
             if (ability.matchesTrigger(trigger)) {
                 String cooldownKey = player.getUniqueId() + ":" + ability.handlerId();
@@ -52,7 +49,7 @@ public final class AbilityDispatchService {
                     continue;
                 }
 
-                TriggerHandler handler = triggerHandlerRegistry.get(ability.handlerId());
+                TriggerHandler handler = TriggerHandlerRegistry.get(ability.handlerId());
                 handler.execute(
                         new AbilityContext(player, instance, stack, event, trigger),
                         ResolvedParams.resolve(ability, def, instance)

+ 21 - 11
src/main/java/me/lethunderhawk/custom/item/abstraction/visual/ItemRenderer.java

@@ -21,29 +21,37 @@ public final class ItemRenderer {
 
     public static void renderInto(ItemStack stack, ItemInstance instance) {
         ItemDefinition def = instance.definition();
-        ItemVisualDefinition visual = def.visual();
+        ItemVisualDefinition visual = def.getVisual();
 
         ItemMeta meta = stack.getItemMeta();
         if (meta == null) return;
 
         // --- Name ---
-        meta.displayName(visual.displayName());
+        meta.displayName(visual.getDisplayName());
 
         // --- Lore ---
         List<Component> lore = new ArrayList<>();
 
+
         def.stats().forEach((key, value) ->
                 lore.add(Component.text(
                         key + ": " + (value >= 0 ? "+" : "") + value,
                         NamedTextColor.RED
                 ))
         );
+        if(!def.stats().isEmpty()){
+            lore.add(Component.text(" "));
+        }
+        lore.addAll(visual.createLoreFromTemplate());
+
+        def.abilities().forEach(ability -> {
+            List<Component> abilitiesLore = ability.renderLore();
+            if (abilitiesLore.isEmpty()) return;
+            abilitiesLore.add(Component.text(" "));
+            lore.addAll(abilitiesLore);
+        });
 
-        def.abilities().forEach(ability ->
-                lore.addAll(ability.renderLore())
-        );
 
-        lore.addAll(visual.loreTemplate());
         meta.lore(lore);
 
         // --- Flags ---
@@ -56,11 +64,13 @@ public final class ItemRenderer {
     }
 
     public static ItemStack render(ItemInstance instance) {
-        ItemVisualDefinition visual = instance.definition().visual();
-
-        ItemStack stack = visual.isHead()
-                ? CustomHeadCreator.createCustomHead(visual.headTexture().get())
-                : new ItemStack(visual.material());
+        ItemVisualDefinition visual = instance.definition().getVisual();
+        ItemStack stack;
+        if(visual.isHead() && visual.getHeadTexture().isPresent()){
+                stack = CustomHeadCreator.createCustomHead(visual.getHeadTexture().get());
+        }else{
+            stack = new ItemStack(visual.getMaterial());
+        }
         renderInto(stack, instance);
         return stack;
     }

+ 41 - 8
src/main/java/me/lethunderhawk/custom/item/command/CustomItemCommand.java

@@ -1,28 +1,30 @@
 package me.lethunderhawk.custom.item.command;
 
 import me.lethunderhawk.custom.item.CustomItemModule;
+import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
 import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinition;
+import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinitionLoader;
+import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import me.lethunderhawk.custom.item.abstraction.instance.ItemInstance;
 import me.lethunderhawk.custom.item.abstraction.migration.MigrationService;
 import me.lethunderhawk.custom.item.abstraction.registry.CustomItemRegistry;
-import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.custom.item.abstraction.registry.TriggerHandlerRegistry;
+import me.lethunderhawk.custom.item.gui.CustomItemListViewer;
 import me.lethunderhawk.fluxapi.util.command.CommandNode;
 import me.lethunderhawk.fluxapi.util.command.CustomCommand;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 
+import java.util.HashMap;
+
 public class CustomItemCommand extends CustomCommand {
 
     public CustomItemCommand(CustomItemModule customItemModule) {
         super(customItemModule);
     }
 
-    private void getAllItems(CommandSender sender, String[] strings) {
-        if(!(sender instanceof Player p)) return;
-        if(!p.hasPermission("custom.items")) return;
-        getAllItems(p);
-    }
-
     @Override
     public CommandNode getRootCommand(){
        return new CommandNode("customItems", "Main Custom Item Command", null);
@@ -32,7 +34,32 @@ public class CustomItemCommand extends CustomCommand {
     public void createCommands() {
         rootCommand.registerSubCommand("getAll", "Get all custom items", this::getAllItems);
         rootCommand.registerSubCommand("reload", "Reload", this::reload);
+        rootCommand.registerSubCommand("only_load", "Load the files from Storage", this::only_load_reload);
         rootCommand.registerSubCommand("migrateMe", "Migrate your inventory to the newest version", this::migrateInventory);
+        rootCommand.registerSubCommand("gui", "Shows a GUI of all Items", this::showGUI);
+        rootCommand.registerSubCommand("run_ability", "execute javascript abilities", this::run_ability);
+    }
+
+    private void run_ability(CommandSender sender, String[] strings) {
+        if(TriggerHandlerRegistry.exists(strings[0])) {
+            TriggerHandlerRegistry.get(strings[0]).execute(new AbilityContext((Player) sender, null, null, null, null), new ResolvedParams(new HashMap<>()));
+        }else{
+            sender.sendMessage(Component.text("That ability does not exist!"));
+        }
+    }
+
+    private void only_load_reload(CommandSender sender, String[] strings) {
+        if(sender.isOp()) {
+            CustomItemRegistry.loadAllFromDefinitions(ItemDefinitionLoader.loadAll());
+            sender.sendMessage(Component.text("Successfully loaded all Items",  NamedTextColor.GREEN));
+        }
+    }
+
+    private void showGUI(CommandSender sender, String[] strings) {
+        if(sender instanceof Player p) {
+            CustomItemListViewer view = new CustomItemListViewer(p);
+            view.openWithListener(p);
+        }
     }
 
     private void reload(CommandSender sender, String[] strings) {
@@ -46,8 +73,14 @@ public class CustomItemCommand extends CustomCommand {
         player.sendMessage("Attempted to migrate your inventory to the newest version");
     }
 
+    private void getAllItems(CommandSender sender, String[] strings) {
+        if(!(sender instanceof Player p)) return;
+        if(!p.hasPermission("custom.items")) return;
+        getAllItems(p);
+    }
+
     private void getAllItems(Player player) {
-        for (ItemDefinition definition : FluxService.get(CustomItemRegistry.class).getAll()) {
+        for (ItemDefinition definition : CustomItemRegistry.getAll()) {
             ItemInstance instance = new ItemInstance(definition);
             player.getInventory().addItem(instance.buildItemStack());
         }

+ 4 - 7
src/main/java/me/lethunderhawk/custom/item/concrete/ability/RegeneratingBlockTrigger.java

@@ -1,10 +1,9 @@
 package me.lethunderhawk.custom.item.concrete.ability;
 
-import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.custom.block.registry.BlockRegistry;
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
-import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import org.bukkit.block.Block;
 import org.bukkit.entity.Player;
 import org.bukkit.event.player.PlayerInteractEvent;
@@ -28,14 +27,12 @@ public class RegeneratingBlockTrigger implements TriggerHandler {
         
         Block clicked = event.getClickedBlock();
         clicked.setType(clicked.getType(), false);
-        BlockRegistry registry = FluxService.get(BlockRegistry.class);
-
 
-        if (!registry.contains(clicked)) {
-            registry.register(clicked, params.getInt("regen_time"));
+        if (!BlockRegistry.contains(clicked)) {
+            BlockRegistry.register(clicked, params.getInt("regen_time"));
             player.sendMessage("Turned into regenerating block!");
         } else {
-            registry.unregister(clicked);
+            BlockRegistry.unregister(clicked);
             player.sendMessage("Turned into a normal block again!");
         }
     }

+ 142 - 0
src/main/java/me/lethunderhawk/custom/item/gui/CustomItemEditor.java

@@ -0,0 +1,142 @@
+package me.lethunderhawk.custom.item.gui;
+
+import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinition;
+import me.lethunderhawk.custom.item.abstraction.instance.ItemInstance;
+import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
+import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
+import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
+import me.lethunderhawk.util.listeners.ChatListener;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class CustomItemEditor extends InventoryGUI {
+    private final ItemDefinition toEdit;
+
+    public CustomItemEditor(Player p, ItemDefinition item) {
+        super("Edit " + item.getId(), 54, p);
+        this.toEdit = item;
+    }
+
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+
+
+        fillRectangle(10, 30, getSurroundingGlassPane());
+        //ChatListener.askQuestion(player);
+        setItem(20, new ItemInstance(toEdit).buildItemStack());
+        //setItemWithClickAction();
+        setItemWithClickAction(0, new ItemOptions(Material.SUNFLOWER)
+                .setName(Component.text("Version " + toEdit.getVersion(), NamedTextColor.GREEN))
+                .setLore(LoreDesigner.createLore("<gray>Update this number and it will automatically migrate this custom item for every upon joining " +
+                        "<br><yellow>Left Click to increase" +
+                        "<br>Right Click to decrease"))
+                .buildItemStack(), this::changeVersion);
+
+        setItemWithClickAction(14, new ItemOptions(Material.OAK_SIGN)
+                .setName(Component.text("Edit the display name", NamedTextColor.GREEN))
+                .setLore(LoreDesigner.createLore("<gray>The display name is basically the thing/name of the item that pops up above your hotbar when switching to this item"))
+                .buildItemStack(), this::changeName);
+
+        setItemWithClickAction(16, new ItemOptions(Material.WIND_CHARGE)
+                .setName(Component.text("Edit abilities of this Item", NamedTextColor.GREEN))
+                .setLore(LoreDesigner.createLore("<gray>You can use the in game editor or edit the config file / script file that corresponds to these abilities <br><red>This editor is in BETA"))
+                .buildItemStack(), this::editAbilities);
+
+        setItemWithClickAction(24, new ItemOptions(Material.PLAYER_HEAD)
+                .setName(Component.text("Change Player-head Texture", NamedTextColor.GREEN))
+                .setLore(LoreDesigner.createLore("<gray>You will be prompted in chat to enter the new Texture Value, you can use minecraft-heads.com for getting this value. <br><red>Only works if the base material is actually a player head."))
+                .buildItemStack(), this::askHeadTexture);
+
+        setItemWithClickAction(32, new ItemOptions(Material.GOLDEN_SWORD)
+                .setName(Component.text("Edit attributes of this Item", NamedTextColor.GREEN))
+                .setLore(LoreDesigner.createLore("<gray>You can use the in game editor or edit the config file <br><red>These attributes and the editor are in BETA"))
+                .buildItemStack(), this::editStats);
+
+        setItemWithClickAction(34, new ItemOptions(Material.GRASS_BLOCK)
+                .setName(Component.text("Change the Base Material", NamedTextColor.GREEN))
+                .setLore(LoreDesigner.createLore("<gray>You will be prompted in chat to enter the new Material name, you can use any existing Minecraft item"))
+                .buildItemStack(), this::askNewMaterial);
+
+        setCloseButton(49);
+        setBackButton(48);
+    }
+
+    private void askNewMaterial(Player player, ClickType type) {
+        player.closeInventory();
+        ChatListener.askQuestion(player, Component.text("What should the new Material be? Currently: " + toEdit.getVisual().getMaterial().name(), NamedTextColor.YELLOW), this::changeMaterial);
+    }
+
+    private void changeMaterial(Player player, Component component) {
+        try{
+            String materialString = PlainTextComponentSerializer.plainText().serialize(component);
+            Material material = Material.getMaterial(materialString);
+            toEdit.getVisual().setMaterial(material);
+            sendSuccess(player);
+        }catch (Exception e){
+            askNewMaterial(player, ClickType.LEFT);
+        }
+    }
+
+    private void sendSuccess(Player player) {
+        player.sendMessage(Component.text("Success.", NamedTextColor.GREEN));
+    }
+
+    private void editStats(Player player, ClickType type) {
+
+    }
+
+    private void askHeadTexture(Player player, ClickType type) {
+        player.closeInventory();
+        if(toEdit.getVisual().getHeadTexture().isPresent()){
+            ChatListener.askQuestion(player,
+                    Component.text("What should the new Head Texture be? \n", NamedTextColor.GREEN).append(
+                            Component.text("[Shift-click to insert into chat bar]", NamedTextColor.YELLOW)
+                                    .insertion(toEdit.getVisual().getHeadTexture().get())), this::changeHeadTexture);
+        }
+    }
+
+    private void changeHeadTexture(Player player, Component component) {
+        try{
+            String texture = PlainTextComponentSerializer.plainText().serialize(component);
+            toEdit.getVisual().setHeadTexture(texture);
+            sendSuccess(player);
+        }catch (Exception e){
+            askNewMaterial(player, ClickType.LEFT);
+        }
+    }
+
+    private void editAbilities(Player player, ClickType type) {
+    }
+
+    private void changeName(Player player, ClickType type) {
+    }
+
+    private void changeVersion(Player player, ClickType type) {
+        if(type.isLeftClick()){
+            toEdit.setVersion(toEdit.getVersion()+1);
+        }else if(type.isRightClick() && toEdit.getVersion() >= 1){
+            toEdit.setVersion(toEdit.getVersion()-1);
+        }
+        update();
+    }
+
+    private ItemStack getSurroundingGlassPane() {
+        ItemStack surrounding = new ItemOptions(Material.ORANGE_STAINED_GLASS_PANE).buildItemStack();
+        ItemMeta meta = surrounding.getItemMeta();
+        meta.setHideTooltip(true);
+        surrounding.setItemMeta(meta);
+        return surrounding;
+    }
+
+    @Override
+    public void update() {
+        buildContent();
+    }
+}

+ 77 - 0
src/main/java/me/lethunderhawk/custom/item/gui/CustomItemListViewer.java

@@ -0,0 +1,77 @@
+package me.lethunderhawk.custom.item.gui;
+
+import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinition;
+import me.lethunderhawk.custom.item.abstraction.instance.ItemInstance;
+import me.lethunderhawk.custom.item.abstraction.registry.CustomItemRegistry;
+import me.lethunderhawk.fluxapi.util.gui.PaginatedInventoryGUI;
+import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
+import net.kyori.adventure.text.Component;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+public class CustomItemListViewer extends PaginatedInventoryGUI<ItemDefinition> {
+    public CustomItemListViewer(Player player) {
+        super("Custom Item Preview", 54, player);
+    }
+
+    @Override
+    protected ItemStack createItem(ItemDefinition element) {
+        ItemInstance instance = new ItemInstance(element);
+        ItemStack item = instance.buildItemStack();
+        List<Component> lore = item.lore();
+        if(lore == null) {
+            lore = new ArrayList<>();
+        }
+        lore.addAll(LoreDesigner.createLore("<br><yellow>Left click to obtain<br>Right click to edit"));
+        item.lore(lore);
+        return item;
+    }
+
+    @Override
+    public void buildContent() {
+        fillGlassPaneBackground();
+        List<Integer> slots = computeRectangleSlots(10, 43);
+
+        List<ItemDefinition> items = new ArrayList<>(CustomItemRegistry.getAll());
+        setupPagination(items, slots);
+
+        sortElements(Comparator.comparing(ItemDefinition::getId));
+
+        renderPage();
+        addNavigationButtons();
+
+        setCloseButton(49);
+    }
+
+
+    @Override
+    public void onClick(ItemDefinition element, Player player, ClickType type){
+        if(!player.isOp()) return;
+        if(type.isLeftClick()){
+            getItem(player, element);
+        }
+        if(type.isRightClick()){
+            showEdit(player, element);
+        }
+    }
+
+    private void showEdit(Player player, ItemDefinition element) {
+        CustomItemEditor editor = new CustomItemEditor(player, element);
+        openNext(player, editor);
+    }
+
+    private void getItem(Player player, ItemDefinition element) {
+        ItemInstance instance = new ItemInstance(element);
+        player.getInventory().addItem(instance.buildItemStack());
+    }
+
+    @Override
+    public void update() {
+
+    }
+}

+ 4 - 6
src/main/java/me/lethunderhawk/custom/item/listener/CustomItemListener.java

@@ -18,10 +18,8 @@ import org.bukkit.inventory.ItemStack;
  * Handles events for custom items
  */
 public class CustomItemListener implements Listener {
-    private final AbilityDispatchService dispatchService;
 
-    public CustomItemListener(AbilityDispatchService dispatchService) {
-        this.dispatchService = dispatchService;
+    public CustomItemListener() {
     }
 
     @EventHandler
@@ -36,7 +34,7 @@ public class CustomItemListener implements Listener {
             ItemInstance instance = ItemInstanceReader.read(stack);
 
             if(instance == null) continue;
-            dispatchService.dispatch(
+            AbilityDispatchService.dispatch(
                     player,
                     stack,
                     AbilityTrigger.ON_PICKUP,
@@ -58,7 +56,7 @@ public class CustomItemListener implements Listener {
             case RIGHT_CLICK_BLOCK -> AbilityTrigger.RIGHT_CLICK_BLOCK;
             case PHYSICAL -> AbilityTrigger.PHYSICAL;
         };
-        dispatchService.dispatch(event.getPlayer(), event.getItem(), triggered, event);
+        AbilityDispatchService.dispatch(event.getPlayer(), event.getItem(), triggered, event);
     }
 
     @EventHandler
@@ -72,7 +70,7 @@ public class CustomItemListener implements Listener {
             case BITE -> FishingRodTrigger.BITE;
             case LURED -> FishingRodTrigger.LURED;
         };
-        dispatchService.dispatch(event.getPlayer(), event.getPlayer().getActiveItem(), triggered, event);
+        AbilityDispatchService.dispatch(event.getPlayer(), event.getPlayer().getActiveItem(), triggered, event);
     }
 
 }

+ 50 - 0
src/main/java/me/lethunderhawk/custom/item/listener/CustomItemTriggerEvent.java

@@ -0,0 +1,50 @@
+package me.lethunderhawk.custom.item.listener;
+
+import me.lethunderhawk.custom.item.abstraction.ability.CustomItemTrigger;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.bukkit.inventory.ItemStack;
+
+public class CustomItemTriggerEvent extends Event {
+    private static final HandlerList HANDLER_LIST = new HandlerList();
+    private final Player player;
+    private final ItemStack stack;
+    private final CustomItemTrigger trigger;
+    private final Event originalEvent;
+
+    public CustomItemTriggerEvent(Player player,
+                                  ItemStack stack,
+                                  CustomItemTrigger trigger,
+                                  Event event) {
+        this.player = player;
+        this.stack = stack;
+        this.trigger = trigger;
+        this.originalEvent = event;
+    }
+
+    public Player getPlayer() {
+        return player;
+    }
+
+    public ItemStack getStack() {
+        return stack;
+    }
+
+    public CustomItemTrigger getTrigger() {
+        return trigger;
+    }
+
+    public Event getOriginalEvent() {
+        return originalEvent;
+    }
+
+    public static HandlerList getHandlerList() {
+        return HANDLER_LIST;
+    }
+
+    @Override
+    public HandlerList getHandlers() {
+        return HANDLER_LIST;
+    }
+}

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

@@ -42,7 +42,6 @@ public class CustomRecipeModule extends FluxAPIModule {
     @Override
     public void onDisable() {
         if(recipeManager != null) {
-
             recipeManager.saveRecipes(FluxService.get(ConfigLoader.class));
             listeners.remove(recipeManager);
             HandlerList.unregisterAll(recipeManager);

+ 0 - 1
src/main/resources/config.yml

@@ -37,7 +37,6 @@ registeredSackItems:
     RED_SAND: 4096
     RED_SANDSTONE: 2048
     TERRACOTTA: 4096
-
   COMBAT:
     STRING: 1024
     ENDER_PEARL: 2048

+ 1 - 1
src/main/resources/custom/items/claim_tool.yml

@@ -7,7 +7,7 @@ name: "<blue><bold>Claim Tool"
 
 abilities:
   select:
-    name: Gamble with a Dice
+    name: Create a claim
     description: <gray>Click on a block to set the first corner of your claim.<gray> <br>The next click will create the claim dedicated to your clan
     description_width_ref: Click on a block to set the first
     trigger: LEFT_CLICK_BLOCK

+ 21 - 0
src/main/resources/custom/items/combat_sack.yml

@@ -0,0 +1,21 @@
+id: "combat_sack"
+version: 1
+
+material: PLAYER_HEAD
+
+head:
+  texture: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWI4ZjQ1MmM5ZDZiNjI1NGI1NDc4NTY5YTYwYjBiNmZiMDMxYzdhZDJmMDZlYTNkYWNmMmNlODc0Y2E3MDgzIn19fQ=="
+
+name: "<red>Combat Sack"
+
+abilities:
+  sack:
+    name: Show Combat sack
+    description: <dark_gray>Explore and manage your combat sack
+    triggers:
+      - RIGHT_CLICK
+      - ON_PICKUP
+    handler_id: sack
+    params:
+      sack_type: COMBAT
+      profile_id: combat_sack

+ 21 - 0
src/main/resources/custom/items/farming_sack.yml

@@ -0,0 +1,21 @@
+id: "farming_sack"
+version: 1
+
+material: PLAYER_HEAD
+
+head:
+  texture: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmFiODc5ZTFlNTkwMDQxMTQ2YmM3OGMwMThhZjU4NzdkMzllNTQ3NWViN2RiMzY4ZmNhZjJhY2RhMzczODMzZCJ9fX0="
+
+name: "<yellow>Farming Sack"
+
+abilities:
+  sack:
+    name: Show Farming sack
+    description: <dark_gray>Explore and manage your farming sack
+    triggers:
+      - RIGHT_CLICK
+      - ON_PICKUP
+    handler_id: sack
+    params:
+      sack_type: FARMING
+      profile_id: farming_sack

+ 21 - 0
src/main/resources/custom/items/fishing_sack.yml

@@ -0,0 +1,21 @@
+id: "fishing_sack"
+version: 1
+
+material: PLAYER_HEAD
+
+head:
+  texture: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzRmYmM3N2QyN2RhYjEwZGU1YWJmOWQwYmNjNDcxOThmOTIzYjBlNmMxNzlhM2MzOTgzZTc3ZjEyZjdjMDMzZSJ9fX0="
+
+name: "<blue>Fishing Sack"
+
+abilities:
+  sack:
+    name: Show Fishing sack
+    description: <dark_gray>Explore and manage your fishing sack
+    triggers:
+      - RIGHT_CLICK
+      - ON_PICKUP
+    handler_id: sack
+    params:
+      sack_type: FISHING
+      profile_id: fishing_sack

+ 21 - 0
src/main/resources/custom/items/foraging_sack.yml

@@ -0,0 +1,21 @@
+id: "foraging_sack"
+version: 1
+
+material: PLAYER_HEAD
+
+head:
+  texture: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGY5NjBjNjM5ZDQwMDRkMTg4MjU3NWFlYmE2OWY0NTZmYjNjNzQ0MDc3OTM1NzE0OTQ3ZTE5YzEzMDZkMjczMyJ9fX0="
+
+name: "<brown>Foraging Sack"
+
+abilities:
+  sack:
+    name: Show Foraging sack
+    description: <dark_gray>Explore and manage your foraging sack
+    triggers:
+      - RIGHT_CLICK
+      - ON_PICKUP
+    handler_id: sack
+    params:
+      sack_type: FORAGING
+      profile_id: foraging_sack

+ 21 - 0
src/main/resources/custom/items/mining_sack.yml

@@ -0,0 +1,21 @@
+id: "mining_sack"
+version: 1
+
+material: PLAYER_HEAD
+
+head:
+  texture: "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjA3ZGZmNjU3ZDYxZjMwMmM3ZDJlNzI1MjY1ZDE3YjY0YWE3MzY0MjM5MTk2NGZiNDhmYzE1YmU5NTA4MzFkOCJ9fX0="
+
+name: "<gray>Mining Sack"
+
+abilities:
+  sack:
+    name: Show Mining sack
+    description: <dark_gray>Explore and manage your mining sack
+    triggers:
+      - RIGHT_CLICK
+      - ON_PICKUP
+    handler_id: sack
+    params:
+      sack_type: MINING
+      profile_id: mining_sack

+ 2 - 2
src/main/resources/custom/items/regen_block.yml

@@ -10,10 +10,10 @@ name: "<blue><bold>Regenerating Block"
 
 abilities:
   place:
-    name: Place a block
+    name: Modify Block
     description: <gray>Turn the clicked block into a <red>regenerating</red> block! The current regeneration period is {regen_time}s
     description_width_ref: Allows you to gamble away your
-    trigger: RIGHT_CLICK
+    trigger: RIGHT_CLICK_BLOCK
     handler_id: regenerating_block
     params:
       regen_time: 10

+ 14 - 0
src/main/resources/custom/items/snowballShooter.yml

@@ -0,0 +1,14 @@
+id: "snowball_shooter"
+version: 1
+
+material: SNOWBALL
+
+name: "<blue><bold>Snowball"
+
+abilities:
+  select:
+    name: Gamble with a Dice
+    description: <gray>Current snowballs - {snowballs}/{maxSnowballs}
+    description_width_ref: Click on a block to set the first
+    trigger: RIGHT_CLICK
+    handler_id: snowball

+ 0 - 0
src/main/resources/custom/mobs/custom_drops.yml


+ 0 - 0
src/main/resources/custom/mobs/custom_mobs.yml


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

@@ -3,6 +3,8 @@ version: ${project.version}
 main: me.lethunderhawk.main.BazaarFlux
 api-version: 1.21.10
 prefix: BazaarFlux
+libraries:
+  - org.openjdk.nashorn:nashorn-core:15.7
 depend:
   - FluxAPI