Compare commits

...

69 commits

Author SHA1 Message Date
66c1b1b14f Merge hotfix 1.0.2 2023-04-05 15:17:09 +02:00
496af97e1c
Fix getting user using abstract user classes (#101)
- The Discord plugin uses this to connect MC and DC accounts
- And it was a feataure of this plugin either way
2023-03-12 23:26:47 +01:00
6ec63b5ae7
Fix NuVotifier repository 2023-03-12 22:01:06 +01:00
ea7520d99a
Merge pull request #104 from TBMCPlugins/dependabot/maven/ButtonProcessor/org.yaml-snakeyaml-1.32
Bump snakeyaml from 1.26 to 1.32 in /ButtonProcessor
2023-01-04 22:39:31 +01:00
dependabot[bot]
47461d49c2
Bump snakeyaml from 1.26 to 1.32 in /ButtonProcessor
---
updated-dependencies:
- dependency-name: org.yaml:snakeyaml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-14 20:44:48 +00:00
7120e3c40e
Merge pull request #100 from TBMCPlugins/alisolarflare-color-refactor
Put each color on their own line
2022-01-08 01:04:15 +01:00
alisolarflare
c6b2dc3443
Put each color on their own line
norbi no
2022-01-07 18:41:51 -05:00
4e58ddd475
Update dependencies and fix config charset
The charset used to save the configuration was platform-dependent, changed it to UTF-8
2021-12-30 21:16:20 +01:00
5c83b923da
Merge pull request #99 from TBMCPlugins/dependabot/maven/ButtonProcessor/org.yaml-snakeyaml-1.26
Bump snakeyaml from 1.21 to 1.26 in /ButtonProcessor
2021-12-30 16:47:57 +01:00
83b5f1fec4
Fix command data generation for Scala 2021-08-26 01:59:18 +02:00
dependabot[bot]
59ccc55f98
Bump snakeyaml from 1.21 to 1.26 in /ButtonProcessor
Bumps [snakeyaml](https://bitbucket.org/asomov/snakeyaml) from 1.21 to 1.26.
- [Commits](https://bitbucket.org/asomov/snakeyaml/branches/compare/snakeyaml-1.26..snakeyaml-1.21)

---
updated-dependencies:
- dependency-name: org.yaml:snakeyaml
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-04 22:05:30 +00:00
f92cc773c1
Remove Towny resident rename functionality
Towny doesn't need the name updater plugin anymore
2021-01-18 17:14:15 +01:00
2a008906f4
Remove Votifier component and stuff 2020-10-31 18:24:18 +01:00
9fb35eb6cc
Some fixes and cleanup
Don't require ButtonPlugin for unregistering components
Cleanup
Fixed not detecting missing player name
2020-10-30 22:56:11 +01:00
66be5ab0dc
Remove (/press) 2020-10-30 00:49:43 +01:00
5b5a8877cc
Add a dash and a zero (name and version) 2020-10-28 00:08:00 +01:00
09074f1a2e
Moving player data and some cleanup 2020-10-27 22:48:39 +01:00
ff0d54e00b
Set version automatically, channel cmd IDs
Added support for using the IDs setting for channel commands
2020-10-27 21:58:49 +01:00
5199482053
Set Java version for codeql 2020-10-26 23:18:54 +01:00
c12a24895e
Fix config comments, improve pregen
Also only showing a warning if something is missing from the command yml
2020-10-26 19:54:28 +01:00
51e0ca4f4c
Stop using methods for configs 2020-10-26 17:44:11 +01:00
267a350473
Refactored the aforementioned player data
Now it will only load a yaml file once at a time
Each user class which has a separate folder now has a separate static and non-static shared storage
Also some uncache fixes for MC that I haven't committed before
2020-10-25 21:47:57 +01:00
132eba7db6
Player data upgrade complete
Auto-save for each user
ConfigData stuff
Removed player events
2020-10-24 23:47:33 +02:00
83ecf7717f
Started upgrading player data setup 2020-10-23 01:48:54 +02:00
893b24ed5f
Fix alias support and add tab completion for it 2020-10-14 20:41:26 +02:00
af0aed65e0
Merge pull request #98 from TBMCPlugins/dependabot/maven/CorePOM/junit-junit-4.13.1
Bump junit from 3.8.1 to 4.13.1 in /CorePOM
2020-10-14 19:49:21 +02:00
47dbb987eb
Merge pull request #97 from TBMCPlugins/dependabot/maven/junit-junit-4.13.1
Bump junit from 3.8.1 to 4.13.1
2020-10-14 19:48:54 +02:00
dependabot[bot]
28e26ad3d1
Bump junit from 3.8.1 to 4.13.1 in /CorePOM
Bumps [junit](https://github.com/junit-team/junit4) from 3.8.1 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/commits/r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 17:06:08 +00:00
dependabot[bot]
8e664d073f
Bump junit from 3.8.1 to 4.13.1
Bumps [junit](https://github.com/junit-team/junit4) from 3.8.1 to 4.13.1.
- [Release notes](https://github.com/junit-team/junit4/releases)
- [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.13.1.md)
- [Commits](https://github.com/junit-team/junit4/commits/r4.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-10-13 09:43:10 +00:00
1aa9cd3552
Attempt to support main command aliases 2020-10-13 02:49:48 +02:00
731065fe2a
Log exceptions using the plugin/component logger 2020-10-09 00:08:53 +02:00
01dd8a477e
Fix error when Mockito isn't present in prod 2020-09-15 03:42:49 +02:00
ab4dd75684
Test and config fixes
Added method to check if testing
2020-09-04 17:52:36 +02:00
5b27af8925
Fixed prime restart and already registered cmds
Commands that are registered in plugin.yml
Commands conflicting with Essentials commands have to be registered in plugin.yml
2020-09-04 03:37:32 +02:00
28e44d5179
Added some alias support for subcommands
Hmm
2020-09-02 03:12:07 +02:00
cedeca2f61
A small fix can go a long way 2020-08-31 23:53:29 +02:00
784ad5e1c7
Config fixes, actually registering the configs 2020-08-30 02:21:34 +02:00
9923f26698
Config fixes, reusing the config objects 2020-08-29 02:57:50 +02:00
b89391f84c
Fix doc generation for field configs
And for read-only configs
2020-08-27 16:48:56 +02:00
8f610c9935
Add ConfigData builder, reset support 2020-08-27 16:18:25 +02:00
4310e45a6f
Use existing command from plugin.yml if exists
3:33
2020-08-27 03:33:46 +02:00
43b7bd442a
Resident removal fix, signal config change
Use Essentials to get residents to remove (#95)
Add IHaveConfig.signalChange() method (TBMCPlugins/Chroma-Chat#117)
2020-08-26 22:14:53 +02:00
ef96ec2604
Fix fallback chat format
TBMCPlugins/Chroma-Chat#121
2020-07-28 02:36:26 +02:00
8815877bea
Add proper scheduled restart here & fixes 2020-07-25 20:27:56 +02:00
58fcd4c145
Add per-component logging 2020-06-30 00:43:56 +02:00
1139f832b6
Component disable on error 2020-06-27 02:51:38 +02:00
9dae442950
Improve parameter tab completion
Fixed bad permissions being used for subcommands (older change)
Updated Commodore and made it only register a command node once - this fixes main command argument handling as Bukkit's handler is removed
Added option to ignore the tab completion provided by the parameter type (param converter)
Only showing matching completions (it has to start with the text typed in)
#82
2020-04-07 22:08:09 +02:00
82858b0a41
Added fallback to handle prefixed commands
The Bukkit executor will join the arguments again and passes the command line to the command system
Also added support for tab completion
#96
2020-03-31 02:51:54 +02:00
7b505bb8e9
Fix resident rename
Even more than last time
2020-03-21 19:19:57 +01:00
6b68bdab0f
Add support for varargs and fix param name placement
#82
2020-03-19 22:58:19 +01:00
8344adff1a
Fully implement argument suggestions
Suggesting based on each annotation and the parameter type
Moving the parameter name to the end of the suggestion list
#82
2020-03-19 20:19:41 +01:00
f5406a8c0e
Fix tabcompletion: use custom suggestion provider
It looks like we can't have custom argument types
Checking for permission for each offered option
Suggesting the argument's name as well
#82

TODO:
Handle annotations
Remove test command
2020-03-18 01:34:32 +01:00
95a8e92b51
List commands on /chroma, fixed arg names
And broke the game (can't connect atm)
#84
#82
2020-03-17 00:33:35 +01:00
792a127bdd
Channel command and message colors fix
Fixed channel 'from command' and 'perm check'
Fixed message colors when the chat plugin isn't present (TBMCPlugins/Chroma-Chat#107)
2020-03-15 23:53:10 +01:00
a3160c4040
Add support for channels and groups for (chat) commands
#93
Fixed private command classes erroring
2020-03-15 03:07:10 +01:00
b77871bb8c Add permanent component enable/disable
#81
2020-03-10 16:06:57 +01:00
e676ea516c Increase primerestart cooldown
#42
2020-03-09 12:35:17 +01:00
0e88c61b77
Fix Towny resident rename, remove invalid residents
Added a command to remove residents that aren't real players anymore because of a rename usually (#41)
Name conflicts should be handled manually (#7) - possibly by removing the resident
2020-03-08 21:37:07 +01:00
425cbcd9d2
Fixed argument tabcomplete
The issue was that I was adding to a builder after it was used to build
With the command class passed, it only works with /plugin:cmd and doesn't give hints for the arguments
Without the command class, it doesn't show the next argument until it's typed in and also accepts invalid subcommands
#82
2020-03-07 03:01:01 +01:00
00738fe820
Some attempts and fixes before enlightenment
#82
2020-03-07 02:27:24 +01:00
3c4f9f6c7a
Reload cmd fix, parameter tabcomplete
Made reload cmd only for ButtonPlugins
Implemented parameter tabcomplete using Commodore
Only works for one subcommand at the moment
#82
2020-03-06 00:39:18 +01:00
2cca2e2096 Register chat channels as commands
#72
2020-03-03 17:13:35 +01:00
59aa13cd74 Remove parent POM from the processor
This should fix JitPack attempting to download the annotation processor from the project that's being built
2020-02-26 12:41:46 +01:00
05720afdf8 Separate command class, fix processor parent POM 2020-02-25 17:34:25 +01:00
393c9c9b08 Fix command register
Also deleted plugin updater stuff in last commit
2020-02-24 19:19:18 +01:00
a0c211f4be Attempt to register custom commands 2020-02-24 12:57:57 +01:00
def4a846b2 Reload other plugins, remove plugin updater
#84
2020-02-19 10:47:53 +01:00
b9781f19f0 Handle remaining primitive types in commands
#92
2020-02-18 12:27:18 +01:00
b7260d318c
Merge branch 'commands' 2020-02-16 18:26:18 +01:00
68 changed files with 1891 additions and 1898 deletions

View file

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

75
.github/workflows/codeql-analysis.yml vendored Normal file
View file

@ -0,0 +1,75 @@
# 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

@ -1,42 +0,0 @@
<?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

@ -1,16 +0,0 @@
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
}
}

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

@ -1,6 +1,6 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<repositories> <repositories>
<repository> <repository>
<id>spigot-repo</id> <id>spigot-repo</id>
@ -11,7 +11,7 @@
<dependency> <dependency>
<groupId>org.yaml</groupId> <groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId> <artifactId>snakeyaml</artifactId>
<version>1.21</version> <version>1.32</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -21,13 +21,10 @@
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<parent> <!-- Can't use Core POM because it uses this processor -->
<groupId>com.github.TBMCPlugins</groupId> <groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ChromaCore</artifactId>
<version>master-SNAPSHOT</version>
</parent>
<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>
@ -36,21 +33,25 @@
<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>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version> <version>3.0.0-M3</version>
<configuration> <configuration>
<useSystemClassLoader>false <useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 --> </useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
<properties> <properties>

View file

@ -21,35 +21,29 @@ import java.util.stream.Collectors;
@SupportedAnnotationTypes("buttondevteam.*") @SupportedAnnotationTypes("buttondevteam.*")
public class ButtonProcessor extends AbstractProcessor { public class ButtonProcessor extends AbstractProcessor {
@Override @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (configProcessor == null) if (configProcessor == null)
configProcessor = new ConfigProcessor(processingEnv); configProcessor = new ConfigProcessor(processingEnv);
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); Function<String, Boolean> hasAnnotation = ann -> annotationMirrors.stream()
//System.out.println("Annotations: " + annotationMirrors); .anyMatch(am -> am.getAnnotationType().toString().contains(ann));
Function<String, Boolean> hasAnnotation = ann -> annotationMirrors.stream() if (hasAnnotation.apply("ChromaGamerEnforcer") && !hasAnnotation.apply("UserClass")
.anyMatch(am -> am.getAnnotationType().toString().contains(ann)); && !targetcl.getModifiers().contains(Modifier.ABSTRACT))
if (hasAnnotation.apply("ChromaGamerEnforcer") && !hasAnnotation.apply("UserClass") processingEnv.getMessager().printMessage(Kind.ERROR,
&& !targetcl.getModifiers().contains(Modifier.ABSTRACT)) "No UserClass annotation found for " + targetcl.getSimpleName(), targetcl);
processingEnv.getMessager().printMessage(Kind.ERROR, if (hasAnnotation.apply("TBMCPlayerEnforcer") && !hasAnnotation.apply("PlayerClass")
"No UserClass annotation found for " + targetcl.getSimpleName(), targetcl); && !targetcl.getModifiers().contains(Modifier.ABSTRACT))
if (hasAnnotation.apply("TBMCPlayerEnforcer") && !hasAnnotation.apply("PlayerClass") processingEnv.getMessager().printMessage(Kind.ERROR,
&& !targetcl.getModifiers().contains(Modifier.ABSTRACT)) "No PlayerClass annotation found for " + targetcl.getSimpleName(), targetcl);
processingEnv.getMessager().printMessage(Kind.ERROR, processSubcommands(targetcl, annotationMirrors);
"No PlayerClass annotation found for " + targetcl.getSimpleName(), targetcl); if (hasAnnotation.apply("HasConfig"))
for (AnnotationMirror annotation : annotationMirrors) { configProcessor.process(targetcl);
String type = annotation.getAnnotationType().toString(); }
//System.out.println("Type: " + type); }
}
processSubcommands(targetcl, annotationMirrors);
if (hasAnnotation.apply("HasConfig"))
configProcessor.process(targetcl);
}
}
try { try {
if (found) { if (found) {
FileObject fo = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "commands.yml"); FileObject fo = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "commands.yml");
@ -59,38 +53,33 @@ public class ButtonProcessor extends AbstractProcessor {
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
return true; // claim the annotations return true; // claim the annotations
} }
private YamlConfiguration yc = new YamlConfiguration(); private final YamlConfiguration yc = new YamlConfiguration();
private boolean found = false; private boolean found = false;
private ConfigProcessor configProcessor; private ConfigProcessor configProcessor;
private void processSubcommands(Element targetcl, List<? extends AnnotationMirror> annotationMirrors) { private void processSubcommands(Element method, List<? extends AnnotationMirror> annotationMirrors) {
if (!(targetcl instanceof ExecutableElement)) if (!(method 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;
//System.out.print("Processing method: " + targetcl.getEnclosingElement()+" "+targetcl); ConfigurationSection cs = yc.createSection(method.getEnclosingElement().toString()
ConfigurationSection cs = yc.createSection(targetcl.getEnclosingElement().toString() + "." + method.getSimpleName().toString()); //Need to do the 2 config sections at once so it doesn't overwrite the class section
+ "." + targetcl.getSimpleName().toString()); //Need to do the 2 config sections at once so it doesn't overwrite the class section System.out.println("Found subcommand: " + method);
System.out.println(targetcl); cs.set("method", method.toString());
cs.set("method", targetcl.toString()); cs.set("params", ((ExecutableElement) method).getParameters().stream().skip(1).map(p -> {
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;
} }
@Override @Override
public SourceVersion getSupportedSourceVersion() { public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported(); return SourceVersion.latestSupported();
} }
} }

View file

@ -48,28 +48,27 @@ public class ConfigProcessor {
e.printStackTrace(); e.printStackTrace();
} }
for (Element e : targetcl.getEnclosedElements()) { for (Element e : targetcl.getEnclosedElements()) {
/*System.out.println("Element: "+e); TypeMirror tm;
System.out.println("Type: "+e.getClass()+" - "+e.getKind()); if (e instanceof ExecutableElement)
if(e instanceof ExecutableElement) tm = ((ExecutableElement) e).getReturnType();
System.out.println("METHOD!");*/ else if (e.getKind().isField())
if (!(e instanceof ExecutableElement)) continue; tm = e.asType();
TypeMirror tm = ((ExecutableElement) e).getReturnType(); else
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().contentEquals("ConfigData")) if (!dt.asElement().getSimpleName().toString().contains("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("DOC: " + doc); System.out.println("Adding docs for config: " + e.getSimpleName());
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("JAVADOC"); System.out.println("Adding docs for class: " + targetcl.getSimpleName());
System.out.println(javadoc.trim()); yc.set(path + ".generalDescriptionInsteadOfAConfig", javadoc.trim());
yc.set(path, javadoc.trim());
} }
try { try {
yc.save(file); yc.save(file);

View file

@ -1,15 +1,16 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <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"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId> <groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId> <artifactId>CorePOM</artifactId>
<version>master-SNAPSHOT</version> <version>master-SNAPSHOT</version>
<relativePath>../CorePOM</relativePath> <relativePath>../CorePOM</relativePath>
</parent> </parent>
<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>
@ -29,17 +30,28 @@
<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>2.4.2</version> <version>3.2.1</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
<goals> <goals>
<goal>shade</goal> <goal>shade</goal>
</goals> </goals>
<configuration> <configuration>
<artifactSet> <artifactSet>
</artifactSet> <includes>
</configuration> <include>me.lucko:commodore</include>
<include>org.javatuples:javatuples</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>me.lucko.commodore</pattern>
<!-- vvv Replace with the package of your plugin vvv -->
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
@ -106,21 +118,21 @@
<repository> <repository>
<id>ess-repo</id> <id>ess-repo</id>
<url>https://ci.ender.zone/plugin/repository/everything/</url> <url>https://ci.ender.zone/plugin/repository/everything/</url>
</repository>
<repository>
<id>Votifier</id>
<url>https://dl.bintray.com/nuvotifier/maven/</url>
</repository> </repository>
<repository> <repository>
<id>Multiverse-Core</id> <id>Multiverse-Core</id>
<url>http://repo.onarandombox.com/content/repositories/multiverse/</url> <url>https://repo.onarandombox.com/content/groups/public/</url>
</repository> </repository>
</repositories> <repository>
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>
</repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.reflections</groupId> <groupId>org.reflections</groupId>
<artifactId>reflections</artifactId> <artifactId>reflections</artifactId>
<version>0.9.10</version> <version>0.10.2</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
@ -145,37 +157,49 @@
<dependency> <dependency>
<groupId>org.javassist</groupId> <groupId>org.javassist</groupId>
<artifactId>javassist</artifactId> <artifactId>javassist</artifactId>
<version>3.20.0-GA</version> <version>3.28.0-GA</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.mockito</groupId> <groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId> <artifactId>mockito-core</artifactId>
<version>3.0.0</version> <version>4.2.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId> <groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId> <artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version> <version>master-SNAPSHOT</version>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>net.ess3</groupId> <groupId>net.ess3</groupId>
<artifactId>EssentialsX</artifactId> <artifactId>EssentialsX</artifactId>
<version>2.17.1</version> <version>2.17.1</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <!-- https://mvnrepository.com/artifact/com.github.NuVotifier.NuVotifier/nuvotifier-bukkit -->
<groupId>com.vexsoftware</groupId> <dependency>
<artifactId>nuvotifier-universal</artifactId> <groupId>com.github.NuVotifier.NuVotifier</groupId>
<version>2.3.4</version> <artifactId>nuvotifier-bukkit</artifactId>
<scope>provided</scope> <version>v2.7.1</version>
</dependency> <scope>provided</scope>
<dependency> </dependency>
<groupId>com.onarandombox.multiversecore</groupId> <dependency>
<artifactId>Multiverse-Core</artifactId> <groupId>com.onarandombox.multiversecore</groupId>
<version>4.0.1</version> <artifactId>Multiverse-Core</artifactId>
<scope>provided</scope> <version>4.3.1</version>
</dependency> <scope>provided</scope>
</dependency>
<dependency>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.11</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
</dependencies> </dependencies>
<organization> <organization>
<name>TBMCPlugins</name> <name>TBMCPlugins</name>
@ -192,6 +216,7 @@
<!-- 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,17 +1,38 @@
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 {
@Command2.Subcommand //TODO: Main permissions (groups) like 'mod' public ChromaCommand() {
public void reload(CommandSender sender) { getManager().addParamConverter(ButtonPlugin.class, name ->
if (MainPlugin.Instance.tryReloadConfig()) (ButtonPlugin) Optional.ofNullable(Bukkit.getPluginManager().getPlugin(name))
sender.sendMessage("§bCore config reloaded."); .filter(plugin -> plugin instanceof ButtonPlugin).orElse(null),
"No Chroma plugin found by that name.", () -> Arrays.stream(Bukkit.getPluginManager().getPlugins())
.filter(plugin -> plugin instanceof ButtonPlugin).map(Plugin::getName)::iterator);
}
@Command2.Subcommand
public void reload(CommandSender sender, @Command2.OptionalArg ButtonPlugin plugin) {
if (plugin == null)
plugin = MainPlugin.Instance;
if (plugin.tryReloadConfig())
sender.sendMessage("§b" + plugin.getName() + " config reloaded.");
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,13 +6,17 @@ 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",
@ -20,14 +24,15 @@ import java.util.Optional;
}) })
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 enables a component. If you want to permanently enable a component, change it's 'enabled' config option.\"" "Temporarily or permanently enables a component."
}) })
public boolean enable(CommandSender sender, Plugin plugin, String component) { public boolean enable(CommandSender sender, Plugin plugin, String component, @Command2.OptionalArg boolean permanent) {
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.");
@ -35,15 +40,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); return enable_disable(sender, plugin, component, true, permanent);
} }
@Subcommand(helpText = { @Subcommand(helpText = {
"Disable component", "Disable component",
"Temporarily disables a component. If you want to permanently disable a component, change it's 'enabled' config option." "Temporarily or permanently disables a component."
}) })
public boolean disable(CommandSender sender, Plugin plugin, String component) { public boolean disable(CommandSender sender, Plugin plugin, String component, @Command2.OptionalArg boolean permanent) {
return enable_disable(sender, plugin, component, false); return enable_disable(sender, plugin, component, false, permanent);
} }
@Subcommand(helpText = { @Subcommand(helpText = {
@ -57,25 +62,41 @@ public class ComponentCommand extends ICommand2MC {
return true; return true;
} }
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable) { @CustomTabCompleteMethod(param = "component", subcommand = {"enable", "disable"})
public Iterable<String> componentTabcomplete(Plugin plugin) {
return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator;
}
@CustomTabCompleteMethod(param = "plugin", subcommand = {"list", "enable", "disable"}, ignoreTypeCompletion = true)
public Iterable<String> pluginTabcomplete() {
return Component.getComponents().values().stream().map(Component::getPlugin)
.distinct().map(Plugin::getName)::iterator;
}
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) {
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);
sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled."); if (permanent)
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); TBMCCoreAPI.SendException("Couldn't " + (enable ? "en" : "dis") + "able component " + component + "!", e, (JavaPlugin) plugin);
} }
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 = Component.getComponents().values().stream() val oc = getPluginComponents(plugin).filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny();
.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); TBMCCoreAPI.SendException("Failed to enable one of the components: " + c.getClass().getSimpleName(), e, c);
} }
}); });
componentsEnabled = true; componentsEnabled = true;

