A bunch of changes and improvements - and Kotlin

- Tried to reduce usage of MainPlugin.Instance, ended up refactoring more Component/ButtonPlugin things
- Config fixes, among other things
This commit is contained in:
Norbi Peti 2023-04-14 00:48:18 +02:00
parent d6fba6a495
commit 56d1f0c2a2
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
16 changed files with 684 additions and 697 deletions

View file

@ -24,7 +24,7 @@ public class ChromaCommand extends ICommand2MC {
@Command2.Subcommand @Command2.Subcommand
public void reload(CommandSender sender, @Command2.OptionalArg ButtonPlugin plugin) { public void reload(CommandSender sender, @Command2.OptionalArg ButtonPlugin plugin) {
if (plugin == null) if (plugin == null)
plugin = MainPlugin.Instance; plugin = getPlugin();
if (plugin.tryReloadConfig()) if (plugin.tryReloadConfig())
sender.sendMessage("§b" + plugin.getName() + " config reloaded."); sender.sendMessage("§b" + plugin.getName() + " config reloaded.");
else else

View file

@ -1,66 +1,81 @@
package buttondevteam.core; package buttondevteam.core
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component
import lombok.val; import buttondevteam.lib.architecture.Component.Companion.components
import buttondevteam.lib.architecture.Component.Companion.setComponentEnabled
import buttondevteam.lib.architecture.Component.Companion.unregisterComponent
import org.bukkit.plugin.java.JavaPlugin
public final class ComponentManager { object ComponentManager {
private ComponentManager() {} private var componentsEnabled = false
private static boolean componentsEnabled = false; /**
* This flag is used to enable components registered after the others were enabled.
* @return Whether already registered components have been enabled
*/
fun areComponentsEnabled(): Boolean {
return componentsEnabled
}
/** /**
* This flag is used to enable components registered after the others were enabled. * Enables components based on a configuration - any component registered afterwards will be also enabled
* @return Whether already registered components have been enabled */
*/ fun enableComponents() {
public static boolean areComponentsEnabled() { return componentsEnabled; } components.values.stream().filter { c: Component<out JavaPlugin> -> c.shouldBeEnabled.get() }
.forEach { c ->
try {
setComponentEnabled(c, true)
} catch (e: Exception) {
TBMCCoreAPI.SendException("Failed to enable one of the components: " + c.javaClass.simpleName, e, c)
} catch (e: NoClassDefFoundError) {
TBMCCoreAPI.SendException("Failed to enable one of the components: " + c.javaClass.simpleName, e, c)
}
}
componentsEnabled = true
}
/** /**
* Enables components based on a configuration - any component registered afterwards will be also enabled * Unregister all components of a plugin that are enabled - called on [ButtonPlugin] disable
*/ */
public static void enableComponents() { @Suppress("UNCHECKED_CAST")
//Component.getComponents().values().stream().filter(c->cs.getConfigurationSection(c.getClass().getSimpleName()).getBoolean("enabled")).forEach(c-> { fun <T : ButtonPlugin> unregComponents(plugin: T) {
Component.getComponents().values().stream().filter(c -> c.shouldBeEnabled.get()).forEach(c -> { while (!plugin.componentStack.empty()) //Unregister in reverse order
try { unregisterComponent(plugin, plugin.componentStack.pop() as Component<T>) //Components are pushed on register
Component.setComponentEnabled(c, true); }
} catch (Exception | NoClassDefFoundError e) {
TBMCCoreAPI.SendException("Failed to enable one of the components: " + c.getClass().getSimpleName(), e, c);
}
});
componentsEnabled = true;
}
/** /**
* Unregister all components of a plugin that are enabled - called on {@link ButtonPlugin} disable * Will also return false if the component is not registered.
*/ *
@SuppressWarnings("unchecked") * @param cl The component class
public static <T extends ButtonPlugin> void unregComponents(T plugin) { * @return Whether the component is registered and enabled
while (!plugin.getComponentStack().empty()) //Unregister in reverse order */
Component.unregisterComponent(plugin, (Component<T>) plugin.getComponentStack().pop()); //Components are pushed on register @JvmStatic
//componentsEnabled = false; - continue enabling new components after a plugin gets disabled fun isEnabled(cl: Class<out Component<*>?>?): Boolean {
} val c = components[cl]
return c != null && c.isEnabled
}
/** /**
* Will also return false if the component is not registered. * Will also return null if the component is not registered.
* *
* @param cl The component class * @param cl The component class
* @return Whether the component is registered and enabled * @return The component if it's registered and enabled
*/ */
public static boolean isEnabled(Class<? extends Component> cl) { @JvmStatic
val c = Component.getComponents().get(cl); @Suppress("UNCHECKED_CAST")
return c != null && c.isEnabled(); fun <T : Component<*>> getIfEnabled(cl: Class<T>): T? {
} val c = components[cl]
return if (c != null && c.isEnabled) c as T else null
}
/** /**
* Will also return null if the component is not registered. * It will return null if the component is not registered. Use this method if you don't want to check if the component is enabled.
* */
* @param cl The component class @JvmStatic
* @return The component if it's registered and enabled @Suppress("UNCHECKED_CAST")
*/ fun <T : Component<*>> get(cl: Class<T>): T? {
@SuppressWarnings("unchecked") return components[cl] as T?
public static <T extends Component> T getIfEnabled(Class<T> cl) { }
val c = Component.getComponents().get(cl);
return c != null && c.isEnabled() ? (T) c : null;
}
} }

View file

@ -1,173 +1,194 @@
package buttondevteam.core; package buttondevteam.core
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel
import buttondevteam.core.component.channel.ChannelComponent; import buttondevteam.core.component.channel.ChannelComponent
import buttondevteam.core.component.channel.ChatRoom; import buttondevteam.core.component.channel.ChatRoom
import buttondevteam.core.component.members.MemberComponent; import buttondevteam.core.component.members.MemberComponent
import buttondevteam.core.component.randomtp.RandomTPComponent; import buttondevteam.core.component.randomtp.RandomTPComponent
import buttondevteam.core.component.restart.RestartComponent; import buttondevteam.core.component.restart.RestartComponent
import buttondevteam.core.component.spawn.SpawnComponent; import buttondevteam.core.component.spawn.SpawnComponent
import buttondevteam.core.component.towny.TownyComponent; import buttondevteam.core.component.towny.TownyComponent
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component.Companion.registerComponent
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.chat.Color
import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.TBMCChatAPI
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.ChromaGamerBase
import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.TBMCPlayer
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayerBase
import buttondevteam.lib.player.TBMCPlayerBase; import com.earth2me.essentials.Essentials
import com.earth2me.essentials.Essentials; import net.milkbowl.vault.economy.Economy
import lombok.Getter; import net.milkbowl.vault.permission.Permission
import lombok.Setter; import org.bukkit.Bukkit
import net.milkbowl.vault.economy.Economy; import org.bukkit.OfflinePlayer
import net.milkbowl.vault.permission.Permission; import org.bukkit.command.BlockCommandSender
import org.bukkit.Bukkit; import org.bukkit.command.Command
import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender
import org.bukkit.command.BlockCommandSender; import org.bukkit.command.ConsoleCommandSender
import org.bukkit.command.Command; import org.bukkit.entity.Player
import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin
import org.bukkit.command.ConsoleCommandSender; import java.io.File
import org.bukkit.entity.HumanEntity; import java.io.IOException
import org.bukkit.entity.Player; import java.nio.file.Files
import org.bukkit.plugin.PluginDescriptionFile; import java.util.*
import org.bukkit.plugin.RegisteredServiceProvider; import java.util.function.Function
import java.util.function.Supplier
import java.util.logging.Logger
import javax.annotation.Nullable; class MainPlugin : ButtonPlugin() {
import java.io.File; private var logger: Logger? = null
import java.io.IOException; private var economy: Economy? = null
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.logging.Logger;
public class MainPlugin extends ButtonPlugin { /**
public static MainPlugin Instance; * Whether the Core's chat handler should be enabled.
public static Permission permission; * Other chat plugins handling messages from other platforms should set this to false.
@Nullable */
public static Essentials ess; var isChatHandlerEnabled = true
private Logger logger; /**
@Nullable * Sets whether the plugin should write a list of installed plugins in a txt file.
private Economy economy; * It can be useful if some other software needs to know the plugins.
/** */
* Whether the Core's chat handler should be enabled. private val writePluginList = iConfig.getData("writePluginList", false)
* Other chat plugins handling messages from other platforms should set this to false.
*/
@Getter
@Setter
private boolean chatHandlerEnabled = true;
/** /**
* Sets whether the plugin should write a list of installed plugins in a txt file. * The chat format to use for messages from other platforms if Chroma-Chat is not installed.
* It can be useful if some other software needs to know the plugins. */
*/ @JvmField
private final ConfigData<Boolean> writePluginList = getIConfig().getData("writePluginList", false); var chatFormat = iConfig.getData("chatFormat", "[{origin}|{channel}] <{name}> {message}")
/** /**
* The chat format to use for messages from other platforms if Chroma-Chat is not installed. * Print some debug information.
*/ */
ConfigData<String> chatFormat = getIConfig().getData("chatFormat", "[{origin}|" + @JvmField
"{channel}] <{name}> {message}"); val test = iConfig.getData("test", false)
/** /**
* Print some debug information. * If a Chroma command clashes with another plugin's command, this setting determines whether the Chroma command should be executed or the other plugin's.
*/ */
public final ConfigData<Boolean> test = getIConfig().getData("test", false); val prioritizeCustomCommands = iConfig.getData("prioritizeCustomCommands", false)
public override fun pluginEnable() {
/** Instance = this
* If a Chroma command clashes with another plugin's command, this setting determines whether the Chroma command should be executed or the other plugin's. val pdf = description
*/ logger = getLogger()
public final ConfigData<Boolean> prioritizeCustomCommands = getIConfig().getData("prioritizeCustomCommands", false); if (!setupPermissions()) throw NullPointerException("No permission plugin found!")
if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials
@Override getLogger().warning("No economy plugin found! Components using economy will not be registered.")
public void pluginEnable() { saveConfig()
Instance = this; registerComponent(this, RestartComponent())
PluginDescriptionFile pdf = getDescription(); registerComponent(this, ChannelComponent())
logger = getLogger(); registerComponent(this, RandomTPComponent())
if (!setupPermissions()) registerComponent(this, MemberComponent())
throw new NullPointerException("No permission plugin found!"); if (Bukkit.getPluginManager().isPluginEnabled("Multiverse-Core"))
if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials registerComponent(this, SpawnComponent())
getLogger().warning("No economy plugin found! Components using economy will not be registered."); if (Bukkit.getPluginManager().isPluginEnabled("Towny")) //It fails to load the component class otherwise
saveConfig(); registerComponent(this, TownyComponent())
Component.registerComponent(this, new RestartComponent()); /*if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new ChannelComponent());
Component.registerComponent(this, new RandomTPComponent());
Component.registerComponent(this, new MemberComponent());
if (Bukkit.getPluginManager().isPluginEnabled("Multiverse-Core"))
Component.registerComponent(this, new SpawnComponent());
if (Bukkit.getPluginManager().isPluginEnabled("Towny")) //It fails to load the component class otherwise
Component.registerComponent(this, new TownyComponent());
/*if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new VotifierComponent(economy));*/ Component.registerComponent(this, new VotifierComponent(economy));*/
ComponentManager.enableComponents(); ComponentManager.enableComponents()
registerCommand(new ComponentCommand()); registerCommand(ComponentCommand())
registerCommand(new ChromaCommand()); registerCommand(ChromaCommand())
TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); TBMCCoreAPI.RegisterEventsForExceptions(PlayerListener(this), this)
TBMCCoreAPI.RegisterEventsForExceptions(Companion.getCommand2MC(), this); TBMCCoreAPI.RegisterEventsForExceptions(command2MC, this)
ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender //Console & cmdblocks
? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks ChromaGamerBase.addConverter { commandSender: CommandSender ->
ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player Optional.ofNullable(
? TBMCPlayer.getPlayer(((Player) sender).getUniqueId(), TBMCPlayer.class) : null)); //Players, has higher priority if (commandSender is ConsoleCommandSender || commandSender is BlockCommandSender)
TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class, TBMCPlayer::new); TBMCPlayer.getPlayer(UUID(0, 0), TBMCPlayer::class.java)
TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); //The /ooc ID has moved to the config else null
TBMCChatAPI.RegisterChatChannel( )
Channel.AdminChat = new Channel("§cADMIN§f", Color.Red, "a", Channel.inGroupFilter(null))); }
TBMCChatAPI.RegisterChatChannel( //Players, has higher priority
Channel.ModChat = new Channel("§9MOD§f", Color.Blue, "mod", Channel.inGroupFilter("mod"))); ChromaGamerBase.addConverter { sender: CommandSender ->
TBMCChatAPI.RegisterChatChannel(new Channel("§6DEV§f", Color.Gold, "dev", Channel.inGroupFilter("developer"))); Optional.ofNullable(
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§cRED§f", Color.DarkRed, "red")); if (sender is Player) TBMCPlayer.getPlayer(sender.uniqueId, TBMCPlayer::class.java) else null
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§6ORANGE§f", Color.Gold, "orange")); )
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§eYELLOW§f", Color.Yellow, "yellow")); }
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§aGREEN§f", Color.Green, "green")); TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase::class.java) { TBMCPlayer() }
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue")); TBMCChatAPI.RegisterChatChannel(Channel("§fg§f", Color.White, "g", null)
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple")); .also { Channel.GlobalChat = it }) //The /ooc ID has moved to the config
Supplier<Iterable<String>> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator; TBMCChatAPI.RegisterChatChannel(Channel("§cADMIN§f", Color.Red, "a", Channel.inGroupFilter(null))
Companion.getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier); .also { Channel.AdminChat = it })
Companion.getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier); TBMCChatAPI.RegisterChatChannel(Channel("§9MOD§f", Color.Blue, "mod", Channel.inGroupFilter("mod"))
if (writePluginList.get()) { .also { Channel.ModChat = it })
try { TBMCChatAPI.RegisterChatChannel(
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator); Channel(
} catch (IOException e) { "§6DEV§f",
TBMCCoreAPI.SendException("Failed to write plugin list!", e, this); Color.Gold,
} "dev",
} Channel.inGroupFilter("developer")
if (getServer().getPluginManager().isPluginEnabled("Essentials")) )
ess = Essentials.getPlugin(Essentials.class); ) // TODO: Make groups configurable
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test.get() + "."); TBMCChatAPI.RegisterChatChannel(ChatRoom("§cRED§f", Color.DarkRed, "red"))
} TBMCChatAPI.RegisterChatChannel(ChatRoom("§6ORANGE§f", Color.Gold, "orange"))
TBMCChatAPI.RegisterChatChannel(ChatRoom("§eYELLOW§f", Color.Yellow, "yellow"))
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>(
Player::class.java, Function { name: String ->
Bukkit.getPlayer(name)
}, "Online 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
)
logger!!.info(pdf.name + " has been Enabled (V." + pdf.version + ") Test: " + test.get() + ".")
}
@Override public override fun pluginDisable() {
public void pluginDisable() { logger!!.info("Saving player data...")
logger.info("Saving player data..."); ChromaGamerBase.saveUsers()
ChromaGamerBase.saveUsers(); logger!!.info("Player data saved.")
logger.info("Player data saved."); }
}
private boolean setupPermissions() { private fun setupPermissions(): Boolean {
permission = setupProvider(Permission.class); permission = setupProvider(Permission::class.java)
return (permission != null); return permission != null
} }
private boolean setupEconomy() { private fun setupEconomy(): Boolean {
economy = setupProvider(Economy.class); economy = setupProvider(Economy::class.java)
return (economy != null); return economy != null
} }
private <T> T setupProvider(Class<T> cl) { private fun <T> setupProvider(cl: Class<T>): T? {
RegisteredServiceProvider<T> provider = getServer().getServicesManager() val provider = server.servicesManager
.getRegistration(cl); .getRegistration(cl)
if (provider != null) return provider?.provider
return provider.getProvider(); }
return null;
}
@Override override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): Boolean {
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (command.name == "dontrunthiscmd") return true //Used in chat preprocess for console
if (command.getName().equals("dontrunthiscmd")) return true; //Used in chat preprocess for console sender.sendMessage("§cThis command isn't available.") //In theory, unregistered commands use this method
sender.sendMessage("§cThis command isn't available."); //In theory, unregistered commands use this method return true
return true; }
}
companion object {
@JvmField
var Instance: MainPlugin = null
@JvmField
var permission: Permission? = null
@JvmField
var ess: Essentials? = null
}
} }

