Quellcode durchsuchen

Abilities can turn off/on if they are shown in lore of items inside each config.
fix: item names in shop editor longer than the sign-menu width can be typed into chat by writing chat on the sign, or exit to exit safely
Added Custom Trigger Variants, (e.g. fishing)
Added auto-migration on join if players inventory-item differs from config file
Added Support for custom mobs (in-code enum, to be changed), damage indicator and health

Jan vor 2 Wochen
Ursprung
Commit
1c99cef7fc
41 geänderte Dateien mit 1001 neuen und 212 gelöschten Zeilen
  1. 7 7
      src/main/java/me/lethunderhawk/clans/Clan.java
  2. 6 11
      src/main/java/me/lethunderhawk/clans/gui/ClanMenu.java
  3. 68 7
      src/main/java/me/lethunderhawk/clans/gui/MemberListGUI.java
  4. 80 0
      src/main/java/me/lethunderhawk/custom/block/RegeneratingBlock.java
  5. 15 4
      src/main/java/me/lethunderhawk/custom/block/RegeneratingBlockListener.java
  6. 4 0
      src/main/java/me/lethunderhawk/custom/block/UnPushableBlock.java
  7. 5 100
      src/main/java/me/lethunderhawk/custom/block/registry/BlockRegistry.java
  8. 116 0
      src/main/java/me/lethunderhawk/custom/entity/CustomEntity.java
  9. 57 0
      src/main/java/me/lethunderhawk/custom/entity/CustomEntityCommand.java
  10. 90 0
      src/main/java/me/lethunderhawk/custom/entity/CustomEntityListener.java
  11. 30 0
      src/main/java/me/lethunderhawk/custom/entity/CustomEntityModule.java
  12. 55 0
      src/main/java/me/lethunderhawk/custom/entity/CustomEntityRegistry.java
  13. 57 0
      src/main/java/me/lethunderhawk/custom/entity/EntityUtil.java
  14. 36 0
      src/main/java/me/lethunderhawk/custom/entity/LootItem.java
  15. 18 14
      src/main/java/me/lethunderhawk/custom/item/CustomItemModule.java
  16. 1 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityContext.java
  17. 9 4
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityDefinition.java
  18. 9 14
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityTrigger.java
  19. 29 0
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/CustomItemTrigger.java
  20. 35 0
      src/main/java/me/lethunderhawk/custom/item/abstraction/ability/FishingRodTrigger.java
  21. 9 6
      src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinitionLoader.java
  22. 1 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/handling/TriggerHandler.java
  23. 2 1
      src/main/java/me/lethunderhawk/custom/item/abstraction/instance/ItemInstanceReader.java
  24. 6 5
      src/main/java/me/lethunderhawk/custom/item/abstraction/migration/MigrationService.java
  25. 6 6
      src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerHandlerRegistry.java
  26. 29 0
      src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerRegistry.java
  27. 8 8
      src/main/java/me/lethunderhawk/custom/item/abstraction/runtime/AbilityDispatchService.java
  28. 2 2
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/ClaimToolTrigger.java
  29. 2 2
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/HyperionTrigger.java
  30. 2 2
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/RegeneratingBlockTrigger.java
  31. 2 2
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/RollingDiceTrigger.java
  32. 2 2
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/SackTrigger.java
  33. 2 2
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/SnowBallShooterTrigger.java
  34. 2 2
      src/main/java/me/lethunderhawk/custom/item/concrete/ability/TestTriggerHandler.java
  35. 26 6
      src/main/java/me/lethunderhawk/custom/item/listener/CustomItemListener.java
  36. 38 1
      src/main/java/me/lethunderhawk/economy/shop/BuyingItemsEditGUI.java
  37. 36 0
      src/main/java/me/lethunderhawk/economy/shop/SellingItemsEditGUI.java
  38. 7 1
      src/main/java/me/lethunderhawk/economy/shop/ShopGUI.java
  39. 5 1
      src/main/java/me/lethunderhawk/main/BazaarFlux.java
  40. 29 0
      src/main/java/me/lethunderhawk/util/listeners/ChatListener.java
  41. 58 0
      src/main/resources/custom/enchantment/messages.yml

+ 7 - 7
src/main/java/me/lethunderhawk/clans/Clan.java

@@ -7,6 +7,7 @@ import net.kyori.adventure.text.event.ClickEvent;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.Bukkit;
 import org.bukkit.Location;
+import org.bukkit.OfflinePlayer;
 import org.bukkit.entity.Player;
 
 import java.util.*;
@@ -120,14 +121,13 @@ public class Clan {
         return owner;
     }
 
