ソースを参照

NPC config files are properly loaded and stored.

Jan 4 週間 前
コミット
e35b8d5c9a
28 ファイル変更900 行追加580 行削除
  1. 14 5
      src/main/java/me/lethunderhawk/fluxapi/main/FluxAPI.java
  2. 66 0
      src/main/java/me/lethunderhawk/fluxapi/npc/NPCModule.java
  3. 14 1
      src/main/java/me/lethunderhawk/fluxapi/npc/abstraction/DummyNPC.java
  4. 64 39
      src/main/java/me/lethunderhawk/fluxapi/npc/abstraction/NPC.java
  5. 179 0
      src/main/java/me/lethunderhawk/fluxapi/npc/abstraction/NPCOptions.java
  6. 20 10
      src/main/java/me/lethunderhawk/fluxapi/npc/command/NPCCommand.java
  7. 5 6
      src/main/java/me/lethunderhawk/fluxapi/npc/gui/NPCOptionsGUI.java
  8. 108 0
      src/main/java/me/lethunderhawk/fluxapi/npc/listener/NPCListener.java
  9. 127 0
      src/main/java/me/lethunderhawk/fluxapi/npc/manager/NPCManager.java
  10. 1 1
      src/main/java/me/lethunderhawk/fluxapi/npc/util/MojangAPI.java
  11. 18 10
      src/main/java/me/lethunderhawk/fluxapi/profile/ProfileManager.java
  12. 4 8
      src/main/java/me/lethunderhawk/fluxapi/profile/ProfileModule.java
  13. 175 0
      src/main/java/me/lethunderhawk/fluxapi/profile/config/ConfigLoader.java
  14. 1 1
      src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/ConfigFactory.java
  15. 1 1
      src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/PersistentConfig.java
  16. 3 2
      src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/subconfig/ProfileSubConfig.java
  17. 1 1
      src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/subconfig/ProfileSubConfigFactory.java
  18. 3 3
      src/main/java/me/lethunderhawk/fluxapi/profile/config/registry/ConfigService.java
  19. 3 3
      src/main/java/me/lethunderhawk/fluxapi/profile/config/registry/ProfileModuleRegistry.java
  20. 3 3
      src/main/java/me/lethunderhawk/fluxapi/profile/listener/JoinListener.java
  21. 88 0
      src/main/java/me/lethunderhawk/fluxapi/profile/representation/FluxProfile.java
  22. 1 1
      src/main/java/me/lethunderhawk/fluxapi/util/animation/Animation.java
  23. 0 40
      src/main/java/me/lethunderhawk/npc/NPCModule.java
  24. 0 118
      src/main/java/me/lethunderhawk/npc/abstraction/NPCOptions.java
  25. 0 148
      src/main/java/me/lethunderhawk/npc/manager/NPCManager.java
  26. 0 87
      src/main/java/me/lethunderhawk/profile/config/ConfigLoader.java
  27. 0 91
      src/main/java/me/lethunderhawk/profile/representation/FluxProfile.java
  28. 1 1
      src/main/resources/plugin.yml

+ 14 - 5
src/main/java/me/lethunderhawk/main/FluxAPI.java → src/main/java/me/lethunderhawk/fluxapi/main/FluxAPI.java