View file

@ -1,116 +1,110 @@
package buttondevteam.core; package buttondevteam.core
import buttondevteam.lib.*; import buttondevteam.lib.*
import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.ButtonPlugin
import buttondevteam.lib.chat.ChatMessage; import buttondevteam.lib.chat.ChatMessage
import buttondevteam.lib.chat.Command2MCSender; import buttondevteam.lib.chat.Command2MCSender
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCChatAPI
import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.ChromaGamerBase
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer
import buttondevteam.lib.player.TBMCPlayerBase; import buttondevteam.lib.player.TBMCPlayerBase
import lombok.val; import org.bukkit.Bukkit
import org.bukkit.Bukkit; import org.bukkit.command.CommandSender
import org.bukkit.command.CommandSender; import org.bukkit.entity.Player
import org.bukkit.entity.Player; import org.bukkit.event.Cancellable
import org.bukkit.event.Cancellable; import org.bukkit.event.EventHandler
import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority
import org.bukkit.event.EventPriority; import org.bukkit.event.Listener
import org.bukkit.event.Listener; import org.bukkit.event.player.AsyncPlayerChatEvent
import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent
import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.server.ServerCommandEvent
import org.bukkit.event.server.ServerCommandEvent;
import java.util.Arrays; class PlayerListener(val plugin: MainPlugin) : Listener {
@EventHandler(priority = EventPriority.NORMAL)
fun onPlayerJoin(event: PlayerJoinEvent) {
val p = event.player
val player = TBMCPlayerBase.getPlayer(p.uniqueId, TBMCPlayer::class.java)
val pname = player.PlayerName.get()
if (pname.isEmpty()) {
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)
}
}
public class PlayerListener implements Listener { @EventHandler(priority = EventPriority.NORMAL)
fun onPlayerLeave(event: PlayerQuitEvent) {
TBMCPlayerBase.getPlayer(event.player.uniqueId, TBMCPlayer::class.java).uncache()
}
@EventHandler(priority = EventPriority.NORMAL) @EventHandler(priority = EventPriority.HIGHEST)
public void OnPlayerJoin(PlayerJoinEvent event) { fun onSystemChat(event: TBMCSystemChatEvent) {
var p = event.getPlayer(); if (event.isHandled) return
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class); if (event.exceptions.any { "Minecraft".equals(it, ignoreCase = true) }) return
String pname = player.PlayerName.get(); Bukkit.getOnlinePlayers().stream().filter { sender: CommandSender -> event.shouldSendTo(sender) }
if (pname.length() == 0) { .forEach { p: Player -> p.sendMessage(event.channel.displayName.get().substring(0, 2) + event.message) }
player.PlayerName.set(p.getName()); }
MainPlugin.Instance.getLogger().info("Player name saved: " + player.PlayerName.get());
} else if (!p.getName().equals(pname)) {
MainPlugin.Instance.getLogger().info(pname + " renamed to " + p.getName());
player.PlayerName.set(p.getName());
}
}
@EventHandler(priority = EventPriority.NORMAL) @EventHandler
public void OnPlayerLeave(PlayerQuitEvent event) { fun onPlayerChatPreprocess(event: PlayerCommandPreprocessEvent) {
TBMCPlayerBase.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class).uncache(); handlePreprocess(event.player, event.message, event)
} }
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler
public void onSystemChat(TBMCSystemChatEvent event) { fun onSystemChatPreprocess(event: ServerCommandEvent) {
if (event.isHandled()) handlePreprocess(event.sender, "/" + event.command, event)
return; // Only handle here if ButtonChat couldn't - ButtonChat doesn't even handle this if (event.isCancelled) event.command = "dontrunthiscmd" //Bugfix
if (Arrays.stream(event.getExceptions()).anyMatch("Minecraft"::equalsIgnoreCase)) }
return;
Bukkit.getOnlinePlayers().stream().filter(event::shouldSendTo)
.forEach(p -> p.sendMessage(event.getChannel().DisplayName.get().substring(0, 2) + event.getMessage()));
}
@EventHandler private fun handlePreprocess(sender: CommandSender, message: String, event: Cancellable) {
public void onPlayerChatPreprocess(PlayerCommandPreprocessEvent event) { if (event.isCancelled) return
handlePreprocess(event.getPlayer(), event.getMessage(), event); val cg = ChromaGamerBase.getFromSender(sender)
} ?: throw RuntimeException("Couldn't get user from sender for " + sender.name + "!")
val ev = TBMCCommandPreprocessEvent(sender, cg.channel.get(), message, sender)
Bukkit.getPluginManager().callEvent(ev)
if (ev.isCancelled) event.isCancelled = true //Cancel the original event
}
@EventHandler @EventHandler
public void onSystemChatPreprocess(ServerCommandEvent event) { fun onTBMCPreprocess(event: TBMCCommandPreprocessEvent) {
handlePreprocess(event.getSender(), "/" + event.getCommand(), event); if (event.isCancelled) return
if (event.isCancelled()) event.setCommand("dontrunthiscmd"); //Bugfix try {
} val sender = Command2MCSender(event.sender, event.channel, event.permCheck)
event.isCancelled = ButtonPlugin.command2MC.handleCommand(sender, event.message)
} catch (e: Exception) {
TBMCCoreAPI.SendException(
"Command processing failed for sender '${event.sender}' and message '${event.message}'",
e,
plugin
)
}
}
private void handlePreprocess(CommandSender sender, String message, Cancellable event) { @EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest
if (event.isCancelled()) return; fun onPlayerChat(event: AsyncPlayerChatEvent) {
val cg = ChromaGamerBase.getFromSender(sender); if (event.isCancelled) return //The chat plugin should cancel it after this handler
if (cg == null) throw new RuntimeException("Couldn't get user from sender for " + sender.getName() + "!"); val cp = TBMCPlayer.getPlayer(event.player.uniqueId, TBMCPlayer::class.java)
val ev = new TBMCCommandPreprocessEvent(sender, cg.channel.get(), message, sender); TBMCChatAPI.SendChatMessage(ChatMessage.builder(event.player, cp, event.message).build())
Bukkit.getPluginManager().callEvent(ev); //Not cancelling the original event here, it's cancelled in the chat plugin
if (ev.isCancelled()) //This way other plugins can deal with the MC formatting if the chat plugin isn't present, but other platforms still get the message
event.setCancelled(true); //Cancel the original event }
}
@EventHandler @EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest
public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) { fun onPlayerChat(event: TBMCChatEvent) {
if (event.isCancelled()) return; if (event.isCancelled) return
try { if (!plugin.isChatHandlerEnabled) return
event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender(), event.getChannel(), event.getPermCheck()), event.getMessage())); if (event.origin == "Minecraft") return //Let other plugins handle MC messages
} catch (Exception e) { val channel = event.channel
TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e, MainPlugin.Instance); val msg = plugin.chatFormat.get()
} .replace("{channel}", channel.displayName.get())
} .replace("{origin}", event.origin.substring(0, 1))
.replace("{name}", ChromaUtils.getDisplayName(event.sender))
@EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest .replace("{message}", String.format("§%x%s", channel.color.get().ordinal, event.message))
public void onPlayerChat(AsyncPlayerChatEvent event) { for (player in Bukkit.getOnlinePlayers()) if (event.shouldSendTo(player)) player.sendMessage(msg)
if (event.isCancelled()) Bukkit.getConsoleSender().sendMessage(msg)
return; //The chat plugin should cancel it after this handler }
val cp = TBMCPlayer.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class);
TBMCChatAPI.SendChatMessage(ChatMessage.builder(event.getPlayer(), cp, event.getMessage()).build());
//Not cancelling the original event here, it's cancelled in the chat plugin
//This way other plugins can deal with the MC formatting if the chat plugin isn't present, but other platforms still get the message
}
@EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest
public void onPlayerChat(TBMCChatEvent event) {
if (event.isCancelled())
return;
if (!MainPlugin.Instance.isChatHandlerEnabled()) return;
if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages
var channel = event.getChannel();
String msg = MainPlugin.Instance.chatFormat.get()
.replace("{channel}", channel.DisplayName.get())
.replace("{origin}", event.getOrigin().substring(0, 1))
.replace("{name}", ChromaUtils.getDisplayName(event.getSender()))
.replace("{message}", String.format("§%x%s", channel.Color.get().ordinal(), event.getMessage()));
for (Player player : Bukkit.getOnlinePlayers())
if (event.shouldSendTo(player))
player.sendMessage(msg);
Bukkit.getConsoleSender().sendMessage(msg);
}
} }

