Started upgrading player data setup

This commit is contained in:
Norbi Peti 2020-10-23 01:48:22 +02:00
parent 893b24ed5f
commit 83ecf7717f
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 71 additions and 193 deletions

View file

@ -35,7 +35,7 @@ public final class IHaveConfig {
* *
* @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null. * @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null.
*/ */
IHaveConfig(Runnable saveAction) { public IHaveConfig(Runnable saveAction) {
this.saveAction = saveAction; this.saveAction = saveAction;
} }

View file

@ -1,25 +0,0 @@
package buttondevteam.lib.player;
import buttondevteam.core.component.channel.Channel;
import org.bukkit.configuration.file.YamlConfiguration;
public class ChannelPlayerData { //I just want this to work
private final PlayerData<String> data;
private final Channel def;
public ChannelPlayerData(String name, YamlConfiguration yaml, Channel def) {
data = new PlayerData<>(name, yaml, "");
this.def = def;
}
public Channel get() {
String str = data.get();
if (str.isEmpty())
return def;
return Channel.getChannels().filter(c -> str.equals(c.ID)).findAny().orElse(def);
}
public void set(Channel value) {
data.set(value.ID);
}
}

View file

@ -3,7 +3,10 @@ package buttondevteam.lib.player;
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import com.google.common.collect.HashBiMap; import com.google.common.collect.HashBiMap;
import lombok.Getter;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -16,17 +19,32 @@ import java.util.HashMap;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
@ChromaGamerEnforcer @ChromaGamerEnforcer
public abstract class ChromaGamerBase implements AutoCloseable { public abstract class ChromaGamerBase {
public static final String TBMC_PLAYERS_DIR = "TBMC/players/"; private static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final HashBiMap<Class<? extends ChromaGamerBase>, String> playerTypes = HashBiMap.create(); private static final HashBiMap<Class<? extends ChromaGamerBase>, String> playerTypes = HashBiMap.create();
private static final HashMap<Class<? extends ChromaGamerBase>, Supplier<? extends ChromaGamerBase>> constructors = new HashMap<>();
private static final HashMap<Class<? extends ChromaGamerBase>, HashMap<String, ChromaGamerBase>> userCache = new HashMap<>();
private static final ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/** /**
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) * Use {@link #getConfig()} where possible; the 'id' must be always set
*/ */
public static void RegisterPluginUserClass(Class<? extends ChromaGamerBase> userclass) { protected YamlConfiguration plugindata;
@Getter
protected final IHaveConfig config = new IHaveConfig(this::save);
public void init() {
config.reset(plugindata);
}
/**
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) and to init the configs.
*/
public static <T extends ChromaGamerBase> void RegisterPluginUserClass(Class<T> userclass, Supplier<T> constructor) {
if (userclass.isAnnotationPresent(UserClass.class)) if (userclass.isAnnotationPresent(UserClass.class))
playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername()); playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername());
else if (userclass.isAnnotationPresent(AbstractUserClass.class)) else if (userclass.isAnnotationPresent(AbstractUserClass.class))
@ -34,6 +52,7 @@ public abstract class ChromaGamerBase implements AutoCloseable {
userclass.getAnnotation(AbstractUserClass.class).foldername()); userclass.getAnnotation(AbstractUserClass.class).foldername());
else // <-- Really important else // <-- Really important
throw new RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase"); throw new RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase");
constructors.put(userclass, constructor);
} }
/** /**
@ -61,19 +80,6 @@ public abstract class ChromaGamerBase implements AutoCloseable {
return playerTypes.inverse().get(foldername); return playerTypes.inverse().get(foldername);
} }
/**
* This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, use Discord IDs, etc.<br>
* <b>Does not include .yml</b>
*/
public final String getFileName() {
return plugindata.getString(getFolder() + "_id");
}
/**
* Use {@link #data(Object)} or {@link #data(String, Object)} where possible; the 'id' must be always set
*/
protected YamlConfiguration plugindata;
/*** /***
* Loads a user from disk and returns the user object. Make sure to use the subclasses' methods, where possible, like {@link TBMCPlayerBase#getPlayer(java.util.UUID, Class)} * Loads a user from disk and returns the user object. Make sure to use the subclasses' methods, where possible, like {@link TBMCPlayerBase#getPlayer(java.util.UUID, Class)}
* *
@ -82,21 +88,23 @@ public abstract class ChromaGamerBase implements AutoCloseable {
* @return The user object * @return The user object
*/ */
public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) { public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
try { HashMap<String, ? extends ChromaGamerBase> uc;
T obj = cl.newInstance(); if (userCache.containsKey(cl)) {
uc = userCache.get(cl);
if (uc.containsKey(fname))
//noinspection unchecked
return (T) uc.get(fname);
}
@SuppressWarnings("unchecked") T obj = (T) constructors.get(cl).get();
final String folder = getFolderForType(cl); final String folder = getFolderForType(cl);
final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml"); final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml");
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
obj.plugindata = YamlConfiguration.loadConfiguration(file); obj.plugindata = YamlConfiguration.loadConfiguration(file);
obj.plugindata.set(folder + "_id", fname); obj.plugindata.set(folder + "_id", fname);
obj.init();
userCache.computeIfAbsent(cl, key -> new HashMap<>()).put(fname, obj);
return obj; return obj;
} catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while loading a " + cl.getSimpleName() + "!", e, MainPlugin.Instance);
} }
return null;
}
private static ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/** /**
* Adds a converter to the start of the list. * Adds a converter to the start of the list.
@ -123,20 +131,12 @@ public abstract class ChromaGamerBase implements AutoCloseable {
} }
/** /**
* Saves the player. It'll pass all exceptions to the caller. To automatically handle the exception, use {@link #save()} instead. * Saves the player. It'll handle all exceptions that may happen.
*/ */
@Override private final void save() {
public void close() throws Exception { try {
if (plugindata.getKeys(false).size() > 0) if (plugindata.getKeys(false).size() > 0)
plugindata.save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml")); plugindata.save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
}
/**
* Saves the player. It'll handle all exceptions that may happen. To catch the exception, use {@link #close()} instead.
*/
public void save() {
try {
close();
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Error while saving player to " + getFolder() + "/" + getFileName() + ".yml!", e, MainPlugin.Instance); TBMCCoreAPI.SendException("Error while saving player to " + getFolder() + "/" + getFileName() + ".yml!", e, MainPlugin.Instance);
} }
@ -147,7 +147,7 @@ public abstract class ChromaGamerBase implements AutoCloseable {
* *
* @param user The account to connect with * @param user The account to connect with
*/ */
public <T extends ChromaGamerBase> void connectWith(T user) { public final <T extends ChromaGamerBase> void connectWith(T user) {
// Set the ID, go through all linked files and connect them as well // Set the ID, go through all linked files and connect them as well
if (!playerTypes.containsKey(getClass())) if (!playerTypes.containsKey(getClass()))
throw new RuntimeException("Class not registered as a user class! Use TBMCCoreAPI.RegisterUserClass"); throw new RuntimeException("Class not registered as a user class! Use TBMCCoreAPI.RegisterUserClass");
@ -166,14 +166,14 @@ public abstract class ChromaGamerBase implements AutoCloseable {
final String otherid = sourcedata.getString(entry.getValue() + "_id"); final String otherid = sourcedata.getString(entry.getValue() + "_id");
if (otherid == null) if (otherid == null)
continue; continue;
try (ChromaGamerBase cg = getUser(otherid, entry.getKey())) { ChromaGamerBase cg = getUser(otherid, entry.getKey());
cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs
for (val item : playerTypes.entrySet()) cg.config.signalChange();
if (sourcedata.contains(item.getValue() + "_id")) for (val item : playerTypes.entrySet()) {
if (sourcedata.contains(item.getValue() + "_id")) {
cg.plugindata.set(item.getValue() + "_id", sourcedata.getString(item.getValue() + "_id")); // Set all existing IDs cg.plugindata.set(item.getValue() + "_id", sourcedata.getString(item.getValue() + "_id")); // Set all existing IDs
} catch (Exception e) { cg.config.signalChange();
TBMCCoreAPI.SendException("Failed to update " + sourcefolder + " ID in player files for " + id }
+ " in folder with " + entry.getValue() + " id " + otherid + "!", e, MainPlugin.Instance);
} }
} }
}; };
@ -187,7 +187,7 @@ public abstract class ChromaGamerBase implements AutoCloseable {
* @param cl The player class to get the ID from * @param cl The player class to get the ID from
* @return The ID or null if not found * @return The ID or null if not found
*/ */
public <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) { public final <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return plugindata.getString(getFolderForType(cl) + "_id"); return plugindata.getString(getFolderForType(cl) + "_id");
} }
@ -200,7 +200,7 @@ public abstract class ChromaGamerBase implements AutoCloseable {
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @Nullable
public <T extends ChromaGamerBase> T getAs(Class<T> cl) { // TODO: Provide a way to use TBMCPlayerBase's loaded players public final <T extends ChromaGamerBase> T getAs(Class<T> cl) {
if (cl.getSimpleName().equals(getClass().getSimpleName())) if (cl.getSimpleName().equals(getClass().getSimpleName()))
return (T) this; return (T) this;
String newfolder = getFolderForType(cl); String newfolder = getFolderForType(cl);
@ -213,99 +213,25 @@ public abstract class ChromaGamerBase implements AutoCloseable {
return getUser(plugindata.getString(newfolder + "_id"), cl); return getUser(plugindata.getString(newfolder + "_id"), cl);
} }
public String getFolder() { /**
* This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, use Discord IDs, etc.<br>
* <b>Does not include .yml</b>
*/
public final String getFileName() {
return plugindata.getString(getFolder() + "_id");
}
public final String getFolder() {
return getFolderForType(getClass()); return getFolderForType(getClass());
} }
private void ThrowIfNoUser() {
if (!getClass().isAnnotationPresent(UserClass.class)
&& !getClass().isAnnotationPresent(AbstractUserClass.class))
throw new RuntimeException("Class not registered as a user class! Use @UserClass");
}
@SuppressWarnings("rawtypes")
private final HashMap<String, PlayerData> datamap = new HashMap<>();
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(String sectionname, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
@SuppressWarnings("rawtypes")
private final HashMap<String, EnumPlayerData> dataenummap = new HashMap<>();
private ChannelPlayerData datachannel;
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(String sectionname, Class<T> cl, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(Class<T> cl, T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Channel
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected ChannelPlayerData dataChannel(Channel def) { //TODO: Make interface with fromString() method and require use of that for player data types
ThrowIfNoUser();
if (datachannel == null)
datachannel = new ChannelPlayerData("channel", plugindata, def);
return datachannel;
}
/** /**
* Get player information. This method calls the {@link TBMCPlayerGetInfoEvent} to get all the player information across the TBMC plugins. * Get player information. This method calls the {@link TBMCPlayerGetInfoEvent} to get all the player information across the TBMC plugins.
* *
* @param target The {@link InfoTarget} to return the info for. * @param target The {@link InfoTarget} to return the info for.
* @return The player information. * @return The player information.
*/ */
public String getInfo(InfoTarget target) { public final String getInfo(InfoTarget target) {
TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target); TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target);
Bukkit.getServer().getPluginManager().callEvent(event); Bukkit.getServer().getPluginManager().callEvent(event);
return event.getResult(); return event.getResult();
@ -317,7 +243,6 @@ public abstract class ChromaGamerBase implements AutoCloseable {
//----------------------------------------------------------------- //-----------------------------------------------------------------
public ChannelPlayerData channel() { public final ConfigData<Channel> channel = getConfig().getData("channel", Channel.GlobalChat,
return dataChannel(Channel.GlobalChat); id -> Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID);
}
} }

View file

@ -1,26 +0,0 @@
package buttondevteam.lib.player;
import org.bukkit.configuration.file.YamlConfiguration;
public class EnumPlayerData<T extends Enum<T>> {
private final PlayerData<String> data;
private final Class<T> cl;
private final T def;
public EnumPlayerData(String name, YamlConfiguration yaml, Class<T> cl, T def) {
data = new PlayerData<String>(name, yaml, "");
this.cl = cl;
this.def = def;
}
public T get() {
String str = data.get();
if (str.isEmpty())
return def;
return Enum.valueOf(cl, str);
}
public void set(T value) {
data.set(value.toString());
}
}

View file

@ -23,6 +23,10 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase {
pluginname = getClass().getAnnotation(PlayerClass.class).pluginname(); pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
else else
throw new RuntimeException("Class not defined as player class! Use @PlayerClass"); throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
var section = plugindata.getConfigurationSection(pluginname);
if (section == null) section = plugindata.createSection(pluginname);
config.reset(section);
} }
public UUID getUUID() { public UUID getUUID() {