View file

@ -8,9 +8,6 @@ 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;
@ -26,10 +23,12 @@ 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;
@ -38,10 +37,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 {
@ -65,48 +64,35 @@ 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 ConfigData<Boolean> writePluginList() { private final ConfigData<Boolean> writePluginList = getIConfig().getData("writePluginList", false);
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() { ConfigData<String> chatFormat = getIConfig().getData("chatFormat", "[{origin}|" +
return getIConfig().getData("chatFormat", "[{origin}|" + "{channel}] <{name}> {message}");
"{channel}] <{name}> {message}");
}
/** /**
* Print some debug information. * Print some debug information.
*/ */
public ConfigData<Boolean> test() { public final ConfigData<Boolean> test = getIConfig().getData("test", false);
return getIConfig().getData("test", false);
}
/* /**
* 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. * 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.
* 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 ConfigData<Boolean> useVaultForCommands() { public final ConfigData<Boolean> prioritizeCustomCommands = getIConfig().getData("prioritizeCustomCommands", false);
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 so this shouldn't happen if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials
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());
@ -114,8 +100,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());
@ -125,7 +111,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); TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class, TBMCPlayer::new);
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)));
@ -138,38 +124,26 @@ 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"));
if (writePluginList().get()) { Supplier<Iterable<String>> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator;
getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier);
getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier);
if (writePluginList.get()) {
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); TBMCCoreAPI.SendException("Failed to write plugin list!", e, this);
} }
} }
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...");
TBMCPlayerBase.savePlayers(); ChromaGamerBase.saveUsers();
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,6 +5,7 @@ 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;
@ -27,23 +28,32 @@ public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL) @EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerJoin(PlayerJoinEvent event) { public void OnPlayerJoin(PlayerJoinEvent event) {
TBMCPlayerBase.joinPlayer(event.getPlayer()); var p = event.getPlayer();
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class);
String pname = player.PlayerName.get();
if (pname.length() == 0) {
player.PlayerName.set(p.getName());
MainPlugin.Instance.getLogger().info("Player name saved: " + player.PlayerName.get());
} else if (!p.getName().equals(pname)) {
MainPlugin.Instance.getLogger().info(pname + " renamed to " + p.getName());
player.PlayerName.set(p.getName());
}
} }
@EventHandler(priority = EventPriority.NORMAL) @EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerLeave(PlayerQuitEvent event) { public void OnPlayerLeave(PlayerQuitEvent event) {
TBMCPlayerBase.quitPlayer(event.getPlayer()); TBMCPlayerBase.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class).uncache();
} }
@EventHandler(priority = EventPriority.HIGHEST) @EventHandler(priority = EventPriority.HIGHEST)
public void onSystemChat(TBMCSystemChatEvent event) { public void onSystemChat(TBMCSystemChatEvent event) {
if (event.isHandled()) if (event.isHandled())
return; // Only handle here if ButtonChat couldn't - ButtonChat doesn't even handle this return; // Only handle here if ButtonChat couldn't - ButtonChat doesn't even handle this
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
public void onPlayerChatPreprocess(PlayerCommandPreprocessEvent event) { public void onPlayerChatPreprocess(PlayerCommandPreprocessEvent event) {
@ -58,7 +68,9 @@ 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 ev = new TBMCCommandPreprocessEvent(sender, message); val cg = ChromaGamerBase.getFromSender(sender);
if (cg == null) throw new RuntimeException("Couldn't get user from sender for " + sender.getName() + "!");
val ev = new TBMCCommandPreprocessEvent(sender, cg.channel.get(), message, sender);
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
@ -68,9 +80,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.getMessage())); event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender(), event.getChannel(), event.getPermCheck()), event.getMessage()));
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e); TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e, MainPlugin.Instance);
} }
} }
@ -90,11 +102,12 @@ 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
String msg = MainPlugin.Instance.chatFormat().get() var channel = event.getChannel();
.replace("{channel}", event.getChannel().DisplayName().get()) String msg = MainPlugin.Instance.chatFormat.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}", event.getMessage()); .replace("{message}", String.format("§%x%s", channel.Color.get().ordinal(), 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,6 +2,7 @@ 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;
@ -19,11 +20,13 @@ 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
public Object answer(InvocationOnMock invocation) { public Object answer(InvocationOnMock invocation) {
if (returns(invocation, String.class)) if (returns(invocation, String.class))
return "test"; return "test";
if (returns(invocation, Logger.class)) if (returns(invocation, Logger.class))
@ -41,7 +44,6 @@ 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,6 +4,7 @@ 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;
@ -20,127 +21,135 @@ 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}
*/ */
public static final int SCORE_SEND_OK = 0; public static final int SCORE_SEND_OK = 0;
/** /**
* Specifies a score that means the user doesn't have permission to see or send the message. Any negative value has the same effect. * Specifies a score that means the user doesn't have permission to see or send the message. Any negative value has the same effect.
*/ */
public static final int SCORE_SEND_NOPE = -1; public static final int SCORE_SEND_NOPE = -1;
/** /**
* Send the message to everyone <i>who has access to the channel</i> - this does not necessarily mean all players * Send the message to everyone <i>who has access to the channel</i> - this does not necessarily mean all players
*/ */
public static final String GROUP_EVERYONE = "everyone"; public static final String GROUP_EVERYONE = "everyone";
private static ChannelComponent component; private static ChannelComponent component;
private String defDisplayName; private String defDisplayName;
private Color defColor; private Color defColor;
private void throwGame() { private IHaveConfig config;
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 String ID;
public ConfigData<String[]> IDs;
/**
* Filters both the sender and the targets
*/
private final Function<CommandSender, RecipientTestResult> filteranderrormsg;
private static final List<Channel> channels = new ArrayList<>();
/**
* Creates a channel.
*
* @param displayname The name that should appear at the start of the message. <b>A chat color is expected at the beginning (§9).</b>
* @param color The default color of the messages sent in the channel
* @param command The command to be used for the channel <i>without /</i>. For example "mod". It's also used for scoreboard objective names.
* @param filteranderrormsg Checks all senders against the criteria provided here and sends the message if the index matches the sender's - if no score at all, displays the error.<br>
* May be null to send to everyone.
*/
public Channel(String displayname, Color color, String command,
Function<CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = filteranderrormsg;
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
} }
public final ConfigData<Color> Color() { /**
throwGame(); * Must be only called from a subclass - otherwise it'll throw an exception.
return component.getConfig().getData(ID + ".color", defColor, c -> Color.valueOf((String) c), Enum::toString); *
} * @see Channel#Channel(String, Color, String, Function)
public final String ID; */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ConfigData<String[]> IDs() { protected <T extends Channel> Channel(String displayname, Color color, String command,
throwGame(); BiFunction<T, CommandSender, RecipientTestResult> filteranderrormsg) {
return component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList); defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
} }
/**
* Filters both the sender and the targets
*/
private final Function<CommandSender, RecipientTestResult> filteranderrormsg;
private static final List<Channel> channels = new ArrayList<>(); 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() {
* Creates a channel. return filteranderrormsg == null;
* }
* @param displayname The name that should appear at the start of the message. <b>A chat color is expected at the beginning (§9).</b>
* @param color The default color of the messages sent in the channel
* @param command The command to be used for the channel <i>without /</i>. For example "mod". It's also used for scoreboard objective names.
* @param filteranderrormsg Checks all senders against the criteria provided here and sends the message if the index matches the sender's - if no score at all, displays the error.<br>
* May be null to send to everyone.
*/
public Channel(String displayname, Color color, String command,
Function<CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = filteranderrormsg;
}
/** /**
* Must be only called from a subclass - otherwise it'll throw an exception. * Note: Errors are sent to the sender automatically
* *
* @see Channel#Channel(String, Color, String, Function) * @param sender The user we're sending to
*/ * @param score The (source) score to compare with the user's
@SuppressWarnings("unchecked") */
protected <T extends Channel> Channel(String displayname, Color color, String command, public boolean shouldSendTo(CommandSender sender, int score) {
BiFunction<T, CommandSender, RecipientTestResult> filteranderrormsg) { return score == getMCScore(sender); //If there's any error, the score won't be equal
defDisplayName = displayname; }
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
}
public boolean isGlobal() { /**
return filteranderrormsg == null; * Note: Errors are sent to the sender automatically
} */
public int getMCScore(CommandSender sender) {
return getRTR(sender).score; //No need to check if there was an error
}
/** /**
* Note: Errors are sent to the sender automatically * Note: Errors are sent to the sender automatically<br>
* * <p>
* @param sender The user we're sending to * Null means don't send
* @param score The (source) score to compare with the user's */
*/ @Nullable
public boolean shouldSendTo(CommandSender sender, int score) { public String getGroupID(CommandSender sender) {
return score == getMCScore(sender); //If there's any error, the score won't be equal return getRTR(sender).groupID; //No need to check if there was an error
} }
/** public RecipientTestResult getRTR(CommandSender sender) {
* Note: Errors are sent to the sender automatically if (filteranderrormsg == null)
*/ return new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
public int getMCScore(CommandSender sender) { return filteranderrormsg.apply(sender);
return getRTR(sender).score; //No need to check if there was an error }
}
/**
* Note: Errors are sent to the sender automatically<br>
* <p>
* Null means don't send
*/
@Nullable
public String getGroupID(CommandSender sender) {
return getRTR(sender).groupID; //No need to check if there was an error
}
public RecipientTestResult getRTR(CommandSender sender) {
if (filteranderrormsg == null)
return new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
return filteranderrormsg.apply(sender);
}
/** /**
* Get a stream of the enabled channels * Get a stream of the enabled channels
@ -148,7 +157,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());
} }
/** /**
@ -160,72 +169,69 @@ public class Channel {
return Collections.unmodifiableList(channels); return Collections.unmodifiableList(channels);
} }
/** /**
* Convenience method for the function parameter of {@link #Channel(String, Color, String, Function)}. It checks if the sender is OP or optionally has the specified group. The error message is * Convenience method for the function parameter of {@link #Channel(String, Color, String, Function)}. It checks if the sender is OP or optionally has the specified group. The error message is
* generated automatically. * generated automatically.
* *
* @param permgroup The group that can access the channel or <b>null</b> to only allow OPs. * @param permgroup The group that can access the channel or <b>null</b> to only allow OPs.
* @return If has access * @return If has access
*/ */
public static Function<CommandSender, RecipientTestResult> inGroupFilter(String permgroup) { public static Function<CommandSender, RecipientTestResult> inGroupFilter(String permgroup) {
return noScoreResult( return noScoreResult(
s -> s.isOp() || (permgroup != null && (s instanceof Player && MainPlugin.permission != null && MainPlugin.permission.playerInGroup((Player) s, permgroup))), s -> s.isOp() || (permgroup != null && (s instanceof Player && MainPlugin.permission != null && MainPlugin.permission.playerInGroup((Player) s, permgroup))),
"You need to be a(n) " + (permgroup != null ? permgroup : "OP") + " to use this channel."); "You need to be a(n) " + (permgroup != null ? permgroup : "OP") + " to use this channel.");
} }
public static Function<CommandSender, RecipientTestResult> noScoreResult(Predicate<CommandSender> filter, public static Function<CommandSender, RecipientTestResult> noScoreResult(Predicate<CommandSender> filter,
String errormsg) { String errormsg) {
return s -> filter.test(s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg); return s -> filter.test(s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
} }
public static <T extends Channel> BiFunction<T, CommandSender, RecipientTestResult> noScoreResult( public static <T extends Channel> BiFunction<T, CommandSender, RecipientTestResult> noScoreResult(
BiPredicate<T, CommandSender> filter, String errormsg) { BiPredicate<T, CommandSender> filter, String errormsg) {
return (this_, s) -> filter.test(this_, s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg); return (this_, s) -> filter.test(this_, s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
} }
public static Channel GlobalChat; public static Channel GlobalChat;
public static Channel AdminChat; public static Channel AdminChat;
public static Channel ModChat; public static Channel ModChat;
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
} }
public static class RecipientTestResult { public static class RecipientTestResult {
public final String errormessage; public final String errormessage;
public final int score; // Anything below 0 is "never send" public final int score; // Anything below 0 is "never send"
public final String groupID; public final String groupID;
public static final RecipientTestResult ALL = new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE); public static final RecipientTestResult ALL = new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
/** /**
* Creates a result that indicates an <b>error</b> * Creates a result that indicates an <b>error</b>
* *
* @param errormessage The error message to show the sender if they don't meet the criteria. * @param errormessage The error message to show the sender if they don't meet the criteria.
*/ */
public RecipientTestResult(String errormessage) { public RecipientTestResult(String errormessage) {
this.errormessage = errormessage; this.errormessage = errormessage;
this.score = SCORE_SEND_NOPE; this.score = SCORE_SEND_NOPE;
this.groupID = null; this.groupID = null;
} }
/** /**
* Creates a result that indicates a <b>success</b> * Creates a result that indicates a <b>success</b>
* *
* @param score The score that identifies the target group. <b>Must be non-negative.</b> For example, the index of the town or nation to send to. * @param score The score that identifies the target group. <b>Must be non-negative.</b> For example, the index of the town or nation to send to.
* @param groupID The ID of the target group. * @param groupID The ID of the target group.
*/ */
public RecipientTestResult(int score, String groupID) { public RecipientTestResult(int score, String groupID) {
if (score < 0) throw new IllegalArgumentException("Score must be non-negative!"); if (score < 0) throw new IllegalArgumentException("Score must be non-negative!");
this.score = score; this.score = score;
this.groupID = groupID; this.groupID = groupID;
this.errormessage = null; this.errormessage = null;
} }
} }
} }

