Parcourir la source

Profiles can now be stored dynamically

Jan il y a 3 semaines
Parent
commit
ea0e451ee4

+ 1 - 1
pom.xml

@@ -6,7 +6,7 @@
 
     <groupId>me.lethunderhawk</groupId>
     <artifactId>FluxAPI</artifactId>
-    <version>1.2.1-SNAPSHOT.2</version>
+    <version>1.2.2</version>
     <packaging>jar</packaging>
 
     <name>FluxAPI</name>

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

@@ -2,6 +2,7 @@ package me.lethunderhawk.fluxapi.main;
 
 import me.lethunderhawk.fluxapi.FluxService;
 import me.lethunderhawk.fluxapi.npc.NPCModule;
+import me.lethunderhawk.fluxapi.profile.ProfileModule;
 import me.lethunderhawk.fluxapi.util.config.ConfigLoader;
 import org.bukkit.plugin.java.JavaPlugin;
 
@@ -14,16 +15,18 @@ public class FluxAPI extends JavaPlugin {
         ConfigLoader configLoader = new ConfigLoader(this);
         FluxService.register(ConfigLoader.class, configLoader);
 
-        //ProfileModule profile = new ProfileModule(this);
-        //profile.onEnable();
+        ProfileModule profileModule = new ProfileModule(this);
+        profileModule.onEnable();
+        FluxService.registerModule(ProfileModule.class, profileModule);
 
-        NPCModule module = new NPCModule(this);
-        module.onEnable();
-        FluxService.registerModule(NPCModule.class, module);
+        NPCModule npcModule = new NPCModule(this);
+        npcModule.onEnable();
+        FluxService.registerModule(NPCModule.class, npcModule);
     }
 
     @Override
     public void onDisable() {
         FluxService.get(NPCModule.class).onDisable();
+        FluxService.get(ProfileModule.class).onDisable();
     }
 }

+ 71 - 44
src/main/java/me/lethunderhawk/fluxapi/profile/ProfileManager.java

@@ -1,38 +1,52 @@
 package me.lethunderhawk.fluxapi.profile;
 
-import me.lethunderhawk.fluxapi.util.config.ConfigLoader;
 import me.lethunderhawk.fluxapi.profile.representation.FluxProfile;
-import org.bukkit.entity.Player;
+import me.lethunderhawk.fluxapi.util.config.ConfigLoader;
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
 
