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.
*/
IHaveConfig(Runnable saveAction) {
public IHaveConfig(Runnable 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.component.channel.Channel;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import com.google.common.collect.HashBiMap;
import lombok.Getter;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -16,17 +19,32 @@ import java.util.HashMap;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ChromaGamerEnforcer
public abstract class ChromaGamerBase implements AutoCloseable {
public static final String TBMC_PLAYERS_DIR = "TBMC/players/";
public abstract class ChromaGamerBase {
private static final String TBMC_PLAYERS_DIR = "TBMC/players/";
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))
playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername());
else if (userclass.isAnnotationPresent(AbstractUserClass.class))
@ -34,6 +52,7 @@ public abstract class ChromaGamerBase implements AutoCloseable {
userclass.getAnnotation(AbstractUserClass.class).foldername());
else // <-- Really important
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);
}
/**
* 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)}
*
@ -82,21 +88,23 @@ public abstract class ChromaGamerBase implements AutoCloseable {
* @return The user object
*/
public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
try {
T obj = cl.newInstance();
HashMap<String, ? extends ChromaGamerBase> uc;
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 File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml");
file.getParentFile().mkdirs();
obj.plugindata = YamlConfiguration.loadConfiguration(file);
obj.plugindata.set(folder + "_id", fname);
obj.init();
userCache.computeIfAbsent(cl, key -> new HashMap<>()).put(fname, 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.
@ -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
public void close() throws Exception {
private final void save() {
try {
if (plugindata.getKeys(false).size() > 0)
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) {
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
*/
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
if (!playerTypes.containsKey(getClass()))
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");
if (otherid == null)
continue;
try (ChromaGamerBase cg = getUser(otherid, entry.getKey())) {
ChromaGamerBase cg = getUser(otherid, entry.getKey());
cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs
for (val item : playerTypes.entrySet())
if (sourcedata.contains(item.getValue() + "_id"))
cg.config.signalChange();
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
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to update " + sourcefolder + " ID in player files for " + id
+ " in folder with " + entry.getValue() + " id " + otherid + "!", e, MainPlugin.Instance);
cg.config.signalChange();
}
}
}
};
@ -187,7 +187,7 @@ public abstract class ChromaGamerBase implements AutoCloseable {
* @param cl The player class to get the ID from
* @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");
}
@ -200,7 +200,7 @@ public abstract class ChromaGamerBase implements AutoCloseable {
*/
@SuppressWarnings("unchecked")
@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()))
return (T) this;
String newfolder = getFolderForType(cl);
@ -213,99 +213,25 @@ public abstract class ChromaGamerBase implements AutoCloseable {
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());
}
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.
*
* @param target The {@link InfoTarget} to return the info for.
* @return The player information.
*/
public String getInfo(InfoTarget target) {
public final String getInfo(InfoTarget target) {
TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target);
Bukkit.getServer().getPluginManager().callEvent(event);
return event.getResult();
@ -317,7 +243,6 @@ public abstract class ChromaGamerBase implements AutoCloseable {
//-----------------------------------------------------------------
public ChannelPlayerData channel() {
return dataChannel(Channel.GlobalChat);
}
public final ConfigData<Channel> channel = getConfig().getData("channel", 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();
else
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() {