View file

@ -1,237 +1,231 @@
package buttondevteam.core.component.channel; package buttondevteam.core.component.channel
import buttondevteam.core.ComponentManager; import buttondevteam.core.ComponentManager.get
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.IHaveConfig
import buttondevteam.lib.architecture.IHaveConfig; import buttondevteam.lib.architecture.ListConfigData
import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.Color
import com.google.common.collect.Lists; import org.bukkit.Bukkit
import org.bukkit.Bukkit; import org.bukkit.command.CommandSender
import org.bukkit.command.CommandSender; import org.bukkit.entity.Player
import org.bukkit.entity.Player; import java.util.*
import java.util.function.Function
import javax.annotation.Nullable; import java.util.function.Predicate
import java.util.ArrayList; import java.util.stream.Stream
import java.util.Collections;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/** /**
* Represents a chat channel. May only be instantiated after the channel component is registered. * Represents a chat channel. May only be instantiated after the channel component is registered.
*/ */
public class Channel { open class Channel
/** /**
* Specifies a score that means it's OK to send - but it does not define any groups, only send or not send. See {@link #GROUP_EVERYONE} * Creates a channel.
*/ *
public static final int SCORE_SEND_OK = 0; * @param filterAndErrorMSG Checks all senders against the criteria provided here and sends the message if the index matches the sender's - if no score at all, displays the error.<br></br>
/** * May be null to send to everyone.
* Specifies a score that means the user doesn't have permission to see or send the message. Any negative value has the same effect. */(
*/ /**
public static final int SCORE_SEND_NOPE = -1; * The name that should appear at the start of the message. **A chat color is expected at the beginning (§9).**
/** */
* Send the message to everyone <i>who has access to the channel</i> - this does not necessarily mean all players private val defDisplayName: String,
*/ /**
public static final String GROUP_EVERYONE = "everyone"; * The default color of the messages sent in the channel
*/
private val defColor: Color,
/**
* The channel identifier. It's the same as the command to be used for the channel *without / *. For example "mod".
* It's also used for scoreboard objective names.
*/
val identifier: String,
/**
* A function that determines who has permission to see the channel.
* If the sender doesn't have access, they cannot send the message.
* Only those with access can see the messages.
* If null, everyone has access.
*/
private val filterAndErrorMSG: Function<CommandSender, RecipientTestResult>?
) {
private val config: IHaveConfig? = null // TODO: Use this
private static ChannelComponent component; @JvmField
val isEnabled = component.config.getData("${this.identifier}.enabled", true)
private String defDisplayName; /**
private Color defColor; * Must start with a color code
*/
@JvmField
val displayName: ConfigData<String> =
component.config.getData("${this.identifier}.displayName", this.defDisplayName)
private IHaveConfig config; @JvmField
val color: ConfigData<Color> = component.config.getData("${this.identifier}.color",
this.defColor, { c -> Color.valueOf((c as String)) }, Color::toString
)
public final ConfigData<Boolean> Enabled; @JvmField
val extraIdentifiers: ListConfigData<String> = component.config.getListData("${this.identifier}.IDs", listOf())
/** val isGlobal: Boolean
* Must start with a color code get() = filterAndErrorMSG == null
*/
public final ConfigData<String> DisplayName;
public final ConfigData<Color> Color; /**
public final String ID; * Note: Errors are sent to the sender automatically
*
* @param sender The user we're sending to
* @param score The (source) score to compare with the user's
*/
fun shouldSendTo(sender: CommandSender, score: Int): Boolean {
return score == getMCScore(sender) //If there's any error, the score won't be equal
}
public ConfigData<String[]> IDs; /**
* Note: Errors are sent to the sender automatically
*/
fun getMCScore(sender: CommandSender): Int {
return getRTR(sender).score //No need to check if there was an error
}
/** /**
* Filters both the sender and the targets * Note: Errors are sent to the sender automatically<br></br>
*/ *
private final Function<CommandSender, RecipientTestResult> filteranderrormsg; *
* Null means don't send
*/
fun getGroupID(sender: CommandSender): String? {
return getRTR(sender).groupID //No need to check if there was an error
}
private static final List<Channel> channels = new ArrayList<>(); fun getRTR(sender: CommandSender): RecipientTestResult {
return filterAndErrorMSG?.apply(sender) ?: RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE)
}
/** class RecipientTestResult {
* Creates a channel. @JvmField
* val errormessage: String?
* @param displayname The name that should appear at the start of the message. <b>A chat color is expected at the beginning (§9).</b>
* @param color The default color of the messages sent in the channel
* @param command The command to be used for the channel <i>without /</i>. For example "mod". It's also used for scoreboard objective names.
* @param filteranderrormsg Checks all senders against the criteria provided here and sends the message if the index matches the sender's - if no score at all, displays the error.<br>
* May be null to send to everyone.
*/
public Channel(String displayname, Color color, String command,
Function<CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = filteranderrormsg;
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
}
/** @JvmField
* Must be only called from a subclass - otherwise it'll throw an exception. val score // Anything below 0 is "never send"
* : Int
* @see Channel#Channel(String, Color, String, Function)
*/
@SuppressWarnings("unchecked")
protected <T extends Channel> Channel(String displayname, Color color, String command,
BiFunction<T, CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
}
private static void init() { @JvmField
if (component == null) val groupID: String?
component = (ChannelComponent) Component.getComponents().get(ChannelComponent.class);
if (component == null)
throw new RuntimeException("Attempting to create a channel before the component is registered!");
}
public boolean isGlobal() { /**
return filteranderrormsg == null; * Creates a result that indicates an **error**
} *
* @param errormessage The error message to show the sender if they don't meet the criteria.
*/
constructor(errormessage: String?) {
this.errormessage = errormessage
score = SCORE_SEND_NOPE
groupID = null
}
/** /**
* Note: Errors are sent to the sender automatically * Creates a result that indicates a **success**
* *
* @param sender The user we're sending to * @param score The score that identifies the target group. **Must be non-negative.** For example, the index of the town or nation to send to.
* @param score The (source) score to compare with the user's * @param groupID The ID of the target group.
*/ */
public boolean shouldSendTo(CommandSender sender, int score) { constructor(score: Int, groupID: String?) {
return score == getMCScore(sender); //If there's any error, the score won't be equal require(score >= 0) { "Score must be non-negative!" }
} this.score = score
this.groupID = groupID
errormessage = null
}
/** companion object {
* Note: Errors are sent to the sender automatically @JvmField
*/ val ALL = RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE)
public int getMCScore(CommandSender sender) { }
return getRTR(sender).score; //No need to check if there was an error }
}
/** companion object {
* Note: Errors are sent to the sender automatically<br> /**
* <p> * Specifies a score that means it's OK to send - but it does not define any groups, only send or not send. See [.GROUP_EVERYONE]
* Null means don't send */
*/ const val SCORE_SEND_OK = 0
@Nullable
public String getGroupID(CommandSender sender) {
return getRTR(sender).groupID; //No need to check if there was an error
}
public RecipientTestResult getRTR(CommandSender sender) { /**
if (filteranderrormsg == null) * Specifies a score that means the user doesn't have permission to see or send the message. Any negative value has the same effect.
return new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE); */
return filteranderrormsg.apply(sender); const val SCORE_SEND_NOPE = -1
}
/** /**
* Get a stream of the enabled channels * Send the message to everyone *who has access to the channel* - this does not necessarily mean all players
* */
* @return Only the enabled channels const val GROUP_EVERYONE = "everyone"
*/ private val component: ChannelComponent by lazy {
public static Stream<Channel> getChannels() { get(ChannelComponent::class.java)
return channels.stream().filter(ch -> ch.Enabled.get()); ?: throw RuntimeException("Attempting to create a channel before the component is registered!")
} }
private val channels: MutableList<Channel> = ArrayList()
/** /**
* Return all channels whether they're enabled or not * Get a stream of the enabled channels
* *
* @return A list of all channels * @return Only the enabled channels
*/ */
public static List<Channel> getChannelList() { @JvmStatic
return Collections.unmodifiableList(channels); fun getChannels(): Stream<Channel> {
} return channels.stream().filter { ch: Channel -> ch.isEnabled.get() }
}
/** @JvmStatic
* Convenience method for the function parameter of {@link #Channel(String, Color, String, Function)}. It checks if the sender is OP or optionally has the specified group. The error message is val channelList: List<Channel>
* generated automatically. /**
* * Return all channels whether they're enabled or not
* @param permgroup The group that can access the channel or <b>null</b> to only allow OPs. *
* @return If has access * @return A list of all channels
*/ */
public static Function<CommandSender, RecipientTestResult> inGroupFilter(String permgroup) { get() = Collections.unmodifiableList(channels)
return noScoreResult(
s -> s.isOp() || (permgroup != null && (s instanceof Player && MainPlugin.permission != null && MainPlugin.permission.playerInGroup((Player) s, permgroup))),
"You need to be a(n) " + (permgroup != null ? permgroup : "OP") + " to use this channel.");
}
public static Function<CommandSender, RecipientTestResult> noScoreResult(Predicate<CommandSender> filter, /**
String errormsg) { * Convenience method for the function parameter of [.Channel]. It checks if the sender is OP or optionally has the specified group. The error message is
return s -> filter.test(s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg); * generated automatically.
} *
* @param permgroup The group that can access the channel or **null** to only allow OPs.
* @return If has access
*/
fun inGroupFilter(permgroup: String?): Function<CommandSender, RecipientTestResult> {
return noScoreResult(
{ s ->
s.isOp || s is Player && permgroup?.let { pg ->
MainPlugin.permission?.playerInGroup(
s,
pg
)
} ?: false
},
"You need to be a(n) " + (permgroup ?: "OP") + " to use this channel."
)
}
public static <T extends Channel> BiFunction<T, CommandSender, RecipientTestResult> noScoreResult( fun noScoreResult(
BiPredicate<T, CommandSender> filter, String errormsg) { filter: Predicate<CommandSender>,
return (this_, s) -> filter.test(this_, s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg); errormsg: String?
} ): Function<CommandSender, RecipientTestResult> {
return Function { s ->
if (filter.test(s)) RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE)
else RecipientTestResult(errormsg)
}
}
public static Channel GlobalChat; @JvmField
public static Channel AdminChat; var GlobalChat: Channel? = null
public static Channel ModChat; var AdminChat: Channel? = null
var ModChat: Channel? = null
public static void RegisterChannel(Channel channel) { @JvmStatic
if (!channel.isGlobal() && !ComponentManager.isEnabled(ChannelComponent.class)) fun registerChannel(channel: Channel) {
return; //Allow registering the global chat (and I guess other chats like the RP chat) if (!channel.isGlobal && !component.isEnabled) return //Allow registering the global chat (and I guess other chats like the RP chat)
channels.add(channel); channels.add(channel)
component.registerChannelCommand(channel); component.registerChannelCommand(channel)
Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> Bukkit.getPluginManager().callEvent(new ChatChannelRegisterEvent(channel))); // Wait for server start Bukkit.getScheduler().runTask(component.plugin,
} Runnable {
Bukkit.getPluginManager().callEvent(ChatChannelRegisterEvent(channel))
}) // Wait for server start
}
}
public static class RecipientTestResult {
public final String errormessage;
public final int score; // Anything below 0 is "never send"
public final String groupID;
public static final RecipientTestResult ALL = new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
/**
* Creates a result that indicates an <b>error</b>
*
* @param errormessage The error message to show the sender if they don't meet the criteria.
*/
public RecipientTestResult(String errormessage) {
this.errormessage = errormessage;
this.score = SCORE_SEND_NOPE;
this.groupID = null;
}
/**
* Creates a result that indicates a <b>success</b>
*
* @param score The score that identifies the target group. <b>Must be non-negative.</b> For example, the index of the town or nation to send to.
* @param groupID The ID of the target group.
*/
public RecipientTestResult(int score, String groupID) {
if (score < 0) throw new IllegalArgumentException("Score must be non-negative!");
this.score = score;
this.groupID = groupID;
this.errormessage = null;
}
}
} }

