Compare commits

..

No commits in common. "master" and "commands" have entirely different histories.

68 changed files with 1893 additions and 1886 deletions

View file

@ -13,5 +13,3 @@ tab_width=4
indent_style=space
indent_size=2
[*.xml]
indent_style = tab

View file

@ -1,75 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '0 10 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: [ 'java' ]
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Setup Java JDK
uses: actions/setup-java@v1.3.0
with:
java-version: 11
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>ChromaCore</artifactId>
<groupId>com.github.TBMCPlugins</groupId>
<version>master-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>BuildConfigUpdater</artifactId>
<repositories>
<repository>
<id>Jitpack</id>
<url>https://jitpack.io/</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>Chroma-Core</artifactId>
<version>master-SNAPSHOT</version>
</dependency>
<dependency> <!-- Needed for TBMCCoreAPI -->
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.12.2-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
</project>

View file

@ -0,0 +1,16 @@
import buttondevteam.core.component.updater.PluginUpdater;
import java.util.List;
import java.util.stream.Collectors;
public class BCUMain {
public static void main(String[] args) {
System.out.println("Getting list of repositories...");
List<String> plugins = PluginUpdater.GetPluginNames();
System.out.println("Removing non-Maven projects...");
plugins.removeIf(plugin -> PluginUpdater.isNotMaven(plugin, "master"));
System.out.println(plugins.stream().collect(Collectors.joining("\n")));
for (String plugin : plugins) { //TODO: We don't want to apply it all at once, especially to unused/unowned repos
} //TODO: Add it to ButtonCore - or actually as a plugin or ButtonProcessor
}
}

13
ButtonProcessor/pom.xml Normal file → Executable file
View file

@ -11,7 +11,7 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.32</version>
<version>1.21</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -21,10 +21,13 @@
<scope>compile</scope>
</dependency>
</dependencies>
<!-- Can't use Core POM because it uses this processor -->
<parent>
<groupId>com.github.TBMCPlugins</groupId>
<artifactId>ChromaCore</artifactId>
<version>master-SNAPSHOT</version>
</parent>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ButtonProcessor</name>
@ -33,13 +36,9 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>

View file