-import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public final class ProfileManager {
 
-public class ProfileManager {
+    private static final String PROFILE_FOLDER = "profiles/";
 
-    private final Map<UUID, FluxProfile> profiles = new HashMap<>();
+    private final Map<UUID, FluxProfile> profiles = new ConcurrentHashMap<>();
     private final ConfigLoader loader;
 
     public ProfileManager(ConfigLoader loader) {
         this.loader = loader;
     }
 
-    public FluxProfile loadProfile(UUID uuid) {
+    /* =========================================================
+       =============== LOAD ====================================
+       ========================================================= */
 
-        if (profiles.containsKey(uuid)) {
-            return profiles.get(uuid);
+    public FluxProfile loadProfile(UUID uuid) {
+        FluxProfile cached = profiles.get(uuid);
+        if (cached != null) {
+            return cached;
         }
 
+        String filePath = PROFILE_FOLDER + uuid + ".yml";
+        String node = uuid.toString();
+
         FluxProfile profile = loader.loadObject(
-                "/profiles/" + uuid.toString(),
-                uuid.toString(),
+                filePath,
+                node,
                 FluxProfile.class
         );
-        if(profile == null) {
+
+        if (profile == null) {
             profile = createDefaultProfile(uuid);
+            // Immediately save default so file exists
+            loader.saveObject(filePath, node, profile);
         }
 
         profiles.put(uuid, profile);
+
         return profile;
     }
 
@@ -40,66 +54,79 @@ public class ProfileManager {
         return new FluxProfile(uuid.toString());
     }
 
-    /*
-     * Manual profile creation (rarely needed now).
-     */
-    public FluxProfile createNewProfile(Player player) {
-
-        UUID uuid = player.getUniqueId();
-
-        FluxProfile profile = createDefaultProfile(uuid);
+    /* =========================================================
+       =============== MEMORY ACCESS ============================
+       ========================================================= */
 
-        profiles.put(uuid, profile);
-
-        return profile;
-    }
-
-    /*
-     * Get from memory only.
-     */
     public FluxProfile getProfile(UUID uuid) {
         return profiles.get(uuid);
     }
 
-    /*
-     * Save single profile.
-     */
-    public void saveProfile(UUID uuid) {
-
-        FluxProfile profile = profiles.get(uuid);
+    public boolean isLoaded(UUID uuid) {
+        return profiles.containsKey(uuid);
+    }
 
-        if (profile == null) return;
+    /* =========================================================
+       =============== SAVE =====================================
+       ========================================================= */
 
-        saveProfile(profile);
+    public void saveProfile(UUID uuid) {
+        FluxProfile profile = profiles.get(uuid);
+        if (profile != null) {
+            saveProfile(profile);
+        }
     }
 
     public void saveProfile(FluxProfile profile) {
 
         if (profile == null) return;
 
+        String filePath = PROFILE_FOLDER + profile.getPlayerUUID() + ".yml";
+        String node = profile.getPlayerUUID();
+
         loader.saveObject(
-                "/profiles/" + profile.getPlayerUUID() + ".yml",
-                profile.getPlayerUUID(),
-                profile);
+                filePath,
+                node,
+                profile
+        );
     }
 
-    /*
-     * Save all loaded profiles.
-     */
     public void saveAllProfiles() {
         for (FluxProfile profile : profiles.values()) {
             saveProfile(profile);
         }
     }
 
-    /*
-     * Unload profile (save before removal).
-     */
+    /* =========================================================
+       =============== UNLOAD ===================================
+       ========================================================= */
+
     public void unloadProfile(UUID uuid) {
+
         FluxProfile profile = profiles.remove(uuid);
 
         if (profile != null) {
             saveProfile(profile);
         }
     }
+
+    public <T extends ConfigurationSerializable> T getSubProfile(
+            UUID uuid,
+            String id,
+            Class<T> type
+    ) {
+        FluxProfile profile = profiles.get(uuid);
+
+        if (profile == null) {
+            profile = loadProfile(uuid);
+        }
+
+        return profile.getSubProfile(id, type);
+    }
+
+    public void unloadAllProfiles() {
+        for (UUID uuid : new HashSet<>(profiles.keySet())) {
+            unloadProfile(uuid);
+        }
+    }
 }

+ 2 - 2
src/main/java/me/lethunderhawk/fluxapi/profile/ProfileModule.java

@@ -1,9 +1,9 @@
 package me.lethunderhawk.fluxapi.profile;
 
 import me.lethunderhawk.fluxapi.FluxService;
-import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
-import me.lethunderhawk.fluxapi.util.config.ConfigLoader;
 import me.lethunderhawk.fluxapi.profile.listener.JoinListener;
+import me.lethunderhawk.fluxapi.util.config.ConfigLoader;
+import me.lethunderhawk.fluxapi.util.interfaces.BazaarFluxModule;
 import org.bukkit.event.HandlerList;
 import org.bukkit.plugin.java.JavaPlugin;
 

+ 34 - 0
src/main/java/me/lethunderhawk/fluxapi/profile/event/FluxProfileLoadEvent.java

@@ -0,0 +1,34 @@
+package me.lethunderhawk.fluxapi.profile.event;
+
+import me.lethunderhawk.fluxapi.profile.representation.FluxProfile;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class FluxProfileLoadEvent extends Event {
+
+    private static final HandlerList HANDLERS = new HandlerList();
+
+    private final FluxProfile profile;
+
+    public FluxProfileLoadEvent(@NotNull FluxProfile profile) {
+        this.profile = profile;
+    }
+
+    public @NotNull FluxProfile getProfile() {
+        return profile;
+    }
+
+    public @NotNull String getPlayerUUID() {
+        return profile.getPlayerUUID();
+    }
+
+    @Override
+    public @NotNull HandlerList getHandlers() {
+        return HANDLERS;
+    }
+
+    public static @NotNull HandlerList getHandlerList() {
+        return HANDLERS;
+    }
+}

+ 43 - 0
src/main/java/me/lethunderhawk/fluxapi/profile/event/FluxProfileUpdateEvent.java

@@ -0,0 +1,43 @@
+package me.lethunderhawk.fluxapi.profile.event;
+
+import me.lethunderhawk.fluxapi.profile.representation.FluxProfile;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class FluxProfileUpdateEvent extends Event {
+
+    private static final HandlerList HANDLERS = new HandlerList();
+
+    private final FluxProfile profile;
+    private final String updatedSection;
+
+    public FluxProfileUpdateEvent(
+            @NotNull FluxProfile profile,
+            @NotNull String updatedSection
+    ) {
+        this.profile = profile;
+        this.updatedSection = updatedSection;
+    }
+
+    public @NotNull FluxProfile getProfile() {
+        return profile;
+    }
+
+    public @NotNull String getPlayerUUID() {
+        return profile.getPlayerUUID();
+    }
+
+    public @NotNull String getUpdatedSection() {
+        return updatedSection;
+    }
+
+    @Override
+    public @NotNull HandlerList getHandlers() {
+        return HANDLERS;
+    }
+
+    public static @NotNull HandlerList getHandlerList() {
+        return HANDLERS;
+    }
+}

+ 194 - 10
src/main/java/me/lethunderhawk/fluxapi/profile/representation/FluxProfile.java

@@ -1,37 +1,225 @@
 package me.lethunderhawk.fluxapi.profile.representation;
 
+import me.lethunderhawk.fluxapi.profile.event.FluxProfileLoadEvent;
+import me.lethunderhawk.fluxapi.profile.event.FluxProfileUpdateEvent;
+import org.bukkit.Bukkit;
 import org.bukkit.configuration.serialization.ConfigurationSerializable;
+import org.bukkit.configuration.serialization.ConfigurationSerialization;
 import org.jetbrains.annotations.NotNull;
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 public class FluxProfile implements ConfigurationSerializable {
 
+    /* ------------------------------------------------ */
+    /* Static Registry                                  */
+    /* ------------------------------------------------ */
+
+    private static final Map<String, Class<? extends ConfigurationSerializable>> REGISTERED_TYPES = new ConcurrentHashMap<>();
+    private static final Map<String, SubProfileFactory<?>> DEFAULT_FACTORIES = new HashMap<>();
+    /**
+     * Register a sub profile type.
+     *
+     * @param id    The yaml key (e.g. "customdata1")
+     * @param clazz The serializable class
+     */
+    public static void registerSubProfile(
+            @NotNull String id,
+            @NotNull Class<? extends ConfigurationSerializable> clazz
+    ) {
+        REGISTERED_TYPES.put(id, clazz);
+        ConfigurationSerialization.registerClass(clazz);
+
+    }
+    /**
+     * Register a sub profile type with a default Object.
+     *
+     * @param id    The yaml key (e.g. "customdata1")
+     * @param clazz The serializable class
+     * @param factory The default factory used to create a subConfig if there is nothing saved yet
+     */
+    public static <T extends ConfigurationSerializable> void registerSubProfile(
+            @NotNull String id,
+            @NotNull Class<T> clazz,
+            @NotNull SubProfileFactory<T> factory
+    ) {
+        REGISTERED_TYPES.put(id, clazz);
+        DEFAULT_FACTORIES.put(id, factory);
+        ConfigurationSerialization.registerClass(clazz);
+    }
+
+    /* ------------------------------------------------ */
+    /* Instance Data                                    */
+    /* ------------------------------------------------ */
+
     private final String playerUUID;
-    private final Map<String, ConfigurationSerializable> subConfigs = new HashMap<>();
+    private boolean dirty = false;
+
+    // Fully deserialized objects
+    private final Map<String, ConfigurationSerializable> subProfiles = new HashMap<>();
 
-    public FluxProfile(String playerUUID) {
+    // Raw maps (used if plugin not yet registered)
+    private final Map<String, Map<String, Object>> unresolvedData = new HashMap<>();
+
+    /* ------------------------------------------------ */
+    /* Constructors                                      */
+    /* ------------------------------------------------ */
+
+    public FluxProfile(@NotNull String playerUUID) {
         this.playerUUID = playerUUID;
     }
 
-    // Deserialization constructor
-    public FluxProfile(Map<String, Object> map) {
+    /**
+     * Bukkit deserialization constructor
+     */
+    public FluxProfile(@NotNull Map<String, Object> map) {
 
         this.playerUUID = (String) map.get("uuid");
 
         if (playerUUID == null) {
             throw new IllegalStateException("Profile missing UUID.");
         }
+
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+
+            String key = entry.getKey();
+
+            if (key.equals("uuid")) continue;
+
+            Object value = entry.getValue();
+
+            // CASE 1: Already deserialized by Bukkit
+            if (value instanceof ConfigurationSerializable serializable) {
+
+                subProfiles.put(key, serializable);
+                continue;
+            }
+
+            // CASE 2: Raw map (class not registered yet)
+            if (value instanceof Map<?, ?> rawMap) {
+
+                @SuppressWarnings("unchecked")
+                Map<String, Object> section =
+                        new HashMap<>((Map<String, Object>) rawMap);
+
+                Object typeKey = section.get("==");
+
+                if (typeKey instanceof String className) {
+
+                    try {
+                        Class<?> clazz = Class.forName(className);
+
+                        if (ConfigurationSerializable.class.isAssignableFrom(clazz)) {
+
+                            ConfigurationSerializable obj =
+                                    ConfigurationSerialization.deserializeObject(
+                                            section,
+                                            clazz.asSubclass(ConfigurationSerializable.class)
+                                    );
+
+                            subProfiles.put(key, obj);
+                            continue;
+                        }
+
+                    } catch (ClassNotFoundException ignored) {}
+                }
+
+                unresolvedData.put(key, section);
+            }
+
+            System.out.println("Loaded key: " + key + " type: " + value.getClass().getName());
+        }
+
+        Bukkit.getPluginManager().callEvent(new FluxProfileLoadEvent(this));
     }
 
+
+
+    /* ------------------------------------------------ */
+    /* Public API                                        */
+    /* ------------------------------------------------ */
+
+    public @NotNull String getPlayerUUID() {
+        return playerUUID;
+    }
+
+    /**
+     * Add or replace a sub profile.
+     */
+    public void addSubProfile(
+            @NotNull String id,
+            @NotNull ConfigurationSerializable profile
+    ) {
+        subProfiles.put(id, profile);
+
+        Bukkit.getPluginManager().callEvent(
+                new FluxProfileUpdateEvent(this, id)
+        );
+    }
+
+    /**
+     * Type-safe retrieval.
+     * Automatically resolves unresolved raw data if class gets registered later.
+     */
+    @SuppressWarnings("unchecked")
+    public synchronized <T extends ConfigurationSerializable> T getSubProfile(
+            @NotNull String id,
+            @NotNull Class<T> expectedType
+    ) {
+        // Already deserialized
+        ConfigurationSerializable existing = subProfiles.get(id);
+        if (existing != null) {
+            return expectedType.cast(existing);
+        }
+
+        // Raw saved data exists → deserialize FIRST
+        Map<String, Object> raw = unresolvedData.remove(id);
+        if (raw != null) {
+
+            Class<? extends ConfigurationSerializable> clazz = REGISTERED_TYPES.get(id);
+
+            if (clazz != null) {
+                ConfigurationSerializable obj =
+                        ConfigurationSerialization.deserializeObject(raw, clazz);
+
+                subProfiles.put(id, obj);
+                return expectedType.cast(obj);
+            } else {
+                // Class not yet registered → put raw back
+                unresolvedData.put(id, raw);
+                return null;
+            }
+        }
+
+        // Only if NO SAVED DATA exists → create default
+        SubProfileFactory<?> factory = DEFAULT_FACTORIES.get(id);
+        if (factory != null) {
+
+            T created = (T) factory.create(playerUUID);
+            subProfiles.put(id, created);
+            return created;
+        }
+
+        return null;
+    }
+
+    /* ------------------------------------------------ */
+    /* Serialization                                     */
+    /* ------------------------------------------------ */
+
     @Override
     public @NotNull Map<String, Object> serialize() {
 
         Map<String, Object> data = new HashMap<>();
         data.put("uuid", playerUUID);
-        data.put("ein", 1);
-        data.putAll(subConfigs);
+
+        // Store resolved profiles
+        data.putAll(subProfiles);
+
+        // Preserve unresolved raw data
+        data.putAll(unresolvedData);
 
         return data;
     }
@@ -39,8 +227,4 @@ public class FluxProfile implements ConfigurationSerializable {
     public static FluxProfile deserialize(Map<String, Object> map) {
         return new FluxProfile(map);
     }
-
-    public String getPlayerUUID() {
-        return playerUUID;
-    }
 }

+ 8 - 0
src/main/java/me/lethunderhawk/fluxapi/profile/representation/SubProfileFactory.java

@@ -0,0 +1,8 @@
+package me.lethunderhawk.fluxapi.profile.representation;
+
+import org.bukkit.configuration.serialization.ConfigurationSerializable;
+
+@FunctionalInterface
+public interface SubProfileFactory<T extends ConfigurationSerializable> {
+    T create(String playerUUID);
+}