View file

@ -1,13 +1,17 @@
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 { public class ChannelComponent extends Component<JavaPlugin> {
static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave; static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave;
@Override @Override
@ -29,6 +33,51 @@ public class ChannelComponent extends Component {
@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,7 +19,6 @@ 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;
} }
@ -39,8 +38,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,23 +25,17 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
/** /**
* The permission group to give to the player * The permission group to give to the player
*/ */
ConfigData<String> memberGroup() { final ConfigData<String> memberGroup = getConfig().getData("memberGroup", "member");
return getConfig().getData("memberGroup", "member");
}
/** /**
* The amount of hours needed to play before promotion * The amount of hours needed to play before promotion
*/ */
private ConfigData<Integer> playedHours() { private final ConfigData<Integer> playedHours = getConfig().getData("playedHours", 12);
return getConfig().getData("playedHours", 12);
}
/** /**
* The amount of days passed since first login * The amount of days passed since first login
*/ */
private ConfigData<Integer> registeredForDays() { private final ConfigData<Integer> registeredForDays = getConfig().getData("registeredForDays", 7);
return getConfig().getData("registeredForDays", 7);
}
private AbstractMap.SimpleEntry<Statistic, Integer> playtime; private AbstractMap.SimpleEntry<Statistic, Integer> playtime;
@ -69,22 +63,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!");
MainPlugin.Instance.getLogger().info("Added " + player.getName() + " as a member."); log("Added " + player.getName() + " as a member.");
return true; return true;
} else { } else {
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."); logWarn("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) {
MainPlugin.Instance.getLogger().warning("Failed to assign the member role! Groups are not supported by the permissions implementation."); logWarn("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) {
@ -92,14 +86,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;
@ -113,7 +107,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,11 +59,10 @@ public class RandomTP extends ICommand2MC
{ {
world = Bukkit.getWorld("World"); world = Bukkit.getWorld("World");
border = world.getWorldBorder(); border = world.getWorldBorder();
Logger logger = component.getPlugin().getLogger(); component.log("Getting new location");
logger.info("Getting new location");
if(border.getSize() > 100000) if(border.getSize() > 100000)
logger.warning("World border is wide, it may take a minute..."); component.logWarn("World border is wide, it may take a minute...");
logger.info("Success: "+newLocation()); component.log("Success: "+newLocation());
} }
/*================================================================================================*/ /*================================================================================================*/

View file

@ -21,6 +21,7 @@ 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,6 +4,8 @@ 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;
@ -13,16 +15,29 @@ 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() {
registerCommand(new ScheduledRestartCommand(this)); var scheduledRestartCommand = 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
@ -30,10 +45,28 @@ 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()
@ -43,7 +76,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 * 1000000000L - lasttime > 0) { //Ten seconds passed since last reminder } else if (!(event.getPlayer() instanceof IFakePlayer) && System.nanoTime() - 10 * 60 * 1000000000L - lasttime > 0) { //10 minutes 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

@ -45,20 +45,20 @@ public class ScheduledRestartCommand extends ICommand2MC {
} }
final int restarttime = restartCounter = seconds * 20; final int restarttime = restartCounter = seconds * 20;
restartbar = Bukkit.createBossBar("Server restart in " + seconds + "s", BarColor.RED, BarStyle.SOLID, restartbar = Bukkit.createBossBar("Server restart in " + seconds + "s", BarColor.RED, BarStyle.SOLID,
BarFlag.DARKEN_SKY); BarFlag.DARKEN_SKY);
restartbar.setProgress(1); restartbar.setProgress(1);
Bukkit.getOnlinePlayers().forEach(p -> restartbar.addPlayer(p)); Bukkit.getOnlinePlayers().forEach(p -> restartbar.addPlayer(p));
sender.sendMessage("Scheduled restart in " + seconds); sender.sendMessage("Scheduled restart in " + seconds);
ScheduledServerRestartEvent e = new ScheduledServerRestartEvent(restarttime, this); ScheduledServerRestartEvent e = new ScheduledServerRestartEvent(restarttime, this);
Bukkit.getPluginManager().callEvent(e); Bukkit.getPluginManager().callEvent(e);
restarttask = Bukkit.getScheduler().runTaskTimer(MainPlugin.Instance, () -> { restarttask = Bukkit.getScheduler().runTaskTimer(MainPlugin.Instance, () -> {
if (restartCounter < 0) { if (restartCounter < 0) {
restarttask.cancel(); restarttask.cancel();
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! (/press)", component.getRestartBroadcast()); TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds!", 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,6 +2,7 @@ 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;
@ -23,11 +24,12 @@ 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();
} }
@ -47,14 +49,13 @@ 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);
@ -77,9 +78,7 @@ 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 ConfigData<String> targetServer() { private final ConfigData<String> targetServer = getConfig().getData("targetServer", "");
return getConfig().getData("targetServer", "");
}
private Location spawnloc; private Location spawnloc;
@ -91,7 +90,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)
@ -106,7 +105,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

@ -0,0 +1,61 @@
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,54 +1,18 @@
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;
/** /**
* Automatically renames Towny players if they changed their Minecraft name * Provides a command to remove invalid Towny residents.
*/ */
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

@ -1,193 +0,0 @@
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

@ -1,21 +0,0 @@
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

@ -1,42 +0,0 @@
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

@ -1,51 +0,0 @@
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 indentify the user. * @return The full name that can be used to uniquely identify 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 event cancelled state or false if async. * @return The value supplied by the action or def 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,4 +86,16 @@ 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,14 @@
package buttondevteam.lib; package buttondevteam.lib;
import org.bukkit.event.Event; import org.bukkit.Bukkit;
import org.bukkit.event.Event;
class EventExceptionCoreHandler extends EventExceptionHandler {
class EventExceptionCoreHandler extends EventExceptionHandler {
@Override
public boolean handle(Throwable ex, Event event) { @Override
TBMCCoreAPI.SendException("An error occured while executing " + event.getEventName() + "!", ex); public boolean handle(Throwable ex, Event event) {
return true; TBMCCoreAPI.SendException("An error occured while executing " + event.getEventName() + "!", ex, false, Bukkit.getLogger()::warning);
} return true;
}
}
}

View file

@ -30,7 +30,6 @@ 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); //TODO: Performance-wise it'd be much better to use serialization for player data - it's only converted once return channel.getGroupID(sender);
} }
} }

