From 4ed001cb545a7cbde2a6adb4db433a3931bd336a Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Fri, 14 Apr 2023 03:07:25 +0200 Subject: [PATCH] More conversions, user classes, some configs Removed writePluginList config option --- Chroma-Core/pom.xml | 2 +- .../java/buttondevteam/core/MainPlugin.kt | 56 +- .../component/channel/ChannelComponent.kt | 130 ++-- .../core/component/members/MemberCommand.java | 2 +- .../restart/ScheduledRestartCommand.java | 2 +- .../java/buttondevteam/lib/ChromaUtils.java | 2 +- .../java/buttondevteam/lib/TBMCCoreAPI.java | 4 +- .../lib/architecture/ConfigData.kt | 8 +- .../lib/architecture/IHaveConfig.kt | 4 +- .../lib/architecture/ListConfigData.kt | 2 +- .../java/buttondevteam/lib/chat/Command2.kt | 6 +- .../java/buttondevteam/lib/chat/Command2MC.kt | 24 +- .../commands/CommandArgumentHelpManager.kt | 10 +- .../lib/player/ChromaGamerBase.kt | 594 +++++++++--------- .../lib/player/CommonUserData.kt | 20 +- .../lib/player/TBMCPlayerBase.java | 4 +- 16 files changed, 423 insertions(+), 447 deletions(-) diff --git a/Chroma-Core/pom.xml b/Chroma-Core/pom.xml index 219be89..104f341 100755 --- a/Chroma-Core/pom.xml +++ b/Chroma-Core/pom.xml @@ -248,7 +248,7 @@ github UTF-8 1.0.1 - 1.8.10 + 1.8.20 https://github.com/TBMCPlugins/mvn-repo diff --git a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt index a830b9f..7628897 100755 --- a/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt +++ b/Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.kt @@ -26,17 +26,10 @@ import org.bukkit.command.Command import org.bukkit.command.CommandSender import org.bukkit.command.ConsoleCommandSender import org.bukkit.entity.Player -import org.bukkit.plugin.Plugin -import java.io.File -import java.io.IOException -import java.nio.file.Files import java.util.* -import java.util.function.Function import java.util.function.Supplier -import java.util.logging.Logger class MainPlugin : ButtonPlugin() { - private var logger: Logger? = null private var economy: Economy? = null /** @@ -45,12 +38,6 @@ class MainPlugin : ButtonPlugin() { */ var isChatHandlerEnabled = true - /** - * Sets whether the plugin should write a list of installed plugins in a txt file. - * It can be useful if some other software needs to know the plugins. - */ - private val writePluginList = iConfig.getData("writePluginList", false) - /** * The chat format to use for messages from other platforms if Chroma-Chat is not installed. */ @@ -68,12 +55,11 @@ class MainPlugin : ButtonPlugin() { */ val prioritizeCustomCommands = iConfig.getData("prioritizeCustomCommands", false) public override fun pluginEnable() { - Instance = this + instance = this val pdf = description - logger = getLogger() if (!setupPermissions()) throw NullPointerException("No permission plugin found!") if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials - getLogger().warning("No economy plugin found! Components using economy will not be registered.") + logger.warning("No economy plugin found! Components using economy will not be registered.") saveConfig() registerComponent(this, RestartComponent()) registerComponent(this, ChannelComponent()) @@ -125,31 +111,20 @@ class MainPlugin : ButtonPlugin() { TBMCChatAPI.RegisterChatChannel(ChatRoom("§aGREEN§f", Color.Green, "green")) TBMCChatAPI.RegisterChatChannel(ChatRoom("§bBLUE§f", Color.Blue, "blue")) TBMCChatAPI.RegisterChatChannel(ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple")) - val playerSupplier = Supplier { Bukkit.getOnlinePlayers().map { obj: Player -> obj.name }.asIterable() } - command2MC.addParamConverter(OfflinePlayer::class.java, { name: String? -> - Bukkit.getOfflinePlayer( - name!! - ) - }, "Player not found!", playerSupplier) - command2MC.addParamConverter( - Player::class.java, Function { name: String -> - Bukkit.getPlayer(name) - }, "Online player not found!", playerSupplier + val playerSupplier = Supplier { Bukkit.getOnlinePlayers().map { obj -> obj.name }.asIterable() } + command2MC.addParamConverter( + OfflinePlayer::class.java, + { name -> Bukkit.getOfflinePlayer(name) }, + "Player not found!", + playerSupplier ) - if (writePluginList.get()) { - try { - Files.write(File("plugins", "plugins.txt").toPath(), Iterable { - Arrays.stream(Bukkit.getPluginManager().plugins) - .map { p: Plugin -> p.dataFolder.name as CharSequence } - .iterator() - }) - } catch (e: IOException) { - TBMCCoreAPI.SendException("Failed to write plugin list!", e, this) - } - } - if (server.pluginManager.isPluginEnabled("Essentials")) ess = getPlugin( - Essentials::class.java + command2MC.addParamConverter( + Player::class.java, + { name -> Bukkit.getPlayer(name) }, + "Online player not found!", + playerSupplier ) + if (server.pluginManager.isPluginEnabled("Essentials")) ess = getPlugin(Essentials::class.java) logger!!.info(pdf.name + " has been Enabled (V." + pdf.version + ") Test: " + test.get() + ".") } @@ -182,8 +157,7 @@ class MainPlugin : ButtonPlugin() { } companion object { - @JvmField - var Instance: MainPlugin = null + lateinit var instance: MainPlugin @JvmField var permission: Permission? = null 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 7735410..deea2e9 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,83 +1,69 @@ -package buttondevteam.core.component.channel; +package buttondevteam.core.component.channel -import buttondevteam.lib.ChromaUtils; -import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.chat.*; -import buttondevteam.lib.player.ChromaGamerBase; -import lombok.RequiredArgsConstructor; -import org.bukkit.plugin.java.JavaPlugin; +import buttondevteam.core.MainPlugin +import buttondevteam.lib.ChromaUtils +import buttondevteam.lib.TBMCSystemChatEvent.BroadcastTarget +import buttondevteam.lib.architecture.Component +import buttondevteam.lib.chat.* +import buttondevteam.lib.chat.Command2.* +import buttondevteam.lib.player.ChromaGamerBase +import org.bukkit.plugin.java.JavaPlugin /** * Manages chat channels. If disabled, only global channels will be registered. */ -public class ChannelComponent extends Component { - static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave; +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 + } - @Override - protected void register(JavaPlugin plugin) { - super.register(plugin); - roomJoinLeave = TBMCSystemChatEvent.BroadcastTarget.add("roomJoinLeave"); //Even if it's disabled, global channels continue to work - } + override fun unregister(plugin: JavaPlugin) { + super.unregister(plugin) + BroadcastTarget.remove(roomJoinLeave) + roomJoinLeave = null + } - @Override - protected void unregister(JavaPlugin plugin) { - super.unregister(plugin); - TBMCSystemChatEvent.BroadcastTarget.remove(roomJoinLeave); - roomJoinLeave = null; - } + override fun enable() {} + override fun disable() {} + fun registerChannelCommand(channel: Channel) { + if (!ChromaUtils.isTest()) registerCommand(ChannelCommand(channel)) + } - @Override - protected void enable() { - } + @CommandClass + private class ChannelCommand(private val channel: Channel) : ICommand2MC() { + override fun getCommandPath(): String { + return channel.identifier + } - @Override - protected void disable() { - } + override fun getCommandPaths(): Array { + return channel.extraIdentifiers.get().toTypedArray() + } - void registerChannelCommand(Channel channel) { - if (!ChromaUtils.isTest()) - registerCommand(new ChannelCommand(channel)); - } + @Subcommand + fun def(senderMC: Command2MCSender, @OptionalArg @TextArg message: String?) { + val sender = senderMC.sender + val user = ChromaGamerBase.getFromSender(sender) + if (user == null) { + sender.sendMessage("§cYou can't use channels from this platform.") + return + } + if (message == null) { + val oldch = user.channel.get() + if (oldch is ChatRoom) oldch.leaveRoom(sender) + if (oldch == channel) user.channel.set(Channel.GlobalChat) else { + user.channel.set(channel) + if (channel is ChatRoom) channel.joinRoom(sender) + } + sender.sendMessage("§6You are now talking in: §b" + user.channel.get().displayName.get()) + } else TBMCChatAPI.SendChatMessage( + ChatMessage.builder(sender, user, message).fromCommand(true) + .permCheck(senderMC.permCheck).build(), channel + ) + } + } - @CommandClass - @RequiredArgsConstructor - private static class ChannelCommand extends ICommand2MC { - private final Channel channel; - - @Override - public String getCommandPath() { - return channel.identifier; - } - - @Override - public String[] getCommandPaths() { - return channel.extraIdentifiers.get(); - } - - @Command2.Subcommand - public void def(Command2MCSender senderMC, @Command2.OptionalArg @Command2.TextArg String message) { - var sender = senderMC.getSender(); - var user = ChromaGamerBase.getFromSender(sender); - if (user == null) { - sender.sendMessage("§cYou can't use channels from this platform."); - return; - } - if (message == null) { - Channel oldch = user.channel.get(); - if (oldch instanceof ChatRoom) - ((ChatRoom) oldch).leaveRoom(sender); - if (oldch.equals(channel)) - user.channel.set(Channel.GlobalChat); - else { - user.channel.set(channel); - if (channel instanceof ChatRoom) - ((ChatRoom) channel).joinRoom(sender); - } - sender.sendMessage("§6You are now talking in: §b" + user.channel.get().displayName.get()); - } else - TBMCChatAPI.SendChatMessage(ChatMessage.builder(sender, user, message).fromCommand(true) - .permCheck(senderMC.getPermCheck()).build(), channel); - } - } -} + companion object { + var roomJoinLeave: BroadcastTarget? = null + } +} \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java b/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java index 1d7fd87..318a52c 100644 --- a/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java +++ b/Chroma-Core/src/main/java/buttondevteam/core/component/members/MemberCommand.java @@ -33,7 +33,7 @@ public class MemberCommand extends ICommand2MC { } public boolean addRemove(CommandSender sender, OfflinePlayer op, boolean add) { - Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> { + Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.instance, () -> { if (!op.hasPlayedBefore()) { sender.sendMessage("§cCannot find player or haven't played before."); return; 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 e798091..6ccb05c 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 @@ -51,7 +51,7 @@ public class ScheduledRestartCommand extends ICommand2MC { sender.sendMessage("Scheduled restart in " + seconds); ScheduledServerRestartEvent e = new ScheduledServerRestartEvent(restarttime, this); Bukkit.getPluginManager().callEvent(e); - restarttask = Bukkit.getScheduler().runTaskTimer(MainPlugin.Instance, () -> { + restarttask = Bukkit.getScheduler().runTaskTimer(MainPlugin.instance, () -> { if (restartCounter < 0) { restarttask.cancel(); restartbar.getPlayers().forEach(p -> restartbar.removePlayer(p)); diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.java b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.java index ef818c9..da43f2d 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.java @@ -81,7 +81,7 @@ public final class ChromaUtils { */ public static T doItAsync(Supplier what, T def) { if (Bukkit.isPrimaryThread()) - Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, what::get); + Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.instance, what::get); else return what.get(); return def; diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java b/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java index aa83020..8c8c05a 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java @@ -170,7 +170,7 @@ public class TBMCCoreAPI { } public static boolean IsTestServer() { - if (MainPlugin.Instance == null) return true; - return MainPlugin.Instance.test.get(); + if (MainPlugin.instance == null) return true; + return MainPlugin.instance.test.get(); } } \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt index b2c3006..d9d871f 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ConfigData.kt @@ -67,9 +67,9 @@ class ConfigData internal constructor( return getter.apply(convert(`val`, pdef)).also { value = it } } - override fun set(value: T?) { + override fun set(value: T?) { // TODO: Have a separate method for removing the value from the config and make this non-nullable if (readOnly) return //Safety for Discord channel/role data - val `val` = value?.let { setter.apply(value) } + val `val` = value?.let { setter.apply(it) } setInternal(`val`) this.value = value } @@ -89,14 +89,14 @@ class ConfigData internal constructor( val sa = config.saveAction val root = cc.root if (root == null) { - MainPlugin.Instance.logger.warning("Attempted to save config with no root! Name: ${config.config.name}") + MainPlugin.instance.logger.warning("Attempted to save config with no root! Name: ${config.config.name}") return } if (!saveTasks.containsKey(cc.root)) { synchronized(saveTasks) { saveTasks.put( root, - SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, { + SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.instance, { synchronized(saveTasks) { saveTasks.remove(root) sa.run() diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt index 7ef3f09..eaa767d 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/IHaveConfig.kt @@ -179,7 +179,7 @@ class IHaveConfig( .filter(Predicate> { obj: ConfigData? -> Objects.nonNull(obj) }) .collect(Collectors.toList()) } else { - if (TBMCCoreAPI.IsTestServer()) MainPlugin.Instance.logger.warning( + if (TBMCCoreAPI.IsTestServer()) MainPlugin.instance.logger.warning( "Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString( m.parameterTypes ) @@ -187,7 +187,7 @@ class IHaveConfig( continue } for (c in configList) { - if (c.path.length == 0) c.setPath(mName) else if (c.path != mName) MainPlugin.Instance.logger.warning( + if (c.path.length == 0) c.setPath(mName) else if (c.path != mName) MainPlugin.instance.logger.warning( "Config name does not match: " + c.path + " instead of " + mName ) c.get() //Saves the default value if needed - also checks validity diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt index 41860fa..e17e009 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/architecture/ListConfigData.kt @@ -23,7 +23,7 @@ class ListConfigData internal constructor( listConfig.reset() } - override fun get(): List? { + override fun get(): List { return listConfig.get() } diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt index 91e42ce..b9a8f75 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.kt @@ -94,7 +94,7 @@ abstract class Command2, TP : Command2Sender>( return false // Unknown command } //Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread - Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance) { _ -> + Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.instance) { _ -> try { dispatcher.execute(results) } catch (e: CommandSyntaxException) { @@ -103,7 +103,7 @@ abstract class Command2, TP : Command2Sender>( TBMCCoreAPI.SendException( "Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline, e, - MainPlugin.Instance + MainPlugin.instance ) } } @@ -140,7 +140,7 @@ abstract class Command2, TP : Command2Sender>( lastNode.addChild(getExecutableNode(meth, command, ann, remainingPath, CommandArgumentHelpManager(command), fullPath)) if (mainCommandNode == null) mainCommandNode = mainNode else if (mainNode!!.name != mainCommandNode.name) { - MainPlugin.Instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName) + MainPlugin.instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName) } } if (mainCommandNode == null) { diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt index 08289ee..98a3508 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.kt @@ -170,8 +170,9 @@ class Command2MC : Command2('/', true), Listener val i = commandline.indexOf(' ') val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash //Our commands aren't PluginCommands, unless it's specified in the plugin.yml - return if ((!checkPlugin || (MainPlugin.Instance.prioritizeCustomCommands.get() == true)) - || Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false) + return if ((!checkPlugin || (MainPlugin.instance.prioritizeCustomCommands.get() == true)) + || Bukkit.getPluginCommand(mainpath)?.let { it.plugin is ButtonPlugin } != false + ) super.handleCommand(sender, commandline) else false } @@ -207,7 +208,11 @@ class Command2MC : Command2('/', true), Listener private fun executeCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean { val user = ChromaGamerBase.getFromSender(sender) if (user == null) { - TBMCCoreAPI.SendException("Failed to run Bukkit command for user!", Throwable("No Chroma user found"), MainPlugin.Instance) + TBMCCoreAPI.SendException( + "Failed to run Bukkit command for user!", + Throwable("No Chroma user found"), + MainPlugin.instance + ) sender.sendMessage("§cAn internal error occurred.") return true } @@ -250,9 +255,16 @@ class Command2MC : Command2('/', true), Listener private fun registerTabcomplete(command2MC: ICommand2MC, commandNode: LiteralCommandNode, bukkitCommand: Command) { if (commodore == null) { - commodore = CommodoreProvider.getCommodore(MainPlugin.Instance) //Register all to the Core, it's easier - commodore.register(LiteralArgumentBuilder.literal("un").redirect(RequiredArgumentBuilder.argument("unsomething", - StringArgumentType.word()).suggests { context: CommandContext?, builder: SuggestionsBuilder -> builder.suggest("untest").buildFuture() }.build())) + commodore = CommodoreProvider.getCommodore(MainPlugin.instance) //Register all to the Core, it's easier + commodore.register(LiteralArgumentBuilder.literal("un") + .redirect(RequiredArgumentBuilder.argument( + "unsomething", + StringArgumentType.word() + ).suggests { context: CommandContext?, builder: SuggestionsBuilder -> + builder.suggest("untest").buildFuture() + }.build() + ) + ) } commodore!!.dispatcher.root.getChild(commandNode.name) // TODO: Probably unnecessary val customTCmethods = Arrays.stream(command2MC.javaClass.declaredMethods) //val doesn't recognize the type arguments diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt index f59ff29..74381d2 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/chat/commands/CommandArgumentHelpManager.kt @@ -32,18 +32,18 @@ class CommandArgumentHelpManager, TP : Command2Sender>(comman TBMCCoreAPI.SendException( "Error while getting command data!", Exception("Resource not found!"), - MainPlugin.Instance + MainPlugin.instance ) return@use } val config = YamlConfiguration.loadConfiguration(InputStreamReader(str)) commandConfig = config.getConfigurationSection(commandClass.canonicalName.replace('$', '.')) if (commandConfig == null) { - MainPlugin.Instance.logger.warning("Failed to get command data for $commandClass! Make sure to use 'clean install' when building the project.") + MainPlugin.instance.logger.warning("Failed to get command data for $commandClass! Make sure to use 'clean install' when building the project.") } } } catch (e: IOException) { - TBMCCoreAPI.SendException("Error while getting command data!", e, MainPlugin.Instance) + TBMCCoreAPI.SendException("Error while getting command data!", e, MainPlugin.instance) } } @@ -56,7 +56,7 @@ class CommandArgumentHelpManager, TP : Command2Sender>(comman fun getParameterHelpForMethod(method: Method): String? { val cs = commandConfig?.getConfigurationSection(method.name) if (cs == null) { - MainPlugin.Instance.logger.warning("Failed to get command data for $method! Make sure to use 'clean install' when building the project.") + MainPlugin.instance.logger.warning("Failed to get command data for $method! Make sure to use 'clean install' when building the project.") return null } val mname = cs.getString("method") @@ -68,7 +68,7 @@ class CommandArgumentHelpManager, TP : Command2Sender>(comman } else TBMCCoreAPI.SendException( "Error while getting command data for $method!", Exception("Method '$method' != $mname or params is $params"), - MainPlugin.Instance + MainPlugin.instance ) return null } 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 bb523d0..e209380 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.kt @@ -1,317 +1,327 @@ -package buttondevteam.lib.player; +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 lombok.Getter; -import lombok.val; -import org.bukkit.Bukkit; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.YamlConfiguration; - -import javax.annotation.Nullable; -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; +import buttondevteam.core.MainPlugin +import buttondevteam.core.component.channel.Channel +import buttondevteam.core.component.channel.Channel.Companion.getChannels +import buttondevteam.lib.TBMCCoreAPI +import buttondevteam.lib.architecture.ConfigData +import buttondevteam.lib.architecture.ConfigData.Companion.saveNow +import buttondevteam.lib.architecture.IHaveConfig +import org.bukkit.Bukkit +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 @ChromaGamerEnforcer -public abstract class ChromaGamerBase { - private static final String TBMC_PLAYERS_DIR = "TBMC/players/"; - private static final ArrayList>> senderConverters = new ArrayList<>(); - /** - * Holds data per user class - */ - private static final HashMap, StaticUserData> staticDataMap = new HashMap<>(); +abstract class ChromaGamerBase { + lateinit var config: IHaveConfig - /** - * Use {@link #getConfig()} where possible; the 'id' must be always set - */ - //protected YamlConfiguration plugindata; + @JvmField + protected var commonUserData: CommonUserData<*>? = null + protected open fun init() { + config.reset(commonUserData!!.playerData) + } - @Getter - protected final IHaveConfig config = new IHaveConfig(this::save); - protected CommonUserData commonUserData; + protected fun updateUserConfig() {} - /** - * Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) and to init the configs. - * Also, to construct an instance if an abstract class is provided. - */ - public static void RegisterPluginUserClass(Class userclass, Supplier constructor) { - Class cl; - String folderName; - if (userclass.isAnnotationPresent(UserClass.class)) { - cl = userclass; - folderName = userclass.getAnnotation(UserClass.class).foldername(); - } else if (userclass.isAnnotationPresent(AbstractUserClass.class)) { - var ucl = userclass.getAnnotation(AbstractUserClass.class).prototype(); - if (!userclass.isAssignableFrom(ucl)) - throw new RuntimeException("The prototype class (" + ucl.getSimpleName() + ") must be a subclass of the userclass parameter (" + userclass.getSimpleName() + ")!"); - //noinspection unchecked - cl = (Class) ucl; - folderName = userclass.getAnnotation(AbstractUserClass.class).foldername(); - } else // <-- Really important - throw new RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase"); - var sud = new StaticUserData(folderName); - sud.getConstructors().put(cl, constructor); - sud.getConstructors().put(userclass, constructor); // Alawys register abstract and prototype class (TBMCPlayerBase and TBMCPlayer) - staticDataMap.put(userclass, sud); - } + /** + * 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" + ) + ) + } catch (e: Exception) { + TBMCCoreAPI.SendException( + "Error while saving player to " + folder + "/" + fileName + ".yml!", + e, + MainPlugin.instance + ) + } + } - /** - * Returns the folder name for the given player class. - * - * @param cl The class to get the folder from (like {@link TBMCPlayerBase} or one of it's subclasses) - * @return The folder name for the given type - * @throws RuntimeException If the class doesn't have the {@link UserClass} annotation. - */ - public static String getFolderForType(Class cl) { - if (cl.isAnnotationPresent(UserClass.class)) - return cl.getAnnotation(UserClass.class).foldername(); - else if (cl.isAnnotationPresent(AbstractUserClass.class)) - return cl.getAnnotation(AbstractUserClass.class).foldername(); - throw new RuntimeException("Class not registered as a user class! Use @UserClass or @AbstractUserClass"); - } + /** + * 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!" } } + } - /** - * 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 - */ - public static Class getTypeForFolder(String foldername) { - synchronized (staticDataMap) { - return staticDataMap.entrySet().stream().filter(e -> e.getValue().getFolder().equalsIgnoreCase(foldername)) - .map(Map.Entry::getKey).findAny().orElse(null); - } - } + protected open fun scheduleUncache() { + Bukkit.getScheduler().runTaskLaterAsynchronously( + MainPlugin.instance, + Runnable { uncache() }, + (2 * 60 * 60 * 20).toLong() + ) //2 hours + } - /*** - * Retrieves a user from cache or loads it from disk. - * - * @param fname Filename without .yml, the user's identifier for that type - * @param cl User class - * @return The user object - */ - public static synchronized T getUser(String fname, Class cl) { - StaticUserData staticUserData = null; - for (var sud : staticDataMap.entrySet()) { - if (sud.getKey().isAssignableFrom(cl)) { - staticUserData = sud.getValue(); - break; - } - } - if (staticUserData == null) - throw new RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass"); - var commonUserData = staticUserData.getUserDataMap().get(fname); - if (commonUserData == null) { - final String folder = staticUserData.getFolder(); - final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml"); - file.getParentFile().mkdirs(); - var playerData = YamlConfiguration.loadConfiguration(file); - commonUserData = new CommonUserData<>(playerData); - playerData.set(staticUserData.getFolder() + "_id", fname); - staticUserData.getUserDataMap().put(fname, commonUserData); - } - if (commonUserData.getUserCache().containsKey(cl)) - return (T) commonUserData.getUserCache().get(cl); - T obj; - if (staticUserData.getConstructors().containsKey(cl)) - //noinspection unchecked - obj = (T) staticUserData.getConstructors().get(cl).get(); - else { - try { - obj = cl.getConstructor().newInstance(); - } catch (Exception e) { - throw new RuntimeException("Failed to create new instance of user of type " + cl.getSimpleName() + "!", e); - } - } - obj.commonUserData = commonUserData; - obj.init(); - obj.scheduleUncache(); - return obj; - } + /** + * Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set. + * + * @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 + } + } + cg.config.signalChange() + } + } + sync.accept(ownData) + sync.accept(userData) + } - /** - * Adds a converter to the start of the list. - * - * @param converter The converter that returns an object corresponding to the sender or null, if it's not the right type. - */ - public static void addConverter(Function> converter) { - senderConverters.add(0, converter); - } + /** + * Returns the ID for the T typed player object connected with this one or null if no connection found. + * + * @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")!! + } - /** - * Get from the given sender. the object's type will depend on the sender's type. May be null, but shouldn't be. - * - * @param sender The sender to use - * @return A user as returned by a converter or null if none can supply it - */ - public static ChromaGamerBase getFromSender(CommandSender sender) { // TODO: Use Command2Sender - for (val converter : senderConverters) { - val ocg = converter.apply(sender); - if (ocg.isPresent()) - return ocg.get(); - } - return null; - } + /** + * Returns a player instance of the given type that represents the same player. This will return a new instance unless the player is cached.