View file

@ -47,12 +47,12 @@ public class ChannelComponent extends Component<JavaPlugin> {
@Override @Override
public String getCommandPath() { public String getCommandPath() {
return channel.ID; return channel.identifier;
} }
@Override @Override
public String[] getCommandPaths() { public String[] getCommandPaths() {
return channel.IDs.get(); return channel.extraIdentifiers.get();
} }
@Command2.Subcommand @Command2.Subcommand
@ -74,7 +74,7 @@ public class ChannelComponent extends Component<JavaPlugin> {
if (channel instanceof ChatRoom) if (channel instanceof ChatRoom)
((ChatRoom) channel).joinRoom(sender); ((ChatRoom) channel).joinRoom(sender);
} }
sender.sendMessage("§6You are now talking in: §b" + user.channel.get().DisplayName.get()); sender.sendMessage("§6You are now talking in: §b" + user.channel.get().displayName.get());
} else } else
TBMCChatAPI.SendChatMessage(ChatMessage.builder(sender, user, message).fromCommand(true) TBMCChatAPI.SendChatMessage(ChatMessage.builder(sender, user, message).fromCommand(true)
.permCheck(senderMC.getPermCheck()).build(), channel); .permCheck(senderMC.getPermCheck()).build(), channel);

View file

@ -1,28 +1,35 @@
package buttondevteam.core.component.channel; package buttondevteam.core.component.channel
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent
import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.Color
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCChatAPI
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender
import java.util.ArrayList; class ChatRoom(displayname: String, color: Color, command: String) : Channel(
import java.util.List; displayname, color, command, null // TODO: Custom filter for rooms using abstract method
) {
private val usersInRoom: MutableList<CommandSender> = ArrayList()
private fun isInRoom(sender: CommandSender): Boolean {
return usersInRoom.contains(sender)
}
public class ChatRoom extends Channel { fun joinRoom(sender: CommandSender) {
private final List<CommandSender> usersInRoom = new ArrayList<>(); usersInRoom.add(sender)
TBMCChatAPI.SendSystemMessage(
this,
RecipientTestResult.ALL,
sender.name + " joined the room",
TBMCSystemChatEvent.BroadcastTarget.ALL
) //Always show message in the same kind of channel
}
public ChatRoom(String displayname, Color color, String command) { fun leaveRoom(sender: CommandSender) {
<ChatRoom>super(displayname, color, command, noScoreResult((this_, s) -> this_.usersInRoom.contains(s), usersInRoom.remove(sender)
"Not implemented yet. Please report it to the devs along with which platform you're trying to talk from.")); TBMCChatAPI.SendSystemMessage(
} this,
RecipientTestResult.ALL,
public void joinRoom(CommandSender sender) { sender.name + " left the room",
usersInRoom.add(sender); ChannelComponent.roomJoinLeave
TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " joined the room", TBMCSystemChatEvent.BroadcastTarget.ALL); //Always show message in the same kind of channel )
} }
public void leaveRoom(CommandSender sender) {
usersInRoom.remove(sender);
TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " left the room", ChannelComponent.roomJoinLeave);
}
} }

