From b756b912bd69a258551cf40631e8da4423836940 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Mon, 12 Dec 2022 17:29:31 +0100 Subject: [PATCH] Player configuration API with default implementation on paper server. Also merging pandalib-paper-player into pandalib-paper due to unavoidable circular dependency --- pandalib-paper-players/pom.xml | 46 ---- .../lib/paper/players/PaperOffPlayer.java | 35 +++ .../lib/paper/players/PaperOnlinePlayer.java | 0 .../players/PaperPlayerConfigStorage.java | 213 ++++++++++++++++++ .../players/standalone/AbstractOffPlayer.java | 19 ++ 5 files changed, 267 insertions(+), 46 deletions(-) delete mode 100644 pandalib-paper-players/pom.xml rename {pandalib-paper-players => pandalib-paper}/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java (76%) rename {pandalib-paper-players => pandalib-paper}/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java (100%) create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java diff --git a/pandalib-paper-players/pom.xml b/pandalib-paper-players/pom.xml deleted file mode 100644 index 3900bf6..0000000 --- a/pandalib-paper-players/pom.xml +++ /dev/null @@ -1,46 +0,0 @@ - - - - pandalib-parent - fr.pandacube.lib - 1.0-SNAPSHOT - ../pom.xml - - 4.0.0 - - pandalib-paper-players - jar - - - - papermc - https://papermc.io/repo/repository/maven-public/ - - - - - - fr.pandacube.lib - pandalib-players - ${project.version} - - - - - - io.papermc.paper - paper-api - ${paper.version}-SNAPSHOT - provided - - - io.papermc.paper - paper-mojangapi - ${paper.version}-SNAPSHOT - provided - - - - \ No newline at end of file diff --git a/pandalib-paper-players/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java similarity index 76% rename from pandalib-paper-players/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java index d6f99f1..5f026ac 100644 --- a/pandalib-paper-players/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOffPlayer.java @@ -7,6 +7,8 @@ import org.bukkit.scoreboard.Team; import fr.pandacube.lib.players.standalone.AbstractOffPlayer; +import java.util.function.UnaryOperator; + /** * Represents any player on a paper server, either offline or online. */ @@ -94,4 +96,37 @@ public interface PaperOffPlayer extends AbstractOffPlayer { return teamPrefix + teamColor + name + teamSuffix; } + + + + + /* + * Player config + */ + + @Override + default String getConfig(String key) throws Exception { + return PaperPlayerConfigStorage.get(getUniqueId(), key); + } + + @Override + default String getConfig(String key, String deflt) throws Exception { + return PaperPlayerConfigStorage.get(getUniqueId(), key, deflt); + } + + @Override + default void setConfig(String key, String value) throws Exception { + PaperPlayerConfigStorage.set(getUniqueId(), key, value); + } + + @Override + default void updateConfig(String key, String deflt, UnaryOperator updater) throws Exception { + PaperPlayerConfigStorage.update(getUniqueId(), key, deflt, updater); + } + + @Override + default void unsetConfig(String key) throws Exception { + PaperPlayerConfigStorage.unset(getUniqueId(), key); + } + } diff --git a/pandalib-paper-players/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java similarity index 100% rename from pandalib-paper-players/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java rename to pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperOnlinePlayer.java diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java new file mode 100644 index 0000000..cc6d662 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/players/PaperPlayerConfigStorage.java @@ -0,0 +1,213 @@ +package fr.pandacube.lib.paper.players; + +import fr.pandacube.lib.paper.PandaLibPaper; +import fr.pandacube.lib.util.Log; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.file.YamlConfiguration; + +import java.io.File; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.UUID; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; + +public class PaperPlayerConfigStorage { + + static File storageFile = new File(PandaLibPaper.getPlugin().getDataFolder(), "playerdata.yml"); + static boolean initialized = false; + + static LinkedHashMap data = new LinkedHashMap<>(); + static LinkedHashMap> playerSortedData = new LinkedHashMap<>(); + static LinkedHashMap> keySortedData = new LinkedHashMap<>(); + static boolean changed = false; + + + + private static synchronized void initIfNeeded() { + if (initialized) + return; + + try { + load(); + } catch (InvalidConfigurationException|IOException e) { + throw new RuntimeException("Unable to load the player data file.", e); + } + + // auto-save every 30 seconds + Bukkit.getScheduler().runTaskTimerAsynchronously(PandaLibPaper.getPlugin(), PaperPlayerConfigStorage::save, 600, 600); + + initialized = true; + } + + + + + + + private static synchronized void load() throws IOException, InvalidConfigurationException { + YamlConfiguration config = new YamlConfiguration(); + config.load(storageFile); + data.clear(); + playerSortedData.clear(); + keySortedData.clear(); + for (String pIdStr : config.getKeys(false)) { + UUID pId; + try { + pId = UUID.fromString(pIdStr); + } catch (IllegalArgumentException e) { + Log.severe("Invalid player UUID: '" + pIdStr + "'", e); + continue; + } + ConfigurationSection sec = config.getConfigurationSection(pIdStr); + for (String key : sec.getKeys(false)) { + String value = sec.getString(key); + create(pId, key, value); + } + } + changed = false; + } + + + + private static synchronized void save() { + YamlConfiguration config = new YamlConfiguration(); + for (UUID pId : playerSortedData.keySet()) { + String pIdStr = pId.toString(); + ConfigurationSection sec = new YamlConfiguration(); + for (ConfigEntry e : playerSortedData.get(pId)) { + sec.set(e.key, e.value); + } + config.set(pIdStr, sec); + } + try { + config.save(storageFile); + } catch (IOException e) { + throw new RuntimeException("Unable to save the player data file.", e); + } + changed = false; + } + + + + + + + + private static synchronized void create(UUID player, String key, String newValue) { + ConfigKey cKey = new ConfigKey(player, key); + ConfigEntry e = new ConfigEntry(player, key, newValue); + data.put(cKey, e); + playerSortedData.computeIfAbsent(player, p -> new LinkedHashSet<>()).add(e); + keySortedData.computeIfAbsent(key, p -> new LinkedHashSet<>()).add(e); + } + + + public static synchronized void set(UUID player, String key, String newValue) { + initIfNeeded(); + ConfigKey cKey = new ConfigKey(player, key); + ConfigEntry e = data.get(cKey); + if (e != null && newValue == null) { // delete + data.remove(cKey); + if (playerSortedData.containsKey(player)) + playerSortedData.get(player).remove(e); + if (keySortedData.containsKey(key)) + keySortedData.get(key).remove(e); + changed = true; + } + else if (e == null && newValue != null) { // create + create(player, key, newValue); + changed = true; + } + else if (e != null && !newValue.equals(e.value)) { // update + e.value = newValue; + changed = true; + } + } + + public static synchronized String get(UUID player, String key) { + initIfNeeded(); + ConfigEntry e = data.get(new ConfigKey(player, key)); + return e != null ? e.value : null; + } + + public static String get(UUID p, String k, String deflt) { + String value = get(p, k); + return value == null ? deflt : value; + } + + public static synchronized void update(UUID p, String k, String deflt, UnaryOperator updater) { + String oldValue = get(p, k, deflt); + set(p, k, updater.apply(oldValue)); + } + + public static void unset(UUID p, String k) { + set(p, k, null); + } + + + public static LinkedHashSet getAllFromPlayer(UUID p) { + initIfNeeded(); + return new LinkedHashSet<>(playerSortedData.getOrDefault(p, new LinkedHashSet<>())); + } + + public static LinkedHashSet getAllWithKeys(String key) { + initIfNeeded(); + return new LinkedHashSet<>(keySortedData.getOrDefault(key, new LinkedHashSet<>())); + } + + public static LinkedHashSet getAllWithKeyValue(String k, String v) { + initIfNeeded(); + return getAllWithKeys(k).stream() + .filter(c -> c.value.equals(v)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + } + + + + private record ConfigKey(UUID playerId, String key) { } + + public static class ConfigEntry { + private final UUID playerId; + private final String key; + private String value; + + private ConfigEntry(UUID playerId, String key, String value) { + this.playerId = playerId; + this.key = key; + this.value = value; + } + + public UUID getPlayerId() { + return playerId; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + private void setValue(String value) { + this.value = value; + } + + @Override + public int hashCode() { + return Objects.hash(playerId, key); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof ConfigEntry o + && Objects.equals(playerId, o.playerId) + && Objects.equals(key, o.key); + } + } +} diff --git a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOffPlayer.java b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOffPlayer.java index 04d80e9..864bcd5 100644 --- a/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOffPlayer.java +++ b/pandalib-players/src/main/java/fr/pandacube/lib/players/standalone/AbstractOffPlayer.java @@ -1,6 +1,7 @@ package fr.pandacube.lib.players.standalone; import java.util.UUID; +import java.util.function.UnaryOperator; /** * Represents any player, either offline or online. @@ -65,6 +66,24 @@ public interface AbstractOffPlayer { */ String getDisplayName(); + + + + + /* + * Player config + */ + + String getConfig(String key) throws Exception; + + String getConfig(String key, String deflt) throws Exception; + + void setConfig(String key, String value) throws Exception; + + void updateConfig(String key, String deflt, UnaryOperator updater) throws Exception; + + void unsetConfig(String key) throws Exception; +