View file

@ -30,13 +30,9 @@ 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; // TODO: Message object with data? this.message = message;
} }
/*
* 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,6 +1,8 @@
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;
@ -14,20 +16,18 @@ 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 String message; private final 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.core.component.updater.PluginUpdater; import buttondevteam.lib.architecture.Component;
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,6 +16,8 @@ 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>() {
@ -30,28 +32,6 @@ 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();
@ -74,11 +54,21 @@ 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) { public static void SendException(String sourcemsg, Throwable e, Component<?> component) {
SendException(sourcemsg, e, false); SendException(sourcemsg, e, false, component::logWarn);
} }
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);
@ -87,7 +77,7 @@ public class TBMCCoreAPI {
if (!event.isHandled()) if (!event.isHandled())
exceptionsToSend.put(sourcemsg, e); exceptionsToSend.put(sourcemsg, e);
} }
Bukkit.getLogger().warning(sourcemsg); logWarn.accept(sourcemsg);
e.printStackTrace(); e.printStackTrace();
if (debugPotato) { if (debugPotato) {
List<Player> devsOnline = new ArrayList<>(); List<Player> devsOnline = new ArrayList<>();
@ -128,6 +118,7 @@ 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
* *
@ -139,8 +130,8 @@ public class TBMCCoreAPI {
EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler); EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler);
} }
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass) { public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass, Supplier<T> constructor) {
ChromaGamerBase.RegisterPluginUserClass(userclass); ChromaGamerBase.RegisterPluginUserClass(userclass, constructor);
} }
/** /**
@ -180,6 +171,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 Command2MC command2MC = new Command2MC(); private static final Command2MC command2MC = new Command2MC();
@Getter(AccessLevel.PROTECTED) @Getter(AccessLevel.PROTECTED)
private IHaveConfig iConfig; private final IHaveConfig iConfig = new IHaveConfig(this::saveConfig);
private CommentedConfiguration yaml; private CommentedConfiguration yaml;
@Getter(AccessLevel.PROTECTED) @Getter(AccessLevel.PROTECTED)
private IHaveConfig data; //TODO private IHaveConfig data; //TODO
@ -35,8 +35,7 @@ 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 Stack<Component<?>> componentStack = new Stack<>(); private final Stack<Component<?>> componentStack = new Stack<>();
;
protected abstract void pluginEnable(); protected abstract void pluginEnable();
@ -60,7 +59,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); TBMCCoreAPI.SendException("Error while enabling plugin " + getName() + "!", e, this);
} }
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);
@ -72,7 +71,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 = new IHaveConfig(section, this::saveConfig); iConfig.reset(section);
return true; return true;
} }
@ -84,10 +83,9 @@ 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); TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e, this);
} }
} }
@ -126,15 +124,17 @@ 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(), Arrays.stream(((String) kv.getValue()).split("\n")) yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""),
.map(str -> "# " + str.trim()).toArray(String[]::new)); Arrays.stream(((String) kv.getValue()).split("\n"))
.map(str -> "# " + str.trim()).toArray(String[]::new));
return true; return true;
} }
@Override @Override
public FileConfiguration getConfig() { public FileConfiguration getConfig() {
if (yaml == null) if (yaml == null)
justReload(); //TODO: If it fails to load, it'll probably throw an NPE justReload();
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); TBMCCoreAPI.SendException("Failed to save config", e, this);
} }
} }

View file

@ -10,6 +10,7 @@ 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;
@ -19,7 +20,7 @@ import java.util.HashMap;
* *
* @author dumptruckman &amp; Articdive * @author dumptruckman &amp; Articdive
*/ */
public class CommentedConfiguration extends YamlConfiguration { //TODO: Remove FileMgmt dependency public class CommentedConfiguration extends YamlConfiguration {
private HashMap<String, String> comments; private HashMap<String, String> comments;
private File file; private File file;
@ -174,7 +175,7 @@ public class CommentedConfiguration extends YamlConfiguration { //TODO: Remove F
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()); Files.write(file.toPath(), newContents.toString().getBytes(StandardCharsets.UTF_8));
} }
} }