View file

@ -3,7 +3,6 @@ package buttondevteam.lib.architecture
import buttondevteam.buttonproc.HasConfig import buttondevteam.buttonproc.HasConfig
import buttondevteam.core.ComponentManager import buttondevteam.core.ComponentManager
import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.Component.Companion.updateConfig
import buttondevteam.lib.chat.Command2MC import buttondevteam.lib.chat.Command2MC
import buttondevteam.lib.chat.ICommand2MC import buttondevteam.lib.chat.ICommand2MC
import org.bukkit.configuration.InvalidConfigurationException import org.bukkit.configuration.InvalidConfigurationException
@ -17,7 +16,8 @@ import java.util.function.Consumer
@HasConfig(global = true) @HasConfig(global = true)
abstract class ButtonPlugin : JavaPlugin() { abstract class ButtonPlugin : JavaPlugin() {
protected val iConfig = IHaveConfig { saveConfig() } protected var iConfig = getIConfigInstance()
private set
private var yaml: YamlConfiguration? = null private var yaml: YamlConfiguration? = null
protected val data //TODO protected val data //TODO
@ -52,12 +52,16 @@ abstract class ButtonPlugin : JavaPlugin() {
IHaveConfig.pregenConfig(this, null) IHaveConfig.pregenConfig(this, null)
} }
private fun getIConfigInstance(): IHaveConfig {
return IHaveConfig(
::saveConfig,
this.config.getConfigurationSection("global") ?: this.config.createSection("global")
)
}
private fun reloadIConfig(): Boolean { private fun reloadIConfig(): Boolean {
val config = config iConfig = getIConfigInstance()
var section = config.getConfigurationSection("global") return isConfigLoaded // If loading fails, getConfig() returns a temporary instance
if (section == null) section = config.createSection("global")
iConfig.reset(section)
return configLoaded // If loading fails, getConfig() returns a temporary instance
} }
override fun onDisable() { override fun onDisable() {
@ -79,7 +83,7 @@ abstract class ButtonPlugin : JavaPlugin() {
fun tryReloadConfig(): Boolean { fun tryReloadConfig(): Boolean {
if (!justReload()) return false if (!justReload()) return false
reloadIConfig() reloadIConfig()
componentStack.forEach(Consumer { c: Component<*>? -> updateConfig(this, c!!) }) componentStack.forEach(Consumer { c -> c.updateComponentData() })
return true return true
} }
@ -122,10 +126,10 @@ abstract class ButtonPlugin : JavaPlugin() {
} }
override fun saveConfig() { override fun saveConfig() {
if (configLoaded) super.saveConfig() if (isConfigLoaded) super.saveConfig()
} }
val configLoaded get() = yaml != null val isConfigLoaded get() = yaml != null
/** /**
* Registers command and sets its plugin. * Registers command and sets its plugin.
@ -142,6 +146,7 @@ abstract class ButtonPlugin : JavaPlugin() {
annotation class ConfigOpts(val disableConfigGen: Boolean = false) annotation class ConfigOpts(val disableConfigGen: Boolean = false)
companion object { companion object {
//Needs to be static as we don't know the plugin when a command is handled //Needs to be static as we don't know the plugin when a command is handled
@JvmStatic
val command2MC = Command2MC() val command2MC = Command2MC()
fun configGenAllowed(obj: Any): Boolean { fun configGenAllowed(obj: Any): Boolean {
return !Optional.ofNullable(obj.javaClass.getAnnotation(ConfigOpts::class.java)) return !Optional.ofNullable(obj.javaClass.getAnnotation(ConfigOpts::class.java))

View file

@ -18,10 +18,10 @@ import java.util.stream.Collectors
@HasConfig(global = false) //Used for obtaining javadoc @HasConfig(global = false) //Used for obtaining javadoc
abstract class Component<TP : JavaPlugin> { abstract class Component<TP : JavaPlugin> {
var isEnabled = false var isEnabled = false
private var wrapper: ButtonComponent<TP>? = null internal var componentData: ComponentData<TP>? = null
val config get() = wrapper!!.config val config get() = componentData!!.config
val plugin get() = wrapper!!.plugin val plugin get() = componentData!!.plugin
private val data //TODO private val data //TODO
: IHaveConfig? = null : IHaveConfig? = null
@ -120,11 +120,34 @@ abstract class Component<TP : JavaPlugin> {
return res return res
} }
private val className: String private val className: String get() = javaClass.simpleName
get() = javaClass.simpleName
internal fun updateComponentData(plugin: TP = this.plugin) {
componentData = ComponentData(plugin, { plugin.saveConfig() }, this.getConfigSection(plugin))
}
private fun getConfigSection(plugin: JavaPlugin): ConfigurationSection {
var compconf = plugin.config.getConfigurationSection("components")
if (compconf == null) compconf = plugin.config.createSection("components")
var configSect = compconf.getConfigurationSection(className)
if (configSect == null) configSect = compconf.createSection(className)
return configSect
// TODO: Support tests (provide Bukkit configuration for tests)
}
companion object { companion object {
private val components = HashMap<Class<out Component<*>>, Component<out JavaPlugin>>() private val _components = HashMap<Class<out Component<*>>, Component<out JavaPlugin>>()
/**
* Returns the currently registered components<br></br>
*
* @return The currently registered components
*/
@JvmStatic
val components: Map<Class<out Component<*>>, Component<out JavaPlugin>>
get() {
return Collections.unmodifiableMap(_components)
}
/** /**
* Registers a component checking it's dependencies and calling [.register].<br></br> * Registers a component checking it's dependencies and calling [.register].<br></br>
@ -178,10 +201,11 @@ abstract class Component<TP : JavaPlugin> {
) )
return false return false
} }
val wrapper = ButtonComponent(plugin, { plugin.saveConfig() }, getConfigSection(plugin, component))
component.register(plugin) component.register(plugin)
components[component.javaClass] = component // The plugin is saved with this call, so it must be specified
if (plugin is ButtonPlugin) (plugin as ButtonPlugin).componentStack.push(component) component.updateComponentData(plugin)
_components[component.javaClass] = component
if (plugin is ButtonPlugin) plugin.componentStack.push(component)
if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) { if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) {
return try { //Enable components registered after the previous ones getting enabled return try { //Enable components registered after the previous ones getting enabled
setComponentEnabled(component, true) setComponentEnabled(component, true)
@ -220,7 +244,7 @@ abstract class Component<TP : JavaPlugin> {
} }
} }
component.unregister(plugin) component.unregister(plugin)
components.remove(component.javaClass) _components.remove(component.javaClass)
} }
true true
} catch (e: Exception) { } catch (e: Exception) {
@ -242,7 +266,7 @@ abstract class Component<TP : JavaPlugin> {
if (component.isEnabled == enabled) return //Don't do anything if (component.isEnabled == enabled) return //Don't do anything
if (enabled.also { component.isEnabled = it }) { if (enabled.also { component.isEnabled = it }) {
try { try {
getConfigSection(component.plugin!!, component) component.getConfigSection(component.plugin)
component.enable() component.enable()
if (ButtonPlugin.configGenAllowed(component)) { if (ButtonPlugin.configGenAllowed(component)) {
IHaveConfig.pregenConfig(component, null) IHaveConfig.pregenConfig(component, null)
@ -267,24 +291,5 @@ abstract class Component<TP : JavaPlugin> {
ButtonPlugin.command2MC.unregisterCommands(component) ButtonPlugin.command2MC.unregisterCommands(component)
} }
} }
private fun getConfigSection(plugin: JavaPlugin, component: Component<*>): ConfigurationSection {
var compconf = plugin.config.getConfigurationSection("components")
if (compconf == null) compconf = plugin.config.createSection("components")
var configSect = compconf.getConfigurationSection(component.className)
if (configSect == null) configSect = compconf.createSection(component.className)
return configSect
// TODO: Support tests (provide Bukkit configuration for tests)
}
/**
* Returns the currently registered components<br></br>
*
* @return The currently registered components
*/
@JvmStatic
fun getComponents(): Map<Class<out Component<*>>, Component<out JavaPlugin>> {
return Collections.unmodifiableMap(components)
}
} }
} }