@ -27,8 +27,10 @@ public class ButtonProcessor extends AbstractProcessor {
for (TypeElement te : annotations) {
Set<? extends Element> classes = roundEnv.getElementsAnnotatedWith(te);
for (Element targetcl : classes) {
System.out.println("Processing " + targetcl);
List<? extends AnnotationMirror> annotationMirrors = processingEnv.getElementUtils()
.getAllAnnotationMirrors(targetcl);
//System.out.println("Annotations: " + annotationMirrors);
Function<String, Boolean> hasAnnotation = ann -> annotationMirrors.stream()
.anyMatch(am -> am.getAnnotationType().toString().contains(ann));
if (hasAnnotation.apply("ChromaGamerEnforcer") && !hasAnnotation.apply("UserClass")
@ -39,6 +41,10 @@ public class ButtonProcessor extends AbstractProcessor {
&& !targetcl.getModifiers().contains(Modifier.ABSTRACT))
processingEnv.getMessager().printMessage(Kind.ERROR,
"No PlayerClass annotation found for " + targetcl.getSimpleName(), targetcl);
for (AnnotationMirror annotation : annotationMirrors) {
String type = annotation.getAnnotationType().toString();
//System.out.println("Type: " + type);
}
processSubcommands(targetcl, annotationMirrors);
if (hasAnnotation.apply("HasConfig"))
configProcessor.process(targetcl);
@ -56,25 +62,30 @@ public class ButtonProcessor extends AbstractProcessor {
return true; // claim the annotations
}
private final YamlConfiguration yc = new YamlConfiguration();
private YamlConfiguration yc = new YamlConfiguration();
private boolean found = false;
private ConfigProcessor configProcessor;
private void processSubcommands(Element method, List<? extends AnnotationMirror> annotationMirrors) {
if (!(method instanceof ExecutableElement))
private void processSubcommands(Element targetcl, List<? extends AnnotationMirror> annotationMirrors) {
if (!(targetcl instanceof ExecutableElement))
return;
//System.out.println("Annotations: "+annotationMirrors);
if (annotationMirrors.stream().noneMatch(an -> an.getAnnotationType().toString().endsWith("Subcommand")))
return;
ConfigurationSection cs = yc.createSection(method.getEnclosingElement().toString()
+ "." + method.getSimpleName().toString()); //Need to do the 2 config sections at once so it doesn't overwrite the class section
System.out.println("Found subcommand: " + method);
cs.set("method", method.toString());
cs.set("params", ((ExecutableElement) method).getParameters().stream().skip(1).map(p -> {
//System.out.print("Processing method: " + targetcl.getEnclosingElement()+" "+targetcl);
ConfigurationSection cs = yc.createSection(targetcl.getEnclosingElement().toString()
+ "." + targetcl.getSimpleName().toString()); //Need to do the 2 config sections at once so it doesn't overwrite the class section
System.out.println(targetcl);
cs.set("method", targetcl.toString());
cs.set("params", ((ExecutableElement) targetcl).getParameters().stream().skip(1).map(p -> {
//String tn=p.asType().toString();
//return tn.substring(tn.lastIndexOf('.')+1)+" "+p.getSimpleName();
boolean optional = p.getAnnotationMirrors().stream().anyMatch(am -> am.getAnnotationType().toString().endsWith("OptionalArg"));
if (optional)
return "[" + p.getSimpleName() + "]";
return "<" + p.getSimpleName() + ">";
}).collect(Collectors.joining(" ")));
//System.out.println();
found = true;
}

View file

@ -48,27 +48,28 @@ public class ConfigProcessor {
e.printStackTrace();
}
for (Element e : targetcl.getEnclosedElements()) {
TypeMirror tm;
/*System.out.println("Element: "+e);
System.out.println("Type: "+e.getClass()+" - "+e.getKind());
if(e instanceof ExecutableElement)
tm = ((ExecutableElement) e).getReturnType();
else if (e.getKind().isField())
tm = e.asType();
else
continue;
System.out.println("METHOD!");*/
if (!(e instanceof ExecutableElement)) continue;
TypeMirror tm = ((ExecutableElement) e).getReturnType();
if (tm.getKind() != TypeKind.DECLARED) continue;
DeclaredType dt = (DeclaredType) tm;
if (!dt.asElement().getSimpleName().toString().contains("ConfigData"))
if (!dt.asElement().getSimpleName().contentEquals("ConfigData"))
continue; //Ahhha! There was a return here! (MinecraftChatModule getListener())
System.out.println("Config: " + e.getSimpleName());
String doc = procEnv.getElementUtils().getDocComment(e);
if (doc == null) continue;
System.out.println("Adding docs for config: " + e.getSimpleName());
System.out.println("DOC: " + doc);
yc.set(path + "." + e.getSimpleName(), doc.trim());
}
String javadoc = procEnv.getElementUtils().getDocComment(targetcl);
if (javadoc != null) {
System.out.println("Adding docs for class: " + targetcl.getSimpleName());
yc.set(path + ".generalDescriptionInsteadOfAConfig", javadoc.trim());
System.out.println("JAVADOC");
System.out.println(javadoc.trim());
yc.set(path, javadoc.trim());
}
try {
yc.save(file);

View file

@ -10,7 +10,6 @@
<artifactId>Chroma-Core</artifactId>
<name>Chroma-Core</name>
<description>Chroma-Core</description>
<version>v${noprefix.version}-SNAPSHOT</version>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
@ -30,7 +29,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.1</version>
<version>2.4.2</version>
<executions>
<execution>
<phase>package</phase>
@ -39,18 +38,7 @@
</goals>
<configuration>
<artifactSet>
<includes>
<include>me.lucko:commodore</include>
<include>org.javatuples:javatuples</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>me.lucko.commodore</pattern>
<!-- vvv Replace with the package of your plugin vvv -->
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
@ -120,19 +108,19 @@
<url>https://ci.ender.zone/plugin/repository/everything/</url>
</repository>
<repository>
<id>Multiverse-Core</id>
<url>https://repo.onarandombox.com/content/groups/public/</url>
<id>Votifier</id>
<url>https://dl.bintray.com/nuvotifier/maven/</url>
</repository>
<repository>
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
<id>Multiverse-Core</id>
<url>http://repo.onarandombox.com/content/repositories/multiverse/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
<version>0.9.10</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -157,12 +145,12 @@
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
<version>3.20.0-GA</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.2.0</version>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
@ -176,30 +164,18 @@
<version>2.17.1</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.NuVotifier.NuVotifier/nuvotifier-bukkit -->
<dependency>
<groupId>com.github.NuVotifier.NuVotifier</groupId>
<artifactId>nuvotifier-bukkit</artifactId>
<version>v2.7.1</version>
<groupId>com.vexsoftware</groupId>
<artifactId>nuvotifier-universal</artifactId>
<version>2.3.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.onarandombox.multiversecore</groupId>
<artifactId>Multiverse-Core</artifactId>
<version>4.3.1</version>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.11</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<organization>
<name>TBMCPlugins</name>
@ -216,7 +192,6 @@
<!-- github server corresponds to entry in ~/.m2/settings.xml -->
<github.global.server>github</github.global.server>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<noprefix.version>1.0.1</noprefix.version>
</properties>
<scm>
<url>https://github.com/TBMCPlugins/mvn-repo</url>

View file

@ -1,38 +1,17 @@
package buttondevteam.core;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import java.util.Arrays;
import java.util.Optional;
@CommandClass
public class ChromaCommand extends ICommand2MC {
public ChromaCommand() {
getManager().addParamConverter(ButtonPlugin.class, name ->
(ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name))
.filter(plugin -> plugin instanceof ButtonPlugin).orElse(null),
"No Chroma plugin found by that name.", () -> Arrays.stream(Bukkit.getPluginManager().getPlugins())
.filter(plugin -> plugin instanceof ButtonPlugin).map(Plugin::getName)::iterator);
}
@Command2.Subcommand
public void reload(CommandSender sender, @Command2.OptionalArg ButtonPlugin plugin) {
if (plugin == null)
plugin = MainPlugin.Instance;
if (plugin.tryReloadConfig())
sender.sendMessage("§b" + plugin.getName() + " config reloaded.");
@Command2.Subcommand //TODO: Main permissions (groups) like 'mod'
public void reload(CommandSender sender) {
if (MainPlugin.Instance.tryReloadConfig())
sender.sendMessage("§bCore config reloaded.");
else
sender.sendMessage("§cFailed to reload config. Check console.");
}
@Command2.Subcommand
public void def(CommandSender sender) {
sender.sendMessage(ButtonPlugin.getCommand2MC().getCommandsText());
}
}

View file

@ -6,17 +6,13 @@ import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.Command2.Subcommand;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.CustomTabCompleteMethod;
import buttondevteam.lib.chat.ICommand2MC;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
@CommandClass(modOnly = true, helpText = {
"Component command",
@ -24,15 +20,14 @@ import java.util.stream.Stream;
})
public class ComponentCommand extends ICommand2MC {
public ComponentCommand() {
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!",
() -> Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator);
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!");
}
@Subcommand(helpText = {
"Enable component",
"Temporarily or permanently enables a component."
"Temporarily enables a component. If you want to permanently enable a component, change it's 'enabled' config option.\""
})
public boolean enable(CommandSender sender, Plugin plugin, String component, @Command2.OptionalArg boolean permanent) {
public boolean enable(CommandSender sender, Plugin plugin, String component) {
if (plugin instanceof ButtonPlugin) {
if (!((ButtonPlugin) plugin).justReload()) {
sender.sendMessage("§cCouldn't reload config, check console.");
@ -40,15 +35,15 @@ public class ComponentCommand extends ICommand2MC {
}
} else
plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable
return enable_disable(sender, plugin, component, true, permanent);
return enable_disable(sender, plugin, component, true);
}
@Subcommand(helpText = {
"Disable component",
"Temporarily or permanently disables a component."
"Temporarily disables a component. If you want to permanently disable a component, change it's 'enabled' config option."
})
public boolean disable(CommandSender sender, Plugin plugin, String component, @Command2.OptionalArg boolean permanent) {
return enable_disable(sender, plugin, component, false, permanent);
public boolean disable(CommandSender sender, Plugin plugin, String component) {
return enable_disable(sender, plugin, component, false);
}
@Subcommand(helpText = {
@ -62,41 +57,25 @@ public class ComponentCommand extends ICommand2MC {
return true;
}
@CustomTabCompleteMethod(param = "component", subcommand = {"enable", "disable"})
public Iterable<String> componentTabcomplete(Plugin plugin) {
return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator;
}
@CustomTabCompleteMethod(param = "plugin", subcommand = {"list", "enable", "disable"}, ignoreTypeCompletion = true)
public Iterable<String> pluginTabcomplete() {
return Component.getComponents().values().stream().map(Component::getPlugin)
.distinct().map(Plugin::getName)::iterator;
}
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) {
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable) {
try {
val oc = getComponentOrError(plugin, component, sender);
if (!oc.isPresent())
return true;
Component.setComponentEnabled(oc.get(), enable);
if (permanent)
oc.get().shouldBeEnabled.set(enable);
sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled " + (permanent ? "permanently" : "temporarily") + ".");
sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled.");
} catch (Exception e) {
TBMCCoreAPI.SendException("Couldn't " + (enable ? "en" : "dis") + "able component " + component + "!", e, (JavaPlugin) plugin);
TBMCCoreAPI.SendException("Couldn't " + (enable ? "en" : "dis") + "able component " + component + "!", e);
}
return true;
}
private Stream<Component<? extends JavaPlugin>> getPluginComponents(Plugin plugin) {
return Component.getComponents().values().stream()
.filter(c -> plugin.getName().equals(c.getPlugin().getName()));
}
private Optional<Component<?>> getComponentOrError(Plugin plugin, String arg, CommandSender sender) {
val oc = getPluginComponents(plugin).filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny();
val oc = Component.getComponents().values().stream()
.filter(c -> plugin.getName().equals(c.getPlugin().getName()))
.filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny();
if (!oc.isPresent())
sender.sendMessage("§cComponent not found!"); //^ Much simpler to solve in the new command system
return oc;
}
} //TODO: Tabcompletion for the new command system
}

View file

@ -21,11 +21,11 @@ public final class ComponentManager {
*/
public static void enableComponents() {
//Component.getComponents().values().stream().filter(c->cs.getConfigurationSection(c.getClass().getSimpleName()).getBoolean("enabled")).forEach(c-> {
Component.getComponents().values().stream().filter(c -> c.shouldBeEnabled.get()).forEach(c -> {
Component.getComponents().values().stream().filter(c -> c.shouldBeEnabled().get()).forEach(c -> {
try {
Component.setComponentEnabled(c, true);
} catch (Exception | NoClassDefFoundError e) {
TBMCCoreAPI.SendException("Failed to enable one of the components: " + c.getClass().getSimpleName(), e, c);
TBMCCoreAPI.SendException("Failed to enable one of the components: " + c.getClass().getSimpleName(), e);
}
});
componentsEnabled = true;

View file

@ -8,6 +8,9 @@ import buttondevteam.core.component.randomtp.RandomTPComponent;
import buttondevteam.core.component.restart.RestartComponent;
import buttondevteam.core.component.spawn.SpawnComponent;
import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.core.component.updater.PluginUpdater;
import buttondevteam.core.component.updater.PluginUpdaterComponent;
import buttondevteam.core.component.votifier.VotifierComponent;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component;
@ -23,12 +26,10 @@ import lombok.Setter;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.RegisteredServiceProvider;
@ -37,10 +38,10 @@ import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.logging.Logger;
public class MainPlugin extends ButtonPlugin {
@ -64,35 +65,48 @@ public class MainPlugin extends ButtonPlugin {
* Sets whether the plugin should write a list of installed plugins in a txt file.
* It can be useful if some other software needs to know the plugins.
*/
private final ConfigData<Boolean> writePluginList = getIConfig().getData("writePluginList", false);
private ConfigData<Boolean> writePluginList() {
return getIConfig().getData("writePluginList", false);
}
/**
* The chat format to use for messages from other platforms if Chroma-Chat is not installed.
*/
ConfigData<String> chatFormat = getIConfig().getData("chatFormat", "[{origin}|" +
ConfigData<String> chatFormat() {
return getIConfig().getData("chatFormat", "[{origin}|" +
"{channel}] <{name}> {message}");
}
/**
* Print some debug information.
*/
public final ConfigData<Boolean> test = getIConfig().getData("test", false);
public ConfigData<Boolean> test() {
return getIConfig().getData("test", false);
}
/**
* If a Chroma command clashes with another plugin's command, this setting determines whether the Chroma command should be executed or the other plugin's.
/*
* By default, the plugin uses Vault for all command permission checks, but this can have issues (with PEX for example) where default permissions aren't granted.
* When this setting is off, the plugin uses Bukkit's built-in way of handling permissions, which usually works fine for players.
* You can also grant chroma.command.* to each player (mod-only commands need another permission, chroma.mod).
*/
public final ConfigData<Boolean> prioritizeCustomCommands = getIConfig().getData("prioritizeCustomCommands", false);
/*public ConfigData<Boolean> useVaultForCommands() {
return getIConfig().getData("useVaultForCommands", true);
}*/
@Override
public void pluginEnable() {
// Logs "Plugin Enabled", registers commands
Instance = this;
PluginDescriptionFile pdf = getDescription();
logger = getLogger();
if (!setupPermissions())
throw new NullPointerException("No permission plugin found!");
if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials
if (!setupEconomy()) //Though Essentials always provides economy so this shouldn't happen
getLogger().warning("No economy plugin found! Components using economy will not be registered.");
saveConfig();
Component.registerComponent(this, new PluginUpdaterComponent());
Component.registerComponent(this, new RestartComponent());
//noinspection unchecked - needed for testing
Component.registerComponent(this, new ChannelComponent());
Component.registerComponent(this, new RandomTPComponent());
Component.registerComponent(this, new MemberComponent());
@ -100,8 +114,8 @@ public class MainPlugin extends ButtonPlugin {
Component.registerComponent(this, new SpawnComponent());
if (Bukkit.getPluginManager().isPluginEnabled("Towny")) //It fails to load the component class otherwise
Component.registerComponent(this, new TownyComponent());
/*if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new VotifierComponent(economy));*/
if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new VotifierComponent(economy));
ComponentManager.enableComponents();
registerCommand(new ComponentCommand());
registerCommand(new ChromaCommand());
@ -111,7 +125,7 @@ public class MainPlugin extends ButtonPlugin {
? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks
ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player
? TBMCPlayer.getPlayer(((Player) sender).getUniqueId(), TBMCPlayer.class) : null)); //Players, has higher priority
TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class, TBMCPlayer::new);
TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class);
TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); //The /ooc ID has moved to the config
TBMCChatAPI.RegisterChatChannel(
Channel.AdminChat = new Channel("§cADMIN§f", Color.Red, "a", Channel.inGroupFilter(null)));
@ -124,26 +138,38 @@ public class MainPlugin extends ButtonPlugin {
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§aGREEN§f", Color.Green, "green"));
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue"));
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple"));
Supplier<Iterable<String>> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator;
getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier);
getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier);
if (writePluginList.get()) {
if (writePluginList().get()) {
try {
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator);
} catch (IOException e) {
TBMCCoreAPI.SendException("Failed to write plugin list!", e, this);
TBMCCoreAPI.SendException("Failed to write plugin list!", e);
}
}
if (getServer().getPluginManager().isPluginEnabled("Essentials"))
ess = Essentials.getPlugin(Essentials.class);
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test.get() + ".");
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test().get() + ".");
}
@Override
public void pluginDisable() {
logger.info("Saving player data...");
ChromaGamerBase.saveUsers();
TBMCPlayerBase.savePlayers();
logger.info("Player data saved.");
new Thread(() -> {
File[] files = PluginUpdater.updatedir.listFiles();
if (files == null)
return;
logger.info("Updating " + files.length + " plugins...");
for (File file : files) {
try {
Files.move(file.toPath(), new File("plugins", file.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING);
logger.info("Updated " + file.getName());
} catch (IOException e) {
e.printStackTrace();
}
}
logger.info("Update complete!");
}).start();
}
private boolean setupPermissions() {

View file

@ -5,7 +5,6 @@ import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.chat.ChatMessage;
import buttondevteam.lib.chat.Command2MCSender;
import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayer;
import buttondevteam.lib.player.TBMCPlayerBase;
import lombok.val;
@ -28,21 +27,12 @@ public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerJoin(PlayerJoinEvent event) {
var p = event.getPlayer();
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class);
String pname = player.PlayerName.get();
if (pname.length() == 0) {
player.PlayerName.set(p.getName());
MainPlugin.Instance.getLogger().info("Player name saved: " + player.PlayerName.get());
} else if (!p.getName().equals(pname)) {
MainPlugin.Instance.getLogger().info(pname + " renamed to " + p.getName());
player.PlayerName.set(p.getName());
}
TBMCPlayerBase.joinPlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerLeave(PlayerQuitEvent event) {
TBMCPlayerBase.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class).uncache();
TBMCPlayerBase.quitPlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.HIGHEST)
@ -52,7 +42,7 @@ public class PlayerListener implements Listener {
if (Arrays.stream(event.getExceptions()).anyMatch("Minecraft"::equalsIgnoreCase))
return;
Bukkit.getOnlinePlayers().stream().filter(event::shouldSendTo)
.forEach(p -> p.sendMessage(event.getChannel().DisplayName.get().substring(0, 2) + event.getMessage()));
.forEach(p -> p.sendMessage(event.getChannel().DisplayName().get().substring(0, 2) + event.getMessage()));
}
@EventHandler
@ -68,9 +58,7 @@ public class PlayerListener implements Listener {
private void handlePreprocess(CommandSender sender, String message, Cancellable event) {
if (event.isCancelled()) return;
val cg = ChromaGamerBase.getFromSender(sender);
if (cg == null) throw new RuntimeException("Couldn't get user from sender for " + sender.getName() + "!");
val ev = new TBMCCommandPreprocessEvent(sender, cg.channel.get(), message, sender);
val ev = new TBMCCommandPreprocessEvent(sender, message);
Bukkit.getPluginManager().callEvent(ev);
if (ev.isCancelled())
event.setCancelled(true); //Cancel the original event
@ -80,9 +68,9 @@ public class PlayerListener implements Listener {
public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) {
if (event.isCancelled()) return;
try {
event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender(), event.getChannel(), event.getPermCheck()), event.getMessage()));
event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender()), event.getMessage()));
} catch (Exception e) {
TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e, MainPlugin.Instance);
TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e);
}
}
@ -102,12 +90,11 @@ public class PlayerListener implements Listener {
return;
if (!MainPlugin.Instance.isChatHandlerEnabled()) return;
if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages
var channel = event.getChannel();
String msg = MainPlugin.Instance.chatFormat.get()
.replace("{channel}", channel.DisplayName.get())
String msg = MainPlugin.Instance.chatFormat().get()
.replace("{channel}", event.getChannel().DisplayName().get())
.replace("{origin}", event.getOrigin().substring(0, 1))
.replace("{name}", ChromaUtils.getDisplayName(event.getSender()))
.replace("{message}", String.format("§%x%s", channel.Color.get().ordinal(), event.getMessage()));
.replace("{message}", event.getMessage());
for (Player player : Bukkit.getOnlinePlayers())
if (event.shouldSendTo(player))
player.sendMessage(msg);

View file

@ -2,7 +2,6 @@ package buttondevteam.core;
import buttondevteam.core.component.channel.Channel;
import buttondevteam.core.component.channel.ChannelComponent;
import buttondevteam.lib.ChromaUtils;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.Color;
import buttondevteam.lib.chat.TBMCChatAPI;
@ -20,9 +19,7 @@ import java.util.Collections;
import java.util.logging.Logger;
public class TestPrepare {
public static void PrepareServer() {
ChromaUtils.setTest(); //Needs to be in a separate class because of the potential lack of Mockito
Bukkit.setServer(Mockito.mock(Server.class, new Answer<Object>() {
@Override
@ -44,6 +41,7 @@ public class TestPrepare {
return cl.isAssignableFrom(invocation.getMethod().getReturnType());
}
}));
//noinspection unchecked
Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent());
TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null));
}

View file

@ -4,7 +4,6 @@ import buttondevteam.core.ComponentManager;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import buttondevteam.lib.chat.Color;
import com.google.common.collect.Lists;
import org.bukkit.Bukkit;
@ -21,9 +20,6 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Represents a chat channel. May only be instantiated after the channel component is registered.
*/
public class Channel {
/**
* Specifies a score that means it's OK to send - but it does not define any groups, only send or not send. See {@link #GROUP_EVERYONE}
@ -43,20 +39,34 @@ public class Channel {
private String defDisplayName;
private Color defColor;
private IHaveConfig config;
private void throwGame() {
if (component == null) throw new RuntimeException("Cannot access channel properties until registered!");
}
public final ConfigData<Boolean> Enabled;
public final ConfigData<Boolean> Enabled() {
throwGame();
return component.getConfig().getData(ID + ".enabled", true);
}
/**
* Must start with a color code
*/
public final ConfigData<String> DisplayName;
public final ConfigData<String> DisplayName() {
throwGame();
return component.getConfig().getData(ID + ".displayName", defDisplayName); //TODO: Use config map
}
public final ConfigData<Color> Color;
public final ConfigData<Color> Color() {
throwGame();
return component.getConfig().getData(ID + ".color", defColor, c -> Color.valueOf((String) c), Enum::toString);
}
public final String ID;
public ConfigData<String[]> IDs;
@SuppressWarnings("unchecked")
public ConfigData<String[]> IDs() {
throwGame();
return component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
}
/**
* Filters both the sender and the targets
*/
@ -79,12 +89,6 @@ public class Channel {
defColor = color;
ID = command;
this.filteranderrormsg = filteranderrormsg;
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
}
/**
@ -99,19 +103,6 @@ public class Channel {
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
}
private static void init() {
if (component == null)
component = (ChannelComponent) Component.getComponents().get(ChannelComponent.class);
if (component == null)
throw new RuntimeException("Attempting to create a channel before the component is registered!");
}
public boolean isGlobal() {
@ -157,7 +148,7 @@ public class Channel {
* @return Only the enabled channels
*/
public static Stream<Channel> getChannels() {
return channels.stream().filter(ch -> ch.Enabled.get());
return channels.stream().filter(ch -> ch.Enabled().get());
}
/**
@ -199,8 +190,11 @@ public class Channel {
public static void RegisterChannel(Channel channel) {
if (!channel.isGlobal() && !ComponentManager.isEnabled(ChannelComponent.class))
return; //Allow registering the global chat (and I guess other chats like the RP chat)
if (component == null)
component = (ChannelComponent) Component.getComponents().get(ChannelComponent.class);
if (component == null)
throw new RuntimeException("Attempting to register a channel before the component is registered!");
channels.add(channel);
component.registerChannelCommand(channel);
Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> Bukkit.getPluginManager().callEvent(new ChatChannelRegisterEvent(channel))); // Wait for server start
}

View file

@ -1,17 +1,13 @@
package buttondevteam.core.component.channel;
import buttondevteam.lib.ChromaUtils;
import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.*;
import buttondevteam.lib.player.ChromaGamerBase;
import lombok.RequiredArgsConstructor;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Manages chat channels. If disabled, only global channels will be registered.
*/
public class ChannelComponent extends Component<JavaPlugin> {
public class ChannelComponent extends Component {
static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave;
@Override
@ -33,51 +29,6 @@ public class ChannelComponent extends Component<JavaPlugin> {
@Override
protected void disable() {
}
void registerChannelCommand(Channel channel) {
if (!ChromaUtils.isTest())
registerCommand(new ChannelCommand(channel));
}
@CommandClass
@RequiredArgsConstructor
private static class ChannelCommand extends ICommand2MC {
private final Channel channel;
@Override
public String getCommandPath() {
return channel.ID;
}
@Override
public String[] getCommandPaths() {
return channel.IDs.get();
}
@Command2.Subcommand
public void def(Command2MCSender senderMC, @Command2.OptionalArg @Command2.TextArg String message) {
var sender = senderMC.getSender();
var user = ChromaGamerBase.getFromSender(sender);
if (user == null) {
sender.sendMessage("§cYou can't use channels from this platform.");
return;
}
if (message == null) {
Channel oldch = user.channel.get();
if (oldch instanceof ChatRoom)
((ChatRoom) oldch).leaveRoom(sender);
if (oldch.equals(channel))
user.channel.set(Channel.GlobalChat);
else {
user.channel.set(channel);
if (channel instanceof ChatRoom)
((ChatRoom) channel).joinRoom(sender);
}
sender.sendMessage("§6You are now talking in: §b" + user.channel.get().DisplayName.get());
} else
TBMCChatAPI.SendChatMessage(ChatMessage.builder(sender, user, message).fromCommand(true)
.permCheck(senderMC.getPermCheck()).build(), channel);
}
}
}

View file

@ -19,6 +19,7 @@ public class MemberCommand extends ICommand2MC {
private final MemberComponent component;
public MemberCommand(MemberComponent component) {
getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!");
this.component = component;
}
@ -38,8 +39,8 @@ public class MemberCommand extends ICommand2MC {
sender.sendMessage("§cCannot find player or haven't played before.");
return;
}
if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup.get())
: MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup.get()))
if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup().get())
: MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup().get()))
sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!");
else
sender.sendMessage("§cFailed to " + (add ? "add" : "remove") + " " + op.getName() + " as a member!");

View file

@ -25,17 +25,23 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
/**
* The permission group to give to the player
*/
final ConfigData<String> memberGroup = getConfig().getData("memberGroup", "member");
ConfigData<String> memberGroup() {
return getConfig().getData("memberGroup", "member");
}
/**
* The amount of hours needed to play before promotion
*/
private final ConfigData<Integer> playedHours = getConfig().getData("playedHours", 12);
private ConfigData<Integer> playedHours() {
return getConfig().getData("playedHours", 12);
}
/**
* The amount of days passed since first login
*/
private final ConfigData<Integer> registeredForDays = getConfig().getData("registeredForDays", 7);
private ConfigData<Integer> registeredForDays() {
return getConfig().getData("registeredForDays", 7);
}
private AbstractMap.SimpleEntry<Statistic, Integer> playtime;
@ -63,22 +69,22 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
public Boolean addPlayerAsMember(Player player) {
try {
if (permission.playerAddGroup(null, player, memberGroup.get())) {
if (permission.playerAddGroup(null, player, memberGroup().get())) {
player.sendMessage("§bYou are a member now!");
log("Added " + player.getName() + " as a member.");
MainPlugin.Instance.getLogger().info("Added " + player.getName() + " as a member.");
return true;
} else {
logWarn("Failed to assign the member role! Please make sure the member group exists or disable the component if it's unused.");
MainPlugin.Instance.getLogger().warning("Failed to assign the member role! Please make sure the member group exists or disable the component if it's unused.");
return false;
}
} catch (UnsupportedOperationException e) {
logWarn("Failed to assign the member role! Groups are not supported by the permissions implementation.");
MainPlugin.Instance.getLogger().warning("Failed to assign the member role! Groups are not supported by the permissions implementation.");
return null;
}
}
public boolean checkNotMember(Player player) {
return permission != null && !permission.playerInGroup(player, memberGroup.get());
return permission != null && !permission.playerInGroup(player, memberGroup().get());
}
public boolean checkRegTime(Player player) {
@ -86,14 +92,14 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
}
public boolean checkPlayTime(Player player) {
return getPlayTime(player) > playtime.getValue() * playedHours.get();
return getPlayTime(player) > playtime.getValue() * playedHours().get();
}
/**
* Returns milliseconds
*/
public long getRegTime(Player player) {
Instant date = new Date(player.getFirstPlayed()).toInstant().plus(registeredForDays.get(), ChronoUnit.DAYS);
Instant date = new Date(player.getFirstPlayed()).toInstant().plus(registeredForDays().get(), ChronoUnit.DAYS);
if (date.isAfter(Instant.now()))
return date.toEpochMilli() - Instant.now().toEpochMilli();
return -1;
@ -107,7 +113,7 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
* Returns hours
*/
public double getPlayTime(Player player) {
double pt = playedHours.get() - (double) getPlayTimeTotal(player) / playtime.getValue();
double pt = playedHours().get() - (double) getPlayTimeTotal(player) / playtime.getValue();
if (pt < 0) return -1;
return pt;
}

View file

@ -59,10 +59,11 @@ public class RandomTP extends ICommand2MC
{
world = Bukkit.getWorld("World");
border = world.getWorldBorder();
component.log("Getting new location");
Logger logger = component.getPlugin().getLogger();
logger.info("Getting new location");
if(border.getSize() > 100000)
component.logWarn("World border is wide, it may take a minute...");
component.log("Success: "+newLocation());
logger.warning("World border is wide, it may take a minute...");
logger.info("Success: "+newLocation());
}
/*================================================================================================*/

View file

@ -21,7 +21,6 @@ import org.bukkit.command.CommandSender;
public class PrimeRestartCommand extends ICommand2MC {
private final RestartComponent component;
@Command2.Subcommand
public void def(CommandSender sender, @Command2.TextArg @Command2.OptionalArg String somethingrandom) {
loud = somethingrandom != null;
if (Bukkit.getOnlinePlayers().size() > 0) {

View file

@ -4,8 +4,6 @@ import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel;
import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ComponentMetadata;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.chat.IFakePlayer;
import buttondevteam.lib.chat.TBMCChatAPI;
import lombok.Getter;
@ -15,29 +13,16 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
/**
* Provides commands such as /schrestart (restart after a countdown) and /primerestart (restart when nobody is online).
* Also can automatically restart at a given time.
* Provides commands such as /schrestart (restart after a countdown) and /primerestart (restart when nobody is online)
*/
@ComponentMetadata(enabledByDefault = false)
public class RestartComponent extends Component<MainPlugin> implements Listener {
@Override
public void enable() {
var scheduledRestartCommand = new ScheduledRestartCommand(this);
registerCommand(scheduledRestartCommand);
registerCommand(new ScheduledRestartCommand(this));
registerCommand(new PrimeRestartCommand(this));
registerListener(this);
restartBroadcast = TBMCSystemChatEvent.BroadcastTarget.add("restartCountdown");
int restartAt = this.restartAt.get();
if (restartAt < 0) return;
int restart = syncStart(restartAt);
log("Scheduled restart " + (restart / 3600. / 20.) + " hours from now");
Bukkit.getScheduler().runTaskLater(getPlugin(), () -> scheduledRestartCommand.def(Bukkit.getConsoleSender(), 0), restart);
}
@Override
@ -45,28 +30,10 @@ public class RestartComponent extends Component<MainPlugin> implements Listener
TBMCSystemChatEvent.BroadcastTarget.remove(restartBroadcast);
}
/**
* Specifies the hour of day when the server should be restarted. Set to -1 to disable.
*/
private final ConfigData<Integer> restartAt = getConfig().getData("restartAt", 12);
private long lasttime = 0;
@Getter
private TBMCSystemChatEvent.BroadcastTarget restartBroadcast;
private int syncStart(int hour) {
var now = ZonedDateTime.now(ZoneId.ofOffset("", ZoneOffset.UTC));
int secs = now.getHour() * 3600 + now.getMinute() * 60 + now.getSecond();
int diff = secs - hour * 3600;
if (diff < 0) {
diff += 24 * 3600;
}
int count = diff / (24 * 3600);
int intervalPart = diff - count * 24 * 3600;
int remaining = 24 * 3600 - intervalPart;
return remaining * 20;
}
@EventHandler
public void onPlayerLeave(PlayerQuitEvent event) {
if (PrimeRestartCommand.isPlsrestart()
@ -76,7 +43,7 @@ public class RestartComponent extends Component<MainPlugin> implements Listener
if (PrimeRestartCommand.isLoud())
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online anymore. Restarting.", restartBroadcast);
Bukkit.spigot().restart();
} else if (!(event.getPlayer() instanceof IFakePlayer) && System.nanoTime() - 10 * 60 * 1000000000L - lasttime > 0) { //10 minutes passed since last reminder
} else if (!(event.getPlayer() instanceof IFakePlayer) && System.nanoTime() - 10 * 1000000000L - lasttime > 0) { //Ten seconds passed since last reminder
lasttime = System.nanoTime();
if (PrimeRestartCommand.isLoud())
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", restartBroadcast);

View file

@ -58,7 +58,7 @@ public class ScheduledRestartCommand extends ICommand2MC {
Bukkit.spigot().restart();
}
if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size()>0)
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds!", component.getRestartBroadcast());
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds! (/press)", component.getRestartBroadcast());
restartbar.setProgress(restartCounter / (double) restarttime);
restartbar.setTitle(String.format("Server restart in %.2f", restartCounter / 20f));
restartCounter--;

View file

@ -2,7 +2,6 @@ package buttondevteam.core.component.spawn;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ComponentMetadata;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
@ -24,12 +23,11 @@ import java.math.BigDecimal;
/**
* Provides a /spawn command that works with BungeeCord. Make sure to set up on each server.
*/
@ComponentMetadata(enabledByDefault = false)
public class SpawnComponent extends Component<MainPlugin> implements PluginMessageListener {
@Override
protected void enable() {
registerCommand(new SpawnCommand());
if (targetServer.get().length() == 0) {
if (targetServer().get().length() == 0) {
spawnloc = MultiverseCore.getPlugin(MultiverseCore.class).getMVWorldManager().getFirstSpawnWorld()
.getSpawnLocation();
}
@ -49,13 +47,14 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
if (!channel.equals("BungeeCord")) {
return;
}
if (targetServer.get().length() != 0)
if (targetServer().get().length() != 0)
return;
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
if ("ChromaCore-Spawn".equals(subchannel)) {
// Use the code sample in the 'Response' sections below to read
// the data.
System.out.println("Heh nice");
short len = in.readShort();
byte[] msgbytes = new byte[len];
in.readFully(msgbytes);
@ -78,7 +77,9 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
/**
* The BungeeCord server that has the spawn. Set to empty if this server is the target.
*/
private final ConfigData<String> targetServer = getConfig().getData("targetServer", "");
private ConfigData<String> targetServer() {
return getConfig().getData("targetServer", "");
}
private Location spawnloc;
@ -90,7 +91,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
@SuppressWarnings("UnstableApiUsage")
@Command2.Subcommand
public void def(Player player) {
if (targetServer.get().length() == 0) {
if (targetServer().get().length() == 0) {
player.sendMessage("§bTeleporting to spawn...");
try {
if (MainPlugin.ess != null)
@ -105,7 +106,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
}
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(targetServer.get());
out.writeUTF(targetServer().get());
player.sendPluginMessage(getPlugin(), "BungeeCord", out.toByteArray());

View file

@ -1,61 +0,0 @@
package buttondevteam.core.component.towny;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.CustomTabComplete;
import buttondevteam.lib.chat.ICommand2MC;
import buttondevteam.lib.player.TBMCPlayer;
import com.earth2me.essentials.Essentials;
import com.earth2me.essentials.User;
import com.palmergames.bukkit.towny.TownySettings;
import com.palmergames.bukkit.towny.TownyUniverse;
import com.palmergames.bukkit.towny.object.Resident;
import com.palmergames.bukkit.towny.object.TownyObject;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import java.util.AbstractMap;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@CommandClass(path = "chroma remresidents", modOnly = true, helpText = {
"Removes invalid Towny residents from their towns (usually after a rename that didn't get caught by the plugin)",
"If the delete eco account setting is off, then it will completely delete the resident",
"(The economy account could still be used by the player)"
})
public class RemoveResidentsCommand extends ICommand2MC {
@Command2.Subcommand
public void def(CommandSender sender, @Command2.OptionalArg @CustomTabComplete("remove") String remove) {
Bukkit.getScheduler().runTaskAsynchronously(getPlugin(), () -> {
sender.sendMessage("Starting...");
var ds = TownyUniverse.getInstance().getDataSource();
var res = ds.getResidents().stream()
.flatMap(r -> {
var st = Stream.of(r) //https://stackoverflow.com/questions/37299312/in-java-8-lambdas-how-to-access-original-object-in-the-stream
.map(TownyObject::getName);
return (MainPlugin.ess == null
? st.map(Bukkit::getOfflinePlayer)
: st.map(MainPlugin.ess::getOfflineUser).map(User::getBase))
.filter(p -> !p.hasPlayedBefore())
.map(p -> new AbstractMap.SimpleEntry<>(r, p));
}).collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue));
if (MainPlugin.ess == null)
sender.sendMessage("§cEssentials not found, players who haven't joined after changing their names are also listed here.");
sender.sendMessage("Residents to remove:");
res.values().forEach(op -> sender.sendMessage(op.getName()));
if (TownySettings.isDeleteEcoAccount())
sender.sendMessage("§bWill only remove from town, as delete eco account setting is on");
else
sender.sendMessage("§eWill completely delete the resident, delete eco account setting is off");
if (remove != null && remove.equalsIgnoreCase("remove")) {
sender.sendMessage("Removing residents..."); //Removes from town and deletes town if needed - doesn't delete the resident if the setting is on
//If it did, that could mean the player's economy is deleted too, unless this setting is false
res.keySet().forEach(TownySettings.isDeleteEcoAccount() ? ds::removeResident : ds::removeResidentList);
sender.sendMessage("Done");
}
});
}
}

View file

@ -1,18 +1,54 @@
package buttondevteam.core.component.towny;
import buttondevteam.core.ComponentManager;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.Component;
import com.palmergames.bukkit.towny.Towny;
import com.palmergames.bukkit.towny.TownyUniverse;
import com.palmergames.bukkit.towny.exceptions.AlreadyRegisteredException;
import com.palmergames.bukkit.towny.exceptions.NotRegisteredException;
import com.palmergames.bukkit.towny.object.Resident;
import org.bukkit.Bukkit;
/**
* Provides a command to remove invalid Towny residents.
* Automatically renames Towny players if they changed their Minecraft name
*/
public class TownyComponent extends Component<MainPlugin> {
@Override
protected void enable() {
registerCommand(new RemoveResidentsCommand());
}
@Override
protected void disable() {
}
/**
* Only renames the resident if this component is enabled. Used to handle name changes.
*
* @param oldName The player's old name as known by us
* @param newName The player's new name
*/
public static void renameInTowny(String oldName, String newName) {
if (!ComponentManager.isEnabled(TownyComponent.class))
return;
Bukkit.getLogger().info("Renaming" + oldName + " in Towny to " + newName);
TownyUniverse tu = Towny.getPlugin(Towny.class).getTownyUniverse();
Resident resident = tu.getResidentMap().get(oldName.toLowerCase()); //The map keys are lowercase
if (resident == null) {
Bukkit.getLogger().warning("Resident not found - couldn't rename in Towny.");
TBMCCoreAPI.sendDebugMessage("Resident not found - couldn't rename in Towny.");
} else if (tu.getResidentMap().contains(newName.toLowerCase())) {
Bukkit.getLogger().warning("Target resident name is already in use."); // TODO: Handle
TBMCCoreAPI.sendDebugMessage("Target resident name is already in use.");
} else
try {
tu.getDataSource().renamePlayer(resident, newName); //Fixed in Towny 0.91.1.2
Bukkit.getLogger().info("Renaming done.");
} catch (AlreadyRegisteredException e) {
TBMCCoreAPI.SendException("Failed to rename resident, there's already one with this name.", e);
} catch (NotRegisteredException e) {
TBMCCoreAPI.SendException("Failed to rename resident, the resident isn't registered.", e);
}
}
}

View file

@ -0,0 +1,193 @@
package buttondevteam.core.component.updater;
import buttondevteam.lib.TBMCCoreAPI;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class PluginUpdater {
private PluginUpdater() {
}
public static final File updatedir = new File("TBMC", "pluginupdates");
/**
* See {@link TBMCCoreAPI#UpdatePlugin(String, CommandSender, String)}
*/
public static boolean UpdatePlugin(String name, CommandSender sender, String branch) {
if (!updatedir.exists() && !updatedir.mkdirs()) {
error(sender, "Failed to create update directory!");
return false;
}
info(sender, "Checking plugin name...");
List<String> plugins = GetPluginNames();
String correctname = null;
for (String plugin : plugins) {
if (plugin.equalsIgnoreCase(name)) {
correctname = plugin; // Fixes capitalization
break;
}
}
if (correctname == null) {
error(sender, "Can't find plugin: " + name);
return false;
}
info(sender, "Checking branch name...");
if (!TBMCCoreAPI.IsTestServer() && !branch.equalsIgnoreCase("master")) {
error(sender, "The server is in production mode, updating only allowed from master!");
return false;
}
Optional<String> correctbranch = GetPluginBranches(correctname).stream().filter(b -> b.equalsIgnoreCase(branch))
.findAny();
if (!correctbranch.isPresent()) {
error(sender, "Can't find branch \"" + branch + "\" for plugin \"" + correctname + "\"");
return false;
}
if (isNotMaven(correctname, correctbranch.get())) {
error(sender, "The plugin doesn't appear to have a pom.xml. Make sure it's a Maven project.");
return false;
}
info(sender, "Updating TBMC plugin: " + correctname + " from " + correctbranch.get());
return updatePluginJitPack(sender, correctname, correctbranch.get());
}
private static boolean updatePluginJitPack(CommandSender sender, String correctname,
String correctbranch) {
/*URL url;
File result = new File(updatedir, correctname + ".jar");
try {
url = new URL("https://jitpack.io/com/github/TBMCPlugins/"
+ (correctname.equals("ButtonCore") ? "ButtonCore/ButtonCore" : correctname) + "/"
+ correctbranch + "-SNAPSHOT/" + correctname + "-" + correctbranch + "-SNAPSHOT.jar"); // ButtonCore exception required since it hosts Towny as well
FileUtils.copyURLToFile(url, result);
if (!result.exists() || result.length() < 25) {
result.delete();
error(sender, "The downloaded JAR for " + correctname + " from " + correctbranch
+ " is too small (smnaller than 25 bytes). Am I downloading from the right place?");
return false;
} else {
info(sender, "Updating plugin " + correctname + " from " + correctbranch + " done!");
return true;
}
} catch (FileNotFoundException e) {
error(sender,
"Can't find JAR for " + correctname + " from " + correctbranch
+ ", the build probably failed. Build log (scroll to bottom):" + "\n"
+ "https://jitpack.io/com/github/TBMCPlugins/" + correctname + "/" + correctbranch
+ "-SNAPSHOT/build.log\nIf you'd like to rebuild the same commit, go to:\nhttps://jitpack.io/#TBMCPlugins/"
+ correctname + "\nAnd delete the newest build.");
} catch (IOException e) {
error(sender, "IO error while updating " + correctname + "\n" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
error(sender, "Unknown error while updating " + correctname + ": " + e); - TODO: Either add Commons or don't use FileUtils
}*/
info(sender, "Plugin updating is currently not supported");
return false;
}
/**
* Checks if pom.xml is not present for the project.
*
* @param pluginname
* Does not have to match case
* @param branch
* Does not have to match case
*/
public static boolean isNotMaven(String pluginname, String branch) {
try {
return TBMCCoreAPI
.DownloadString(
"https://raw.githubusercontent.com/TBMCPlugins/" + pluginname + "/" + branch + "/pom.xml")
.equals("404: Not Found\n");
} catch (IOException e1) {
return true;
}
}
private static void error(CommandSender sender, String message) {
if (!sender.equals(Bukkit.getConsoleSender()))
Bukkit.getLogger().warning(message);
sender.sendMessage("§c" + message);
}
private static void info(CommandSender sender, String message) {
if (!sender.equals(Bukkit.getConsoleSender()))
Bukkit.getLogger().info(message);
sender.sendMessage("§b" + message);
}
/**
* Retrieves all the repository names from the GitHub organization.
*
* @return A list of names
*/
public static List<String> GetPluginNames() {
List<String> ret = new ArrayList<>();
try {
String resp = TBMCCoreAPI.DownloadString("https://api.github.com/orgs/" + "TBMCPlugins" + "/repos"); //TODO: PluginUpdater
JsonArray arr = new JsonParser().parse(resp).getAsJsonArray();
for (JsonElement obj : arr) {
JsonObject jobj = obj.getAsJsonObject();
ret.add(jobj.get("name").getAsString());
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
/**
* Retrieves all the branches from the plugin repository.
*
* @return A list of names
*/
public static List<String> GetPluginBranches(String plugin) {
List<String> ret = new ArrayList<>();
try {
String resp = TBMCCoreAPI
.DownloadString("https://api.github.com/repos/TBMCPlugins/" + plugin + "/branches");
JsonArray arr = new JsonParser().parse(resp).getAsJsonArray();
for (JsonElement obj : arr) {
JsonObject jobj = obj.getAsJsonObject();
ret.add(jobj.get("name").getAsString());
}
} catch (Exception e) {
e.printStackTrace();
}
return ret;
}
public static class UpdatedEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final JsonObject data;
public UpdatedEvent(JsonObject data) {
this.data = data;
}
public JsonObject getData() {
return data;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}
}

View file

@ -0,0 +1,21 @@
package buttondevteam.core.component.updater;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ComponentMetadata;
/**
* Downloads plugin updates built from their source using JitPack - doesn't work anymore
*/
@ComponentMetadata(enabledByDefault = false)
public class PluginUpdaterComponent extends Component<MainPlugin> { //TODO: Config
@Override
public void enable() {
registerCommand(new UpdatePluginCommand());
}
@Override
public void disable() { //Commands are automatically unregistered
}
}

View file

@ -0,0 +1,42 @@
package buttondevteam.core.component.updater;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import java.lang.reflect.Method;
@CommandClass(modOnly = true)
public class UpdatePluginCommand extends ICommand2MC {
public void def(CommandSender sender, @Command2.OptionalArg String plugin, @Command2.OptionalArg String branch) {
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
if (plugin == null) {
sender.sendMessage("Downloading plugin names...");
boolean first = true;
for (String plugin2 : PluginUpdater.GetPluginNames()) {
if (first) {
sender.sendMessage("§6---- Plugin names ----");
first = false;
}
sender.sendMessage("- " + plugin2);
}
} else {
TBMCCoreAPI.UpdatePlugin(plugin, sender, branch == null ? "master" : branch);
}
});
}
@Override
public String[] getHelpText(Method method, Command2.Subcommand ann) {
return new String[]{ //
"§6---- Update plugin ----", //
"This command downloads the latest version of a custom plugin from GitHub", //
"To update a plugin: add its name", //
"To list the plugin names: don't type a name" //
};
}
}

View file

@ -0,0 +1,51 @@
package buttondevteam.core.component.votifier;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ComponentMetadata;
import buttondevteam.lib.architecture.ConfigData;
import com.vexsoftware.votifier.model.Vote;
import com.vexsoftware.votifier.model.VotifierEvent;
import lombok.RequiredArgsConstructor;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
/**
* Do not use (EULA)
*/
@RequiredArgsConstructor
@ComponentMetadata(enabledByDefault = false)
public class VotifierComponent extends Component<MainPlugin> {
private final Economy economy;
private ConfigData<Double> rewardAmount() {
return getConfig().getData("rewardAmount", 0.0);
}
@Override
protected void enable() {
}
@Override
protected void disable() {
}
@EventHandler
@SuppressWarnings("deprecation")
public void onVotifierEvent(VotifierEvent event) {
Vote vote = event.getVote();
getPlugin().getLogger().info("Vote: " + vote);
org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(vote.getUsername());
Player p = Bukkit.getPlayer(vote.getUsername());
/*if (op != null) {
economy.depositPlayer(op, rewardAmount().get());
}
if (p != null) {
p.sendMessage("§bThanks for voting! $50 was added to your account.");
}*/
}
}

View file

@ -37,7 +37,7 @@ public final class ChromaUtils {
/**
* May return null.
*
* @return The full name that can be used to uniquely identify the user.
* @return The full name that can be used to uniquely indentify the user.
*/
String getFancyFullName();
}
@ -77,7 +77,7 @@ public final class ChromaUtils {
*
* @param what What to do
* @param def Default if async
* @return The value supplied by the action or def if async.
* @return The event cancelled state or false if async.
*/
public static <T> T doItAsync(Supplier<T> what, T def) {
if (Bukkit.isPrimaryThread())
@ -86,16 +86,4 @@ public final class ChromaUtils {
return what.get();
return def;
}
private static boolean test = false;
/**
* Returns true while unit testing.
*/
public static boolean isTest() { return test; }
/**
* Call when unit testing.
*/
public static void setTest() { test = true; }
}

View file

@ -1,13 +1,12 @@
package buttondevteam.lib;
import org.bukkit.Bukkit;
import org.bukkit.event.Event;
class EventExceptionCoreHandler extends EventExceptionHandler {
@Override
public boolean handle(Throwable ex, Event event) {
TBMCCoreAPI.SendException("An error occured while executing " + event.getEventName() + "!", ex, false, Bukkit.getLogger()::warning);
TBMCCoreAPI.SendException("An error occured while executing " + event.getEventName() + "!", ex);
return true;
}

View file

@ -30,6 +30,7 @@ public class TBMCChatEvent extends TBMCChatEventBase {
private boolean isIgnoreSenderPermissions() {
return cm.getPermCheck() != cm.getSender();
}
// TODO: Message object with data?
/**
* This will allow the sender of the message if {@link #isIgnoreSenderPermissions()} is true.

View file

@ -54,6 +54,6 @@ public abstract class TBMCChatEventBase extends Event implements Cancellable {
*/
@Nullable
public String getGroupID(CommandSender sender) {
return channel.getGroupID(sender);
return channel.getGroupID(sender); //TODO: Performance-wise it'd be much better to use serialization for player data - it's only converted once
}
}

View file

@ -30,9 +30,13 @@ public class TBMCChatPreprocessEvent extends Event implements Cancellable {
super(true);
this.sender = sender;
this.channel = channel;
this.message = message;
this.message = message; // TODO: Message object with data?
}
/*
* public TBMCPlayer getPlayer() { return TBMCPlayer.getPlayer(sender); // TODO: Get Chroma user }
*/
@Override
public HandlerList getHandlers() {
return handlers;

View file

@ -1,8 +1,6 @@
package buttondevteam.lib;
import buttondevteam.core.component.channel.Channel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.bukkit.command.CommandSender;
import org.bukkit.event.Cancellable;
@ -16,18 +14,20 @@ import org.bukkit.event.HandlerList;
* @author NorbiPeti
*/
@Getter
@RequiredArgsConstructor
public class TBMCCommandPreprocessEvent extends Event implements Cancellable {
private static final HandlerList handlers = new HandlerList();
private final CommandSender sender;
private final Channel channel;
@Setter
private final String message;
private final CommandSender permCheck;
private String message;
@Setter
private boolean cancelled;
public TBMCCommandPreprocessEvent(CommandSender sender, String message) {
this.sender = sender;
this.message = message;
}
@Override
public HandlerList getHandlers() {
return handlers;

View file

@ -1,14 +1,14 @@
package buttondevteam.lib;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.core.component.updater.PluginUpdater;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.potato.DebugPotato;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.io.InputStream;
@ -16,8 +16,6 @@ import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class TBMCCoreAPI {
static final List<String> coders = new ArrayList<String>() {
@ -32,6 +30,28 @@ public class TBMCCoreAPI {
}
};
/**
* Updates or installs the specified plugin. The plugin must use Maven.
*
* @param name The plugin's repository name.
* @param sender The command sender (if not console, messages will be printed to console as well).
*/
public static void UpdatePlugin(String name, CommandSender sender) {
UpdatePlugin(name, sender, "master");
}
/**
* Updates or installs the specified plugin from the specified branch. The plugin must use Maven.
*
* @param name The plugin's repository name.
* @param sender The command sender (if not console, messages will be printed to console as well).
* @param branch The branch to download the plugin from.
* @return Success or not
*/
public static boolean UpdatePlugin(String name, CommandSender sender, String branch) {
return PluginUpdater.UpdatePlugin(name, sender, branch);
}
public static String DownloadString(String urlstr) throws IOException {
URL url = new URL(urlstr);
URLConnection con = url.openConnection();
@ -54,21 +74,11 @@ public class TBMCCoreAPI {
* @param sourcemsg A message that is shown at the top of the exception (before the exception's message)
* @param e The exception to send
*/
public static void SendException(String sourcemsg, Throwable e, Component<?> component) {
SendException(sourcemsg, e, false, component::logWarn);
public static void SendException(String sourcemsg, Throwable e) {
SendException(sourcemsg, e, false);
}
/**
* Send exception to the {@link TBMCExceptionEvent}.
*
* @param sourcemsg A message that is shown at the top of the exception (before the exception's message)
* @param e The exception to send
*/
public static void SendException(String sourcemsg, Throwable e, JavaPlugin plugin) {
SendException(sourcemsg, e, false, plugin.getLogger()::warning);
}
public static void SendException(String sourcemsg, Throwable e, boolean debugPotato, Consumer<String> logWarn) {
public static void SendException(String sourcemsg, Throwable e, boolean debugPotato) {
try {
SendUnsentExceptions();
TBMCExceptionEvent event = new TBMCExceptionEvent(sourcemsg, e);
@ -77,7 +87,7 @@ public class TBMCCoreAPI {
if (!event.isHandled())
exceptionsToSend.put(sourcemsg, e);
}
logWarn.accept(sourcemsg);
Bukkit.getLogger().warning(sourcemsg);
e.printStackTrace();
if (debugPotato) {
List<Player> devsOnline = new ArrayList<>();
@ -118,7 +128,6 @@ public class TBMCCoreAPI {
}
private static EventExceptionCoreHandler eventExceptionCoreHandler;
/**
* Registers Bukkit events, handling the exceptions occurring in those events
*
@ -130,8 +139,8 @@ public class TBMCCoreAPI {
EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler);
}
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass, Supplier<T> constructor) {
ChromaGamerBase.RegisterPluginUserClass(userclass, constructor);
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass) {
ChromaGamerBase.RegisterPluginUserClass(userclass);
}
/**
@ -171,6 +180,6 @@ public class TBMCCoreAPI {
public static boolean IsTestServer() {
if (MainPlugin.Instance == null) return true;
return MainPlugin.Instance.test.get();
return MainPlugin.Instance.test().get();
}
}

View file

@ -25,9 +25,9 @@ import java.util.Stack;
@HasConfig(global = true)
public abstract class ButtonPlugin extends JavaPlugin {
@Getter //Needs to be static as we don't know the plugin when a command is handled
private static final Command2MC command2MC = new Command2MC();
private static Command2MC command2MC = new Command2MC();
@Getter(AccessLevel.PROTECTED)
private final IHaveConfig iConfig = new IHaveConfig(this::saveConfig);
private IHaveConfig iConfig;
private CommentedConfiguration yaml;
@Getter(AccessLevel.PROTECTED)
private IHaveConfig data; //TODO
@ -35,7 +35,8 @@ public abstract class ButtonPlugin extends JavaPlugin {
* Used to unregister components in the right order - and to reload configs
*/
@Getter
private final Stack<Component<?>> componentStack = new Stack<>();
private Stack<Component<?>> componentStack = new Stack<>();
;
protected abstract void pluginEnable();
@ -59,7 +60,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
try {
pluginEnable();
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while enabling plugin " + getName() + "!", e, this);
TBMCCoreAPI.SendException("Error while enabling plugin " + getName() + "!", e);
}
if (configGenAllowed(this)) //If it's not disabled (by default it's not)
IHaveConfig.pregenConfig(this, null);
@ -71,7 +72,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
return false;
var section = config.getConfigurationSection("global");
if (section == null) section = config.createSection("global");
iConfig.reset(section);
iConfig = new IHaveConfig(section, this::saveConfig);
return true;
}
@ -83,9 +84,10 @@ public abstract class ButtonPlugin extends JavaPlugin {
pluginDisable();
if (ConfigData.saveNow(getConfig()))
getLogger().info("Saved configuration changes.");
iConfig = null; //Clearing the hashmap is not enough, we need to update the section as well
getCommand2MC().unregisterCommands(this);
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e, this);
TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e);
}
}
@ -124,8 +126,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
var yc = YamlConfiguration.loadConfiguration(res);
for (var kv : yc.getValues(true).entrySet())
if (kv.getValue() instanceof String)
yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""),
Arrays.stream(((String) kv.getValue()).split("\n"))
yaml.addComment(kv.getKey(), Arrays.stream(((String) kv.getValue()).split("\n"))
.map(str -> "# " + str.trim()).toArray(String[]::new));
return true;
}
@ -133,8 +134,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
@Override
public FileConfiguration getConfig() {
if (yaml == null)
justReload();
if (yaml == null) return new YamlConfiguration(); //Return a temporary instance
justReload(); //TODO: If it fails to load, it'll probably throw an NPE
return yaml;
}
@ -144,7 +144,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
if (yaml != null)
yaml.save();
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to save config", e, this);
TBMCCoreAPI.SendException("Failed to save config", e);
}
}

View file

@ -10,7 +10,6 @@ import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
@ -20,7 +19,7 @@ import java.util.HashMap;
*
* @author dumptruckman &amp; Articdive
*/
public class CommentedConfiguration extends YamlConfiguration {
public class CommentedConfiguration extends YamlConfiguration { //TODO: Remove FileMgmt dependency
private HashMap<String, String> comments;
private File file;
@ -175,7 +174,7 @@ public class CommentedConfiguration extends YamlConfiguration {
while (newContents.toString().startsWith(" " + System.getProperty("line.separator"))) {
newContents = new StringBuilder(newContents.toString().replaceFirst(" " + System.getProperty("line.separator"), ""));
}
Files.write(file.toPath(), newContents.toString().getBytes(StandardCharsets.UTF_8));
Files.write(file.toPath(), newContents.toString().getBytes());
}
}

View file

@ -31,11 +31,14 @@ public abstract class Component<TP extends JavaPlugin> {
@Getter
@NonNull
private TP plugin;
private @Getter final IHaveConfig config = new IHaveConfig(null);
@NonNull
private @Getter
IHaveConfig config;
private @Getter IHaveConfig data; //TODO
public final ConfigData<Boolean> shouldBeEnabled = config.getData("enabled",
Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true));
public final ConfigData<Boolean> shouldBeEnabled() {
return config.getData("enabled", Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true));
}
/**
* Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.<br>
@ -58,7 +61,7 @@ public abstract class Component<TP extends JavaPlugin> {
* @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) {
public static <T extends ButtonPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
return registerUnregisterComponent(plugin, component, false);
}
@ -76,22 +79,21 @@ public abstract class Component<TP extends JavaPlugin> {
}
if (register) {
if (components.containsKey(component.getClass())) {
TBMCCoreAPI.SendException("Failed to register component " + component.getClassName(), new IllegalArgumentException("The component is already registered!"), plugin);
TBMCCoreAPI.SendException("Failed to register component " + component.getClassName(), new IllegalArgumentException("The component is already registered!"));
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()) {
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);
TBMCCoreAPI.SendException("Failed to enable component " + component.getClassName() + "!", e);
return true;
}
}
@ -102,7 +104,7 @@ public abstract class Component<TP extends JavaPlugin> {
try {
setComponentEnabled(component, false);
} catch (Exception | NoClassDefFoundError e) {
TBMCCoreAPI.SendException("Failed to disable component " + component.getClassName() + "!", e, component);
TBMCCoreAPI.SendException("Failed to disable component " + component.getClassName() + "!", e);
return false; //If failed to disable, won't unregister either
}
}
@ -111,56 +113,47 @@ public abstract class Component<TP extends JavaPlugin> {
}
return true;
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + "!", e, plugin);
TBMCCoreAPI.SendException("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + "!", e);
return false;
}
}
/**
* Enables or disables the given component. If the component fails to enable, it will be disabled.
* Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.<br>
* Make sure to register the dependencies first.
*
* @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 {
//System.out.println("Updating config for "+component.getClassName());
updateConfig(component.getPlugin(), component);
//System.out.println("Enabling "+component.getClassName());
component.enable();
if (ButtonPlugin.configGenAllowed(component)) {
//System.out.println("Pregenning config for "+component.getClassName());
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;
}
}
//System.out.println("Done enabling "+component.getClassName());
} else {
component.disable();
ButtonPlugin.getCommand2MC().unregisterCommands(component);
}
}
public static void updateConfig(JavaPlugin plugin, Component<?> component) {
public static void updateConfig(JavaPlugin plugin, Component component) {
if (plugin.getConfig() != null) { //Production
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
component.config = new IHaveConfig(configSect, plugin::saveConfig);
} else //Testing
component.config = new IHaveConfig(null, plugin::saveConfig);
}
/**
@ -173,21 +166,13 @@ public abstract class Component<TP extends JavaPlugin> {
return Collections.unmodifiableMap(components);
}
public void log(String message) {
plugin.getLogger().info("[" + getClassName() + "] " + message);
}
public void logWarn(String message) {
plugin.getLogger().warning("[" + getClassName() + "] " + message);
}
/**
* Registers the module, when called by the JavaPlugin class.
* This gets fired when the plugin is enabled. Use {@link #enable()} to register commands and such.
*
* @param plugin Plugin object
*/
@SuppressWarnings({"unused"})
@SuppressWarnings({"unused", "WeakerAccess"})
protected void register(JavaPlugin plugin) {
}
@ -198,7 +183,7 @@ public abstract class Component<TP extends JavaPlugin> {
*
* @param plugin Plugin object
*/
@SuppressWarnings({"unused"})
@SuppressWarnings({"WeakerAccess", "unused"})
protected void unregister(JavaPlugin plugin) {
}
@ -252,15 +237,10 @@ public abstract class Component<TP extends JavaPlugin> {
var cs = c.getConfigurationSection(key);
if (cs == null) cs = c.createSection(key);
val res = cs.getValues(false).entrySet().stream().filter(e -> e.getValue() instanceof ConfigurationSection)
.collect(Collectors.toMap(Map.Entry::getKey, kv -> {
var conf = new IHaveConfig(getPlugin()::saveConfig);
conf.reset((ConfigurationSection) kv.getValue());
return conf;
}));
.collect(Collectors.toMap(Map.Entry::getKey, kv -> new IHaveConfig((ConfigurationSection) kv.getValue(), getPlugin()::saveConfig)));
if (res.size() == 0) {
for (val entry : defaultProvider.entrySet()) {
val conf = new IHaveConfig(getPlugin()::saveConfig);
conf.reset(cs.createSection(entry.getKey()));
val conf = new IHaveConfig(cs.createSection(entry.getKey()), getPlugin()::saveConfig);
entry.getValue().accept(conf);
res.put(entry.getKey(), conf);
}

View file

@ -2,9 +2,13 @@ package buttondevteam.lib.architecture;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.ChromaUtils;
import lombok.*;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Bukkit;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitTask;
@ -18,67 +22,64 @@ import java.util.function.Function;
* Use the getter/setter constructor if {@link T} isn't a primitive type or String.<br>
* Use {@link Component#getConfig()} or {@link ButtonPlugin#getIConfig()} then {@link IHaveConfig#getData(String, Object)} to get an instance.
*/
//@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class ConfigData<T> {
private static final HashMap<Configuration, SaveTask> saveTasks = new HashMap<>();
/**
* May be null for testing
*/
private IHaveConfig config;
private final ConfigurationSection config;
@Getter
@Setter(AccessLevel.PACKAGE)
private String path;
protected final T def;
private final Object primitiveDef;
private final Runnable saveAction;
/**
* The parameter is of a primitive type as returned by {@link YamlConfiguration#get(String)}
*/
private final Function<Object, T> getter;
private Function<Object, T> getter;
/**
* The result should be a primitive type or string that can be retrieved correctly later
*/
private final Function<T, Object> setter;
private Function<T, Object> setter;
/**
* The config value should not change outside this instance
*/
private T value;
ConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
if (def == null) {
if (primitiveDef == null)
throw new IllegalArgumentException("Either def or primitiveDef must be set.");
if (getter == null)
throw new IllegalArgumentException("A getter and setter must be present when using primitiveDef.");
def = getter.apply(primitiveDef);
} else if (primitiveDef == null)
if (setter == null)
primitiveDef = def;
else
primitiveDef = setter.apply(def);
if ((getter == null) != (setter == null))
throw new IllegalArgumentException("Both setters and getters must be present (or none if def is primitive).");
//This constructor is needed because it sets the getter and setter
ConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter, Runnable saveAction) {
this.config = config;
this.path = path;
this.def = def;
this.primitiveDef = primitiveDef;
this.getter = getter;
this.setter = setter;
get(); //Generate config automatically
this.saveAction = saveAction;
}
@java.beans.ConstructorProperties({"config", "path", "def", "primitiveDef", "saveAction"})
ConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Runnable saveAction) {
this.config = config;
this.path = path;
this.def = def;
this.primitiveDef = primitiveDef;
this.saveAction = saveAction;
}
@Override
public String toString() {
return "ConfigData{" + "path='" + path + '\'' + ", value=" + value + '}';
}
void reset() {
value = null;
return "ConfigData{" +
"path='" + path + '\'' +
", value=" + value +
'}';
}
@SuppressWarnings("unchecked")
public T get() {
if (value != null) return value; //Speed things up
var config = this.config.getConfig();
Object val;
if (config == null || !config.isSet(path)) { //Call set() if config == null
val = primitiveDef;
@ -118,27 +119,21 @@ public class ConfigData<T> {
if (setter != null && value != null)
val = setter.apply(value);
else val = value;
if (config.getConfig() != null)
if (config != null)
setInternal(val);
this.value = value;
}
private void setInternal(Object val) {
config.getConfig().set(path, val);
signalChange(config);
}
static void signalChange(IHaveConfig config) {
var cc = config.getConfig();
var sa = config.getSaveAction();
if (!saveTasks.containsKey(cc.getRoot())) {
config.set(path, val);
if (!saveTasks.containsKey(config.getRoot())) {
synchronized (saveTasks) {
saveTasks.put(cc.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> {
saveTasks.put(config.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> {
synchronized (saveTasks) {
saveTasks.remove(cc.getRoot());
sa.run();
saveTasks.remove(config.getRoot());
saveAction.run();
}
}, 100), sa));
}, 100), saveAction));
}
}
}
@ -161,92 +156,4 @@ public class ConfigData<T> {
}
return false;
}
public static <T> ConfigData.ConfigDataBuilder<T> builder(IHaveConfig config, String path) {
return new ConfigDataBuilder<T>(config, path);
}
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
public static class ConfigDataBuilder<T> {
private final IHaveConfig config;
private final String path;
private T def;
private Object primitiveDef;
private Function<Object, T> getter;
private Function<T, Object> setter;
/**
* The default value to use, as used in code. If not a primitive type, use the {@link #getter(Function)} and {@link #setter(Function)} methods.
* <br/>
* To set the value as it is stored, use {@link #primitiveDef(Object)}.
*
* @param def The default value
* @return This builder
*/
public ConfigDataBuilder<T> def(T def) {
this.def = def;
return this;
}
/**
* The default value to use, as stored in yaml. Must be a primitive type. Make sure to use the {@link #getter(Function)} and {@link #setter(Function)} methods.
* <br/>
* To set the value as used in the code, use {@link #def(Object)}.
*
* @param primitiveDef The default value
* @return This builder
*/
public ConfigDataBuilder<T> primitiveDef(Object primitiveDef) {
this.primitiveDef = primitiveDef;
return this;
}
/**
* A function to use to obtain the runtime object from the yaml representation (usually string).
* The {@link #setter(Function)} must also be set.
*
* @param getter A function that receives the primitive type and returns the runtime type
* @return This builder
*/
public ConfigDataBuilder<T> getter(Function<Object, T> getter) {
this.getter = getter;
return this;
}
/**
* A function to use to obtain the yaml representation (usually string) from the runtime object.
* The {@link #getter(Function)} must also be set.
*
* @param setter A function that receives the runtime type and returns the primitive type
* @return This builder
*/
public ConfigDataBuilder<T> setter(Function<T, Object> setter) {
this.setter = setter;
return this;
}
/**
* Builds a modifiable config representation. Use if you want to change the value <i>in code</i>.
*
* @return A ConfigData instance.
*/
public ConfigData<T> build() {
ConfigData<T> config = new ConfigData<>(this.config, path, def, primitiveDef, getter, setter);
this.config.onConfigBuild(config);
return config;
}
/**
* Builds a read-only config representation. Use if you only want the value to be changed <i>in the config</i>.
*
* @return A ReadOnlyConfigData instance.
*/
public ReadOnlyConfigData<T> buildReadOnly() {
ReadOnlyConfigData<T> config = new ReadOnlyConfigData<>(this.config, path, def, primitiveDef, getter, setter);
this.config.onConfigBuild(config);
return config;
}
public String toString() {return "ConfigData.ConfigDataBuilder(config=" + this.config + ", path=" + this.path + ", def=" + this.def + ", primitiveDef=" + this.primitiveDef + ", getter=" + this.getter + ", setter=" + this.setter + ")";}
}
}

View file

@ -3,11 +3,8 @@ package buttondevteam.lib.architecture;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.java.JavaPlugin;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
@ -21,41 +18,22 @@ import java.util.stream.Collectors;
*/
public final class IHaveConfig {
private final HashMap<String, ConfigData<?>> datamap = new HashMap<>();
/**
* Returns the Bukkit ConfigurationSection. Use {@link #signalChange()} after changing it.
*/
@Getter
private ConfigurationSection config;
@Getter
@Setter
private Runnable saveAction;
private final Runnable saveAction;
/**
* May be used in testing.
*
* @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null.
* @param section May be null for testing
*/
public IHaveConfig(Runnable saveAction) {
IHaveConfig(ConfigurationSection section, Runnable saveAction) {
config = section;
this.saveAction = saveAction;
}
/**
* Gets a config object for the given path. The def or primitiveDef must be set. If a getter is present, a setter must be present as well.
*
* @param path The dot-separated path relative to this config instance
* @param <T> The runtime type of the config value
* @return A ConfigData builder to set how to obtain the value
*/
public <T> ConfigData.ConfigDataBuilder<T> getConfig(String path) {
return ConfigData.builder(this, path);
}
void onConfigBuild(ConfigData<?> config) {
datamap.put(config.getPath(), config);
}
/**
* This method overload should only be used with primitives or String.
* This method overload should only be used with primitves or String.
*
* @param path The path in config to use
* @param def The value to use by default
@ -65,7 +43,7 @@ public final class IHaveConfig {
@SuppressWarnings("unchecked")
public <T> ConfigData<T> getData(String path, T def) {
ConfigData<?> data = datamap.get(path);
if (data == null) datamap.put(path, data = new ConfigData<>(this, path, def, def, null, null));
if (data == null) datamap.put(path, data = new ConfigData<>(config, path, def, def, saveAction));
return (ConfigData<T>) data;
}
@ -83,7 +61,7 @@ public final class IHaveConfig {
public <T> ConfigData<T> getData(String path, T def, Function<Object, T> getter, Function<T, Object> setter) {
ConfigData<?> data = datamap.get(path);
if (data == null)
datamap.put(path, data = new ConfigData<>(this, path, def, setter.apply(def), getter, setter));
datamap.put(path, data = new ConfigData<>(config, path, def, setter.apply(def), getter, setter, saveAction));
return (ConfigData<T>) data;
}
@ -101,7 +79,7 @@ public final class IHaveConfig {
public <T> ConfigData<T> getDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
ConfigData<?> data = datamap.get(path);
if (data == null)
datamap.put(path, data = new ConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
datamap.put(path, data = new ConfigData<>(config, path, getter.apply(primitiveDef), primitiveDef, getter, setter, saveAction));
return (ConfigData<T>) data;
}
@ -119,7 +97,7 @@ public final class IHaveConfig {
public <T> ReadOnlyConfigData<T> getReadOnlyDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
ConfigData<?> data = datamap.get(path);
if (data == null)
datamap.put(path, data = new ReadOnlyConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
datamap.put(path, data = new ReadOnlyConfigData<>(config, path, getter.apply(primitiveDef), primitiveDef, getter, setter, saveAction));
return (ReadOnlyConfigData<T>) data;
}
@ -136,7 +114,7 @@ public final class IHaveConfig {
ConfigData<?> data = datamap.get(path);
if (data == null) {
val defval = def.get();
datamap.put(path, data = new ConfigData<>(this, path, defval, defval, null, null));
datamap.put(path, data = new ConfigData<>(config, path, defval, defval, saveAction));
}
return (ConfigData<T>) data;
}
@ -156,7 +134,7 @@ public final class IHaveConfig {
ConfigData<?> data = datamap.get(path);
if (data == null) {
val defval = def.get();
datamap.put(path, data = new ConfigData<>(this, path, defval, setter.apply(defval), getter, setter));
datamap.put(path, data = new ConfigData<>(config, path, defval, setter.apply(defval), getter, setter, saveAction));
}
return (ConfigData<T>) data;
}
@ -172,25 +150,10 @@ public final class IHaveConfig {
public <T> ListConfigData<T> getListData(String path) {
ConfigData<?> data = datamap.get(path);
if (data == null)
datamap.put(path, data = new ListConfigData<>(this, path, new ListConfigData.List<T>()));
datamap.put(path, data = new ListConfigData<>(config, path, new ListConfigData.List<T>(), saveAction));
return (ListConfigData<T>) data;
}
/**
* Schedules a save operation. Use after changing the ConfigurationSection directly.
*/
public void signalChange() {
ConfigData.signalChange(this);
}
/**
* Clears all caches and loads everything from yaml.
*/
public void reset(ConfigurationSection config) {
this.config = config;
datamap.forEach((path, data) -> data.reset());
}
/**
* Generates the config YAML.
*
@ -201,13 +164,6 @@ public final class IHaveConfig {
val ms = obj.getClass().getDeclaredMethods();
for (val m : ms) {
if (!m.getReturnType().getName().equals(ConfigData.class.getName())) continue;
final String mName;
{
var name = m.getName();
var ind = name.lastIndexOf('$');
if (ind == -1) mName = name;
else mName = name.substring(ind + 1);
}
try {
m.setAccessible(true);
List<ConfigData<?>> configList;
@ -220,36 +176,24 @@ public final class IHaveConfig {
try {
return (ConfigData<?>) m.invoke(obj, kv.getValue());
} catch (IllegalAccessException | InvocationTargetException e) {
String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!";
if (obj instanceof Component<?>)
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
else if (obj instanceof JavaPlugin)
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj);
else
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning);
TBMCCoreAPI.SendException("Failed to pregenerate " + m.getName() + " for " + obj + " using config " + kv.getKey() + "!", e);
return null;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
} else {
if (TBMCCoreAPI.IsTestServer())
MainPlugin.Instance.getLogger().warning("Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes()));
MainPlugin.Instance.getLogger().warning("Method " + m.getName() + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes()));
continue;
}
for (val c : configList) {
if (c.getPath().length() == 0)
c.setPath(mName);
else if (!c.getPath().equals(mName))
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + mName);
c.setPath(m.getName());
else if (!c.getPath().equals(m.getName()))
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + m.getName());
c.get(); //Saves the default value if needed - also checks validity
}
} catch (Exception e) {
String msg = "Failed to pregenerate " + mName + " for " + obj + "!";
if (obj instanceof Component<?>)
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
else if (obj instanceof JavaPlugin)
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj);
else
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning);
TBMCCoreAPI.SendException("Failed to pregenerate " + m.getName() + " for " + obj + "!", e);
}
}
}

View file

@ -1,6 +1,7 @@
package buttondevteam.lib.architecture;
import lombok.val;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -11,12 +12,12 @@ import java.util.function.UnaryOperator;
public class ListConfigData<T> extends ConfigData<ListConfigData.List<T>> {
@SuppressWarnings("unchecked")
ListConfigData(IHaveConfig config, String path, List<T> def) {
ListConfigData(ConfigurationSection config, String path, List<T> def, Runnable saveAction) {
super(config, path, def, new ArrayList<>(def), list -> {
var l = new List<>((ArrayList<T>) list);
l.listConfig = def.listConfig;
return l;
}, ArrayList::new);
}, ArrayList::new, saveAction);
def.listConfig = this; //Can't make the List class non-static or pass this in the super() constructor
}

View file

@ -1,13 +1,15 @@
package buttondevteam.lib.architecture;
import org.bukkit.configuration.ConfigurationSection;
import java.util.function.Function;
public class ReadOnlyConfigData<T> extends ConfigData<T> {
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
super(config, path, def, primitiveDef, getter, setter);
ReadOnlyConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter, Runnable saveAction) {
super(config, path, def, primitiveDef, getter, setter, saveAction);
}
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef) {
super(config, path, def, primitiveDef, null, null);
ReadOnlyConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Runnable saveAction) {
super(config, path, def, primitiveDef, saveAction);
}
}

View file

@ -6,22 +6,12 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum Color implements TellrawSerializableEnum {
Black("black", 0, 0, 0),
DarkBlue("dark_blue", 0, 0, 170),
DarkGreen("dark_green", 0, 170, 0),
DarkAqua("dark_aqua", 0, 170, 170),
DarkRed("dark_red", 170, 0, 0),
DarkPurple("dark_purple", 0, 170, 0),
Gold("gold", 255, 170,0),
Gray("gray", 170, 170, 170),
DarkGray("dark_gray", 85, 85, 85),
Blue("blue", 85, 85, 255),
Green("green", 85, 255, 85),
Aqua("aqua", 85, 255, 255),
Red("red", 255, 85,85),
LightPurple("light_purple", 255, 85, 255),
Yellow("yellow", 255, 255, 85),
White("white", 255, 255, 255);
Black("black", 0, 0, 0), DarkBlue("dark_blue", 0, 0, 170), DarkGreen("dark_green", 0, 170, 0), DarkAqua("dark_aqua",
0, 170, 170), DarkRed("dark_red", 170, 0, 0), DarkPurple("dark_purple", 0, 170, 0), Gold("gold", 255, 170,
0), Gray("gray", 170, 170, 170), DarkGray("dark_gray", 85, 85, 85), Blue("blue", 85, 85,
255), Green("green", 85, 255, 85), Aqua("aqua", 85, 255, 255), Red("red", 255, 85,
85), LightPurple("light_purple", 255, 85,
255), Yellow("yellow", 255, 255, 85), White("white", 255, 255, 255);
private final String name;
private final int red;

View file

@ -11,6 +11,7 @@ import lombok.RequiredArgsConstructor;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
import java.io.InputStreamReader;
@ -22,18 +23,14 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* 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.
*/
public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Sender> {
public abstract class Command2<TC extends ICommand2, TP extends Command2Sender> {
protected Command2() {
commandHelp.add("§6---- Commands ----");
}
@ -63,12 +60,10 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
String[] helpText() default {};
/**
* The main permission which allows using this command (individual access can be still revoked with "chroma.command.X").
* The main permission which allows using this command (individual access can be still granted with "chroma.command.X").
* Used to be "tbmc.admin". The {@link #MOD_GROUP} is provided to use with this.
*/
String permGroup() default "";
String[] aliases() default {};
String permGroup() default ""; //TODO
}
@Target(ElementType.PARAMETER)
@ -77,14 +72,13 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
}
@AllArgsConstructor
protected static class SubcommandData<T extends ICommand2<?>> {
protected static class SubcommandData<T extends ICommand2> {
public final Method method;
public final T command;
public final String[] parameters;
public String[] helpText;
}
/*protected static class SubcommandHelpData<T extends ICommand2> extends SubcommandData<T> {
protected static class SubcommandHelpData<T extends ICommand2> extends SubcommandData<T> {
private final TreeSet<String> ht = new TreeSet<>();
private BukkitTask task;
@ -107,19 +101,18 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
task = null; //Run again, if needed
});
}
}*/
}
@RequiredArgsConstructor
protected static class ParamConverter<T> {
public final Function<String, T> converter;
public final String errormsg;
public final Supplier<Iterable<String>> allSupplier;
}
protected final HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
protected HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
private HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
private final ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
private ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
private char commandChar;
@ -127,27 +120,25 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
* Adds a param converter that obtains a specific object from a string parameter.
* The converter may return null.
*
* @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)
* @param <T> The type of the result
*/
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 <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
paramConverters.put(cl, new ParamConverter<>(converter, errormsg));
}
public boolean handleCommand(TP sender, String commandline) {
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
String subcommand = commandline.substring(0, i).toLowerCase();
SubcommandData<TC> sd = subcommands.get(subcommand);
SubcommandData<TC> sd = subcommands.get(subcommand); //O(1)
if (sd == null) continue;
boolean sync = Bukkit.isPrimaryThread();
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
try {
handleCommandAsync(sender, commandline, sd, subcommand, sync);
} catch (Exception e) {
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e, MainPlugin.Instance);
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e);
}
});
return true; //We found a method
@ -167,7 +158,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
* @param sync Whether the command was originally sync
* @throws Exception If something's not right
*/
private void handleCommandAsync(TP sender, String commandline, SubcommandData<TC> sd, String subcommand, boolean sync) throws Exception {
public void handleCommandAsync(TP sender, String commandline, SubcommandData<TC> sd, String subcommand, boolean sync) throws Exception {
if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands
sender.sendMessage(sd.helpText);
return;
@ -230,18 +221,6 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
continue;
} else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
try {
if (cl == boolean.class) {
params.add(Boolean.parseBoolean(param));
continue;
}
if (cl == char.class) {
if (param.length() != 1) {
sender.sendMessage("§c'" + param + "' is not a character.");
return;
}
params.add(param.charAt(0));
continue;
}
//noinspection unchecked
Number n = ChromaUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
params.add(n);
@ -253,7 +232,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
}
val conv = paramConverters.get(cl);
if (conv == null)
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method + "'");
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'");
val cparam = conv.converter.apply(param);
if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
@ -263,7 +242,6 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
}
Runnable lol = () -> {
try {
sd.method.setAccessible(true); //It may be part of a private class
val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time)
if (ret instanceof Boolean) {
if (!(boolean) ret) //Show usage
@ -271,9 +249,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
} else if (ret != null)
throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret);
} catch (InvocationTargetException e) {
TBMCCoreAPI.SendException("An error occurred in a command handler for " + subcommand + "!", e.getCause(), MainPlugin.Instance);
TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause());
} catch (Exception e) {
TBMCCoreAPI.SendException("Command handling failed for sender " + sender + " and subcommand " + subcommand, e, MainPlugin.Instance);
TBMCCoreAPI.SendException("Command handling failed for sender " + sender + " and subcommand " + subcommand, e);
}
};
if (sync)
@ -284,12 +262,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
public abstract void registerCommand(TC command);
protected List<SubcommandData<TC>> registerCommand(TC command, char commandChar) {
return registerCommand(command, command.getCommandPath(), commandChar);
}
protected List<SubcommandData<TC>> registerCommand(TC command, String path, @SuppressWarnings("SameParameterValue") char commandChar) {
protected void registerCommand(TC command, @SuppressWarnings("SameParameterValue") char commandChar) {
this.commandChar = commandChar;
val path = command.getCommandPath();
int x = path.indexOf(' ');
val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x);
//var scmdmap = subcommandStrings.computeIfAbsent(mainPath, k -> new HashSet<>()); //Used to display subcommands
@ -298,7 +273,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
boolean nosubs = true;
boolean isSubcommand = x != -1;
try { //Register the default handler first so it can be reliably overwritten
mainMethod = command.getClass().getMethod("def", Command2Sender.class);
mainMethod = command.getClass().getMethod("def", Command2Sender.class, String.class);
val cc = command.getClass().getAnnotation(CommandClass.class);
var ht = cc == null || isSubcommand ? new String[0] : cc.helpText(); //If it's not the main command, don't add it
if (ht.length > 0)
@ -309,53 +284,46 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
if (!commandHelp.contains(mainPath))
commandHelp.add(mainPath);
} catch (Exception e) {
TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e, MainPlugin.Instance);
TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e);
}
var addedSubcommands = new ArrayList<SubcommandData<TC>>();
for (val method : command.getClass().getMethods()) {
val ann = method.getAnnotation(Subcommand.class);
if (ann == null) continue; //Don't call the method on non-subcommands because they're not in the yaml
var ht = command.getHelpText(method, ann);
if (ht != null) { //The method is a subcommand
if (ht != null) {
val subcommand = commandChar + path + //Add command path (class name by default)
getCommandPath(method.getName(), ' '); //Add method name, unless it's 'def'
var params = new String[method.getParameterCount() - 1];
ht = getParameterHelp(method, ht, subcommand, params);
var sd = new SubcommandData<>(method, command, params, ht);
registerCommand(path, method.getName(), ann, sd);
for (String p : command.getCommandPaths())
registerCommand(p, method.getName(), ann, sd);
addedSubcommands.add(sd);
ht = getParameterHelp(method, ht, subcommand);
subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text
scmdHelpList.add(subcommand);
nosubs = false;
}
}
if (nosubs && scmdHelpList.size() > 0)
scmdHelpList.remove(scmdHelpList.size() - 1); //Remove Subcommands header
if (mainMethod != null && !subcommands.containsKey(commandChar + path)) { //Command specified by the class
var sd = new SubcommandData<>(mainMethod, command, null, scmdHelpList.toArray(new String[0]));
subcommands.put(commandChar + path, sd);
addedSubcommands.add(sd);
}
if (mainMethod != null && !subcommands.containsKey(commandChar + path)) //Command specified by the class
subcommands.put(commandChar + path, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0])));
if (mainMethod != null && !mainPath.equals(commandChar + path)) { //Main command, typically the same as the above
if (isSubcommand) { //The class itself is a subcommand
val scmd = subcommands.computeIfAbsent(mainPath, p -> new SubcommandData<>(null, null, new String[0], new String[]{"§6---- Subcommands ----"}));
val scmd = subcommands.computeIfAbsent(mainPath, p -> new SubcommandData<>(null, null, new String[]{"§6---- Subcommands ----"}));
val scmdHelp = Arrays.copyOf(scmd.helpText, scmd.helpText.length + scmdHelpList.size());
for (int i = 0; i < scmdHelpList.size(); i++)
scmdHelp[scmd.helpText.length + i] = scmdHelpList.get(i);
scmd.helpText = scmdHelp;
} else if (!subcommands.containsKey(mainPath))
subcommands.put(mainPath, new SubcommandData<>(null, null, scmdHelpList.toArray(new String[0])));
}
return addedSubcommands;
}
private String[] getParameterHelp(Method method, String[] ht, String subcommand, String[] parameters) {
private String[] getParameterHelp(Method method, String[] ht, String subcommand) {
val str = method.getDeclaringClass().getResourceAsStream("/commands.yml");
if (str == null)
TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance);
TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"));
else {
if (ht.length > 0)
ht[0] = "§6---- " + ht[0] + " ----";
YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor
val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName().replace('$', '.'));
val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName());
if (ccs != null) {
val cs = ccs.getConfigurationSection(method.getName());
if (cs != null) {
@ -367,26 +335,16 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
String[] both = Arrays.copyOf(ht, ht.length + 1);
both[ht.length] = "§6Usage:§r " + subcommand + " " + params;
ht = both;
var paramArray = params.split(" ");
for (int j = 0; j < paramArray.length && j < parameters.length; j++)
parameters[j] = paramArray[j];
} else
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance);
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method.toString() + "' != " + mname + " or params is " + params));
} else
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (cs is null)! Make sure to use 'clean install' when building the project.");
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("cs is " + cs));
} else
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (ccs is null)! Make sure to use 'clean install' when building the project.");
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("ccs is " + ccs + " - class: " + method.getDeclaringClass().getCanonicalName()));
}
return ht;
}
private void registerCommand(String path, String methodName, Subcommand ann, SubcommandData<TC> sd) {
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
subcommands.put(subcommand, sd);
for (String alias : ann.aliases())
subcommands.put(commandChar + path + alias, sd);
}
public abstract boolean hasPermission(TP sender, TC command, Method subcommand);
public String[] getCommandsText() {
@ -413,17 +371,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
for (val method : command.getClass().getMethods()) {
val ann = method.getAnnotation(Subcommand.class);
if (ann == null) continue;
unregisterCommand(path, method.getName(), ann);
for (String p : command.getCommandPaths())
unregisterCommand(p, method.getName(), ann);
}
}
private void unregisterCommand(String path, String methodName, Subcommand ann) {
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
val subcommand = commandChar + path + getCommandPath(method.getName(), ' ');
subcommands.remove(subcommand);
for (String alias : ann.aliases())
subcommands.remove(commandChar + path + alias);
}
}
/**

View file

@ -1,41 +1,26 @@
package buttondevteam.lib.chat;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.player.ChromaGamerBase;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import lombok.val;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.*;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.TabCompleteEvent;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.javatuples.Triplet;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
/**
@ -45,19 +30,7 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
*/
@Override
public void registerCommand(ICommand2MC command) {
/*String mainpath;
var plugin = command.getPlugin();
{
String cpath = command.getCommandPath();
int i = cpath.indexOf(' ');
mainpath = cpath.substring(0, i == -1 ? cpath.length() : i);
}*/
var subcmds = super.registerCommand(command, '/');
var bcmd = registerOfficially(command, subcmds);
if (bcmd != null)
for (String alias : bcmd.getAliases())
super.registerCommand(command, command.getCommandPath().replaceFirst("^" + bcmd.getName(), Matcher.quoteReplacement(alias)), '/');
super.registerCommand(command, '/');
var perm = "chroma.command." + command.getCommandPath().replace(' ', '.');
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(new Permission(perm,
@ -73,9 +46,9 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
}
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,
perm = "chroma." + pg;
if (Bukkit.getPluginManager().getPermission(perm) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(new Permission(perm,
PermissionDefault.OP)); //Do not allow any commands that belong to a group
}
}
@ -104,6 +77,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
p = MainPlugin.permission.playerHas(sender instanceof Player ? ((Player) sender).getLocation().getWorld().getName() : null, (OfflinePlayer) sender, perm);
else
p = false; //Use sender's method
//System.out.println("playerHas " + perm + ": " + p);
//System.out.println("hasPermission: " + sender.hasPermission(perm));
if (!p) p = sender.hasPermission(perm);
} else break; //If any of the permissions aren't granted then don't allow
}
@ -154,8 +129,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
* {@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 <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
super.addParamConverter(cl, converter, "§c" + errormsg);
}
public void unregisterCommands(ButtonPlugin plugin) {
@ -173,310 +148,111 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false));
}
/*@EventHandler
public void onTabComplete(TabCompleteEvent event) {
try {
event.getCompletions().clear(); //Remove player names
} catch (UnsupportedOperationException e) {
//System.out.println("Tabcomplete: " + event.getBuffer());
//System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions"));
//System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners()));
}
}*/
@Override
public boolean handleCommand(Command2MCSender sender, String commandline) {
return handleCommand(sender, commandline, true);
}
private boolean handleCommand(Command2MCSender sender, String commandline, boolean checkPlugin) {
int i = commandline.indexOf(' ');
String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash
PluginCommand pcmd;
if (!checkPlugin
|| MainPlugin.Instance.prioritizeCustomCommands.get()
|| (pcmd = Bukkit.getPluginCommand(mainpath)) == null //Our commands aren't PluginCommands
|| pcmd.getPlugin() instanceof ButtonPlugin) //Unless it's specified in the plugin.yml
return super.handleCommand(sender, commandline);
else
return false;
}
private boolean shouldRegisterOfficially = true;
private Command registerOfficially(ICommand2MC command, List<SubcommandData<ICommand2MC>> subcmds) {
if (!shouldRegisterOfficially || command.getPlugin() == null) return null;
try {
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, subcmds, 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) {
var user = ChromaGamerBase.getFromSender(sender);
if (user == null) {
TBMCCoreAPI.SendException("Failed to run Bukkit command for user!", new Throwable("No Chroma user found"), MainPlugin.Instance);
sender.sendMessage("§cAn internal error occurred.");
@EventHandler
private void handleTabComplete(TabCompleteEvent event) {
String commandline = event.getBuffer();
CommandSender sender = event.getSender();
//System.out.println("tab");
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
String subcommand = commandline.substring(0, i).toLowerCase();
if (subcommand.length() == 0 || subcommand.charAt(0) != '/') subcommand = '/' + subcommand; //Console
//System.out.println("Subcommand: " + subcommand);
SubcommandData<ICommand2MC> sd = subcommands.get(subcommand); //O(1)
if (sd == null) continue;
//System.out.println("ht: " + Arrays.toString(sd.helpText));
Arrays.stream(sd.helpText).skip(1).map(ht -> new HashMap.SimpleEntry<>(ht, subcommands.get(ht))).filter(e -> e.getValue() != null)
.filter(kv -> kv.getKey().startsWith(commandline))
.filter(kv -> hasPermission(sender, kv.getValue().command, kv.getValue().method))
.forEach(kv -> event.getCompletions().add((kv.getKey()).substring(kv.getKey().lastIndexOf(' ', commandline.length()) + 1)));
if (sd.method == null || sd.command == null)
return;
/*if (!hasPermission(sender, sd.command, sd.method)) { - TODO: Arguments
sender.sendMessage("§cYou don't have permission to use this command");
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 {
protected BukkitCommand(String name) {
super(name);
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args);
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
return Collections.emptyList();
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
return Collections.emptyList();
}
}
private static class TabcompleteHelper {
private static Commodore commodore;
private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent,
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 ButtonPlugin.getCommand2MC().hasPermission(sender, subcommand.command, subcommand.method);
});
scmd = scmdBuilder.build();
parent.addChild(scmd);
return scmd;
}
private static void registerTabcomplete(ICommand2MC command2MC, List<SubcommandData<ICommand2MC>> subcmds, 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];
ArgumentType<?> type;
final Class<?> ptype = parameter.getType();
final boolean customParamType;
{
boolean customParamTypeTemp = false;
if (ptype == String.class)
if (parameter.isAnnotationPresent(TextArg.class))
type = StringArgumentType.greedyString();
else
type = StringArgumentType.word();
else if (ptype == int.class || ptype == Integer.class
|| ptype == byte.class || ptype == Byte.class
|| ptype == short.class || ptype == Short.class)
type = IntegerArgumentType.integer(); //TODO: Min, max
else if (ptype == long.class || ptype == Long.class)
type = LongArgumentType.longArg();
else if (ptype == float.class || ptype == Float.class)
type = FloatArgumentType.floatArg();
else if (ptype == double.class || ptype == Double.class)
type = DoubleArgumentType.doubleArg();
else if (ptype == char.class || ptype == Character.class)
type = StringArgumentType.word();
else if (ptype == boolean.class || ptype == Boolean.class)
type = BoolArgumentType.bool();
else if (parameter.isVarArgs())
type = StringArgumentType.greedyString();
val params = new ArrayList<Object>(sd.method.getParameterCount());
int j = subcommand.length(), pj;
Class<?>[] parameterTypes = sd.method.getParameterTypes();
if (parameterTypes.length == 0)
throw new Exception("No sender parameter for method '" + sd.method + "'");
val sendertype = parameterTypes[0];
final ChromaGamerBase cg;
if (sendertype.isAssignableFrom(sender.getClass()))
params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type
else if (sender instanceof Command2MCSender
&& sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
params.add(((Command2MCSender) sender).getSender());
else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
&& sender instanceof Command2MCSender
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
&& cg.getClass() == sendertype) //The command expects a user of our system
params.add(cg);
else {
type = StringArgumentType.word();
customParamTypeTemp = true;
sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command.");
return true;
}
customParamType = customParamTypeTemp;
val paramArr = sd.method.getParameters();
for (int i1 = 1; i1 < parameterTypes.length; i1++) {
Class<?> cl = parameterTypes[i1];
pj = j + 1; //Start index
if (pj == commandline.length() + 1) { //No param given
if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) {
if (cl.isPrimitive())
params.add(Defaults.defaultValue(cl));
else if (Number.class.isAssignableFrom(cl)
|| Number.class.isAssignableFrom(cl))
params.add(Defaults.defaultValue(Primitives.unwrap(cl)));
else
params.add(null);
continue; //Fill the remaining params with nulls
} else {
sender.sendMessage(sd.helpText); //Required param missing
return true;
}
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());
if (paramArr[i1].isVarArgs()) {
par0ams.add(commandline.substring(j + 1).split(" +"));
continue;
}
val paramValueString = context.getArgument(subcmd.parameters[k], String.class);
if (paramObj.getType() == String.class) {
args[j] = paramValueString;
j = commandline.indexOf(' ', j + 1); //End index
if (j == -1 || paramArr[i1].isAnnotationPresent(TextArg.class)) //Last parameter
j = commandline.length();
String param = commandline.substring(pj, j);
if (cl == String.class) {
params.add(param);
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
} else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
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());
Number n = ThorpeUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
params.add(n);
} catch (ParseException e) {
sender.sendMessage("§c'" + param + "' is not a number.");
return true;
}
continue;
}
val conv = paramConverters.get(cl);
if (conv == null)
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'");
val cparam = conv.converter.apply(param);
if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
return true;
}
params.add(cparam);
}
try {
val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time)
if (ret instanceof Boolean) {
if (!(boolean) ret) //Show usage
sender.sendMessage(sd.helpText);
} else if (ret != null)
throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret);
return true; //We found a method
} catch (InvocationTargetException e) {
TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause());
}*/
}
}
}
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;
}
}

