소스 검색

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 2 주 전
부모
커밋
5494dd2ccd
53개의 변경된 파일1304개의 추가작업 그리고 220개의 파일을 삭제
  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