Convert some code to Kotlin
Why not Lombok didn't work and I had other frustations with Java streams Removed some unnecessary tab complete code
This commit is contained in:
parent
9a859de583
commit
0bf1f9789b
9 changed files with 1143 additions and 1250 deletions
|
@ -35,11 +35,11 @@
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.0</version>
|
<version>3.8.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<compilerArgument>-proc:none</compilerArgument>
|
<compilerArgument>-proc:none</compilerArgument>
|
||||||
<source>8</source>
|
<source>17</source>
|
||||||
<target>8</target>
|
<target>17</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
|
@ -27,31 +27,62 @@
|
||||||
</resources>
|
</resources>
|
||||||
<finalName>Chroma-Core</finalName>
|
<finalName>Chroma-Core</finalName>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>compile</id>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirs>
|
||||||
|
<sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
|
||||||
|
<sourceDir>${project.basedir}/src/main/java</sourceDir>
|
||||||
|
</sourceDirs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>test-compile</id>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sourceDirs>
|
||||||
|
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
|
||||||
|
<sourceDir>${project.basedir}/src/test/java</sourceDir>
|
||||||
|
</sourceDirs>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-shade-plugin</artifactId>
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
<version>3.2.1</version>
|
<version>3.2.1</version>
|
||||||
<executions>
|
<executions>
|
||||||
<execution>
|
<execution>
|
||||||
<phase>package</phase>
|
<phase>package</phase>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>shade</goal>
|
<goal>shade</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<artifactSet>
|
<artifactSet>
|
||||||
<includes>
|
<includes>
|
||||||
<include>me.lucko:commodore</include>
|
<include>me.lucko:commodore</include>
|
||||||
<include>org.javatuples:javatuples</include>
|
<include>org.javatuples:javatuples</include>
|
||||||
</includes>
|
</includes>
|
||||||
</artifactSet>
|
</artifactSet>
|
||||||
<relocations>
|
<relocations>
|
||||||
<relocation>
|
<relocation>
|
||||||
<pattern>me.lucko.commodore</pattern>
|
<pattern>me.lucko.commodore</pattern>
|
||||||
<!-- vvv Replace with the package of your plugin vvv -->
|
<!-- vvv Replace with the package of your plugin vvv -->
|
||||||
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
|
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
|
||||||
</relocation>
|
</relocation>
|
||||||
</relocations>
|
</relocations>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
@ -200,6 +231,11 @@
|
||||||
<artifactId>javatuples</artifactId>
|
<artifactId>javatuples</artifactId>
|
||||||
<version>1.2</version>
|
<version>1.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<organization>
|
<organization>
|
||||||
<name>TBMCPlugins</name>
|
<name>TBMCPlugins</name>
|
||||||
|
@ -217,6 +253,7 @@
|
||||||
<github.global.server>github</github.global.server>
|
<github.global.server>github</github.global.server>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<noprefix.version>1.0.1</noprefix.version>
|
<noprefix.version>1.0.1</noprefix.version>
|
||||||
|
<kotlin.version>1.8.10</kotlin.version>
|
||||||
</properties>
|
</properties>
|
||||||
<scm>
|
<scm>
|
||||||
<url>https://github.com/TBMCPlugins/mvn-repo</url>
|
<url>https://github.com/TBMCPlugins/mvn-repo</url>
|
||||||
|
|
|
@ -1,171 +1,158 @@
|
||||||
package buttondevteam.lib.architecture;
|
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.chat.Command2MC;
|
import buttondevteam.lib.architecture.Component.Companion.updateConfig
|
||||||
import buttondevteam.lib.chat.ICommand2MC;
|
import buttondevteam.lib.chat.Command2MC
|
||||||
import lombok.AccessLevel;
|
import buttondevteam.lib.chat.Command2MC.registerCommand
|
||||||
import lombok.Getter;
|
import buttondevteam.lib.chat.Command2MC.unregisterCommands
|
||||||
import org.bukkit.configuration.InvalidConfigurationException;
|
import buttondevteam.lib.chat.ICommand2MC
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import lombok.AccessLevel
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
import lombok.Getter
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.configuration.InvalidConfigurationException
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration
|
||||||
import java.io.File;
|
import org.bukkit.configuration.file.YamlConfiguration
|
||||||
import java.io.IOException;
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
import java.lang.annotation.ElementType;
|
import java.io.File
|
||||||
import java.lang.annotation.Retention;
|
import java.io.IOException
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.util.*
|
||||||
import java.lang.annotation.Target;
|
import java.util.function.Consumer
|
||||||
import java.util.Arrays;
|
import java.util.function.Function
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Stack;
|
|
||||||
|
|
||||||
@HasConfig(global = true)
|
@HasConfig(global = true)
|
||||||
public abstract class ButtonPlugin extends JavaPlugin {
|
abstract class ButtonPlugin : JavaPlugin() {
|
||||||
@Getter //Needs to be static as we don't know the plugin when a command is handled
|
@Getter(AccessLevel.PROTECTED)
|
||||||
private static final Command2MC command2MC = new Command2MC();
|
private val iConfig = IHaveConfig { saveConfig() }
|
||||||
@Getter(AccessLevel.PROTECTED)
|
private var yaml: CommentedConfiguration? = null
|
||||||
private final IHaveConfig iConfig = new IHaveConfig(this::saveConfig);
|
|
||||||
private CommentedConfiguration yaml;
|
|
||||||
@Getter(AccessLevel.PROTECTED)
|
|
||||||
private IHaveConfig data; //TODO
|
|
||||||
/**
|
|
||||||
* Used to unregister components in the right order - and to reload configs
|
|
||||||
*/
|
|
||||||
@Getter
|
|
||||||
private final Stack<Component<?>> componentStack = new Stack<>();
|
|
||||||
|
|
||||||
protected abstract void pluginEnable();
|
@Getter(AccessLevel.PROTECTED)
|
||||||
|
private val data //TODO
|
||||||
|
: IHaveConfig? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the components are unregistered
|
* Used to unregister components in the right order - and to reload configs
|
||||||
*/
|
*/
|
||||||
protected abstract void pluginDisable();
|
@Getter
|
||||||
|
private val componentStack = Stack<Component<*>>()
|
||||||
|
protected abstract fun pluginEnable()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called before the components are unregistered
|
* Called after the components are unregistered
|
||||||
*/
|
*/
|
||||||
protected void pluginPreDisable() {
|
protected abstract fun pluginDisable()
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
/**
|
||||||
public final void onEnable() {
|
* Called before the components are unregistered
|
||||||
if (!loadConfig()) {
|
*/
|
||||||
getLogger().warning("Please fix the issues and restart the server to load the plugin.");
|
protected fun pluginPreDisable() {}
|
||||||
return;
|
override fun onEnable() {
|
||||||
}
|
if (!loadConfig()) {
|
||||||
try {
|
logger.warning("Please fix the issues and restart the server to load the plugin.")
|
||||||
pluginEnable();
|
return
|
||||||
} catch (Exception e) {
|
}
|
||||||
TBMCCoreAPI.SendException("Error while enabling plugin " + getName() + "!", e, this);
|
try {
|
||||||
}
|
pluginEnable()
|
||||||
if (configGenAllowed(this)) //If it's not disabled (by default it's not)
|
} catch (e: Exception) {
|
||||||
IHaveConfig.pregenConfig(this, null);
|
TBMCCoreAPI.SendException("Error while enabling plugin $name!", e, this)
|
||||||
}
|
}
|
||||||
|
if (configGenAllowed(this)) //If it's not disabled (by default it's not)
|
||||||
|
IHaveConfig.pregenConfig(this, null)
|
||||||
|
}
|
||||||
|
|
||||||
private boolean loadConfig() {
|
private fun loadConfig(): Boolean {
|
||||||
var config = getConfig();
|
val config = config ?: return false
|
||||||
if (config == null)
|
var section = config.getConfigurationSection("global")
|
||||||
return false;
|
if (section == null) section = config.createSection("global")
|
||||||
var section = config.getConfigurationSection("global");
|
iConfig.reset(section)
|
||||||
if (section == null) section = config.createSection("global");
|
return true
|
||||||
iConfig.reset(section);
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
override fun onDisable() {
|
||||||
public final void onDisable() {
|
try {
|
||||||
try {
|
pluginPreDisable()
|
||||||
pluginPreDisable();
|
ComponentManager.unregComponents(this)
|
||||||
ComponentManager.unregComponents(this);
|
pluginDisable()
|
||||||
pluginDisable();
|
if (ConfigData.saveNow(config)) logger.info("Saved configuration changes.")
|
||||||
if (ConfigData.saveNow(getConfig()))
|
ButtonPlugin.getCommand2MC().unregisterCommands(this)
|
||||||
getLogger().info("Saved configuration changes.");
|
} catch (e: Exception) {
|
||||||
getCommand2MC().unregisterCommands(this);
|
TBMCCoreAPI.SendException("Error while disabling plugin $name!", e, this)
|
||||||
} catch (Exception e) {
|
}
|
||||||
TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e, this);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
override fun reloadConfig() {
|
||||||
public void reloadConfig() {
|
tryReloadConfig()
|
||||||
tryReloadConfig();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public boolean tryReloadConfig() {
|
fun tryReloadConfig(): Boolean {
|
||||||
if (!justReload()) return false;
|
if (!justReload()) return false
|
||||||
loadConfig();
|
loadConfig()
|
||||||
componentStack.forEach(c -> Component.updateConfig(this, c));
|
componentStack.forEach(Consumer { c: Component<*>? -> updateConfig(this, c!!) })
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean justReload() {
|
fun justReload(): Boolean {
|
||||||
if (yaml != null && ConfigData.saveNow(getConfig())) {
|
if (yaml != null && ConfigData.saveNow(config)) {
|
||||||
getLogger().warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.");
|
logger.warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.")
|
||||||
return false;
|
return false
|
||||||
}
|
}
|
||||||
var file = new File(getDataFolder(), "config.yml");
|
val file = File(dataFolder, "config.yml")
|
||||||
var yaml = new CommentedConfiguration(file);
|
val yaml = CommentedConfiguration(file)
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
try {
|
try {
|
||||||
yaml.load(file);
|
yaml.load(file)
|
||||||
} catch (IOException | InvalidConfigurationException e) {
|
} catch (e: IOException) {
|
||||||
getLogger().warning("Failed to load config! Check for syntax errors.");
|
logger.warning("Failed to load config! Check for syntax errors.")
|
||||||
e.printStackTrace();
|
e.printStackTrace()
|
||||||
return false;
|
return false
|
||||||
}
|
} catch (e: InvalidConfigurationException) {
|
||||||
}
|
logger.warning("Failed to load config! Check for syntax errors.")
|
||||||
this.yaml = yaml;
|
e.printStackTrace()
|
||||||
var res = getTextResource("configHelp.yml");
|
return false
|
||||||
if (res == null)
|
}
|
||||||
return true;
|
}
|
||||||
var yc = YamlConfiguration.loadConfiguration(res);
|
this.yaml = yaml
|
||||||
for (var kv : yc.getValues(true).entrySet())
|
val res = getTextResource("configHelp.yml") ?: return true
|
||||||
if (kv.getValue() instanceof String)
|
val yc = YamlConfiguration.loadConfiguration(res)
|
||||||
yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""),
|
for ((key, value) in yc.getValues(true)) if (value is String) yaml.addComment(key.replace(".generalDescriptionInsteadOfAConfig", ""),
|
||||||
Arrays.stream(((String) kv.getValue()).split("\n"))
|
*Arrays.stream<String>(value.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
|
||||||
.map(str -> "# " + str.trim()).toArray(String[]::new));
|
.map<String> { str: String -> "# " + str.trim { it <= ' ' } }.toArray<String> { _Dummy_.__Array__() })
|
||||||
return true;
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
override fun getConfig(): FileConfiguration {
|
||||||
public FileConfiguration getConfig() {
|
if (yaml == null) justReload()
|
||||||
if (yaml == null)
|
return if (yaml == null) YamlConfiguration() else yaml //Return a temporary instance
|
||||||
justReload();
|
}
|
||||||
if (yaml == null) return new YamlConfiguration(); //Return a temporary instance
|
|
||||||
return yaml;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
override fun saveConfig() {
|
||||||
public void saveConfig() {
|
try {
|
||||||
try {
|
if (yaml != null) yaml!!.save()
|
||||||
if (yaml != null)
|
} catch (e: Exception) {
|
||||||
yaml.save();
|
TBMCCoreAPI.SendException("Failed to save config", e, this)
|
||||||
} catch (Exception e) {
|
}
|
||||||
TBMCCoreAPI.SendException("Failed to save config", e, this);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers command and sets its plugin.
|
* Registers command and sets its plugin.
|
||||||
*
|
*
|
||||||
* @param command The command to register
|
* @param command The command to register
|
||||||
*/
|
*/
|
||||||
protected void registerCommand(ICommand2MC command) {
|
fun registerCommand(command: ICommand2MC) {
|
||||||
command.registerToPlugin(this);
|
command.registerToPlugin(this)
|
||||||
getCommand2MC().registerCommand(command);
|
ButtonPlugin.getCommand2MC().registerCommand(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
|
||||||
public @interface ConfigOpts {
|
annotation class ConfigOpts(val disableConfigGen: Boolean = false)
|
||||||
boolean disableConfigGen() default false;
|
companion object {
|
||||||
}
|
@Getter //Needs to be static as we don't know the plugin when a command is handled
|
||||||
|
|
||||||
public static boolean configGenAllowed(Object obj) {
|
private val command2MC = Command2MC()
|
||||||
return !Optional.ofNullable(obj.getClass().getAnnotation(ConfigOpts.class))
|
fun configGenAllowed(obj: Any): Boolean {
|
||||||
.map(ConfigOpts::disableConfigGen).orElse(false);
|
return !Optional.ofNullable(obj.javaClass.getAnnotation(ConfigOpts::class.java))
|
||||||
}
|
.map(Function<ConfigOpts, Boolean> { obj: ConfigOpts -> obj.disableConfigGen() }).orElse(false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,274 +1,281 @@
|
||||||
package buttondevteam.lib.architecture;
|
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.exceptions.UnregisteredComponentException;
|
import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException
|
||||||
import buttondevteam.lib.chat.ICommand2MC;
|
import buttondevteam.lib.chat.ICommand2MC
|
||||||
import lombok.Getter;
|
import lombok.Getter
|
||||||
import lombok.NonNull;
|
import org.bukkit.configuration.ConfigurationSection
|
||||||
import lombok.val;
|
import org.bukkit.event.Listener
|
||||||
import org.bukkit.configuration.ConfigurationSection;
|
import org.bukkit.plugin.java.JavaPlugin
|
||||||
import org.bukkit.event.Listener;
|
import java.util.*
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import java.util.function.Consumer
|
||||||
|
import java.util.function.Function
|
||||||
import java.util.Collections;
|
import java.util.stream.Collectors
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration is based on class name
|
* Configuration is based on class name
|
||||||
*/
|
*/
|
||||||
@HasConfig(global = false) //Used for obtaining javadoc
|
@HasConfig(global = false) //Used for obtaining javadoc
|
||||||
public abstract class Component<TP extends JavaPlugin> {
|
|
||||||
@SuppressWarnings("rawtypes") private static HashMap<Class<? extends Component>, Component<? extends JavaPlugin>> components = new HashMap<>();
|
|
||||||
|
|
||||||
@Getter
|
abstract class Component<TP : JavaPlugin?> {
|
||||||
private boolean enabled = false;
|
@Getter
|
||||||
@Getter
|
private var enabled = false
|
||||||
@NonNull
|
|
||||||
private TP plugin;
|
|
||||||
private @Getter final IHaveConfig config = new IHaveConfig(null);
|
|
||||||
private @Getter IHaveConfig data; //TODO
|
|
||||||
|
|
||||||
public final ConfigData<Boolean> shouldBeEnabled = config.getData("enabled",
|
@Getter
|
||||||
Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true));
|
private var plugin: TP = null
|
||||||
|
|
||||||
/**
|
@Getter
|
||||||
* Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.<br>
|
private val config = IHaveConfig(null)
|
||||||
* Make sure to register the dependencies first.<br>
|
|
||||||
* The component will be enabled automatically, regardless of when it was registered.<br>
|
|
||||||
* <b>If not using {@link ButtonPlugin}, call {@link ComponentManager#unregComponents(ButtonPlugin)} on plugin disable.</b>
|
|
||||||
*
|
|
||||||
* @param component The component to register
|
|
||||||
* @return Whether the component is registered successfully (it may have failed to enable)
|
|
||||||
*/
|
|
||||||
public static <T extends JavaPlugin> boolean registerComponent(T plugin, Component<T> component) {
|
|
||||||
return registerUnregisterComponent(plugin, component, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
@Getter
|
||||||
* Unregisters a component by calling {@link #unregister(JavaPlugin)}.<br>
|
private val data //TODO
|
||||||
* Make sure to unregister the dependencies last.<br>
|
: IHaveConfig? = null
|
||||||
* <b>Components will be unregistered in opposite order of registering by default by {@link ButtonPlugin} or {@link ComponentManager#unregComponents(ButtonPlugin)}.</b>
|
|
||||||
*
|
|
||||||
* @param component The component to unregister
|
|
||||||
* @return Whether the component is unregistered successfully (it also got disabled)
|
|
||||||
*/
|
|
||||||
public static <T extends JavaPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
|
|
||||||
return registerUnregisterComponent(plugin, component, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T extends JavaPlugin> boolean registerUnregisterComponent(T plugin, Component<T> component, boolean register) {
|
@JvmField
|
||||||
try {
|
val shouldBeEnabled = config.getData("enabled",
|
||||||
val metaAnn = component.getClass().getAnnotation(ComponentMetadata.class);
|
Optional.ofNullable(javaClass.getAnnotation(ComponentMetadata::class.java)).map(Function<ComponentMetadata, Boolean> { obj: ComponentMetadata -> obj.enabledByDefault() }).orElse(true))
|
||||||
if (metaAnn != null) {
|
|
||||||
@SuppressWarnings("rawtypes") Class<? extends Component>[] dependencies = metaAnn.depends();
|
|
||||||
for (val dep : dependencies) { //TODO: Support dependencies at enable/disable as well
|
|
||||||
if (!components.containsKey(dep)) {
|
|
||||||
plugin.getLogger().warning("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + " as a required dependency is missing/disabled: " + dep.getSimpleName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (register) {
|
|
||||||
if (components.containsKey(component.getClass())) {
|
|
||||||
TBMCCoreAPI.SendException("Failed to register component " + component.getClassName(), new IllegalArgumentException("The component is already registered!"), plugin);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
component.plugin = plugin;
|
|
||||||
component.config.setSaveAction(plugin::saveConfig);
|
|
||||||
updateConfig(plugin, component);
|
|
||||||
component.register(plugin);
|
|
||||||
components.put(component.getClass(), component);
|
|
||||||
if (plugin instanceof ButtonPlugin)
|
|
||||||
((ButtonPlugin) plugin).getComponentStack().push(component);
|
|
||||||
if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) {
|
|
||||||
try { //Enable components registered after the previous ones getting enabled
|
|
||||||
setComponentEnabled(component, true);
|
|
||||||
return true;
|
|
||||||
} catch (Exception | NoClassDefFoundError e) {
|
|
||||||
TBMCCoreAPI.SendException("Failed to enable component " + component.getClassName() + "!", e, component);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!components.containsKey(component.getClass()))
|
|
||||||
return true; //Already unregistered
|
|
||||||
if (component.enabled) {
|
|
||||||
try {
|
|
||||||
setComponentEnabled(component, false);
|
|
||||||
} catch (Exception | NoClassDefFoundError e) {
|
|
||||||
TBMCCoreAPI.SendException("Failed to disable component " + component.getClassName() + "!", e, component);
|
|
||||||
return false; //If failed to disable, won't unregister either
|
|
||||||
}
|
|
||||||
}
|
|
||||||
component.unregister(plugin);
|
|
||||||
components.remove(component.getClass());
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
TBMCCoreAPI.SendException("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + "!", e, plugin);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
fun log(message: String) {
|
||||||
* Enables or disables the given component. If the component fails to enable, it will be disabled.
|
plugin!!.logger.info("[" + className + "] " + message)
|
||||||
*
|
}
|
||||||
* @param component The component to register
|
|
||||||
* @param enabled Whether it's enabled or not
|
|
||||||
*/
|
|
||||||
public static void setComponentEnabled(Component<?> component, boolean enabled) throws UnregisteredComponentException {
|
|
||||||
if (!components.containsKey(component.getClass()))
|
|
||||||
throw new UnregisteredComponentException(component);
|
|
||||||
if (component.enabled == enabled) return; //Don't do anything
|
|
||||||
if (component.enabled = enabled) {
|
|
||||||
try {
|
|
||||||
updateConfig(component.getPlugin(), component);
|
|
||||||
component.enable();
|
|
||||||
if (ButtonPlugin.configGenAllowed(component)) {
|
|
||||||
IHaveConfig.pregenConfig(component, null);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
try { //Automatically disable components that fail to enable properly
|
|
||||||
setComponentEnabled(component, false);
|
|
||||||
throw e;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
Throwable t = ex;
|
|
||||||
for (var th = t; th != null; th = th.getCause())
|
|
||||||
t = th; //Set if not null
|
|
||||||
if (t != e)
|
|
||||||
t.initCause(e);
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
component.disable();
|
|
||||||
ButtonPlugin.getCommand2MC().unregisterCommands(component);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void updateConfig(JavaPlugin plugin, Component<?> component) {
|
fun logWarn(message: String) {
|
||||||
if (plugin.getConfig() != null) { //Production
|
plugin!!.logger.warning("[" + className + "] " + message)
|
||||||
var compconf = plugin.getConfig().getConfigurationSection("components");
|
}
|
||||||
if (compconf == null) compconf = plugin.getConfig().createSection("components");
|
|
||||||
var configSect = compconf.getConfigurationSection(component.getClassName());
|
|
||||||
if (configSect == null)
|
|
||||||
configSect = compconf.createSection(component.getClassName());
|
|
||||||
component.config.reset(configSect);
|
|
||||||
} //Testing: it's already set
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the currently registered components<br>
|
* Registers the module, when called by the JavaPlugin class.
|
||||||
*
|
* This gets fired when the plugin is enabled. Use [.enable] to register commands and such.
|
||||||
* @return The currently registered components
|
*
|
||||||
*/
|
* @param plugin Plugin object
|
||||||
@SuppressWarnings("rawtypes")
|
*/
|
||||||
public static Map<Class<? extends Component>, Component<? extends JavaPlugin>> getComponents() {
|
protected open fun register(plugin: JavaPlugin?) {}
|
||||||
return Collections.unmodifiableMap(components);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void log(String message) {
|
/**
|
||||||
plugin.getLogger().info("[" + getClassName() + "] " + message);
|
* Unregisters the module, when called by the JavaPlugin class.
|
||||||
}
|
* This gets fired when the plugin is disabled.
|
||||||
|
* Do any cleanups needed within this method.
|
||||||
|
*
|
||||||
|
* @param plugin Plugin object
|
||||||
|
*/
|
||||||
|
protected open fun unregister(plugin: JavaPlugin?) {}
|
||||||
|
|
||||||
public void logWarn(String message) {
|
/**
|
||||||
plugin.getLogger().warning("[" + getClassName() + "] " + message);
|
* Enables the module, when called by the JavaPlugin class. Call
|
||||||
}
|
* registerCommand() and registerListener() within this method.<br></br>
|
||||||
|
* To access the plugin, use [.getPlugin].
|
||||||
|
*/
|
||||||
|
protected abstract fun enable()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the module, when called by the JavaPlugin class.
|
* Disables the module, when called by the JavaPlugin class. Do
|
||||||
* This gets fired when the plugin is enabled. Use {@link #enable()} to register commands and such.
|
* any cleanups needed within this method.
|
||||||
*
|
* To access the plugin, use [.getPlugin].
|
||||||
* @param plugin Plugin object
|
*/
|
||||||
*/
|
protected abstract fun disable()
|
||||||
@SuppressWarnings({"unused"})
|
|
||||||
protected void register(JavaPlugin plugin) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unregisters the module, when called by the JavaPlugin class.
|
* Registers a command to the component. Make sure to use [buttondevteam.lib.chat.CommandClass] and [buttondevteam.lib.chat.Command2.Subcommand].
|
||||||
* This gets fired when the plugin is disabled.
|
* You don't need to register the command in plugin.yml.
|
||||||
* Do any cleanups needed within this method.
|
*
|
||||||
*
|
* @param command Custom coded command class
|
||||||
* @param plugin Plugin object
|
*/
|
||||||
*/
|
fun registerCommand(command: ICommand2MC) {
|
||||||
@SuppressWarnings({"unused"})
|
if (plugin is ButtonPlugin) command.registerToPlugin(plugin as ButtonPlugin)
|
||||||
protected void unregister(JavaPlugin plugin) {
|
command.registerToComponent(this)
|
||||||
}
|
ButtonPlugin.getCommand2MC().registerCommand(command)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enables the module, when called by the JavaPlugin class. Call
|
* Registers a Listener to this component
|
||||||
* registerCommand() and registerListener() within this method.<br>
|
*
|
||||||
* To access the plugin, use {@link #getPlugin()}.
|
* @param listener The event listener to register
|
||||||
*/
|
* @return The provided listener
|
||||||
protected abstract void enable();
|
*/
|
||||||
|
protected fun registerListener(listener: Listener): Listener {
|
||||||
|
TBMCCoreAPI.RegisterEventsForExceptions(listener, plugin)
|
||||||
|
return listener
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disables the module, when called by the JavaPlugin class. Do
|
* Returns a map of configs that are under the given key.
|
||||||
* any cleanups needed within this method.
|
*
|
||||||
* To access the plugin, use {@link #getPlugin()}.
|
* @param key The key to use
|
||||||
*/
|
* @param defaultProvider A mapping between config paths and config generators
|
||||||
protected abstract void disable();
|
* @return A map containing configs
|
||||||
|
*/
|
||||||
|
fun getConfigMap(key: String?, defaultProvider: Map<String, Consumer<IHaveConfig?>>): Map<String, IHaveConfig> {
|
||||||
|
val c: ConfigurationSection = getConfig().getConfig()
|
||||||
|
var cs = c.getConfigurationSection(key)
|
||||||
|
if (cs == null) cs = c.createSection(key)
|
||||||
|
val res = cs!!.getValues(false).entries.stream().filter { (_, value): Map.Entry<String?, Any?> -> value is ConfigurationSection }
|
||||||
|
.collect(Collectors.toMap<Map.Entry<String?, Any?>, String, IHaveConfig>(Function<Map.Entry<String?, Any?>, String> { (key1, value) -> java.util.Map.Entry.key }, Function<Map.Entry<String?, Any?>, IHaveConfig> { (_, value): Map.Entry<String?, Any?> ->
|
||||||
|
val conf = IHaveConfig { getPlugin().saveConfig() }
|
||||||
|
conf.reset(value as ConfigurationSection?)
|
||||||
|
conf
|
||||||
|
}))
|
||||||
|
if (res.size == 0) {
|
||||||
|
for ((key1, value) in defaultProvider) {
|
||||||
|
val conf = IHaveConfig { getPlugin().saveConfig() }
|
||||||
|
conf.reset(cs.createSection(key1))
|
||||||
|
value.accept(conf)
|
||||||
|
res[key1] = conf
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
private val className: String
|
||||||
* Registers a command to the component. Make sure to use {@link buttondevteam.lib.chat.CommandClass} and {@link buttondevteam.lib.chat.Command2.Subcommand}.
|
private get() = javaClass.simpleName
|
||||||
* You don't need to register the command in plugin.yml.
|
|
||||||
*
|
|
||||||
* @param command Custom coded command class
|
|
||||||
*/
|
|
||||||
protected final void registerCommand(ICommand2MC command) {
|
|
||||||
if (plugin instanceof ButtonPlugin)
|
|
||||||
command.registerToPlugin((ButtonPlugin) plugin);
|
|
||||||
command.registerToComponent(this);
|
|
||||||
ButtonPlugin.getCommand2MC().registerCommand(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
companion object {
|
||||||
* Registers a Listener to this component
|
private val components = HashMap<Class<out Component<*>>, Component<out JavaPlugin>>()
|
||||||
*
|
|
||||||
* @param listener The event listener to register
|
|
||||||
* @return The provided listener
|
|
||||||
*/
|
|
||||||
protected final Listener registerListener(Listener listener) {
|
|
||||||
TBMCCoreAPI.RegisterEventsForExceptions(listener, plugin);
|
|
||||||
return listener;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a map of configs that are under the given key.
|
* Registers a component checking it's dependencies and calling [.register].<br></br>
|
||||||
*
|
* Make sure to register the dependencies first.<br></br>
|
||||||
* @param key The key to use
|
* The component will be enabled automatically, regardless of when it was registered.<br></br>
|
||||||
* @param defaultProvider A mapping between config paths and config generators
|
* **If not using [ButtonPlugin], call [ComponentManager.unregComponents] on plugin disable.**
|
||||||
* @return A map containing configs
|
*
|
||||||
*/
|
* @param component The component to register
|
||||||
protected Map<String, IHaveConfig> getConfigMap(String key, Map<String, Consumer<IHaveConfig>> defaultProvider) {
|
* @return Whether the component is registered successfully (it may have failed to enable)
|
||||||
val c = getConfig().getConfig();
|
*/
|
||||||
var cs = c.getConfigurationSection(key);
|
@JvmStatic
|
||||||
if (cs == null) cs = c.createSection(key);
|
fun <T : JavaPlugin?> registerComponent(plugin: T, component: Component<T>): Boolean {
|
||||||
val res = cs.getValues(false).entrySet().stream().filter(e -> e.getValue() instanceof ConfigurationSection)
|
return registerUnregisterComponent(plugin, component, true)
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, kv -> {
|
}
|
||||||
var conf = new IHaveConfig(getPlugin()::saveConfig);
|
|
||||||
conf.reset((ConfigurationSection) kv.getValue());
|
|
||||||
return conf;
|
|
||||||
}));
|
|
||||||
if (res.size() == 0) {
|
|
||||||
for (val entry : defaultProvider.entrySet()) {
|
|
||||||
val conf = new IHaveConfig(getPlugin()::saveConfig);
|
|
||||||
conf.reset(cs.createSection(entry.getKey()));
|
|
||||||
entry.getValue().accept(conf);
|
|
||||||
res.put(entry.getKey(), conf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getClassName() {
|
/**
|
||||||
return getClass().getSimpleName();
|
* Unregisters a component by calling [.unregister].<br></br>
|
||||||
}
|
* Make sure to unregister the dependencies last.<br></br>
|
||||||
}
|
* **Components will be unregistered in opposite order of registering by default by [ButtonPlugin] or [ComponentManager.unregComponents].**
|
||||||
|
*
|
||||||
|
* @param component The component to unregister
|
||||||
|
* @return Whether the component is unregistered successfully (it also got disabled)
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun <T : JavaPlugin?> unregisterComponent(plugin: T, component: Component<T>): Boolean {
|
||||||
|
return registerUnregisterComponent(plugin, component, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : JavaPlugin?> registerUnregisterComponent(plugin: T, component: Component<T>, register: Boolean): Boolean {
|
||||||
|
return try {
|
||||||
|
val metaAnn = component.javaClass.getAnnotation(ComponentMetadata::class.java)
|
||||||
|
if (metaAnn != null) {
|
||||||
|
val dependencies: Array<Class<out Component<*>>> = metaAnn.depends()
|
||||||
|
for (dep in dependencies) { //TODO: Support dependencies at enable/disable as well
|
||||||
|
if (!components.containsKey(dep)) {
|
||||||
|
plugin!!.logger.warning("Failed to " + (if (register) "" else "un") + "register component " + component.className + " as a required dependency is missing/disabled: " + dep.simpleName)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (register) {
|
||||||
|
if (components.containsKey(component.javaClass)) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to register component " + component.className, IllegalArgumentException("The component is already registered!"), plugin)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
component.plugin = plugin
|
||||||
|
component.config.saveAction = Runnable { plugin!!.saveConfig() }
|
||||||
|
updateConfig(plugin, component)
|
||||||
|
component.register(plugin)
|
||||||
|
components[component.javaClass] = component
|
||||||
|
if (plugin is ButtonPlugin) (plugin as ButtonPlugin).componentStack.push(component)
|
||||||
|
if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) {
|
||||||
|
return try { //Enable components registered after the previous ones getting enabled
|
||||||
|
setComponentEnabled(component, true)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component)
|
||||||
|
true
|
||||||
|
} catch (e: NoClassDefFoundError) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to enable component " + component.className + "!", e, component)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!components.containsKey(component.javaClass)) return true //Already unregistered
|
||||||
|
if (component.enabled) {
|
||||||
|
try {
|
||||||
|
setComponentEnabled(component, false)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to disable component " + component.className + "!", e, component)
|
||||||
|
return false //If failed to disable, won't unregister either
|
||||||
|
} catch (e: NoClassDefFoundError) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to disable component " + component.className + "!", e, component)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
component.unregister(plugin)
|
||||||
|
components.remove(component.javaClass)
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to " + (if (register) "" else "un") + "register component " + component.className + "!", e, plugin)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables or disables the given component. If the component fails to enable, it will be disabled.
|
||||||
|
*
|
||||||
|
* @param component The component to register
|
||||||
|
* @param enabled Whether it's enabled or not
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(UnregisteredComponentException::class)
|
||||||
|
fun setComponentEnabled(component: Component<*>, enabled: Boolean) {
|
||||||
|
if (!components.containsKey(component.javaClass)) throw UnregisteredComponentException(component)
|
||||||
|
if (component.enabled == enabled) return //Don't do anything
|
||||||
|
if (enabled.also { component.enabled = it }) {
|
||||||
|
try {
|
||||||
|
updateConfig(component.getPlugin(), component)
|
||||||
|
component.enable()
|
||||||
|
if (ButtonPlugin.configGenAllowed(component)) {
|
||||||
|
IHaveConfig.pregenConfig(component, null)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
try { //Automatically disable components that fail to enable properly
|
||||||
|
setComponentEnabled(component, false)
|
||||||
|
throw e
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
var t: Throwable = ex
|
||||||
|
var th: Throwable? = t
|
||||||
|
while (th != null) {
|
||||||
|
t = th //Set if not null
|
||||||
|
th = th.cause
|
||||||
|
}
|
||||||
|
if (t !== e) t.initCause(e)
|
||||||
|
throw ex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
component.disable()
|
||||||
|
ButtonPlugin.getCommand2MC().unregisterCommands(component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun updateConfig(plugin: JavaPlugin, component: Component<*>) {
|
||||||
|
if (plugin.config != null) { //Production
|
||||||
|
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)
|
||||||
|
component.config.reset(configSect)
|
||||||
|
} //Testing: it's already set
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,325 +1,278 @@
|
||||||
package buttondevteam.lib.chat;
|
package buttondevteam.lib.chat
|
||||||
|
|
||||||
import buttondevteam.core.MainPlugin;
|
import buttondevteam.core.MainPlugin
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import buttondevteam.lib.TBMCCoreAPI
|
||||||
import buttondevteam.lib.chat.commands.CommandArgument;
|
import buttondevteam.lib.chat.Command2.Subcommand
|
||||||
import buttondevteam.lib.chat.commands.CommandArgumentHelpManager;
|
import buttondevteam.lib.chat.commands.*
|
||||||
import buttondevteam.lib.chat.commands.NumberArg;
|
import buttondevteam.lib.player.ChromaGamerBase
|
||||||
import buttondevteam.lib.chat.commands.SubcommandData;
|
import com.mojang.brigadier.CommandDispatcher
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
import com.mojang.brigadier.arguments.*
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.builder.ArgumentBuilder
|
||||||
import com.mojang.brigadier.arguments.*;
|
import com.mojang.brigadier.context.CommandContext
|
||||||
import com.mojang.brigadier.builder.ArgumentBuilder;
|
import com.mojang.brigadier.context.ParsedCommandNode
|
||||||
import com.mojang.brigadier.context.CommandContext;
|
import com.mojang.brigadier.exceptions.CommandSyntaxException
|
||||||
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
import com.mojang.brigadier.tree.CommandNode
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import lombok.RequiredArgsConstructor
|
||||||
import lombok.RequiredArgsConstructor;
|
import org.bukkit.Bukkit
|
||||||
import lombok.val;
|
import org.javatuples.Pair
|
||||||
import org.bukkit.Bukkit;
|
import org.javatuples.Triplet
|
||||||
import org.javatuples.Pair;
|
import java.lang.reflect.Method
|
||||||
import org.javatuples.Triplet;
|
import java.util.function.Function
|
||||||
import org.jetbrains.annotations.NotNull;
|
import java.util.function.Predicate
|
||||||
|
import java.util.function.Supplier
|
||||||
import java.lang.annotation.ElementType;
|
import java.util.stream.Collectors
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The method name is the subcommand, use underlines (_) to add further subcommands.
|
* The method name is the subcommand, use underlines (_) to add further subcommands.
|
||||||
* The args may be null if the conversion failed and it's optional.
|
* The args may be null if the conversion failed and it's optional.
|
||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Sender> {
|
abstract class Command2<TC : ICommand2<TP>, TP : Command2Sender> {
|
||||||
|
/**
|
||||||
|
* Parameters annotated with this receive all the remaining arguments
|
||||||
|
*/
|
||||||
|
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||||
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
|
annotation class TextArg
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parameters annotated with this receive all the remaining arguments
|
* Methods annotated with this will be recognised as subcommands
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.PARAMETER)
|
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
public @interface TextArg {
|
annotation class Subcommand(
|
||||||
}
|
/**
|
||||||
|
* Help text to show players. A usage message will be also shown below it.
|
||||||
|
*/
|
||||||
|
val helpText: Array<String> = [],
|
||||||
|
/**
|
||||||
|
* The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
|
||||||
|
* Used to be "tbmc.admin". The [.MOD_GROUP] is provided to use with this.
|
||||||
|
*/
|
||||||
|
val permGroup: String = "", val aliases: Array<String> = []) {
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Allowed for OPs only by default
|
||||||
|
*/
|
||||||
|
const val MOD_GROUP = "mod"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
@Target(AnnotationTarget.VALUE_PARAMETER)
|
||||||
* Methods annotated with this will be recognised as subcommands
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
*/
|
annotation class OptionalArg
|
||||||
@Target(ElementType.METHOD)
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface Subcommand {
|
|
||||||
/**
|
|
||||||
* Allowed for OPs only by default
|
|
||||||
*/
|
|
||||||
String MOD_GROUP = "mod";
|
|
||||||
|
|
||||||
/**
|
protected class ParamConverter<T>(val converter: Function<String, T>, val errormsg: String, val allSupplier: Supplier<Iterable<String>>)
|
||||||
* Help text to show players. A usage message will be also shown below it.
|
|
||||||
*/
|
|
||||||
String[] helpText() default {};
|
|
||||||
|
|
||||||
/**
|
protected val paramConverters = HashMap<Class<*>, ParamConverter<*>>()
|
||||||
* The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
|
private val commandHelp = ArrayList<String>() //Mainly needed by Discord
|
||||||
* Used to be "tbmc.admin". The {@link #MOD_GROUP} is provided to use with this.
|
private val dispatcher = CommandDispatcher<TP>()
|
||||||
*/
|
|
||||||
String permGroup() default "";
|
|
||||||
|
|
||||||
String[] aliases() default {};
|
/**
|
||||||
}
|
* The first character in the command line that shows that it's a command.
|
||||||
|
*/
|
||||||
|
private val commandChar = 0.toChar()
|
||||||
|
|
||||||
@Target(ElementType.PARAMETER)
|
/**
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
* Whether the command's actual code has to be run on the primary thread.
|
||||||
public @interface OptionalArg {
|
*/
|
||||||
}
|
private val runOnPrimaryThread = false
|
||||||
|
|
||||||
@RequiredArgsConstructor
|
/**
|
||||||
protected static class ParamConverter<T> {
|
* Adds a param converter that obtains a specific object from a string parameter.
|
||||||
public final Function<String, T> converter;
|
* The converter may return null.
|
||||||
public final String errormsg;
|
*
|
||||||
public final Supplier<Iterable<String>> allSupplier;
|
* @param <T> The type of the result
|
||||||
}
|
* @param cl The class of the result object
|
||||||
|
* @param converter The converter to use
|
||||||
|
* @param allSupplier The supplier of all possible values (ideally)
|
||||||
|
</T> */
|
||||||
|
open fun <T> addParamConverter(cl: Class<T>, converter: Function<String, T>, errormsg: String,
|
||||||
|
allSupplier: Supplier<Iterable<String>>) {
|
||||||
|
paramConverters[cl] = ParamConverter<T>(converter, errormsg, allSupplier)
|
||||||
|
}
|
||||||
|
|
||||||
protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
|
open fun handleCommand(sender: TP, commandline: String): Boolean {
|
||||||
private final ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
|
val results = dispatcher.parse(commandline, sender)
|
||||||
private final CommandDispatcher<TP> dispatcher = new CommandDispatcher<>();
|
if (results.reader.canRead()) {
|
||||||
|
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) {
|
||||||
|
try {
|
||||||
|
dispatcher.execute(results)
|
||||||
|
} catch (e: CommandSyntaxException) {
|
||||||
|
sender.sendMessage(e.message)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.name + "(" + sender.javaClass.canonicalName + ") and message " + commandline, e, MainPlugin.Instance)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true //We found a method
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
//TODO: Add to the help
|
||||||
* The first character in the command line that shows that it's a command.
|
private fun processSenderType(sender: TP, sd: SubcommandData<TC, TP>, params: ArrayList<Any>): Boolean {
|
||||||
*/
|
val sendertype = sd.senderType
|
||||||
private final char commandChar;
|
val cg: ChromaGamerBase
|
||||||
/**
|
if (sendertype.isAssignableFrom(sender.javaClass)) params.add(sender) //The command either expects a CommandSender or it is a Player, or some other expected type
|
||||||
* Whether the command's actual code has to be run on the primary thread.
|
else if (sender is Command2MCSender // TODO: This is Minecraft only
|
||||||
*/
|
&& sendertype.isAssignableFrom((sender as Command2MCSender).sender.javaClass))
|
||||||
private final boolean runOnPrimaryThread;
|
params.add((sender as Command2MCSender).sender)
|
||||||
|
else if ((ChromaGamerBase::class.java.isAssignableFrom(sendertype) && sender is Command2MCSender)
|
||||||
|
&& ChromaGamerBase.getFromSender((sender as Command2MCSender).sender).also { cg = it } != null && cg.javaClass == sendertype) //The command expects a user of our system
|
||||||
|
params.add(cg) else {
|
||||||
|
val type = sendertype.simpleName.fold("") { s, ch -> s + if (ch.isUpperCase()) " " + ch.lowercase() else ch }
|
||||||
|
sender.sendMessage("§cYou need to be a $type to use this command.")
|
||||||
|
sender.sendMessage(sd.getHelpText(sender)) //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a param converter that obtains a specific object from a string parameter.
|
* Register a command in the command system. The way this command gets registered may change depending on the implementation.
|
||||||
* The converter may return null.
|
* Always invoke [.registerCommandSuper] when implementing this method.
|
||||||
*
|
*
|
||||||
* @param <T> The type of the result
|
* @param command The command to register
|
||||||
* @param cl The class of the result object
|
*/
|
||||||
* @param converter The converter to use
|
abstract fun registerCommand(command: TC)
|
||||||
* @param allSupplier The supplier of all possible values (ideally)
|
|
||||||
*/
|
|
||||||
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg,
|
|
||||||
Supplier<Iterable<String>> allSupplier) {
|
|
||||||
paramConverters.put(cl, new ParamConverter<>(converter, errormsg, allSupplier));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean handleCommand(TP sender, String commandline) {
|
/**
|
||||||
var results = dispatcher.parse(commandline, sender);
|
* Registers a command in the Command2 system, so it can be looked up and executed.
|
||||||
if (results.getReader().canRead()) {
|
*
|
||||||
return false; // Unknown command
|
* @param command The command to register
|
||||||
}
|
* @return The Brigadier command node if you need it for something (like tab completion)
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
|
*/
|
||||||
try {
|
protected fun registerCommandSuper(command: TC): LiteralCommandNode<TP> {
|
||||||
dispatcher.execute(results);
|
var mainCommandNode: LiteralCommandNode<TP>? = null
|
||||||
} catch (CommandSyntaxException e) {
|
for (meth in command.javaClass.getMethods()) {
|
||||||
sender.sendMessage(e.getMessage());
|
val ann = meth.getAnnotation<Subcommand>(Subcommand::class.java) ?: continue
|
||||||
} catch (Exception e) {
|
val methodPath = CommandUtils.getCommandPath(meth.name, ' ')
|
||||||
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e, MainPlugin.Instance);
|
val result = registerNodeFromPath(command!!.commandPath + methodPath)
|
||||||
}
|
result.value0.addChild(getExecutableNode(meth, command, ann, result.value2, CommandArgumentHelpManager(command)))
|
||||||
});
|
if (mainCommandNode == null) mainCommandNode = result.value1 else if (result.value1!!.name != mainCommandNode.name) {
|
||||||
return true; //We found a method
|
MainPlugin.Instance.logger.warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.javaClass.simpleName)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (mainCommandNode == null) {
|
||||||
|
throw RuntimeException("There are no subcommands defined in the command class " + command.javaClass.getSimpleName() + "!")
|
||||||
|
}
|
||||||
|
return mainCommandNode
|
||||||
|
}
|
||||||
|
|
||||||
//Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
|
/**
|
||||||
|
* Returns the node that can actually execute the given subcommand.
|
||||||
|
*
|
||||||
|
* @param method The subcommand method
|
||||||
|
* @param command The command object
|
||||||
|
* @param path The command path
|
||||||
|
* @return The executable node
|
||||||
|
*/
|
||||||
|
private fun getExecutableNode(method: Method, command: TC, ann: Subcommand, path: String, argHelpManager: CommandArgumentHelpManager<TC, TP>): LiteralCommandNode<TP> {
|
||||||
|
val paramsAndSenderType = getCommandParametersAndSender(method, argHelpManager) // Param order is important
|
||||||
|
val params = paramsAndSenderType.value0
|
||||||
|
val paramMap = HashMap<String, CommandArgument?>()
|
||||||
|
for (param in params) {
|
||||||
|
paramMap[param!!.name] = param
|
||||||
|
}
|
||||||
|
val node = CoreCommandBuilder.literal<TP, TC>(path, params[0]!!.type, paramMap, params, command)
|
||||||
|
.helps(command!!.getHelpText(method, ann)).permits { sender: TP -> hasPermission(sender, command, method) }
|
||||||
|
.executes { context: CommandContext<TP> -> executeCommand(context) }
|
||||||
|
var parent: ArgumentBuilder<TP, *> = node
|
||||||
|
for (param in params) { // Register parameters in the right order
|
||||||
|
parent.then(CoreArgumentBuilder.argument(param!!.name, getArgumentType(param), param.optional).also { parent = it })
|
||||||
|
}
|
||||||
|
return node.build()
|
||||||
|
}
|
||||||
|
|
||||||
//TODO: Add to the help
|
/**
|
||||||
|
* Registers all necessary no-op nodes for the given path.
|
||||||
|
*
|
||||||
|
* @param path The full command path
|
||||||
|
* @return The last no-op node that can be used to register the executable node,
|
||||||
|
* the main command node and the last part of the command path (that isn't registered yet)
|
||||||
|
*/
|
||||||
|
private fun registerNodeFromPath(path: String): Triplet<CommandNode<TP>, LiteralCommandNode<TP>?, String> {
|
||||||
|
val split = path.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
var parent: CommandNode<TP> = dispatcher.root
|
||||||
|
var mainCommand: LiteralCommandNode<TP>? = null
|
||||||
|
for (i in 0 until split.size - 1) {
|
||||||
|
val part = split[i]
|
||||||
|
val child = parent.getChild(part)
|
||||||
|
if (child == null) parent.addChild(CoreCommandBuilder.literalNoOp<TP, TC>(part).executes { context: CommandContext<TP> -> executeHelpText(context) }.build().also { parent = it }) else parent = child
|
||||||
|
if (i == 0) mainCommand = parent as LiteralCommandNode<TP> // Has to be a literal, if not, well, error
|
||||||
|
}
|
||||||
|
return Triplet(parent, mainCommand, split[split.size - 1])
|
||||||
|
}
|
||||||
|
|
||||||
private boolean processSenderType(TP sender, SubcommandData<TC, TP> sd, ArrayList<Object> params) {
|
/**
|
||||||
val sendertype = sd.senderType;
|
* Get parameter data for the given subcommand. Attempts to read it from the commands file, if it fails, it will return generic info.
|
||||||
final ChromaGamerBase cg;
|
* The first parameter is always the sender both in the methods themselves and in the returned array.
|
||||||
if (sendertype.isAssignableFrom(sender.getClass()))
|
*
|
||||||
params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type
|
* @param method The method the subcommand is created from
|
||||||
else if (sender instanceof Command2MCSender // TODO: This is Minecraft only
|
* @return Parameter data objects and the sender type
|
||||||
&& sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
|
* @throws RuntimeException If there is no sender parameter declared in the method
|
||||||
params.add(((Command2MCSender) sender).getSender());
|
*/
|
||||||
else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
|
private fun getCommandParametersAndSender(method: Method, argHelpManager: CommandArgumentHelpManager<TC, TP>): Pair<Array<CommandArgument?>, Class<*>> {
|
||||||
&& sender instanceof Command2MCSender
|
val parameters = method.parameters
|
||||||
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
|
if (parameters.size == 0) throw RuntimeException("No sender parameter for method '$method'")
|
||||||
&& cg.getClass() == sendertype) //The command expects a user of our system
|
val ret = arrayOfNulls<CommandArgument>(parameters.size)
|
||||||
params.add(cg);
|
val usage = argHelpManager.getParameterHelpForMethod(method)
|
||||||
else {
|
val paramNames = usage?.split(" ".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray()
|
||||||
String type = sendertype.getSimpleName().chars().mapToObj(ch -> Character.isUpperCase(ch)
|
for (i in 1 until parameters.size) {
|
||||||
? " " + Character.toLowerCase(ch)
|
val numAnn = parameters[i].getAnnotation(NumberArg::class.java)
|
||||||
: ch + "").collect(Collectors.joining());
|
ret[i - 1] = CommandArgument(paramNames?.get(i) ?: "param$i", parameters[i].type,
|
||||||
sender.sendMessage("§cYou need to be a " + type + " to use this command.");
|
parameters[i].isVarArgs || parameters[i].isAnnotationPresent(TextArg::class.java),
|
||||||
sender.sendMessage(sd.getHelpText(sender)); //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only
|
if (numAnn == null) null else Pair(numAnn.lowerLimit(), numAnn.upperLimit()),
|
||||||
return true;
|
parameters[i].isAnnotationPresent(OptionalArg::class.java),
|
||||||
}
|
paramNames?.get(i) ?: "param$i") // TODO: Description (JavaDoc?)
|
||||||
return false;
|
}
|
||||||
}
|
return Pair(ret, parameters[0].type)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a command in the command system. The way this command gets registered may change depending on the implementation.
|
* Converts the Chroma representation of the argument declaration into Brigadier format.
|
||||||
* Always invoke {@link #registerCommandSuper(ICommand2)} when implementing this method.
|
* It does part of the command argument type processing.
|
||||||
*
|
*
|
||||||
* @param command The command to register
|
* @param arg Our representation of the command argument
|
||||||
*/
|
* @return The Brigadier representation of the command argument
|
||||||
public abstract void registerCommand(TC command);
|
*/
|
||||||
|
private fun getArgumentType(arg: CommandArgument?): ArgumentType<*> {
|
||||||
|
val ptype = arg!!.type
|
||||||
|
val lowerLimit: Number = arg.limits.value0
|
||||||
|
val upperLimit: Number = arg.limits.value1
|
||||||
|
return if (arg.greedy) StringArgumentType.greedyString() else if (ptype == String::class.java) StringArgumentType.word() else if (ptype == Int::class.javaPrimitiveType || ptype == Int::class.java || ptype == Byte::class.javaPrimitiveType || ptype == Byte::class.java || ptype == Short::class.javaPrimitiveType || ptype == Short::class.java) IntegerArgumentType.integer(lowerLimit.toInt(), upperLimit.toInt()) else if (ptype == Long::class.javaPrimitiveType || ptype == Long::class.java) LongArgumentType.longArg(lowerLimit.toLong(), upperLimit.toLong()) else if (ptype == Float::class.javaPrimitiveType || ptype == Float::class.java) FloatArgumentType.floatArg(lowerLimit.toFloat(), upperLimit.toFloat()) else if (ptype == Double::class.javaPrimitiveType || ptype == Double::class.java) DoubleArgumentType.doubleArg(lowerLimit.toDouble(), upperLimit.toDouble()) else if (ptype == Char::class.javaPrimitiveType || ptype == Char::class.java) StringArgumentType.word() else if (ptype == Boolean::class.javaPrimitiveType || ptype == Boolean::class.java) BoolArgumentType.bool() else {
|
||||||
|
StringArgumentType.word()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a command in the Command2 system, so it can be looked up and executed.
|
* Displays the help text based on the executed command. Each command node might have a help text stored.
|
||||||
*
|
* The help text is displayed either because of incorrect usage or it's explicitly requested.
|
||||||
* @param command The command to register
|
*
|
||||||
* @return The Brigadier command node if you need it for something (like tab completion)
|
* @param context The command context
|
||||||
*/
|
* @return Vanilla command success level (0)
|
||||||
protected LiteralCommandNode<TP> registerCommandSuper(TC command) {
|
*/
|
||||||
LiteralCommandNode<TP> mainCommandNode = null;
|
private fun executeHelpText(context: CommandContext<TP>): Int {
|
||||||
for (val meth : command.getClass().getMethods()) {
|
println("""
|
||||||
val ann = meth.getAnnotation(Subcommand.class);
|
Nodes:
|
||||||
if (ann == null) continue;
|
${context.nodes.stream().map { node: ParsedCommandNode<TP> -> node.node.name + "@" + node.range }.collect(Collectors.joining("\n"))}
|
||||||
String methodPath = getCommandPath(meth.getName(), ' ');
|
""".trimIndent())
|
||||||
val result = registerNodeFromPath(command.getCommandPath() + methodPath);
|
return 0
|
||||||
result.getValue0().addChild(getExecutableNode(meth, command, ann, result.getValue2(), new CommandArgumentHelpManager<>(command)));
|
}
|
||||||
if (mainCommandNode == null) mainCommandNode = result.getValue1();
|
|
||||||
else if (!result.getValue1().getName().equals(mainCommandNode.getName())) {
|
|
||||||
MainPlugin.Instance.getLogger().warning("Multiple commands are defined in the same class! This is not supported. Class: " + command.getClass().getSimpleName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mainCommandNode == null) {
|
|
||||||
throw new RuntimeException("There are no subcommands defined in the command class " + command.getClass().getSimpleName() + "!");
|
|
||||||
}
|
|
||||||
return mainCommandNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the node that can actually execute the given subcommand.
|
* Executes the command itself by calling the subcommand method associated with the input command node.
|
||||||
*
|
*
|
||||||
* @param method The subcommand method
|
* @param context The command context
|
||||||
* @param command The command object
|
* @return Vanilla command success level (0)
|
||||||
* @param path The command path
|
*/
|
||||||
* @return The executable node
|
private fun executeCommand(context: CommandContext<TP>): Int {
|
||||||
*/
|
println("Execute command")
|
||||||
private LiteralCommandNode<TP> getExecutableNode(Method method, TC command, Subcommand ann, String path, CommandArgumentHelpManager<TC, TP> argHelpManager) {
|
println("Should be running sync: $runOnPrimaryThread")
|
||||||
val paramsAndSenderType = getCommandParametersAndSender(method, argHelpManager); // Param order is important
|
|
||||||
val params = paramsAndSenderType.getValue0();
|
|
||||||
val paramMap = new HashMap<String, CommandArgument>();
|
|
||||||
for (val param : params) {
|
|
||||||
paramMap.put(param.name, param);
|
|
||||||
}
|
|
||||||
val node = CoreCommandBuilder.<TP, TC>literal(path, params[0].type, paramMap, params, command)
|
|
||||||
.helps(command.getHelpText(method, ann)).permits(sender -> hasPermission(sender, command, method))
|
|
||||||
.executes(this::executeCommand);
|
|
||||||
ArgumentBuilder<TP, ?> parent = node;
|
|
||||||
for (val param : params) { // Register parameters in the right order
|
|
||||||
parent.then(parent = CoreArgumentBuilder.argument(param.name, getArgumentType(param), param.optional));
|
|
||||||
}
|
|
||||||
return node.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/*if (!hasPermission(sender, sd.command, sd.method)) {
|
||||||
* Registers all necessary no-op nodes for the given path.
|
|
||||||
*
|
|
||||||
* @param path The full command path
|
|
||||||
* @return The last no-op node that can be used to register the executable node,
|
|
||||||
* the main command node and the last part of the command path (that isn't registered yet)
|
|
||||||
*/
|
|
||||||
private Triplet<CommandNode<TP>, LiteralCommandNode<TP>, String> registerNodeFromPath(String path) {
|
|
||||||
String[] split = path.split(" ");
|
|
||||||
CommandNode<TP> parent = dispatcher.getRoot();
|
|
||||||
LiteralCommandNode<TP> mainCommand = null;
|
|
||||||
for (int i = 0; i < split.length - 1; i++) {
|
|
||||||
String part = split[i];
|
|
||||||
var child = parent.getChild(part);
|
|
||||||
if (child == null)
|
|
||||||
parent.addChild(parent = CoreCommandBuilder.<TP, TC>literalNoOp(part).executes(this::executeHelpText).build());
|
|
||||||
else parent = child;
|
|
||||||
if (i == 0) mainCommand = (LiteralCommandNode<TP>) parent; // Has to be a literal, if not, well, error
|
|
||||||
}
|
|
||||||
return new Triplet<>(parent, mainCommand, split[split.length - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get parameter data for the given subcommand. Attempts to read it from the commands file, if it fails, it will return generic info.
|
|
||||||
* The first parameter is always the sender both in the methods themselves and in the returned array.
|
|
||||||
*
|
|
||||||
* @param method The method the subcommand is created from
|
|
||||||
* @return Parameter data objects and the sender type
|
|
||||||
* @throws RuntimeException If there is no sender parameter declared in the method
|
|
||||||
*/
|
|
||||||
private Pair<CommandArgument[], Class<?>> getCommandParametersAndSender(Method method, CommandArgumentHelpManager<TC, TP> argHelpManager) {
|
|
||||||
val parameters = method.getParameters();
|
|
||||||
if (parameters.length == 0)
|
|
||||||
throw new RuntimeException("No sender parameter for method '" + method + "'");
|
|
||||||
val ret = new CommandArgument[parameters.length];
|
|
||||||
val usage = argHelpManager.getParameterHelpForMethod(method);
|
|
||||||
val paramNames = usage != null ? usage.split(" ") : null;
|
|
||||||
for (int i = 1; i < parameters.length; i++) {
|
|
||||||
val numAnn = parameters[i].getAnnotation(NumberArg.class);
|
|
||||||
ret[i - 1] = new CommandArgument(paramNames == null ? "param" + i : paramNames[i], parameters[i].getType(),
|
|
||||||
parameters[i].isVarArgs() || parameters[i].isAnnotationPresent(TextArg.class),
|
|
||||||
numAnn == null ? null : new Pair<>(numAnn.lowerLimit(), numAnn.upperLimit()),
|
|
||||||
parameters[i].isAnnotationPresent(OptionalArg.class),
|
|
||||||
paramNames == null ? "param" + i : paramNames[i]); // TODO: Description (JavaDoc?)
|
|
||||||
}
|
|
||||||
return new Pair<>(ret, parameters[0].getType());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the Chroma representation of the argument declaration into Brigadier format.
|
|
||||||
* It does part of the command argument type processing.
|
|
||||||
*
|
|
||||||
* @param arg Our representation of the command argument
|
|
||||||
* @return The Brigadier representation of the command argument
|
|
||||||
*/
|
|
||||||
private ArgumentType<?> getArgumentType(CommandArgument arg) {
|
|
||||||
final Class<?> ptype = arg.type;
|
|
||||||
Number lowerLimit = arg.limits.getValue0(), upperLimit = arg.limits.getValue1();
|
|
||||||
if (arg.greedy)
|
|
||||||
return StringArgumentType.greedyString();
|
|
||||||
else if (ptype == String.class)
|
|
||||||
return StringArgumentType.word();
|
|
||||||
else if (ptype == int.class || ptype == Integer.class
|
|
||||||
|| ptype == byte.class || ptype == Byte.class
|
|
||||||
|| ptype == short.class || ptype == Short.class)
|
|
||||||
return IntegerArgumentType.integer(lowerLimit.intValue(), upperLimit.intValue());
|
|
||||||
else if (ptype == long.class || ptype == Long.class)
|
|
||||||
return LongArgumentType.longArg(lowerLimit.longValue(), upperLimit.longValue());
|
|
||||||
else if (ptype == float.class || ptype == Float.class)
|
|
||||||
return FloatArgumentType.floatArg(lowerLimit.floatValue(), upperLimit.floatValue());
|
|
||||||
else if (ptype == double.class || ptype == Double.class)
|
|
||||||
return DoubleArgumentType.doubleArg(lowerLimit.doubleValue(), upperLimit.doubleValue());
|
|
||||||
else if (ptype == char.class || ptype == Character.class)
|
|
||||||
return StringArgumentType.word();
|
|
||||||
else if (ptype == boolean.class || ptype == Boolean.class)
|
|
||||||
return BoolArgumentType.bool();
|
|
||||||
else {
|
|
||||||
return StringArgumentType.word();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays the help text based on the executed command. Each command node might have a help text stored.
|
|
||||||
* The help text is displayed either because of incorrect usage or it's explicitly requested.
|
|
||||||
*
|
|
||||||
* @param context The command context
|
|
||||||
* @return Vanilla command success level (0)
|
|
||||||
*/
|
|
||||||
private int executeHelpText(CommandContext<TP> context) {
|
|
||||||
System.out.println("Nodes:\n" + context.getNodes().stream().map(node -> node.getNode().getName() + "@" + node.getRange()).collect(Collectors.joining("\n")));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Executes the command itself by calling the subcommand method associated with the input command node.
|
|
||||||
*
|
|
||||||
* @param context The command context
|
|
||||||
* @return Vanilla command success level (0)
|
|
||||||
*/
|
|
||||||
private int executeCommand(CommandContext<TP> context) {
|
|
||||||
System.out.println("Execute command");
|
|
||||||
System.out.println("Should be running sync: " + runOnPrimaryThread);
|
|
||||||
|
|
||||||
/*if (!hasPermission(sender, sd.command, sd.method)) {
|
|
||||||
sender.sendMessage("§cYou don't have permission to use this command");
|
sender.sendMessage("§cYou don't have permission to use this command");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -327,8 +280,8 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
if (processSenderType(sender, sd, params, parameterTypes)) return; // Checks if the sender is the wrong type
|
if (processSenderType(sender, sd, params, parameterTypes)) return; // Checks if the sender is the wrong type
|
||||||
val args = parsed.getContext().getArguments();
|
val args = parsed.getContext().getArguments();
|
||||||
for (var arg : sd.arguments.entrySet()) {*/
|
for (var arg : sd.arguments.entrySet()) {*/
|
||||||
// TODO: Invoke using custom method
|
// TODO: Invoke using custom method
|
||||||
/*if (pj == commandline.length() + 1) { //No param given
|
/*if (pj == commandline.length() + 1) { //No param given
|
||||||
if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) {
|
if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) {
|
||||||
if (cl.isPrimitive())
|
if (cl.isPrimitive())
|
||||||
params.add(Defaults.defaultValue(cl));
|
params.add(Defaults.defaultValue(cl));
|
||||||
|
@ -343,13 +296,13 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
/*if (paramArr[i1].isVarArgs()) { - TODO: Varargs support? (colors?)
|
/*if (paramArr[i1].isVarArgs()) { - TODO: Varargs support? (colors?)
|
||||||
params.add(commandline.substring(j + 1).split(" +"));
|
params.add(commandline.substring(j + 1).split(" +"));
|
||||||
continue;
|
continue;
|
||||||
}*/
|
}*/
|
||||||
// TODO: Character handling (strlen)
|
// TODO: Character handling (strlen)
|
||||||
// TODO: Param converter
|
// TODO: Param converter
|
||||||
/*}
|
/*}
|
||||||
Runnable invokeCommand = () -> {
|
Runnable invokeCommand = () -> {
|
||||||
try {
|
try {
|
||||||
sd.method.setAccessible(true); //It may be part of a private class
|
sd.method.setAccessible(true); //It may be part of a private class
|
||||||
|
@ -368,72 +321,53 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
|
||||||
if (sync)
|
if (sync)
|
||||||
Bukkit.getScheduler().runTask(MainPlugin.Instance, invokeCommand);
|
Bukkit.getScheduler().runTask(MainPlugin.Instance, invokeCommand);
|
||||||
else
|
else
|
||||||
invokeCommand.run();*/
|
invokeCommand.run();*/return 0
|
||||||
return 0;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean hasPermission(TP sender, TC command, Method subcommand);
|
abstract fun hasPermission(sender: TP, command: TC, subcommand: Method?): Boolean
|
||||||
|
val commandsText: Array<String>
|
||||||
|
get() = commandHelp.toTypedArray()
|
||||||
|
|
||||||
public String[] getCommandsText() {
|
/**
|
||||||
return commandHelp.toArray(new String[0]);
|
* Get all registered command nodes. This returns all registered Chroma commands with all the information about them.
|
||||||
}
|
*
|
||||||
|
* @return A set of command node objects containing the commands
|
||||||
|
*/
|
||||||
|
val commandNodes: Set<CoreCommandNode<TP, TC?>?>
|
||||||
|
get() = dispatcher.root.children.stream().map { node: CommandNode<TP>? -> node as CoreCommandNode<TP, TC?>? }.collect(Collectors.toUnmodifiableSet())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the path of the given subcommand excluding the class' path. It will start with the given replace char.
|
* Get a node that belongs to the given command.
|
||||||
*
|
*
|
||||||
* @param methodName The method's name, method.getName()
|
* @param command The exact name of the command
|
||||||
* @param replaceChar The character to use between subcommands
|
* @return A command node
|
||||||
* @return The command path starting with the replace char.
|
*/
|
||||||
*/
|
fun getCommandNode(command: String?): CoreCommandNode<TP, TC> {
|
||||||
@NotNull
|
return dispatcher.root.getChild(command) as CoreCommandNode<TP, TC>
|
||||||
public String getCommandPath(String methodName, char replaceChar) {
|
}
|
||||||
return methodName.equals("def") ? "" : replaceChar + methodName.replace('_', replaceChar).toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all registered command nodes. This returns all registered Chroma commands with all the information about them.
|
* Unregister all subcommands that were registered with the given command class.
|
||||||
*
|
*
|
||||||
* @return A set of command node objects containing the commands
|
* @param command The command class (object) to unregister
|
||||||
*/
|
*/
|
||||||
public Set<CoreCommandNode<TP, TC>> getCommandNodes() {
|
fun unregisterCommand(command: ICommand2<TP>) {
|
||||||
return dispatcher.getRoot().getChildren().stream().map(node -> (CoreCommandNode<TP, TC>) node).collect(Collectors.toUnmodifiableSet());
|
dispatcher.root.children.removeIf { node: CommandNode<TP> -> (node as CoreCommandNode<TP, TC>).data.command === command }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a node that belongs to the given command.
|
* Unregisters all commands that match the given predicate.
|
||||||
*
|
*
|
||||||
* @param command The exact name of the command
|
* @param condition The condition for removing a given command
|
||||||
* @return A command node
|
*/
|
||||||
*/
|
fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC>?>, nested: Boolean) {
|
||||||
public CoreCommandNode<TP, TC> getCommandNode(String command) {
|
dispatcher.root.children.removeIf { node: CommandNode<TP>? -> condition.test(node as CoreCommandNode<TP, TC>?) }
|
||||||
return (CoreCommandNode<TP, TC>) dispatcher.getRoot().getChild(command);
|
if (nested) for (child in dispatcher.root.children) unregisterCommandIf(condition, child as CoreCommandNode<TP, TC>)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private fun unregisterCommandIf(condition: Predicate<CoreCommandNode<TP, TC>?>, root: CoreCommandNode<TP, TC>) {
|
||||||
* Unregister all subcommands that were registered with the given command class.
|
// Can't use getCoreChildren() here because the collection needs to be modifiable
|
||||||
*
|
root.children.removeIf { node: CommandNode<TP>? -> condition.test(node as CoreCommandNode<TP, TC>?) }
|
||||||
* @param command The command class (object) to unregister
|
for (child in root.coreChildren) unregisterCommandIf(condition, child)
|
||||||
*/
|
}
|
||||||
public void unregisterCommand(ICommand2<TP> command) {
|
}
|
||||||
dispatcher.getRoot().getChildren().removeIf(node -> ((CoreCommandNode<TP, TC>) node).getData().command == command);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters all commands that match the given predicate.
|
|
||||||
*
|
|
||||||
* @param condition The condition for removing a given command
|
|
||||||
*/
|
|
||||||
public void unregisterCommandIf(Predicate<CoreCommandNode<TP, TC>> condition, boolean nested) {
|
|
||||||
dispatcher.getRoot().getChildren().removeIf(node -> condition.test((CoreCommandNode<TP, TC>) node));
|
|
||||||
if (nested)
|
|
||||||
for (var child : dispatcher.getRoot().getChildren())
|
|
||||||
unregisterCommandIf(condition, (CoreCommandNode<TP, TC>) child);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unregisterCommandIf(Predicate<CoreCommandNode<TP, TC>> condition, CoreCommandNode<TP, TC> root) {
|
|
||||||
// Can't use getCoreChildren() here because the collection needs to be modifiable
|
|
||||||
root.getChildren().removeIf(node -> condition.test((CoreCommandNode<TP, TC>) node));
|
|
||||||
for (var child : root.getCoreChildren())
|
|
||||||
unregisterCommandIf(condition, child);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,438 +1,390 @@
|
||||||
package buttondevteam.lib.chat;
|
package buttondevteam.lib.chat
|
||||||
|
|
||||||
import buttondevteam.core.MainPlugin;
|
import buttondevteam.core.MainPlugin
|
||||||
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 buttondevteam.lib.chat.commands.SubcommandData;
|
import buttondevteam.lib.chat.commands.CommandUtils
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
import buttondevteam.lib.chat.commands.SubcommandData
|
||||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
import buttondevteam.lib.player.ChromaGamerBase
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
import com.mojang.brigadier.arguments.StringArgumentType
|
||||||
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.context.CommandContext
|
||||||
import lombok.val;
|
import com.mojang.brigadier.suggestion.Suggestion
|
||||||
import me.lucko.commodore.Commodore;
|
import com.mojang.brigadier.suggestion.SuggestionProvider
|
||||||
import me.lucko.commodore.CommodoreProvider;
|
import com.mojang.brigadier.suggestion.Suggestions
|
||||||
import org.bukkit.Bukkit;
|
import com.mojang.brigadier.suggestion.SuggestionsBuilder
|
||||||
import org.bukkit.Location;
|
import com.mojang.brigadier.tree.ArgumentCommandNode
|
||||||
import org.bukkit.OfflinePlayer;
|
import com.mojang.brigadier.tree.CommandNode
|
||||||
import org.bukkit.command.*;
|
import com.mojang.brigadier.tree.LiteralCommandNode
|
||||||
import org.bukkit.entity.Player;
|
import me.lucko.commodore.Commodore
|
||||||
import org.bukkit.event.Listener;
|
import me.lucko.commodore.CommodoreProvider
|
||||||
import org.bukkit.permissions.Permission;
|
import org.bukkit.Bukkit
|
||||||
import org.bukkit.permissions.PermissionDefault;
|
import org.bukkit.Location
|
||||||
import org.javatuples.Triplet;
|
import org.bukkit.OfflinePlayer
|
||||||
|
import org.bukkit.command.*
|
||||||
|
import org.bukkit.entity.Player
|
||||||
|
import org.bukkit.event.Listener
|
||||||
|
import org.bukkit.permissions.Permission
|
||||||
|
import org.bukkit.permissions.PermissionDefault
|
||||||
|
import org.javatuples.Triplet
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.lang.reflect.Parameter
|
||||||
|
import java.util.*
|
||||||
|
import java.util.function.BiConsumer
|
||||||
|
import java.util.function.Function
|
||||||
|
import java.util.function.Supplier
|
||||||
|
|
||||||
import java.lang.annotation.Annotation;
|
class Command2MC : Command2<ICommand2MC?, Command2MCSender?>('/', true), Listener {
|
||||||
import java.lang.reflect.Method;
|
/**
|
||||||
import java.lang.reflect.Parameter;
|
* Don't use directly, use the method in Component and ButtonPlugin to automatically unregister the command when needed.
|
||||||
import java.util.Arrays;
|
*
|
||||||
import java.util.Collections;
|
* @param command The command to register
|
||||||
import java.util.List;
|
*/
|
||||||
import java.util.Optional;
|
override fun registerCommand(command: ICommand2MC) {
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
/*String mainpath;
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
|
|
||||||
public Command2MC() {
|
|
||||||
super('/', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Don't use directly, use the method in Component and ButtonPlugin to automatically unregister the command when needed.
|
|
||||||
*
|
|
||||||
* @param command The command to register
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void registerCommand(ICommand2MC command) {
|
|
||||||
/*String mainpath;
|
|
||||||
var plugin = command.getPlugin();
|
var plugin = command.getPlugin();
|
||||||
{
|
{
|
||||||
String cpath = command.getCommandPath();
|
String cpath = command.getCommandPath();
|
||||||
int i = cpath.indexOf(' ');
|
int i = cpath.indexOf(' ');
|
||||||
mainpath = cpath.substring(0, i == -1 ? cpath.length() : i);
|
mainpath = cpath.substring(0, i == -1 ? cpath.length() : i);
|
||||||
}*/
|
}*/
|
||||||
var commandNode = super.registerCommandSuper(command);
|
val commandNode = super.registerCommandSuper(command)
|
||||||
var bcmd = registerOfficially(command, commandNode);
|
val bcmd = registerOfficially(command, commandNode)
|
||||||
if (bcmd != null) // TODO: Support aliases
|
if (bcmd != null) // TODO: Support aliases
|
||||||
super.registerCommandSuper(command);
|
super.registerCommandSuper(command)
|
||||||
|
val perm = "chroma.command." + command.commandPath.replace(' ', '.')
|
||||||
|
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
|
||||||
|
Bukkit.getPluginManager().addPermission(Permission(perm,
|
||||||
|
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
|
||||||
|
for (method in command.javaClass.methods) {
|
||||||
|
if (!method.isAnnotationPresent(Subcommand::class.java)) continue
|
||||||
|
val path = CommandUtils.getCommandPath(method.name, '.')
|
||||||
|
if (path.length > 0) {
|
||||||
|
val subperm = perm + path
|
||||||
|
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
|
||||||
|
Bukkit.getPluginManager().addPermission(Permission(subperm,
|
||||||
|
PermissionDefault.TRUE)) //Allow commands by default, it will check mod-only
|
||||||
|
}
|
||||||
|
val pg = permGroup(command, method)
|
||||||
|
if (pg.length == 0) continue
|
||||||
|
val permGroup = "chroma.$pg"
|
||||||
|
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
|
||||||
|
Bukkit.getPluginManager().addPermission(Permission(permGroup,
|
||||||
|
PermissionDefault.OP)) //Do not allow any commands that belong to a group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var perm = "chroma.command." + command.getCommandPath().replace(' ', '.');
|
override fun hasPermission(sender: Command2MCSender, command: ICommand2MC, method: Method): Boolean {
|
||||||
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
|
return hasPermission(sender.sender, command, method)
|
||||||
Bukkit.getPluginManager().addPermission(new Permission(perm,
|
}
|
||||||
PermissionDefault.TRUE)); //Allow commands by default, it will check mod-only
|
|
||||||
for (val method : command.getClass().getMethods()) {
|
|
||||||
if (!method.isAnnotationPresent(Subcommand.class)) continue;
|
|
||||||
var path = getCommandPath(method.getName(), '.');
|
|
||||||
if (path.length() > 0) {
|
|
||||||
var subperm = perm + path;
|
|
||||||
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
|
|
||||||
Bukkit.getPluginManager().addPermission(new Permission(subperm,
|
|
||||||
PermissionDefault.TRUE)); //Allow commands by default, it will check mod-only
|
|
||||||
}
|
|
||||||
String pg = permGroup(command, method);
|
|
||||||
if (pg.length() == 0) continue;
|
|
||||||
String permGroup = "chroma." + pg;
|
|
||||||
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
|
|
||||||
Bukkit.getPluginManager().addPermission(new Permission(permGroup,
|
|
||||||
PermissionDefault.OP)); //Do not allow any commands that belong to a group
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
fun hasPermission(sender: CommandSender, command: ICommand2MC?, method: Method): Boolean {
|
||||||
public boolean hasPermission(Command2MCSender sender, ICommand2MC command, Method method) {
|
if (sender is ConsoleCommandSender) return true //Always allow the console
|
||||||
return hasPermission(sender.getSender(), command, method);
|
if (command == null) return true //Allow viewing the command - it doesn't do anything anyway
|
||||||
}
|
var pg: String
|
||||||
|
var p = true
|
||||||
|
val cmdperm = "chroma.command." + command.commandPath.replace(' ', '.')
|
||||||
|
val path = CommandUtils.getCommandPath(method.name, '.')
|
||||||
|
val perms = arrayOf(
|
||||||
|
if (path.length > 0) cmdperm + path else null,
|
||||||
|
cmdperm,
|
||||||
|
if (permGroup(command, method).also { pg = it }.length > 0) "chroma.$pg" else null
|
||||||
|
)
|
||||||
|
for (perm in perms) {
|
||||||
|
if (perm != null) {
|
||||||
|
if (p) { //Use OfflinePlayer to avoid fetching player data
|
||||||
|
p = if (sender is OfflinePlayer) MainPlugin.permission.playerHas(if (sender is Player) sender.location.world.name else null, sender as OfflinePlayer, perm) else false //Use sender's method
|
||||||
|
if (!p) p = sender.hasPermission(perm)
|
||||||
|
} else break //If any of the permissions aren't granted then don't allow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasPermission(CommandSender sender, ICommand2MC command, Method method) {
|
/**
|
||||||
if (sender instanceof ConsoleCommandSender) return true; //Always allow the console
|
* Returns the first group found in the hierarchy starting from the command method **or** the mod group if *any* of the superclasses are mod only.
|
||||||
if (command == null) return true; //Allow viewing the command - it doesn't do anything anyway
|
*
|
||||||
String pg;
|
* @param method The subcommand to check
|
||||||
boolean p = true;
|
* @return The permission group for the subcommand or empty string
|
||||||
var cmdperm = "chroma.command." + command.getCommandPath().replace(' ', '.');
|
*/
|
||||||
var path = getCommandPath(method.getName(), '.');
|
private fun permGroup(command: ICommand2MC, method: Method?): String {
|
||||||
String[] perms = {
|
if (method != null) {
|
||||||
path.length() > 0 ? cmdperm + path : null,
|
val sc = method.getAnnotation(Subcommand::class.java)
|
||||||
cmdperm,
|
if (sc != null && sc.permGroup().length > 0) {
|
||||||
(pg = permGroup(command, method)).length() > 0 ? "chroma." + pg : null
|
return sc.permGroup()
|
||||||
};
|
}
|
||||||
for (String perm : perms) {
|
}
|
||||||
if (perm != null) {
|
return if (getAnnForValue(command.javaClass, CommandClass::class.java, Function { obj: CommandClass -> obj.modOnly() }, false)) Subcommand.MOD_GROUP else getAnnForValue(command.javaClass, CommandClass::class.java, Function<CommandClass, String> { obj: CommandClass -> obj.permGroup() }, "")
|
||||||
if (p) { //Use OfflinePlayer to avoid fetching player data
|
}
|
||||||
if (sender instanceof OfflinePlayer)
|
|
||||||
p = MainPlugin.permission.playerHas(sender instanceof Player ? ((Player) sender).getLocation().getWorld().getName() : null, (OfflinePlayer) sender, perm);
|
|
||||||
else
|
|
||||||
p = false; //Use sender's method
|
|
||||||
if (!p) p = sender.hasPermission(perm);
|
|
||||||
} else break; //If any of the permissions aren't granted then don't allow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the first group found in the hierarchy starting from the command method <b>or</b> the mod group if <i>any</i></i> of the superclasses are mod only.
|
* Loops until it finds a value that is **not** the same as def
|
||||||
*
|
*
|
||||||
* @param method The subcommand to check
|
* @param sourceCl The class which has the annotation
|
||||||
* @return The permission group for the subcommand or empty string
|
* @param annCl The annotation to get
|
||||||
*/
|
* @param annMethod The annotation method to check
|
||||||
private String permGroup(ICommand2MC command, Method method) {
|
* @param def The value to ignore when looking for the result
|
||||||
if (method != null) {
|
* @param <T> The annotation type
|
||||||
val sc = method.getAnnotation(Subcommand.class);
|
* @param <V> The type of the value
|
||||||
if (sc != null && sc.permGroup().length() > 0) {
|
* @return The value returned by the first superclass or def
|
||||||
return sc.permGroup();
|
</V></T> */
|
||||||
}
|
private fun <T : Annotation?, V> getAnnForValue(sourceCl: Class<*>, annCl: Class<T>, annMethod: Function<T, V>, def: V): V {
|
||||||
}
|
var cl: Class<*>? = sourceCl
|
||||||
if (getAnnForValue(command.getClass(), CommandClass.class, CommandClass::modOnly, false))
|
while (cl != null) {
|
||||||
return Subcommand.MOD_GROUP;
|
val cc = cl.getAnnotation(annCl)
|
||||||
return getAnnForValue(command.getClass(), CommandClass.class, CommandClass::permGroup, "");
|
var r: V
|
||||||
}
|
if (cc != null && annMethod.apply(cc).also { r = it } !== def) return r
|
||||||
|
cl = cl.superclass
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loops until it finds a value that is <b>not</b> the same as def
|
* Automatically colors the message red.
|
||||||
*
|
* {@see super#addParamConverter}
|
||||||
* @param sourceCl The class which has the annotation
|
*/
|
||||||
* @param annCl The annotation to get
|
override fun <T> addParamConverter(cl: Class<T>, converter: Function<String, T>, errormsg: String, allSupplier: Supplier<Iterable<String>>) {
|
||||||
* @param annMethod The annotation method to check
|
super.addParamConverter(cl, converter, "§c$errormsg", allSupplier)
|
||||||
* @param def The value to ignore when looking for the result
|
}
|
||||||
* @param <T> The annotation type
|
|
||||||
* @param <V> The type of the value
|
|
||||||
* @return The value returned by the first superclass or def
|
|
||||||
*/
|
|
||||||
private <T extends Annotation, V> V getAnnForValue(Class<?> sourceCl, Class<T> annCl, Function<T, V> annMethod, V def) {
|
|
||||||
for (Class<?> cl = sourceCl; cl != null; cl = cl.getSuperclass()) {
|
|
||||||
val cc = cl.getAnnotation(annCl);
|
|
||||||
V r;
|
|
||||||
if (cc != null && (r = annMethod.apply(cc)) != def) return r;
|
|
||||||
}
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
fun unregisterCommands(plugin: ButtonPlugin) {
|
||||||
* Automatically colors the message red.
|
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> -> Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }.map { obj: ButtonPlugin? -> plugin.equals(obj) }.orElse(false) }, true)
|
||||||
* {@see super#addParamConverter}
|
}
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, Supplier<Iterable<String>> allSupplier) {
|
|
||||||
super.addParamConverter(cl, converter, "§c" + errormsg, allSupplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void unregisterCommands(ButtonPlugin plugin) {
|
fun unregisterCommands(component: Component<*>) {
|
||||||
unregisterCommandIf(node -> Optional.ofNullable(node.getData().command).map(ICommand2MC::getPlugin).map(plugin::equals).orElse(false), true);
|
unregisterCommandIf({ node: CoreCommandNode<Command2MCSender?, ICommand2MC?> ->
|
||||||
}
|
Optional.ofNullable(node.data.command).map { obj: ICommand2MC -> obj.plugin }
|
||||||
|
.map { comp: ButtonPlugin -> component.javaClass.simpleName == comp.javaClass.simpleName }.orElse(false)
|
||||||
|
}, true)
|
||||||
|
}
|
||||||
|
|
||||||
public void unregisterCommands(Component<?> component) {
|
override fun handleCommand(sender: Command2MCSender, commandline: String): Boolean {
|
||||||
unregisterCommandIf(node -> Optional.ofNullable(node.getData().command).map(ICommand2MC::getPlugin)
|
return handleCommand(sender, commandline, true)
|
||||||
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false), true);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
private fun handleCommand(sender: Command2MCSender, commandline: String, checkPlugin: Boolean): Boolean {
|
||||||
public boolean handleCommand(Command2MCSender sender, String commandline) {
|
val i = commandline.indexOf(' ')
|
||||||
return handleCommand(sender, commandline, true);
|
val mainpath = commandline.substring(1, if (i == -1) commandline.length else i) //Without the slash
|
||||||
}
|
var pcmd: PluginCommand
|
||||||
|
return if ((!checkPlugin
|
||||||
|
|| MainPlugin.Instance.prioritizeCustomCommands.get()) || Bukkit.getPluginCommand(mainpath).also { pcmd = it } == null //Our commands aren't PluginCommands
|
||||||
|
|| pcmd.plugin is ButtonPlugin) //Unless it's specified in the plugin.yml
|
||||||
|
super.handleCommand(sender, commandline) else false
|
||||||
|
}
|
||||||
|
|
||||||
private boolean handleCommand(Command2MCSender sender, String commandline, boolean checkPlugin) {
|
private var shouldRegisterOfficially = true
|
||||||
int i = commandline.indexOf(' ');
|
private fun registerOfficially(command: ICommand2MC, node: LiteralCommandNode<Command2MCSender>): Command? {
|
||||||
String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash
|
return if (!shouldRegisterOfficially || command.plugin == null) null else try {
|
||||||
PluginCommand pcmd;
|
val cmdmap = Bukkit.getServer().javaClass.getMethod("getCommandMap").invoke(Bukkit.getServer()) as SimpleCommandMap
|
||||||
if (!checkPlugin
|
val path = command.commandPath
|
||||||
|| MainPlugin.Instance.prioritizeCustomCommands.get()
|
val x = path.indexOf(' ')
|
||||||
|| (pcmd = Bukkit.getPluginCommand(mainpath)) == null //Our commands aren't PluginCommands
|
val mainPath = path.substring(0, if (x == -1) path.length else x)
|
||||||
|| pcmd.getPlugin() instanceof ButtonPlugin) //Unless it's specified in the plugin.yml
|
var bukkitCommand: Command
|
||||||
return super.handleCommand(sender, commandline);
|
run {
|
||||||
else
|
//Commands conflicting with Essentials have to be registered in plugin.yml
|
||||||
return false;
|
val oldcmd = cmdmap.getCommand(command.plugin.name + ":" + mainPath) //The label with the fallback prefix is always registered
|
||||||
}
|
if (oldcmd == null) {
|
||||||
|
bukkitCommand = BukkitCommand(mainPath)
|
||||||
|
cmdmap.register(command.plugin.name, bukkitCommand)
|
||||||
|
} else {
|
||||||
|
bukkitCommand = oldcmd
|
||||||
|
if (bukkitCommand is PluginCommand) (bukkitCommand as PluginCommand).executor = CommandExecutor { sender: CommandSender, command: Command, label: String, args: Array<String> -> this.executeCommand(sender, command, label, args) }
|
||||||
|
}
|
||||||
|
bukkitCommand = oldcmd ?: BukkitCommand(mainPath)
|
||||||
|
}
|
||||||
|
if (CommodoreProvider.isSupported()) TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand)
|
||||||
|
bukkitCommand
|
||||||
|
} catch (e: Exception) {
|
||||||
|
if (command.component == null) TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.plugin) else TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.component)
|
||||||
|
shouldRegisterOfficially = false
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean shouldRegisterOfficially = true;
|
private fun executeCommand(sender: CommandSender, command: Command, label: String, args: Array<String>): 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)
|
||||||
|
sender.sendMessage("§cAn internal error occurred.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
handleCommand(Command2MCSender(sender, user.channel.get(), sender),
|
||||||
|
("/" + command.name + " " + java.lang.String.join(" ", *args)).trim { it <= ' ' }, false) ///trim(): remove space if there are no args
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private Command registerOfficially(ICommand2MC command, LiteralCommandNode<Command2MCSender> node) {
|
private class BukkitCommand(name: String?) : Command(name) {
|
||||||
if (!shouldRegisterOfficially || command.getPlugin() == null) return null;
|
override fun execute(sender: CommandSender, commandLabel: String, args: Array<String>): Boolean {
|
||||||
try {
|
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args)
|
||||||
var cmdmap = (SimpleCommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer());
|
}
|
||||||
var path = command.getCommandPath();
|
|
||||||
int x = path.indexOf(' ');
|
|
||||||
var mainPath = path.substring(0, x == -1 ? path.length() : x);
|
|
||||||
Command bukkitCommand;
|
|
||||||
{ //Commands conflicting with Essentials have to be registered in plugin.yml
|
|
||||||
var oldcmd = cmdmap.getCommand(command.getPlugin().getName() + ":" + mainPath); //The label with the fallback prefix is always registered
|
|
||||||
if (oldcmd == null) {
|
|
||||||
bukkitCommand = new BukkitCommand(mainPath);
|
|
||||||
cmdmap.register(command.getPlugin().getName(), bukkitCommand);
|
|
||||||
} else {
|
|
||||||
bukkitCommand = oldcmd;
|
|
||||||
if (bukkitCommand instanceof PluginCommand)
|
|
||||||
((PluginCommand) bukkitCommand).setExecutor(this::executeCommand);
|
|
||||||
}
|
|
||||||
bukkitCommand = oldcmd == null ? new BukkitCommand(mainPath) : oldcmd;
|
|
||||||
}
|
|
||||||
if (CommodoreProvider.isSupported())
|
|
||||||
TabcompleteHelper.registerTabcomplete(command, node, bukkitCommand);
|
|
||||||
return bukkitCommand;
|
|
||||||
} catch (Exception e) {
|
|
||||||
if (command.getComponent() == null)
|
|
||||||
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getPlugin());
|
|
||||||
else
|
|
||||||
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getComponent());
|
|
||||||
shouldRegisterOfficially = false;
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean executeCommand(CommandSender sender, Command command, String label, String[] args) {
|
@Throws(IllegalArgumentException::class)
|
||||||
var user = ChromaGamerBase.getFromSender(sender);
|
override fun tabComplete(sender: CommandSender, alias: String, args: Array<String>): List<String> {
|
||||||
if (user == null) {
|
return emptyList()
|
||||||
TBMCCoreAPI.SendException("Failed to run Bukkit command for user!", new Throwable("No Chroma user found"), MainPlugin.Instance);
|
}
|
||||||
sender.sendMessage("§cAn internal error occurred.");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
handleCommand(new Command2MCSender(sender, user.channel.get(), sender),
|
|
||||||
("/" + command.getName() + " " + String.join(" ", args)).trim(), false); ///trim(): remove space if there are no args
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class BukkitCommand extends Command {
|
@Throws(IllegalArgumentException::class)
|
||||||
protected BukkitCommand(String name) {
|
override fun tabComplete(sender: CommandSender, alias: String, args: Array<String>, location: Location): List<String> {
|
||||||
super(name);
|
return emptyList()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private object TabcompleteHelper {
|
||||||
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
|
private var commodore: Commodore? = null
|
||||||
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args);
|
private fun appendSubcommand(path: String, parent: CommandNode<Any>,
|
||||||
}
|
subcommand: SubcommandData<ICommand2MC>?): LiteralCommandNode<Any> {
|
||||||
|
var scmd: LiteralCommandNode<Any>
|
||||||
|
if (parent.getChild(path) as LiteralCommandNode<kotlin.Any?>?. also { scmd = it } != null) return scmd
|
||||||
|
val scmdBuilder = LiteralArgumentBuilder.literal<Any>(path)
|
||||||
|
if (subcommand != null) scmdBuilder.requires { o: Any? ->
|
||||||
|
val sender = commodore!!.getBukkitSender(o)
|
||||||
|
subcommand.hasPermission(sender)
|
||||||
|
}
|
||||||
|
scmd = scmdBuilder.build()
|
||||||
|
parent.addChild(scmd)
|
||||||
|
return scmd
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
private fun registerTabcomplete(command2MC: ICommand2MC, commandNode: LiteralCommandNode<Command2MCSender>, bukkitCommand: Command) {
|
||||||
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
|
if (commodore == null) {
|
||||||
return Collections.emptyList();
|
commodore = CommodoreProvider.getCommodore(MainPlugin.Instance) //Register all to the Core, it's easier
|
||||||
}
|
commodore.register(LiteralArgumentBuilder.literal<Any?>("un").redirect(RequiredArgumentBuilder.argument<Any?, String>("unsomething",
|
||||||
|
StringArgumentType.word()).suggests { context: CommandContext<Any?>?, 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
|
||||||
|
.flatMap { method: Method ->
|
||||||
|
Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod::class.java)).stream()
|
||||||
|
.flatMap { ctcmAnn: CustomTabCompleteMethod ->
|
||||||
|
val paths = Optional.of<Array<String?>>(ctcmAnn.subcommand()).filter { s: Array<String?> -> s.size > 0 }
|
||||||
|
.orElseGet {
|
||||||
|
arrayOf(
|
||||||
|
CommandUtils.getCommandPath(method.name, ' ').trim { it <= ' ' }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Arrays.stream(paths).map { name: String? -> Triplet(name, ctcmAnn, method) }
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
|
for (subcmd in subcmds) {
|
||||||
|
val subpathAsOne = CommandUtils.getCommandPath(subcmd.method.getName(), ' ').trim { it <= ' ' }
|
||||||
|
val subpath = subpathAsOne.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
var scmd: CommandNode<Any> = cmd
|
||||||
|
if (subpath[0].length > 0) { //If the method is def, it will contain one empty string
|
||||||
|
for (s in subpath) {
|
||||||
|
scmd = appendSubcommand(s, scmd, subcmd) //Add method name part of the path (could_be_multiple())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val parameters: Array<Parameter> = subcmd.method.getParameters()
|
||||||
|
for (i in 1 until parameters.size) { //Skip sender
|
||||||
|
val parameter = parameters[i]
|
||||||
|
val customParamType: Boolean
|
||||||
|
// TODO: Arg type
|
||||||
|
val param: Any = subcmd.parameters.get(i - 1)
|
||||||
|
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete::class.java))
|
||||||
|
.map(Function<CustomTabComplete, Array<String>> { obj: CustomTabComplete -> obj.value() })
|
||||||
|
val customTCmethod = customTCmethods.stream().filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> subpathAsOne.equals(t.value0, ignoreCase = true) }
|
||||||
|
.filter { t: Triplet<String?, CustomTabCompleteMethod, Method> -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.value1.param()) }
|
||||||
|
.findAny()
|
||||||
|
val argb: RequiredArgumentBuilder<S, T> = RequiredArgumentBuilder.argument(param, type)
|
||||||
|
.suggests(SuggestionProvider<S?> { context: CommandContext<S?>, builder: SuggestionsBuilder ->
|
||||||
|
if (parameter.isVarArgs) { //Do it before the builder is used
|
||||||
|
val nextTokenStart = context.getInput().lastIndexOf(' ') + 1
|
||||||
|
builder = builder.createOffset(nextTokenStart)
|
||||||
|
}
|
||||||
|
if (customTC.isPresent) for (ctc in customTC.get()) builder.suggest(ctc)
|
||||||
|
var ignoreCustomParamType = false
|
||||||
|
if (customTCmethod.isPresent) {
|
||||||
|
val tr = customTCmethod.get()
|
||||||
|
if (tr.value1.ignoreTypeCompletion()) ignoreCustomParamType = true
|
||||||
|
val method = tr.value2
|
||||||
|
val params = method.parameters
|
||||||
|
val args = arrayOfNulls<Any>(params.size)
|
||||||
|
var j = 0
|
||||||
|
var k = 0
|
||||||
|
while (j < args.size && k < subcmd.parameters.length) {
|
||||||
|
val paramObj = params[j]
|
||||||
|
if (CommandSender::class.java.isAssignableFrom(paramObj.type)) {
|
||||||
|
args[j] = commodore!!.getBukkitSender(context.getSource())
|
||||||
|
j++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val paramValueString = context.getArgument(subcmd.parameters.get(k), String::class.java)
|
||||||
|
if (paramObj.type == String::class.java) {
|
||||||
|
args[j] = paramValueString
|
||||||
|
j++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val converter = getParamConverter(params[j].type, command2MC) ?: break
|
||||||
|
val paramValue = converter.converter.apply(paramValueString)
|
||||||
|
?: //For example, the player provided an invalid plugin name
|
||||||
|
break
|
||||||
|
args[j] = paramValue
|
||||||
|
k++ //Only increment if not CommandSender
|
||||||
|
j++
|
||||||
|
}
|
||||||
|
if (args.size == 0 || args[args.size - 1] != null) { //Arguments filled entirely
|
||||||
|
try {
|
||||||
|
val suggestions = method.invoke(command2MC, *args)
|
||||||
|
if (suggestions is Iterable<*>) {
|
||||||
|
for (suggestion in suggestions) if (suggestion is String) builder.suggest(suggestion as String?) else throw ClassCastException("Bad return type! It should return an Iterable<String> or a String[].")
|
||||||
|
} else if (suggestions is Array<String>) for (suggestion in suggestions as Array<String?>) builder.suggest(suggestion) else throw ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
val msg = "Failed to run tabcomplete method " + method.name + " for command " + command2MC.javaClass.simpleName
|
||||||
|
if (command2MC.component == null) TBMCCoreAPI.SendException(msg, e, command2MC.plugin) else TBMCCoreAPI.SendException(msg, e, command2MC.component)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ignoreCustomParamType && customParamType) {
|
||||||
|
val converter = getParamConverter(ptype, command2MC)
|
||||||
|
if (converter != null) {
|
||||||
|
val suggestions = converter.allSupplier.get()
|
||||||
|
for (suggestion in suggestions) builder.suggest(suggestion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ptype === Boolean::class.javaPrimitiveType || ptype === Boolean::class.java) builder.suggest("true").suggest("false")
|
||||||
|
val loweredInput = builder.remaining.lowercase(Locale.getDefault())
|
||||||
|
builder.suggest(param).buildFuture().whenComplete(BiConsumer<Suggestions, Throwable> { s: Suggestions, e: Throwable? -> //The list is automatically ordered
|
||||||
|
s.list.add(s.list.removeAt(0))
|
||||||
|
}) //So we need to put the <param> at the end after that
|
||||||
|
.whenComplete(BiConsumer<Suggestions, Throwable> { ss: Suggestions, e: Throwable? ->
|
||||||
|
ss.list.removeIf { s: Suggestion ->
|
||||||
|
val text = s.text
|
||||||
|
!text.startsWith("<") && !text.startsWith("[") && !text.lowercase(Locale.getDefault()).startsWith(loweredInput)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
val arg: ArgumentCommandNode<S, T> = argb.build()
|
||||||
|
scmd.addChild(arg)
|
||||||
|
scmd = arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shouldRegister.get()) {
|
||||||
|
commodore.register(maincmd)
|
||||||
|
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
|
||||||
|
val pluginName = command2MC.plugin.name.lowercase(Locale.getDefault())
|
||||||
|
val prefixedcmd = LiteralArgumentBuilder.literal<Any>(pluginName + ":" + path.get(0))
|
||||||
|
.redirect(maincmd).build()
|
||||||
|
commodore!!.register(prefixedcmd)
|
||||||
|
for (alias in bukkitCommand.aliases) {
|
||||||
|
commodore!!.register(LiteralArgumentBuilder.literal<Any>(alias).redirect(maincmd).build())
|
||||||
|
commodore!!.register(LiteralArgumentBuilder.literal<Any>("$pluginName:$alias").redirect(maincmd).build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
companion object {
|
||||||
public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
|
private fun getParamConverter(cl: Class<*>, command2MC: ICommand2MC): ParamConverter<*>? {
|
||||||
return Collections.emptyList();
|
val converter = ButtonPlugin.getCommand2MC().paramConverters[cl]
|
||||||
}
|
if (converter == null) {
|
||||||
}
|
val msg = "Could not find a suitable converter for type " + cl.simpleName
|
||||||
|
val exception: Exception = NullPointerException("converter is null")
|
||||||
private static class TabcompleteHelper {
|
if (command2MC.component == null) TBMCCoreAPI.SendException(msg, exception, command2MC.plugin) else TBMCCoreAPI.SendException(msg, exception, command2MC.component)
|
||||||
private static Commodore commodore;
|
return null
|
||||||
|
}
|
||||||
private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent,
|
return converter
|
||||||
SubcommandData<ICommand2MC> subcommand) {
|
}
|
||||||
LiteralCommandNode<Object> scmd;
|
}
|
||||||
if ((scmd = (LiteralCommandNode<Object>) parent.getChild(path)) != null)
|
}
|
||||||
return scmd;
|
|
||||||
var scmdBuilder = LiteralArgumentBuilder.literal(path);
|
|
||||||
if (subcommand != null)
|
|
||||||
scmdBuilder.requires(o -> {
|
|
||||||
var sender = commodore.getBukkitSender(o);
|
|
||||||
return subcommand.hasPermission(sender);
|
|
||||||
});
|
|
||||||
scmd = scmdBuilder.build();
|
|
||||||
parent.addChild(scmd);
|
|
||||||
return scmd;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void registerTabcomplete(ICommand2MC command2MC, LiteralCommandNode<Command2MCSender> commandNode, Command bukkitCommand) {
|
|
||||||
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, builder) -> builder.suggest("untest").buildFuture()).build()));
|
|
||||||
}
|
|
||||||
String[] path = command2MC.getCommandPath().split(" ");
|
|
||||||
var shouldRegister = new AtomicBoolean(true);
|
|
||||||
@SuppressWarnings("unchecked") var maincmd = commodore.getRegisteredNodes().stream()
|
|
||||||
.filter(node -> node.getLiteral().equalsIgnoreCase(path[0]))
|
|
||||||
.filter(node -> {shouldRegister.set(false); return true;})
|
|
||||||
.map(node -> (LiteralCommandNode<Object>) node).findAny()
|
|
||||||
.orElseGet(() -> LiteralArgumentBuilder.literal(path[0]).build()); //Commodore 1.8 removes previous nodes
|
|
||||||
var cmd = maincmd;
|
|
||||||
for (int i = 1; i < path.length; i++) {
|
|
||||||
var scmd = subcmds.stream().filter(sd -> sd.method.getName().equals("def")).findAny().orElse(null);
|
|
||||||
cmd = appendSubcommand(path[i], cmd, scmd); //Add each part of the path as a child of the previous one
|
|
||||||
}
|
|
||||||
final var customTCmethods = Arrays.stream(command2MC.getClass().getDeclaredMethods()) //val doesn't recognize the type arguments
|
|
||||||
.flatMap(method -> Stream.of(Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod.class)))
|
|
||||||
.filter(Optional::isPresent).map(Optional::get) // Java 9 has .stream()
|
|
||||||
.flatMap(ctcm -> {
|
|
||||||
var paths = Optional.of(ctcm.subcommand()).filter(s -> s.length > 0)
|
|
||||||
.orElseGet(() -> new String[]{
|
|
||||||
ButtonPlugin.getCommand2MC().getCommandPath(method.getName(), ' ').trim()
|
|
||||||
});
|
|
||||||
return Arrays.stream(paths).map(name -> new Triplet<>(name, ctcm, method));
|
|
||||||
})).collect(Collectors.toList());
|
|
||||||
for (SubcommandData<ICommand2MC> subcmd : subcmds) {
|
|
||||||
String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim();
|
|
||||||
String[] subpath = subpathAsOne.split(" ");
|
|
||||||
CommandNode<Object> scmd = cmd;
|
|
||||||
if (subpath[0].length() > 0) { //If the method is def, it will contain one empty string
|
|
||||||
for (String s : subpath) {
|
|
||||||
scmd = appendSubcommand(s, scmd, subcmd); //Add method name part of the path (could_be_multiple())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Parameter[] parameters = subcmd.method.getParameters();
|
|
||||||
for (int i = 1; i < parameters.length; i++) { //Skip sender
|
|
||||||
Parameter parameter = parameters[i];
|
|
||||||
final boolean customParamType;
|
|
||||||
// TODO: Arg type
|
|
||||||
val param = subcmd.parameters[i - 1];
|
|
||||||
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
|
|
||||||
.map(CustomTabComplete::value);
|
|
||||||
var customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0()))
|
|
||||||
.filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1().param()))
|
|
||||||
.findAny();
|
|
||||||
var argb = RequiredArgumentBuilder.argument(param, type)
|
|
||||||
.suggests((context, builder) -> {
|
|
||||||
if (parameter.isVarArgs()) { //Do it before the builder is used
|
|
||||||
int nextTokenStart = context.getInput().lastIndexOf(' ') + 1;
|
|
||||||
builder = builder.createOffset(nextTokenStart);
|
|
||||||
}
|
|
||||||
if (customTC.isPresent())
|
|
||||||
for (val ctc : customTC.get())
|
|
||||||
builder.suggest(ctc);
|
|
||||||
boolean ignoreCustomParamType = false;
|
|
||||||
if (customTCmethod.isPresent()) {
|
|
||||||
var tr = customTCmethod.get();
|
|
||||||
if (tr.getValue1().ignoreTypeCompletion())
|
|
||||||
ignoreCustomParamType = true;
|
|
||||||
final var method = tr.getValue2();
|
|
||||||
val params = method.getParameters();
|
|
||||||
val args = new Object[params.length];
|
|
||||||
for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) {
|
|
||||||
val paramObj = params[j];
|
|
||||||
if (CommandSender.class.isAssignableFrom(paramObj.getType())) {
|
|
||||||
args[j] = commodore.getBukkitSender(context.getSource());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
val paramValueString = context.getArgument(subcmd.parameters[k], String.class);
|
|
||||||
if (paramObj.getType() == String.class) {
|
|
||||||
args[j] = paramValueString;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
val converter = getParamConverter(params[j].getType(), command2MC);
|
|
||||||
if (converter == null)
|
|
||||||
break;
|
|
||||||
val paramValue = converter.converter.apply(paramValueString);
|
|
||||||
if (paramValue == null) //For example, the player provided an invalid plugin name
|
|
||||||
break;
|
|
||||||
args[j] = paramValue;
|
|
||||||
k++; //Only increment if not CommandSender
|
|
||||||
}
|
|
||||||
if (args.length == 0 || args[args.length - 1] != null) { //Arguments filled entirely
|
|
||||||
try {
|
|
||||||
val suggestions = method.invoke(command2MC, args);
|
|
||||||
if (suggestions instanceof Iterable) {
|
|
||||||
//noinspection unchecked
|
|
||||||
for (Object suggestion : (Iterable<Object>) suggestions)
|
|
||||||
if (suggestion instanceof String)
|
|
||||||
builder.suggest((String) suggestion);
|
|
||||||
else
|
|
||||||
throw new ClassCastException("Bad return type! It should return an Iterable<String> or a String[].");
|
|
||||||
} else if (suggestions instanceof String[])
|
|
||||||
for (String suggestion : (String[]) suggestions)
|
|
||||||
builder.suggest(suggestion);
|
|
||||||
else
|
|
||||||
throw new ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.");
|
|
||||||
} catch (Exception e) {
|
|
||||||
String msg = "Failed to run tabcomplete method " + method.getName() + " for command " + command2MC.getClass().getSimpleName();
|
|
||||||
if (command2MC.getComponent() == null)
|
|
||||||
TBMCCoreAPI.SendException(msg, e, command2MC.getPlugin());
|
|
||||||
else
|
|
||||||
TBMCCoreAPI.SendException(msg, e, command2MC.getComponent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!ignoreCustomParamType && customParamType) {
|
|
||||||
val converter = getParamConverter(ptype, command2MC);
|
|
||||||
if (converter != null) {
|
|
||||||
var suggestions = converter.allSupplier.get();
|
|
||||||
for (String suggestion : suggestions)
|
|
||||||
builder.suggest(suggestion);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ptype == boolean.class || ptype == Boolean.class)
|
|
||||||
builder.suggest("true").suggest("false");
|
|
||||||
final String loweredInput = builder.getRemaining().toLowerCase();
|
|
||||||
return builder.suggest(param).buildFuture().whenComplete((s, e) -> //The list is automatically ordered
|
|
||||||
s.getList().add(s.getList().remove(0))) //So we need to put the <param> at the end after that
|
|
||||||
.whenComplete((ss, e) -> ss.getList().removeIf(s -> {
|
|
||||||
String text = s.getText();
|
|
||||||
return !text.startsWith("<") && !text.startsWith("[") && !text.toLowerCase().startsWith(loweredInput);
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
var arg = argb.build();
|
|
||||||
scmd.addChild(arg);
|
|
||||||
scmd = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shouldRegister.get()) {
|
|
||||||
commodore.register(maincmd);
|
|
||||||
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
|
|
||||||
String pluginName = command2MC.getPlugin().getName().toLowerCase();
|
|
||||||
var prefixedcmd = LiteralArgumentBuilder.literal(pluginName + ":" + path[0])
|
|
||||||
.redirect(maincmd).build();
|
|
||||||
commodore.register(prefixedcmd);
|
|
||||||
for (String alias : bukkitCommand.getAliases()) {
|
|
||||||
commodore.register(LiteralArgumentBuilder.literal(alias).redirect(maincmd).build());
|
|
||||||
commodore.register(LiteralArgumentBuilder.literal(pluginName + ":" + alias).redirect(maincmd).build());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ParamConverter<?> getParamConverter(Class<?> cl, ICommand2MC command2MC) {
|
|
||||||
val converter = ButtonPlugin.getCommand2MC().paramConverters.get(cl);
|
|
||||||
if (converter == null) {
|
|
||||||
String msg = "Could not find a suitable converter for type " + cl.getSimpleName();
|
|
||||||
Exception exception = new NullPointerException("converter is null");
|
|
||||||
if (command2MC.getComponent() == null)
|
|
||||||
TBMCCoreAPI.SendException(msg, exception, command2MC.getPlugin());
|
|
||||||
else
|
|
||||||
TBMCCoreAPI.SendException(msg, exception, command2MC.getComponent());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return converter;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package buttondevteam.lib.chat.commands;
|
||||||
|
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@UtilityClass
|
||||||
|
public class CommandUtils {
|
||||||
|
/**
|
||||||
|
* Returns the path of the given subcommand excluding the class' path. It will start with the given replace char.
|
||||||
|
*
|
||||||
|
* @param methodName The method's name, method.getName()
|
||||||
|
* @param replaceChar The character to use between subcommands
|
||||||
|
* @return The command path starting with the replacement char.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static String getCommandPath(String methodName, char replaceChar) {
|
||||||
|
return methodName.equals("def") ? "" : replaceChar + methodName.replace('_', replaceChar).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>master-SNAPSHOT</version>
|
<version>master-SNAPSHOT</version>
|
||||||
<properties>
|
<properties>
|
||||||
<lombok.version>1.18.10</lombok.version>
|
<lombok.version>1.18.26</lombok.version>
|
||||||
</properties>
|
</properties>
|
||||||
<name>Core POM for Chroma</name>
|
<name>Core POM for Chroma</name>
|
||||||
|
|
||||||
|
@ -21,30 +21,14 @@
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.1</version>
|
<version>3.8.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>8</release>
|
<release>17</release>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<annotationProcessorPath>
|
<path>
|
||||||
<groupId>com.github.bsideup.jabel</groupId>
|
|
||||||
<artifactId>jabel-javac-plugin</artifactId>
|
|
||||||
<version>0.2.0</version>
|
|
||||||
</annotationProcessorPath>
|
|
||||||
<annotationProcessorPath>
|
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>${lombok.version}</version>
|
<version>1.18.26</version>
|
||||||
</annotationProcessorPath>
|
</path>
|
||||||
<annotationProcessorPath>
|
|
||||||
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
|
|
||||||
<artifactId>ButtonProcessor</artifactId>
|
|
||||||
<version>master-SNAPSHOT</version>
|
|
||||||
</annotationProcessorPath>
|
|
||||||
</annotationProcessorPaths>
|
</annotationProcessorPaths>
|
||||||
<annotationProcessors> <!-- Order is important, so these lines are needed -->
|
|
||||||
<annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor>
|
|
||||||
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
|
|
||||||
</annotationProcessor>
|
|
||||||
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
|
|
||||||
</annotationProcessors>
|
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
@ -97,31 +81,4 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>intellij-idea-only</id>
|
|
||||||
<activation>
|
|
||||||
<property>
|
|
||||||
<name>idea.maven.embedder.version</name>
|
|
||||||
</property>
|
|
||||||
</activation>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<release>11</release>
|
|
||||||
<!--
|
|
||||||
<compilerArgs>
|
|
||||||
<arg>HYPHENHYPHENenable-preview</arg>
|
|
||||||
</compilerArgs>
|
|
||||||
-->
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
</project>
|
</project>
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -9,8 +9,8 @@
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>master-SNAPSHOT</version>
|
<version>master-SNAPSHOT</version>
|
||||||
<properties>
|
<properties>
|
||||||
<lombok.version>1.18.12</lombok.version>
|
<lombok.version>1.18.26</lombok.version>
|
||||||
</properties>
|
</properties>
|
||||||
<name>Chroma Parent</name>
|
<name>Chroma Parent</name>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
@ -27,7 +27,7 @@
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.8.1</version>
|
<version>3.8.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<release>8</release>
|
<release>17</release>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
|
|
Loading…
Reference in a new issue