View file

@ -1,6 +1,5 @@
package buttondevteam.lib.chat;
import buttondevteam.core.component.channel.Channel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.command.CommandSender;
@ -8,8 +7,6 @@ import org.bukkit.command.CommandSender;
@RequiredArgsConstructor
public class Command2MCSender implements Command2Sender {
private @Getter final CommandSender sender;
private @Getter final Channel channel;
private @Getter final CommandSender permCheck;
@Override
public void sendMessage(String message) {

View file

@ -1,15 +0,0 @@
package buttondevteam.lib.chat;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Can be used if an argument should be completed with predefined strings.
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomTabComplete {
String[] value();
}

View file

@ -1,29 +0,0 @@
package buttondevteam.lib.chat;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* The method must return with {@link String}[] or {@link Iterable}&lt;{@link String}&gt; and may have the sender and preceding arguments as parameters.
* The predecing arguments must be in order, from first to whatever is needed. If the nth arg is needed, you need to specify n params.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CustomTabCompleteMethod {
/**
* The parameter's name where we want to give completion
*/
String param();
/**
* The subcommand(s) which have the parameter, by default the method's name
*/
String[] subcommand() default {};
/**
* Parameter types can provide tab completions. This allows disabling that.
*/
boolean ignoreTypeCompletion() default false;
}

View file

@ -12,9 +12,10 @@ public abstract class ICommand2<TP extends Command2Sender> {
* Default handler for commands, can be used to copy the args too.
*
* @param sender The sender which ran the command
* @param args All of the arguments passed as is
* @return The success of the command
*/
public boolean def(TP sender) {
public boolean def(TP sender, @Command2.TextArg String args) {
return false;
}
@ -63,18 +64,6 @@ public abstract class ICommand2<TP extends Command2Sender> {
return path;
}
private static final String[] EMPTY_PATHS = new String[0];
/**
* All of the command's paths it will be invoked on. Does not include aliases or the default path.
* Must be lowercase and must include the full path.
*
* @return The full command paths that this command should be registered under in addition to the default one.
*/
public String[] getCommandPaths() {
return EMPTY_PATHS;
}
private String getcmdpath() {
if (!getClass().isAnnotationPresent(CommandClass.class))
throw new RuntimeException(

View file

@ -22,7 +22,7 @@ public class TBMCChatAPI {
* @return The event cancelled state
*/
public static boolean SendChatMessage(ChatMessage cm) {
return SendChatMessage(cm, cm.getUser().channel.get());
return SendChatMessage(cm, cm.getUser().channel().get());
}
/**
@ -35,9 +35,9 @@ public class TBMCChatAPI {
*/
public static boolean SendChatMessage(ChatMessage cm, Channel channel) {
if (!Channel.getChannelList().contains(channel))
throw new RuntimeException("Channel " + channel.DisplayName.get() + " not registered!");
if (!channel.Enabled.get()) {
cm.getSender().sendMessage("§cThe channel '" + channel.DisplayName.get() + "' is disabled!");
throw new RuntimeException("Channel " + channel.DisplayName().get() + " not registered!");
if (!channel.Enabled().get()) {
cm.getSender().sendMessage("§cThe channel '" + channel.DisplayName().get() + "' is disabled!");
return true; //Cancel sending if channel is disabled
}
Supplier<Boolean> task = () -> {
@ -70,11 +70,11 @@ public class TBMCChatAPI {
*/
public static boolean SendSystemMessage(Channel channel, RecipientTestResult rtr, String message, TBMCSystemChatEvent.BroadcastTarget target, String... exceptions) {
if (!Channel.getChannelList().contains(channel))
throw new RuntimeException("Channel " + channel.DisplayName.get() + " not registered!");
if (!channel.Enabled.get())
throw new RuntimeException("Channel " + channel.DisplayName().get() + " not registered!");
if (!channel.Enabled().get())
return true; //Cancel sending
if (!Arrays.asList(exceptions).contains("Minecraft"))
Bukkit.getConsoleSender().sendMessage("[" + channel.DisplayName.get() + "] " + message);
Bukkit.getConsoleSender().sendMessage("[" + channel.DisplayName().get() + "] " + message);
TBMCSystemChatEvent event = new TBMCSystemChatEvent(channel, message, rtr.score, rtr.groupID, exceptions, target);
return ChromaUtils.callEventAsync(event);
}

View file

@ -0,0 +1,25 @@
package buttondevteam.lib.player;
import buttondevteam.core.component.channel.Channel;
import org.bukkit.configuration.file.YamlConfiguration;
public class ChannelPlayerData { //I just want this to work
private final PlayerData<String> data;
private final Channel def;
public ChannelPlayerData(String name, YamlConfiguration yaml, Channel def) {
data = new PlayerData<>(name, yaml, "");
this.def = def;
}
public Channel get() {
String str = data.get();
if (str.isEmpty())
return def;
return Channel.getChannels().filter(c -> str.equals(c.ID)).findAny().orElse(def);
}
public void set(Channel value) {
data.set(value.ID);
}
}

View file

@ -1,11 +1,8 @@
package buttondevteam.lib.player;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import lombok.Getter;
import com.google.common.collect.HashBiMap;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -15,60 +12,37 @@ import javax.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ChromaGamerEnforcer
public abstract class ChromaGamerBase {
private static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/**
* Holds data per user class
*/
private static final HashMap<Class<? extends ChromaGamerBase>, StaticUserData<?>> staticDataMap = new HashMap<>();
public abstract class ChromaGamerBase implements AutoCloseable {
public static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final HashBiMap<Class<? extends ChromaGamerBase>, String> playerTypes = HashBiMap.create();
/**
* Use {@link #getConfig()} where possible; the 'id' must be always set
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)})
*/
//protected YamlConfiguration plugindata;
@Getter
protected final IHaveConfig config = new IHaveConfig(this::save);
protected CommonUserData<?> commonUserData;
/**
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) and to init the configs.
*/
public static <T extends ChromaGamerBase> void RegisterPluginUserClass(Class<T> userclass, Supplier<T> constructor) {
Class<? extends T> cl;
String folderName;
if (userclass.isAnnotationPresent(UserClass.class)) {
cl = userclass;
folderName = userclass.getAnnotation(UserClass.class).foldername();
} else if (userclass.isAnnotationPresent(AbstractUserClass.class)) {
var ucl = userclass.getAnnotation(AbstractUserClass.class).prototype();
if (!userclass.isAssignableFrom(ucl))
throw new RuntimeException("The prototype class (" + ucl.getSimpleName() + ") must be a subclass of the userclass parameter (" + userclass.getSimpleName() + ")!");
//noinspection unchecked
cl = (Class<? extends T>) ucl;
folderName = userclass.getAnnotation(AbstractUserClass.class).foldername();
} else // <-- Really important
public static void RegisterPluginUserClass(Class<? extends ChromaGamerBase> userclass) {
if (userclass.isAnnotationPresent(UserClass.class))
playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername());
else if (userclass.isAnnotationPresent(AbstractUserClass.class))
playerTypes.put(userclass.getAnnotation(AbstractUserClass.class).prototype(),
userclass.getAnnotation(AbstractUserClass.class).foldername());
else // <-- Really important
throw new RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase");
var sud = new StaticUserData<T>(folderName);
sud.getConstructors().put(cl, constructor);
sud.getConstructors().put(userclass, constructor); // Alawys register abstract and prototype class (TBMCPlayerBase and TBMCPlayer)
staticDataMap.put(userclass, sud);
}
/**
* Returns the folder name for the given player class.
*
* @param cl The class to get the folder from (like {@link TBMCPlayerBase} or one of it's subclasses)
* @param cl
* The class to get the folder from (like {@link TBMCPlayerBase} or one of it's subclasses)
* @return The folder name for the given type
* @throws RuntimeException If the class doesn't have the {@link UserClass} annotation.
* @throws RuntimeException
* If the class doesn't have the {@link UserClass} annotation.
*/
public static <T extends ChromaGamerBase> String getFolderForType(Class<T> cl) {
if (cl.isAnnotationPresent(UserClass.class))
@ -81,61 +55,50 @@ public abstract class ChromaGamerBase {
/**
* Returns the player class for the given folder name.
*
* @param foldername The folder to get the class from (like "minecraft")
* @param foldername
* The folder to get the class from (like "minecraft")
* @return The type for the given folder name or null if not found
*/
public static Class<? extends ChromaGamerBase> getTypeForFolder(String foldername) {
synchronized (staticDataMap) {
return staticDataMap.entrySet().stream().filter(e -> e.getValue().getFolder().equalsIgnoreCase(foldername))
.map(Map.Entry::getKey).findAny().orElse(null);
}
return playerTypes.inverse().get(foldername);
}
/**
* This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, use Discord IDs, etc.<br>
* <b>Does not include .yml</b>
*/
public final String getFileName() {
return plugindata.getString(getFolder() + "_id");
}
/**
* Use {@link #data(Object)} or {@link #data(String, Object)} where possible; the 'id' must be always set
*/
protected YamlConfiguration plugindata;
/***
* Retrieves a user from cache or loads it from disk.
* Loads a user from disk and returns the user object. Make sure to use the subclasses' methods, where possible, like {@link TBMCPlayerBase#getPlayer(java.util.UUID, Class)}
*
* @param fname Filename without .yml, the user's identifier for that type
* @param fname Filename without .yml, usually UUID
* @param cl User class
* @return The user object
*/
public static synchronized <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
StaticUserData<?> staticUserData = null;
for (var sud : staticDataMap.entrySet()) {
if (sud.getKey().isAssignableFrom(cl)) {
staticUserData = sud.getValue();
break;
}
}
if (staticUserData == null)
throw new RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass");
var commonUserData = staticUserData.getUserDataMap().get(fname);
if (commonUserData == null) {
final String folder = staticUserData.getFolder();
public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
try {
T obj = cl.newInstance();
final String folder = getFolderForType(cl);
final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml");
file.getParentFile().mkdirs();
var playerData = YamlConfiguration.loadConfiguration(file);
commonUserData = new CommonUserData<>(playerData);
playerData.set(staticUserData.getFolder() + "_id", fname);
staticUserData.getUserDataMap().put(fname, commonUserData);
}
if (commonUserData.getUserCache().containsKey(cl))
return (T) commonUserData.getUserCache().get(cl);
T obj;
if (staticUserData.getConstructors().containsKey(cl))
//noinspection unchecked
obj = (T) staticUserData.getConstructors().get(cl).get();
else {
try {
obj = cl.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create new instance of user of type " + cl.getSimpleName() + "!", e);
}
}
obj.commonUserData = commonUserData;
obj.init();
obj.scheduleUncache();
obj.plugindata = YamlConfiguration.loadConfiguration(file);
obj.plugindata.set(folder + "_id", fname);
return obj;
} catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while loading a " + cl.getSimpleName() + "!", e);
}
return null;
}
private static ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/**
* Adds a converter to the start of the list.
@ -161,110 +124,88 @@ public abstract class ChromaGamerBase {
return null;
}
public static void saveUsers() {
synchronized (staticDataMap) {
for (var sud : staticDataMap.values())
for (var cud : sud.getUserDataMap().values())
ConfigData.saveNow(cud.getPlayerData()); //Calls save()
}
}
protected void init() {
config.reset(commonUserData.getPlayerData());
/**
* Saves the player. It'll pass all exceptions to the caller. To automatically handle the exception, use {@link #save()} instead.
*/
@Override
public void close() throws Exception {
if (plugindata.getKeys(false).size() > 0)
plugindata.save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
}
/**
* Saves the player. It'll handle all exceptions that may happen. Called automatically.
* Saves the player. It'll handle all exceptions that may happen. To catch the exception, use {@link #close()} instead.
*/
protected void save() {
public void save() {
try {
if (commonUserData.getPlayerData().getKeys(false).size() > 0)
commonUserData.getPlayerData().save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
close();
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while saving player to " + getFolder() + "/" + getFileName() + ".yml!", e, MainPlugin.Instance);
TBMCCoreAPI.SendException("Error while saving player to " + getFolder() + "/" + getFileName() + ".yml!", e);
}
}
/**
* Removes the user from the cache. This will be called automatically after some time by default.
*/
public void uncache() {
final var userCache = commonUserData.getUserCache();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (userCache) {
if (userCache.containsKey(getClass()))
if (userCache.remove(getClass()) != this)
throw new IllegalStateException("A different player instance was cached!");
}
}
protected void scheduleUncache() {
Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, this::uncache, 2 * 60 * 60 * 20); //2 hours
}
/**
* Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set.
* Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set
*
* @param user The account to connect with
* @param user
* The account to connect with
*/
public final <T extends ChromaGamerBase> void connectWith(T user) {
public <T extends ChromaGamerBase> void connectWith(T user) {
// Set the ID, go through all linked files and connect them as well
if (!playerTypes.containsKey(getClass()))
throw new RuntimeException("Class not registered as a user class! Use TBMCCoreAPI.RegisterUserClass");
final String ownFolder = getFolder();
final String userFolder = user.getFolder();
if (ownFolder.equalsIgnoreCase(userFolder))
throw new RuntimeException("Do not connect two accounts of the same type! Type: "+ownFolder);
var ownData = commonUserData.getPlayerData();
var userData = user.commonUserData.getPlayerData();
userData.set(ownFolder + "_id", ownData.getString(ownFolder + "_id"));
ownData.set(userFolder + "_id", userData.getString(userFolder + "_id"));
config.signalChange();
user.config.signalChange();
user.plugindata.set(ownFolder + "_id", plugindata.getString(ownFolder + "_id"));
plugindata.set(userFolder + "_id", user.plugindata.getString(userFolder + "_id"));
Consumer<YamlConfiguration> sync = sourcedata -> {
final String sourcefolder = sourcedata == ownData ? ownFolder : userFolder;
final String sourcefolder = sourcedata == plugindata ? ownFolder : userFolder;
final String id = sourcedata.getString(sourcefolder + "_id");
for (val entry : staticDataMap.entrySet()) { // Set our ID in all files we can find, both from our connections and the new ones
for (val entry : playerTypes.entrySet()) { // Set our ID in all files we can find, both from our connections and the new ones
if (entry.getKey() == getClass() || entry.getKey() == user.getClass())
continue;
var entryFolder = entry.getValue().getFolder();
final String otherid = sourcedata.getString(entryFolder + "_id");
final String otherid = sourcedata.getString(entry.getValue() + "_id");
if (otherid == null)
continue;
ChromaGamerBase cg = getUser(otherid, entry.getKey());
var cgData = cg.commonUserData.getPlayerData();
cgData.set(sourcefolder + "_id", id); // Set new IDs
for (val item : staticDataMap.entrySet()) {
var itemFolder = item.getValue().getFolder();
if (sourcedata.contains(itemFolder + "_id")) {
cgData.set(itemFolder + "_id", sourcedata.getString(itemFolder + "_id")); // Set all existing IDs
try (ChromaGamerBase cg = getUser(otherid, entry.getKey())) {
cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs
for (val item : playerTypes.entrySet())
if (sourcedata.contains(item.getValue() + "_id"))
cg.plugindata.set(item.getValue() + "_id", sourcedata.getString(item.getValue() + "_id")); // Set all existing IDs
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to update " + sourcefolder + " ID in player files for " + id
+ " in folder with " + entry.getValue() + " id " + otherid + "!", e);
}
}
cg.config.signalChange();
}
};
sync.accept(ownData);
sync.accept(userData);
sync.accept(plugindata);
sync.accept(user.plugindata);
}
/**
* Returns the ID for the T typed player object connected with this one or null if no connection found.
* Retunrs the ID for the T typed player object connected with this one or null if no connection found.
*
* @param cl The player class to get the ID from
* @param cl
* The player class to get the ID from
* @return The ID or null if not found
*/
public final <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return commonUserData.getPlayerData().getString(getFolderForType(cl) + "_id");
public <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return plugindata.getString(getFolderForType(cl) + "_id");
}
/**
* Returns a player instance of the given type that represents the same player. This will return a new instance unless the player is cached.<br>
* If the class is a subclass of the current class then the same ID is used, otherwise, a connected ID is used, if found.
* Returns this player as a plugin player. This will return a new instance unless the player is online.<br>
* Make sure to close both the returned and this object. A try-with-resources block or two can help.<br>
*
* @param cl The target player class
* @return The player as a {@link T} object or null if the user doesn't have an account there
* @param cl
* The target player class
* @return The player as a {@link T} object or null if not having an account there
*/
@SuppressWarnings("unchecked")
@Nullable
public final <T extends ChromaGamerBase> T getAs(Class<T> cl) {
public <T extends ChromaGamerBase> T getAs(Class<T> cl) { // TODO: Provide a way to use TBMCPlayerBase's loaded players
if (cl.getSimpleName().equals(getClass().getSimpleName()))
return (T) this;
String newfolder = getFolderForType(cl);
@ -272,34 +213,105 @@ public abstract class ChromaGamerBase {
throw new RuntimeException("The specified class " + cl.getSimpleName() + " isn't registered!");
if (newfolder.equals(getFolder())) // If in the same folder, the same filename is used
return getUser(getFileName(), cl);
var playerData = commonUserData.getPlayerData();
if (!playerData.contains(newfolder + "_id"))
if (!plugindata.contains(newfolder + "_id"))
return null;
return getUser(playerData.getString(newfolder + "_id"), cl);
return getUser(plugindata.getString(newfolder + "_id"), cl);
}
/**
* This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, Discord IDs, etc.<br>
* <b>Does not include .yml</b>
*/
public final String getFileName() {
return commonUserData.getPlayerData().getString(getFolder() + "_id");
}
/**
* This method returns the folder that this player data is stored in. For example: "minecraft".
*/
public final String getFolder() {
public String getFolder() {
return getFolderForType(getClass());
}
private void ThrowIfNoUser() {
if (!getClass().isAnnotationPresent(UserClass.class)
&& !getClass().isAnnotationPresent(AbstractUserClass.class))
throw new RuntimeException("Class not registered as a user class! Use @UserClass");
}
@SuppressWarnings("rawtypes")
private final HashMap<String, PlayerData> datamap = new HashMap<>();
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(String sectionname, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
@SuppressWarnings("rawtypes")
private final HashMap<String, EnumPlayerData> dataenummap = new HashMap<>();
private ChannelPlayerData datachannel;
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(String sectionname, Class<T> cl, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(Class<T> cl, T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Channel
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected ChannelPlayerData dataChannel(Channel def) { //TODO: Make interface with fromString() method and require use of that for player data types
ThrowIfNoUser();
if (datachannel == null)
datachannel = new ChannelPlayerData("channel", plugindata, def);
return datachannel;
}
/**
* Get player information. This method calls the {@link TBMCPlayerGetInfoEvent} to get all the player information across the TBMC plugins.
*
* @param target The {@link InfoTarget} to return the info for.
* @param target
* The {@link InfoTarget} to return the info for.
* @return The player information.
*/
public final String getInfo(InfoTarget target) {
public String getInfo(InfoTarget target) {
TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target);
Bukkit.getServer().getPluginManager().callEvent(event);
return event.getResult();
@ -311,6 +323,7 @@ public abstract class ChromaGamerBase {
//-----------------------------------------------------------------
public final ConfigData<Channel> channel = config.getData("channel", Channel.GlobalChat,
id -> Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID);
public ChannelPlayerData channel() {
return dataChannel(Channel.GlobalChat);
}
}

View file

@ -1,19 +0,0 @@
package buttondevteam.lib.player;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.HashMap;
/**
* Per user, regardless of actual type
*
* @param <T> The user class, may be abstract
*/
@Getter
@RequiredArgsConstructor
public class CommonUserData<T extends ChromaGamerBase> {
private final HashMap<Class<? extends T>, ? extends T> userCache = new HashMap<>();
private final YamlConfiguration playerData;
}

View file

@ -0,0 +1,26 @@
package buttondevteam.lib.player;
import org.bukkit.configuration.file.YamlConfiguration;
public class EnumPlayerData<T extends Enum<T>> {
private final PlayerData<String> data;
private final Class<T> cl;
private final T def;
public EnumPlayerData(String name, YamlConfiguration yaml, Class<T> cl, T def) {
data = new PlayerData<String>(name, yaml, "");
this.cl = cl;
this.def = def;
}
public T get() {
String str = data.get();
if (str.isEmpty())
return def;
return Enum.valueOf(cl, str);
}
public void set(T value) {
data.set(value.toString());
}
}

View file

@ -0,0 +1,37 @@
package buttondevteam.lib.player;
import org.bukkit.configuration.file.YamlConfiguration;
public class PlayerData<T> {
private final String name;
private final YamlConfiguration yaml;
private final T def;
public PlayerData(String name, YamlConfiguration yaml, T def) {
this.name = name;
this.yaml = yaml;
this.def = def;
}
@SuppressWarnings("unchecked")
// @Deprecated - What was once enforced (2 days ago from now) vanished now
public T get() {
Object value = yaml.get(name, def);
if (value instanceof Integer) {
if (def instanceof Short) // If the default is Short the value must be as well because both are T
return (T) (Short) ((Integer) value).shortValue();
if (def instanceof Long)
return (T) (Long) ((Integer) value).longValue();
}
return (T) value;
}
public void set(T value) {
yaml.set(name, value);
}
@Override
public String toString() {
return get().toString();
}
}

View file

@ -1,23 +0,0 @@
package buttondevteam.lib.player;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import java.util.function.Supplier;
/**
* Per user class
*
* @param <T> The user class type, may be abstract
*/
@Getter
@RequiredArgsConstructor
public class StaticUserData<T extends ChromaGamerBase> {
private final HashMap<Class<? extends T>, Supplier<T>> constructors = new HashMap<>();
/**
* Key: User ID
*/
private final HashMap<String, CommonUserData<?>> userDataMap = new HashMap<>();
private final String folder;
}

View file

@ -1,6 +1,6 @@
package buttondevteam.lib.player;
@PlayerClass(pluginname = "Chroma-Core")
@PlayerClass(pluginname = "ButtonCore") //TODO: Migrate
public final class TBMCPlayer extends TBMCPlayerBase {
}

View file

@ -1,21 +1,28 @@
package buttondevteam.lib.player;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import lombok.Getter;
import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.lib.TBMCCoreAPI;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer.class)
@TBMCPlayerEnforcer
public abstract class TBMCPlayerBase extends ChromaGamerBase {
protected UUID uuid;
@Getter
private final IHaveConfig config = new IHaveConfig(this::save);
private String pluginname;
protected TBMCPlayerBase() {
if (getClass().isAnnotationPresent(PlayerClass.class))
pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
else
throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
}
public UUID getUUID() {
if (uuid == null)
@ -23,61 +30,156 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase {
return uuid;
}
public final ConfigData<String> PlayerName = super.config.getData("PlayerName", "");
public PlayerData<String> PlayerName() {
return super.data(null);
}
/**
* Get player as a plugin player.
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@Override
protected <T> PlayerData<T> data(T def) {
return super.data(pluginname, def);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@Override
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(Class<T> cl, T def) {
return super.dataEnum(pluginname, cl, def);
}
/**
* Get player as a plugin player
*
* @param uuid The UUID of the player to get
* @param cl The type of the player
* @return The requested player object
*/
@SuppressWarnings("unchecked")
public static <T extends TBMCPlayerBase> T getPlayer(UUID uuid, Class<T> cl) {
var player = ChromaGamerBase.getUser(uuid.toString(), cl);
if (!player.getUUID().equals(uuid)) //It will be set from the filename because we check it for scheduling the uncache.
throw new IllegalStateException("Player UUID differs after converting from and to string...");
if (playermap.containsKey(uuid + "-" + cl.getSimpleName()))
return (T) playermap.get(uuid + "-" + cl.getSimpleName());
try {
T player;
if (playermap.containsKey(uuid + "-" + TBMCPlayer.class.getSimpleName())) {
player = cl.newInstance();
player.plugindata = playermap.get(uuid + "-" + TBMCPlayer.class.getSimpleName()).plugindata;
playermap.put(uuid + "-" + cl.getSimpleName(), player); // It will get removed on player quit
} else
player = ChromaGamerBase.getUser(uuid.toString(), cl);
player.uuid = uuid;
return player;
} catch (Exception e) {
TBMCCoreAPI.SendException(
"Failed to get player with UUID " + uuid + " and class " + cl.getSimpleName() + "!", e);
return null;
}
@Override
public void init() {
super.init();
String pluginname;
if (getClass().isAnnotationPresent(PlayerClass.class))
pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
else
throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
var playerData = commonUserData.getPlayerData();
var section = playerData.getConfigurationSection(pluginname);
if (section == null) section = playerData.createSection(pluginname);
config.reset(section);
}
@Override
protected void scheduleUncache() { //Don't schedule it, it will happen on quit - if the player is online
var p = Bukkit.getPlayer(getUUID());
if (p == null || !p.isOnline())
super.scheduleUncache();
}
/**
* This method returns a TBMC player from their name. See {@link Bukkit#getOfflinePlayer(String)}.
* Key: UUID-Class
*/
static final ConcurrentHashMap<String, TBMCPlayerBase> playermap = new ConcurrentHashMap<>();
/**
* Gets the TBMCPlayer object as a specific plugin player, keeping it's data<br>
* Make sure to use try-with-resources with this to save the data, as it may need to load the file
*
* @param cl The TBMCPlayer subclass
*/
public <T extends TBMCPlayerBase> T asPluginPlayer(Class<T> cl) {
return getPlayer(uuid, cl);
}
/**
* Only intended to use from ButtonCore
*/
public static void savePlayer(TBMCPlayerBase player) {
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerSaveEvent(player));
try {
player.close();
} catch (Exception e) {
new Exception("Failed to save player data for " + player.PlayerName().get(), e).printStackTrace();
}
}
/**
* Only intended to use from ButtonCore
*/
public static void joinPlayer(Player p) {
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class);
Bukkit.getLogger().info("Loaded player: " + player.PlayerName().get());
if (player.PlayerName().get() == null) {
player.PlayerName().set(p.getName());
Bukkit.getLogger().info("Player name saved: " + player.PlayerName().get());
} else if (!p.getName().equals(player.PlayerName().get())) {
TownyComponent.renameInTowny(player.PlayerName().get(), p.getName());
player.PlayerName().set(p.getName());
Bukkit.getLogger().info("Renamed to " + p.getName());
}
playermap.put(p.getUniqueId() + "-" + TBMCPlayer.class.getSimpleName(), player);
// Load in other plugins
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerLoadEvent(player));
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerJoinEvent(player, p));
player.save();
}
/**
* Only intended to use from ButtonCore
*/
public static void quitPlayer(Player p) {
final TBMCPlayerBase player = playermap.get(p.getUniqueId() + "-" + TBMCPlayer.class.getSimpleName());
player.save();
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerQuitEvent(player, p));
playermap.entrySet().removeIf(entry -> entry.getKey().startsWith(p.getUniqueId().toString()));
}
public static void savePlayers() {
playermap.values().forEach(p -> {
try {
p.close();
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while saving player " + p.PlayerName().get() + " (" + p.getFolder()
+ "/" + p.getFileName() + ")!", e);
}
});
}
/**
* This method returns a TBMC player from their name. Calling this method may return an offline player which will load it, therefore it's highly recommended to use {@link #close()} to unload the
* player data. Using try-with-resources may be the easiest way to achieve this. Example:
*
* <pre>
* {@code
* try(TBMCPlayer player = getFromName(p))
* {
* ...
* }
* </pre>
*
* @param name The player's name
* @return The {@link TBMCPlayer} object for the player
*/
@SuppressWarnings("deprecation")
public static <T extends TBMCPlayerBase> T getFromName(String name, Class<T> cl) {
@SuppressWarnings("deprecation")
OfflinePlayer p = Bukkit.getOfflinePlayer(name);
if (p != null)
return getPlayer(p.getUniqueId(), cl);
else
return null;
}
@Override
protected void save() {
Set<String> keys = commonUserData.getPlayerData().getKeys(false);
public void close() throws Exception {
Set<String> keys = plugindata.getKeys(false);
if (keys.size() > 1) // PlayerName is always saved, but we don't need a file for just that
super.save();
super.close();
}
}

View file

@ -0,0 +1,34 @@
package buttondevteam.lib.player;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerJoinEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
private final Player player_;
public TBMCPlayerJoinEvent(TBMCPlayerBase player, Player player_) {
this.player = player;
this.player_ = player_;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
public Player getPlayer() { // :P
return player_;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -0,0 +1,27 @@
package buttondevteam.lib.player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerLoadEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
public TBMCPlayerLoadEvent(TBMCPlayerBase player) {
this.player = player;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -0,0 +1,34 @@
package buttondevteam.lib.player;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerQuitEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
private final Player player_;
public TBMCPlayerQuitEvent(TBMCPlayerBase player, Player player_) {
this.player = player;
this.player_ = player_;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
public Player getPlayer() {
return player_;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -0,0 +1,27 @@
package buttondevteam.lib.player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerSaveEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
public TBMCPlayerSaveEvent(TBMCPlayerBase player) {
this.player = player;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -1,6 +1,6 @@
name: Chroma-Core
name: ChromaCore
main: buttondevteam.core.MainPlugin
version: '${noprefix.version}'
version: 1.0
author: NorbiPeti
commands:
updateplugin:
@ -9,6 +9,10 @@ commands:
description: Schedules a restart for a given time.
primerestart:
description: Restarts the server as soon as nobody is online.
randomtp:
description: teleport player to random location within world border. Every five players teleport to the same general area, and then a new general area is randomly selected for the next five players.
member:
description: Add or remove a member
component:
description: Enable or disable or list components
dontrunthiscmd:
@ -19,4 +23,3 @@ softdepend:
- Votifier
- Multiverse-Core
- Essentials
api-version: '1.13'

View file

@ -0,0 +1,82 @@
package buttondevteam.core;
import buttondevteam.core.TestPlayerClass.TestEnum;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayerBase;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.UUID;
public class PlayerDataTest extends TestCase {
public PlayerDataTest() {
super("Player data test");
}
/**
* @return the suite of tests being tested
*/
public static Test suite() {
return new TestSuite(PlayerDataTest.class);
}
public void testConfig() throws Exception {
TestPrepare.PrepareServer();
//FileUtils.deleteDirectory(new File(ChromaGamerBase.TBMC_PLAYERS_DIR));
File file = new File(ChromaGamerBase.TBMC_PLAYERS_DIR);
if (file.exists()) {
Files.walkFileTree(file.toPath(), new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e)
throws IOException {
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
// directory iteration failed
throw e;
}
}
});
}
UUID uuid = new UUID(0L, 0L);
try (TestPlayerClass p = TBMCPlayerBase.getPlayer(uuid, TestPlayerClass.class)) {
p.PlayerName().set("Test");
assertEquals("Test", p.PlayerName().get());
assertEquals(TestEnum.A, p.testenum().get());
assertEquals((short) 0, (short) p.TestShort().get());
assertFalse(p.TestBool().get());
p.testenum().set(TestEnum.B);
assertEquals(TestEnum.B, p.testenum().get());
p.TestShort().set((short) 5);
assertEquals((short) 5, (short) p.TestShort().get());
p.TestBool().set(true);
assertTrue(p.TestBool().get());
} catch (Exception e) {
throw e;
}
try (TestPlayerClass p = TBMCPlayerBase.getPlayer(uuid, TestPlayerClass.class)) {
assertEquals("Test", p.PlayerName().get());
assertEquals(TestEnum.B, p.testenum().get());
assertEquals((short) 5, (short) p.TestShort().get());
assertTrue(p.TestBool().get());
} catch (Exception e) {
throw e;
}
}
}

View file

@ -0,0 +1,25 @@
package buttondevteam.core;
import buttondevteam.lib.player.EnumPlayerData;
import buttondevteam.lib.player.PlayerClass;
import buttondevteam.lib.player.PlayerData;
import buttondevteam.lib.player.TBMCPlayerBase;
@PlayerClass(pluginname = "TestPlugin")
public class TestPlayerClass extends TBMCPlayerBase {
public EnumPlayerData<TestEnum> testenum() {
return dataEnum(TestEnum.class, TestEnum.A);
}
public enum TestEnum {
A, B
}
public PlayerData<Short> TestShort() {
return data((short) 0);
}
public PlayerData<Boolean> TestBool() {
return data(false);
}
}

19
CorePOM/pom.xml Normal file → Executable file
View file

@ -54,23 +54,6 @@
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration>
</plugin>
<!-- <plugin>
<groupId>io.github.1tchy</groupId>
<artifactId>variable-search-replace-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<text>${project.version}</text>
<search>^v</search>
<variableName>noprefix.version</variableName>
</configuration>
</execution>
</executions>
</plugin> -->
</plugins>
</pluginManagement>
</build>
@ -86,7 +69,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->

5
pom.xml Normal file → Executable file
View file

@ -9,7 +9,7 @@
<packaging>pom</packaging>
<version>master-SNAPSHOT</version>
<properties>
<lombok.version>1.18.12</lombok.version>
<lombok.version>1.18.10</lombok.version>
</properties>
<name>Chroma Parent</name>
@ -17,6 +17,7 @@
<module>CorePOM</module>
<module>Chroma-Core</module>
<module>ButtonProcessor</module>
<module>BuildConfigUpdater</module>
</modules>
<build>
@ -44,7 +45,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->