View file

@ -6,10 +6,10 @@ import org.bukkit.plugin.java.JavaPlugin
/** /**
* A wrapper for plugin components. This is used internally. * A wrapper for plugin components. This is used internally.
*/ */
class ButtonComponent<TP : JavaPlugin>( class ComponentData<TP : JavaPlugin>(
val plugin: TP, val plugin: TP,
saveAction: Runnable, saveAction: Runnable,
config: ConfigurationSection config: ConfigurationSection
) { ) {
val config = IHaveConfig(saveAction, config) val config = IHaveConfig(saveAction, config) // TODO: Use lateinit instead of this class
} }

View file

@ -82,33 +82,6 @@ class ConfigData<T> internal constructor(
private class SaveTask(val task: BukkitTask, val saveAction: Runnable) private class SaveTask(val task: BukkitTask, val saveAction: Runnable)
class ConfigDataBuilder<T> internal constructor(
private val config: IHaveConfig,
private val path: String,
private val primitiveDef: Any?,
private val getter: Function<Any?, T>,
private val setter: Function<T, Any?>
) {
/**
* Builds a modifiable config representation. Use if you want to change the value *in code*.
*
* @return A ConfigData instance.
*/
fun build(readOnly: Boolean = false): ConfigData<T> {
val config = ConfigData(config, path, primitiveDef, getter, setter, readOnly)
this.config.onConfigBuild(config)
return config
}
fun buildList(readOnly: Boolean = false): ListConfigData<T> {
if (primitiveDef is List<*>) {
val config = ListConfigData(config, path, primitiveDef, getter, setter, readOnly)
this.config.onConfigBuild(config)
return config
}
}
}
companion object { companion object {
private val saveTasks = HashMap<Configuration, SaveTask>() private val saveTasks = HashMap<Configuration, SaveTask>()
fun signalChange(config: IHaveConfig) { fun signalChange(config: IHaveConfig) {
@ -147,9 +120,5 @@ class ConfigData<T> internal constructor(
} }
return false return false
} }
fun <T> builder(config: IHaveConfig, path: String, primitiveDef: Any?, getter: Function<Any?, T>, setter: Function<T, Any?>): ConfigDataBuilder<T> {
return ConfigDataBuilder(config, path, primitiveDef, getter, setter)
}
} }
} }