@@ -1,8 +1,9 @@
-package me.lethunderhawk.main;
+package me.lethunderhawk.fluxapi.main;
 
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.npc.NPCModule;
-import me.lethunderhawk.profile.ProfileModule;
+import me.lethunderhawk.fluxapi.npc.NPCModule;
+import me.lethunderhawk.fluxapi.profile.ProfileModule;
+import me.lethunderhawk.fluxapi.profile.config.ConfigLoader;
 import org.bukkit.plugin.java.JavaPlugin;
 
 public class FluxAPI extends JavaPlugin {
@@ -11,11 +12,19 @@ public class FluxAPI extends JavaPlugin {
     public void onEnable() {
         FluxService.register(FluxAPI.class, this);
 
+        ConfigLoader configLoader = new ConfigLoader(this);
+        FluxService.register(ConfigLoader.class, configLoader);
+
+        ProfileModule profile = new ProfileModule(this);
+        profile.onEnable();
+
         NPCModule module = new NPCModule(this);
         module.onEnable();
         FluxService.registerModule(NPCModule.class, module);
+    }
 
-        ProfileModule profile = new ProfileModule(this);
-        profile.onEnable();
+    @Override
+    public void onDisable() {
+        FluxService.get(NPCModule.class).onDisable();
     }
 }

+ 66 - 0
src/main/java/me/lethunderhawk/fluxapi/npc/NPCModule.java

@@ -0,0 +1,66 @@
+package me.lethunderhawk.fluxapi.npc;
+
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.npc.abstraction.DummyNPC;
+import me.lethunderhawk.fluxapi.npc.abstraction.NPCOptions;
+import me.lethunderhawk.fluxapi.npc.command.NPCCommand;
+import me.lethunderhawk.fluxapi.npc.listener.NPCListener;
+import me.lethunderhawk.fluxapi.npc.manager.NPCManager;
+import me.lethunderhawk.fluxapi.profile.config.ConfigLoader;
+import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
+import org.bukkit.event.HandlerList;
+import org.bukkit.plugin.java.JavaPlugin;
+
+public class NPCModule extends BazaarFluxModule {
+    private final String saveLocation = "/npc/npcs.yml";
+    private NPCManager npcManager;
+    private NPCListener listener;
+
+    public NPCModule(JavaPlugin plugin) {
+        super(plugin);
+    }
+
+    public String getPrefix() {
+        return "[NPC]";
+    }
+
+    @Override
+    public void onEnable() {
+        ConfigurationSerialization.registerClass(NPCOptions.class);
+        ConfigurationSerialization.registerClass(NPCManager.class);
+        ConfigurationSerialization.registerClass(DummyNPC.class);
+
+        this.npcManager = FluxService.get(ConfigLoader.class).loadObject(
+                saveLocation,
+                "manager",
+                NPCManager.class
+        );
+
+
+
+        FluxService.register(NPCManager.class, npcManager);
+        npcManager.initializeSpawning();
+
+        this.listener = new NPCListener();
+        plugin.getServer().getPluginManager().registerEvents(listener, plugin);
+
+        NPCCommand npcCommand = new NPCCommand(this);
+        plugin.getCommand("npc").setExecutor(npcCommand);
+        plugin.getCommand("npc").setTabCompleter(npcCommand);
+
+
+        plugin.getLogger().info("NPC Extension Enabled");
+    }
+
+    @Override
+    public void onDisable() {
+        npcManager.saveNPCsToFile(saveLocation);
+        npcManager.deleteAllNpcEntities();
+
+        HandlerList.unregisterAll(listener);
+        FluxService.unregister(NPCManager.class);
+        listener = null;
+        npcManager = null;
+    }
+}

+ 14 - 1
src/main/java/me/lethunderhawk/npc/abstraction/DummyNPC.java → src/main/java/me/lethunderhawk/fluxapi/npc/abstraction/DummyNPC.java

@@ -1,7 +1,10 @@
-package me.lethunderhawk.npc.abstraction;
+package me.lethunderhawk.fluxapi.npc.abstraction;
 
 import net.kyori.adventure.text.Component;
 import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Map;
 
 public class DummyNPC extends NPC{
 
@@ -18,4 +21,14 @@ public class DummyNPC extends NPC{
     public void onRightClick(Player player) {
         player.sendMessage(Component.text("<" + getOptions().getName() + "> You right clicked me!"));
     }
+
+    @Override
+    public @NotNull Map<String, Object> serialize() {
+        return Map.of(
+                "options", getOptions()
+        );
+    }
+    public static DummyNPC deserialize(Map<String, Object> map) {
+        return new DummyNPC((NPCOptions) map.get("options"));
+    }
 }

+ 64 - 39
src/main/java/me/lethunderhawk/npc/abstraction/NPC.java → src/main/java/me/lethunderhawk/fluxapi/npc/abstraction/NPC.java

@@ -1,39 +1,20 @@
-package me.lethunderhawk.npc.abstraction;
+package me.lethunderhawk.fluxapi.npc.abstraction;
 
 import com.destroystokyo.paper.profile.ProfileProperty;
 import io.papermc.paper.datacomponent.item.ResolvableProfile;
-import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.npc.manager.NPCManager;
 import net.kyori.adventure.text.Component;
+import org.bukkit.Bukkit;
 import org.bukkit.Location;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
 import org.bukkit.entity.*;
 
 import java.util.UUID;
 
-public abstract class NPC{
+public abstract class NPC implements ConfigurationSerializable {
     private final NPCOptions npcOptions;
-    private final Entity entity;
 
     public NPC(NPCOptions npcOptions) {
-        this.entity = npcOptions.getLocation().getWorld().spawnEntity(npcOptions.getLocation(), npcOptions.getEntityType());
         this.npcOptions = npcOptions;
-        if(npcOptions.getEntityType() == EntityType.MANNEQUIN){
-            Mannequin mannequin = (Mannequin) entity;
-
-            ProfileProperty skin = new ProfileProperty("textures", npcOptions.getTexture(), npcOptions.getTextureSignature());
-            ResolvableProfile profile = ResolvableProfile.resolvableProfile().addProperty(skin).build();
-
-            mannequin.setProfile(profile);
-            mannequin.setDescription(npcOptions.getDescription());
-        }
-        entity.customName(Component.text(npcOptions.getName()));
-        entity.setCustomNameVisible(npcOptions.isNameVisible());
-        entity.setPersistent(true);
-        entity.setGravity(false);
-        entity.setNoPhysics(true);
-        if(entity instanceof LivingEntity livingEntity){
-            livingEntity.setAI(false);
-        }
     }
 
     public abstract void onLeftClick(Player player);
@@ -48,20 +29,20 @@ public abstract class NPC{
         onRightClick(player);
     }
 
-    public Entity getEntity() {
-        return entity;
-    }
-
     public String getName() {
         return npcOptions.getName();
     }
 
-    public void delete() {
-        entity.remove();
+    public void deleteEntity() {
+        Entity entity = npcOptions.getEntity();
+        if(entity != null) {
+            entity.remove();
+            npcOptions.setEntityUUID(null);
+        }
     }
 
-    public UUID getUUID() {
-        return entity.getUniqueId();
+    public UUID getNpcUUID() {
+        return npcOptions.getNpcUUID();
     }
 
     public NPCOptions getOptions() {
@@ -70,20 +51,29 @@ public abstract class NPC{
 
     public void rename(String newName) {
         npcOptions.setName(newName);
-        entity.customName(Component.text(newName));
-    }
-    public void register(){
-        FluxService.get(NPCManager.class).registerNPC(this);
+        npcOptions.getEntity().customName(Component.text(newName));
     }
 
     public void lookAt(Location target) {
-        if (target == null || this.entity == null || !(entity instanceof LivingEntity livingEntity)) return;
+        Entity entity = npcOptions.getEntity();
+        entity = Bukkit.getEntity(entity.getUniqueId());
+        if (target == null || entity == null) {
+            Bukkit.getLogger().info("HEY I SHOULD LOOK!!");
+            return;
+        }
 
-        Location npcLoc = this.entity.getLocation();
+        Location npcLoc = entity.getLocation();
 
         // Use eye height for more natural look
+
         double dx = target.getX() - npcLoc.getX();
-        double dy = target.getY() - (npcLoc.getY() + livingEntity.getEyeHeight());
+        double dy;
+        if(entity instanceof LivingEntity livingEntity) {
+             dy = target.getY() - (npcLoc.getY() + livingEntity.getEyeHeight());
+        }else{
+            dy = target.getY() - npcLoc.getY();
+        }
+
         double dz = target.getZ() - npcLoc.getZ();
 
         // Prevent division by zero
@@ -97,7 +87,7 @@ public abstract class NPC{
         // Normalize yaw to Minecraft format
         yaw = normalizeYaw(yaw);
 
-        this.entity.setRotation(yaw, pitch);
+        entity.setRotation(yaw, pitch);
     }
     private float normalizeYaw(float yaw) {
         yaw %= 360.0F;
@@ -105,4 +95,39 @@ public abstract class NPC{
         if (yaw < -180.0F) yaw += 360.0F;
         return yaw;
     }
+
+    public Entity spawn(){
+        Entity entity = npcOptions.getLocation().getWorld().spawnEntity(npcOptions.getLocation(), npcOptions.getEntityType());
+
+        if(npcOptions.getEntityType() == EntityType.MANNEQUIN){
+            Mannequin mannequin = (Mannequin) entity;
+
+            ProfileProperty skin = new ProfileProperty("textures", npcOptions.getTexture(), npcOptions.getTextureSignature());
+            ResolvableProfile profile = ResolvableProfile.resolvableProfile().addProperty(skin).build();
+
+            mannequin.setProfile(profile);
+            if(npcOptions.getDescription() != null && !npcOptions.getDescription().isEmpty()){
+                mannequin.setDescription(Component.text(npcOptions.getDescription()));
+            }else{
+                mannequin.setDescription(null);
+            }
+            mannequin.setImmovable(true);
+        }
+
+        entity.customName(Component.text(npcOptions.getName()));
+        entity.setCustomNameVisible(npcOptions.isNameVisible());
+        entity.setGravity(false);
+
+        if(entity instanceof LivingEntity livingEntity){
+            livingEntity.setAI(false);
+        }
+        npcOptions.setEntityUUID(entity.getUniqueId());
+        Bukkit.getLogger().info("Spawned entity UUID: " + entity.getUniqueId());
+        return entity;
+    }
+
+    public UUID getEntityUUID() {
+        return npcOptions.getEntityUUID();
+    }
+
 }

+ 179 - 0
src/main/java/me/lethunderhawk/fluxapi/npc/abstraction/NPCOptions.java

@@ -0,0 +1,179 @@
+package me.lethunderhawk.fluxapi.npc.abstraction;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.jetbrains.annotations.NotNull;
+import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class NPCOptions implements ConfigurationSerializable {
+    private String name;
+    private String texture;
+    private String signature;
+    private Location location;
+    private boolean visibleName = true;
+    private boolean listed;
+    private EntityType entityType = EntityType.MANNEQUIN;
+    private String description = "";
+    private boolean lookingAtNearest = false;
+    private UUID npcUUID, entityUUID;
+
+    public NPCOptions(UUID uuid){
+        this.npcUUID = uuid;
+    }
+    // ------ Getters
+    public String getName() {
+        return name;
+    }
+
+    public String getTexture() {
+        return texture;
+    }
+
+    public String getTextureSignature() {
+        return signature;
+    }
+
+    public Location getLocation() {
+        return location;
+    }
+
+    public boolean isNameVisible() {
+        return visibleName;
+    }
+
+    public EntityType getEntityType(){
+        return entityType;
+    }
+
+    public boolean isListed() {
+        return listed;
+    }
+
+    public UUID getNpcUUID() {
+        return npcUUID;
+    }
+
+    public @Nullable String getDescription() {
+        return description;
+    }
+
+    // ------ Setters
+    public NPCOptions setName(@NotNull String name) {
+        this.name = name;
+        return this;
+    }
+
+    public NPCOptions setEntityType(@NotNull EntityType entityType) {
+        this.entityType = entityType;
+        return this;
+    }
+
+    public NPCOptions setTexture(String texture) {
+        this.texture = texture;
+        return this;
+    }
+
+    public NPCOptions setSignature(String signature) {
+        this.signature = signature;
+        return this;
+    }
+
+    public NPCOptions setLocation(Location location) {
+        this.location = location;
+        return this;
+    }
+
+    public NPCOptions setVisibleName(boolean visibleName) {
+        this.visibleName = visibleName;
+        return this;
+    }
+
+    public NPCOptions setListed(boolean listed) {
+        this.listed = listed;
+        return this;
+    }
+
+    public NPCOptions setDescription(@Nullable String description) {
+        this.description = description;
+        return this;
+    }
+    public NPCOptions setNPCUUID(@NotNull UUID npcUUID) {
+        this.npcUUID = npcUUID;
+        return this;
+    }
+
+    public boolean isLookingAtNearest() {
+        return lookingAtNearest;
+    }
+
+    public NPCOptions setLookingAtNearest(boolean lookingAtNearest) {
+        this.lookingAtNearest = lookingAtNearest;
+        return this;
+    }
+
+
+
+    public Entity getEntity() {
+        return Bukkit.getEntity(entityUUID);
+    }
+
+    public void setEntityUUID(UUID entityUUID) {
+        this.entityUUID = entityUUID;
+    }
+
+    public @Nullable UUID getEntityUUID() {
+        return entityUUID;
+    }
+
+    @Override
+    public @NonNull Map<String, Object> serialize() {
+        Map<String, Object> map = new HashMap<>();
+
+        map.put("uuid", npcUUID.toString());
+        map.put("name", name);
+        map.put("texture", texture);
+        map.put("signature", signature);
+        map.put("location", location);
+        map.put("visibleName", visibleName);
+        map.put("listed", listed);
+        map.put("description", description);
+        map.put("lookingAtNearest", lookingAtNearest);
+        map.put("entityType", entityType.name());
+
+        return map;
+    }
+
+    public static NPCOptions deserialize(Map<String, Object> map) {
+
+        UUID uuid = UUID.fromString((String) map.get("uuid"));
+
+        NPCOptions options = new NPCOptions(uuid);
+
+        options.setName((String) map.getOrDefault("name", ""));
+
+        options.setTexture((String) map.get("texture"));
+        options.setSignature((String) map.get("signature"));
+
+        options.setLocation((Location) map.get("location"));
+
+        options.setVisibleName((Boolean) map.getOrDefault("visibleName", false));
+        options.setListed((Boolean) map.getOrDefault("listed", false));
+        options.setDescription((String) map.get("description"));
+        options.setLookingAtNearest((Boolean) map.getOrDefault("lookingAtNearest", false));
+
+        String entityTypeStr = (String) map.get("entityType");
+        if (entityTypeStr != null) {
+            options.setEntityType(EntityType.valueOf(entityTypeStr));
+        }
+
+        return options;
+    }
+}

+ 20 - 10
src/main/java/me/lethunderhawk/npc/command/NPCCommand.java → src/main/java/me/lethunderhawk/fluxapi/npc/command/NPCCommand.java

@@ -1,16 +1,16 @@
-package me.lethunderhawk.npc.command;
+package me.lethunderhawk.fluxapi.npc.command;
 
 import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.npc.abstraction.DummyNPC;
+import me.lethunderhawk.fluxapi.npc.abstraction.NPC;
+import me.lethunderhawk.fluxapi.npc.abstraction.NPCOptions;
+import me.lethunderhawk.fluxapi.npc.gui.NPCOptionsGUI;
+import me.lethunderhawk.fluxapi.npc.manager.NPCManager;
+import me.lethunderhawk.fluxapi.npc.util.MojangAPI;
 import me.lethunderhawk.fluxapi.util.command.CommandNode;
 import me.lethunderhawk.fluxapi.util.command.CustomCommand;
 import me.lethunderhawk.fluxapi.util.gui.InventoryManager;
 import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
-import me.lethunderhawk.npc.abstraction.DummyNPC;
-import me.lethunderhawk.npc.abstraction.NPCOptions;
-import me.lethunderhawk.npc.util.MojangAPI;
-import me.lethunderhawk.npc.manager.NPCManager;
-import me.lethunderhawk.npc.abstraction.NPC;
-import me.lethunderhawk.npc.gui.NPCOptionsGUI;
 import org.bukkit.command.CommandSender;
 import org.bukkit.entity.Player;
 
@@ -19,8 +19,11 @@ import java.util.UUID;
 import java.util.stream.Collectors;
 
 public class NPCCommand extends CustomCommand {
+    private final NPCManager manager;
+
     public NPCCommand(BazaarFluxModule module) {
         super(new CommandNode("npc", "Base npc command", null), module);
+        this.manager = FluxService.get(NPCManager.class);
     }
 
     @Override
@@ -31,6 +34,12 @@ public class NPCCommand extends CustomCommand {
         delete.setTabCompleter(this::npcDeletionCompleter);
         CommandNode options = registerCommand("options", "Test options command", this::showOptionsMenu);
         options.setTabCompleter(this::npcOptionsCompleter);
+        registerCommand("reload", "Reload this module", this::reload);
+    }
+
+    private void reload(CommandSender sender, String[] strings) {
+        if(!sender.hasPermission("npc.reload")) return;
+        module.reload(sender, strings);
     }
 
     private List<String> npcDeletionCompleter(CommandSender sender, String[] args) {
@@ -70,14 +79,14 @@ public class NPCCommand extends CustomCommand {
     private void createNPC(CommandSender sender, String[] strings) {
 
         if(!(sender instanceof Player p)) return;
-
+        if(strings.length < 2) return;
         String name = strings[0];
         String textureName = strings[1];
         String[] stringdata = new String[]{"", ""};
         if(textureName != null){
             stringdata = MojangAPI.getSkinDataFromName(textureName);
         }
-        NPCOptions options = new NPCOptions()
+        NPCOptions options = new NPCOptions(UUID.randomUUID())
                 .setName(name)
                 .setLocation(p.getLocation())
                 .setTexture(stringdata[0])
@@ -85,7 +94,8 @@ public class NPCCommand extends CustomCommand {
                 .setLookingAtNearest(true);
 
         NPC npc = new DummyNPC(options);
-        npc.register();
+        manager.registerNPC(npc);
+        manager.spawnNpc(npc);
     }
 
     private void deleteNPC(CommandSender sender, String[] strings) {

+ 5 - 6
src/main/java/me/lethunderhawk/npc/gui/NPCOptionsGUI.java → src/main/java/me/lethunderhawk/fluxapi/npc/gui/NPCOptionsGUI.java

@@ -1,13 +1,13 @@
-package me.lethunderhawk.npc.gui;
+package me.lethunderhawk.fluxapi.npc.gui;
 
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.gui.InventoryGUI;
 import me.lethunderhawk.fluxapi.util.gui.input.SignMenuFactory;
 import me.lethunderhawk.fluxapi.util.itemdesign.ItemOptions;
 import me.lethunderhawk.fluxapi.util.itemdesign.LoreDesigner;
-import me.lethunderhawk.main.FluxAPI;
-import me.lethunderhawk.npc.abstraction.NPC;
-import me.lethunderhawk.npc.manager.NPCManager;
+import me.lethunderhawk.fluxapi.main.FluxAPI;
+import me.lethunderhawk.fluxapi.npc.abstraction.NPC;
+import me.lethunderhawk.fluxapi.npc.manager.NPCManager;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.format.NamedTextColor;
 import org.bukkit.Material;
@@ -49,14 +49,13 @@ public class NPCOptionsGUI extends InventoryGUI {
                 String newName = input[0];
                 if(newName.isEmpty()) return false;
                 pl.sendMessage("Renamed " + npc.getName() + " to " + newName);
-                FluxService.get(NPCManager.class).renameNPC(npc.getUUID(), newName);
+                FluxService.get(NPCManager.class).renameNPCbyId(npc.getOptions().getEntity().getUniqueId(), newName);
                 return true;
             });
             menu.open(player);
         }
     }
 
-
     @Override
     public void update() {
 

+ 108 - 0
src/main/java/me/lethunderhawk/fluxapi/npc/listener/NPCListener.java

@@ -0,0 +1,108 @@
+package me.lethunderhawk.fluxapi.npc.listener;
+
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.npc.abstraction.NPC;
+import me.lethunderhawk.fluxapi.npc.manager.NPCManager;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+import org.bukkit.event.player.PlayerInteractAtEntityEvent;
+import org.bukkit.event.player.PlayerMoveEvent;
+import org.bukkit.inventory.EquipmentSlot;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+public final class NPCListener implements Listener {
+    private final int FOCUS_RANGE_SQUARED = 250;
+
+    public NPCListener() {
+    }
+
+    @EventHandler
+    public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent e) {
+        if(!(e.getDamager() instanceof Player player)) return;
+
+        NPC npc = getNPC(e.getEntity());
+        if(npc == null) return;
+
+        e.setCancelled(true);
+        if(player.isSneaking()){
+            npc.onShiftLeftClick(player);
+        }else{
+            npc.onLeftClick(player);
+        }
+    }
+
+    @EventHandler
+    public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent e) {
+        Player player = e.getPlayer();
+
+        NPC npc = getNPC(e.getRightClicked());
+        if(npc == null) return;
+
+        if(e.getHand() != EquipmentSlot.HAND) return;
+        if(player.isSneaking()){
+            npc.onShiftRightClick(player);
+        }else{
+            npc.onRightClick(player);
+        }
+        e.setCancelled(true);
+    }
+
+    @EventHandler
+    public void onPlayerMove(PlayerMoveEvent event) {
+
+        // Ignore pure rotation (huge performance gain)
+        if (event.getFrom().getX() == event.getTo().getX()
+                && event.getFrom().getY() == event.getTo().getY()
+                && event.getFrom().getZ() == event.getTo().getZ()) {
+            return;
+        }
+
+        Player player = event.getPlayer();
+        Location playerLocation = player.getEyeLocation();
+        for (NPC npc : getRegisteredNPCs()) {
+            if(!npc.getOptions().isLookingAtNearest()) continue;
+            if(npc.getOptions().getEntity() == null){
+                Bukkit.getLogger().info("Entity is null for NPC UUID: " +  npc.getNpcUUID());
+                continue;
+            }
+            Location npcLocation = npc.getOptions().getEntity().getLocation();
+
+            // Skip different worlds immediately
+            if (!npcLocation.getWorld().equals(playerLocation.getWorld())) {
+
+                continue;
+            }
+
+            // Use squared distance (NO sqrt → much faster)
+            if (npcLocation.distanceSquared(playerLocation) > FOCUS_RANGE_SQUARED) {
+                continue;
+            }
+
+            // Optional: Visibility check if really needed
+            if (!player.canSee(npc.getOptions().getEntity())) {
+                continue;
+            }
+
+            npc.lookAt(playerLocation);
+
+        }
+    }
+    private NPC getNPC(@NotNull Entity entity) {
+        for(NPC npc : FluxService.get(NPCManager.class).getRegisteredNPCs()){
+            if(npc.getEntityUUID().equals(entity.getUniqueId())){
+                return npc;
+            }
+        }
+        return null;
+    }
+    private Collection<NPC> getRegisteredNPCs(){
+        return FluxService.get(NPCManager.class).getRegisteredNPCs();
+    }
+}

+ 127 - 0
src/main/java/me/lethunderhawk/fluxapi/npc/manager/NPCManager.java

@@ -0,0 +1,127 @@
+package me.lethunderhawk.fluxapi.npc.manager;
+
+import me.lethunderhawk.fluxapi.FluxService;
+import me.lethunderhawk.fluxapi.npc.abstraction.NPC;
+import me.lethunderhawk.fluxapi.profile.config.ConfigLoader;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class NPCManager implements ConfigurationSerializable {
+    private final HashMap<UUID, NPC> npcsById = new HashMap<>(); // NPCs UUID to NPC representation
+    public NPCManager() {
+    }
+
+
+    public void registerNPC(NPC toRegister){
+        npcsById.put(toRegister.getNpcUUID(), toRegister);
+    }
+
+    public NPC findByUUID(String npcId){
+        UUID manUUID = UUID.fromString(npcId);
+        return npcsById.get(manUUID);
+    }
+
+    public Collection<UUID> getRegisteredNPCsUUID() {
+        return npcsById.keySet();
+    }
+
+    public void deleteNPCByUUIDString(String npcId) {
+        UUID uuid = UUID.fromString(npcId);
+        NPC npc = npcsById.remove(uuid);
+        if(npc == null) return;
+        Entity toRemove = npc.getOptions().getEntity();
+        if(toRemove == null) return;
+        npc.deleteEntity();
+
+    }
+
+    public void renameNPCbyId(UUID npcId, String newName) {
+        npcsById.get(npcId).rename(newName);
+    }
+
+    public void serialize(YamlConfiguration cfg) {
+        Bukkit.getLogger().info("Saving NPCManager. NPC count: " + npcsById.size());
+
+        ConfigurationSection topSection = cfg.createSection("npcs");
+
+        for (NPC npc : npcsById.values()) {
+            Bukkit.getLogger().info("Saving NPC: " + npc.getNpcUUID());
+            topSection.set(npc.getNpcUUID().toString(), npc.getOptions());
+        }
+    }
+
+    public String getFileName() {
+        return "/npc/npcs";
+    }
+
+    public void deleteAllNpcEntities() {
+        Map<UUID, NPC> toRemove = new HashMap<>(npcsById);
+        for(UUID uuid : toRemove.keySet()) {
+            NPC npc = npcsById.remove(uuid);
+            npc.deleteEntity();
+        }
+    }
+
+    public void initializeSpawning() {
+        Bukkit.getLogger().info("Initializing spawning. NPC count: " + npcsById.size());
+        for (NPC npc : npcsById.values()) {
+            spawnNpc(npc);
+        }
+    }
+
+    public void spawnNpc(NPC npc) {
+        Bukkit.getLogger().info("Spawning NPC " + npc.getNpcUUID());
+        npc.spawn();
+    }
+
+    public Collection<NPC> getRegisteredNPCs() {
+        return npcsById.values();
+    }
+
+    public void saveNPCsToFile(String saveLocation) {
+        FluxService.get(ConfigLoader.class).saveObject(
+                saveLocation,
+                "manager",
+                this
+        );
+    }
+
+    public void loadNPCsFromFile(String saveLocation) {
+
+    }
+
+    @Override public @NotNull Map<String, Object> serialize() {
+        Map<String, Object> map = new HashMap<>();
+        Map<String, NPC> npcMap = new HashMap<>();
+        for(Map.Entry<UUID, NPC> entry : npcsById.entrySet()) {
+            npcMap.put(entry.getKey().toString(), entry.getValue());
+        }
+        map.put("npcs", npcMap);
+        Bukkit.getLogger().info("Saved NPCs. Map size: " + npcMap.size());
+        return map;
+    }
+
+    public static NPCManager deserialize(Map<String, Object> args) {
+        NPCManager manager = new NPCManager();
+
+        Object raw = args.get("npcs");
+        if (raw instanceof Map<?, ?> map) {
+            for (Map.Entry<?, ?> entry : map.entrySet()) {
+                if (entry.getValue() instanceof NPC npc) {
+                    manager.registerNPC(npc);
+                }
+            }
+        }
+
+        return manager;
+    }
+}

+ 1 - 1
src/main/java/me/lethunderhawk/npc/util/MojangAPI.java → src/main/java/me/lethunderhawk/fluxapi/npc/util/MojangAPI.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.npc.util;
+package me.lethunderhawk.fluxapi.npc.util;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;

+ 18 - 10
src/main/java/me/lethunderhawk/profile/ProfileManager.java → src/main/java/me/lethunderhawk/fluxapi/profile/ProfileManager.java

@@ -1,7 +1,7 @@
-package me.lethunderhawk.profile;
+package me.lethunderhawk.fluxapi.profile;
 
-import me.lethunderhawk.profile.config.ConfigLoader;
-import me.lethunderhawk.profile.representation.FluxProfile;
+import me.lethunderhawk.fluxapi.profile.config.ConfigLoader;
+import me.lethunderhawk.fluxapi.profile.representation.FluxProfile;
 import org.bukkit.entity.Player;
 
 import java.util.HashMap;
@@ -26,11 +26,10 @@ public class ProfileManager {
             return profiles.get(uuid);
         }
 
-        FluxProfile profile = loader.loadConfig(
-                FluxProfile.class,
+        FluxProfile profile = loader.loadObject(
+                "/profiles/" + uuid.toString(),
                 uuid.toString(),
-                FluxProfile::deserialize,
-                () -> createDefaultProfile(uuid)
+                FluxProfile.class
         );
 
         profiles.put(uuid, profile);
@@ -87,7 +86,16 @@ public class ProfileManager {
 
         if (profile == null) return;
 
-        loader.save(profile);
+        saveProfile(profile);
+    }
+    public void saveProfile(FluxProfile profile) {
+
+        if (profile == null) return;
+
+        loader.saveObject(
+                "/profiles/" + profile.getPlayerUUID() + ".yml",
+                profile.getPlayerUUID(),
+                profile);
     }
 
     /*
@@ -96,7 +104,7 @@ public class ProfileManager {
     public void saveAllProfiles() {
 
         for (FluxProfile profile : profiles.values()) {
-            loader.save(profile);
+            saveProfile(profile);
         }
     }
 
@@ -108,7 +116,7 @@ public class ProfileManager {
         FluxProfile profile = profiles.remove(uuid);
 
         if (profile != null) {
-            loader.save(profile);
+            saveProfile(profile);
         }
     }
 }

+ 4 - 8
src/main/java/me/lethunderhawk/profile/ProfileModule.java → src/main/java/me/lethunderhawk/fluxapi/profile/ProfileModule.java

@@ -1,15 +1,14 @@
-package me.lethunderhawk.profile;
+package me.lethunderhawk.fluxapi.profile;
 
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
-import me.lethunderhawk.profile.config.ConfigLoader;
-import me.lethunderhawk.profile.listener.JoinListener;
+import me.lethunderhawk.fluxapi.profile.config.ConfigLoader;
+import me.lethunderhawk.fluxapi.profile.listener.JoinListener;
 import org.bukkit.event.HandlerList;
 import org.bukkit.plugin.java.JavaPlugin;
 
 public class ProfileModule extends BazaarFluxModule {
     private JoinListener joinListener;
-    private ConfigLoader configLoader;
     private ProfileManager profileManager;
 
     public ProfileModule(JavaPlugin plugin) {
@@ -23,10 +22,7 @@ public class ProfileModule extends BazaarFluxModule {
 
     @Override
     public void onEnable() {
-        configLoader = new ConfigLoader(this.plugin);
-        FluxService.register(ConfigLoader.class, configLoader);
-
-        profileManager = new ProfileManager(configLoader);
+        profileManager = new ProfileManager(FluxService.get(ConfigLoader.class));
         FluxService.register(ProfileManager.class, profileManager);
 
         joinListener = new JoinListener(profileManager);

+ 175 - 0
src/main/java/me/lethunderhawk/fluxapi/profile/config/ConfigLoader.java

@@ -0,0 +1,175 @@
+package me.lethunderhawk.fluxapi.profile.config;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.plugin.java.JavaPlugin;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.function.Function;
+
+public final class ConfigLoader {
+
+    private final JavaPlugin plugin;
+    private final File dataFolder;
+
+    public ConfigLoader(JavaPlugin plugin) {
+        this.plugin = plugin;
+        this.dataFolder = plugin.getDataFolder();
+
+        if (!dataFolder.exists()) {
+            dataFolder.mkdirs();
+        }
+    }
+
+    /* =========================================================
+       =============== BASIC FILE HANDLING =====================
+       ========================================================= */
+
+    public File getFile(String path) {
+        File file = new File(dataFolder, path.endsWith(".yml") ? path : path + ".yml");
+
+        if (!file.getParentFile().exists()) {
+            file.getParentFile().mkdirs();
+        }
+
+        return file;
+    }
+
+    public YamlConfiguration loadConfig(String path) {
+        File file = getFile(path);
+        return YamlConfiguration.loadConfiguration(file);
+    }
+
+    public void saveConfig(YamlConfiguration cfg, String path) {
+        File file = getFile(path);
+        try {
+            cfg.save(file);
+        } catch (IOException e) {
+            throw new RuntimeException("Could not save file: " + file.getName(), e);
+        }
+    }
+
+    /* =========================================================
+       =============== SINGLE OBJECT ============================
+       ========================================================= */
+
+    public <T extends ConfigurationSerializable> void saveObject(
+            String path,
+            String node,
+            T object
+    ) {
+        YamlConfiguration cfg = loadConfig(path);
+        cfg.set(node, object);
+        saveConfig(cfg, path);
+    }
+
+    public <T extends ConfigurationSerializable> T loadObject(
+            String path,
+            String node,
+            Class<T> type
+    ) {
+        YamlConfiguration cfg = loadConfig(path);
+        Object obj = cfg.get(node);
+
+        if (obj == null) return null;
+
+        if (!type.isInstance(obj)) {
+            throw new IllegalStateException(
+                    "Expected type " + type.getName() +
+                            " but got " + obj.getClass().getName()
+            );
+        }
+
+        return type.cast(obj);
+    }
+
+    /* =========================================================
+       =============== COLLECTION ===============================
+       ========================================================= */
+
+    public <T extends ConfigurationSerializable> void saveCollection(
+            String path,
+            String node,
+            Collection<T> collection
+    ) {
+        YamlConfiguration cfg = loadConfig(path);
+        for (T object : collection) {
+            cfg.set(node, object);
+        }
+        saveConfig(cfg, path);
+    }
+
+    public <T extends ConfigurationSerializable> List<T> loadCollection(
+            String path,
+            String node,
+            Class<T> type
+    ) {
+        YamlConfiguration cfg = loadConfig(path);
+
+        List<?> rawList = cfg.getList(node);
+        if (rawList == null) return new ArrayList<>();
+
+        List<T> result = new ArrayList<>();
+
+        for (Object obj : rawList) {
+            if (type.isInstance(obj)) {
+                result.add(type.cast(obj));
+            }
+        }
+
+        return result;
+    }
+
+    /* =========================================================
+       =============== MAP STORAGE ==============================
+       ========================================================= */
+
+    public <K, V extends ConfigurationSerializable> void saveMap(
+            String path,
+            String node,
+            Map<K, V> map,
+            Function<K, String> keySerializer
+    ) {
+        YamlConfiguration cfg = loadConfig(path);
+
+        ConfigurationSection section = cfg.createSection(node);
+
+        for (Map.Entry<K, V> entry : map.entrySet()) {
+            String key = keySerializer.apply(entry.getKey());
+            section.set(key, entry.getValue());
+        }
+
+        saveConfig(cfg, path);
+    }
+
+    public <K, V extends ConfigurationSerializable> Map<K, V> loadMap(
+            String path,
+            String node,
+            Function<String, K> keyDeserializer,
+            Class<V> type
+    ) {
+        YamlConfiguration cfg = loadConfig(path);
+
+        ConfigurationSection section = cfg.getConfigurationSection(node);
+        if (section == null) return new HashMap<>();
+
+        Map<K, V> result = new HashMap<>();
+
+        for (String key : section.getKeys(false)) {
+            Object obj = section.get(key);
+
+            if (type.isInstance(obj)) {
+                result.put(
+                        keyDeserializer.apply(key),
+                        type.cast(obj)
+                );
+            }
+        }
+
+        return result;
+    }
+}
+

+ 1 - 1
src/main/java/me/lethunderhawk/profile/config/abstraction/ConfigFactory.java → src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/ConfigFactory.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.profile.config.abstraction;
+package me.lethunderhawk.fluxapi.profile.config.abstraction;
 
 import org.bukkit.configuration.file.YamlConfiguration;
 

+ 1 - 1
src/main/java/me/lethunderhawk/profile/config/abstraction/PersistentConfig.java → src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/PersistentConfig.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.profile.config.abstraction;
+package me.lethunderhawk.fluxapi.profile.config.abstraction;
 
 import org.bukkit.configuration.file.YamlConfiguration;
 

+ 3 - 2
src/main/java/me/lethunderhawk/profile/config/abstraction/subconfig/ProfileSubConfig.java → src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/subconfig/ProfileSubConfig.java

@@ -1,8 +1,9 @@
-package me.lethunderhawk.profile.config.abstraction.subconfig;
+package me.lethunderhawk.fluxapi.profile.config.abstraction.subconfig;
 
 import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
 
-public interface ProfileSubConfig {
+public interface ProfileSubConfig extends ConfigurationSerializable {
 
     /**
      * Unique ID of this sub config.

+ 1 - 1
src/main/java/me/lethunderhawk/profile/config/abstraction/subconfig/ProfileSubConfigFactory.java → src/main/java/me/lethunderhawk/fluxapi/profile/config/abstraction/subconfig/ProfileSubConfigFactory.java

@@ -1,4 +1,4 @@
-package me.lethunderhawk.profile.config.abstraction.subconfig;
+package me.lethunderhawk.fluxapi.profile.config.abstraction.subconfig;
 
 import org.bukkit.configuration.ConfigurationSection;
 

+ 3 - 3
src/main/java/me/lethunderhawk/profile/config/registry/ConfigService.java → src/main/java/me/lethunderhawk/fluxapi/profile/config/registry/ConfigService.java

@@ -1,7 +1,7 @@
-package me.lethunderhawk.profile.config.registry;
+package me.lethunderhawk.fluxapi.profile.config.registry;
 
-import me.lethunderhawk.profile.config.abstraction.ConfigFactory;
-import me.lethunderhawk.profile.config.abstraction.PersistentConfig;
+import me.lethunderhawk.fluxapi.profile.config.abstraction.ConfigFactory;
+import me.lethunderhawk.fluxapi.profile.config.abstraction.PersistentConfig;
 import org.bukkit.configuration.file.YamlConfiguration;
 
 import java.io.File;

+ 3 - 3
src/main/java/me/lethunderhawk/profile/config/registry/ProfileModuleRegistry.java → src/main/java/me/lethunderhawk/fluxapi/profile/config/registry/ProfileModuleRegistry.java

@@ -1,7 +1,7 @@
-package me.lethunderhawk.profile.config.registry;
+package me.lethunderhawk.fluxapi.profile.config.registry;
 
-import me.lethunderhawk.profile.config.abstraction.subconfig.ProfileSubConfig;
-import me.lethunderhawk.profile.config.abstraction.subconfig.ProfileSubConfigFactory;
+import me.lethunderhawk.fluxapi.profile.config.abstraction.subconfig.ProfileSubConfig;
+import me.lethunderhawk.fluxapi.profile.config.abstraction.subconfig.ProfileSubConfigFactory;
 
 import java.util.HashMap;
 import java.util.Map;

+ 3 - 3
src/main/java/me/lethunderhawk/profile/listener/JoinListener.java → src/main/java/me/lethunderhawk/fluxapi/profile/listener/JoinListener.java

@@ -1,6 +1,6 @@
-package me.lethunderhawk.profile.listener;
+package me.lethunderhawk.fluxapi.profile.listener;
 
-import me.lethunderhawk.profile.ProfileManager;
+import me.lethunderhawk.fluxapi.profile.ProfileManager;
 import org.bukkit.event.EventHandler;
 import org.bukkit.event.Listener;
 import org.bukkit.event.player.PlayerJoinEvent;
@@ -15,7 +15,7 @@ public class JoinListener implements Listener {
 
     @EventHandler
     public void onPlayerQuit(PlayerQuitEvent event) {
-        profileManager.saveProfile(event.getPlayer().getUniqueId());
+        profileManager.unloadProfile(event.getPlayer().getUniqueId());
     }
 
     @EventHandler

+ 88 - 0
src/main/java/me/lethunderhawk/fluxapi/profile/representation/FluxProfile.java

@@ -0,0 +1,88 @@
+package me.lethunderhawk.fluxapi.profile.representation;
+
+import me.lethunderhawk.fluxapi.profile.config.abstraction.subconfig.ProfileSubConfig;
+import me.lethunderhawk.fluxapi.profile.config.abstraction.subconfig.ProfileSubConfigFactory;
+import me.lethunderhawk.fluxapi.profile.config.registry.ProfileModuleRegistry;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class FluxProfile implements ConfigurationSerializable {
+
+    private final String playerUUID;
+    private final Map<String, ProfileSubConfig> subConfigs = new HashMap<>();
+
+    public FluxProfile(String playerUUID) {
+        this.playerUUID = playerUUID;
+    }
+
+    // Deserialization constructor
+    public FluxProfile(Map<String, Object> map) {
+
+        this.playerUUID = (String) map.get("uuid");
+
+        if (playerUUID == null) {
+            throw new IllegalStateException("Profile missing UUID.");
+        }
+
+        // Load sub configs
+        for (String id : ProfileModuleRegistry.getRegisteredIds()) {
+
+            Object raw = map.get(id);
+
+            if (raw instanceof ProfileSubConfig sub) {
+                subConfigs.put(id, sub);
+            }
+        }
+
+        // Inject missing modules
+        for (String id : ProfileModuleRegistry.getRegisteredIds()) {
+
+            if (!subConfigs.containsKey(id)) {
+
+                ProfileSubConfigFactory<?> factory =
+                        ProfileModuleRegistry.getFactory(id);
+
+                if (factory != null) {
+                    ProfileSubConfig defaultModule =
+                            factory.deserialize(new YamlConfiguration());
+
+                    subConfigs.put(id, defaultModule);
+                }
+            }
+        }
+    }
+
+    @Override
+    public @NotNull Map<String, Object> serialize() {
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("uuid", playerUUID);
+        data.put("ein", 1);
+        for (Map.Entry<String, ProfileSubConfig> entry : subConfigs.entrySet()) {
+            data.put(entry.getKey(), entry.getValue());
+        }
+
+        return data;
+    }
+
+    public static FluxProfile deserialize(Map<String, Object> map) {
+        return new FluxProfile(map);
+    }
+
+    public String getPlayerUUID() {
+        return playerUUID;
+    }
+
+    public void addSubConfig(ProfileSubConfig subConfig) {
+        subConfigs.put(subConfig.getId(), subConfig);
+    }
+
+    @SuppressWarnings("unchecked")
+    public <T extends ProfileSubConfig> T getSubConfig(String id, Class<T> type) {
+        return (T) subConfigs.get(id);
+    }
+}

+ 1 - 1
src/main/java/me/lethunderhawk/fluxapi/util/animation/Animation.java

@@ -1,7 +1,7 @@
 package me.lethunderhawk.fluxapi.util.animation;
 
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.main.FluxAPI;
+import me.lethunderhawk.fluxapi.main.FluxAPI;
 import org.bukkit.Bukkit;
 import org.bukkit.World;
 import org.bukkit.plugin.java.JavaPlugin;

+ 0 - 40
src/main/java/me/lethunderhawk/npc/NPCModule.java

@@ -1,40 +0,0 @@
-package me.lethunderhawk.npc;
-
-import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
-import me.lethunderhawk.npc.command.NPCCommand;
-import me.lethunderhawk.npc.manager.NPCManager;
-import org.bukkit.event.HandlerList;
-import org.bukkit.plugin.java.JavaPlugin;
-
-public class NPCModule extends BazaarFluxModule{
-    private NPCManager NPCManager;
-
-    public NPCModule(JavaPlugin plugin) {
-        super(plugin);
-    }
-
-    public String getPrefix() {
-        return "[NPC]";
-    }
-
-    @Override
-    public void onEnable() {
-        this.NPCManager = new NPCManager();
-
-        FluxService.register(NPCManager.class, NPCManager);
-
-        NPCCommand npcCommand = new NPCCommand(this);
-        plugin.getCommand("npc").setExecutor(npcCommand);
-        plugin.getCommand("npc").setTabCompleter(npcCommand);
-
-        plugin.getServer().getPluginManager().registerEvents(NPCManager, plugin);
-        plugin.getLogger().info("NPC Extension Enabled");
-    }
-
-    @Override
-    public void onDisable() {
-        HandlerList.unregisterAll(NPCManager);
-        NPCManager = null;
-    }
-}

+ 0 - 118
src/main/java/me/lethunderhawk/npc/abstraction/NPCOptions.java

@@ -1,118 +0,0 @@
-package me.lethunderhawk.npc.abstraction;
-
-import net.kyori.adventure.text.Component;
-import org.bukkit.Location;
-import org.bukkit.entity.EntityType;
-import org.jetbrains.annotations.NotNull;
-import org.jspecify.annotations.Nullable;
-
-import java.util.UUID;
-
-public class NPCOptions {
-    private String name;
-    private String texture;
-    private String signature;
-    private Location location;
-    private boolean visibleName = true;
-    private UUID uuid;
-    private boolean listed;
-    private EntityType entityType = EntityType.MANNEQUIN;
-    private Component description = null;
-    private boolean lookingAtNearest = false;
-    public NPCOptions(){
-    }
-    // ------ Getters
-    public String getName() {
-        return name;
-    }
-
-    public String getTexture() {
-        return texture;
-    }
-
-    public String getTextureSignature() {
-        return signature;
-    }
-
-    public Location getLocation() {
-        return location;
-    }
-
-    public boolean isNameVisible() {
-        return visibleName;
-    }
-
-    public UUID getUUID(){
-        if(uuid == null){
-            uuid = UUID.randomUUID();
-        }
-        return uuid;
-    }
-
-    public EntityType getEntityType(){
-        return entityType;
-    }
-
-    public boolean isListed() {
-        return listed;
-    }
-
-    public @Nullable Component getDescription() {
-        return description;
-    }
-
-    // ------ Setters
-    public NPCOptions setName(@NotNull String name) {
-        this.name = name;
-        return this;
-    }
-
-    public NPCOptions setEntityType(@NotNull EntityType entityType) {
-        this.entityType = entityType;
-        return this;
-    }
-
-    public NPCOptions setTexture(@NotNull String texture) {
-        this.texture = texture;
-        return this;
-    }
-
-    public NPCOptions setSignature(@NotNull String signature) {
-        this.signature = signature;
-        return this;
-    }
-
-    public NPCOptions setLocation(@NotNull Location location) {
-        this.location = location;
-        return this;
-    }
-
-    public NPCOptions setVisibleName(boolean visibleName) {
-        this.visibleName = visibleName;
-        return this;
-    }
-
-    public NPCOptions setUUID(@NotNull UUID uuid) {
-        this.uuid = uuid;
-        return this;
-    }
-
-    public NPCOptions setListed(boolean listed) {
-        this.listed = listed;
-        return this;
-    }
-
-    public NPCOptions setDescription(@Nullable Component description) {
-        this.description = description;
-        return this;
-    }
-
-    public boolean isLookingAtNearest() {
-        return lookingAtNearest;
-    }
-
-    public NPCOptions setLookingAtNearest(boolean lookingAtNearest) {
-        this.lookingAtNearest = lookingAtNearest;
-        return this;
-    }
-}

+ 0 - 148
src/main/java/me/lethunderhawk/npc/manager/NPCManager.java

@@ -1,148 +0,0 @@
-package me.lethunderhawk.npc.manager;
-
-import me.lethunderhawk.npc.abstraction.DummyNPC;
-import me.lethunderhawk.npc.abstraction.NPC;
-import me.lethunderhawk.npc.abstraction.NPCOptions;
-import org.bukkit.Location;
-import org.bukkit.entity.Entity;
-import org.bukkit.entity.EntityType;
-import org.bukkit.entity.Mannequin;
-import org.bukkit.entity.Player;
-import org.bukkit.event.EventHandler;
-import org.bukkit.event.Listener;
-import org.bukkit.event.entity.EntityDamageByEntityEvent;
-import org.bukkit.event.player.PlayerInteractAtEntityEvent;
-import org.bukkit.event.player.PlayerMoveEvent;
-import org.bukkit.inventory.EquipmentSlot;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.UUID;
-
-public class NPCManager implements Listener {
-    private static final double FOCUS_RANGE_SQUARED = 250;
-    public int focusRange = 30;
-    private final Map<UUID, NPC> npcs = new HashMap<>();
-
-    public NPCManager() {
-
-    }
-
-    /**
-     * Registers a new NPC with {@link EntityType} set as {@link Mannequin} with attributes
-     * @param displayName The name of the NPC
-     * @param textureValue The texture of the NPC
-     * @param textureSignature The signature of the NPC's texture
-     * @param location The location to spawn it in
-     */
-    public void addNPC(String displayName, String textureValue, String textureSignature, Location location ){
-        NPCOptions npcOptions = new NPCOptions()
-                .setName(displayName)
-                .setTexture(textureValue)
-                .setSignature(textureSignature)
-                .setLocation(location);
-
-        NPC npc = new DummyNPC(npcOptions);
-    }
-
-    /**
-     * Registers a new NPC
-     * @param npcOptions The NPC's options
-     */
-    public void addNPC(NPCOptions npcOptions){
-        NPC npc = new DummyNPC(npcOptions);
-    }
-
-    @EventHandler
-    public void onEntityDamageByEntityEvent(EntityDamageByEntityEvent e) {
-        if(!(e.getDamager() instanceof Player player)) return;
-
-        NPC npc = getNPC(e.getEntity());
-        if(npc == null) return;
-
-        e.setCancelled(true);
-        if(player.isSneaking()){
-            npc.onShiftLeftClick(player);
-        }else{
-            npc.onLeftClick(player);
-        }
-    }
-    @EventHandler
-    public void onPlayerInteractAtEntity(PlayerInteractAtEntityEvent e) {
-        Player player = e.getPlayer();
-
-        NPC npc = getNPC(e.getRightClicked());
-        if(npc == null) return;
-
-        if(e.getHand() != EquipmentSlot.HAND) return;
-        if(player.isSneaking()){
-            npc.onShiftRightClick(player);
-        }else{
-            npc.onRightClick(player);
-        }
-        e.setCancelled(true);
-    }
-
-    @EventHandler(ignoreCancelled = true)
-    public void onPlayerMove(PlayerMoveEvent event) {
-
-        // Ignore pure rotation (huge performance gain)
-        if (event.getFrom().getX() == event.getTo().getX()
-                && event.getFrom().getY() == event.getTo().getY()
-                && event.getFrom().getZ() == event.getTo().getZ()) {
-            return;
-        }
-
-        Player player = event.getPlayer();
-        Location playerLocation = player.getEyeLocation();
-
-        for (NPC npc : npcs.values()) {
-            if(!npc.getOptions().isLookingAtNearest()) return;
-            Location npcLocation = npc.getEntity().getLocation();
-
-            // Skip different worlds immediately
-            if (!npcLocation.getWorld().equals(playerLocation.getWorld())) {
-                continue;
-            }
-
-            // Use squared distance (NO sqrt → much faster)
-            if (npcLocation.distanceSquared(playerLocation) > FOCUS_RANGE_SQUARED) {
-                continue;
-            }
-
-            // Optional: Visibility check if really needed
-            if (!player.canSee(npc.getEntity())) {
-                continue;
-            }
-
-            npc.lookAt(playerLocation);
-        }
-    }
-
-    private NPC getNPC(Entity entity){
-        UUID entityUUID = entity.getUniqueId();
-        return npcs.getOrDefault(entityUUID, null);
-    }
-
-    public void registerNPC(NPC toRegister){
-        npcs.put(toRegister.getEntity().getUniqueId(), toRegister);
-    }
-
-    public NPC findByUUID(String uuid){
-        UUID manUUID = UUID.fromString(uuid);
-        return npcs.get(manUUID);
-    }
-
-    public Collection<UUID> getRegisteredNPCsUUID() {
-        return npcs.keySet();
-    }
-
-    public void deleteNPCByUUIDString(String uuid) {
-        npcs.remove(UUID.fromString(uuid)).getEntity().remove();
-    }
-
-    public void renameNPC(UUID entityUUID, String newName) {
-        npcs.get(entityUUID).rename(newName);
-    }
-}

+ 0 - 87
src/main/java/me/lethunderhawk/profile/config/ConfigLoader.java

@@ -1,87 +0,0 @@
-package me.lethunderhawk.profile.config;
-
-import me.lethunderhawk.profile.config.abstraction.ConfigFactory;
-import me.lethunderhawk.profile.config.abstraction.PersistentConfig;
-import org.bukkit.configuration.file.YamlConfiguration;
-import org.bukkit.plugin.java.JavaPlugin;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.function.Supplier;
-
-public class ConfigLoader {
-
-    private final File folder;
-    private final JavaPlugin main;
-
-    public ConfigLoader(JavaPlugin plugin) {
-        this.main = plugin;
-        this.folder = main.getDataFolder();
-
-        if (!folder.exists()) {
-            folder.mkdirs();
-        }
-    }
-
-    public <T extends PersistentConfig> T loadConfig(
-            Class<T> type,
-            String fileName,
-            ConfigFactory<T> factory,
-            Supplier<T> defaultSupplier
-    ) {
-
-        File file = getFile(fileName);
-
-        // If file does not exist → create default config
-        if (!file.exists()) {
-
-            try {
-                file.getParentFile().mkdirs();
-                file.createNewFile();
-            } catch (IOException e) {
-                throw new RuntimeException("Could not create config file: " + fileName, e);
-            }
-
-            T defaultConfig = defaultSupplier.get();
-            save(defaultConfig);
-
-            main.getLogger().info("[ProfileConfig] Created new profile " + fileName + ".yml");
-
-            return defaultConfig;
-        }
-
-        YamlConfiguration cfg = YamlConfiguration.loadConfiguration(file);
-
-        // If file exists but is empty → also create default
-        if (cfg.getKeys(false).isEmpty()) {
-
-            T defaultConfig = defaultSupplier.get();
-            save(defaultConfig);
-
-            main.getLogger().info("[ProfileConfig] Initialized empty profile " + fileName + ".yml");
-
-            return defaultConfig;
-        }
-
-        return factory.deserialize(cfg);
-    }
-
-
-    public void save(PersistentConfig persistentConfig) {
-        File file = getFile(persistentConfig.getFileName());
-        YamlConfiguration cfg = new YamlConfiguration();
-
-        persistentConfig.serialize(cfg);
-
-        try {
-            cfg.save(file);
-        } catch (IOException e) {
-            throw new RuntimeException("Could not save config file: " + file.getName(), e);
-        }
-    }
-
-    private File getFile(String name) {
-        return new File(folder, name + ".yml");
-    }
-}
-

+ 0 - 91
src/main/java/me/lethunderhawk/profile/representation/FluxProfile.java

@@ -1,91 +0,0 @@
-package me.lethunderhawk.profile.representation;
-
-import me.lethunderhawk.profile.config.abstraction.PersistentConfig;
-import me.lethunderhawk.profile.config.abstraction.subconfig.ProfileSubConfig;
-import me.lethunderhawk.profile.config.abstraction.subconfig.ProfileSubConfigFactory;
-import me.lethunderhawk.profile.config.registry.ProfileModuleRegistry;
-import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.configuration.file.YamlConfiguration;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public class FluxProfile implements PersistentConfig {
-
-    private final String playerUUID;
-
-    // key = subconfig id
-    private final Map<String, ProfileSubConfig> subConfigs = new HashMap<>();
-
-    public FluxProfile(String playerUUID) {
-        this.playerUUID = playerUUID;
-    }
-
-    /*
-     * Add a module dynamically.
-     */
-    public void addSubConfig(ProfileSubConfig subConfig) {
-        subConfigs.put(subConfig.getId(), subConfig);
-    }
-
-    /*
-     * Get module by type safely.
-     */
-    @SuppressWarnings("unchecked")
-    public <T extends ProfileSubConfig> T getSubConfig(String id, Class<T> type) {
-        return (T) subConfigs.get(id);
-    }
-
-    @Override
-    public void serialize(YamlConfiguration cfg) {
-
-        cfg.set("uuid", playerUUID);
-
-        for (ProfileSubConfig subConfig : subConfigs.values()) {
-
-            ConfigurationSection section =
-                    cfg.createSection(subConfig.getId());
-
-            subConfig.serialize(section);
-        }
-    }
-
-    public static FluxProfile deserialize(YamlConfiguration cfg) {
-
-        String uuid = cfg.getString("uuid");
-
-        if (uuid == null) {
-            throw new IllegalStateException("Profile file missing UUID.");
-        }
-
-        FluxProfile profile = new FluxProfile(uuid);
-
-        for (String id : ProfileModuleRegistry.getRegisteredIds()) {
-
-            if (!cfg.isConfigurationSection(id)) continue;
-
-            ConfigurationSection section = cfg.getConfigurationSection(id);
-
-            ProfileSubConfigFactory<?> factory =
-                    ProfileModuleRegistry.getFactory(id);
-
-            if (factory == null) continue;
-
-            ProfileSubConfig subConfig =
-                    factory.deserialize(section);
-
-            profile.addSubConfig(subConfig);
-        }
-
-        return profile;
-    }
-
-    @Override
-    public String getFileName() {
-        return playerUUID;
-    }
-
-    public String getPlayerUUID() {
-        return playerUUID;
-    }
-}

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

@@ -1,6 +1,6 @@
 name: ${project.artifactId}
 version: ${project.version}
-main: me.lethunderhawk.main.FluxAPI
+main: me.lethunderhawk.fluxapi.main.FluxAPI
 api-version: 1.21.10
 prefix: FluxAPI
 depend: