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_style=space
indent_size=2 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> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>1.32</version> <version>1.21</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -21,10 +21,13 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </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> <groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId> <artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>ButtonProcessor</name> <name>ButtonProcessor</name>
@ -33,13 +36,9 @@
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration> <configuration>
<compilerArgument>-proc:none</compilerArgument> <compilerArgument>-proc:none</compilerArgument>
<source>8</source>
<target>8</target>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>

View file

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

View file

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

View file

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

View file

@ -1,38 +1,17 @@
package buttondevteam.core; package buttondevteam.core;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.ICommand2MC; import buttondevteam.lib.chat.ICommand2MC;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import java.util.Arrays;
import java.util.Optional;
@CommandClass @CommandClass
public class ChromaCommand extends ICommand2MC { public class ChromaCommand extends ICommand2MC {
public ChromaCommand() { @Command2.Subcommand //TODO: Main permissions (groups) like 'mod'
getManager().addParamConverter(ButtonPlugin.class, name -> public void reload(CommandSender sender) {
(ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name)) if (MainPlugin.Instance.tryReloadConfig())
.filter(plugin -> plugin instanceof ButtonPlugin).orElse(null), sender.sendMessage("§bCore config reloaded.");
"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.");
else else
sender.sendMessage("§cFailed to reload config. Check console."); 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;
import buttondevteam.lib.chat.Command2.Subcommand; import buttondevteam.lib.chat.Command2.Subcommand;
import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.CommandClass;
import buttondevteam.lib.chat.CustomTabCompleteMethod;
import buttondevteam.lib.chat.ICommand2MC; import buttondevteam.lib.chat.ICommand2MC;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Stream;
@CommandClass(modOnly = true, helpText = { @CommandClass(modOnly = true, helpText = {
"Component command", "Component command",
@ -24,15 +20,14 @@ import java.util.stream.Stream;
}) })
public class ComponentCommand extends ICommand2MC { public class ComponentCommand extends ICommand2MC {
public ComponentCommand() { public ComponentCommand() {
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!", getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!");
() -> Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator);
} }
@Subcommand(helpText = { @Subcommand(helpText = {
"Enable component", "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 (plugin instanceof ButtonPlugin) {
if (!((ButtonPlugin) plugin).justReload()) { if (!((ButtonPlugin) plugin).justReload()) {
sender.sendMessage("§cCouldn't reload config, check console."); sender.sendMessage("§cCouldn't reload config, check console.");
@ -40,15 +35,15 @@ public class ComponentCommand extends ICommand2MC {
} }
} else } else
plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable 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 = { @Subcommand(helpText = {
"Disable component", "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) { public boolean disable(CommandSender sender, Plugin plugin, String component) {
return enable_disable(sender, plugin, component, false, permanent); return enable_disable(sender, plugin, component, false);
} }
@Subcommand(helpText = { @Subcommand(helpText = {
@ -62,41 +57,25 @@ public class ComponentCommand extends ICommand2MC {
return true; return true;
} }
@CustomTabCompleteMethod(param = "component", subcommand = {"enable", "disable"}) private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable) {
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) {
try { try {
val oc = getComponentOrError(plugin, component, sender); val oc = getComponentOrError(plugin, component, sender);
if (!oc.isPresent()) if (!oc.isPresent())
return true; return true;
Component.setComponentEnabled(oc.get(), enable); Component.setComponentEnabled(oc.get(), enable);
if (permanent) sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled.");
oc.get().shouldBeEnabled.set(enable);
sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled " + (permanent ? "permanently" : "temporarily") + ".");
} catch (Exception e) { } 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; 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) { 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()) if (!oc.isPresent())
sender.sendMessage("§cComponent not found!"); //^ Much simpler to solve in the new command system sender.sendMessage("§cComponent not found!"); //^ Much simpler to solve in the new command system
return oc; return oc;
} } //TODO: Tabcompletion for the new command system
} }

View file

@ -21,11 +21,11 @@ public final class ComponentManager {
*/ */
public static void enableComponents() { 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->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 { try {
Component.setComponentEnabled(c, true); Component.setComponentEnabled(c, true);
} catch (Exception | NoClassDefFoundError e) { } 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; componentsEnabled = true;

View file

@ -8,6 +8,9 @@ import buttondevteam.core.component.randomtp.RandomTPComponent;
import buttondevteam.core.component.restart.RestartComponent; import buttondevteam.core.component.restart.RestartComponent;
import buttondevteam.core.component.spawn.SpawnComponent; import buttondevteam.core.component.spawn.SpawnComponent;
import buttondevteam.core.component.towny.TownyComponent; import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.core.component.updater.PluginUpdater;
import buttondevteam.core.component.updater.PluginUpdaterComponent;
import buttondevteam.core.component.votifier.VotifierComponent;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
@ -23,12 +26,10 @@ import lombok.Setter;
import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission; import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.BlockCommandSender; import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
@ -37,10 +38,10 @@ import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.function.Supplier;
import java.util.logging.Logger; import java.util.logging.Logger;
public class MainPlugin extends ButtonPlugin { 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. * 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. * 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. * 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}"); "{channel}] <{name}> {message}");
}
/** /**
* Print some debug information. * 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 @Override
public void pluginEnable() { public void pluginEnable() {
// Logs "Plugin Enabled", registers commands
Instance = this; Instance = this;
PluginDescriptionFile pdf = getDescription(); PluginDescriptionFile pdf = getDescription();
logger = getLogger(); logger = getLogger();
if (!setupPermissions()) if (!setupPermissions())
throw new NullPointerException("No permission plugin found!"); 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."); getLogger().warning("No economy plugin found! Components using economy will not be registered.");
saveConfig(); saveConfig();
Component.registerComponent(this, new PluginUpdaterComponent());
Component.registerComponent(this, new RestartComponent()); Component.registerComponent(this, new RestartComponent());
//noinspection unchecked - needed for testing
Component.registerComponent(this, new ChannelComponent()); Component.registerComponent(this, new ChannelComponent());
Component.registerComponent(this, new RandomTPComponent()); Component.registerComponent(this, new RandomTPComponent());
Component.registerComponent(this, new MemberComponent()); Component.registerComponent(this, new MemberComponent());
@ -100,8 +114,8 @@ public class MainPlugin extends ButtonPlugin {
Component.registerComponent(this, new SpawnComponent()); Component.registerComponent(this, new SpawnComponent());
if (Bukkit.getPluginManager().isPluginEnabled("Towny")) //It fails to load the component class otherwise if (Bukkit.getPluginManager().isPluginEnabled("Towny")) //It fails to load the component class otherwise
Component.registerComponent(this, new TownyComponent()); Component.registerComponent(this, new TownyComponent());
/*if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null) if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new VotifierComponent(economy));*/ Component.registerComponent(this, new VotifierComponent(economy));
ComponentManager.enableComponents(); ComponentManager.enableComponents();
registerCommand(new ComponentCommand()); registerCommand(new ComponentCommand());
registerCommand(new ChromaCommand()); registerCommand(new ChromaCommand());
@ -111,7 +125,7 @@ public class MainPlugin extends ButtonPlugin {
? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks ? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks
ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player
? TBMCPlayer.getPlayer(((Player) sender).getUniqueId(), TBMCPlayer.class) : null)); //Players, has higher priority ? 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.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); //The /ooc ID has moved to the config
TBMCChatAPI.RegisterChatChannel( TBMCChatAPI.RegisterChatChannel(
Channel.AdminChat = new Channel("§cADMIN§f", Color.Red, "a", Channel.inGroupFilter(null))); 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("§aGREEN§f", Color.Green, "green"));
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue")); TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue"));
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple")); TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple"));
Supplier<Iterable<String>> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator; if (writePluginList().get()) {
getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier);
getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier);
if (writePluginList.get()) {
try { try {
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator); Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator);
} catch (IOException e) { } 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")) if (getServer().getPluginManager().isPluginEnabled("Essentials"))
ess = Essentials.getPlugin(Essentials.class); 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 @Override
public void pluginDisable() { public void pluginDisable() {
logger.info("Saving player data..."); logger.info("Saving player data...");
ChromaGamerBase.saveUsers(); TBMCPlayerBase.savePlayers();
logger.info("Player data saved."); 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() { private boolean setupPermissions() {

View file

@ -5,7 +5,6 @@ import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.chat.ChatMessage; import buttondevteam.lib.chat.ChatMessage;
import buttondevteam.lib.chat.Command2MCSender; import buttondevteam.lib.chat.Command2MCSender;
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayer;
import buttondevteam.lib.player.TBMCPlayerBase; import buttondevteam.lib.player.TBMCPlayerBase;
import lombok.val; import lombok.val;
@ -28,21 +27,12 @@ public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL) @EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerJoin(PlayerJoinEvent event) { public void OnPlayerJoin(PlayerJoinEvent event) {
var p = event.getPlayer(); TBMCPlayerBase.joinPlayer(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());
}
} }
@EventHandler(priority = EventPriority.NORMAL) @EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerLeave(PlayerQuitEvent event) { public void OnPlayerLeave(PlayerQuitEvent event) {
TBMCPlayerBase.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class).uncache(); TBMCPlayerBase.quitPlayer(event.getPlayer());
} }
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
@ -52,7 +42,7 @@ public class PlayerListener implements Listener {
if (Arrays.stream(event.getExceptions()).anyMatch("Minecraft"::equalsIgnoreCase)) if (Arrays.stream(event.getExceptions()).anyMatch("Minecraft"::equalsIgnoreCase))
return; return;
Bukkit.getOnlinePlayers().stream().filter(event::shouldSendTo) 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 @EventHandler
@ -68,9 +58,7 @@ public class PlayerListener implements Listener {
private void handlePreprocess(CommandSender sender, String message, Cancellable event) { private void handlePreprocess(CommandSender sender, String message, Cancellable event) {
if (event.isCancelled()) return; if (event.isCancelled()) return;
val cg = ChromaGamerBase.getFromSender(sender); val ev = new TBMCCommandPreprocessEvent(sender, message);
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);
Bukkit.getPluginManager().callEvent(ev); Bukkit.getPluginManager().callEvent(ev);
if (ev.isCancelled()) if (ev.isCancelled())
event.setCancelled(true); //Cancel the original event event.setCancelled(true); //Cancel the original event
@ -80,9 +68,9 @@ public class PlayerListener implements Listener {
public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) { public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) {
if (event.isCancelled()) return; if (event.isCancelled()) return;
try { 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) { } 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; return;
if (!MainPlugin.Instance.isChatHandlerEnabled()) return; if (!MainPlugin.Instance.isChatHandlerEnabled()) return;
if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages
var channel = event.getChannel(); String msg = MainPlugin.Instance.chatFormat().get()
String msg = MainPlugin.Instance.chatFormat.get() .replace("{channel}", event.getChannel().DisplayName().get())
.replace("{channel}", channel.DisplayName.get())
.replace("{origin}", event.getOrigin().substring(0, 1)) .replace("{origin}", event.getOrigin().substring(0, 1))
.replace("{name}", ChromaUtils.getDisplayName(event.getSender())) .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()) for (Player player : Bukkit.getOnlinePlayers())
if (event.shouldSendTo(player)) if (event.shouldSendTo(player))
player.sendMessage(msg); player.sendMessage(msg);

View file

@ -2,7 +2,6 @@ package buttondevteam.core;
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel;
import buttondevteam.core.component.channel.ChannelComponent; import buttondevteam.core.component.channel.ChannelComponent;
import buttondevteam.lib.ChromaUtils;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.Color;
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCChatAPI;
@ -20,9 +19,7 @@ import java.util.Collections;
import java.util.logging.Logger; import java.util.logging.Logger;
public class TestPrepare { public class TestPrepare {
public static void PrepareServer() { 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>() { Bukkit.setServer(Mockito.mock(Server.class, new Answer<Object>() {
@Override @Override
@ -44,6 +41,7 @@ public class TestPrepare {
return cl.isAssignableFrom(invocation.getMethod().getReturnType()); return cl.isAssignableFrom(invocation.getMethod().getReturnType());
} }
})); }));
//noinspection unchecked
Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent()); Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent());
TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); 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.core.MainPlugin;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.Color;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@ -21,9 +20,6 @@ import java.util.function.Function;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Stream; import java.util.stream.Stream;
/**
* Represents a chat channel. May only be instantiated after the channel component is registered.
*/
public class Channel { 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} * 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 String defDisplayName;
private Color defColor; 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 * 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 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 * Filters both the sender and the targets
*/ */
@ -79,12 +89,6 @@ public class Channel {
defColor = color; defColor = color;
ID = command; ID = command;
this.filteranderrormsg = filteranderrormsg; 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; defColor = color;
ID = command; ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s); 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() { public boolean isGlobal() {
@ -157,7 +148,7 @@ public class Channel {
* @return Only the enabled channels * @return Only the enabled channels
*/ */
public static Stream<Channel> getChannels() { 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) { public static void RegisterChannel(Channel channel) {
if (!channel.isGlobal() && !ComponentManager.isEnabled(ChannelComponent.class)) if (!channel.isGlobal() && !ComponentManager.isEnabled(ChannelComponent.class))
return; //Allow registering the global chat (and I guess other chats like the RP chat) 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); channels.add(channel);
component.registerChannelCommand(channel);
Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> Bukkit.getPluginManager().callEvent(new ChatChannelRegisterEvent(channel))); // Wait for server start 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; package buttondevteam.core.component.channel;
import buttondevteam.lib.ChromaUtils;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.chat.*;
import buttondevteam.lib.player.ChromaGamerBase;
import lombok.RequiredArgsConstructor;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
/** /**
* Manages chat channels. If disabled, only global channels will be registered. * 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; static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave;
@Override @Override
@ -33,51 +29,6 @@ public class ChannelComponent extends Component<JavaPlugin> {
@Override @Override
protected void disable() { 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; private final MemberComponent component;
public MemberCommand(MemberComponent component) { public MemberCommand(MemberComponent component) {
getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!");
this.component = component; this.component = component;
} }
@ -38,8 +39,8 @@ public class MemberCommand extends ICommand2MC {
sender.sendMessage("§cCannot find player or haven't played before."); sender.sendMessage("§cCannot find player or haven't played before.");
return; return;
} }
if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup.get()) if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup().get())
: MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup.get())) : MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup().get()))
sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!"); sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!");
else else
sender.sendMessage("§cFailed to " + (add ? "add" : "remove") + " " + op.getName() + " as a member!"); 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 * 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 * 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 * 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; private AbstractMap.SimpleEntry<Statistic, Integer> playtime;
@ -63,22 +69,22 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
public Boolean addPlayerAsMember(Player player) { public Boolean addPlayerAsMember(Player player) {
try { try {
if (permission.playerAddGroup(null, player, memberGroup.get())) { if (permission.playerAddGroup(null, player, memberGroup().get())) {
player.sendMessage("§bYou are a member now!"); 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; return true;
} else { } 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; return false;
} }
} catch (UnsupportedOperationException e) { } 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; return null;
} }
} }
public boolean checkNotMember(Player player) { 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) { public boolean checkRegTime(Player player) {
@ -86,14 +92,14 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
} }
public boolean checkPlayTime(Player player) { public boolean checkPlayTime(Player player) {
return getPlayTime(player) > playtime.getValue() * playedHours.get(); return getPlayTime(player) > playtime.getValue() * playedHours().get();
} }
/** /**
* Returns milliseconds * Returns milliseconds
*/ */
public long getRegTime(Player player) { 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())) if (date.isAfter(Instant.now()))
return date.toEpochMilli() - Instant.now().toEpochMilli(); return date.toEpochMilli() - Instant.now().toEpochMilli();
return -1; return -1;
@ -107,7 +113,7 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
* Returns hours * Returns hours
*/ */
public double getPlayTime(Player player) { 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; if (pt < 0) return -1;
return pt; return pt;
} }

View file

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

View file

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

View file

@ -4,8 +4,6 @@ import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel;
import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.TBMCSystemChatEvent;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ComponentMetadata;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.chat.IFakePlayer; import buttondevteam.lib.chat.IFakePlayer;
import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCChatAPI;
import lombok.Getter; import lombok.Getter;
@ -15,29 +13,16 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent; 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). * Provides commands such as /schrestart (restart after a countdown) and /primerestart (restart when nobody is online)
* Also can automatically restart at a given time.
*/ */
@ComponentMetadata(enabledByDefault = false)
public class RestartComponent extends Component<MainPlugin> implements Listener { public class RestartComponent extends Component<MainPlugin> implements Listener {
@Override @Override
public void enable() { public void enable() {
var scheduledRestartCommand = new ScheduledRestartCommand(this); registerCommand(new ScheduledRestartCommand(this));
registerCommand(scheduledRestartCommand);
registerCommand(new PrimeRestartCommand(this)); registerCommand(new PrimeRestartCommand(this));
registerListener(this); registerListener(this);
restartBroadcast = TBMCSystemChatEvent.BroadcastTarget.add("restartCountdown"); 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 @Override
@ -45,28 +30,10 @@ public class RestartComponent extends Component<MainPlugin> implements Listener
TBMCSystemChatEvent.BroadcastTarget.remove(restartBroadcast); 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; private long lasttime = 0;
@Getter @Getter
private TBMCSystemChatEvent.BroadcastTarget restartBroadcast; 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 @EventHandler
public void onPlayerLeave(PlayerQuitEvent event) { public void onPlayerLeave(PlayerQuitEvent event) {
if (PrimeRestartCommand.isPlsrestart() if (PrimeRestartCommand.isPlsrestart()
@ -76,7 +43,7 @@ public class RestartComponent extends Component<MainPlugin> implements Listener
if (PrimeRestartCommand.isLoud()) if (PrimeRestartCommand.isLoud())
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online anymore. Restarting.", restartBroadcast); TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online anymore. Restarting.", restartBroadcast);
Bukkit.spigot().restart(); 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(); lasttime = System.nanoTime();
if (PrimeRestartCommand.isLoud()) if (PrimeRestartCommand.isLoud())
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", restartBroadcast); TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", restartBroadcast);

View file

@ -57,8 +57,8 @@ public class ScheduledRestartCommand extends ICommand2MC {
restartbar.getPlayers().forEach(p -> restartbar.removePlayer(p)); restartbar.getPlayers().forEach(p -> restartbar.removePlayer(p));
Bukkit.spigot().restart(); Bukkit.spigot().restart();
} }
if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size() > 0) 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.setProgress(restartCounter / (double) restarttime);
restartbar.setTitle(String.format("Server restart in %.2f", restartCounter / 20f)); restartbar.setTitle(String.format("Server restart in %.2f", restartCounter / 20f));
restartCounter--; restartCounter--;

View file

@ -2,7 +2,6 @@ package buttondevteam.core.component.spawn;
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ComponentMetadata;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.Command2;
import buttondevteam.lib.chat.CommandClass; 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. * 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 { public class SpawnComponent extends Component<MainPlugin> implements PluginMessageListener {
@Override @Override
protected void enable() { protected void enable() {
registerCommand(new SpawnCommand()); registerCommand(new SpawnCommand());
if (targetServer.get().length() == 0) { if (targetServer().get().length() == 0) {
spawnloc = MultiverseCore.getPlugin(MultiverseCore.class).getMVWorldManager().getFirstSpawnWorld() spawnloc = MultiverseCore.getPlugin(MultiverseCore.class).getMVWorldManager().getFirstSpawnWorld()
.getSpawnLocation(); .getSpawnLocation();
} }
@ -49,13 +47,14 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
if (!channel.equals("BungeeCord")) { if (!channel.equals("BungeeCord")) {
return; return;
} }
if (targetServer.get().length() != 0) if (targetServer().get().length() != 0)
return; return;
ByteArrayDataInput in = ByteStreams.newDataInput(message); ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF(); String subchannel = in.readUTF();
if ("ChromaCore-Spawn".equals(subchannel)) { if ("ChromaCore-Spawn".equals(subchannel)) {
// Use the code sample in the 'Response' sections below to read // Use the code sample in the 'Response' sections below to read
// the data. // the data.
System.out.println("Heh nice");
short len = in.readShort(); short len = in.readShort();
byte[] msgbytes = new byte[len]; byte[] msgbytes = new byte[len];
in.readFully(msgbytes); 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. * 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; private Location spawnloc;
@ -90,7 +91,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
@SuppressWarnings("UnstableApiUsage") @SuppressWarnings("UnstableApiUsage")
@Command2.Subcommand @Command2.Subcommand
public void def(Player player) { public void def(Player player) {
if (targetServer.get().length() == 0) { if (targetServer().get().length() == 0) {
player.sendMessage("§bTeleporting to spawn..."); player.sendMessage("§bTeleporting to spawn...");
try { try {
if (MainPlugin.ess != null) if (MainPlugin.ess != null)
@ -105,7 +106,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
} }
ByteArrayDataOutput out = ByteStreams.newDataOutput(); ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect"); out.writeUTF("Connect");
out.writeUTF(targetServer.get()); out.writeUTF(targetServer().get());
player.sendPluginMessage(getPlugin(), "BungeeCord", out.toByteArray()); 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; package buttondevteam.core.component.towny;
import buttondevteam.core.ComponentManager;
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.Component; 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> { public class TownyComponent extends Component<MainPlugin> {
@Override @Override
protected void enable() { protected void enable() {
registerCommand(new RemoveResidentsCommand());
} }
@Override @Override
protected void disable() { 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. * 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(); String getFancyFullName();
} }
@ -77,7 +77,7 @@ public final class ChromaUtils {
* *
* @param what What to do * @param what What to do
* @param def Default if async * @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) { public static <T> T doItAsync(Supplier<T> what, T def) {
if (Bukkit.isPrimaryThread()) if (Bukkit.isPrimaryThread())
@ -86,16 +86,4 @@ public final class ChromaUtils {
return what.get(); return what.get();
return def; 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; package buttondevteam.lib;
import org.bukkit.Bukkit;
import org.bukkit.event.Event; import org.bukkit.event.Event;
class EventExceptionCoreHandler extends EventExceptionHandler { class EventExceptionCoreHandler extends EventExceptionHandler {
@Override @Override
public boolean handle(Throwable ex, Event event) { 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; return true;
} }

View file

@ -30,6 +30,7 @@ public class TBMCChatEvent extends TBMCChatEventBase {
private boolean isIgnoreSenderPermissions() { private boolean isIgnoreSenderPermissions() {
return cm.getPermCheck() != cm.getSender(); return cm.getPermCheck() != cm.getSender();
} }
// TODO: Message object with data?
/** /**
* This will allow the sender of the message if {@link #isIgnoreSenderPermissions()} is true. * 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 @Nullable
public String getGroupID(CommandSender sender) { 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); super(true);
this.sender = sender; this.sender = sender;
this.channel = channel; 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 @Override
public HandlerList getHandlers() { public HandlerList getHandlers() {
return handlers; return handlers;

View file

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

View file

@ -1,14 +1,14 @@
package buttondevteam.lib; package buttondevteam.lib;
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component; import buttondevteam.core.component.updater.PluginUpdater;
import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.potato.DebugPotato; import buttondevteam.lib.potato.DebugPotato;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -16,8 +16,6 @@ import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.*; import java.util.*;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class TBMCCoreAPI { public class TBMCCoreAPI {
static final List<String> coders = new ArrayList<String>() { 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 { public static String DownloadString(String urlstr) throws IOException {
URL url = new URL(urlstr); URL url = new URL(urlstr);
URLConnection con = url.openConnection(); 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 sourcemsg A message that is shown at the top of the exception (before the exception's message)
* @param e The exception to send * @param e The exception to send
*/ */
public static void SendException(String sourcemsg, Throwable e, Component<?> component) { public static void SendException(String sourcemsg, Throwable e) {
SendException(sourcemsg, e, false, component::logWarn); SendException(sourcemsg, e, false);
} }
/** public static void SendException(String sourcemsg, Throwable e, boolean debugPotato) {
* 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) {
try { try {
SendUnsentExceptions(); SendUnsentExceptions();
TBMCExceptionEvent event = new TBMCExceptionEvent(sourcemsg, e); TBMCExceptionEvent event = new TBMCExceptionEvent(sourcemsg, e);
@ -77,7 +87,7 @@ public class TBMCCoreAPI {
if (!event.isHandled()) if (!event.isHandled())
exceptionsToSend.put(sourcemsg, e); exceptionsToSend.put(sourcemsg, e);
} }
logWarn.accept(sourcemsg); Bukkit.getLogger().warning(sourcemsg);
e.printStackTrace(); e.printStackTrace();
if (debugPotato) { if (debugPotato) {
List<Player> devsOnline = new ArrayList<>(); List<Player> devsOnline = new ArrayList<>();
@ -118,7 +128,6 @@ public class TBMCCoreAPI {
} }
private static EventExceptionCoreHandler eventExceptionCoreHandler; private static EventExceptionCoreHandler eventExceptionCoreHandler;
/** /**
* Registers Bukkit events, handling the exceptions occurring in those events * Registers Bukkit events, handling the exceptions occurring in those events
* *
@ -130,8 +139,8 @@ public class TBMCCoreAPI {
EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler); EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler);
} }
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass, Supplier<T> constructor) { public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass) {
ChromaGamerBase.RegisterPluginUserClass(userclass, constructor); ChromaGamerBase.RegisterPluginUserClass(userclass);
} }
/** /**
@ -171,6 +180,6 @@ public class TBMCCoreAPI {
public static boolean IsTestServer() { public static boolean IsTestServer() {
if (MainPlugin.Instance == null) return true; 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) @HasConfig(global = true)
public abstract class ButtonPlugin extends JavaPlugin { public abstract class ButtonPlugin extends JavaPlugin {
@Getter //Needs to be static as we don't know the plugin when a command is handled @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) @Getter(AccessLevel.PROTECTED)
private final IHaveConfig iConfig = new IHaveConfig(this::saveConfig); private IHaveConfig iConfig;
private CommentedConfiguration yaml; private CommentedConfiguration yaml;
@Getter(AccessLevel.PROTECTED) @Getter(AccessLevel.PROTECTED)
private IHaveConfig data; //TODO 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 * Used to unregister components in the right order - and to reload configs
*/ */
@Getter @Getter
private final Stack<Component<?>> componentStack = new Stack<>(); private Stack<Component<?>> componentStack = new Stack<>();
;
protected abstract void pluginEnable(); protected abstract void pluginEnable();
@ -59,7 +60,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
try { try {
pluginEnable(); pluginEnable();
} catch (Exception e) { } 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) if (configGenAllowed(this)) //If it's not disabled (by default it's not)
IHaveConfig.pregenConfig(this, null); IHaveConfig.pregenConfig(this, null);
@ -71,7 +72,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
return false; return false;
var section = config.getConfigurationSection("global"); var section = config.getConfigurationSection("global");
if (section == null) section = config.createSection("global"); if (section == null) section = config.createSection("global");
iConfig.reset(section); iConfig = new IHaveConfig(section, this::saveConfig);
return true; return true;
} }
@ -83,9 +84,10 @@ public abstract class ButtonPlugin extends JavaPlugin {
pluginDisable(); pluginDisable();
if (ConfigData.saveNow(getConfig())) if (ConfigData.saveNow(getConfig()))
getLogger().info("Saved configuration changes."); getLogger().info("Saved configuration changes.");
iConfig = null; //Clearing the hashmap is not enough, we need to update the section as well
getCommand2MC().unregisterCommands(this); getCommand2MC().unregisterCommands(this);
} catch (Exception e) { } 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); var yc = YamlConfiguration.loadConfiguration(res);
for (var kv : yc.getValues(true).entrySet()) for (var kv : yc.getValues(true).entrySet())
if (kv.getValue() instanceof String) if (kv.getValue() instanceof String)
yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""), yaml.addComment(kv.getKey(), Arrays.stream(((String) kv.getValue()).split("\n"))
Arrays.stream(((String) kv.getValue()).split("\n"))
.map(str -> "# " + str.trim()).toArray(String[]::new)); .map(str -> "# " + str.trim()).toArray(String[]::new));
return true; return true;
} }
@ -133,8 +134,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
@Override @Override
public FileConfiguration getConfig() { public FileConfiguration getConfig() {
if (yaml == null) if (yaml == null)
justReload(); justReload(); //TODO: If it fails to load, it'll probably throw an NPE
if (yaml == null) return new YamlConfiguration(); //Return a temporary instance
return yaml; return yaml;
} }
@ -144,7 +144,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
if (yaml != null) if (yaml != null)
yaml.save(); yaml.save();
} catch (Exception e) { } 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.File;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.HashMap; import java.util.HashMap;
@ -20,7 +19,7 @@ import java.util.HashMap;
* *
* @author dumptruckman &amp; Articdive * @author dumptruckman &amp; Articdive
*/ */
public class CommentedConfiguration extends YamlConfiguration { public class CommentedConfiguration extends YamlConfiguration { //TODO: Remove FileMgmt dependency
private HashMap<String, String> comments; private HashMap<String, String> comments;
private File file; private File file;
@ -175,7 +174,7 @@ public class CommentedConfiguration extends YamlConfiguration {
while (newContents.toString().startsWith(" " + System.getProperty("line.separator"))) { while (newContents.toString().startsWith(" " + System.getProperty("line.separator"))) {
newContents = new StringBuilder(newContents.toString().replaceFirst(" " + 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 @Getter
@NonNull @NonNull
private TP plugin; private TP plugin;
private @Getter final IHaveConfig config = new IHaveConfig(null); @NonNull
private @Getter
IHaveConfig config;
private @Getter IHaveConfig data; //TODO private @Getter IHaveConfig data; //TODO
public final ConfigData<Boolean> shouldBeEnabled = config.getData("enabled", public final ConfigData<Boolean> shouldBeEnabled() {
Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true)); 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> * 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 * @param component The component to unregister
* @return Whether the component is unregistered successfully (it also got disabled) * @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); return registerUnregisterComponent(plugin, component, false);
} }
@ -76,22 +79,21 @@ public abstract class Component<TP extends JavaPlugin> {
} }
if (register) { if (register) {
if (components.containsKey(component.getClass())) { 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; return false;
} }
component.plugin = plugin; component.plugin = plugin;
component.config.setSaveAction(plugin::saveConfig);
updateConfig(plugin, component); updateConfig(plugin, component);
component.register(plugin); component.register(plugin);
components.put(component.getClass(), component); components.put(component.getClass(), component);
if (plugin instanceof ButtonPlugin) if (plugin instanceof ButtonPlugin)
((ButtonPlugin) plugin).getComponentStack().push(component); ((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 try { //Enable components registered after the previous ones getting enabled
setComponentEnabled(component, true); setComponentEnabled(component, true);
return true; return true;
} catch (Exception | NoClassDefFoundError e) { } 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; return true;
} }
} }
@ -102,7 +104,7 @@ public abstract class Component<TP extends JavaPlugin> {
try { try {
setComponentEnabled(component, false); setComponentEnabled(component, false);
} catch (Exception | NoClassDefFoundError e) { } 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 return false; //If failed to disable, won't unregister either
} }
} }
@ -111,56 +113,47 @@ public abstract class Component<TP extends JavaPlugin> {
} }
return true; return true;
} catch (Exception e) { } 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; 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 component The component to register
* @param enabled Whether it's enabled or not
*/ */
public static void setComponentEnabled(Component<?> component, boolean enabled) throws UnregisteredComponentException { public static void setComponentEnabled(Component<?> component, boolean enabled) throws UnregisteredComponentException {
if (!components.containsKey(component.getClass())) if (!components.containsKey(component.getClass()))
throw new UnregisteredComponentException(component); throw new UnregisteredComponentException(component);
if (component.enabled == enabled) return; //Don't do anything if (component.enabled == enabled) return; //Don't do anything
if (component.enabled = enabled) { if (component.enabled = enabled) {
try { //System.out.println("Updating config for "+component.getClassName());
updateConfig(component.getPlugin(), component); updateConfig(component.getPlugin(), component);
//System.out.println("Enabling "+component.getClassName());
component.enable(); component.enable();
if (ButtonPlugin.configGenAllowed(component)) { if (ButtonPlugin.configGenAllowed(component)) {
//System.out.println("Pregenning config for "+component.getClassName());
IHaveConfig.pregenConfig(component, null); IHaveConfig.pregenConfig(component, null);
} }
} catch (Exception e) { //System.out.println("Done enabling "+component.getClassName());
try { //Automatically disable components that fail to enable properly
setComponentEnabled(component, false);
throw e;
} catch (Exception ex) {
Throwable t = ex;
for (var th = t; th != null; th = th.getCause())
t = th; //Set if not null
if (t != e)
t.initCause(e);
throw ex;
}
}
} else { } else {
component.disable(); component.disable();
ButtonPlugin.getCommand2MC().unregisterCommands(component); 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 if (plugin.getConfig() != null) { //Production
var compconf = plugin.getConfig().getConfigurationSection("components"); var compconf = plugin.getConfig().getConfigurationSection("components");
if (compconf == null) compconf = plugin.getConfig().createSection("components"); if (compconf == null) compconf = plugin.getConfig().createSection("components");
var configSect = compconf.getConfigurationSection(component.getClassName()); var configSect = compconf.getConfigurationSection(component.getClassName());
if (configSect == null) if (configSect == null)
configSect = compconf.createSection(component.getClassName()); configSect = compconf.createSection(component.getClassName());
component.config.reset(configSect); component.config = new IHaveConfig(configSect, plugin::saveConfig);
} //Testing: it's already set } else //Testing
component.config = new IHaveConfig(null, plugin::saveConfig);
} }
/** /**
@ -173,21 +166,13 @@ public abstract class Component<TP extends JavaPlugin> {
return Collections.unmodifiableMap(components); 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. * 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. * This gets fired when the plugin is enabled. Use {@link #enable()} to register commands and such.
* *
* @param plugin Plugin object * @param plugin Plugin object
*/ */
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused", "WeakerAccess"})
protected void register(JavaPlugin plugin) { protected void register(JavaPlugin plugin) {
} }
@ -198,7 +183,7 @@ public abstract class Component<TP extends JavaPlugin> {
* *
* @param plugin Plugin object * @param plugin Plugin object
*/ */
@SuppressWarnings({"unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
protected void unregister(JavaPlugin plugin) { protected void unregister(JavaPlugin plugin) {
} }
@ -252,15 +237,10 @@ public abstract class Component<TP extends JavaPlugin> {
var cs = c.getConfigurationSection(key); var cs = c.getConfigurationSection(key);
if (cs == null) cs = c.createSection(key); if (cs == null) cs = c.createSection(key);
val res = cs.getValues(false).entrySet().stream().filter(e -> e.getValue() instanceof ConfigurationSection) val res = cs.getValues(false).entrySet().stream().filter(e -> e.getValue() instanceof ConfigurationSection)
.collect(Collectors.toMap(Map.Entry::getKey, kv -> { .collect(Collectors.toMap(Map.Entry::getKey, kv -> new IHaveConfig((ConfigurationSection) kv.getValue(), getPlugin()::saveConfig)));
var conf = new IHaveConfig(getPlugin()::saveConfig);
conf.reset((ConfigurationSection) kv.getValue());
return conf;
}));
if (res.size() == 0) { if (res.size() == 0) {
for (val entry : defaultProvider.entrySet()) { for (val entry : defaultProvider.entrySet()) {
val conf = new IHaveConfig(getPlugin()::saveConfig); val conf = new IHaveConfig(cs.createSection(entry.getKey()), getPlugin()::saveConfig);
conf.reset(cs.createSection(entry.getKey()));
entry.getValue().accept(conf); entry.getValue().accept(conf);
res.put(entry.getKey(), conf); res.put(entry.getKey(), conf);
} }

View file

@ -2,9 +2,13 @@ package buttondevteam.lib.architecture;
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin;
import buttondevteam.lib.ChromaUtils; 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.Bukkit;
import org.bukkit.configuration.Configuration; import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitTask; 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 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. * 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> { public class ConfigData<T> {
private static final HashMap<Configuration, SaveTask> saveTasks = new HashMap<>(); private static final HashMap<Configuration, SaveTask> saveTasks = new HashMap<>();
/** /**
* May be null for testing * May be null for testing
*/ */
private IHaveConfig config; private final ConfigurationSection config;
@Getter @Getter
@Setter(AccessLevel.PACKAGE) @Setter(AccessLevel.PACKAGE)
private String path; private String path;
protected final T def; protected final T def;
private final Object primitiveDef; private final Object primitiveDef;
private final Runnable saveAction;
/** /**
* The parameter is of a primitive type as returned by {@link YamlConfiguration#get(String)} * 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 * 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 * The config value should not change outside this instance
*/ */
private T value; private T value;
ConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) { //This constructor is needed because it sets the getter and setter
if (def == null) { ConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter, Runnable saveAction) {
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.config = config; this.config = config;
this.path = path; this.path = path;
this.def = def; this.def = def;
this.primitiveDef = primitiveDef; this.primitiveDef = primitiveDef;
this.getter = getter; this.getter = getter;
this.setter = setter; 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 @Override
public String toString() { public String toString() {
return "ConfigData{" + "path='" + path + '\'' + ", value=" + value + '}'; return "ConfigData{" +
} "path='" + path + '\'' +
", value=" + value +
void reset() { '}';
value = null;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public T get() { public T get() {
if (value != null) return value; //Speed things up if (value != null) return value; //Speed things up
var config = this.config.getConfig();
Object val; Object val;
if (config == null || !config.isSet(path)) { //Call set() if config == null if (config == null || !config.isSet(path)) { //Call set() if config == null
val = primitiveDef; val = primitiveDef;
@ -118,27 +119,21 @@ public class ConfigData<T> {
if (setter != null && value != null) if (setter != null && value != null)
val = setter.apply(value); val = setter.apply(value);
else val = value; else val = value;
if (config.getConfig() != null) if (config != null)
setInternal(val); setInternal(val);
this.value = value; this.value = value;
} }
private void setInternal(Object val) { private void setInternal(Object val) {
config.getConfig().set(path, val); config.set(path, val);
signalChange(config); if (!saveTasks.containsKey(config.getRoot())) {
}
static void signalChange(IHaveConfig config) {
var cc = config.getConfig();
var sa = config.getSaveAction();
if (!saveTasks.containsKey(cc.getRoot())) {
synchronized (saveTasks) { 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) { synchronized (saveTasks) {
saveTasks.remove(cc.getRoot()); saveTasks.remove(config.getRoot());
sa.run(); saveAction.run();
} }
}, 100), sa)); }, 100), saveAction));
} }
} }
} }
@ -161,92 +156,4 @@ public class ConfigData<T> {
} }
return false; 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.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.java.JavaPlugin;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
@ -21,41 +18,22 @@ import java.util.stream.Collectors;
*/ */
public final class IHaveConfig { public final class IHaveConfig {
private final HashMap<String, ConfigData<?>> datamap = new HashMap<>(); private final HashMap<String, ConfigData<?>> datamap = new HashMap<>();
/**
* Returns the Bukkit ConfigurationSection. Use {@link #signalChange()} after changing it.
*/
@Getter @Getter
private ConfigurationSection config; private ConfigurationSection config;
@Getter private final Runnable saveAction;
@Setter
private Runnable saveAction;
/** /**
* May be used in testing. * 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; 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. * This method overload should only be used with primitves or String.
*
* @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.
* *
* @param path The path in config to use * @param path The path in config to use
* @param def The value to use by default * @param def The value to use by default
@ -65,7 +43,7 @@ public final class IHaveConfig {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> ConfigData<T> getData(String path, T def) { public <T> ConfigData<T> getData(String path, T def) {
ConfigData<?> data = datamap.get(path); 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; 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) { public <T> ConfigData<T> getData(String path, T def, Function<Object, T> getter, Function<T, Object> setter) {
ConfigData<?> data = datamap.get(path); ConfigData<?> data = datamap.get(path);
if (data == null) 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; 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) { public <T> ConfigData<T> getDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
ConfigData<?> data = datamap.get(path); ConfigData<?> data = datamap.get(path);
if (data == null) 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; 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) { public <T> ReadOnlyConfigData<T> getReadOnlyDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
ConfigData<?> data = datamap.get(path); ConfigData<?> data = datamap.get(path);
if (data == null) 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; return (ReadOnlyConfigData<T>) data;
} }
@ -136,7 +114,7 @@ public final class IHaveConfig {
ConfigData<?> data = datamap.get(path); ConfigData<?> data = datamap.get(path);
if (data == null) { if (data == null) {
val defval = def.get(); 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; return (ConfigData<T>) data;
} }
@ -156,7 +134,7 @@ public final class IHaveConfig {
ConfigData<?> data = datamap.get(path); ConfigData<?> data = datamap.get(path);
if (data == null) { if (data == null) {
val defval = def.get(); 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; return (ConfigData<T>) data;
} }
@ -172,25 +150,10 @@ public final class IHaveConfig {
public <T> ListConfigData<T> getListData(String path) { public <T> ListConfigData<T> getListData(String path) {
ConfigData<?> data = datamap.get(path); ConfigData<?> data = datamap.get(path);
if (data == null) 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; 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. * Generates the config YAML.
* *
@ -201,13 +164,6 @@ public final class IHaveConfig {
val ms = obj.getClass().getDeclaredMethods(); val ms = obj.getClass().getDeclaredMethods();
for (val m : ms) { for (val m : ms) {
if (!m.getReturnType().getName().equals(ConfigData.class.getName())) continue; 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 { try {
m.setAccessible(true); m.setAccessible(true);
List<ConfigData<?>> configList; List<ConfigData<?>> configList;
@ -220,36 +176,24 @@ public final class IHaveConfig {
try { try {
return (ConfigData<?>) m.invoke(obj, kv.getValue()); return (ConfigData<?>) m.invoke(obj, kv.getValue());
} catch (IllegalAccessException | InvocationTargetException e) { } catch (IllegalAccessException | InvocationTargetException e) {
String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!"; TBMCCoreAPI.SendException("Failed to pregenerate " + m.getName() + " for " + obj + " using config " + kv.getKey() + "!", e);
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);
return null; return null;
} }
}).filter(Objects::nonNull).collect(Collectors.toList()); }).filter(Objects::nonNull).collect(Collectors.toList());
} else { } else {
if (TBMCCoreAPI.IsTestServer()) 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; continue;
} }
for (val c : configList) { for (val c : configList) {
if (c.getPath().length() == 0) if (c.getPath().length() == 0)
c.setPath(mName); c.setPath(m.getName());
else if (!c.getPath().equals(mName)) else if (!c.getPath().equals(m.getName()))
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + mName); 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 c.get(); //Saves the default value if needed - also checks validity
} }
} catch (Exception e) { } catch (Exception e) {
String msg = "Failed to pregenerate " + mName + " for " + obj + "!"; TBMCCoreAPI.SendException("Failed to pregenerate " + m.getName() + " for " + obj + "!", e);
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);
} }
} }
} }

View file

@ -1,6 +1,7 @@
package buttondevteam.lib.architecture; package buttondevteam.lib.architecture;
import lombok.val; import lombok.val;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
@ -11,12 +12,12 @@ import java.util.function.UnaryOperator;
public class ListConfigData<T> extends ConfigData<ListConfigData.List<T>> { public class ListConfigData<T> extends ConfigData<ListConfigData.List<T>> {
@SuppressWarnings("unchecked") @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 -> { super(config, path, def, new ArrayList<>(def), list -> {
var l = new List<>((ArrayList<T>) list); var l = new List<>((ArrayList<T>) list);
l.listConfig = def.listConfig; l.listConfig = def.listConfig;
return l; 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 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; package buttondevteam.lib.architecture;
import org.bukkit.configuration.ConfigurationSection;
import java.util.function.Function; import java.util.function.Function;
public class ReadOnlyConfigData<T> extends ConfigData<T> { public class ReadOnlyConfigData<T> extends ConfigData<T> {
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> 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); super(config, path, def, primitiveDef, getter, setter, saveAction);
} }
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef) { ReadOnlyConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Runnable saveAction) {
super(config, path, def, primitiveDef, null, null); super(config, path, def, primitiveDef, saveAction);
} }
} }

View file

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

View file

@ -11,6 +11,7 @@ import lombok.RequiredArgsConstructor;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.io.InputStreamReader; import java.io.InputStreamReader;
@ -22,18 +23,14 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
/** /**
* The method name is the subcommand, use underlines (_) to add further subcommands. * The method name is the subcommand, use underlines (_) to add further subcommands.
* The args may be null if the conversion failed and it's optional. * The args may be null if the conversion failed and it's optional.
*/ */
public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Sender> { public abstract class Command2<TC extends ICommand2, TP extends Command2Sender> {
protected Command2() { protected Command2() {
commandHelp.add("§6---- Commands ----"); commandHelp.add("§6---- Commands ----");
} }
@ -63,12 +60,10 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
String[] helpText() default {}; 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. * Used to be "tbmc.admin". The {@link #MOD_GROUP} is provided to use with this.
*/ */
String permGroup() default ""; String permGroup() default ""; //TODO
String[] aliases() default {};
} }
@Target(ElementType.PARAMETER) @Target(ElementType.PARAMETER)
@ -77,14 +72,13 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
} }
@AllArgsConstructor @AllArgsConstructor
protected static class SubcommandData<T extends ICommand2<?>> { protected static class SubcommandData<T extends ICommand2> {
public final Method method; public final Method method;
public final T command; public final T command;
public final String[] parameters;
public String[] helpText; 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 final TreeSet<String> ht = new TreeSet<>();
private BukkitTask task; private BukkitTask task;
@ -107,19 +101,18 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
task = null; //Run again, if needed task = null; //Run again, if needed
}); });
} }
}*/ }
@RequiredArgsConstructor @RequiredArgsConstructor
protected static class ParamConverter<T> { protected static class ParamConverter<T> {
public final Function<String, T> converter; public final Function<String, T> converter;
public final String errormsg; public final String errormsg;
public final Supplier<Iterable<String>> allSupplier;
} }
protected final HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>(); protected HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = 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; 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. * Adds a param converter that obtains a specific object from a string parameter.
* The converter may return null. * The converter may return null.
* *
* @param <T> The type of the result
* @param cl The class of the result object * @param cl The class of the result object
* @param converter The converter to use * @param converter The converter to use
* @param allSupplier The supplier of all possible values (ideally) * @param <T> The type of the result
*/ */
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
Supplier<Iterable<String>> allSupplier) { paramConverters.put(cl, new ParamConverter<>(converter, errormsg));
paramConverters.put(cl, new ParamConverter<>(converter, errormsg, allSupplier));
} }
public boolean handleCommand(TP sender, String commandline) { public boolean handleCommand(TP sender, String commandline) {
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
String subcommand = commandline.substring(0, i).toLowerCase(); 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; if (sd == null) continue;
boolean sync = Bukkit.isPrimaryThread(); boolean sync = Bukkit.isPrimaryThread();
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> { Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
try { try {
handleCommandAsync(sender, commandline, sd, subcommand, sync); handleCommandAsync(sender, commandline, sd, subcommand, sync);
} catch (Exception e) { } 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 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 * @param sync Whether the command was originally sync
* @throws Exception If something's not right * @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 if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands
sender.sendMessage(sd.helpText); sender.sendMessage(sd.helpText);
return; return;
@ -230,18 +221,6 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
continue; continue;
} else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) { } else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
try { 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 //noinspection unchecked
Number n = ChromaUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl); Number n = ChromaUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
params.add(n); params.add(n);
@ -253,7 +232,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
} }
val conv = paramConverters.get(cl); val conv = paramConverters.get(cl);
if (conv == null) 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); val cparam = conv.converter.apply(param);
if (cparam == null) { if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found 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 = () -> { Runnable lol = () -> {
try { 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) 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 (ret instanceof Boolean) {
if (!(boolean) ret) //Show usage if (!(boolean) ret) //Show usage
@ -271,9 +249,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
} else if (ret != null) } else if (ret != null)
throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret); throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret);
} catch (InvocationTargetException e) { } 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) { } 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) if (sync)
@ -284,12 +262,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
public abstract void registerCommand(TC command); public abstract void registerCommand(TC command);
protected List<SubcommandData<TC>> registerCommand(TC command, char commandChar) { protected void registerCommand(TC command, @SuppressWarnings("SameParameterValue") char commandChar) {
return registerCommand(command, command.getCommandPath(), commandChar);
}
protected List<SubcommandData<TC>> registerCommand(TC command, String path, @SuppressWarnings("SameParameterValue") char commandChar) {
this.commandChar = commandChar; this.commandChar = commandChar;
val path = command.getCommandPath();
int x = path.indexOf(' '); int x = path.indexOf(' ');
val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x); val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x);
//var scmdmap = subcommandStrings.computeIfAbsent(mainPath, k -> new HashSet<>()); //Used to display subcommands //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 nosubs = true;
boolean isSubcommand = x != -1; boolean isSubcommand = x != -1;
try { //Register the default handler first so it can be reliably overwritten 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); 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 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) if (ht.length > 0)
@ -309,53 +284,46 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
if (!commandHelp.contains(mainPath)) if (!commandHelp.contains(mainPath))
commandHelp.add(mainPath); commandHelp.add(mainPath);
} catch (Exception e) { } 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()) { for (val method : command.getClass().getMethods()) {
val ann = method.getAnnotation(Subcommand.class); 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 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); 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) val subcommand = commandChar + path + //Add command path (class name by default)
getCommandPath(method.getName(), ' '); //Add method name, unless it's 'def' getCommandPath(method.getName(), ' '); //Add method name, unless it's 'def'
var params = new String[method.getParameterCount() - 1]; ht = getParameterHelp(method, ht, subcommand);
ht = getParameterHelp(method, ht, subcommand, params); subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text
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);
scmdHelpList.add(subcommand); scmdHelpList.add(subcommand);
nosubs = false; nosubs = false;
} }
} }
if (nosubs && scmdHelpList.size() > 0) if (nosubs && scmdHelpList.size() > 0)
scmdHelpList.remove(scmdHelpList.size() - 1); //Remove Subcommands header scmdHelpList.remove(scmdHelpList.size() - 1); //Remove Subcommands header
if (mainMethod != null && !subcommands.containsKey(commandChar + path)) { //Command specified by the class 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, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0])));
subcommands.put(commandChar + path, sd); if (mainMethod != null && !mainPath.equals(commandChar + path)) { //Main command, typically the same as the above
addedSubcommands.add(sd);
}
if (isSubcommand) { //The class itself is a subcommand 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()); val scmdHelp = Arrays.copyOf(scmd.helpText, scmd.helpText.length + scmdHelpList.size());
for (int i = 0; i < scmdHelpList.size(); i++) for (int i = 0; i < scmdHelpList.size(); i++)
scmdHelp[scmd.helpText.length + i] = scmdHelpList.get(i); scmdHelp[scmd.helpText.length + i] = scmdHelpList.get(i);
scmd.helpText = scmdHelp; 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"); val str = method.getDeclaringClass().getResourceAsStream("/commands.yml");
if (str == null) 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 { else {
if (ht.length > 0) if (ht.length > 0)
ht[0] = "§6---- " + ht[0] + " ----"; ht[0] = "§6---- " + ht[0] + " ----";
YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor 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) { if (ccs != null) {
val cs = ccs.getConfigurationSection(method.getName()); val cs = ccs.getConfigurationSection(method.getName());
if (cs != null) { 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); String[] both = Arrays.copyOf(ht, ht.length + 1);
both[ht.length] = "§6Usage:§r " + subcommand + " " + params; both[ht.length] = "§6Usage:§r " + subcommand + " " + params;
ht = both; ht = both;
var paramArray = params.split(" ");
for (int j = 0; j < paramArray.length && j < parameters.length; j++)
parameters[j] = paramArray[j];
} else } 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 } 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 } 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; 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 abstract boolean hasPermission(TP sender, TC command, Method subcommand);
public String[] getCommandsText() { public String[] getCommandsText() {
@ -413,17 +371,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
for (val method : command.getClass().getMethods()) { for (val method : command.getClass().getMethods()) {
val ann = method.getAnnotation(Subcommand.class); val ann = method.getAnnotation(Subcommand.class);
if (ann == null) continue; if (ann == null) continue;
unregisterCommand(path, method.getName(), ann); val subcommand = commandChar + path + getCommandPath(method.getName(), ' ');
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, ' ');
subcommands.remove(subcommand); subcommands.remove(subcommand);
for (String alias : ann.aliases()) }
subcommands.remove(commandChar + path + alias);
} }
/** /**

View file

@ -1,41 +1,26 @@
package buttondevteam.lib.chat; package buttondevteam.lib.chat;
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.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 lombok.val;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer; 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.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.server.TabCompleteEvent;
import org.bukkit.permissions.Permission; import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault; import org.bukkit.permissions.PermissionDefault;
import org.javatuples.Triplet;
import java.lang.annotation.Annotation; import java.lang.annotation.Annotation;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.HashMap;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function; 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 { public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
/** /**
@ -45,19 +30,7 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
*/ */
@Override @Override
public void registerCommand(ICommand2MC command) { public void registerCommand(ICommand2MC command) {
/*String mainpath; super.registerCommand(command, '/');
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)), '/');
var perm = "chroma.command." + command.getCommandPath().replace(' ', '.'); var perm = "chroma.command." + command.getCommandPath().replace(' ', '.');
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(new Permission(perm, Bukkit.getPluginManager().addPermission(new Permission(perm,
@ -73,9 +46,9 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
} }
String pg = permGroup(command, method); String pg = permGroup(command, method);
if (pg.length() == 0) continue; if (pg.length() == 0) continue;
String permGroup = "chroma." + pg; perm = "chroma." + pg;
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times if (Bukkit.getPluginManager().getPermission(perm) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(new Permission(permGroup, Bukkit.getPluginManager().addPermission(new Permission(perm,
PermissionDefault.OP)); //Do not allow any commands that belong to a group 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); p = MainPlugin.permission.playerHas(sender instanceof Player ? ((Player) sender).getLocation().getWorld().getName() : null, (OfflinePlayer) sender, perm);
else else
p = false; //Use sender's method 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); if (!p) p = sender.hasPermission(perm);
} else break; //If any of the permissions aren't granted then don't allow } 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} * {@see super#addParamConverter}
*/ */
@Override @Override
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, Supplier<Iterable<String>> allSupplier) { public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
super.addParamConverter(cl, converter, "§c" + errormsg, allSupplier); super.addParamConverter(cl, converter, "§c" + errormsg);
} }
public void unregisterCommands(ButtonPlugin plugin) { 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)); .map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false));
} }
/*@EventHandler @EventHandler
public void onTabComplete(TabCompleteEvent event) { private void handleTabComplete(TabCompleteEvent event) {
try { String commandline = event.getBuffer();
event.getCompletions().clear(); //Remove player names CommandSender sender = event.getSender();
} catch (UnsupportedOperationException e) { //System.out.println("tab");
//System.out.println("Tabcomplete: " + event.getBuffer()); for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
//System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions")); String subcommand = commandline.substring(0, i).toLowerCase();
//System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners())); 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;
@Override //System.out.println("ht: " + Arrays.toString(sd.helpText));
public boolean handleCommand(Command2MCSender sender, String commandline) { Arrays.stream(sd.helpText).skip(1).map(ht -> new HashMap.SimpleEntry<>(ht, subcommands.get(ht))).filter(e -> e.getValue() != null)
return handleCommand(sender, commandline, true); .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)));
private boolean handleCommand(Command2MCSender sender, String commandline, boolean checkPlugin) { if (sd.method == null || sd.command == null)
int i = commandline.indexOf(' '); return;
String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash /*if (!hasPermission(sender, sd.command, sd.method)) { - TODO: Arguments
PluginCommand pcmd; sender.sendMessage("§cYou don't have permission to use this command");
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.");
return true; return true;
} }
handleCommand(new Command2MCSender(sender, user.channel.get(), sender), val params = new ArrayList<Object>(sd.method.getParameterCount());
("/" + command.getName() + " " + String.join(" ", args)).trim(), false); ///trim(): remove space if there are no args int j = subcommand.length(), pj;
return true; Class<?>[] parameterTypes = sd.method.getParameterTypes();
} if (parameterTypes.length == 0)
throw new Exception("No sender parameter for method '" + sd.method + "'");
private static class BukkitCommand extends Command { val sendertype = parameterTypes[0];
protected BukkitCommand(String name) { final ChromaGamerBase cg;
super(name); 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
@Override && sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
public boolean execute(CommandSender sender, String commandLabel, String[] args) { params.add(((Command2MCSender) sender).getSender());
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args); else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
} && sender instanceof Command2MCSender
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
@Override && cg.getClass() == sendertype) //The command expects a user of our system
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { params.add(cg);
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();
else { else {
type = StringArgumentType.word(); sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command.");
customParamTypeTemp = true; 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()) if (paramArr[i1].isVarArgs()) {
for (val ctc : customTC.get()) par0ams.add(commandline.substring(j + 1).split(" +"));
builder.suggest(ctc);
boolean ignoreCustomParamType = false;
if (customTCmethod.isPresent()) {
var tr = customTCmethod.get();
if (tr.getValue1().ignoreTypeCompletion())
ignoreCustomParamType = true;
final var method = tr.getValue2();
val params = method.getParameters();
val args = new Object[params.length];
for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) {
val paramObj = params[j];
if (CommandSender.class.isAssignableFrom(paramObj.getType())) {
args[j] = commodore.getBukkitSender(context.getSource());
continue; continue;
} }
val paramValueString = context.getArgument(subcmd.parameters[k], String.class); j = commandline.indexOf(' ', j + 1); //End index
if (paramObj.getType() == String.class) { if (j == -1 || paramArr[i1].isAnnotationPresent(TextArg.class)) //Last parameter
args[j] = paramValueString; j = commandline.length();
String param = commandline.substring(pj, j);
if (cl == String.class) {
params.add(param);
continue; continue;
} } else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
val converter = getParamConverter(params[j].getType(), command2MC);
if (converter == null)
break;
val paramValue = converter.converter.apply(paramValueString);
if (paramValue == null) //For example, the player provided an invalid plugin name
break;
args[j] = paramValue;
k++; //Only increment if not CommandSender
}
if (args.length == 0 || args[args.length - 1] != null) { //Arguments filled entirely
try { try {
val suggestions = method.invoke(command2MC, args);
if (suggestions instanceof Iterable) {
//noinspection unchecked //noinspection unchecked
for (Object suggestion : (Iterable<Object>) suggestions) Number n = ThorpeUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
if (suggestion instanceof String) params.add(n);
builder.suggest((String) suggestion); } catch (ParseException e) {
else sender.sendMessage("§c'" + param + "' is not a number.");
throw new ClassCastException("Bad return type! It should return an Iterable<String> or a String[]."); return true;
} 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());
} }
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;
} }
if (!ignoreCustomParamType && customParamType) { params.add(cparam);
val converter = getParamConverter(ptype, command2MC);
if (converter != null) {
var suggestions = converter.allSupplier.get();
for (String suggestion : suggestions)
builder.suggest(suggestion);
} }
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 (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; package buttondevteam.lib.chat;
import buttondevteam.core.component.channel.Channel;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -8,8 +7,6 @@ import org.bukkit.command.CommandSender;
@RequiredArgsConstructor @RequiredArgsConstructor
public class Command2MCSender implements Command2Sender { public class Command2MCSender implements Command2Sender {
private @Getter final CommandSender sender; private @Getter final CommandSender sender;
private @Getter final Channel channel;
private @Getter final CommandSender permCheck;
@Override @Override
public void sendMessage(String message) { 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. * Default handler for commands, can be used to copy the args too.
* *
* @param sender The sender which ran the command * @param sender The sender which ran the command
* @param args All of the arguments passed as is
* @return The success of the command * @return The success of the command
*/ */
public boolean def(TP sender) { public boolean def(TP sender, @Command2.TextArg String args) {
return false; return false;
} }
@ -63,18 +64,6 @@ public abstract class ICommand2<TP extends Command2Sender> {
return path; 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() { private String getcmdpath() {
if (!getClass().isAnnotationPresent(CommandClass.class)) if (!getClass().isAnnotationPresent(CommandClass.class))
throw new RuntimeException( throw new RuntimeException(

View file

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

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; package buttondevteam.lib.player;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel; import buttondevteam.core.component.channel.Channel;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ConfigData; import com.google.common.collect.HashBiMap;
import buttondevteam.lib.architecture.IHaveConfig;
import lombok.Getter;
import lombok.val; import lombok.val;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -15,60 +12,37 @@ import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier;
@ChromaGamerEnforcer @ChromaGamerEnforcer
public abstract class ChromaGamerBase { public abstract class ChromaGamerBase implements AutoCloseable {
private static final String TBMC_PLAYERS_DIR = "TBMC/players/"; public static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/** private static final HashBiMap<Class<? extends ChromaGamerBase>, String> playerTypes = HashBiMap.create();
* Holds data per user class
*/
private static final HashMap<Class<? extends ChromaGamerBase>, StaticUserData<?>> staticDataMap = new HashMap<>();
/** /**
* 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; public static void RegisterPluginUserClass(Class<? extends ChromaGamerBase> userclass) {
if (userclass.isAnnotationPresent(UserClass.class))
@Getter playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername());
protected final IHaveConfig config = new IHaveConfig(this::save); else if (userclass.isAnnotationPresent(AbstractUserClass.class))
protected CommonUserData<?> commonUserData; playerTypes.put(userclass.getAnnotation(AbstractUserClass.class).prototype(),
userclass.getAnnotation(AbstractUserClass.class).foldername());
/** else // <-- Really important
* 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
throw new RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase"); 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. * 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 * @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) { public static <T extends ChromaGamerBase> String getFolderForType(Class<T> cl) {
if (cl.isAnnotationPresent(UserClass.class)) if (cl.isAnnotationPresent(UserClass.class))
@ -81,61 +55,50 @@ public abstract class ChromaGamerBase {
/** /**
* Returns the player class for the given folder name. * 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 * @return The type for the given folder name or null if not found
*/ */
public static Class<? extends ChromaGamerBase> getTypeForFolder(String foldername) { public static Class<? extends ChromaGamerBase> getTypeForFolder(String foldername) {
synchronized (staticDataMap) { return playerTypes.inverse().get(foldername);
return staticDataMap.entrySet().stream().filter(e -> e.getValue().getFolder().equalsIgnoreCase(foldername))
.map(Map.Entry::getKey).findAny().orElse(null);
}
} }
/**
* 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 * @param cl User class
* @return The user object * @return The user object
*/ */
public static synchronized <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) { public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
StaticUserData<?> staticUserData = null; try {
for (var sud : staticDataMap.entrySet()) { T obj = cl.newInstance();
if (sud.getKey().isAssignableFrom(cl)) { final String folder = getFolderForType(cl);
staticUserData = sud.getValue();
break;
}
}
if (staticUserData == null)
throw new RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass");
var commonUserData = staticUserData.getUserDataMap().get(fname);
if (commonUserData == null) {
final String folder = staticUserData.getFolder();
final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml"); final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml");
file.getParentFile().mkdirs(); file.getParentFile().mkdirs();
var playerData = YamlConfiguration.loadConfiguration(file); obj.plugindata = YamlConfiguration.loadConfiguration(file);
commonUserData = new CommonUserData<>(playerData); obj.plugindata.set(folder + "_id", fname);
playerData.set(staticUserData.getFolder() + "_id", fname);
staticUserData.getUserDataMap().put(fname, commonUserData);
}
if (commonUserData.getUserCache().containsKey(cl))
return (T) commonUserData.getUserCache().get(cl);
T obj;
if (staticUserData.getConstructors().containsKey(cl))
//noinspection unchecked
obj = (T) staticUserData.getConstructors().get(cl).get();
else {
try {
obj = cl.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create new instance of user of type " + cl.getSimpleName() + "!", e);
}
}
obj.commonUserData = commonUserData;
obj.init();
obj.scheduleUncache();
return obj; 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. * Adds a converter to the start of the list.
@ -161,110 +124,88 @@ public abstract class ChromaGamerBase {
return null; return null;
} }
public static void saveUsers() { /**
synchronized (staticDataMap) { * Saves the player. It'll pass all exceptions to the caller. To automatically handle the exception, use {@link #save()} instead.
for (var sud : staticDataMap.values()) */
for (var cud : sud.getUserDataMap().values()) @Override
ConfigData.saveNow(cud.getPlayerData()); //Calls save() public void close() throws Exception {
} if (plugindata.getKeys(false).size() > 0)
} plugindata.save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
protected void init() {
config.reset(commonUserData.getPlayerData());
} }
/** /**
* 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 { try {
if (commonUserData.getPlayerData().getKeys(false).size() > 0) close();
commonUserData.getPlayerData().save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
} catch (Exception e) { } 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. * Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set
*/
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.
* *
* @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 // 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 ownFolder = getFolder();
final String userFolder = user.getFolder(); final String userFolder = user.getFolder();
if (ownFolder.equalsIgnoreCase(userFolder)) if (ownFolder.equalsIgnoreCase(userFolder))
throw new RuntimeException("Do not connect two accounts of the same type! Type: " + ownFolder); throw new RuntimeException("Do not connect two accounts of the same type! Type: "+ownFolder);
var ownData = commonUserData.getPlayerData(); user.plugindata.set(ownFolder + "_id", plugindata.getString(ownFolder + "_id"));
var userData = user.commonUserData.getPlayerData(); plugindata.set(userFolder + "_id", user.plugindata.getString(userFolder + "_id"));
userData.set(ownFolder + "_id", ownData.getString(ownFolder + "_id"));
ownData.set(userFolder + "_id", userData.getString(userFolder + "_id"));
config.signalChange();
user.config.signalChange();
Consumer<YamlConfiguration> sync = sourcedata -> { 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"); 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()) if (entry.getKey() == getClass() || entry.getKey() == user.getClass())
continue; continue;
var entryFolder = entry.getValue().getFolder(); final String otherid = sourcedata.getString(entry.getValue() + "_id");
final String otherid = sourcedata.getString(entryFolder + "_id");
if (otherid == null) if (otherid == null)
continue; continue;
ChromaGamerBase cg = getUser(otherid, entry.getKey()); try (ChromaGamerBase cg = getUser(otherid, entry.getKey())) {
var cgData = cg.commonUserData.getPlayerData(); cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs
cgData.set(sourcefolder + "_id", id); // Set new IDs for (val item : playerTypes.entrySet())
for (val item : staticDataMap.entrySet()) { if (sourcedata.contains(item.getValue() + "_id"))
var itemFolder = item.getValue().getFolder(); cg.plugindata.set(item.getValue() + "_id", sourcedata.getString(item.getValue() + "_id")); // Set all existing IDs
if (sourcedata.contains(itemFolder + "_id")) { } catch (Exception e) {
cgData.set(itemFolder + "_id", sourcedata.getString(itemFolder + "_id")); // Set all existing IDs 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(plugindata);
sync.accept(userData); 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 * @return The ID or null if not found
*/ */
public final <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) { public <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return commonUserData.getPlayerData().getString(getFolderForType(cl) + "_id"); 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> * Returns this player as a plugin player. This will return a new instance unless the player is online.<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. * 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 * @param cl
* @return The player as a {@link T} object or null if the user doesn't have an account there * The target player class
* @return The player as a {@link T} object or null if not having an account there
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @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())) if (cl.getSimpleName().equals(getClass().getSimpleName()))
return (T) this; return (T) this;
String newfolder = getFolderForType(cl); String newfolder = getFolderForType(cl);
@ -272,34 +213,105 @@ public abstract class ChromaGamerBase {
throw new RuntimeException("The specified class " + cl.getSimpleName() + " isn't registered!"); 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 if (newfolder.equals(getFolder())) // If in the same folder, the same filename is used
return getUser(getFileName(), cl); return getUser(getFileName(), cl);
var playerData = commonUserData.getPlayerData(); if (!plugindata.contains(newfolder + "_id"))
if (!playerData.contains(newfolder + "_id"))
return null; return null;
return getUser(playerData.getString(newfolder + "_id"), cl); return getUser(plugindata.getString(newfolder + "_id"), cl);
} }
/** public String getFolder() {
* 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() {
return getFolderForType(getClass()); 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. * 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. * @return The player information.
*/ */
public final String getInfo(InfoTarget target) { public String getInfo(InfoTarget target) {
TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target); TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target);
Bukkit.getServer().getPluginManager().callEvent(event); Bukkit.getServer().getPluginManager().callEvent(event);
return event.getResult(); return event.getResult();
@ -311,6 +323,7 @@ public abstract class ChromaGamerBase {
//----------------------------------------------------------------- //-----------------------------------------------------------------
public final ConfigData<Channel> channel = config.getData("channel", Channel.GlobalChat, public ChannelPlayerData channel() {
id -> Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID); 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; package buttondevteam.lib.player;
@PlayerClass(pluginname = "Chroma-Core") @PlayerClass(pluginname = "ButtonCore") //TODO: Migrate
public final class TBMCPlayer extends TBMCPlayerBase { public final class TBMCPlayer extends TBMCPlayerBase {
} }

View file

@ -1,21 +1,28 @@
package buttondevteam.lib.player; package buttondevteam.lib.player;
import buttondevteam.lib.architecture.ConfigData; import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.lib.architecture.IHaveConfig; import buttondevteam.lib.TBMCCoreAPI;
import lombok.Getter;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer.class) @AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer.class)
@TBMCPlayerEnforcer @TBMCPlayerEnforcer
public abstract class TBMCPlayerBase extends ChromaGamerBase { public abstract class TBMCPlayerBase extends ChromaGamerBase {
protected UUID uuid; protected UUID uuid;
@Getter private String pluginname;
private final IHaveConfig config = new IHaveConfig(this::save);
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() { public UUID getUUID() {
if (uuid == null) if (uuid == null)
@ -23,61 +30,156 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase {
return uuid; 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 uuid The UUID of the player to get
* @param cl The type of the player * @param cl The type of the player
* @return The requested player object * @return The requested player object
*/ */
@SuppressWarnings("unchecked")
public static <T extends TBMCPlayerBase> T getPlayer(UUID uuid, Class<T> cl) { public static <T extends TBMCPlayerBase> T getPlayer(UUID uuid, Class<T> cl) {
var player = ChromaGamerBase.getUser(uuid.toString(), cl); if (playermap.containsKey(uuid + "-" + cl.getSimpleName()))
if (!player.getUUID().equals(uuid)) //It will be set from the filename because we check it for scheduling the uncache. return (T) playermap.get(uuid + "-" + cl.getSimpleName());
throw new IllegalStateException("Player UUID differs after converting from and to string..."); 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; 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 * @param name The player's name
* @return The {@link TBMCPlayer} object for the player * @return The {@link TBMCPlayer} object for the player
*/ */
@SuppressWarnings("deprecation")
public static <T extends TBMCPlayerBase> T getFromName(String name, Class<T> cl) { public static <T extends TBMCPlayerBase> T getFromName(String name, Class<T> cl) {
@SuppressWarnings("deprecation")
OfflinePlayer p = Bukkit.getOfflinePlayer(name); OfflinePlayer p = Bukkit.getOfflinePlayer(name);
if (p != null)
return getPlayer(p.getUniqueId(), cl); return getPlayer(p.getUniqueId(), cl);
else
return null;
} }
@Override @Override
protected void save() { public void close() throws Exception {
Set<String> keys = commonUserData.getPlayerData().getKeys(false); Set<String> keys = plugindata.getKeys(false);
if (keys.size() > 1) // PlayerName is always saved, but we don't need a file for just that 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 main: buttondevteam.core.MainPlugin
version: '${noprefix.version}' version: 1.0
author: NorbiPeti author: NorbiPeti
commands: commands:
updateplugin: updateplugin:
@ -9,6 +9,10 @@ commands:
description: Schedules a restart for a given time. description: Schedules a restart for a given time.
primerestart: primerestart:
description: Restarts the server as soon as nobody is online. 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: component:
description: Enable or disable or list components description: Enable or disable or list components
dontrunthiscmd: dontrunthiscmd:
@ -19,4 +23,3 @@ softdepend:
- Votifier - Votifier
- Multiverse-Core - Multiverse-Core
- Essentials - 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 --> </useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration> </configuration>
</plugin> </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> </plugins>
</pluginManagement> </pluginManagement>
</build> </build>
@ -86,7 +69,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>4.13.1</version> <version>3.8.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->

5
pom.xml Normal file → Executable file
View file

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