View file

@ -2,7 +2,6 @@ package buttondevteam.lib.architecture
import buttondevteam.core.MainPlugin import buttondevteam.core.MainPlugin
import buttondevteam.lib.TBMCCoreAPI import buttondevteam.lib.TBMCCoreAPI
import buttondevteam.lib.architecture.ConfigData.ConfigDataBuilder
import buttondevteam.lib.architecture.config.IConfigData import buttondevteam.lib.architecture.config.IConfigData
import org.bukkit.Bukkit import org.bukkit.Bukkit
import org.bukkit.configuration.ConfigurationSection import org.bukkit.configuration.ConfigurationSection
@ -11,7 +10,6 @@ import java.lang.reflect.InvocationTargetException
import java.util.* import java.util.*
import java.util.function.Function import java.util.function.Function
import java.util.function.Predicate import java.util.function.Predicate
import java.util.function.Supplier
import java.util.stream.Collectors import java.util.stream.Collectors
/** /**
@ -30,33 +28,6 @@ class IHaveConfig(
) { ) {
private val datamap = HashMap<String, IConfigData<*>>() private val datamap = HashMap<String, IConfigData<*>>()
/**
* You may use this method with any data type, but always provide getters and setters that convert to primitive types
* if you're not using primitive types directly.
* These primitive types are strings, numbers, characters, booleans and lists of these things.
*
* @param path The path in config to use
* @param def The value to use by default
* @param getter A function that converts a primitive representation to the correct value
* @param setter A function that converts a value to a primitive representation
* @param primDef Whether the default value is a primitive value that needs to be converted to the correct type using the getter
* @param T The type of this variable (can be any class)
* @return The data object that can be used to get or set the value
*/
@Suppress("UNCHECKED_CAST")
fun <T> getConfig( // TODO: Remove
path: String,
def: T,
getter: Function<Any?, T>? = null,
setter: Function<T, Any?>? = null
): ConfigDataBuilder<T> {
return ConfigData.builder(this, path, if (setter != null) setter.apply(def) else def, getter ?: Function { it as T }, setter ?: Function { it })
}
fun onConfigBuild(config: IConfigData<*>) {
datamap[config.path] = config
}
/** /**
* You may use this method with any data type, but always provide getters and setters that convert to primitive types * You may use this method with any data type, but always provide getters and setters that convert to primitive types
* if you're not using primitive types directly. * if you're not using primitive types directly.
@ -109,30 +80,26 @@ class IHaveConfig(
* This method overload should only be used with primitves or String. * This method overload should only be used with primitves or String.
* *
* @param path The path in config to use * @param path The path in config to use
* @param def The value to use by default
* @param <T> The type of this variable (only use primitives or String) * @param <T> The type of this variable (only use primitives or String)
* @return The data object that can be used to get or set the value * @return The data object that can be used to get or set the value
</T> */ </T> */
fun <T> getData(path: String, def: Supplier<T>): ConfigData<T> { // TODO: Remove @Suppress("UNCHECKED_CAST")
fun <T> getListData(
path: String,
def: List<T>,
elementGetter: Function<Any?, T>? = null,
elementSetter: Function<T, Any?>? = null,
readOnly: Boolean = false
): ListConfigData<T> {
var data = datamap[path] var data = datamap[path]
if (data == null) { if (data == null) datamap[path] = ListConfigData(
val defval = def.get() this,
datamap[path] = ConfigData(this, path, defval, defval, null, null).also { data = it } path,
} def,
@Suppress("UNCHECKED_CAST") elementGetter ?: Function { it as T },
return data as ConfigData<T> elementSetter ?: Function { it },
} readOnly
).also { data = it }
/**
* This method overload should only be used with primitves or String.
*
* @param path The path in config to use
* @param <T> The type of this variable (only use primitives or String)
* @return The data object that can be used to get or set the value
</T> */
fun <T> getListData(path: String): ListConfigData<T> {
var data = datamap[path]
if (data == null) datamap[path] = ListConfigData(this, path, ListConfigData.List<T>()).also { data = it }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return data as ListConfigData<T> return data as ListConfigData<T>
} }

