Fix test issues and command issues

- Fixed test config loading (it loaded it twice, probably would've on a regular server too)
- Fixed missing permission plugin when testing
- Fixed command node casting errors
- Fixed event handler lists not being JVM static
This commit is contained in:
Norbi Peti 2023-06-27 01:07:22 +02:00
parent 529fef1c5f
commit 4f0e05892a
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
17 changed files with 105 additions and 55 deletions

View file

@ -8,6 +8,7 @@ import buttondevteam.core.component.randomtp.RandomTPComponent
import buttondevteam.core.component.restart.RestartComponent
import buttondevteam.core.component.spawn.SpawnComponent
import buttondevteam.core.component.towny.TownyComponent
import buttondevteam.lib.ChromaUtils
import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component.Companion.registerComponent
@ -17,6 +18,7 @@ import buttondevteam.lib.chat.TBMCChatAPI
import buttondevteam.lib.player.ChromaGamerBase
import buttondevteam.lib.player.TBMCPlayer
import buttondevteam.lib.player.TBMCPlayerBase
import buttondevteam.lib.test.TestPermissions
import com.earth2me.essentials.Essentials
import net.milkbowl.vault.economy.Economy
import net.milkbowl.vault.permission.Permission
@ -66,14 +68,16 @@ class MainPlugin : ButtonPlugin {
val externalPlayerPermissionGroup get() = iConfig.getData("externalPlayerPermissionGroup", "default")
constructor() : super()
constructor(loader: JavaPluginLoader, description: PluginDescriptionFile, dataFolder: File, file: File) : super(loader, description, dataFolder, file)
constructor(loader: JavaPluginLoader, description: PluginDescriptionFile, dataFolder: File, file: File, @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") test: java.lang.Boolean) : super(loader, description, dataFolder, file) {
ChromaUtils.isTest = test.booleanValue()
}
public override fun pluginEnable() {
instance = this
val pdf = description
setupPermissions()
if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials
setupPermissions(ChromaUtils.isTest)
if (!setupEconomy() && !ChromaUtils.isTest) //Though Essentials always provides economy, but we don't require Essentials
logger.warning("No economy plugin found! Components using economy will not be registered.")
ConfigData.saveNow(config) // Run pending save tasks
registerComponent(this, RestartComponent())
@ -159,8 +163,10 @@ class MainPlugin : ButtonPlugin {
logger.info("Player data saved.")
}
private fun setupPermissions() {
permission = setupProvider(Permission::class.java) ?: throw NullPointerException("No permission plugin found!")
private fun setupPermissions(test: Boolean) {
permission = setupProvider(Permission::class.java)
?: if (test) TestPermissions()
else throw NullPointerException("No permission plugin found!")
}
private fun setupEconomy(): Boolean {
@ -182,6 +188,7 @@ class MainPlugin : ButtonPlugin {
companion object {
lateinit var instance: MainPlugin
private set
lateinit var permission: Permission

View file

@ -1,21 +0,0 @@
package buttondevteam.core
import be.seeseemelk.mockbukkit.MockBukkit
import buttondevteam.core.component.channel.Channel
import buttondevteam.core.component.channel.ChannelComponent
import buttondevteam.lib.ChromaUtils.isTest
import buttondevteam.lib.architecture.Component.Companion.registerComponent
import buttondevteam.lib.chat.Color
import buttondevteam.lib.chat.TBMCChatAPI.registerChatChannel
import org.bukkit.ChatColor
@Deprecated("Use MockBukkit")
object TestPrepare {
@JvmStatic
fun prepareServer() {
isTest = true //Needs to be in a separate class because of the potential lack of Mockito
MockBukkit.mock()
registerComponent(MockBukkit.load(MainPlugin::class.java), ChannelComponent())
registerChatChannel(Channel("${ChatColor.WHITE}g${ChatColor.WHITE}", Color.White, "g", null).also { Channel.globalChat = it })
}
}

View file

@ -10,6 +10,7 @@ class ScheduledServerRestartEvent(val restartTicks: Int, val command: ScheduledR
}
companion object {
@JvmStatic
val handlerList = HandlerList()
}
}

View file

@ -54,6 +54,7 @@ class TBMCChatEvent(
val isFromCommand get() = chatMessage.isFromCommand
companion object {
@JvmStatic
val handlerList = HandlerList()
}
}

View file

@ -24,6 +24,7 @@ class TBMCChatPreprocessEvent(val sender: Command2Sender, val channel: Channel,
override fun setCancelled(cancelled: Boolean) = run { this.cancelled = cancelled }
companion object {
@JvmStatic
val handlerList = HandlerList()
}
}

View file

@ -28,6 +28,7 @@ class TBMCCommandPreprocessEvent(
override fun setCancelled(cancelled: Boolean) = run { this.cancelled = cancelled }
companion object {
@JvmStatic
val handlerList = HandlerList()
}
}

View file

@ -18,6 +18,7 @@ class TBMCExceptionEvent(val sourceMessage: String, val exception: Throwable) :
}
companion object {
@JvmStatic
val handlerList = HandlerList()
}
}

View file

@ -56,6 +56,7 @@ class TBMCSystemChatEvent(
}
companion object {
@JvmStatic
val handlerList = HandlerList()
}
}

View file

@ -5,6 +5,7 @@ import buttondevteam.core.ComponentManager
import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.chat.Command2MC
import buttondevteam.lib.chat.ICommand2MC
import org.bukkit.Bukkit
import org.bukkit.configuration.InvalidConfigurationException
import org.bukkit.configuration.file.FileConfiguration
import org.bukkit.configuration.file.YamlConfiguration
@ -50,8 +51,9 @@ abstract class ButtonPlugin : JavaPlugin {
*/
protected open fun pluginPreDisable() {}
override fun onEnable() {
if (!tryReloadConfig()) {
if (!isConfigLoaded) {
logger.warning("Please fix the issues and restart the server to load the plugin.")
Bukkit.getPluginManager().disablePlugin(this)
return
}
try {
@ -65,6 +67,7 @@ abstract class ButtonPlugin : JavaPlugin {
override fun onDisable() {
try {
if (!isConfigLoaded) return
pluginPreDisable()
ComponentManager.unregComponents(this)
pluginDisable()

View file

@ -80,7 +80,7 @@ class ConfigData<T : Any?> internal constructor(
if (config != null) {
config.set(path, `val`)
signalChange(this.config)
} else {
} else if (!ChromaUtils.isTest) {
ChromaUtils.logWarn("Attempted to get/set config value with no config! Path: $path, value: $`val`")
}
}

View file

@ -3,7 +3,7 @@ package buttondevteam.lib.chat
import buttondevteam.core.MainPlugin
import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.chat.commands.*
import buttondevteam.lib.chat.commands.CommandUtils.core
import buttondevteam.lib.chat.commands.CommandUtils.coreCommand
import buttondevteam.lib.chat.commands.CommandUtils.coreExecutable
import com.mojang.brigadier.CommandDispatcher
import com.mojang.brigadier.arguments.*
@ -164,14 +164,14 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
*/
private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, remainingPath: String,
argHelpManager: CommandArgumentHelpManager<TC, TP>, fullPath: String): LiteralCommandNode<TP> {
val (params, _) = getCommandParametersAndSender(method, argHelpManager) // Param order is important
val (params, senderType) = getCommandParametersAndSender(method, argHelpManager) // Param order is important
val paramMap = HashMap<String, CommandArgument>()
for (param in params) {
paramMap[param.name] = param
}
val helpText = command.getHelpText(method, ann)
val node = CoreCommandBuilder.literal(
remainingPath, params[0].type, paramMap, params, command,
remainingPath, senderType, paramMap, params, command,
{ helpText }, // TODO: Help text getter support
{ sender: TP, data: SubcommandData<TC, TP> -> hasPermission(sender, data) },
method.annotations.filterNot { it is Subcommand }.toTypedArray(),
@ -364,9 +364,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @return A set of command node objects containing the commands
*/
val commandNodes: Set<CoreCommandNode<TP, NoOpSubcommandData>>
get() = dispatcher.root.children.stream()
.map { node: CommandNode<TP> -> node.core<TP, NoOpSubcommandData>() }
.collect(Collectors.toUnmodifiableSet())
get() = dispatcher.root.children.mapNotNull { it.coreCommand<TP, NoOpSubcommandData>() }.toSet()
/**
* Get a node that belongs to the given command.
@ -375,7 +373,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
* @return A command node
*/
fun getCommandNode(command: String): CoreCommandNode<TP, NoOpSubcommandData>? { // TODO: What should this return? No-op? Executable? What's the use case?
return dispatcher.root.getChild(command)?.core()
return dispatcher.root.getChild(command)?.coreCommand()
}
/**
@ -394,7 +392,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
*/
fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, SubcommandData<TC, TP>>>, nested: Boolean) {
dispatcher.root.children.removeIf { node -> node.coreExecutable<TP, TC>()?.let { condition.test(it) } ?: false }
if (nested) for (child in dispatcher.root.children) unregisterCommandIf(condition, child.core())
if (nested) for (child in dispatcher.root.children) child.coreCommand<_, NoOpSubcommandData>()?.let { unregisterCommandIf(condition, it) }
}
private fun unregisterCommandIf(
@ -404,7 +402,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
// TODO: Remvoe no-op nodes without children
// Can't use getCoreChildren() here because the collection needs to be modifiable
root.children.removeIf { node -> node.coreExecutable<TP, TC>()?.let { condition.test(it) } ?: false }
for (child in root.children) unregisterCommandIf(condition, child.core())
for (child in root.children) child.coreCommand<_, NoOpSubcommandData>()?.let { unregisterCommandIf(condition, it) }
}
/**
@ -417,7 +415,7 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
mainCommand: LiteralCommandNode<TP>,
deep: Boolean = true
): List<CoreExecutableNode<TP, TC>> {
return getSubcommands(deep, mainCommand.core())
return mainCommand.coreCommand<_, NoOpSubcommandData>()?.let { getSubcommands(deep, it) } ?: emptyList()
}
private fun getSubcommands(
@ -425,6 +423,6 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
root: CoreNoOpNode<TP>
): List<CoreExecutableNode<TP, TC>> {
return root.children.mapNotNull { it.coreExecutable<TP, TC>() } +
if (deep) root.children.flatMap { getSubcommands(deep, it.core()) } else emptyList()
if (deep) root.children.flatMap { child -> child.coreCommand<_, NoOpSubcommandData>()?.let { getSubcommands(deep, it) } ?: emptyList() } else emptyList()
}
}

View file

@ -1,6 +1,7 @@
package buttondevteam.lib.chat
import buttondevteam.core.MainPlugin
import buttondevteam.lib.ChromaUtils
import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component
@ -238,7 +239,11 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
fun registerTabcomplete(command2MC: ICommand2MC, commandNode: CoreCommandNode<Command2MCSender, *>, bukkitCommand: Command) {
if (!CommodoreProvider.isSupported()) {
throw UnsupportedOperationException("Commodore is not supported! Please use 1.14 or higher. Server version: ${Bukkit.getVersion()}")
if (ChromaUtils.isTest) {
return
} else {
throw UnsupportedOperationException("Commodore is not supported! Please use 1.14 or higher. Server version: ${Bukkit.getVersion()}")
}
}
// TODO: Allow extending annotation processing for methods and parameters
val customTabCompleteMethods = command2MC.javaClass.declaredMethods
@ -316,5 +321,3 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
}
}
}
private typealias CNode = CommandNode<Command2MCSender>

View file

@ -36,6 +36,13 @@ class CoreArgumentBuilder<S : Command2Sender, T>(
)
}
override fun then(argument: ArgumentBuilder<S, *>?): CoreArgumentBuilder<S, T> {
if (argument is CoreArgumentBuilder<*, *>) {
(argument as CoreArgumentBuilder<S, *>).data = data
}
return super.then(argument)
}
companion object {
fun <S : Command2Sender, T> argument(name: String, type: ArgumentType<T>, optional: Boolean): CoreArgumentBuilder<S, T> {
return CoreArgumentBuilder(name, type, optional)

View file

@ -12,8 +12,8 @@ class CoreCommandNode<T : Command2Sender, TSD : NoOpSubcommandData>(
literal: String,
command: Command<T>,
requirement: Predicate<T>,
redirect: CommandNode<T>,
modifier: RedirectModifier<T>,
redirect: CommandNode<T>?,
modifier: RedirectModifier<T>?,
forks: Boolean,
val data: TSD
) : LiteralCommandNode<T>(literal, command, requirement, redirect, modifier, forks)

View file

@ -1,6 +1,7 @@
package buttondevteam.lib.chat.commands
import buttondevteam.core.MainPlugin
import buttondevteam.lib.ChromaUtils
import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.chat.Command2Sender
import buttondevteam.lib.chat.ICommand2
@ -29,11 +30,9 @@ class CommandArgumentHelpManager<TC : ICommand2<TP>, TP : Command2Sender>(comman
try {
commandClass.getResourceAsStream("/commands.yml").use { str ->
if (str == null) {
TBMCCoreAPI.SendException(
"Error while getting command data!",
Exception("Resource not found!"),
MainPlugin.instance
)
if (!ChromaUtils.isTest) {
ChromaUtils.logWarn("Failed to get command data for $commandClass! No commands.yml file found.")
}
return@use
}
val config = YamlConfiguration.loadConfiguration(InputStreamReader(str))
@ -56,7 +55,9 @@ class CommandArgumentHelpManager<TC : ICommand2<TP>, 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.")
if (!ChromaUtils.isTest) {
MainPlugin.instance.logger.warning("Failed to get command data for $method! Make sure to use 'clean install' when building the project.")
}
return null
}

View file

@ -28,19 +28,20 @@ object CommandUtils {
}
/**
* Casts the node to whatever you say. Use responsibly.
* Casts the node to whatever you say if it's a command node. Use responsibly. Returns null if an argument node.
*/
@Suppress("UNCHECKED_CAST")
fun <TP : Command2Sender, TSD : NoOpSubcommandData> CommandNode<TP>.core(): CoreCommandNode<TP, TSD> {
return this as CoreCommandNode<TP, TSD>
fun <TP : Command2Sender, TSD : NoOpSubcommandData> CommandNode<TP>.coreCommand(): CoreCommandNode<TP, TSD>? {
return if (this is CoreCommandNode<*, *>) this as CoreCommandNode<TP, TSD>
else null
}
/**
* Returns the node as an executable core command node or returns null if it's a no-op node.
*/
fun <TP : Command2Sender, TC : ICommand2<*>> CommandNode<TP>.coreExecutable(): CoreExecutableNode<TP, TC>? {
val ret = core<TP, NoOpSubcommandData>()
return if (ret.data is SubcommandData<*, *>) ret.core() else null
val ret = this.coreCommand<TP, NoOpSubcommandData>()
return if (ret?.data is SubcommandData<*, *>) ret.coreCommand() else null
}
fun <TP : Command2Sender> CommandNode<TP>.coreArgument(): CoreArgumentCommandNode<TP, *>? {

View file

@ -0,0 +1,45 @@
package buttondevteam.lib.test
import net.milkbowl.vault.permission.Permission
class TestPermissions : Permission() {
override fun getName(): String = "TestPermissions"
override fun isEnabled(): Boolean = true
override fun hasSuperPermsCompat(): Boolean = false
@Deprecated("Deprecated in Java")
override fun playerHas(world: String?, player: String?, permission: String?): Boolean = true
@Deprecated("Deprecated in Java")
override fun playerAdd(world: String?, player: String?, permission: String?): Boolean = true
@Deprecated("Deprecated in Java")
override fun playerRemove(world: String?, player: String?, permission: String?): Boolean = true
override fun groupHas(world: String?, group: String?, permission: String?): Boolean = true
override fun groupAdd(world: String?, group: String?, permission: String?): Boolean = true
override fun groupRemove(world: String?, group: String?, permission: String?): Boolean = true
@Deprecated("Deprecated in Java")
override fun playerInGroup(world: String?, player: String?, group: String?): Boolean = true
@Deprecated("Deprecated in Java")
override fun playerAddGroup(world: String?, player: String?, group: String?): Boolean = true
@Deprecated("Deprecated in Java")
override fun playerRemoveGroup(world: String?, player: String?, group: String?): Boolean = true
@Deprecated("Deprecated in Java")
override fun getPlayerGroups(world: String?, player: String?): Array<String> = arrayOf()
@Deprecated("Deprecated in Java")
override fun getPrimaryGroup(world: String?, player: String?): String = "default"
override fun getGroups(): Array<String> = arrayOf()
override fun hasGroupSupport(): Boolean = true
}