+ * If the class is a subclass of the current class then the same ID is used, otherwise, a connected ID is used, if found. + * + * @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? { + 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 + ) + } - public static void saveUsers() { - synchronized (staticDataMap) { - for (var sud : staticDataMap.values()) - for (var cud : sud.getUserDataMap().values()) - ConfigData.saveNow(cud.getPlayerData()); //Calls save() - } - } + val fileName: String + /** + * 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")!! + val folder: String + /** + * This method returns the folder that this player data is stored in. For example: "minecraft". + */ + get() = getFolderForType(javaClass) - protected void init() { - config.reset(commonUserData.getPlayerData()); - } + /** + * Get player information. This method calls the [TBMCPlayerGetInfoEvent] to get all the player information across the TBMC plugins. + * + * @param target The [InfoTarget] to return the info for. + * @return The player information. + */ + fun getInfo(target: InfoTarget?): String { + val event = TBMCPlayerGetInfoEvent(this, target) + Bukkit.getServer().pluginManager.callEvent(event) + return event.result + } - /** - * Saves the player. It'll handle all exceptions that may happen. Called automatically. - */ - protected void save() { - try { - if (commonUserData.getPlayerData().getKeys(false).size() > 0) - commonUserData.getPlayerData().save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml")); - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while saving player to " + getFolder() + "/" + getFileName() + ".yml!", e, MainPlugin.Instance); - } - } + enum class InfoTarget { + MCHover, MCCommand, Discord + } - /** - * Removes the user from the cache. This will be called automatically after some time by default. - */ - public void uncache() { - final var userCache = commonUserData.getUserCache(); - //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized (userCache) { - if (userCache.containsKey(getClass())) - if (userCache.remove(getClass()) != this) - throw new IllegalStateException("A different player instance was cached!"); - } - } + //----------------------------------------------------------------- + @JvmField + 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 } - protected void scheduleUncache() { - Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, this::uncache, 2 * 60 * 60 * 20); //2 hours - } + companion object { + private const val TBMC_PLAYERS_DIR = "TBMC/players/" + private val senderConverters = ArrayList>>() - /** - * Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set. - * - * @param user The account to connect with - */ - public final void connectWith(T user) { - // Set the ID, go through all linked files and connect them as well - final String ownFolder = getFolder(); - final String userFolder = user.getFolder(); - if (ownFolder.equalsIgnoreCase(userFolder)) - throw new RuntimeException("Do not connect two accounts of the same type! Type: " + ownFolder); - var ownData = commonUserData.getPlayerData(); - var userData = user.commonUserData.getPlayerData(); - userData.set(ownFolder + "_id", ownData.getString(ownFolder + "_id")); - ownData.set(userFolder + "_id", userData.getString(userFolder + "_id")); - config.signalChange(); - user.config.signalChange(); - Consumer sync = sourcedata -> { - final String sourcefolder = sourcedata == ownData ? ownFolder : userFolder; - final String id = sourcedata.getString(sourcefolder + "_id"); - for (val entry : staticDataMap.entrySet()) { // Set our ID in all files we can find, both from our connections and the new ones - if (entry.getKey() == getClass() || entry.getKey() == user.getClass()) - continue; - var entryFolder = entry.getValue().getFolder(); - final String otherid = sourcedata.getString(entryFolder + "_id"); - if (otherid == null) - continue; - ChromaGamerBase cg = getUser(otherid, entry.getKey()); - var cgData = cg.commonUserData.getPlayerData(); - cgData.set(sourcefolder + "_id", id); // Set new IDs - for (val item : staticDataMap.entrySet()) { - var itemFolder = item.getValue().getFolder(); - if (sourcedata.contains(itemFolder + "_id")) { - cgData.set(itemFolder + "_id", sourcedata.getString(itemFolder + "_id")); // Set all existing IDs - } - } - cg.config.signalChange(); - } - }; - sync.accept(ownData); - sync.accept(userData); - } + /** + * Holds data per user class + */ + private val staticDataMap = HashMap, StaticUserData<*>>() - /** - * Returns the ID for the T typed player object connected with this one or null if no connection found. - * - * @param cl The player class to get the ID from - * @return The ID or null if not found - */ - public final String getConnectedID(Class cl) { - return commonUserData.getPlayerData().getString(getFolderForType(cl) + "_id"); - } + /** + * 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 + val folderName: String + if (userclass.isAnnotationPresent(UserClass::class.java)) { + cl = 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 + 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) + staticDataMap[userclass] = sud + } - /** - * Returns a player instance of the given type that represents the same player. This will return a new instance unless the player is cached.
- * If the class is a subclass of the current class then the same ID is used, otherwise, a connected ID is used, if found. - * - * @param cl The target player class - * @return The player as a {@link T} object or null if the user doesn't have an account there - */ - @SuppressWarnings("unchecked") - @Nullable - public final T getAs(Class cl) { - if (cl.getSimpleName().equals(getClass().getSimpleName())) - return (T) this; - String newfolder = getFolderForType(cl); - if (newfolder == null) - throw new RuntimeException("The specified class " + cl.getSimpleName() + " isn't registered!"); - if (newfolder.equals(getFolder())) // If in the same folder, the same filename is used - return getUser(getFileName(), cl); - var playerData = commonUserData.getPlayerData(); - if (!playerData.contains(newfolder + "_id")) - return null; - return getUser(playerData.getString(newfolder + "_id"), cl); - } + /** + * Returns the folder name for the given player class. + * + * @param cl The class to get the folder from (like [TBMCPlayerBase] or one of it's subclasses) + * @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 + throw RuntimeException("Class not registered as a user class! Use @UserClass or @AbstractUserClass") + } - /** - * 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 - */ - public final String getFileName() { - return commonUserData.getPlayerData().getString(getFolder() + "_id"); - } + /** + * 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 { + 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) + } + } - /** - * This method returns the folder that this player data is stored in. For example: "minecraft". - */ - public final String getFolder() { - return getFolderForType(getClass()); - } + /*** + * Retrieves a user from cache or loads it from disk. + * + * @param fname Filename without .yml, the user's identifier for that type + * @param cl User class + * @return The user object + */ + @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") - /** - * 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 final String getInfo(InfoTarget target) { - TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target); - Bukkit.getServer().getPluginManager().callEvent(event); - return event.getResult(); - } + @Suppress("UNCHECKED_CAST") + 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 - public enum InfoTarget { - MCHover, MCCommand, Discord - } + return if (commonUserData.userCache.containsKey(cl)) commonUserData.userCache[cl] as T + else { + val obj = createNewUser(cl, staticUserData, commonUserData) + commonUserData.userCache[cl] = obj + obj + } + } - //----------------------------------------------------------------- + private fun createNewUser( + cl: Class, + staticUserData: StaticUserData<*>, + commonUserData: CommonUserData<*> + ): T { + @Suppress("UNCHECKED_CAST") + val obj = staticUserData.constructors[cl]?.get() as T? ?: run { + try { + cl.getConstructor().newInstance() + } catch (e: Exception) { + throw RuntimeException("Failed to create new instance of user of type ${cl.simpleName}!", e) + } + } + obj.commonUserData = commonUserData + obj.init() + obj.scheduleUncache() + return obj + } - public final ConfigData channel = config.getData("channel", Channel.GlobalChat, - id -> Channel.getChannels().filter(ch -> ch.identifier.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID); -} + /** + * Adds a converter to the start of the list. + * + * @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>) { + senderConverters.add(0, converter) + } + + /** + * Get from the given sender. the object's type will depend on the sender's type. May be null, but shouldn't be. + * + * @param sender The sender to use + * @return A user as returned by a converter or null if none can supply it + */ + fun getFromSender(sender: CommandSender): ChromaGamerBase? { // TODO: Use Command2Sender + for (converter in senderConverters) { + val ocg = converter.apply(sender) + if (ocg.isPresent) return ocg.get() + } + return null + } + + fun saveUsers() { + synchronized(staticDataMap) { + for (sud in staticDataMap.values) for (cud in sud.userDataMap.values) saveNow(cud.playerData) //Calls save() + } + } + } +} \ No newline at end of file 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 a9b912a..53ff2bb 100644 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/CommonUserData.kt +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/CommonUserData.kt @@ -1,19 +1,13 @@ -package buttondevteam.lib.player; +package buttondevteam.lib.player -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.util.HashMap; +import org.bukkit.configuration.file.YamlConfiguration /** * Per user, regardless of actual type * * @param The user class, may be abstract - */ -@Getter -@RequiredArgsConstructor -public class CommonUserData { - private final HashMap, ? extends T> userCache = new HashMap<>(); - private final YamlConfiguration playerData; -} + */ +class CommonUserData(@JvmField val playerData: YamlConfiguration) { + @JvmField + val userCache: HashMap, out T> = HashMap() +} \ No newline at end of file diff --git a/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java b/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java index 6f27346..87da8f7 100755 --- a/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java +++ b/Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java @@ -49,7 +49,7 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase { else throw new RuntimeException("Class not defined as player class! Use @PlayerClass"); - var playerData = commonUserData.getPlayerData(); + var playerData = commonUserData.playerData; var section = playerData.getConfigurationSection(pluginname); if (section == null) section = playerData.createSection(pluginname); config.reset(section); @@ -76,7 +76,7 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase { @Override protected void save() { - Set keys = commonUserData.getPlayerData().getKeys(false); + 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(); }