-    public ArrayList<Player> getAllMembers() {
-        ArrayList<Player> playerList = new ArrayList<>();
-        playerList.add(Bukkit.getPlayer(owner));
+    public ArrayList<OfflinePlayer> getAllMembers() {
+        ArrayList<OfflinePlayer> playerList = new ArrayList<>();
+        playerList.add(Bukkit.getOfflinePlayer(owner));
+
         for (UUID uuid : members) {
-            Player player = Bukkit.getPlayer(uuid);
-            if (player != null) {
-                playerList.add(player);
-            }
+            OfflinePlayer player = Bukkit.getOfflinePlayer(uuid);
+            playerList.add(player);
         }
 
         return playerList;

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

@@ -3,9 +3,8 @@ package me.lethunderhawk.clans.gui;
 import me.lethunderhawk.clans.Clan;
 import me.lethunderhawk.clans.ClanManager;
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
 import me.lethunderhawk.fluxapi.util.UnItalic;
-import me.lethunderhawk.fluxapi.util.gui.PlayerHeadListGUI;
+import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.Material;
@@ -49,22 +48,18 @@ public class ClanMenu extends InventoryGUI {
 
     private void buildMemberList() {
         ItemStack memberList = buildMemberListIem();
+
+
         setItemWithClickAction(13, memberList, (p, click) -> {
-            PlayerHeadListGUI playerList = new PlayerHeadListGUI("Members", 36, FluxService.get(ClanManager.class).getMyClan(p.getUniqueId()).getAllMembers(), this::handleClanMemberClick);
+            Clan myClan = FluxService.get(ClanManager.class).getMyClan(p.getUniqueId());
+            if(myClan == null) return;
+            MemberListGUI playerList = new MemberListGUI("Members", 36, myClan.getAllMembers(), p);
             playerList.setBackButton(30);
             playerList.setCloseButton(31);
             openNext(p, playerList);
         });
     }
 
-    private void handleClanMemberClick(Player player, Player target) {
-        ClanManager manager = FluxService.get(ClanManager.class);
-
-        //if(!manager.getMyClan(player.getUniqueId()).getOwnerUUID().equals(player.getUniqueId())) return;
-        //if(player.getUniqueId().equals(target.getUniqueId())) return;
-        manager.leaveClan(target.getUniqueId());
-    }
-
     private ItemStack buildMemberListIem() {
         ItemStack memberListItem = new ItemStack(Material.PLAYER_HEAD);
         ItemMeta meta = memberListItem.getItemMeta();

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

@@ -1,24 +1,85 @@
 package me.lethunderhawk.clans.gui;
 
-import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
+import me.lethunderhawk.clans.ClanManager;
+import me.lethunderhawk.clans.ClanModule;
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.util.UnItalic;
+import me.lethunderhawk.fluxapi.util.gui.PaginatedInventoryGUI;
+import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
+import net.kyori.adventure.text.Component;
+import org.bukkit.Material;
+import org.bukkit.OfflinePlayer;
 import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.SkullMeta;
 
-public class MemberListGUI extends InventoryGUI {
-    private final Player player;
+import java.util.List;
 
-    public MemberListGUI(Player player) {
-        super("Members", 36);
-        this.player = player;
+public class MemberListGUI extends PaginatedInventoryGUI<OfflinePlayer> {
+    private final List<OfflinePlayer> players;
+
+    public MemberListGUI(String title, int size, List<OfflinePlayer> players, Player player) {
+        super(title, size, player);
+        this.players = players;
     }
 
     @Override
     public void buildContent() {
         fillGlassPaneBackground();
+        List<Integer> slots = computeRectangleSlots(10, 25);
+
+        setupPagination(players, slots);
+
+        renderPage();
+        addNavigationButtons();
+
         setBackButton(30);
         setCloseButton(31);
     }
+    @Override
+    protected void onClick(OfflinePlayer target, Player player, ClickType type) {
+        ClanManager manager = FluxService.get(ClanManager.class);
+        if(!player.isOp()) {
+            if (!manager.getMyClan(player.getUniqueId()).getOwnerUUID().equals(player.getUniqueId())) {
+                FluxService.get(ClanModule.class).sendText(player, Component.text("Cant remove this person, since you are not the owner!"));
+                return;
+            }
+
+            if (player.getUniqueId().equals(target.getUniqueId())) {
+                FluxService.get(ClanModule.class).sendText(player, Component.text("Cant remove yourself!"));
+                return;
+            }
+        }
+        manager.leaveClan(target.getUniqueId());
+    }
+    /*public void buildContent() {
+        int slot = 0;
+
+        for(OfflinePlayer target : this.players) {
+            if (slot >= this.getInventory().getSize()) {
+                break;
+            }
+
+            ItemStack head = this.createPlayerHead(target);
+            this.setItemWithClickAction(slot, head, (clickingPlayer, type) -> this.handleClanMemberClick(clickingPlayer, target));
+            ++slot;
+        }
+
+        this.fillBackground(Material.GRAY_STAINED_GLASS_PANE, " ");
+    }*/
 
     public void update() {
-        
+    }
+
+    @Override
+    protected ItemStack createItem(OfflinePlayer player) {
+        ItemStack head = new ItemStack(Material.PLAYER_HEAD);
+        SkullMeta meta = (SkullMeta) head.getItemMeta();
+        meta.setOwningPlayer(player);
+        meta.displayName(LoreDesigner.createSingle("<green>" + player.getName()));
+        meta.lore(LoreDesigner.createLore("<yellow>Click to remove from clan!"));
+        head.setItemMeta(UnItalic.removeItalicFromMeta(meta));
+        return head;
     }
 }

+ 80 - 0
src/main/java/me/lethunderhawk/custom/block/RegeneratingBlock.java

@@ -0,0 +1,80 @@
+package me.lethunderhawk.custom.block;
+
+import me.lethunderhawk.custom.block.registry.BlockRegistry;
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.main.BazaarFlux;
+import org.bukkit.Bukkit;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.scheduler.BukkitTask;
+
+public final class RegeneratingBlock {
+
+    private final BlockRegistry.BlockKey key;
+    private final BlockData originalData;
+    private final long regenTimeSeconds;
+
+    private BukkitTask regenTask;
+    private boolean broken;
+
+    public RegeneratingBlock(BlockRegistry.BlockKey key, BlockData originalData, long regenTimeSeconds) {
+        this.key = key;
+        this.originalData = originalData;
+        this.regenTimeSeconds = regenTimeSeconds;
+    }
+
+    /* ---------- lifecycle ---------- */
+
+    public void onDestroyed() {
+        if (broken) return;
+        broken = true;
+
+        // hook: destroyed
+        onDestroyLogic();
+
+        regenTask = Bukkit.getScheduler().runTaskLater(
+                FluxService.get(BazaarFlux.class),
+                this::regenerate,
+                regenTimeSeconds * 20L
+        );
+    }
+
+    public void onPlaced() {
+        if (!broken) return;
+        broken = false;
+
+        // cancel pending regen
+        if (regenTask != null) {
+            regenTask.cancel();
+            regenTask = null;
+        }
+
+        // hook: placed
+        onPlaceLogic();
+    }
+
+    private void regenerate() {
+        Block block = key.getBlock();
+        if (block == null || !block.getType().isAir()) return;
+
+        block.setBlockData(originalData, false);
+        broken = false;
+
+        // hook: regenerated
+        onRegenLogic();
+    }
+
+    /* ---------- hooks ---------- */
+
+    private void onDestroyLogic() {
+
+    }
+
+    private void onPlaceLogic() {
+        // maybe reward player, cancel cooldown, etc.
+    }
+
+    private void onRegenLogic() {
+        // effects, sounds, mining reset, etc.
+    }
+}

+ 15 - 4
src/main/java/me/lethunderhawk/custom/block/RegeneratingBlockListener.java

@@ -1,18 +1,29 @@
 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;
 import org.bukkit.event.block.BlockBreakEvent;
 import org.bukkit.event.block.BlockPlaceEvent;
 
 public class RegeneratingBlockListener implements Listener {
-    @EventHandler
-    public void onBlockPlace(BlockPlaceEvent e){
+    @EventHandler(ignoreCancelled = true)
+    public void onBlockBreak(BlockBreakEvent event) {
+        Block block = event.getBlock();
+        RegeneratingBlock data = FluxService.get(BlockRegistry.class).getBlocks().get(BlockRegistry.BlockKey.from(block));
+        if (data == null) return;
 
+        data.onDestroyed();
     }
 
-    @EventHandler
-    public void onBlockBreak(BlockBreakEvent e){
+    @EventHandler(ignoreCancelled = true)
+    public void onBlockPlace(BlockPlaceEvent event) {
+        Block block = event.getBlockPlaced();
+        RegeneratingBlock data = FluxService.get(BlockRegistry.class).getBlocks().get(BlockRegistry.BlockKey.from(block));
+        if (data == null) return;
 
+        data.onPlaced();
     }
 }

+ 4 - 0
src/main/java/me/lethunderhawk/custom/block/UnPushableBlock.java

@@ -0,0 +1,4 @@
+package me.lethunderhawk.custom.block;
+
+public class UnPushableBlock {
+}

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

@@ -1,21 +1,14 @@
 package me.lethunderhawk.custom.block.registry;
 
-import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.main.BazaarFlux;
+import me.lethunderhawk.custom.block.RegeneratingBlock;
 import org.bukkit.Bukkit;
 import org.bukkit.World;
 import org.bukkit.block.Block;
-import org.bukkit.block.data.BlockData;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.block.BlockBreakEvent;
-import org.bukkit.event.block.BlockPlaceEvent;
-import org.bukkit.scheduler.BukkitTask;
 
 import java.util.HashMap;
 import java.util.Map;
 
-public class BlockRegistry implements Listener {
+public class BlockRegistry{
 
     private final Map<BlockKey, RegeneratingBlock> blocks = new HashMap<>();
 
@@ -44,105 +37,17 @@ public class BlockRegistry implements Listener {
     public boolean contains(Block clicked) {
         return blocks.containsKey(BlockKey.from(clicked));
     }
+
     /* =====================================================
        EVENTS
        ===================================================== */
 
-    @EventHandler(ignoreCancelled = true)
-    public void onBlockBreak(BlockBreakEvent event) {
-        Block block = event.getBlock();
-        RegeneratingBlock data = blocks.get(BlockKey.from(block));
-        if (data == null) return;
-
-        data.onDestroyed();
-    }
-
-    @EventHandler(ignoreCancelled = true)
-    public void onBlockPlace(BlockPlaceEvent event) {
-        Block block = event.getBlockPlaced();
-        RegeneratingBlock data = blocks.get(BlockKey.from(block));
-        if (data == null) return;
-
-        data.onPlaced();
+    public Map<BlockKey, RegeneratingBlock> getBlocks() {
+        return new HashMap<>(blocks);
     }
 
 
 
-    /* =====================================================
-       INTERNAL DATA CLASS
-       ===================================================== */
-
-    private static final class RegeneratingBlock {
-
-        private final BlockKey key;
-        private final BlockData originalData;
-        private final long regenTimeSeconds;
-
-        private BukkitTask regenTask;
-        private boolean broken;
-
-        private RegeneratingBlock(BlockKey key, BlockData originalData, long regenTimeSeconds) {
-            this.key = key;
-            this.originalData = originalData;
-            this.regenTimeSeconds = regenTimeSeconds;
-        }
-
-        /* ---------- lifecycle ---------- */
-
-        void onDestroyed() {
-            if (broken) return;
-            broken = true;
-
-            // hook: destroyed
-            onDestroyLogic();
-
-            regenTask = Bukkit.getScheduler().runTaskLater(
-                    FluxService.get(BazaarFlux.class),
-                    this::regenerate,
-                    regenTimeSeconds * 20L
-            );
-        }
-
-        void onPlaced() {
-            if (!broken) return;
-            broken = false;
-
-            // cancel pending regen
-            if (regenTask != null) {
-                regenTask.cancel();
-                regenTask = null;
-            }
-
-            // hook: placed
-            onPlaceLogic();
-        }
-
-        private void regenerate() {
-            Block block = key.getBlock();
-            if (block == null || !block.getType().isAir()) return;
-
-            block.setBlockData(originalData, false);
-            broken = false;
-
-            // hook: regenerated
-            onRegenLogic();
-        }
-
-        /* ---------- hooks ---------- */
-
-        private void onDestroyLogic() {
-
-        }
-
-        private void onPlaceLogic() {
-            // maybe reward player, cancel cooldown, etc.
-        }
-
-        private void onRegenLogic() {
-            // effects, sounds, mining reset, etc.
-        }
-    }
-
     /* =====================================================
        BLOCK KEY
        ===================================================== */

+ 116 - 0
src/main/java/me/lethunderhawk/custom/entity/CustomEntity.java

@@ -0,0 +1,116 @@
+package me.lethunderhawk.custom.entity;
+
+import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
+import org.bukkit.Location;
+import org.bukkit.attribute.Attribute;
+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);
+
+    private String name;
+    private double maxHealth;
+    private double spawnChance;
+    private double currentHealth;
+    private EntityType type;
+    private List<LootItem> lootTable = new ArrayList<>();
+    private ItemStack mainHandItem;
+    private ItemStack[] armor;
+
+    CustomEntity(String name, double maxHealth, double spawnChance, EntityType type, List<LootItem> lootTable, ItemStack mainHandItem, ItemStack[] armor) {
+        this.name = name;
+        this.maxHealth = maxHealth;
+        this.spawnChance = spawnChance;
+        this.type = type;
+        this.lootTable = lootTable;
+        this.mainHandItem = mainHandItem;
+        this.armor = armor;
+        this.currentHealth = maxHealth;
+    }
+
+    public LivingEntity writeIntoEntity(LivingEntity entity) {
+        if(!entity.getType().equals(type)) return entity;
+
+        entity.setCustomNameVisible(true);
+
+        if(entity.getAttribute(Attribute.MAX_HEALTH) != null){
+            entity.getAttribute(Attribute.MAX_HEALTH).setBaseValue(maxHealth);
+            entity.setHealth(maxHealth);
+        }
+        EntityUtil.writeIntoBaseName(entity, name);
+        entity.customName(LoreDesigner.createSingle(name));
+
+        EntityUtil.updateHealthInName(entity, maxHealth);
+
+        EntityEquipment equipment = entity.getEquipment();
+        if(armor != null && equipment != null){
+            equipment.setArmorContents(armor);
+            setArmorDropChance(equipment, 0f);
+            equipment.setItemInMainHand(mainHandItem);
+            equipment.setItemInMainHandDropChance(0f);
+        }
+        return entity;
+    }
+
+    public void setArmorDropChance(EntityEquipment equipment, double chance) {
+        equipment.setHelmetDropChance(0f);
+        equipment.setChestplateDropChance(0f);
+        equipment.setLeggingsDropChance(0f);
+        equipment.setBootsDropChance(0f);
+    }
+
+    public LivingEntity spawn(Location location) {
+        LivingEntity entity = (LivingEntity) location.getWorld().spawnEntity(location, type);
+        return writeIntoEntity(entity);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public void tryDropLoot(Location loc) {
+        if(lootTable == null || lootTable.isEmpty()) return;
+        for(LootItem item : lootTable) {
+            item.tryDropItem(loc);
+        }
+    }
+
+    public double getCurrentHealth() {
+        return currentHealth;
+    }
+
+    public EntityType getType() {
+        return type;
+    }
+
+    public List<LootItem> getLootTable() {
+        return lootTable;
+    }
+
+    public ItemStack getMainHandItem() {
+        return mainHandItem;
+    }
+
+    public ItemStack[] getArmor() {
+        return armor;
+    }
+
+    public double getSpawnChance() {
+        return spawnChance;
+    }
+
+    public double getMaxHealth() {
+        return maxHealth;
+    }
+
+    public void updateHealthInName(Entity entity, double newHealth) {
+        entity.customName(LoreDesigner.createSingle(name + " <red>" + (int) Math.round(newHealth) + "/" + (int) maxHealth + "♥"));
+    }
+}

+ 57 - 0
src/main/java/me/lethunderhawk/custom/entity/CustomEntityCommand.java

@@ -0,0 +1,57 @@
+package me.lethunderhawk.custom.entity;
+
+import me.lethunderhawk.fluxapi.util.command.CommandNode;
+import me.lethunderhawk.fluxapi.util.command.CustomCommand;
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
+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 {
+    public CustomEntityCommand(FluxAPIModule module) {
+        super(module);
+
+    }
+
+    @Override
+    public CommandNode getRootCommand() {
+        return new CommandNode("custommobs", "Base Command", null);
+    }
+
+    @Override
+    public void createCommands() {
+        CommandNode spawn = new CommandNode("spawn", "Spawn a custom mob", this::spawn);
+        spawn.setTabCompleter(this::getAllCustomMobs);
+        rootCommand.addSubCommands(
+                spawn,
+                new CommandNode("edit", "Edit the custom mobs", this::edit),
+                new CommandNode("reload", "Reload the Module", this::reload)
+        );
+    }
+
+    private void reload(CommandSender sender, String[] strings) {
+        module.reload(sender);
+    }
+
+    private void spawn(CommandSender sender, String[] strings) {
+        if(!(sender instanceof LivingEntity living)) return;
+        try{
+            CustomEntity entity = CustomEntity.valueOf(strings[0]);
+            entity.spawn(living.getLocation());
+
+        }catch (Exception e){
+            sender.sendMessage(LoreDesigner.createSingle("<red>Invalid Custom Entity!"));
+        }
+    }
+
+    private List<String> getAllCustomMobs(CommandSender sender, String[] strings) {
+        return Arrays.stream(CustomEntity.values()).map(CustomEntity::name).toList();
+    }
+
+    private void edit(CommandSender sender, String[] strings) {
+
+    }
+}

+ 90 - 0
src/main/java/me/lethunderhawk/custom/entity/CustomEntityListener.java

@@ -0,0 +1,90 @@
+package me.lethunderhawk.custom.entity;
+
+import com.destroystokyo.paper.event.entity.EntityRemoveFromWorldEvent;
+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.ArmorStand;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.LivingEntity;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.event.entity.EntityDeathEvent;
+import org.bukkit.event.entity.EntitySpawnEvent;
+
+import java.text.DecimalFormat;
+
+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;
+            }
+        }
+        if(!isCustomEntity && event.getEntity() instanceof LivingEntity living && !(event.getEntity() instanceof ArmorStand)){
+            if(living.getAttribute(Attribute.MAX_HEALTH) instanceof AttributeInstance instance){
+                EntityUtil.updateHealthInName(living, instance.getBaseValue());
+            }
+        }
+    }
+
+    @EventHandler
+    public void onEntityDamage(EntityDamageEvent event){
+        Entity entity = event.getEntity();
+        if(!(entity instanceof LivingEntity living) || entity instanceof ArmorStand) return;
+        double damage = event.getFinalDamage(), health = living.getHealth() + living.getAbsorptionAmount();
+        if(health > damage){
+            health -= damage;
+        }
+
+        EntityUtil.updateHealthInName(living, health);
+        spawnDecayingDamageIndicator(living, damage);
+    }
+
+    @EventHandler
+    public void onEntityDeath(EntityDeathEvent event){
+        LivingEntity le = event.getEntity();
+        EntityUtil.clearBaseName(le);
+
+
+        if(!CustomEntityRegistry.contains(event.getEntity())) return;
+
+        event.setDroppedExp(0);
+        event.getDrops().clear();
+        CustomEntityRegistry.remove(event.getEntity()).tryDropLoot(event.getEntity().getLocation());
+    }
+
+    @EventHandler
+    public void onEntityUnload(EntityRemoveFromWorldEvent event){
+        CustomEntityRegistry.remove(event.getEntity());
+    }
+
+    private void spawnDecayingDamageIndicator(LivingEntity entity, double damage){
+        Location location = entity.getLocation().clone().add(getRandomOffset(),getRandomOffset()+1, getRandomOffset());
+
+        entity.getWorld().spawn(location, ArmorStand.class, armorStand -> {
+            armorStand.setMarker(true);
+            armorStand.setVisible(false);
+            armorStand.setGravity(false);
+            armorStand.setSmall(true);
+            armorStand.setCustomNameVisible(true);
+            armorStand.customName(LoreDesigner.createSingle("<red>" + formatter.format(damage)));
+            CustomEntityRegistry.getIndicators().put(armorStand, 30);
+        });
+    }
+
+    private double getRandomOffset(){
+        double random = Math.random();
+        if(random > 0.5) random *= -1;
+        return random;
+    }
+}

+ 30 - 0
src/main/java/me/lethunderhawk/custom/entity/CustomEntityModule.java

@@ -0,0 +1,30 @@
+package me.lethunderhawk.custom.entity;
+
+import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class CustomEntityModule extends FluxAPIModule {
+
+    public CustomEntityModule(JavaPlugin plugin) {
+        super(plugin);
+    }
+
+    @Override
+    public String getPrefix() {
+        return "[Custom Mobs]";
+    }
+
+    @Override
+    public void onEnable() {
+        registerListener(new CustomEntityListener());
+        registerCommand("custommobs", new CustomEntityCommand(this));
+    }
+
+    @Override
+    public void onDisable() {
+        CustomEntityRegistry.getIndicators().forEach((entity, indicator) -> {
+            entity.remove();
+        });
+        unregisterAllListeners();
+    }
+}

+ 55 - 0
src/main/java/me/lethunderhawk/custom/entity/CustomEntityRegistry.java

@@ -0,0 +1,55 @@
+package me.lethunderhawk.custom.entity;
+
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.main.BazaarFlux;
+import org.bukkit.entity.Entity;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+public class CustomEntityRegistry {
+    private static Map<Entity, CustomEntity> entities = new HashMap<>();
+    private static Map<Entity, Integer> indicators = new HashMap<>();
+
+    static {
+        new BukkitRunnable() {
+            Set<Entity> armorStands = indicators.keySet();
+            List<Entity> removals = new ArrayList<>();
+            @Override
+            public void run() {
+                for(Entity stand : armorStands) {
+                    int ticksLeft = indicators.get(stand);
+                    if(ticksLeft == 0) {
+                        stand.remove();
+                        removals.add(stand);
+                        continue;
+                    }
+                    ticksLeft--;
+                    indicators.put(stand, ticksLeft);
+                }
+                armorStands.removeAll(removals);
+            }
+        }.runTaskTimer(FluxService.get(BazaarFlux.class), 0L, 1L);
+    }
+
+    public static CustomEntity remove(@NotNull Entity entity) {
+        return entities.remove(entity);
+    }
+
+    public static void add(@NotNull Entity entity, @NotNull CustomEntity customEntity) {
+        entities.put(entity, customEntity);
+    }
+
+    public static boolean contains(@NotNull Entity entity) {
+        return entities.containsKey(entity);
+    }
+
+    public static CustomEntity get(Entity entity) {
+        return entities.get(entity);
+    }
+
+    public static Map<Entity, Integer> getIndicators() {
+        return indicators;
+    }
+}

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

@@ -0,0 +1,57 @@
+package me.lethunderhawk.custom.entity;
+
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
+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.LivingEntity;
+import org.bukkit.persistence.PersistentDataType;
+
+public final class EntityUtil {
+    private EntityUtil() {}
+
+    private static final NamespacedKey KEY_BASE_NAME = new NamespacedKey(FluxService.get(BazaarFlux.class), "base_name");
+
+    public static void updateHealthInName(LivingEntity entity, double newHealth) {
+        if (entity.isDead()) return;
+
+        // store base name if not present
+        var pdc = entity.getPersistentDataContainer();
+        if (!pdc.has(KEY_BASE_NAME, PersistentDataType.STRING)) {
+            Component custom_name = entity.customName();
+            String base = custom_name != null ? PlainTextComponentSerializer.plainText().serialize(custom_name) : entity.getName();
+            pdc.set(KEY_BASE_NAME, PersistentDataType.STRING, base);
+        }
+
+        String baseName = pdc.get(KEY_BASE_NAME, PersistentDataType.STRING);
+        if (baseName == null) baseName = entity.getName();
+
+        int health = (int) Math.floor(newHealth);
+        if(health <= 1) health = 1;
+
+        var maxAttr = entity.getAttribute(org.bukkit.attribute.Attribute.MAX_HEALTH);
+        if (maxAttr == null) return;
+        int maxHealth = (int) Math.floor(maxAttr.getBaseValue());
+
+        Component display = LoreDesigner.createSingle(baseName + " <red>" + health + "/"+ maxHealth + "♥");
+
+        entity.customName(display);
+        entity.setCustomNameVisible(true);
+    }
+
+    public static void clearBaseName(LivingEntity entity) {
+        var pdc = entity.getPersistentDataContainer();
+        if (pdc.has(KEY_BASE_NAME, PersistentDataType.STRING)) {
+            pdc.remove(KEY_BASE_NAME);
+            entity.customName(null);
+            entity.setCustomNameVisible(false);
+        }
+    }
+
+    public static void writeIntoBaseName(LivingEntity entity, String name) {
+        var pdc = entity.getPersistentDataContainer();
+        pdc.set(KEY_BASE_NAME, PersistentDataType.STRING, name);
+    }
+}

+ 36 - 0
src/main/java/me/lethunderhawk/custom/entity/LootItem.java

@@ -0,0 +1,36 @@
+package me.lethunderhawk.custom.entity;
+
+import org.bukkit.Location;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Random;
+
+public class LootItem {
+    private ItemStack item;
+    private int min = 1, max = 1;
+    private double dropChance;
+    private static Random random = new Random();
+
+
+    public LootItem(ItemStack item, double dropChance) {
+        this.item = item;
+        this.dropChance = dropChance;
+    }
+
+    public LootItem(ItemStack item, int min, int max, double dropChance) {
+        this.item = item;
+        this.min = min;
+        this.max = max;
+        this.dropChance = dropChance;
+    }
+
+    public void tryDropItem(Location loc) {
+        if(Math.random() * 101 > dropChance) return;
+        int amount = random.nextInt(max - min + 1) + min;
+        if(amount <= 0) return;
+
+        ItemStack item = this.item.clone();
+        item.setAmount(amount);
+        loc.getWorld().dropItem(loc, item);
+    }
+}

+ 18 - 14
src/main/java/me/lethunderhawk/custom/item/CustomItemModule.java

@@ -1,11 +1,12 @@
 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.definition.ItemDefinitionLoader;
 import me.lethunderhawk.custom.item.abstraction.definition.ItemDefinitionValidator;
-import me.lethunderhawk.custom.item.abstraction.registry.AbilityRegistry;
+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.command.CustomItemCommand;
@@ -29,14 +30,14 @@ public class CustomItemModule extends FluxAPIModule {
     }
 
 
-    private static void registerAbilities(AbilityRegistry abilityRegistry) {
-        abilityRegistry.register("test_handler", new TestAbilityHandler());
-        abilityRegistry.register("rolling_dice", new RollingDiceAbility());
-        abilityRegistry.register("claim_tool", new ClaimToolAbility());
-        abilityRegistry.register("hyperion", new HyperionAbility());
-        abilityRegistry.register("regenerating_block", new RegeneratingBlockAbility());
-        abilityRegistry.register("sack", new SackAbility());
-        abilityRegistry.register("snowball", new SnowBallShooterAbility());
+    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());
     }
 
     @Override
@@ -46,8 +47,8 @@ public class CustomItemModule extends FluxAPIModule {
 
     @Override
     public void onEnable() {
-        AbilityRegistry abilityRegistry = new AbilityRegistry();
-        registerAbilities(abilityRegistry);
+        TriggerHandlerRegistry triggerHandlerRegistry = new TriggerHandlerRegistry();
+        registerAbilities(triggerHandlerRegistry);
 
         ItemData.init(FluxService.get(FluxAPI.class));
 
@@ -62,17 +63,20 @@ public class CustomItemModule extends FluxAPIModule {
         CustomItemRegistry itemRegistry = new CustomItemRegistry().fromRegistry(definitions);
 
         registerListener(new CustomItemListener(
-                new AbilityDispatchService(itemRegistry, abilityRegistry)
+                new AbilityDispatchService(itemRegistry, triggerHandlerRegistry)
         ));
 
         // --- FluxService ---
         FluxService.register(CustomItemRegistry.class, itemRegistry);
         FluxService.register(ItemDefinitionLoader.class, loader);
         FluxService.register(ItemDefinitionValidator.class, validator);
-        FluxService.register(AbilityRegistry.class, abilityRegistry);
+        FluxService.register(TriggerHandlerRegistry.class, triggerHandlerRegistry);
+
         BlockRegistry blockRegistry = new BlockRegistry();
-        bazaarFlux.getServer().getPluginManager().registerEvents(blockRegistry, bazaarFlux);
         FluxService.register(BlockRegistry.class, blockRegistry);
+
+        registerListener(new RegeneratingBlockListener());
+
         registerCommand("customItems", new CustomItemCommand(this));
     }
 

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

@@ -10,5 +10,5 @@ public record AbilityContext(
         ItemInstance itemInstance,
         ItemStack itemStack,
         Event event,
-        AbilityTrigger trigger
+        CustomItemTrigger trigger
 ) {}

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

@@ -11,14 +11,14 @@ import java.util.Map;
 
 public class AbilityDefinition implements Serializable {
 
-    private final List<AbilityTrigger> triggers;
+    private final List<CustomItemTrigger> triggers;
     private final String handlerId;
     private final Map<String, String> params;
     private final String name;
     private final String description;
-
+    private boolean show_in_lore;
     public AbilityDefinition(
-            List<AbilityTrigger> triggers,
+            List<CustomItemTrigger> triggers,
             String handlerId,
             String name,
             String description,
@@ -31,7 +31,7 @@ public class AbilityDefinition implements Serializable {
         this.params = Map.copyOf(params);
     }
 
-    public boolean matchesTrigger(AbilityTrigger trigger) {
+    public boolean matchesTrigger(CustomItemTrigger trigger) {
         return triggers.stream().anyMatch(trigger::isSubTypeOf);
     }
 
@@ -75,6 +75,7 @@ public class AbilityDefinition implements Serializable {
     }
 
     public List<Component> renderLore() {
+        if(!show_in_lore) return new ArrayList<>();
         List<Component> lore = new ArrayList<>();
         String abilityName = "Ability: " + getName() + " [" + (triggers.getFirst()).getDisplayName() + "]";
         lore.add(Component.text(abilityName, NamedTextColor.GOLD));
@@ -82,4 +83,8 @@ public class AbilityDefinition implements Serializable {
 
         return lore;
     }
+    public AbilityDefinition setShowInLore(boolean value){
+        show_in_lore = value;
+        return this;
+    }
 }

+ 9 - 14
src/main/java/me/lethunderhawk/custom/item/abstraction/ability/AbilityTrigger.java

@@ -1,6 +1,8 @@
 package me.lethunderhawk.custom.item.abstraction.ability;
 
-public enum AbilityTrigger {
+import me.lethunderhawk.custom.item.abstraction.registry.TriggerRegistry;
+
+public enum AbilityTrigger implements CustomItemTrigger{
     LEFT_CLICK("LEFT CLICK", null), // top-level
     LEFT_CLICK_BLOCK("LEFT CLICK", LEFT_CLICK),
     LEFT_CLICK_AIR("LEFT CLICK", LEFT_CLICK),
@@ -15,7 +17,8 @@ public enum AbilityTrigger {
     ON_HIT("ON HIT", null),
     ON_INTERACT("ON INTERACT", null),
     ON_SNEAK("ON SNEAK", null),
-    ON_PICKUP("ON ITEM PICKUP", null);
+    ON_PICKUP("ON ITEM PICKUP", null),
+    PHYSICAL("PHYSICAL", null),;
 
     private final String displayName;
     private final AbilityTrigger parent;
@@ -25,24 +28,16 @@ public enum AbilityTrigger {
         this.parent = parent;
     }
 
+    @Override
     public String getDisplayName() {
         return displayName;
     }
 
+    @Override
     public AbilityTrigger getParent() {
         return parent;
     }
-
-    /**
-     * Checks if this trigger is equivalent to or a subtype of the given other trigger.
-     * E.g. LEFT_CLICK_BLOCK.isSubTypeOf(LEFT_CLICK) == true
-     */
-    public boolean isSubTypeOf(AbilityTrigger other) {
-        AbilityTrigger current = this;
-        while (current != null) {
-            if (current == other) return true;
-            current = current.getParent();
-        }
-        return false;
+    static {
+        TriggerRegistry.registerAll(values());
     }
 }

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

@@ -0,0 +1,29 @@
+package me.lethunderhawk.custom.item.abstraction.ability;
+
+/**
+ * Recommended to automatically register every enum using:
+ * <br>
+ * {@code static {
+ *         TriggerRegistry.registerAll(values());
+ *     }}
+ */
+public interface CustomItemTrigger {
+
+    String getDisplayName();
+
+    CustomItemTrigger getParent();
+
+    /**
+     * Checks if this trigger is equivalent to or a subtype of the given other trigger.
+     * E.g. LEFT_CLICK_BLOCK.isSubTypeOf(LEFT_CLICK) == true
+     */
+    default boolean isSubTypeOf(CustomItemTrigger other) {
+        CustomItemTrigger current = this;
+        while (current != null) {
+            if (current == other) return true;
+            current = current.getParent();
+        }
+        return false;
+    }
+
+}

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

@@ -0,0 +1,35 @@
+package me.lethunderhawk.custom.item.abstraction.ability;
+
+import me.lethunderhawk.custom.item.abstraction.registry.TriggerRegistry;
+
+public enum FishingRodTrigger implements CustomItemTrigger{
+    FISHING("FISHING", null),
+    CAUGHT_FISH("CAUGHT FISH", null),
+    FISHING_ROD_FAILED("FISHING ROD FAILED", null),
+    BOBBER_HOOKED("BOBBER HOOKED", null),
+
+    LURED("LURED", null),
+    REEL_IN("REEL IN", null),
+    BITE("BITE", null);
+
+
+
+    private final String displayName;
+    private final FishingRodTrigger parent;
+
+    FishingRodTrigger(String displayName, FishingRodTrigger parent) {
+        this.displayName = displayName;
+        this.parent = parent;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public FishingRodTrigger getParent() {
+        return parent;
+    }
+    static {
+        TriggerRegistry.registerAll(values());
+    }
+}

+ 9 - 6
src/main/java/me/lethunderhawk/custom/item/abstraction/definition/ItemDefinitionLoader.java

@@ -1,9 +1,11 @@
 package me.lethunderhawk.custom.item.abstraction.definition;
 
-import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
 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;
@@ -127,17 +129,17 @@ public final class ItemDefinitionLoader {
                     );
                 }
 
-                List<AbilityTrigger> triggers = new ArrayList<>();
+                List<CustomItemTrigger> triggers = new ArrayList<>();
                 List<String> triggerList = abilityCfg.getStringList("triggers");
 
                 if (!triggerList.isEmpty()) {
 
                     for (String raw : triggerList) {
                         try {
-                            triggers.add(AbilityTrigger.valueOf(raw.toUpperCase()));
+                            TriggerRegistry.parse(raw.toUpperCase()).ifPresent(triggers::add);
                         } catch (IllegalArgumentException ex) {
                             throw new IllegalStateException(
-                                    "Invalid ability trigger '" + raw + "' in item " + id
+                                    "Unregistered ability trigger '" + raw + "' in item " + id
                             );
                         }
                     }
@@ -167,6 +169,7 @@ public final class ItemDefinitionLoader {
                         abilityCfg.getString("name");
                 String abilityDescription =
                         abilityCfg.getString("description");
+                boolean show_in_lore = abilityCfg.getBoolean("show_in_lore", true);
 
                 if (handler_id == null) {
                     throw new IllegalStateException(
@@ -201,7 +204,7 @@ public final class ItemDefinitionLoader {
                         abilityName,
                         abilityDescription,
                         params
-                ));
+                ).setShowInLore(show_in_lore));
             }
         }
 

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

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

+ 2 - 1
src/main/java/me/lethunderhawk/custom/item/abstraction/instance/ItemInstanceReader.java

@@ -6,13 +6,14 @@ import org.bukkit.inventory.meta.ItemMeta;
 import org.bukkit.persistence.PersistentDataContainer;
 import org.bukkit.persistence.PersistentDataType;
 
+import javax.annotation.Nullable;
 import java.util.Map;
 
 public final class ItemInstanceReader {
 
     private ItemInstanceReader() {}
 
-    public static ItemInstance read(ItemStack stack) {
+    public static @Nullable ItemInstance read(ItemStack stack) {
         if (stack == null || !stack.hasItemMeta()) return null;
 
         ItemMeta meta = stack.getItemMeta();

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

@@ -22,8 +22,10 @@ public final class MigrationService {
         if (current == null) return original;
 
         ItemDefinition latest = FluxService.get(CustomItemRegistry.class).get(current.itemId());
-        if (latest == null) return original;
+        if (latest == null) {
 
+            return original;
+        }
         if (current.version() == latest.version()) {
             return original;
         }
@@ -52,11 +54,10 @@ public final class MigrationService {
 
         for (int i = 0; i < contents.length; i++) {
             ItemStack item = contents[i];
-            if (item == null) continue;
+            if (item == null || item.isEmpty()) continue;
 
             ItemInstance before = ItemInstanceReader.read(item);
             if (before == null) continue;
-
             int oldVersion = before.version();
 
             ItemStack migrated = migrate(item);
@@ -64,11 +65,11 @@ public final class MigrationService {
 
             ItemInstance after = ItemInstanceReader.read(migrated);
             if (after != null && after.version() != oldVersion) {
-                /*player.sendMessage(
+                player.sendMessage(
                         "Migrated " + after.itemId() +
                                 " from v" + oldVersion +
                                 " to v" + after.version()
-                );*/
+                );
                 changed = true;
             }
         }

+ 6 - 6
src/main/java/me/lethunderhawk/custom/item/abstraction/registry/AbilityRegistry.java → src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerHandlerRegistry.java

@@ -1,23 +1,23 @@
 package me.lethunderhawk.custom.item.abstraction.registry;
 
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 
 import java.util.HashMap;
 import java.util.Map;
 
-public final class AbilityRegistry {
+public final class TriggerHandlerRegistry {
 
-    private final Map<String, AbilityHandler> handlers = new HashMap<>();
+    private final Map<String, TriggerHandler> handlers = new HashMap<>();
 
-    public void register(String id, AbilityHandler handler) {
+    public void register(String id, TriggerHandler handler) {
         if (exists(id)) {
             throw new IllegalStateException("Duplicate ability handler: " + id);
         }
         handlers.put(id, handler);
     }
 
-    public AbilityHandler get(String id) {
-        AbilityHandler handler = handlers.get(id);
+    public TriggerHandler get(String id) {
+        TriggerHandler handler = handlers.get(id);
         if (handler == null) {
             throw new IllegalStateException("Missing ability handler: " + id);
         }

+ 29 - 0
src/main/java/me/lethunderhawk/custom/item/abstraction/registry/TriggerRegistry.java

@@ -0,0 +1,29 @@
+package me.lethunderhawk.custom.item.abstraction.registry;
+
+import me.lethunderhawk.custom.item.abstraction.ability.CustomItemTrigger;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+public final class TriggerRegistry {
+    private static final Map<String, CustomItemTrigger> BY_KEY = new HashMap<>();
+
+    private TriggerRegistry() {}
+
+    public static void registerAll(CustomItemTrigger... triggers) {
+        for (CustomItemTrigger t : triggers) register(t);
+    }
+
+    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
+    }
+
+    public static Optional<CustomItemTrigger> parse(String token) {
+        if (token == null) return Optional.empty();
+        return Optional.ofNullable(BY_KEY.get(token.trim().toLowerCase()));
+    }
+}

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

@@ -2,13 +2,13 @@ package me.lethunderhawk.custom.item.abstraction.runtime;
 
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
 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.definition.ItemDefinition;
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import me.lethunderhawk.custom.item.abstraction.instance.ItemInstance;
 import me.lethunderhawk.custom.item.abstraction.instance.ItemInstanceReader;
-import me.lethunderhawk.custom.item.abstraction.registry.AbilityRegistry;
+import me.lethunderhawk.custom.item.abstraction.registry.TriggerHandlerRegistry;
 import me.lethunderhawk.custom.item.abstraction.registry.CustomItemRegistry;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Event;
@@ -20,17 +20,17 @@ import java.util.Map;
 public final class AbilityDispatchService {
 
     private final CustomItemRegistry itemRegistry;
-    private final AbilityRegistry abilityRegistry;
+    private final TriggerHandlerRegistry triggerHandlerRegistry;
     private final Map<String, Long> cooldowns = new HashMap<>();
-    public AbilityDispatchService(CustomItemRegistry itemRegistry, AbilityRegistry abilityRegistry) {
+    public AbilityDispatchService(CustomItemRegistry itemRegistry, TriggerHandlerRegistry triggerHandlerRegistry) {
         this.itemRegistry = itemRegistry;
-        this.abilityRegistry = abilityRegistry;
+        this.triggerHandlerRegistry = triggerHandlerRegistry;
     }
 
     public void dispatch(
             Player player,
             ItemStack stack,
-            AbilityTrigger trigger,
+            CustomItemTrigger trigger,
             Event event
     ) {
         ItemInstance instance = ItemInstanceReader.read(stack);
@@ -52,7 +52,7 @@ public final class AbilityDispatchService {
                     continue;
                 }
 
-                AbilityHandler handler = abilityRegistry.get(ability.handlerId());
+                TriggerHandler handler = triggerHandlerRegistry.get(ability.handlerId());
                 handler.execute(
                         new AbilityContext(player, instance, stack, event, trigger),
                         ResolvedParams.resolve(ability, def, instance)

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/concrete/ability/ClaimToolAbility.java → src/main/java/me/lethunderhawk/custom/item/concrete/ability/ClaimToolTrigger.java

@@ -7,7 +7,7 @@ import me.lethunderhawk.clans.ClanModule;
 import me.lethunderhawk.clans.claim.Claim;
 import me.lethunderhawk.clans.claim.ClaimManager;
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import me.lethunderhawk.main.BazaarFlux;
 import net.kyori.adventure.text.Component;
@@ -21,7 +21,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
-public class ClaimToolAbility implements AbilityHandler {
+public class ClaimToolTrigger implements TriggerHandler {
 
     private final Map<UUID, Location> firstCorner = new HashMap<>();
 

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/concrete/ability/HyperionAbility.java → src/main/java/me/lethunderhawk/custom/item/concrete/ability/HyperionTrigger.java

@@ -1,7 +1,7 @@
 package me.lethunderhawk.custom.item.concrete.ability;
 
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import org.bukkit.*;
 import org.bukkit.block.Block;
@@ -10,7 +10,7 @@ import org.bukkit.entity.Player;
 import org.bukkit.util.RayTraceResult;
 import org.bukkit.util.Vector;
 
-public class HyperionAbility implements AbilityHandler {
+public class HyperionTrigger implements TriggerHandler {
     private static final double DEFAULT_RANGE = 8.0;
 
     @Override

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/concrete/ability/RegeneratingBlockAbility.java → src/main/java/me/lethunderhawk/custom/item/concrete/ability/RegeneratingBlockTrigger.java

@@ -3,13 +3,13 @@ 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.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import org.bukkit.block.Block;
 import org.bukkit.entity.Player;
 import org.bukkit.event.player.PlayerInteractEvent;
 
-public class RegeneratingBlockAbility implements AbilityHandler {
+public class RegeneratingBlockTrigger implements TriggerHandler {
     @Override
     public void execute(AbilityContext context, ResolvedParams params) {
         PlayerInteractEvent event = ((PlayerInteractEvent) context.event());

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/concrete/ability/RollingDiceAbility.java → src/main/java/me/lethunderhawk/custom/item/concrete/ability/RollingDiceTrigger.java

@@ -3,7 +3,7 @@ package me.lethunderhawk.custom.item.concrete.ability;
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.loottables.RiggedChanceGenerator;
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import me.lethunderhawk.custom.item.concrete.dice.DiceReward;
 import me.lethunderhawk.custom.item.concrete.dice.RollingDiceAnimation;
@@ -14,7 +14,7 @@ import org.bukkit.entity.Player;
 
 import java.util.UUID;
 
-public class RollingDiceAbility implements AbilityHandler {
+public class RollingDiceTrigger implements TriggerHandler {
     private int abilityReward;
     private int abilityCost;
     private int abilityRewardHealth;

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/concrete/ability/SackAbility.java → src/main/java/me/lethunderhawk/custom/item/concrete/ability/SackTrigger.java

@@ -2,7 +2,7 @@ package me.lethunderhawk.custom.item.concrete.ability;
 
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityTrigger;
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.profile.ProfileManager;
@@ -16,7 +16,7 @@ import org.bukkit.event.player.PlayerAttemptPickupItemEvent;
 import org.bukkit.event.player.PlayerInteractEvent;
 import org.bukkit.inventory.ItemStack;
 
-public class SackAbility implements AbilityHandler {
+public class SackTrigger implements TriggerHandler {
 
     @Override
     public void execute(AbilityContext context, ResolvedParams params) {

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/concrete/ability/SnowBallShooterAbility.java → src/main/java/me/lethunderhawk/custom/item/concrete/ability/SnowBallShooterTrigger.java

@@ -1,13 +1,13 @@
 package me.lethunderhawk.custom.item.concrete.ability;
 
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 import me.lethunderhawk.fluxapi.util.itemdesign.ItemData;
 import org.bukkit.event.Cancellable;
 import org.bukkit.inventory.ItemStack;
 
-public class SnowBallShooterAbility implements AbilityHandler {
+public class SnowBallShooterTrigger implements TriggerHandler {
 
     @Override
     public void execute(AbilityContext context, ResolvedParams params) {

+ 2 - 2
src/main/java/me/lethunderhawk/custom/item/concrete/ability/TestAbilityHandler.java → src/main/java/me/lethunderhawk/custom/item/concrete/ability/TestTriggerHandler.java

@@ -1,10 +1,10 @@
 package me.lethunderhawk.custom.item.concrete.ability;
 
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityContext;
-import me.lethunderhawk.custom.item.abstraction.handling.AbilityHandler;
+import me.lethunderhawk.custom.item.abstraction.handling.TriggerHandler;
 import me.lethunderhawk.custom.item.abstraction.handling.ResolvedParams;
 
-public class TestAbilityHandler implements AbilityHandler {
+public class TestTriggerHandler implements TriggerHandler {
     @Override
     public void execute(AbilityContext context, ResolvedParams params) {
         context.player().sendMessage("§cAbility of Test item used.");

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

@@ -2,14 +2,16 @@ package me.lethunderhawk.custom.item.listener;
 
 import io.papermc.paper.event.player.PlayerPickBlockEvent;
 import me.lethunderhawk.custom.item.abstraction.ability.AbilityTrigger;
+import me.lethunderhawk.custom.item.abstraction.ability.CustomItemTrigger;
+import me.lethunderhawk.custom.item.abstraction.ability.FishingRodTrigger;
+import me.lethunderhawk.custom.item.abstraction.instance.ItemInstance;
+import me.lethunderhawk.custom.item.abstraction.instance.ItemInstanceReader;
 import me.lethunderhawk.custom.item.abstraction.migration.MigrationService;
 import me.lethunderhawk.custom.item.abstraction.runtime.AbilityDispatchService;
 import org.bukkit.entity.Player;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
-import org.bukkit.event.player.PlayerAttemptPickupItemEvent;
-import org.bukkit.event.player.PlayerInteractEvent;
-import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.*;
 import org.bukkit.inventory.ItemStack;
 
 /**
@@ -31,6 +33,9 @@ public class CustomItemListener implements Listener {
     public void onItemPickUp(PlayerAttemptPickupItemEvent event) {
         Player player = event.getPlayer();
         for (ItemStack stack : player.getInventory().getContents()) {
+            ItemInstance instance = ItemInstanceReader.read(stack);
+
+            if(instance == null) continue;
             dispatchService.dispatch(
                     player,
                     stack,
@@ -41,18 +46,33 @@ public class CustomItemListener implements Listener {
     }
     @EventHandler
     public void onMiddleClick(PlayerPickBlockEvent event) {
+
         //event.get
     }
     @EventHandler
     public void onAction(PlayerInteractEvent event) {
-        AbilityTrigger triggered = switch (event.getAction()) {
+        CustomItemTrigger triggered = switch (event.getAction()) {
             case LEFT_CLICK_AIR -> AbilityTrigger.LEFT_CLICK_AIR;
             case LEFT_CLICK_BLOCK -> AbilityTrigger.LEFT_CLICK_BLOCK;
             case RIGHT_CLICK_AIR -> AbilityTrigger.RIGHT_CLICK_AIR;
             case RIGHT_CLICK_BLOCK -> AbilityTrigger.RIGHT_CLICK_BLOCK;
-            default -> null;
+            case PHYSICAL -> AbilityTrigger.PHYSICAL;
         };
-
         dispatchService.dispatch(event.getPlayer(), event.getItem(), triggered, event);
     }
+
+    @EventHandler
+    public void onFish(PlayerFishEvent event) {
+        CustomItemTrigger triggered = switch (event.getState()){
+            case FISHING -> FishingRodTrigger.FISHING;
+            case CAUGHT_FISH -> FishingRodTrigger.CAUGHT_FISH;
+            case CAUGHT_ENTITY -> FishingRodTrigger.BOBBER_HOOKED;
+            case IN_GROUND, REEL_IN -> FishingRodTrigger.REEL_IN;
+            case FAILED_ATTEMPT -> FishingRodTrigger.FISHING_ROD_FAILED;
+            case BITE -> FishingRodTrigger.BITE;
+            case LURED -> FishingRodTrigger.LURED;
+        };
+        dispatchService.dispatch(event.getPlayer(), event.getPlayer().getActiveItem(), triggered, event);
+    }
+
 }

+ 38 - 1
src/main/java/me/lethunderhawk/economy/shop/BuyingItemsEditGUI.java

@@ -10,8 +10,10 @@ import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
 import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
 import me.lethunderhawk.main.BazaarFlux;
 import me.lethunderhawk.util.StringUtil;
+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;
@@ -31,7 +33,7 @@ public class BuyingItemsEditGUI extends PaginatedInventoryGUI<Map.Entry<Material
     @Override
     public void buildContent() {
         fillGlassPaneBackground();
-        List<Integer> slots = computeRectangleSlots(10, 23);
+        List<Integer> slots = computeRectangleSlots(10, 25);
 
         setupPagination(manager.getItemValues().getBuyingItems().entrySet(), slots);
 
@@ -58,6 +60,13 @@ public class BuyingItemsEditGUI extends PaginatedInventoryGUI<Map.Entry<Material
                 "l1: Material Name",
                 "l2: The Price"
         )).reopenIfFail(true).response((pl, str) -> {
+            if(str[0].equals("exit")){
+                return true;
+            }
+            if(str[0].equals("chat")){
+                startChat(pl);
+                return true;
+            }
             try{
                 manager.getItemValues().addBuyingItem(Material.valueOf(str[0]), Double.parseDouble(str[1]));
                 openWithListener(player);
@@ -69,6 +78,34 @@ public class BuyingItemsEditGUI extends PaginatedInventoryGUI<Map.Entry<Material
         }).open(player);
     }
 
+    private void startChat(Player player) {
+        ChatListener.askQuestion(player, Component.text("Choose a material (like hopper or grass_block)"), this::getMaterialAndAskForPrice);
+    }
+
+    private void getMaterialAndAskForPrice(Player player, Component component) {
+        String materialName = PlainTextComponentSerializer.plainText().serialize(component);
+        Material material;
+        try{
+            material = Material.valueOf(materialName);
+        }catch (Exception e){
+            player.sendMessage(Component.text("Invalid Material! Start again"));
+            startChat(player);
+            return;
+        }
+        ChatListener.askQuestion(player, Component.text("Choose a price (like 5.5 or 10.0)"), (pl, comp) -> {
+            try{
+                double price = Double.parseDouble(PlainTextComponentSerializer.plainText().serialize(comp));
+                manager.getItemValues().addBuyingItem(material, price);
+                openWithListener(player);
+                update();
+            }catch (Exception e){
+                player.sendMessage(Component.text("Invalid price! Start again"));
+                startChat(player);
+            }
+
+        });
+    }
+
     @Override
     public void update() {
         List<Integer> slots = computeRectangleSlots(10, 23);

+ 36 - 0
src/main/java/me/lethunderhawk/economy/shop/SellingItemsEditGUI.java

@@ -10,8 +10,10 @@ import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
 import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
 import me.lethunderhawk.main.BazaarFlux;
 import me.lethunderhawk.util.StringUtil;
+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;
@@ -59,6 +61,13 @@ public class SellingItemsEditGUI extends PaginatedInventoryGUI<Map.Entry<Materia
                 "l1: Material Name",
                 "l2: The Price"
         )).reopenIfFail(true).response((pl, str) -> {
+            if(str[0].equals("exit")){
+                return true;
+            }
+            if(str[0].equals("chat")){
+                startChat(pl);
+                return true;
+            }
             try{
                 manager.getItemValues().addSellingItem(Material.valueOf(str[0]), Double.parseDouble(str[1]));
                 openWithListener(player);
@@ -69,6 +78,33 @@ public class SellingItemsEditGUI extends PaginatedInventoryGUI<Map.Entry<Materia
             return true;
         }).open(player);
     }
+    private void startChat(Player player) {
+        ChatListener.askQuestion(player, Component.text("Choose a material (like hopper or grass_block)"), this::getMaterialAndAskForPrice);
+    }
+
+    private void getMaterialAndAskForPrice(Player player, Component component) {
+        String materialName = PlainTextComponentSerializer.plainText().serialize(component);
+        Material material;
+        try{
+            material = Material.valueOf(materialName);
+        }catch (Exception e){
+            player.sendMessage(Component.text("Invalid Material! Start again"));
+            startChat(player);
+            return;
+        }
+        ChatListener.askQuestion(player, Component.text("Choose a price (like 5.5 or 10.0)"), (pl, comp) -> {
+            try{
+                double price = Double.parseDouble(PlainTextComponentSerializer.plainText().serialize(comp));
+                manager.getItemValues().addSellingItem(material, price);
+                openWithListener(player);
+                update();
+            }catch (Exception e){
+                player.sendMessage(Component.text("Invalid price! Start again"));
+                startChat(player);
+            }
+
+        });
+    }
 
     @Override
     public void update() {

+ 7 - 1
src/main/java/me/lethunderhawk/economy/shop/ShopGUI.java

@@ -3,6 +3,7 @@ package me.lethunderhawk.economy.shop;
 import me.lethunderhawk.economy.api.EconomyAPI;
 import me.lethunderhawk.economy.currency.EconomyManager;
 import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
 import me.lethunderhawk.fluxapi.util.gui.PaginatedInventoryGUI;
 import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
 import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
@@ -18,7 +19,7 @@ import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
-public class ShopGUI extends PaginatedInventoryGUI<Map.Entry<Material, Double>> {
+public class ShopGUI extends PaginatedInventoryGUI<Map.Entry<Material, Double>> implements InventoryGUI.AutoCloseHandler {
     private final Player player;
     private final Map<Material, Double> prices;
     private final EconomyManager manager;
@@ -109,4 +110,9 @@ public class ShopGUI extends PaginatedInventoryGUI<Map.Entry<Material, Double>>
                 ))
                 .buildItemStack();
     }
+
+    @Override
+    public void onClosedByPlayer(Player player) {
+
+    }
 }

+ 5 - 1
src/main/java/me/lethunderhawk/main/BazaarFlux.java

@@ -1,6 +1,7 @@
 package me.lethunderhawk.main;
 
 import me.lethunderhawk.clans.ClanModule;
+import me.lethunderhawk.custom.entity.CustomEntityModule;
 import me.lethunderhawk.custom.item.CustomItemModule;
 import me.lethunderhawk.custom.recipes.CustomRecipeModule;
 import me.lethunderhawk.economy.EconomyModule;
@@ -9,6 +10,7 @@ import me.lethunderhawk.fluxapi.util.gui.InventoryManager;
 import me.lethunderhawk.fluxapi.util.interfaces.FluxAPIModule;
 import me.lethunderhawk.profile.ProfileModule;
 import me.lethunderhawk.tradeplugin.TradeModule;
+import me.lethunderhawk.util.listeners.ChatListener;
 import org.bukkit.plugin.java.JavaPlugin;
 
 import java.util.ArrayList;
@@ -28,12 +30,14 @@ public class BazaarFlux extends JavaPlugin{
                 new ClanModule(this),
                 new EconomyModule(this),
                 new CustomRecipeModule(this),
+                new CustomEntityModule(this),
+                new ProfileModule(this)
                 //new MinionModule(this),
                 //new DungeonModule(this),
                 //new WorldModule(this),
-                new ProfileModule(this)
                 //new NPCRegistrator(this)
         );
+        getServer().getPluginManager().registerEvents(new ChatListener(), this);
     }
     private void registerAllModules(FluxAPIModule... modules){
         for(FluxAPIModule module : modules){

+ 29 - 0
src/main/java/me/lethunderhawk/util/listeners/ChatListener.java

@@ -0,0 +1,29 @@
+package me.lethunderhawk.util.listeners;
+
+import io.papermc.paper.event.player.AsyncChatEvent;
+import net.kyori.adventure.text.Component;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+
+import java.util.HashMap;
+import java.util.UUID;
+import java.util.function.BiConsumer;
+
+public class ChatListener implements Listener {
+    private static final HashMap<UUID, BiConsumer<Player, Component>> listeners = new HashMap<>();
+
+    @EventHandler
+    public void onChat(AsyncChatEvent event) {
+        if(listeners.containsKey(event.getPlayer().getUniqueId())) {
+            listeners.remove(event.getPlayer().getUniqueId()).accept(event.getPlayer(), event.message());
+            event.setCancelled(true);
+        }
+    }
+
+
+    public static void askQuestion(Player player, Component question, BiConsumer<Player, Component> callback) {
+        listeners.put(player.getUniqueId(), callback);
+        player.sendMessage(question);
+    }
+}

+ 58 - 0
src/main/resources/custom/enchantment/messages.yml

@@ -0,0 +1,58 @@
+en:
+  excavator:
+    name: "§bExcavator"
+    description: |
+      §7Breaks an area of blocks equal to the original block.
+      §7Level 1: 3x3, Level 2: 5x5, Level 3: 7x7.
+  lumberjack:
+    name: "§bLumberjack"
+    description: |
+      §7Instantly chops down entire trees.
+      §7Greater reach per level.
+  veinminer:
+    name: "§bVeinMiner"
+    description: |
+      §7Mines entire ore veins.
+      §7More blocks per level.
+  autosmelt:
+    name: "§bAutoSmelt"
+    description: |
+      §7Blocks are automatically smelted when mined.
+      §7More drops per level.
+  replenish:
+    name: "§bReplenish"
+    description: |
+      §7Automatically replants crops.
+      §7Better effects per level.
+  bleeding:
+    name: "§bBleeding"
+    description: |
+      §7Inflicts bleeding (poison) on hit.
+      §7More duration and power per level.
+  combo:
+    name: "§bCombo"
+    description: |
+      §7Consecutive hits deal more damage.
+      §7More damage per level.
+  decapitate:
+    name: "§bDecapitate"
+    description: |
+      §7Chance to obtain the enemy's head on kill.
+  disarm:
+    name: "§bDisarm"
+    description: |
+      §7Chance to disarm the enemy.
+      §7Higher chance per level.
+  vampirism:
+    name: "§bVampirism"
+    description: |
+      §7Steal life on hit.
+      §7More healing per level.
+  book:
+    format: "{name} {level}"
+    lore: "{description}"
+  not_found: "Enchantment not found."
+  given: "Enchantment book given: {name} {level}"
+  no_permission: "You do not have permission to use this command."
+  only_players: "This command can only be used by players."
+  usage: "Usage: /givecustombook <enchant> [level]"