View file

@ -31,14 +31,11 @@ public abstract class Component<TP extends JavaPlugin> {
@Getter @Getter
@NonNull @NonNull
private TP plugin; private TP plugin;
@NonNull private @Getter final IHaveConfig config = new IHaveConfig(null);
private @Getter
IHaveConfig config;
private @Getter IHaveConfig data; //TODO private @Getter IHaveConfig data; //TODO
public final ConfigData<Boolean> shouldBeEnabled() { public final ConfigData<Boolean> shouldBeEnabled = config.getData("enabled",
return config.getData("enabled", Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true)); 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>
@ -61,7 +58,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 ButtonPlugin> boolean unregisterComponent(T plugin, Component<T> component) { public static <T extends JavaPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
return registerUnregisterComponent(plugin, component, false); return registerUnregisterComponent(plugin, component, false);
} }
@ -79,21 +76,22 @@ 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!")); TBMCCoreAPI.SendException("Failed to register component " + component.getClassName(), new IllegalArgumentException("The component is already registered!"), plugin);
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); TBMCCoreAPI.SendException("Failed to enable component " + component.getClassName() + "!", e, component);
return true; return true;
} }
} }
@ -104,7 +102,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); TBMCCoreAPI.SendException("Failed to disable component " + component.getClassName() + "!", e, component);
return false; //If failed to disable, won't unregister either return false; //If failed to disable, won't unregister either
} }
} }
@ -113,47 +111,56 @@ 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); TBMCCoreAPI.SendException("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + "!", e, plugin);
return false; return false;
} }
} }
/** /**
* Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.<br> * Enables or disables the given component. If the component fails to enable, it will be disabled.
* 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) {
//System.out.println("Updating config for "+component.getClassName()); try {
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)) { IHaveConfig.pregenConfig(component, null);
//System.out.println("Pregenning config for "+component.getClassName()); }
IHaveConfig.pregenConfig(component, null); } catch (Exception e) {
try { //Automatically disable components that fail to enable properly
setComponentEnabled(component, false);
throw e;
} catch (Exception ex) {
Throwable t = ex;
for (var th = t; th != null; th = th.getCause())
t = th; //Set if not null
if (t != e)
t.initCause(e);
throw ex;
}
} }
//System.out.println("Done enabling "+component.getClassName());
} else { } 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 = new IHaveConfig(configSect, plugin::saveConfig); component.config.reset(configSect);
} else //Testing } //Testing: it's already set
component.config = new IHaveConfig(null, plugin::saveConfig);
} }
/** /**
@ -166,13 +173,21 @@ 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", "WeakerAccess"}) @SuppressWarnings({"unused"})
protected void register(JavaPlugin plugin) { protected void register(JavaPlugin plugin) {
} }
@ -183,7 +198,7 @@ public abstract class Component<TP extends JavaPlugin> {
* *
* @param plugin Plugin object * @param plugin Plugin object
*/ */
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"unused"})
protected void unregister(JavaPlugin plugin) { protected void unregister(JavaPlugin plugin) {
} }
@ -237,10 +252,15 @@ 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 -> new IHaveConfig((ConfigurationSection) kv.getValue(), getPlugin()::saveConfig))); .collect(Collectors.toMap(Map.Entry::getKey, kv -> {
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(cs.createSection(entry.getKey()), getPlugin()::saveConfig); val conf = new IHaveConfig(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,13 +2,9 @@ package buttondevteam.lib.architecture;
import buttondevteam.core.MainPlugin; import buttondevteam.core.MainPlugin;
import buttondevteam.lib.ChromaUtils; import buttondevteam.lib.ChromaUtils;
import lombok.AccessLevel; import lombok.*;
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;
@ -22,64 +18,67 @@ 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 final ConfigurationSection config; private IHaveConfig 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 Function<Object, T> getter; private final 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 Function<T, Object> setter; private final 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;
//This constructor is needed because it sets the getter and setter ConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
ConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter, Runnable saveAction) { if (def == null) {
if (primitiveDef == null)
throw new IllegalArgumentException("Either def or primitiveDef must be set.");
if (getter == null)
throw new IllegalArgumentException("A getter and setter must be present when using primitiveDef.");
def = getter.apply(primitiveDef);
} else if (primitiveDef == null)
if (setter == null)
primitiveDef = def;
else
primitiveDef = setter.apply(def);
if ((getter == null) != (setter == null))
throw new IllegalArgumentException("Both setters and getters must be present (or none if def is primitive).");
this.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;
this.saveAction = saveAction; get(); //Generate config automatically
}
@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{" + return "ConfigData{" + "path='" + path + '\'' + ", value=" + value + '}';
"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;
@ -119,21 +118,27 @@ 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 != null) if (config.getConfig() != null)
setInternal(val); setInternal(val);
this.value = value; this.value = value;
} }
private void setInternal(Object val) { private void setInternal(Object val) {
config.set(path, val); config.getConfig().set(path, val);
if (!saveTasks.containsKey(config.getRoot())) { signalChange(config);
}
static void signalChange(IHaveConfig config) {
var cc = config.getConfig();
var sa = config.getSaveAction();
if (!saveTasks.containsKey(cc.getRoot())) {
synchronized (saveTasks) { synchronized (saveTasks) {
saveTasks.put(config.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> { saveTasks.put(cc.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> {
synchronized (saveTasks) { synchronized (saveTasks) {
saveTasks.remove(config.getRoot()); saveTasks.remove(cc.getRoot());
saveAction.run(); sa.run();
} }
}, 100), saveAction)); }, 100), sa));
} }
} }
} }
@ -156,4 +161,92 @@ 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,8 +3,11 @@ 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;
@ -18,22 +21,41 @@ 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;
private final Runnable saveAction; @Getter
@Setter
private Runnable saveAction;
/** /**
* May be used in testing. * May be used in testing.
* *
* @param section May be null for testing * @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null.
*/ */
IHaveConfig(ConfigurationSection section, Runnable saveAction) { public IHaveConfig(Runnable saveAction) {
config = section;
this.saveAction = saveAction; this.saveAction = saveAction;
} }
/** /**
* This method overload should only be used with primitves or String. * Gets a config object for the given path. The def or primitiveDef must be set. If a getter is present, a setter must be present as well.
*
* @param path The dot-separated path relative to this config instance
* @param <T> The runtime type of the config value
* @return A ConfigData builder to set how to obtain the value
*/
public <T> ConfigData.ConfigDataBuilder<T> getConfig(String path) {
return ConfigData.builder(this, path);
}
void onConfigBuild(ConfigData<?> config) {
datamap.put(config.getPath(), config);
}
/**
* This method overload should only be used with primitives or String.
* *
* @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
@ -43,7 +65,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<>(config, path, def, def, saveAction)); if (data == null) datamap.put(path, data = new ConfigData<>(this, path, def, def, null, null));
return (ConfigData<T>) data; return (ConfigData<T>) data;
} }
@ -61,7 +83,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<>(config, path, def, setter.apply(def), getter, setter, saveAction)); datamap.put(path, data = new ConfigData<>(this, path, def, setter.apply(def), getter, setter));
return (ConfigData<T>) data; return (ConfigData<T>) data;
} }
@ -79,7 +101,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<>(config, path, getter.apply(primitiveDef), primitiveDef, getter, setter, saveAction)); datamap.put(path, data = new ConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
return (ConfigData<T>) data; return (ConfigData<T>) data;
} }
@ -97,7 +119,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<>(config, path, getter.apply(primitiveDef), primitiveDef, getter, setter, saveAction)); datamap.put(path, data = new ReadOnlyConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
return (ReadOnlyConfigData<T>) data; return (ReadOnlyConfigData<T>) data;
} }
@ -114,7 +136,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<>(config, path, defval, defval, saveAction)); datamap.put(path, data = new ConfigData<>(this, path, defval, defval, null, null));
} }
return (ConfigData<T>) data; return (ConfigData<T>) data;
} }
@ -134,7 +156,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<>(config, path, defval, setter.apply(defval), getter, setter, saveAction)); datamap.put(path, data = new ConfigData<>(this, path, defval, setter.apply(defval), getter, setter));
} }
return (ConfigData<T>) data; return (ConfigData<T>) data;
} }
@ -150,10 +172,25 @@ 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<>(config, path, new ListConfigData.List<T>(), saveAction)); datamap.put(path, data = new ListConfigData<>(this, path, new ListConfigData.List<T>()));
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.
* *
@ -164,6 +201,13 @@ 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;
@ -176,24 +220,36 @@ 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) {
TBMCCoreAPI.SendException("Failed to pregenerate " + m.getName() + " for " + obj + " using config " + kv.getKey() + "!", e); String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!";
if (obj instanceof Component<?>)
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
else if (obj instanceof JavaPlugin)
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj);
else
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning);
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 " + m.getName() + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes())); MainPlugin.Instance.getLogger().warning("Method " + mName + " 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(m.getName()); c.setPath(mName);
else if (!c.getPath().equals(m.getName())) else if (!c.getPath().equals(mName))
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + m.getName()); MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + mName);
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) {
TBMCCoreAPI.SendException("Failed to pregenerate " + m.getName() + " for " + obj + "!", e); String msg = "Failed to pregenerate " + mName + " for " + obj + "!";
if (obj instanceof Component<?>)
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
else if (obj instanceof JavaPlugin)
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj);
else
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning);
} }
} }
} }

View file

@ -1,7 +1,6 @@
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;
@ -12,12 +11,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(ConfigurationSection config, String path, List<T> def, Runnable saveAction) { ListConfigData(IHaveConfig config, String path, List<T> def) {
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, saveAction); }, ArrayList::new);
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,15 +1,13 @@
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(ConfigurationSection config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter, Runnable saveAction) { ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
super(config, path, def, primitiveDef, getter, setter, saveAction); super(config, path, def, primitiveDef, getter, setter);
} }
ReadOnlyConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Runnable saveAction) { ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef) {
super(config, path, def, primitiveDef, saveAction); super(config, path, def, primitiveDef, null, null);
} }
} }

View file

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

View file

@ -11,7 +11,6 @@ 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;
@ -23,14 +22,18 @@ 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.*; import java.util.ArrayList;
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 extends Command2Sender> { public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Sender> {
protected Command2() { protected Command2() {
commandHelp.add("§6---- Commands ----"); commandHelp.add("§6---- Commands ----");
} }
@ -60,10 +63,12 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
String[] helpText() default {}; String[] helpText() default {};
/** /**
* The main permission which allows using this command (individual access can be still granted with "chroma.command.X"). * The main permission which allows using this command (individual access can be still revoked 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 ""; //TODO String permGroup() default "";
String[] aliases() default {};
} }
@Target(ElementType.PARAMETER) @Target(ElementType.PARAMETER)
@ -72,13 +77,14 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
} }
@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;
@ -101,18 +107,19 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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 HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>(); protected final HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
private HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>(); protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
private ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord private final ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
private char commandChar; private char commandChar;
@ -120,25 +127,27 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
* Adds a param converter that obtains a specific object from a string parameter. * Adds a param converter that obtains a specific object from a string parameter.
* The converter may return null. * The converter may return null.
* *
* @param cl The class of the result object * @param <T> The type of the result
* @param converter The converter to use * @param cl The class of the result object
* @param <T> The type of the result * @param converter The converter to use
* @param allSupplier The supplier of all possible values (ideally)
*/ */
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,
paramConverters.put(cl, new ParamConverter<>(converter, errormsg)); Supplier<Iterable<String>> allSupplier) {
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); //O(1) SubcommandData<TC> sd = subcommands.get(subcommand);
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); TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e, MainPlugin.Instance);
} }
}); });
return true; //We found a method return true; //We found a method
@ -158,7 +167,7 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
* @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
*/ */
public void handleCommandAsync(TP sender, String commandline, SubcommandData<TC> sd, String subcommand, boolean sync) throws Exception { private 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;
@ -221,6 +230,18 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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);
@ -232,7 +253,7 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
} }
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.toString() + "'"); throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method + "'");
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
@ -242,6 +263,7 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
} }
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
@ -249,9 +271,9 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
} 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!", e.getCause()); TBMCCoreAPI.SendException("An error occurred in a command handler for " + subcommand + "!", e.getCause(), MainPlugin.Instance);
} catch (Exception e) { } catch (Exception e) {
TBMCCoreAPI.SendException("Command handling failed for sender " + sender + " and subcommand " + subcommand, e); TBMCCoreAPI.SendException("Command handling failed for sender " + sender + " and subcommand " + subcommand, e, MainPlugin.Instance);
} }
}; };
if (sync) if (sync)
@ -262,9 +284,12 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
public abstract void registerCommand(TC command); public abstract void registerCommand(TC command);
protected void registerCommand(TC command, @SuppressWarnings("SameParameterValue") char commandChar) { protected List<SubcommandData<TC>> registerCommand(TC command, char commandChar) {
return registerCommand(command, command.getCommandPath(), commandChar);
}
protected List<SubcommandData<TC>> registerCommand(TC command, String path, @SuppressWarnings("SameParameterValue") char commandChar) {
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
@ -273,7 +298,7 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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, String.class); mainMethod = command.getClass().getMethod("def", Command2Sender.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)
@ -284,46 +309,53 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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); TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e, MainPlugin.Instance);
} }
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) { if (ht != null) { //The method is a subcommand
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'
ht = getParameterHelp(method, ht, subcommand); var params = new String[method.getParameterCount() - 1];
subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text ht = getParameterHelp(method, ht, subcommand, params);
var sd = new SubcommandData<>(method, command, params, ht);
registerCommand(path, method.getName(), ann, sd);
for (String p : command.getCommandPaths())
registerCommand(p, method.getName(), ann, sd);
addedSubcommands.add(sd);
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
subcommands.put(commandChar + path, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0]))); var sd = new SubcommandData<>(mainMethod, command, null, scmdHelpList.toArray(new String[0]));
if (mainMethod != null && !mainPath.equals(commandChar + path)) { //Main command, typically the same as the above subcommands.put(commandChar + path, sd);
if (isSubcommand) { //The class itself is a subcommand addedSubcommands.add(sd);
val scmd = subcommands.computeIfAbsent(mainPath, p -> new SubcommandData<>(null, null, new String[]{"§6---- Subcommands ----"}));
val scmdHelp = Arrays.copyOf(scmd.helpText, scmd.helpText.length + scmdHelpList.size());
for (int i = 0; i < scmdHelpList.size(); i++)
scmdHelp[scmd.helpText.length + i] = scmdHelpList.get(i);
scmd.helpText = scmdHelp;
} else if (!subcommands.containsKey(mainPath))
subcommands.put(mainPath, new SubcommandData<>(null, null, scmdHelpList.toArray(new String[0])));
} }
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 scmdHelp = Arrays.copyOf(scmd.helpText, scmd.helpText.length + scmdHelpList.size());
for (int i = 0; i < scmdHelpList.size(); i++)
scmdHelp[scmd.helpText.length + i] = scmdHelpList.get(i);
scmd.helpText = scmdHelp;
}
return addedSubcommands;
} }
private String[] getParameterHelp(Method method, String[] ht, String subcommand) { private String[] getParameterHelp(Method method, String[] ht, String subcommand, String[] parameters) {
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!")); TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!"), MainPlugin.Instance);
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()); val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName().replace('$', '.'));
if (ccs != null) { if (ccs != null) {
val cs = ccs.getConfigurationSection(method.getName()); val cs = ccs.getConfigurationSection(method.getName());
if (cs != null) { if (cs != null) {
@ -335,16 +367,26 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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.toString() + "' != " + mname + " or params is " + params)); TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance);
} else } else
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("cs is " + cs)); MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (cs is null)! Make sure to use 'clean install' when building the project.");
} else } else
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("ccs is " + ccs + " - class: " + method.getDeclaringClass().getCanonicalName())); MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (ccs is null)! Make sure to use 'clean install' when building the project.");
} }
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() {
@ -371,11 +413,19 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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;
val subcommand = commandChar + path + getCommandPath(method.getName(), ' '); unregisterCommand(path, method.getName(), ann);
subcommands.remove(subcommand); 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);
for (String alias : ann.aliases())
subcommands.remove(commandChar + path + alias);
}
/** /**
* It will start with the given replace char. * It will start with the given replace char.
* *

View file

@ -1,26 +1,41 @@
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.CommandSender; import org.bukkit.command.*;
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.HashMap; import java.util.Collections;
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 {
/** /**
@ -30,7 +45,19 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
*/ */
@Override @Override
public void registerCommand(ICommand2MC command) { public void registerCommand(ICommand2MC command) {
super.registerCommand(command, '/'); /*String mainpath;
var plugin = command.getPlugin();
{
String cpath = command.getCommandPath();
int i = cpath.indexOf(' ');
mainpath = cpath.substring(0, i == -1 ? cpath.length() : i);
}*/
var subcmds = super.registerCommand(command, '/');
var bcmd = registerOfficially(command, subcmds);
if (bcmd != null)
for (String alias : bcmd.getAliases())
super.registerCommand(command, command.getCommandPath().replaceFirst("^" + bcmd.getName(), Matcher.quoteReplacement(alias)), '/');
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,
@ -46,9 +73,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;
perm = "chroma." + pg; String permGroup = "chroma." + pg;
if (Bukkit.getPluginManager().getPermission(perm) == null) //It may occur multiple times if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(new Permission(perm, Bukkit.getPluginManager().addPermission(new Permission(permGroup,
PermissionDefault.OP)); //Do not allow any commands that belong to a group PermissionDefault.OP)); //Do not allow any commands that belong to a group
} }
} }
@ -77,8 +104,6 @@ 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
} }
@ -129,8 +154,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) { public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, Supplier<Iterable<String>> allSupplier) {
super.addParamConverter(cl, converter, "§c" + errormsg); super.addParamConverter(cl, converter, "§c" + errormsg, allSupplier);
} }
public void unregisterCommands(ButtonPlugin plugin) { public void unregisterCommands(ButtonPlugin plugin) {
@ -148,111 +173,310 @@ 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
private void handleTabComplete(TabCompleteEvent event) { public void onTabComplete(TabCompleteEvent event) {
String commandline = event.getBuffer(); try {
CommandSender sender = event.getSender(); event.getCompletions().clear(); //Remove player names
//System.out.println("tab"); } catch (UnsupportedOperationException e) {
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { //System.out.println("Tabcomplete: " + event.getBuffer());
String subcommand = commandline.substring(0, i).toLowerCase(); //System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions"));
if (subcommand.length() == 0 || subcommand.charAt(0) != '/') subcommand = '/' + subcommand; //Console //System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners()));
//System.out.println("Subcommand: " + subcommand); }
SubcommandData<ICommand2MC> sd = subcommands.get(subcommand); //O(1) }*/
if (sd == null) continue;
//System.out.println("ht: " + Arrays.toString(sd.helpText)); @Override
Arrays.stream(sd.helpText).skip(1).map(ht -> new HashMap.SimpleEntry<>(ht, subcommands.get(ht))).filter(e -> e.getValue() != null) public boolean handleCommand(Command2MCSender sender, String commandline) {
.filter(kv -> kv.getKey().startsWith(commandline)) return handleCommand(sender, commandline, true);
.filter(kv -> hasPermission(sender, kv.getValue().command, kv.getValue().method)) }
.forEach(kv -> event.getCompletions().add((kv.getKey()).substring(kv.getKey().lastIndexOf(' ', commandline.length()) + 1)));
if (sd.method == null || sd.command == null) private boolean handleCommand(Command2MCSender sender, String commandline, boolean checkPlugin) {
return; int i = commandline.indexOf(' ');
/*if (!hasPermission(sender, sd.command, sd.method)) { - TODO: Arguments String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash
sender.sendMessage("§cYou don't have permission to use this command"); PluginCommand pcmd;
return true; 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;
} }
val params = new ArrayList<Object>(sd.method.getParameterCount()); if (CommodoreProvider.isSupported())
int j = subcommand.length(), pj; TabcompleteHelper.registerTabcomplete(command, subcmds, bukkitCommand);
Class<?>[] parameterTypes = sd.method.getParameterTypes(); return bukkitCommand;
if (parameterTypes.length == 0) } catch (Exception e) {
throw new Exception("No sender parameter for method '" + sd.method + "'"); if (command.getComponent() == null)
val sendertype = parameterTypes[0]; TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getPlugin());
final ChromaGamerBase cg; else
if (sendertype.isAssignableFrom(sender.getClass())) TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getComponent());
params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type shouldRegisterOfficially = false;
else if (sender instanceof Command2MCSender return null;
&& sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
params.add(((Command2MCSender) sender).getSender());
else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
&& sender instanceof Command2MCSender
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
&& cg.getClass() == sendertype) //The command expects a user of our system
params.add(cg);
else {
sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command.");
return true;
}
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;
}
}
if (paramArr[i1].isVarArgs()) {
par0ams.add(commandline.substring(j + 1).split(" +"));
continue;
}
j = commandline.indexOf(' ', j + 1); //End index
if (j == -1 || paramArr[i1].isAnnotationPresent(TextArg.class)) //Last parameter
j = commandline.length();
String param = commandline.substring(pj, j);
if (cl == String.class) {
params.add(param);
continue;
} else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
try {
//noinspection unchecked
Number n = ThorpeUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
params.add(n);
} catch (ParseException e) {
sender.sendMessage("§c'" + param + "' is not a number.");
return true;
}
continue;
}
val conv = paramConverters.get(cl);
if (conv == null)
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'");
val cparam = conv.converter.apply(param);
if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
return true;
}
params.add(cparam);
}
try {
val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time)
if (ret instanceof Boolean) {
if (!(boolean) ret) //Show usage
sender.sendMessage(sd.helpText);
} else if (ret != null)
throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret);
return true; //We found a method
} catch (InvocationTargetException e) {
TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause());
}*/
} }
} }
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;
}
handleCommand(new Command2MCSender(sender, user.channel.get(), sender),
("/" + command.getName() + " " + String.join(" ", args)).trim(), false); ///trim(): remove space if there are no args
return true;
}
private static class BukkitCommand extends Command {
protected BukkitCommand(String name) {
super(name);
}
@Override
public boolean execute(CommandSender sender, String commandLabel, String[] args) {
return ButtonPlugin.getCommand2MC().executeCommand(sender, this, commandLabel, args);
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
return Collections.emptyList();
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
return Collections.emptyList();
}
}
private static class TabcompleteHelper {
private static Commodore commodore;
private static LiteralCommandNode<Object> appendSubcommand(String path, CommandNode<Object> parent,
SubcommandData<ICommand2MC> subcommand) {
LiteralCommandNode<Object> scmd;
if ((scmd = (LiteralCommandNode<Object>) parent.getChild(path)) != null)
return scmd;
var scmdBuilder = LiteralArgumentBuilder.literal(path);
if (subcommand != null)
scmdBuilder.requires(o -> {
var sender = commodore.getBukkitSender(o);
return ButtonPlugin.getCommand2MC().hasPermission(sender, subcommand.command, subcommand.method);
});
scmd = scmdBuilder.build();
parent.addChild(scmd);
return scmd;
}
private static void registerTabcomplete(ICommand2MC command2MC, List<SubcommandData<ICommand2MC>> subcmds, Command bukkitCommand) {
if (commodore == null) {
commodore = CommodoreProvider.getCommodore(MainPlugin.Instance); //Register all to the Core, it's easier
commodore.register(LiteralArgumentBuilder.literal("un").redirect(RequiredArgumentBuilder.argument("unsomething",
StringArgumentType.word()).suggests((context, builder) -> builder.suggest("untest").buildFuture()).build()));
}
String[] path = command2MC.getCommandPath().split(" ");
var shouldRegister = new AtomicBoolean(true);
@SuppressWarnings("unchecked") var maincmd = commodore.getRegisteredNodes().stream()
.filter(node -> node.getLiteral().equalsIgnoreCase(path[0]))
.filter(node -> { shouldRegister.set(false); return true; })
.map(node -> (LiteralCommandNode<Object>) node).findAny()
.orElseGet(() -> LiteralArgumentBuilder.literal(path[0]).build()); //Commodore 1.8 removes previous nodes
var cmd = maincmd;
for (int i = 1; i < path.length; i++) {
var scmd = subcmds.stream().filter(sd -> sd.method.getName().equals("def")).findAny().orElse(null);
cmd = appendSubcommand(path[i], cmd, scmd); //Add each part of the path as a child of the previous one
}
final var customTCmethods = Arrays.stream(command2MC.getClass().getDeclaredMethods()) //val doesn't recognize the type arguments
.flatMap(method -> Stream.of(Optional.ofNullable(method.getAnnotation(CustomTabCompleteMethod.class)))
.filter(Optional::isPresent).map(Optional::get) // Java 9 has .stream()
.flatMap(ctcm -> {
var paths = Optional.of(ctcm.subcommand()).filter(s -> s.length > 0)
.orElseGet(() -> new String[]{
ButtonPlugin.getCommand2MC().getCommandPath(method.getName(), ' ').trim()
});
return Arrays.stream(paths).map(name -> new Triplet<>(name, ctcm, method));
})).collect(Collectors.toList());
for (SubcommandData<ICommand2MC> subcmd : subcmds) {
String subpathAsOne = ButtonPlugin.getCommand2MC().getCommandPath(subcmd.method.getName(), ' ').trim();
String[] subpath = subpathAsOne.split(" ");
CommandNode<Object> scmd = cmd;
if (subpath[0].length() > 0) { //If the method is def, it will contain one empty string
for (String s : subpath) {
scmd = appendSubcommand(s, scmd, subcmd); //Add method name part of the path (could_be_multiple())
}
}
Parameter[] parameters = subcmd.method.getParameters();
for (int i = 1; i < parameters.length; i++) { //Skip sender
Parameter parameter = parameters[i];
ArgumentType<?> type;
final Class<?> ptype = parameter.getType();
final boolean customParamType;
{
boolean customParamTypeTemp = false;
if (ptype == String.class)
if (parameter.isAnnotationPresent(TextArg.class))
type = StringArgumentType.greedyString();
else
type = StringArgumentType.word();
else if (ptype == int.class || ptype == Integer.class
|| ptype == byte.class || ptype == Byte.class
|| ptype == short.class || ptype == Short.class)
type = IntegerArgumentType.integer(); //TODO: Min, max
else if (ptype == long.class || ptype == Long.class)
type = LongArgumentType.longArg();
else if (ptype == float.class || ptype == Float.class)
type = FloatArgumentType.floatArg();
else if (ptype == double.class || ptype == Double.class)
type = DoubleArgumentType.doubleArg();
else if (ptype == char.class || ptype == Character.class)
type = StringArgumentType.word();
else if (ptype == boolean.class || ptype == Boolean.class)
type = BoolArgumentType.bool();
else if (parameter.isVarArgs())
type = StringArgumentType.greedyString();
else {
type = StringArgumentType.word();
customParamTypeTemp = true;
}
customParamType = customParamTypeTemp;
}
val param = subcmd.parameters[i - 1];
val customTC = Optional.ofNullable(parameter.getAnnotation(CustomTabComplete.class))
.map(CustomTabComplete::value);
var customTCmethod = customTCmethods.stream().filter(t -> subpathAsOne.equalsIgnoreCase(t.getValue0()))
.filter(t -> param.replaceAll("[\\[\\]<>]", "").equalsIgnoreCase(t.getValue1().param()))
.findAny();
var argb = RequiredArgumentBuilder.argument(param, type)
.suggests((context, builder) -> {
if (parameter.isVarArgs()) { //Do it before the builder is used
int nextTokenStart = context.getInput().lastIndexOf(' ') + 1;
builder = builder.createOffset(nextTokenStart);
}
if (customTC.isPresent())
for (val ctc : customTC.get())
builder.suggest(ctc);
boolean ignoreCustomParamType = false;
if (customTCmethod.isPresent()) {
var tr = customTCmethod.get();
if (tr.getValue1().ignoreTypeCompletion())
ignoreCustomParamType = true;
final var method = tr.getValue2();
val params = method.getParameters();
val args = new Object[params.length];
for (int j = 0, k = 0; j < args.length && k < subcmd.parameters.length; j++) {
val paramObj = params[j];
if (CommandSender.class.isAssignableFrom(paramObj.getType())) {
args[j] = commodore.getBukkitSender(context.getSource());
continue;
}
val paramValueString = context.getArgument(subcmd.parameters[k], String.class);
if (paramObj.getType() == String.class) {
args[j] = paramValueString;
continue;
}
val converter = getParamConverter(params[j].getType(), command2MC);
if (converter == null)
break;
val paramValue = converter.converter.apply(paramValueString);
if (paramValue == null) //For example, the player provided an invalid plugin name
break;
args[j] = paramValue;
k++; //Only increment if not CommandSender
}
if (args.length == 0 || args[args.length - 1] != null) { //Arguments filled entirely
try {
val suggestions = method.invoke(command2MC, args);
if (suggestions instanceof Iterable) {
//noinspection unchecked
for (Object suggestion : (Iterable<Object>) suggestions)
if (suggestion instanceof String)
builder.suggest((String) suggestion);
else
throw new ClassCastException("Bad return type! It should return an Iterable<String> or a String[].");
} else if (suggestions instanceof String[])
for (String suggestion : (String[]) suggestions)
builder.suggest(suggestion);
else
throw new ClassCastException("Bad return type! It should return a String[] or an Iterable<String>.");
} catch (Exception e) {
String msg = "Failed to run tabcomplete method " + method.getName() + " for command " + command2MC.getClass().getSimpleName();
if (command2MC.getComponent() == null)
TBMCCoreAPI.SendException(msg, e, command2MC.getPlugin());
else
TBMCCoreAPI.SendException(msg, e, command2MC.getComponent());
}
}
}
if (!ignoreCustomParamType && customParamType) {
val converter = getParamConverter(ptype, command2MC);
if (converter != null) {
var suggestions = converter.allSupplier.get();
for (String suggestion : suggestions)
builder.suggest(suggestion);
}
}
if (ptype == boolean.class || ptype == Boolean.class)
builder.suggest("true").suggest("false");
final String loweredInput = builder.getRemaining().toLowerCase();
return builder.suggest(param).buildFuture().whenComplete((s, e) -> //The list is automatically ordered
s.getList().add(s.getList().remove(0))) //So we need to put the <param> at the end after that
.whenComplete((ss, e) -> ss.getList().removeIf(s -> {
String text = s.getText();
return !text.startsWith("<") && !text.startsWith("[") && !text.toLowerCase().startsWith(loweredInput);
}));
});
var arg = argb.build();
scmd.addChild(arg);
scmd = arg;
}
}
if (shouldRegister.get()) {
commodore.register(maincmd);
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
String pluginName = command2MC.getPlugin().getName().toLowerCase();
var prefixedcmd = LiteralArgumentBuilder.literal(pluginName + ":" + path[0])
.redirect(maincmd).build();
commodore.register(prefixedcmd);
for (String alias : bukkitCommand.getAliases()) {
commodore.register(LiteralArgumentBuilder.literal(alias).redirect(maincmd).build());
commodore.register(LiteralArgumentBuilder.literal(pluginName + ":" + alias).redirect(maincmd).build());
}
}
}
}
private static ParamConverter<?> getParamConverter(Class<?> cl, ICommand2MC command2MC) {
val converter = ButtonPlugin.getCommand2MC().paramConverters.get(cl);
if (converter == null) {
String msg = "Could not find a suitable converter for type " + cl.getSimpleName();
Exception exception = new NullPointerException("converter is null");
if (command2MC.getComponent() == null)
TBMCCoreAPI.SendException(msg, exception, command2MC.getPlugin());
else
TBMCCoreAPI.SendException(msg, exception, command2MC.getComponent());
return null;
}
return converter;
}
} }

View file

@ -1,5 +1,6 @@
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;
@ -7,6 +8,8 @@ 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

@ -0,0 +1,15 @@
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

@ -0,0 +1,29 @@
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,10 +12,9 @@ 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, @Command2.TextArg String args) { public boolean def(TP sender) {
return false; return false;
} }
@ -64,6 +63,18 @@ 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

@ -1,25 +0,0 @@
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,8 +1,11 @@
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 com.google.common.collect.HashBiMap; import buttondevteam.lib.architecture.ConfigData;
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;
@ -12,37 +15,60 @@ 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 implements AutoCloseable { public abstract class ChromaGamerBase {
public static final String TBMC_PLAYERS_DIR = "TBMC/players/"; private 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<>();
/** /**
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) * Use {@link #getConfig()} where possible; the 'id' must be always set
*/ */
public static void RegisterPluginUserClass(Class<? extends ChromaGamerBase> userclass) { //protected YamlConfiguration plugindata;
if (userclass.isAnnotationPresent(UserClass.class))
playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername()); @Getter
else if (userclass.isAnnotationPresent(AbstractUserClass.class)) protected final IHaveConfig config = new IHaveConfig(this::save);
playerTypes.put(userclass.getAnnotation(AbstractUserClass.class).prototype(), protected CommonUserData<?> commonUserData;
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 * @param cl The class to get the folder from (like {@link TBMCPlayerBase} or one of it's subclasses)
* 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 * @throws RuntimeException If the class doesn't have the {@link UserClass} annotation.
* 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))
@ -54,52 +80,63 @@ public abstract class ChromaGamerBase implements AutoCloseable {
/** /**
* Returns the player class for the given folder name. * Returns the player class for the given folder name.
* *
* @param foldername * @param foldername The folder to get the class from (like "minecraft")
* 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) {
return playerTypes.inverse().get(foldername); synchronized (staticDataMap) {
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;
/*** /***
* 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)} * Retrieves a user from cache or loads it from disk.
* *
* @param fname Filename without .yml, usually UUID * @param fname Filename without .yml, the user's identifier for that type
* @param cl User class * @param cl User class
* @return The user object * @return The user object
*/ */
public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) { public static synchronized <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
try { StaticUserData<?> staticUserData = null;
T obj = cl.newInstance(); for (var sud : staticDataMap.entrySet()) {
final String folder = getFolderForType(cl); if (sud.getKey().isAssignableFrom(cl)) {
staticUserData = sud.getValue();
break;
}
}
if (staticUserData == null)
throw new RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass");
var commonUserData = staticUserData.getUserDataMap().get(fname);
if (commonUserData == null) {
final String folder = staticUserData.getFolder();
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();
obj.plugindata = YamlConfiguration.loadConfiguration(file); var playerData = YamlConfiguration.loadConfiguration(file);
obj.plugindata.set(folder + "_id", fname); commonUserData = new CommonUserData<>(playerData);
return obj; playerData.set(staticUserData.getFolder() + "_id", fname);
} catch (Exception e) { staticUserData.getUserDataMap().put(fname, commonUserData);
TBMCCoreAPI.SendException("An error occured while loading a " + cl.getSimpleName() + "!", e);
} }
return null; 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;
} }
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.
* *
@ -124,88 +161,110 @@ public abstract class ChromaGamerBase implements AutoCloseable {
return null; return null;
} }
/** public static void saveUsers() {
* Saves the player. It'll pass all exceptions to the caller. To automatically handle the exception, use {@link #save()} instead. synchronized (staticDataMap) {
*/ for (var sud : staticDataMap.values())
@Override for (var cud : sud.getUserDataMap().values())
public void close() throws Exception { ConfigData.saveNow(cud.getPlayerData()); //Calls save()
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. To catch the exception, use {@link #close()} instead. * Saves the player. It'll handle all exceptions that may happen. Called automatically.
*/ */
public void save() { protected void save() {
try { try {
close(); if (commonUserData.getPlayerData().getKeys(false).size() > 0)
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); TBMCCoreAPI.SendException("Error while saving player to " + getFolder() + "/" + getFileName() + ".yml!", e, MainPlugin.Instance);
} }
} }
/** /**
* Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set * Removes the user from the cache. This will be called automatically after some time by default.
*
* @param user
* The account to connect with
*/ */
public <T extends ChromaGamerBase> void connectWith(T user) { 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
*/
public final <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);
user.plugindata.set(ownFolder + "_id", plugindata.getString(ownFolder + "_id")); var ownData = commonUserData.getPlayerData();
plugindata.set(userFolder + "_id", user.plugindata.getString(userFolder + "_id")); var userData = user.commonUserData.getPlayerData();
userData.set(ownFolder + "_id", ownData.getString(ownFolder + "_id"));
ownData.set(userFolder + "_id", userData.getString(userFolder + "_id"));
config.signalChange();
user.config.signalChange();
Consumer<YamlConfiguration> sync = sourcedata -> { Consumer<YamlConfiguration> sync = sourcedata -> {
final String sourcefolder = sourcedata == plugindata ? ownFolder : userFolder; final String sourcefolder = sourcedata == ownData ? ownFolder : userFolder;
final String id = sourcedata.getString(sourcefolder + "_id"); final String id = sourcedata.getString(sourcefolder + "_id");
for (val entry : playerTypes.entrySet()) { // Set our ID in all files we can find, both from our connections and the new ones for (val entry : staticDataMap.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;
final String otherid = sourcedata.getString(entry.getValue() + "_id"); var entryFolder = entry.getValue().getFolder();
final String otherid = sourcedata.getString(entryFolder + "_id");
if (otherid == null) if (otherid == null)
continue; continue;
try (ChromaGamerBase cg = getUser(otherid, entry.getKey())) { ChromaGamerBase cg = getUser(otherid, entry.getKey());
cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs var cgData = cg.commonUserData.getPlayerData();
for (val item : playerTypes.entrySet()) cgData.set(sourcefolder + "_id", id); // Set new IDs
if (sourcedata.contains(item.getValue() + "_id")) for (val item : staticDataMap.entrySet()) {
cg.plugindata.set(item.getValue() + "_id", sourcedata.getString(item.getValue() + "_id")); // Set all existing IDs var itemFolder = item.getValue().getFolder();
} catch (Exception e) { if (sourcedata.contains(itemFolder + "_id")) {
TBMCCoreAPI.SendException("Failed to update " + sourcefolder + " ID in player files for " + id cgData.set(itemFolder + "_id", sourcedata.getString(itemFolder + "_id")); // Set all existing IDs
+ " in folder with " + entry.getValue() + " id " + otherid + "!", e); }
} }
cg.config.signalChange();
} }
}; };
sync.accept(plugindata); sync.accept(ownData);
sync.accept(user.plugindata); sync.accept(userData);
} }
/** /**
* Retunrs the ID for the T typed player object connected with this one or null if no connection found. * Returns the ID for the T typed player object connected with this one or null if no connection found.
* *
* @param cl * @param cl The player class to get the ID from
* 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 <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) { public final <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return plugindata.getString(getFolderForType(cl) + "_id"); return commonUserData.getPlayerData().getString(getFolderForType(cl) + "_id");
} }
/** /**
* Returns this player as a plugin player. This will return a new instance unless the player is online.<br> * 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>
* Make sure to close both the returned and this object. A try-with-resources block or two can help.<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.
* *
* @param cl * @param cl The target player class
* The target player class * @return The player as a {@link T} object or null if the user doesn't have an account there
* @return The player as a {@link T} object or null if not having an account there
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nullable @Nullable
public <T extends ChromaGamerBase> T getAs(Class<T> cl) { // TODO: Provide a way to use TBMCPlayerBase's loaded players public final <T extends ChromaGamerBase> T getAs(Class<T> cl) {
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);
@ -213,105 +272,34 @@ public abstract class ChromaGamerBase implements AutoCloseable {
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);
if (!plugindata.contains(newfolder + "_id")) var playerData = commonUserData.getPlayerData();
if (!playerData.contains(newfolder + "_id"))
return null; return null;
return getUser(plugindata.getString(newfolder + "_id"), cl); return getUser(playerData.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 * @param target The {@link InfoTarget} to return the info for.
* The {@link InfoTarget} to return the info for.
* @return The player information. * @return The player information.
*/ */
public String getInfo(InfoTarget target) { public final 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();
@ -323,7 +311,6 @@ public abstract class ChromaGamerBase implements AutoCloseable {
//----------------------------------------------------------------- //-----------------------------------------------------------------
public ChannelPlayerData channel() { public final ConfigData<Channel> channel = config.getData("channel", Channel.GlobalChat,
return dataChannel(Channel.GlobalChat); id -> Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID);
}
} }

