From 39424fa92b75615cad6e1a234c87c12ec372e3bd Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Sun, 16 Apr 2023 03:18:03 +0200 Subject: [PATCH] Fix and improve user class, convert player class And other stuff Reworked ChromaGamerBase.connectWith() Converted other stuff too --- .../java/buttondevteam/core/MainPlugin.kt | 6 +- .../java/buttondevteam/core/PlayerListener.kt | 8 +- .../java/buttondevteam/core/TestPrepare.java | 4 +- .../core/component/channel/Channel.kt | 7 +- .../component/channel/ChannelComponent.kt | 7 +- .../restart/PrimeRestartCommand.java | 4 +- .../component/restart/RestartComponent.java | 4 +- .../restart/ScheduledRestartCommand.java | 2 +- .../java/buttondevteam/lib/ChromaUtils.kt | 173 +++++++-------- .../java/buttondevteam/lib/TBMCCoreAPI.java | 2 +- .../lib/player/ChromaGamerBase.kt | 201 +++++++++--------- .../lib/player/CommonUserData.kt | 9 +- .../lib/player/StaticUserData.kt | 32 ++- .../lib/player/TBMCPlayerBase.kt | 132 +++++------- .../java/buttondevteam/lib/utils/TypedMap.kt | 29 +++ 15 files changed, 307 insertions(+), 313 deletions(-) create mode 100644 Chroma-Core/src/main/java/buttondevteam/lib/utils/TypedMap.kt diff --git a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt index 7628897..3acb223 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt @@ -92,11 +92,11 @@ class MainPlugin : ButtonPlugin() { } TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase::class.java) { TBMCPlayer() } TBMCChatAPI.RegisterChatChannel(Channel("§fg§f", Color.White, "g", null) - .also { Channel.GlobalChat = it }) //The /ooc ID has moved to the config + .also { Channel.globalChat = it }) //The /ooc ID has moved to the config TBMCChatAPI.RegisterChatChannel(Channel("§cADMIN§f", Color.Red, "a", Channel.inGroupFilter(null)) - .also { Channel.AdminChat = it }) + .also { Channel.adminChat = it }) TBMCChatAPI.RegisterChatChannel(Channel("§9MOD§f", Color.Blue, "mod", Channel.inGroupFilter("mod")) - .also { Channel.ModChat = it }) + .also { Channel.modChat = it }) TBMCChatAPI.RegisterChatChannel( Channel( "§6DEV§f", diff --git a/Chroma-Core/src/main/java/buttondevteam/core/PlayerListener.kt b/Chroma-Core/src/main/java/buttondevteam/core/PlayerListener.kt index ef37744..b27ca5b 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/PlayerListener.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/PlayerListener.kt @@ -26,13 +26,13 @@ class PlayerListener(val plugin: MainPlugin) : Listener { fun onPlayerJoin(event: PlayerJoinEvent) { val p = event.player val player = TBMCPlayerBase.getPlayer(p.uniqueId, TBMCPlayer::class.java) - val pname = player.PlayerName.get() + val pname = player.playerName.get() if (pname.isEmpty()) { - player.PlayerName.set(p.name) - plugin.logger.info("Player name saved: " + player.PlayerName.get()) + player.playerName.set(p.name) + plugin.logger.info("Player name saved: " + player.playerName.get()) } else if (p.name != pname) { plugin.logger.info(pname + " renamed to " + p.name) - player.PlayerName.set(p.name) + player.playerName.set(p.name) } } diff --git a/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java b/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java index 81f9b2b..c88cc44 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java @@ -22,7 +22,7 @@ import java.util.logging.Logger; public class TestPrepare { public static void PrepareServer() { - ChromaUtils.setTest(); //Needs to be in a separate class because of the potential lack of Mockito + ChromaUtils.setTest(true); //Needs to be in a separate class because of the potential lack of Mockito Bukkit.setServer(Mockito.mock(Server.class, new Answer() { @Override @@ -45,6 +45,6 @@ public class TestPrepare { } })); Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent()); - TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); + TBMCChatAPI.RegisterChatChannel(Channel.globalChat = new Channel("§fg§f", Color.White, "g", null)); } } diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/channel/Channel.kt b/Chroma-Core/src/main/java/buttondevteam/core/component/channel/Channel.kt index ee0dcdf..9ac5836 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/channel/Channel.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/channel/Channel.kt @@ -211,10 +211,9 @@ open class Channel } } - @JvmField - var GlobalChat: Channel? = null - var AdminChat: Channel? = null - var ModChat: Channel? = null + lateinit var globalChat: Channel + lateinit var adminChat: Channel + lateinit var modChat: Channel @JvmStatic fun registerChannel(channel: Channel) { diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/channel/ChannelComponent.kt b/Chroma-Core/src/main/java/buttondevteam/core/component/channel/ChannelComponent.kt index deea2e9..5981b37 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/channel/ChannelComponent.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/channel/ChannelComponent.kt @@ -1,6 +1,5 @@ package buttondevteam.core.component.channel -import buttondevteam.core.MainPlugin import buttondevteam.lib.ChromaUtils import buttondevteam.lib.TBMCSystemChatEvent.BroadcastTarget import buttondevteam.lib.architecture.Component @@ -12,7 +11,7 @@ import org.bukkit.plugin.java.JavaPlugin /** * Manages chat channels. If disabled, only global channels will be registered. */ -class ChannelComponent : Component() { +class ChannelComponent : Component() { override fun register(plugin: JavaPlugin) { super.register(plugin) roomJoinLeave = BroadcastTarget.add("roomJoinLeave") //Even if it's disabled, global channels continue to work @@ -27,7 +26,7 @@ class ChannelComponent : Component() { override fun enable() {} override fun disable() {} fun registerChannelCommand(channel: Channel) { - if (!ChromaUtils.isTest()) registerCommand(ChannelCommand(channel)) + if (!ChromaUtils.isTest) registerCommand(ChannelCommand(channel)) } @CommandClass @@ -51,7 +50,7 @@ class ChannelComponent : Component() { if (message == null) { val oldch = user.channel.get() if (oldch is ChatRoom) oldch.leaveRoom(sender) - if (oldch == channel) user.channel.set(Channel.GlobalChat) else { + if (oldch == channel) user.channel.set(Channel.globalChat) else { user.channel.set(channel) if (channel is ChatRoom) channel.joinRoom(sender) } diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java index 30c5d8e..f1bf288 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java @@ -27,12 +27,12 @@ public class PrimeRestartCommand extends ICommand2MC { if (Bukkit.getOnlinePlayers().size() > 0) { sender.sendMessage("§bPlayers online, restart delayed."); if (loud) - TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", component.getRestartBroadcast()); + TBMCChatAPI.SendSystemMessage(Channel.globalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", component.getRestartBroadcast()); plsrestart = true; } else { sender.sendMessage("§bNobody is online. Restarting now."); if (loud) - TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online. Restarting server.", component.getRestartBroadcast()); + TBMCChatAPI.SendSystemMessage(Channel.globalChat, Channel.RecipientTestResult.ALL, "§cNobody is online. Restarting server.", component.getRestartBroadcast()); Bukkit.spigot().restart(); } } diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/RestartComponent.java b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/RestartComponent.java index 3966af3..817c4c1 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/RestartComponent.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/RestartComponent.java @@ -74,12 +74,12 @@ public class RestartComponent extends Component implements Listener && !event.getQuitMessage().equalsIgnoreCase("Server is restarting")) { if (Bukkit.getOnlinePlayers().size() <= 1) { if (PrimeRestartCommand.isLoud()) - TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online anymore. Restarting.", restartBroadcast); + TBMCChatAPI.SendSystemMessage(Channel.globalChat, Channel.RecipientTestResult.ALL, "§cNobody is online anymore. Restarting.", restartBroadcast); Bukkit.spigot().restart(); } else if (!(event.getPlayer() instanceof IFakePlayer) && System.nanoTime() - 10 * 60 * 1000000000L - lasttime > 0) { //10 minutes passed since last reminder lasttime = System.nanoTime(); if (PrimeRestartCommand.isLoud()) - TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", restartBroadcast); + TBMCChatAPI.SendSystemMessage(Channel.globalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", restartBroadcast); } } } diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java index 6ccb05c..33d2864 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java @@ -58,7 +58,7 @@ public class ScheduledRestartCommand extends ICommand2MC { Bukkit.spigot().restart(); } if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size() > 0) - TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds!", component.getRestartBroadcast()); + TBMCChatAPI.SendSystemMessage(Channel.globalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds!", component.getRestartBroadcast()); restartbar.setProgress(restartCounter / (double) restarttime); restartbar.setTitle(String.format("Server restart in %.2f", restartCounter / 20f)); restartCounter--; diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt index da43f2d..58593fd 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.kt @@ -1,101 +1,90 @@ -package buttondevteam.lib; +package buttondevteam.lib -import buttondevteam.core.MainPlugin; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.event.Cancellable; -import org.bukkit.event.Event; +import buttondevteam.core.MainPlugin +import org.bukkit.Bukkit +import org.bukkit.command.CommandSender +import org.bukkit.entity.Player +import org.bukkit.event.Cancellable +import org.bukkit.event.Event +import java.util.function.Supplier -import java.util.function.Supplier; +object ChromaUtils { + fun getDisplayName(sender: CommandSender): String { + return when (sender) { + is IHaveFancyName -> sender.fancyName + is Player -> sender.displayName + else -> sender.name + } + } -public final class ChromaUtils { - private ChromaUtils() {} + fun getFullDisplayName(sender: CommandSender): String { + return when (sender) { + is IHaveFancyName -> sender.fancyFullName + else -> getDisplayName(sender) + } + } - public static String getDisplayName(CommandSender sender) { - if (sender instanceof IHaveFancyName) - return ((IHaveFancyName) sender).getFancyName(); - if (sender instanceof Player) - return ((Player) sender).getDisplayName(); - return sender.getName(); - } + fun convertNumber(number: Number, targetcl: Class): Number { + return when { + targetcl == Long::class.javaPrimitiveType || Long::class.java.isAssignableFrom(targetcl) -> number.toLong() + targetcl == Int::class.javaPrimitiveType || Int::class.java.isAssignableFrom(targetcl) -> number.toInt() //Needed because the parser can get longs + targetcl == Short::class.javaPrimitiveType || Short::class.java.isAssignableFrom(targetcl) -> number.toShort() + targetcl == Byte::class.javaPrimitiveType || Byte::class.java.isAssignableFrom(targetcl) -> number.toByte() + targetcl == Float::class.javaPrimitiveType || Float::class.java.isAssignableFrom(targetcl) -> number.toFloat() + targetcl == Double::class.javaPrimitiveType || Double::class.java.isAssignableFrom(targetcl) -> number.toDouble() + else -> number + } + } - public static String getFullDisplayName(CommandSender sender) { - if (sender instanceof IHaveFancyName) - return ((IHaveFancyName) sender).getFancyFullName(); - return getDisplayName(sender); - } + /** + * Calls the event always asynchronously. The return value is always false if async. + * + * @param event The event to call + * @return The event cancelled state or false if async. + */ + @JvmStatic + fun callEventAsync(event: T): Boolean where T : Event, T : Cancellable { + val task = Supplier { + Bukkit.getPluginManager().callEvent(event) + event.isCancelled + } + return doItAsync(task, false) + } - public interface IHaveFancyName { - /** - * May not be null. - * - * @return The name to be displayed in most places. - */ - String getFancyName(); + /** + * Does something always asynchronously. It will execute in the same thread if it's not the server thread. + * + * @param what What to do + * @param def Default if async + * @return The value supplied by the action or def if async. + */ + @JvmStatic + fun doItAsync(what: Supplier, def: T): T { + return if (Bukkit.isPrimaryThread()) { + Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.instance, Runnable { what.get() }) + def + } else what.get() + } - /** - * May return null. - * - * @return The full name that can be used to uniquely identify the user. - */ - String getFancyFullName(); - } + /** + * Returns true while unit testing. + */ + @JvmStatic + var isTest = false - public static Number convertNumber(Number number, Class targetcl) { - if (targetcl == long.class || Long.class.isAssignableFrom(targetcl)) - return number.longValue(); - else if (targetcl == int.class || Integer.class.isAssignableFrom(targetcl)) - return number.intValue(); //Needed because the parser can get longs - else if (targetcl == short.class || Short.class.isAssignableFrom(targetcl)) - return number.shortValue(); - else if (targetcl == byte.class || Byte.class.isAssignableFrom(targetcl)) - return number.byteValue(); - else if (targetcl == float.class || Float.class.isAssignableFrom(targetcl)) - return number.floatValue(); - else if (targetcl == double.class || Double.class.isAssignableFrom(targetcl)) - return number.doubleValue(); - return number; - } + interface IHaveFancyName { + /** + * May not be null. + * + * @return The name to be displayed in most places. + */ + val fancyName: String - /** - * Calls the event always asynchronously. The return value is always false if async. - * - * @param event The event to call - * @return The event cancelled state or false if async. - */ - public static boolean callEventAsync(T event) { - Supplier task = () -> { - Bukkit.getPluginManager().callEvent(event); - return event.isCancelled(); - }; - return doItAsync(task, false); - } - - /** - * Does something always asynchronously. It will execute in the same thread if it's not the server thread. - * - * @param what What to do - * @param def Default if async - * @return The value supplied by the action or def if async. - */ - public static T doItAsync(Supplier what, T def) { - if (Bukkit.isPrimaryThread()) - Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.instance, what::get); - else - return what.get(); - return def; - } - - private static boolean test = false; - - /** - * Returns true while unit testing. - */ - public static boolean isTest() { return test; } - - /** - * Call when unit testing. - */ - public static void setTest() { test = true; } -} + /** + * May return null. + * + * @return The full name that can be used to uniquely identify the user. + */ + val fancyFullName: String + } +} \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java b/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java index 8c8c05a..cf152ad 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java @@ -131,7 +131,7 @@ public class TBMCCoreAPI { } public static void RegisterUserClass(Class userclass, Supplier constructor) { - ChromaGamerBase.RegisterPluginUserClass(userclass, constructor); + ChromaGamerBase.registerPluginUserClass(userclass, constructor); } /** diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.kt b/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.kt index e209380..31e99ae 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.kt @@ -12,7 +12,6 @@ import org.bukkit.command.CommandSender import org.bukkit.configuration.file.YamlConfiguration import java.io.File import java.util.* -import java.util.function.Consumer import java.util.function.Function import java.util.function.Supplier @@ -20,30 +19,21 @@ import java.util.function.Supplier abstract class ChromaGamerBase { lateinit var config: IHaveConfig - @JvmField - protected var commonUserData: CommonUserData<*>? = null + protected lateinit var commonUserData: CommonUserData protected open fun init() { - config.reset(commonUserData!!.playerData) } - protected fun updateUserConfig() {} + protected fun updateUserConfig() {} // TODO: Use this instead of reset() /** * Saves the player. It'll handle all exceptions that may happen. Called automatically. */ protected open fun save() { try { - if (commonUserData!!.playerData.getKeys(false).size > 0) commonUserData!!.playerData.save( - File( - TBMC_PLAYERS_DIR + folder, fileName + ".yml" - ) - ) + if (commonUserData.playerData.getKeys(false).size > 0) + commonUserData.playerData.save(File(TBMC_PLAYERS_DIR + folder, "$fileName.yml")) } catch (e: Exception) { - TBMCCoreAPI.SendException( - "Error while saving player to " + folder + "/" + fileName + ".yml!", - e, - MainPlugin.instance - ) + TBMCCoreAPI.SendException("Error while saving player to $folder/$fileName.yml!", e, MainPlugin.instance) } } @@ -51,8 +41,11 @@ abstract class ChromaGamerBase { * Removes the user from the cache. This will be called automatically after some time by default. */ fun uncache() { - val userCache: HashMap, out ChromaGamerBase> = commonUserData!!.userCache - synchronized(userCache) { if (userCache.containsKey(javaClass)) check(userCache.remove(javaClass) === this) { "A different player instance was cached!" } } + val userCache = commonUserData.userCache + synchronized(userCache) { + if (userCache.containsKey(javaClass)) + check(userCache.remove(javaClass) === this) { "A different player instance was cached!" } + } } protected open fun scheduleUncache() { @@ -68,42 +61,33 @@ abstract class ChromaGamerBase { * * @param user The account to connect with */ - fun connectWith(user: T) { - // Set the ID, go through all linked files and connect them as well - val ownFolder = folder - val userFolder = user!!.folder - if (ownFolder.equals( - userFolder, - ignoreCase = true - ) - ) throw RuntimeException("Do not connect two accounts of the same type! Type: $ownFolder") - val ownData = commonUserData!!.playerData - val userData = user.commonUserData!!.playerData - userData[ownFolder + "_id"] = ownData.getString(ownFolder + "_id") - ownData[userFolder + "_id"] = userData.getString(userFolder + "_id") - config.signalChange() - user.config.signalChange() - val sync = Consumer { sourcedata: YamlConfiguration -> - val sourcefolder = if (sourcedata === ownData) ownFolder else userFolder - val id = sourcedata.getString(sourcefolder + "_id")!! - for ((key, value) in staticDataMap) { // Set our ID in all files we can find, both from our connections and the new ones - if (key == javaClass || key == user.javaClass) continue - val entryFolder = value.folder - val otherid = sourcedata.getString(entryFolder + "_id") ?: continue - val cg = getUser(otherid, key)!! - val cgData = cg.commonUserData!!.playerData - cgData[sourcefolder + "_id"] = id // Set new IDs - for ((_, value1) in staticDataMap) { - val itemFolder = value1.folder - if (sourcedata.contains(itemFolder + "_id")) { - cgData[itemFolder + "_id"] = sourcedata.getString(itemFolder + "_id") // Set all existing IDs + fun connectWith(user: T) { + synchronized(staticDataMap) { + // Set the ID, go through all linked files and connect them as well + if (folder.equals(user.folder, ignoreCase = true)) + throw RuntimeException("Do not connect two accounts of the same type! Type: $folder") + // This method acts from the point of view of the source, the target is the other way around + fun sync(us: ChromaGamerBase, them: ChromaGamerBase) { + val ourData = us.commonUserData.playerData + // Iterate through the target and their connections and set all our IDs in them + for (theirOtherClass in registeredClasses) { + // Skip our own class because we have the IDs + if (theirOtherClass == javaClass) continue + val theirConnected = them.getAs(theirOtherClass) ?: continue + val theirConnectedData = theirConnected.commonUserData.playerData + // Set new IDs + for (ourOtherFolder in registeredFolders) { + if (ourData.contains(ourOtherFolder + "_id")) { + theirConnectedData[ourOtherFolder + "_id"] = + ourData.getString(ourOtherFolder + "_id") // Set all existing IDs + } } + theirConnected.config.signalChange() } - cg.config.signalChange() } + sync(this, user) + sync(user, this) } - sync.accept(ownData) - sync.accept(userData) } /** @@ -112,8 +96,9 @@ abstract class ChromaGamerBase { * @param cl The player class to get the ID from * @return The ID or null if not found */ - fun getConnectedID(cl: Class): String { - return commonUserData!!.playerData.getString(getFolderForType(cl) + "_id")!! + fun getConnectedID(cl: Class): String? { + val data = staticDataMap[cl] ?: throw RuntimeException("Class $cl is not registered!") + return commonUserData.playerData.getString(data.folder + "_id") } /** @@ -123,17 +108,16 @@ abstract class ChromaGamerBase { * @param cl The target player class * @return The player as a [T] object or null if the user doesn't have an account there */ - fun getAs(cl: Class): T? { + fun getAs(cl: Class): T? { + @Suppress("UNCHECKED_CAST") if (cl.simpleName == javaClass.simpleName) return this as T val newfolder = getFolderForType(cl) - ?: throw RuntimeException("The specified class " + cl.simpleName + " isn't registered!") if (newfolder == folder) // If in the same folder, the same filename is used return getUser(fileName, cl) - val playerData = commonUserData!!.playerData - return if (!playerData.contains(newfolder + "_id")) null else getUser( - playerData.getString(newfolder + "_id")!!, - cl - ) + val playerData = commonUserData.playerData + return if (playerData.contains(newfolder + "_id")) + getUser(playerData.getString(newfolder + "_id")!!, cl) + else null } val fileName: String @@ -141,7 +125,7 @@ abstract class ChromaGamerBase { * This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, Discord IDs, etc.