View file

@ -62,7 +62,11 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
annotation class OptionalArg annotation class OptionalArg
protected class ParamConverter<T>(val converter: Function<String, T>, val errormsg: String, val allSupplier: Supplier<Iterable<String>>) protected class ParamConverter<T>(
val converter: Function<String, T?>,
val errormsg: String,
val allSupplier: Supplier<Iterable<String>>
)
protected val paramConverters = HashMap<Class<*>, ParamConverter<*>>() protected val paramConverters = HashMap<Class<*>, ParamConverter<*>>()
private val commandHelp = ArrayList<String>() //Mainly needed by Discord private val commandHelp = ArrayList<String>() //Mainly needed by Discord
@ -70,15 +74,17 @@ abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender>(
/** /**
* Adds a param converter that obtains a specific object from a string parameter. * Adds a param converter that obtains a specific object from a string parameter.
* The converter may return null. * The converter may return null to signal an error.
* *
* @param <T> The type of the result * @param <T> The type of the result
* @param cl The class of the result object * @param cl The class of the result object
* @param converter The converter to use * @param converter The converter to use
* @param allSupplier The supplier of all possible values (ideally) * @param allSupplier The supplier of all possible values (ideally)
</T> */ </T> */
open fun <T> addParamConverter(cl: Class<T>, converter: Function<String, T>, errormsg: String, open fun <T> addParamConverter(
allSupplier: Supplier<Iterable<String>>) { cl: Class<T>, converter: Function<String, T?>, errormsg: String,
allSupplier: Supplier<Iterable<String>>
) {
paramConverters[cl] = ParamConverter(converter, errormsg, allSupplier) paramConverters[cl] = ParamConverter(converter, errormsg, allSupplier)
} }

View file

@ -125,7 +125,12 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
* Automatically colors the message red. * Automatically colors the message red.
* {@see super#addParamConverter} * {@see super#addParamConverter}
*/ */
override fun <T> addParamConverter(cl: Class<T>, converter: Function<String, T>, errormsg: String, allSupplier: Supplier<Iterable<String>>) { override fun <T> addParamConverter(
cl: Class<T>,
converter: Function<String, T?>,
errormsg: String,
allSupplier: Supplier<Iterable<String>>
) {
super.addParamConverter(cl, converter, "§c$errormsg", allSupplier) super.addParamConverter(cl, converter, "§c$errormsg", allSupplier)
} }
@ -213,7 +218,7 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
private class BukkitCommand(name: String?) : Command(name) { private class BukkitCommand(name: String?) : Command(name) {
override fun execute(sender: CommandSender, commandLabel: String, args: Array<String>): Boolean { override fun execute(sender: CommandSender, commandLabel: String, args: Array<String>): Boolean {
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args) return ButtonPlugin.command2MC.executeCommand(sender, this, commandLabel, args)
} }
@Throws(IllegalArgumentException::class) @Throws(IllegalArgumentException::class)
@ -312,10 +317,9 @@ class Command2MC : Command2<ICommand2MC, Command2MCSender>('/', true), Listener
j++ j++
continue continue
} }
//Break if converter is not found or for example, the player provided an invalid plugin name
val converter = getParamConverter(params[j].type, command2MC) ?: break val converter = getParamConverter(params[j].type, command2MC) ?: break
val paramValue = converter.converter.apply(paramValueString) val paramValue = converter.converter.apply(paramValueString) ?: break
?: //For example, the player provided an invalid plugin name
break
args[j] = paramValue args[j] = paramValue
k++ //Only increment if not CommandSender k++ //Only increment if not CommandSender
j++ j++

View file

@ -35,9 +35,9 @@ public class TBMCChatAPI {
*/ */
public static boolean SendChatMessage(ChatMessage cm, Channel channel) { public static boolean SendChatMessage(ChatMessage cm, Channel channel) {
if (!Channel.getChannelList().contains(channel)) if (!Channel.getChannelList().contains(channel))
throw new RuntimeException("Channel " + channel.DisplayName.get() + " not registered!"); throw new RuntimeException("Channel " + channel.getDisplayName().get() + " not registered!");
if (!channel.Enabled.get()) { if (!channel.isEnabled.get()) {
cm.getSender().sendMessage("§cThe channel '" + channel.DisplayName.get() + "' is disabled!"); cm.getSender().sendMessage("§cThe channel '" + channel.displayName.get() + "' is disabled!");
return true; //Cancel sending if channel is disabled return true; //Cancel sending if channel is disabled
} }
Supplier<Boolean> task = () -> { Supplier<Boolean> task = () -> {
@ -70,11 +70,11 @@ public class TBMCChatAPI {
*/ */
public static boolean SendSystemMessage(Channel channel, RecipientTestResult rtr, String message, TBMCSystemChatEvent.BroadcastTarget target, String... exceptions) { public static boolean SendSystemMessage(Channel channel, RecipientTestResult rtr, String message, TBMCSystemChatEvent.BroadcastTarget target, String... exceptions) {
if (!Channel.getChannelList().contains(channel)) if (!Channel.getChannelList().contains(channel))
throw new RuntimeException("Channel " + channel.DisplayName.get() + " not registered!"); throw new RuntimeException("Channel " + channel.displayName.get() + " not registered!");
if (!channel.Enabled.get()) if (!channel.enabled.get())
return true; //Cancel sending return true; //Cancel sending
if (!Arrays.asList(exceptions).contains("Minecraft")) if (!Arrays.asList(exceptions).contains("Minecraft"))
Bukkit.getConsoleSender().sendMessage("[" + channel.DisplayName.get() + "] " + message); Bukkit.getConsoleSender().sendMessage("[" + channel.displayName.get() + "] " + message);
TBMCSystemChatEvent event = new TBMCSystemChatEvent(channel, message, rtr.score, rtr.groupID, exceptions, target); TBMCSystemChatEvent event = new TBMCSystemChatEvent(channel, message, rtr.score, rtr.groupID, exceptions, target);
return ChromaUtils.callEventAsync(event); return ChromaUtils.callEventAsync(event);
} }
@ -92,6 +92,6 @@ public class TBMCChatAPI {
* @param channel A new {@link Channel} to register * @param channel A new {@link Channel} to register
*/ */
public static void RegisterChatChannel(Channel channel) { public static void RegisterChatChannel(Channel channel) {
Channel.RegisterChannel(channel); Channel.registerChannel(channel);
} }
} }

View file

@ -313,5 +313,5 @@ public abstract class ChromaGamerBase {
//----------------------------------------------------------------- //-----------------------------------------------------------------
public final ConfigData<Channel> channel = config.getData("channel", Channel.GlobalChat, public final ConfigData<Channel> channel = config.getData("channel", Channel.GlobalChat,
id -> Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID); id -> Channel.getChannels().filter(ch -> ch.identifier.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID);
} }