View file

@ -0,0 +1,19 @@
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

@ -1,26 +0,0 @@
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

@ -1,37 +0,0 @@
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

@ -0,0 +1,23 @@
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 = "ButtonCore") //TODO: Migrate @PlayerClass(pluginname = "Chroma-Core")
public final class TBMCPlayer extends TBMCPlayerBase { public final class TBMCPlayer extends TBMCPlayerBase {
} }

View file

@ -1,28 +1,21 @@
package buttondevteam.lib.player; package buttondevteam.lib.player;
import buttondevteam.core.component.towny.TownyComponent; import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.IHaveConfig;
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;
private String pluginname; @Getter
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)
@ -30,156 +23,61 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase {
return uuid; return uuid;
} }
public PlayerData<String> PlayerName() { public final ConfigData<String> PlayerName = super.config.getData("PlayerName", "");
return super.data(null);
}
/** /**
* 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" * Get player as a plugin player.
*
* @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) {
if (playermap.containsKey(uuid + "-" + cl.getSimpleName())) var player = ChromaGamerBase.getUser(uuid.toString(), cl);
return (T) playermap.get(uuid + "-" + cl.getSimpleName()); if (!player.getUUID().equals(uuid)) //It will be set from the filename because we check it for scheduling the uncache.
try { throw new IllegalStateException("Player UUID differs after converting from and to string...");
T player; return player;
if (playermap.containsKey(uuid + "-" + TBMCPlayer.class.getSimpleName())) { }
player = cl.newInstance();
player.plugindata = playermap.get(uuid + "-" + TBMCPlayer.class.getSimpleName()).plugindata; @Override
playermap.put(uuid + "-" + cl.getSimpleName(), player); // It will get removed on player quit public void init() {
} else super.init();
player = ChromaGamerBase.getUser(uuid.toString(), cl);
player.uuid = uuid; String pluginname;
return player; if (getClass().isAnnotationPresent(PlayerClass.class))
} catch (Exception e) { pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
TBMCCoreAPI.SendException( else
"Failed to get player with UUID " + uuid + " and class " + cl.getSimpleName() + "!", e); throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
return null;
} 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();
} }
/** /**
* Key: UUID-Class * This method returns a TBMC player from their name. See {@link Bukkit#getOfflinePlayer(String)}.
*/
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
public void close() throws Exception { protected void save() {
Set<String> keys = plugindata.getKeys(false); Set<String> keys = commonUserData.getPlayerData().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.close(); super.save();
} }
} }