* **Does not include .yml** */ - get() = commonUserData!!.playerData.getString(folder + "_id")!! + get() = commonUserData.playerData.getString(folder + "_id")!! val folder: String /** * This method returns the folder that this player data is stored in. For example: "minecraft". @@ -166,11 +150,11 @@ abstract class ChromaGamerBase { //----------------------------------------------------------------- @JvmField - val channel: ConfigData = config.getData("channel", Channel.GlobalChat, + val channel: ConfigData = config.getData("channel", Channel.globalChat, { id -> getChannels().filter { ch: Channel -> ch.identifier.equals(id as String, ignoreCase = true) } - .findAny().orElse(null) - }) { ch -> ch.ID } + .findAny().orElseThrow { RuntimeException("Channel $id not found!") } + }, { ch -> ch.identifier }) companion object { private const val TBMC_PLAYERS_DIR = "TBMC/players/" @@ -179,31 +163,31 @@ abstract class ChromaGamerBase { /** * Holds data per user class */ - private val staticDataMap = HashMap, StaticUserData<*>>() + private val staticDataMap = HashMap, StaticUserData>() /** * Used for connecting with every type of user ([.connectWith]) and to init the configs. * Also, to construct an instance if an abstract class is provided. */ @JvmStatic - fun RegisterPluginUserClass(userclass: Class, constructor: Supplier?) { - val cl: Class + fun registerPluginUserClass(userclass: Class, constructor: Supplier) { + val prototype: Class val folderName: String if (userclass.isAnnotationPresent(UserClass::class.java)) { - cl = userclass + prototype = userclass folderName = userclass.getAnnotation(UserClass::class.java).foldername } else if (userclass.isAnnotationPresent(AbstractUserClass::class.java)) { - val ucl: Class = userclass.getAnnotation( - AbstractUserClass::class.java - ).prototype - if (!userclass.isAssignableFrom(ucl)) throw RuntimeException("The prototype class (" + ucl.simpleName + ") must be a subclass of the userclass parameter (" + userclass.simpleName + ")!") - cl = ucl as Class + val ucl = userclass.getAnnotation(AbstractUserClass::class.java).prototype.java + if (!userclass.isAssignableFrom(ucl)) + throw RuntimeException("The prototype class (${ucl.simpleName}) must be a subclass of the userclass parameter (${userclass.simpleName})!") + @Suppress("UNCHECKED_CAST") + prototype = ucl as Class folderName = userclass.getAnnotation(AbstractUserClass::class.java).foldername } else throw RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase") val sud = StaticUserData(folderName) - sud.constructors[cl] = constructor - sud.constructors[userclass] = - constructor // Alawys register abstract and prototype class (TBMCPlayerBase and TBMCPlayer) + // Alawys register abstract and prototype class (TBMCPlayerBase and TBMCPlayer) + sud.constructors[prototype] = constructor + sud.constructors[userclass] = constructor staticDataMap[userclass] = sud } @@ -214,30 +198,42 @@ abstract class ChromaGamerBase { * @return The folder name for the given type * @throws RuntimeException If the class doesn't have the [UserClass] annotation. */ - fun getFolderForType(cl: Class): String { - if (cl.isAnnotationPresent(UserClass::class.java)) return cl.getAnnotation(UserClass::class.java).foldername else if (cl.isAnnotationPresent( - AbstractUserClass::class.java - ) - ) return cl.getAnnotation(AbstractUserClass::class.java).foldername + fun getFolderForType(cl: Class): String { + if (cl.isAnnotationPresent(UserClass::class.java)) + return cl.getAnnotation(UserClass::class.java).foldername + else if (cl.isAnnotationPresent(AbstractUserClass::class.java)) + return cl.getAnnotation(AbstractUserClass::class.java).foldername throw RuntimeException("Class not registered as a user class! Use @UserClass or @AbstractUserClass") } + private inline val registeredFolders: Iterable get() = staticDataMap.values.map { it.folder } + + private inline val registeredClasses get() = staticDataMap.keys + + /** + * Returns the (per-user) static data for the given player class. + * The static data is only stored once per user class so for example + * if you have two different [TBMCPlayerBase] classes, the static data is the same for both. + * + * @param cl The class to get the data from (like [TBMCPlayerBase] or one of its subclasses) + */ + @Suppress("UNCHECKED_CAST") + private fun getStaticData(cl: Class) = staticDataMap.entries + .filter { (key, _) -> key.isAssignableFrom(cl) } + .map { (_, value) -> value as StaticUserData } + .firstOrNull() + ?: throw RuntimeException("Class $cl not registered as a user class! Use registerUserClass()") + /** * Returns the player class for the given folder name. * * @param foldername The folder to get the class from (like "minecraft") * @return The type for the given folder name or null if not found */ - fun getTypeForFolder(foldername: String?): Class { + fun getTypeForFolder(foldername: String?): Class? { synchronized(staticDataMap) { - return staticDataMap.entries.stream() - .filter { (_, value): Map.Entry, StaticUserData<*>> -> - value.folder.equals( - foldername, - ignoreCase = true - ) - } - .map { (key, value) -> java.util.Map.Entry.key }.findAny().orElse(null) + return staticDataMap.filter { (_, value) -> value.folder.equals(foldername, ignoreCase = true) } + .map { (key, _) -> key }.singleOrNull() } } @@ -250,36 +246,32 @@ abstract class ChromaGamerBase { */ @JvmStatic @Synchronized - fun getUser(fname: String, cl: Class): T { - val staticUserData: StaticUserData<*> = staticDataMap.entries - .filter { (key, _) -> key.isAssignableFrom(cl) } - .map { (_, value) -> value } - .firstOrNull() - ?: throw RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass") + fun getUser(fname: String, cl: Class): T { + @Suppress("UNCHECKED_CAST") + val staticUserData: StaticUserData = getStaticData(cl) @Suppress("UNCHECKED_CAST") - val commonUserData: CommonUserData = (staticUserData.userDataMap[fname] + val commonUserData: CommonUserData = staticUserData.userDataMap[fname] ?: run { val folder = staticUserData.folder val file = File(TBMC_PLAYERS_DIR + folder, "$fname.yml") file.parentFile.mkdirs() val playerData = YamlConfiguration.loadConfiguration(file) playerData[staticUserData.folder + "_id"] = fname - CommonUserData(playerData) - }.also { staticUserData.userDataMap[fname] = it }) as CommonUserData + CommonUserData(playerData) + }.also { staticUserData.userDataMap[fname] = it } - return if (commonUserData.userCache.containsKey(cl)) commonUserData.userCache[cl] as T - else { + return commonUserData.userCache[cl] ?: run { val obj = createNewUser(cl, staticUserData, commonUserData) - commonUserData.userCache[cl] = obj + commonUserData.userCache.put(obj) obj } } - private fun createNewUser( + private fun createNewUser( cl: Class, - staticUserData: StaticUserData<*>, - commonUserData: CommonUserData<*> + staticUserData: StaticUserData, + commonUserData: CommonUserData ): T { @Suppress("UNCHECKED_CAST") val obj = staticUserData.constructors[cl]?.get() as T? ?: run { @@ -290,6 +282,7 @@ abstract class ChromaGamerBase { } } obj.commonUserData = commonUserData + obj.config = IHaveConfig({ obj.save() }, commonUserData.playerData) obj.init() obj.scheduleUncache() return obj @@ -300,7 +293,7 @@ abstract class ChromaGamerBase { * * @param converter The converter that returns an object corresponding to the sender or null, if it's not the right type. */ - fun addConverter(converter: Function>) { + fun addConverter(converter: Function>) { senderConverters.add(0, converter) } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/player/CommonUserData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/player/CommonUserData.kt index 53ff2bb..2a2ce7e 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/CommonUserData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/CommonUserData.kt @@ -1,13 +1,18 @@ package buttondevteam.lib.player +import buttondevteam.lib.utils.TypedMap import org.bukkit.configuration.file.YamlConfiguration /** * Per user, regardless of actual type * - * @param The user class, may be abstract + * @param T The user class, may be abstract */ class CommonUserData(@JvmField val playerData: YamlConfiguration) { + /** + * Caches users of the given user type. This is used to avoid loading the same user multiple times. + * Also contains instances of subclasses of the given user type. + */ @JvmField - val userCache: HashMap, out T> = HashMap() + val userCache: TypedMap = TypedMap() } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/player/StaticUserData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/player/StaticUserData.kt index 0d0a2bd..306f4a1 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/StaticUserData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/StaticUserData.kt @@ -1,23 +1,21 @@ -package buttondevteam.lib.player; +package buttondevteam.lib.player -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.HashMap; -import java.util.function.Supplier; +import java.util.function.Supplier /** * Per user class * - * @param The user class type, may be abstract + * @param T The user class type, may be abstract */ -@Getter -@RequiredArgsConstructor -public class StaticUserData { - private final HashMap, Supplier> constructors = new HashMap<>(); - /** - * Key: User ID - */ - private final HashMap> userDataMap = new HashMap<>(); - private final String folder; -} +class StaticUserData(val folder: String) { + /** + * Constructors for the subclasses of the given user type. + */ + val constructors = HashMap, Supplier>() + + /** + * Key: User ID + */ + val userDataMap = HashMap>() + +} \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.kt b/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.kt index 87da8f7..ad3cd3f 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.kt @@ -1,83 +1,65 @@ -package buttondevteam.lib.player; +package buttondevteam.lib.player -import buttondevteam.lib.architecture.ConfigData; -import buttondevteam.lib.architecture.IHaveConfig; -import lombok.Getter; -import org.bukkit.Bukkit; -import org.bukkit.OfflinePlayer; +import org.bukkit.Bukkit +import java.util.* -import java.util.Set; -import java.util.UUID; - -@AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer.class) +@AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer::class) @TBMCPlayerEnforcer -public abstract class TBMCPlayerBase extends ChromaGamerBase { - protected UUID uuid; +abstract class TBMCPlayerBase : ChromaGamerBase() { + val uniqueId: UUID get() = UUID.fromString(fileName) - @Getter - private final IHaveConfig config = new IHaveConfig(this::save); + @JvmField + val playerName = super.config.getData("PlayerName", "") + public override fun init() { + super.init() + val pluginName = if (javaClass.isAnnotationPresent(PlayerClass::class.java)) + javaClass.getAnnotation(PlayerClass::class.java).pluginname + else + throw RuntimeException("Class not defined as player class! Use @PlayerClass") + val playerData = commonUserData.playerData + var section = playerData.getConfigurationSection(pluginName) + if (section == null) section = playerData.createSection(pluginName) + config.reset(section) + } - public UUID getUUID() { - if (uuid == null) - uuid = UUID.fromString(getFileName()); - return uuid; - } + /** + * If the player is online, we don't uncache, it will be uncached when they log out + */ + override fun scheduleUncache() { + val p = Bukkit.getPlayer(uniqueId) + if (p == null || !p.isOnline) super.scheduleUncache() + } - public final ConfigData PlayerName = super.config.getData("PlayerName", ""); + override fun save() { + val keys = commonUserData.playerData.getKeys(false) + if (keys.size > 1) // PlayerName is always saved, but we don't need a file for just that + super.save() + } - /** - * Get player as a plugin player. - * - * @param uuid The UUID of the player to get - * @param cl The type of the player - * @return The requested player object - */ - public static T getPlayer(UUID uuid, Class cl) { - var player = ChromaGamerBase.getUser(uuid.toString(), cl); - if (!player.getUUID().equals(uuid)) //It will be set from the filename because we check it for scheduling the uncache. - throw new IllegalStateException("Player UUID differs after converting from and to string..."); - return player; - } + companion object { + /** + * Get player as a plugin player. + * + * @param uuid The UUID of the player to get + * @param cl The type of the player + * @return The requested player object + */ + fun getPlayer(uuid: UUID, cl: Class): T { + val player = getUser(uuid.toString(), cl) + check(player.uniqueId == uuid) { "Player UUID differs after converting from and to string..." } + return player + } - @Override - public void init() { - super.init(); - - String pluginname; - if (getClass().isAnnotationPresent(PlayerClass.class)) - pluginname = getClass().getAnnotation(PlayerClass.class).pluginname(); - else - throw new RuntimeException("Class not defined as player class! Use @PlayerClass"); - - var playerData = commonUserData.playerData; - var section = playerData.getConfigurationSection(pluginname); - if (section == null) section = playerData.createSection(pluginname); - config.reset(section); - } - - @Override - protected void scheduleUncache() { //Don't schedule it, it will happen on quit - if the player is online - var p = Bukkit.getPlayer(getUUID()); - if (p == null || !p.isOnline()) - super.scheduleUncache(); - } - - /** - * This method returns a TBMC player from their name. See {@link Bukkit#getOfflinePlayer(String)}. - * - * @param name The player's name - * @return The {@link TBMCPlayer} object for the player - */ - @SuppressWarnings("deprecation") - public static T getFromName(String name, Class cl) { - OfflinePlayer p = Bukkit.getOfflinePlayer(name); - return getPlayer(p.getUniqueId(), cl); - } - - @Override - protected void save() { - Set keys = commonUserData.playerData.getKeys(false); - if (keys.size() > 1) // PlayerName is always saved, but we don't need a file for just that - super.save(); - } -} + /** + * This method returns a TBMC player from their name. See [Bukkit.getOfflinePlayer]. + * + * @param name The player's name + * @return The [TBMCPlayer] object for the player + */ + @Suppress("deprecation") + fun getFromName(name: String, cl: Class): T { + val p = Bukkit.getOfflinePlayer(name) + return getPlayer(p.uniqueId, cl) + } + } +} \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/utils/TypedMap.kt b/Chroma-Core/src/main/java/buttondevteam/lib/utils/TypedMap.kt new file mode 100644 index 0000000..93c97e5 --- /dev/null +++ b/Chroma-Core/src/main/java/buttondevteam/lib/utils/TypedMap.kt @@ -0,0 +1,29 @@ +package buttondevteam.lib.utils + +import kotlin.reflect.KClass + +/** + * A map that can be used to store data of different types. The data is stored in a map, but the type is checked when getting + * the data. + * + * @param T The upper bound of the types that can be stored in this map + */ +class TypedMap { + private val map = HashMap, T>() + fun put(value: S) { + map[value::class] = value + } + + operator fun get(type: Class): S? { + @Suppress("UNCHECKED_CAST") + return map[type.kotlin] as S? + } + + fun containsKey(type: Class): Boolean { + return map.containsKey(type.kotlin) + } + + fun remove(type: Class): T? { + return map.remove(type.kotlin) + } +} \ No newline at end of file