View file

@ -1,34 +0,0 @@
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

@ -1,27 +0,0 @@
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

@ -1,34 +0,0 @@
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

@ -1,27 +0,0 @@
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: ChromaCore name: Chroma-Core
main: buttondevteam.core.MainPlugin main: buttondevteam.core.MainPlugin
version: 1.0 version: '${noprefix.version}'
author: NorbiPeti author: NorbiPeti
commands: commands:
updateplugin: updateplugin:
@ -9,10 +9,6 @@ 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:
@ -22,4 +18,5 @@ softdepend:
- Towny - Towny
- Votifier - Votifier
- Multiverse-Core - Multiverse-Core
- Essentials - Essentials
api-version: '1.13'

View file

@ -1,82 +0,0 @@
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

@ -1,25 +0,0 @@
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);
}
}

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

@ -1,110 +1,127 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd"> http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId> <groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId> <artifactId>CorePOM</artifactId>
<packaging>pom</packaging> <packaging>pom</packaging>
<version>master-SNAPSHOT</version> <version>master-SNAPSHOT</version>
<properties> <properties>
<lombok.version>1.18.10</lombok.version> <lombok.version>1.18.10</lombok.version>
</properties> </properties>
<name>Core POM for Chroma</name> <name>Core POM for Chroma</name>
<build> <build>
<pluginManagement> <pluginManagement>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version> <version>3.8.1</version>
<configuration> <configuration>
<release>8</release> <release>8</release>
<annotationProcessorPaths> <annotationProcessorPaths>
<annotationProcessorPath> <annotationProcessorPath>
<groupId>com.github.bsideup.jabel</groupId> <groupId>com.github.bsideup.jabel</groupId>
<artifactId>jabel-javac-plugin</artifactId> <artifactId>jabel-javac-plugin</artifactId>
<version>0.2.0</version> <version>0.2.0</version>
</annotationProcessorPath> </annotationProcessorPath>
<annotationProcessorPath> <annotationProcessorPath>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>${lombok.version}</version> <version>${lombok.version}</version>
</annotationProcessorPath> </annotationProcessorPath>
<annotationProcessorPath> <annotationProcessorPath>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId> <groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId> <artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version> <version>master-SNAPSHOT</version>
</annotationProcessorPath> </annotationProcessorPath>
</annotationProcessorPaths> </annotationProcessorPaths>
<annotationProcessors> <!-- Order is important, so these lines are needed --> <annotationProcessors> <!-- Order is important, so these lines are needed -->
<annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor> <annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor <annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
</annotationProcessor> </annotationProcessor>
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor> <annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
</annotationProcessors> </annotationProcessors>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<configuration> <configuration>
<useSystemClassLoader>false <useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 --> </useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration> </configuration>
</plugin> </plugin>
</plugins> <!-- <plugin>
</pluginManagement> <groupId>io.github.1tchy</groupId>
</build> <artifactId>variable-search-replace-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<text>${project.version}</text>
<search>^v</search>
<variableName>noprefix.version</variableName>
</configuration>
</execution>
</executions>
</plugin> -->
</plugins>
</pluginManagement>
</build>
<repositories> <repositories>
<repository> <repository>
<id>jitpack.io</id> <id>jitpack.io</id>
<url>https://jitpack.io/</url> <url>https://jitpack.io/</url>
</repository> </repository>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>3.8.1</version> <version>4.13.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>${lombok.version}</version> <version>${lombok.version}</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<profiles> <profiles>
<profile> <profile>
<id>intellij-idea-only</id> <id>intellij-idea-only</id>
<activation> <activation>
<property> <property>
<name>idea.maven.embedder.version</name> <name>idea.maven.embedder.version</name>
</property> </property>
</activation> </activation>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<configuration> <configuration>
<release>11</release> <release>11</release>
<!-- <!--
<compilerArgs> <compilerArgs>
<arg>HYPHENHYPHENenable-preview</arg> <arg>HYPHENHYPHENenable-preview</arg>
</compilerArgs> </compilerArgs>
--> -->
</configuration> </configuration>
</plugin> </plugin>
</plugins> </plugins>
</build> </build>
</profile> </profile>
</profiles> </profiles>
</project> </project>

5
pom.xml Executable file → Normal 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.10</lombok.version> <lombok.version>1.18.12</lombok.version>
</properties> </properties>
<name>Chroma Parent</name> <name>Chroma Parent</name>
@ -17,7 +17,6 @@
<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>
@ -45,7 +44,7 @@
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>
<version>3.8.1</version> <version>4.13.1</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->