Compare commits

...

69 commits

Author SHA1 Message Date
Norbi Peti 66c1b1b14f Merge hotfix 1.0.2 2023-04-05 15:17:09 +02:00
Norbi Peti 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
Norbi Peti 6ec63b5ae7
Fix NuVotifier repository 2023-03-12 22:01:06 +01:00
Norbi Peti 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
Norbi Peti 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
Norbi Peti 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
Norbi Peti 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
Norbi Peti 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
Norbi Peti f92cc773c1
Remove Towny resident rename functionality
Towny doesn't need the name updater plugin anymore
2021-01-18 17:14:15 +01:00
Norbi Peti 2a008906f4
Remove Votifier component and stuff 2020-10-31 18:24:18 +01:00
Norbi Peti 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
Norbi Peti 66be5ab0dc
Remove (/press) 2020-10-30 00:49:43 +01:00
Norbi Peti 5b5a8877cc
Add a dash and a zero (name and version) 2020-10-28 00:08:00 +01:00
Norbi Peti 09074f1a2e
Moving player data and some cleanup 2020-10-27 22:48:39 +01:00
Norbi Peti 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
Norbi Peti 5199482053
Set Java version for codeql 2020-10-26 23:18:54 +01:00
Norbi Peti 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
Norbi Peti 51e0ca4f4c
Stop using methods for configs 2020-10-26 17:44:11 +01:00
Norbi Peti 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
Norbi Peti 132eba7db6
Player data upgrade complete
Auto-save for each user
ConfigData stuff
Removed player events
2020-10-24 23:47:33 +02:00
Norbi Peti 83ecf7717f
Started upgrading player data setup 2020-10-23 01:48:54 +02:00
Norbi Peti 893b24ed5f
Fix alias support and add tab completion for it 2020-10-14 20:41:26 +02:00
Norbi Peti 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
Norbi Peti 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
Norbi Peti 1aa9cd3552
Attempt to support main command aliases 2020-10-13 02:49:48 +02:00
Norbi Peti 731065fe2a
Log exceptions using the plugin/component logger 2020-10-09 00:08:53 +02:00
Norbi Peti 01dd8a477e
Fix error when Mockito isn't present in prod 2020-09-15 03:42:49 +02:00
Norbi Peti ab4dd75684
Test and config fixes
Added method to check if testing
2020-09-04 17:52:36 +02:00
Norbi Peti 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
Norbi Peti 28e44d5179
Added some alias support for subcommands
Hmm
2020-09-02 03:12:07 +02:00
Norbi Peti cedeca2f61
A small fix can go a long way 2020-08-31 23:53:29 +02:00
Norbi Peti 784ad5e1c7
Config fixes, actually registering the configs 2020-08-30 02:21:34 +02:00
Norbi Peti 9923f26698
Config fixes, reusing the config objects 2020-08-29 02:57:50 +02:00
Norbi Peti b89391f84c
Fix doc generation for field configs
And for read-only configs
2020-08-27 16:48:56 +02:00
Norbi Peti 8f610c9935
Add ConfigData builder, reset support 2020-08-27 16:18:25 +02:00
Norbi Peti 4310e45a6f
Use existing command from plugin.yml if exists
3:33
2020-08-27 03:33:46 +02:00
Norbi Peti 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
Norbi Peti ef96ec2604
Fix fallback chat format
TBMCPlugins/Chroma-Chat#121
2020-07-28 02:36:26 +02:00
Norbi Peti 8815877bea
Add proper scheduled restart here & fixes 2020-07-25 20:27:56 +02:00
Norbi Peti 58fcd4c145
Add per-component logging 2020-06-30 00:43:56 +02:00
Norbi Peti 1139f832b6
Component disable on error 2020-06-27 02:51:38 +02:00
Norbi Peti 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
Norbi Peti 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
Norbi Peti 7b505bb8e9
Fix resident rename
Even more than last time
2020-03-21 19:19:57 +01:00
Norbi Peti 6b68bdab0f
Add support for varargs and fix param name placement
#82
2020-03-19 22:58:19 +01:00
Norbi Peti 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
Norbi Peti 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
Norbi Peti 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
Norbi Peti 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
Norbi Peti a3160c4040
Add support for channels and groups for (chat) commands
#93
Fixed private command classes erroring
2020-03-15 03:07:10 +01:00
Norbi Peti b77871bb8c Add permanent component enable/disable
#81
2020-03-10 16:06:57 +01:00
Norbi Peti e676ea516c Increase primerestart cooldown
#42
2020-03-09 12:35:17 +01:00
Norbi Peti 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
Norbi Peti 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
Norbi Peti 00738fe820
Some attempts and fixes before enlightenment
#82
2020-03-07 02:27:24 +01:00
Norbi Peti 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
Norbi Peti 2cca2e2096 Register chat channels as commands
#72
2020-03-03 17:13:35 +01:00
Norbi Peti 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
Norbi Peti 05720afdf8 Separate command class, fix processor parent POM 2020-02-25 17:34:25 +01:00
Norbi Peti 393c9c9b08 Fix command register
Also deleted plugin updater stuff in last commit
2020-02-24 19:19:18 +01:00
Norbi Peti a0c211f4be Attempt to register custom commands 2020-02-24 12:57:57 +01:00
Norbi Peti def4a846b2 Reload other plugins, remove plugin updater
#84
2020-02-19 10:47:53 +01:00
Norbi Peti b9781f19f0 Handle remaining primitive types in commands
#92
2020-02-18 12:27:18 +01:00
Norbi Peti 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_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"
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>
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>
<repositories>
<repository>
<id>spigot-repo</id>
@ -11,7 +11,7 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.21</version>
<version>1.32</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -21,13 +21,10 @@
<scope>compile</scope>
</dependency>
</dependencies>
<parent>
<groupId>com.github.TBMCPlugins</groupId>
<artifactId>ChromaCore</artifactId>
<version>master-SNAPSHOT</version>
</parent>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<!-- Can't use Core POM because it uses this processor -->
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
<packaging>jar</packaging>
<name>ButtonProcessor</name>
@ -36,21 +33,25 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<compilerArgument>-proc:none</compilerArgument>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration>
</plugin>
</plugins>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration>
</plugin>
</plugins>
</build>
<properties>

View file

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

View file

@ -48,28 +48,27 @@ public class ConfigProcessor {
e.printStackTrace();
}
for (Element e : targetcl.getEnclosedElements()) {
/*System.out.println("Element: "+e);
System.out.println("Type: "+e.getClass()+" - "+e.getKind());
if(e instanceof ExecutableElement)
System.out.println("METHOD!");*/
if (!(e instanceof ExecutableElement)) continue;
TypeMirror tm = ((ExecutableElement) e).getReturnType();
TypeMirror tm;
if (e instanceof ExecutableElement)
tm = ((ExecutableElement) e).getReturnType();
else if (e.getKind().isField())
tm = e.asType();
else
continue;
if (tm.getKind() != TypeKind.DECLARED) continue;
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())
System.out.println("Config: " + e.getSimpleName());
String doc = procEnv.getElementUtils().getDocComment(e);
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());
}
String javadoc = procEnv.getElementUtils().getDocComment(targetcl);
if (javadoc != null) {
System.out.println("JAVADOC");
System.out.println(javadoc.trim());
yc.set(path, javadoc.trim());
System.out.println("Adding docs for class: " + targetcl.getSimpleName());
yc.set(path + ".generalDescriptionInsteadOfAConfig", javadoc.trim());
}
try {
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"
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>
<parent>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<version>master-SNAPSHOT</version>
<relativePath>../CorePOM</relativePath>
</parent>
<parent>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<version>master-SNAPSHOT</version>
<relativePath>../CorePOM</relativePath>
</parent>
<artifactId>Chroma-Core</artifactId>
<name>Chroma-Core</name>
<description>Chroma-Core</description>
<version>v${noprefix.version}-SNAPSHOT</version>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
@ -29,17 +30,28 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.2</version>
<version>3.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
</artifactSet>
</configuration>
<configuration>
<artifactSet>
<includes>
<include>me.lucko:commodore</include>
<include>org.javatuples:javatuples</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>me.lucko.commodore</pattern>
<!-- vvv Replace with the package of your plugin vvv -->
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
</relocation>
</relocations>
</configuration>
</execution>
</executions>
</plugin>
@ -106,21 +118,21 @@
<repository>
<id>ess-repo</id>
<url>https://ci.ender.zone/plugin/repository/everything/</url>
</repository>
<repository>
<id>Votifier</id>
<url>https://dl.bintray.com/nuvotifier/maven/</url>
</repository>
<repository>
<id>Multiverse-Core</id>
<url>http://repo.onarandombox.com/content/repositories/multiverse/</url>
<url>https://repo.onarandombox.com/content/groups/public/</url>
</repository>
</repositories>
<repository>
<id>minecraft-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
<version>0.10.2</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -145,37 +157,49 @@
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
<version>3.28.0-GA</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.0.0</version>
<version>4.2.0</version>
</dependency>
<dependency>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
<scope>compile</scope>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>net.ess3</groupId>
<artifactId>EssentialsX</artifactId>
<version>2.17.1</version>
<artifactId>EssentialsX</artifactId>
<version>2.17.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.vexsoftware</groupId>
<artifactId>nuvotifier-universal</artifactId>
<version>2.3.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.onarandombox.multiversecore</groupId>
<artifactId>Multiverse-Core</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.NuVotifier.NuVotifier/nuvotifier-bukkit -->
<dependency>
<groupId>com.github.NuVotifier.NuVotifier</groupId>
<artifactId>nuvotifier-bukkit</artifactId>
<version>v2.7.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.onarandombox.multiversecore</groupId>
<artifactId>Multiverse-Core</artifactId>
<version>4.3.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.11</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.javatuples</groupId>
<artifactId>javatuples</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
<organization>
<name>TBMCPlugins</name>
@ -192,6 +216,7 @@
<!-- github server corresponds to entry in ~/.m2/settings.xml -->
<github.global.server>github</github.global.server>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<noprefix.version>1.0.1</noprefix.version>
</properties>
<scm>
<url>https://github.com/TBMCPlugins/mvn-repo</url>

View file

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

View file

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

View file

@ -8,9 +8,6 @@ import buttondevteam.core.component.randomtp.RandomTPComponent;
import buttondevteam.core.component.restart.RestartComponent;
import buttondevteam.core.component.spawn.SpawnComponent;
import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.core.component.updater.PluginUpdater;
import buttondevteam.core.component.updater.PluginUpdaterComponent;
import buttondevteam.core.component.votifier.VotifierComponent;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component;
@ -26,10 +23,12 @@ import lombok.Setter;
import net.milkbowl.vault.economy.Economy;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.BlockCommandSender;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.RegisteredServiceProvider;
@ -38,10 +37,10 @@ import javax.annotation.Nullable;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.logging.Logger;
public class MainPlugin extends ButtonPlugin {
@ -65,48 +64,35 @@ public class MainPlugin extends ButtonPlugin {
* Sets whether the plugin should write a list of installed plugins in a txt file.
* It can be useful if some other software needs to know the plugins.
*/
private ConfigData<Boolean> writePluginList() {
return getIConfig().getData("writePluginList", false);
}
private final ConfigData<Boolean> writePluginList = getIConfig().getData("writePluginList", false);
/**
* The chat format to use for messages from other platforms if Chroma-Chat is not installed.
*/
ConfigData<String> chatFormat() {
return getIConfig().getData("chatFormat", "[{origin}|" +
"{channel}] <{name}> {message}");
}
ConfigData<String> chatFormat = getIConfig().getData("chatFormat", "[{origin}|" +
"{channel}] <{name}> {message}");
/**
* Print some debug information.
*/
public ConfigData<Boolean> test() {
return getIConfig().getData("test", false);
}
public final ConfigData<Boolean> test = 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.
* 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).
/**
* If a Chroma command clashes with another plugin's command, this setting determines whether the Chroma command should be executed or the other plugin's.
*/
/*public ConfigData<Boolean> useVaultForCommands() {
return getIConfig().getData("useVaultForCommands", true);
}*/
public final ConfigData<Boolean> prioritizeCustomCommands = getIConfig().getData("prioritizeCustomCommands", false);
@Override
public void pluginEnable() {
// Logs "Plugin Enabled", registers commands
Instance = this;
PluginDescriptionFile pdf = getDescription();
logger = getLogger();
if (!setupPermissions())
throw new NullPointerException("No permission plugin found!");
if (!setupEconomy()) //Though Essentials always provides economy 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.");
saveConfig();
Component.registerComponent(this, new PluginUpdaterComponent());
Component.registerComponent(this, new RestartComponent());
//noinspection unchecked - needed for testing
Component.registerComponent(this, new ChannelComponent());
Component.registerComponent(this, new RandomTPComponent());
Component.registerComponent(this, new MemberComponent());
@ -114,8 +100,8 @@ public class MainPlugin extends ButtonPlugin {
Component.registerComponent(this, new SpawnComponent());
if (Bukkit.getPluginManager().isPluginEnabled("Towny")) //It fails to load the component class otherwise
Component.registerComponent(this, new TownyComponent());
if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new VotifierComponent(economy));
/*if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new VotifierComponent(economy));*/
ComponentManager.enableComponents();
registerCommand(new ComponentCommand());
registerCommand(new ChromaCommand());
@ -125,7 +111,7 @@ public class MainPlugin extends ButtonPlugin {
? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks
ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player
? TBMCPlayer.getPlayer(((Player) sender).getUniqueId(), TBMCPlayer.class) : null)); //Players, has higher priority
TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class);
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.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("§bBLUE§f", Color.Blue, "blue"));
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 {
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator);
} catch (IOException e) {
TBMCCoreAPI.SendException("Failed to write plugin list!", e);
TBMCCoreAPI.SendException("Failed to write plugin list!", e, this);
}
}
if (getServer().getPluginManager().isPluginEnabled("Essentials"))
ess = Essentials.getPlugin(Essentials.class);
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test().get() + ".");
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test.get() + ".");
}
@Override
public void pluginDisable() {
logger.info("Saving player data...");
TBMCPlayerBase.savePlayers();
ChromaGamerBase.saveUsers();
logger.info("Player data saved.");
new Thread(() -> {
File[] files = PluginUpdater.updatedir.listFiles();
if (files == null)
return;
logger.info("Updating " + files.length + " plugins...");
for (File file : files) {
try {
Files.move(file.toPath(), new File("plugins", file.getName()).toPath(), StandardCopyOption.REPLACE_EXISTING);
logger.info("Updated " + file.getName());
} catch (IOException e) {
e.printStackTrace();
}
}
logger.info("Update complete!");
}).start();
}
private boolean setupPermissions() {

View file

@ -5,6 +5,7 @@ import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.chat.ChatMessage;
import buttondevteam.lib.chat.Command2MCSender;
import buttondevteam.lib.chat.TBMCChatAPI;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.player.TBMCPlayer;
import buttondevteam.lib.player.TBMCPlayerBase;
import lombok.val;
@ -27,23 +28,32 @@ public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL)
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)
public void OnPlayerLeave(PlayerQuitEvent event) {
TBMCPlayerBase.quitPlayer(event.getPlayer());
TBMCPlayerBase.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class).uncache();
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onSystemChat(TBMCSystemChatEvent event) {
if (event.isHandled())
return; // Only handle here if ButtonChat couldn't - ButtonChat doesn't even handle this
if (event.isHandled())
return; // Only handle here if ButtonChat couldn't - ButtonChat doesn't even handle this
if (Arrays.stream(event.getExceptions()).anyMatch("Minecraft"::equalsIgnoreCase))
return;
Bukkit.getOnlinePlayers().stream().filter(event::shouldSendTo)
.forEach(p -> p.sendMessage(event.getChannel().DisplayName().get().substring(0, 2) + event.getMessage()));
}
Bukkit.getOnlinePlayers().stream().filter(event::shouldSendTo)
.forEach(p -> p.sendMessage(event.getChannel().DisplayName.get().substring(0, 2) + event.getMessage()));
}
@EventHandler
public void onPlayerChatPreprocess(PlayerCommandPreprocessEvent event) {
@ -58,7 +68,9 @@ public class PlayerListener implements Listener {
private void handlePreprocess(CommandSender sender, String message, Cancellable event) {
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);
if (ev.isCancelled())
event.setCancelled(true); //Cancel the original event
@ -68,9 +80,9 @@ public class PlayerListener implements Listener {
public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) {
if (event.isCancelled()) return;
try {
event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender()), event.getMessage()));
event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender(), event.getChannel(), event.getPermCheck()), event.getMessage()));
} 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;
if (!MainPlugin.Instance.isChatHandlerEnabled()) return;
if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages
String msg = MainPlugin.Instance.chatFormat().get()
.replace("{channel}", event.getChannel().DisplayName().get())
var channel = event.getChannel();
String msg = MainPlugin.Instance.chatFormat.get()
.replace("{channel}", channel.DisplayName.get())
.replace("{origin}", event.getOrigin().substring(0, 1))
.replace("{name}", ChromaUtils.getDisplayName(event.getSender()))
.replace("{message}", event.getMessage());
.replace("{message}", String.format("§%x%s", channel.Color.get().ordinal(), event.getMessage()));
for (Player player : Bukkit.getOnlinePlayers())
if (event.shouldSendTo(player))
player.sendMessage(msg);

View file

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

View file

@ -4,6 +4,7 @@ import buttondevteam.core.ComponentManager;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import buttondevteam.lib.chat.Color;
import com.google.common.collect.Lists;
import org.bukkit.Bukkit;
@ -20,127 +21,135 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Represents a chat channel. May only be instantiated after the channel component is registered.
*/
public class Channel {
/**
* Specifies a score that means it's OK to send - but it does not define any groups, only send or not send. See {@link #GROUP_EVERYONE}
*/
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.
*/
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
*/
public static final String GROUP_EVERYONE = "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;
/**
* 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;
/**
* 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";
private static ChannelComponent component;
private String defDisplayName;
private Color defColor;
private void throwGame() {
if (component == null) throw new RuntimeException("Cannot access channel properties until registered!");
}
private IHaveConfig config;
public final ConfigData<Boolean> Enabled() {
throwGame();
return component.getConfig().getData(ID + ".enabled", true);
}
public final ConfigData<Boolean> Enabled;
/**
* Must start with a color code
*/
public final ConfigData<String> DisplayName() {
throwGame();
return component.getConfig().getData(ID + ".displayName", defDisplayName); //TODO: Use config map
public final ConfigData<String> DisplayName;
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();
return component.getConfig().getData(ID + ".color", defColor, c -> Color.valueOf((String) c), Enum::toString);
}
public final String ID;
/**
* Must be only called from a subclass - otherwise it'll throw an exception.
*
* @see Channel#Channel(String, Color, String, Function)
*/
@SuppressWarnings("unchecked")
public ConfigData<String[]> IDs() {
throwGame();
return component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
protected <T extends Channel> Channel(String displayname, Color color, String command,
BiFunction<T, CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
}
/**
* 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!");
}
/**
* 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;
}
public boolean isGlobal() {
return filteranderrormsg == null;
}
/**
* Must be only called from a subclass - otherwise it'll throw an exception.
*
* @see Channel#Channel(String, Color, String, Function)
*/
@SuppressWarnings("unchecked")
protected <T extends Channel> Channel(String displayname, Color color, String command,
BiFunction<T, CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
}
/**
* Note: Errors are sent to the sender automatically
*
* @param sender The user we're sending to
* @param score The (source) score to compare with the user's
*/
public boolean shouldSendTo(CommandSender sender, int score) {
return score == getMCScore(sender); //If there's any error, the score won't be equal
}
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
*
* @param sender The user we're sending to
* @param score The (source) score to compare with the user's
*/
public boolean shouldSendTo(CommandSender sender, int score) {
return score == getMCScore(sender); //If there's any error, the score won't be equal
}
/**
* 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
}
/**
* 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<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);
}
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
@ -148,7 +157,7 @@ public class Channel {
* @return Only the enabled channels
*/
public static Stream<Channel> getChannels() {
return channels.stream().filter(ch -> ch.Enabled().get());
return channels.stream().filter(ch -> ch.Enabled.get());
}
/**
@ -160,72 +169,69 @@ public class Channel {
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
* generated automatically.
*
* @param permgroup The group that can access the channel or <b>null</b> to only allow OPs.
* @return If has access
*/
public static Function<CommandSender, RecipientTestResult> inGroupFilter(String permgroup) {
return noScoreResult(
s -> s.isOp() || (permgroup != null && (s instanceof Player && MainPlugin.permission != null && MainPlugin.permission.playerInGroup((Player) s, permgroup))),
"You need to be a(n) " + (permgroup != null ? permgroup : "OP") + " to use this channel.");
}
/**
* 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.
*
* @param permgroup The group that can access the channel or <b>null</b> to only allow OPs.
* @return If has access
*/
public static Function<CommandSender, RecipientTestResult> inGroupFilter(String permgroup) {
return noScoreResult(
s -> s.isOp() || (permgroup != null && (s instanceof Player && MainPlugin.permission != null && MainPlugin.permission.playerInGroup((Player) s, permgroup))),
"You need to be a(n) " + (permgroup != null ? permgroup : "OP") + " to use this channel.");
}
public static Function<CommandSender, RecipientTestResult> noScoreResult(Predicate<CommandSender> filter,
String errormsg) {
return s -> filter.test(s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
}
public static Function<CommandSender, RecipientTestResult> noScoreResult(Predicate<CommandSender> filter,
String 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(
BiPredicate<T, CommandSender> filter, String errormsg) {
return (this_, s) -> filter.test(this_, s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
}
public static <T extends Channel> BiFunction<T, CommandSender, RecipientTestResult> noScoreResult(
BiPredicate<T, CommandSender> filter, String 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 AdminChat;
public static Channel ModChat;
public static Channel GlobalChat;
public static Channel AdminChat;
public static Channel ModChat;
public static void RegisterChannel(Channel channel) {
if (!channel.isGlobal() && !ComponentManager.isEnabled(ChannelComponent.class))
return; //Allow registering the global chat (and I guess other chats like the RP chat)
if (component == null)
component = (ChannelComponent) Component.getComponents().get(ChannelComponent.class);
if (component == null)
throw new RuntimeException("Attempting to register a channel before the component is registered!");
channels.add(channel);
component.registerChannelCommand(channel);
Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> Bukkit.getPluginManager().callEvent(new ChatChannelRegisterEvent(channel))); // Wait for server start
}
}
public static class RecipientTestResult {
public final String errormessage;
public final int score; // Anything below 0 is "never send"
public final String groupID;
public static final RecipientTestResult ALL = new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
public static class RecipientTestResult {
public final String errormessage;
public final int score; // Anything below 0 is "never send"
public final String groupID;
public static final RecipientTestResult ALL = new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
/**
* Creates a result that indicates an <b>error</b>
*
* @param errormessage The error message to show the sender if they don't meet the criteria.
*/
public RecipientTestResult(String errormessage) {
this.errormessage = errormessage;
this.score = SCORE_SEND_NOPE;
this.groupID = null;
}
/**
* Creates a result that indicates an <b>error</b>
*
* @param errormessage The error message to show the sender if they don't meet the criteria.
*/
public RecipientTestResult(String errormessage) {
this.errormessage = errormessage;
this.score = SCORE_SEND_NOPE;
this.groupID = null;
}
/**
* Creates a result that indicates a <b>success</b>
*
* @param score The score that identifies the target group. <b>Must be non-negative.</b> For example, the index of the town or nation to send to.
* @param groupID The ID of the target group.
*/
public RecipientTestResult(int score, String groupID) {
if (score < 0) throw new IllegalArgumentException("Score must be non-negative!");
this.score = score;
this.groupID = groupID;
this.errormessage = null;
}
}
/**
* Creates a result that indicates a <b>success</b>
*
* @param score The score that identifies the target group. <b>Must be non-negative.</b> For example, the index of the town or nation to send to.
* @param groupID The ID of the target group.
*/
public RecipientTestResult(int score, String groupID) {
if (score < 0) throw new IllegalArgumentException("Score must be non-negative!");
this.score = score;
this.groupID = groupID;
this.errormessage = null;
}
}
}

View file

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

View file

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

View file

@ -25,23 +25,17 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
/**
* The permission group to give to the player
*/
ConfigData<String> memberGroup() {
return getConfig().getData("memberGroup", "member");
}
final ConfigData<String> memberGroup = getConfig().getData("memberGroup", "member");
/**
* The amount of hours needed to play before promotion
*/
private ConfigData<Integer> playedHours() {
return getConfig().getData("playedHours", 12);
}
private final ConfigData<Integer> playedHours = getConfig().getData("playedHours", 12);
/**
* The amount of days passed since first login
*/
private ConfigData<Integer> registeredForDays() {
return getConfig().getData("registeredForDays", 7);
}
private final ConfigData<Integer> registeredForDays = getConfig().getData("registeredForDays", 7);
private AbstractMap.SimpleEntry<Statistic, Integer> playtime;
@ -69,22 +63,22 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
public Boolean addPlayerAsMember(Player player) {
try {
if (permission.playerAddGroup(null, player, memberGroup().get())) {
if (permission.playerAddGroup(null, player, memberGroup.get())) {
player.sendMessage("§bYou are a member now!");
MainPlugin.Instance.getLogger().info("Added " + player.getName() + " as a member.");
log("Added " + player.getName() + " as a member.");
return true;
} 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;
}
} 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;
}
}
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) {
@ -92,14 +86,14 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
}
public boolean checkPlayTime(Player player) {
return getPlayTime(player) > playtime.getValue() * playedHours().get();
return getPlayTime(player) > playtime.getValue() * playedHours.get();
}
/**
* Returns milliseconds
*/
public long getRegTime(Player player) {
Instant date = new Date(player.getFirstPlayed()).toInstant().plus(registeredForDays().get(), ChronoUnit.DAYS);
Instant date = new Date(player.getFirstPlayed()).toInstant().plus(registeredForDays.get(), ChronoUnit.DAYS);
if (date.isAfter(Instant.now()))
return date.toEpochMilli() - Instant.now().toEpochMilli();
return -1;
@ -113,7 +107,7 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
* Returns hours
*/
public double getPlayTime(Player player) {
double pt = playedHours().get() - (double) getPlayTimeTotal(player) / playtime.getValue();
double pt = playedHours.get() - (double) getPlayTimeTotal(player) / playtime.getValue();
if (pt < 0) return -1;
return pt;
}

View file

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

View file

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

View file

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

View file

@ -45,20 +45,20 @@ public class ScheduledRestartCommand extends ICommand2MC {
}
final int restarttime = restartCounter = seconds * 20;
restartbar = Bukkit.createBossBar("Server restart in " + seconds + "s", BarColor.RED, BarStyle.SOLID,
BarFlag.DARKEN_SKY);
BarFlag.DARKEN_SKY);
restartbar.setProgress(1);
Bukkit.getOnlinePlayers().forEach(p -> restartbar.addPlayer(p));
Bukkit.getOnlinePlayers().forEach(p -> restartbar.addPlayer(p));
sender.sendMessage("Scheduled restart in " + seconds);
ScheduledServerRestartEvent e = new ScheduledServerRestartEvent(restarttime, this);
Bukkit.getPluginManager().callEvent(e);
restarttask = Bukkit.getScheduler().runTaskTimer(MainPlugin.Instance, () -> {
if (restartCounter < 0) {
restarttask.cancel();
restartbar.getPlayers().forEach(p -> restartbar.removePlayer(p));
restartbar.getPlayers().forEach(p -> restartbar.removePlayer(p));
Bukkit.spigot().restart();
}
if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size()>0)
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds! (/press)", component.getRestartBroadcast());
if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size() > 0)
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds!", component.getRestartBroadcast());
restartbar.setProgress(restartCounter / (double) restarttime);
restartbar.setTitle(String.format("Server restart in %.2f", restartCounter / 20f));
restartCounter--;

View file

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

View file

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

View file

@ -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.
*
* @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();
}
@ -77,7 +77,7 @@ public final class ChromaUtils {
*
* @param what What to do
* @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) {
if (Bukkit.isPrimaryThread())
@ -86,4 +86,16 @@ public final class ChromaUtils {
return what.get();
return def;
}
private static boolean test = false;
/**
* Returns true while unit testing.
*/
public static boolean isTest() { return test; }
/**
* Call when unit testing.
*/
public static void setTest() { test = true; }
}

View file

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

View file

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

View file

@ -54,6 +54,6 @@ public abstract class TBMCChatEventBase extends Event implements Cancellable {
*/
@Nullable
public String getGroupID(CommandSender sender) {
return channel.getGroupID(sender); //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);
this.sender = sender;
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
public HandlerList getHandlers() {
return handlers;

View file

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

View file

@ -1,14 +1,14 @@
package buttondevteam.lib;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.updater.PluginUpdater;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.player.ChromaGamerBase;
import buttondevteam.lib.potato.DebugPotato;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.IOException;
import java.io.InputStream;
@ -16,6 +16,8 @@ import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class TBMCCoreAPI {
static final List<String> coders = new ArrayList<String>() {
@ -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 {
URL url = new URL(urlstr);
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 e The exception to send
*/
public static void SendException(String sourcemsg, Throwable e) {
SendException(sourcemsg, e, false);
public static void SendException(String sourcemsg, Throwable e, Component<?> component) {
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 {
SendUnsentExceptions();
TBMCExceptionEvent event = new TBMCExceptionEvent(sourcemsg, e);
@ -87,7 +77,7 @@ public class TBMCCoreAPI {
if (!event.isHandled())
exceptionsToSend.put(sourcemsg, e);
}
Bukkit.getLogger().warning(sourcemsg);
logWarn.accept(sourcemsg);
e.printStackTrace();
if (debugPotato) {
List<Player> devsOnline = new ArrayList<>();
@ -128,6 +118,7 @@ public class TBMCCoreAPI {
}
private static EventExceptionCoreHandler eventExceptionCoreHandler;
/**
* Registers Bukkit events, handling the exceptions occurring in those events
*
@ -139,8 +130,8 @@ public class TBMCCoreAPI {
EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler);
}
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass) {
ChromaGamerBase.RegisterPluginUserClass(userclass);
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass, Supplier<T> constructor) {
ChromaGamerBase.RegisterPluginUserClass(userclass, constructor);
}
/**
@ -180,6 +171,6 @@ public class TBMCCoreAPI {
public static boolean IsTestServer() {
if (MainPlugin.Instance == null) return true;
return MainPlugin.Instance.test().get();
return MainPlugin.Instance.test.get();
}
}

View file

@ -25,9 +25,9 @@ import java.util.Stack;
@HasConfig(global = true)
public abstract class ButtonPlugin extends JavaPlugin {
@Getter //Needs to be static as we don't know the plugin when a command is handled
private static Command2MC command2MC = new Command2MC();
private static final Command2MC command2MC = new Command2MC();
@Getter(AccessLevel.PROTECTED)
private IHaveConfig iConfig;
private final IHaveConfig iConfig = new IHaveConfig(this::saveConfig);
private CommentedConfiguration yaml;
@Getter(AccessLevel.PROTECTED)
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
*/
@Getter
private Stack<Component<?>> componentStack = new Stack<>();
;
private final Stack<Component<?>> componentStack = new Stack<>();
protected abstract void pluginEnable();
@ -60,7 +59,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
try {
pluginEnable();
} 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)
IHaveConfig.pregenConfig(this, null);
@ -72,7 +71,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
return false;
var section = config.getConfigurationSection("global");
if (section == null) section = config.createSection("global");
iConfig = new IHaveConfig(section, this::saveConfig);
iConfig.reset(section);
return true;
}
@ -84,10 +83,9 @@ public abstract class ButtonPlugin extends JavaPlugin {
pluginDisable();
if (ConfigData.saveNow(getConfig()))
getLogger().info("Saved configuration changes.");
iConfig = null; //Clearing the hashmap is not enough, we need to update the section as well
getCommand2MC().unregisterCommands(this);
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e);
TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e, this);
}
}
@ -126,15 +124,17 @@ public abstract class ButtonPlugin extends JavaPlugin {
var yc = YamlConfiguration.loadConfiguration(res);
for (var kv : yc.getValues(true).entrySet())
if (kv.getValue() instanceof String)
yaml.addComment(kv.getKey(), Arrays.stream(((String) kv.getValue()).split("\n"))
.map(str -> "# " + str.trim()).toArray(String[]::new));
yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""),
Arrays.stream(((String) kv.getValue()).split("\n"))
.map(str -> "# " + str.trim()).toArray(String[]::new));
return true;
}
@Override
public FileConfiguration getConfig() {
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;
}
@ -144,7 +144,7 @@ public abstract class ButtonPlugin extends JavaPlugin {
if (yaml != null)
yaml.save();
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to save config", e);
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.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
@ -19,7 +20,7 @@ import java.util.HashMap;
*
* @author dumptruckman &amp; Articdive
*/
public class CommentedConfiguration extends YamlConfiguration { //TODO: Remove FileMgmt dependency
public class CommentedConfiguration extends YamlConfiguration {
private HashMap<String, String> comments;
private File file;
@ -174,7 +175,7 @@ public class CommentedConfiguration extends YamlConfiguration { //TODO: Remove F
while (newContents.toString().startsWith(" " + System.getProperty("line.separator"))) {
newContents = new StringBuilder(newContents.toString().replaceFirst(" " + System.getProperty("line.separator"), ""));
}
Files.write(file.toPath(), newContents.toString().getBytes());
Files.write(file.toPath(), newContents.toString().getBytes(StandardCharsets.UTF_8));
}
}

View file

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

View file

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

View file

@ -3,8 +3,11 @@ package buttondevteam.lib.architecture;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import lombok.Getter;
import lombok.Setter;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.plugin.java.JavaPlugin;
import javax.annotation.Nullable;
import java.lang.reflect.InvocationTargetException;
@ -18,22 +21,41 @@ import java.util.stream.Collectors;
*/
public final class IHaveConfig {
private final HashMap<String, ConfigData<?>> datamap = new HashMap<>();
/**
* Returns the Bukkit ConfigurationSection. Use {@link #signalChange()} after changing it.
*/
@Getter
private ConfigurationSection config;
private final Runnable saveAction;
@Getter
@Setter
private Runnable saveAction;
/**
* 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) {
config = section;
public IHaveConfig(Runnable 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 def The value to use by default
@ -43,7 +65,7 @@ public final class IHaveConfig {
@SuppressWarnings("unchecked")
public <T> ConfigData<T> getData(String path, T def) {
ConfigData<?> data = datamap.get(path);
if (data == null) datamap.put(path, data = new ConfigData<>(config, path, def, def, saveAction));
if (data == null) datamap.put(path, data = new ConfigData<>(this, path, def, def, null, null));
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) {
ConfigData<?> data = datamap.get(path);
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;
}
@ -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) {
ConfigData<?> data = datamap.get(path);
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;
}
@ -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) {
ConfigData<?> data = datamap.get(path);
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;
}
@ -114,7 +136,7 @@ public final class IHaveConfig {
ConfigData<?> data = datamap.get(path);
if (data == null) {
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;
}
@ -134,7 +156,7 @@ public final class IHaveConfig {
ConfigData<?> data = datamap.get(path);
if (data == null) {
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;
}
@ -150,10 +172,25 @@ public final class IHaveConfig {
public <T> ListConfigData<T> getListData(String path) {
ConfigData<?> data = datamap.get(path);
if (data == null)
datamap.put(path, data = new ListConfigData<>(config, path, new ListConfigData.List<T>(), saveAction));
datamap.put(path, data = new ListConfigData<>(this, path, new ListConfigData.List<T>()));
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.
*
@ -164,6 +201,13 @@ public final class IHaveConfig {
val ms = obj.getClass().getDeclaredMethods();
for (val m : ms) {
if (!m.getReturnType().getName().equals(ConfigData.class.getName())) continue;
final String mName;
{
var name = m.getName();
var ind = name.lastIndexOf('$');
if (ind == -1) mName = name;
else mName = name.substring(ind + 1);
}
try {
m.setAccessible(true);
List<ConfigData<?>> configList;
@ -176,24 +220,36 @@ public final class IHaveConfig {
try {
return (ConfigData<?>) m.invoke(obj, kv.getValue());
} 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;
}
}).filter(Objects::nonNull).collect(Collectors.toList());
} else {
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;
}
for (val c : configList) {
if (c.getPath().length() == 0)
c.setPath(m.getName());
else if (!c.getPath().equals(m.getName()))
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + m.getName());
c.setPath(mName);
else if (!c.getPath().equals(mName))
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
}
} 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;
import lombok.val;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
@ -12,12 +11,12 @@ import java.util.function.UnaryOperator;
public class ListConfigData<T> extends ConfigData<ListConfigData.List<T>> {
@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 -> {
var l = new List<>((ArrayList<T>) list);
l.listConfig = def.listConfig;
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
}

View file

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

View file

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

View file

@ -11,7 +11,6 @@ import lombok.RequiredArgsConstructor;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
import java.io.InputStreamReader;
@ -23,14 +22,18 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.NumberFormat;
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.Supplier;
/**
* The method name is the subcommand, use underlines (_) to add further subcommands.
* The args may be null if the conversion failed and it's optional.
*/
public abstract class Command2<TC extends ICommand2, TP extends Command2Sender> {
public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Sender> {
protected Command2() {
commandHelp.add("§6---- Commands ----");
}
@ -60,10 +63,12 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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.
*/
String permGroup() default ""; //TODO
String permGroup() default "";
String[] aliases() default {};
}
@Target(ElementType.PARAMETER)
@ -72,13 +77,14 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
}
@AllArgsConstructor
protected static class SubcommandData<T extends ICommand2> {
protected static class SubcommandData<T extends ICommand2<?>> {
public final Method method;
public final T command;
public final String[] parameters;
public String[] helpText;
}
protected static class SubcommandHelpData<T extends ICommand2> extends SubcommandData<T> {
/*protected static class SubcommandHelpData<T extends ICommand2> extends SubcommandData<T> {
private final TreeSet<String> ht = new TreeSet<>();
private BukkitTask task;
@ -101,18 +107,19 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
task = null; //Run again, if needed
});
}
}
}*/
@RequiredArgsConstructor
protected static class ParamConverter<T> {
public final Function<String, T> converter;
public final String errormsg;
public final Supplier<Iterable<String>> allSupplier;
}
protected HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
private HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
protected final HashMap<String, SubcommandData<TC>> subcommands = 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;
@ -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.
* The converter may return null.
*
* @param cl The class of the result object
* @param converter The converter to use
* @param <T> The type of the result
* @param <T> The type of the result
* @param cl The class of the result object
* @param converter The converter to use
* @param allSupplier The supplier of all possible values (ideally)
*/
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
paramConverters.put(cl, new ParamConverter<>(converter, errormsg));
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg,
Supplier<Iterable<String>> allSupplier) {
paramConverters.put(cl, new ParamConverter<>(converter, errormsg, allSupplier));
}
public boolean handleCommand(TP sender, String commandline) {
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
String subcommand = commandline.substring(0, i).toLowerCase();
SubcommandData<TC> sd = subcommands.get(subcommand); //O(1)
SubcommandData<TC> sd = subcommands.get(subcommand);
if (sd == null) continue;
boolean sync = Bukkit.isPrimaryThread();
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
try {
handleCommandAsync(sender, commandline, sd, subcommand, sync);
} catch (Exception e) {
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e);
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e, MainPlugin.Instance);
}
});
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
* @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
sender.sendMessage(sd.helpText);
return;
@ -221,6 +230,18 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
continue;
} else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) {
try {
if (cl == boolean.class) {
params.add(Boolean.parseBoolean(param));
continue;
}
if (cl == char.class) {
if (param.length() != 1) {
sender.sendMessage("§c'" + param + "' is not a character.");
return;
}
params.add(param.charAt(0));
continue;
}
//noinspection unchecked
Number n = ChromaUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class<? extends Number>) cl);
params.add(n);
@ -232,7 +253,7 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
}
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() + "'");
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method + "'");
val cparam = conv.converter.apply(param);
if (cparam == null) {
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 = () -> {
try {
sd.method.setAccessible(true); //It may be part of a private class
val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time)
if (ret instanceof Boolean) {
if (!(boolean) ret) //Show usage
@ -249,9 +271,9 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
} else if (ret != null)
throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret);
} catch (InvocationTargetException e) {
TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause());
TBMCCoreAPI.SendException("An error occurred in a command handler for " + subcommand + "!", e.getCause(), MainPlugin.Instance);
} 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)
@ -262,9 +284,12 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
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;
val path = command.getCommandPath();
int x = path.indexOf(' ');
val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x);
//var scmdmap = subcommandStrings.computeIfAbsent(mainPath, k -> new HashSet<>()); //Used to display subcommands
@ -273,7 +298,7 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
boolean nosubs = true;
boolean isSubcommand = x != -1;
try { //Register the default handler first so it can be reliably overwritten
mainMethod = command.getClass().getMethod("def", Command2Sender.class, String.class);
mainMethod = command.getClass().getMethod("def", Command2Sender.class);
val cc = command.getClass().getAnnotation(CommandClass.class);
var ht = cc == null || isSubcommand ? new String[0] : cc.helpText(); //If it's not the main command, don't add it
if (ht.length > 0)
@ -284,46 +309,53 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
if (!commandHelp.contains(mainPath))
commandHelp.add(mainPath);
} 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()) {
val ann = method.getAnnotation(Subcommand.class);
if (ann == null) continue; //Don't call the method on non-subcommands because they're not in the yaml
var ht = command.getHelpText(method, ann);
if (ht != null) {
if (ht != null) { //The method is a subcommand
val subcommand = commandChar + path + //Add command path (class name by default)
getCommandPath(method.getName(), ' '); //Add method name, unless it's 'def'
ht = getParameterHelp(method, ht, subcommand);
subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text
var params = new String[method.getParameterCount() - 1];
ht = getParameterHelp(method, ht, subcommand, params);
var sd = new SubcommandData<>(method, command, params, ht);
registerCommand(path, method.getName(), ann, sd);
for (String p : command.getCommandPaths())
registerCommand(p, method.getName(), ann, sd);
addedSubcommands.add(sd);
scmdHelpList.add(subcommand);
nosubs = false;
}
}
if (nosubs && scmdHelpList.size() > 0)
scmdHelpList.remove(scmdHelpList.size() - 1); //Remove Subcommands header
if (mainMethod != null && !subcommands.containsKey(commandChar + path)) //Command specified by the class
subcommands.put(commandChar + path, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0])));
if (mainMethod != null && !mainPath.equals(commandChar + path)) { //Main command, typically the same as the above
if (isSubcommand) { //The class itself is a subcommand
val scmd = subcommands.computeIfAbsent(mainPath, p -> new SubcommandData<>(null, null, new String[]{"§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 (mainMethod != null && !subcommands.containsKey(commandChar + path)) { //Command specified by the class
var sd = new SubcommandData<>(mainMethod, command, null, scmdHelpList.toArray(new String[0]));
subcommands.put(commandChar + path, sd);
addedSubcommands.add(sd);
}
if (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");
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 {
if (ht.length > 0)
ht[0] = "§6---- " + ht[0] + " ----";
YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor
val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName());
val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName().replace('$', '.'));
if (ccs != null) {
val cs = ccs.getConfigurationSection(method.getName());
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);
both[ht.length] = "§6Usage:§r " + subcommand + " " + params;
ht = both;
var paramArray = params.split(" ");
for (int j = 0; j < paramArray.length && j < parameters.length; j++)
parameters[j] = paramArray[j];
} else
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method.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
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
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;
}
private void registerCommand(String path, String methodName, Subcommand ann, SubcommandData<TC> sd) {
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
subcommands.put(subcommand, sd);
for (String alias : ann.aliases())
subcommands.put(commandChar + path + alias, sd);
}
public abstract boolean hasPermission(TP sender, TC command, Method subcommand);
public String[] getCommandsText() {
@ -371,11 +413,19 @@ public abstract class Command2<TC extends ICommand2, TP extends Command2Sender>
for (val method : command.getClass().getMethods()) {
val ann = method.getAnnotation(Subcommand.class);
if (ann == null) continue;
val subcommand = commandChar + path + getCommandPath(method.getName(), ' ');
subcommands.remove(subcommand);
unregisterCommand(path, method.getName(), ann);
for (String p : command.getCommandPaths())
unregisterCommand(p, method.getName(), ann);
}
}
private void unregisterCommand(String path, String methodName, Subcommand ann) {
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
subcommands.remove(subcommand);
for (String alias : ann.aliases())
subcommands.remove(commandChar + path + alias);
}
/**
* It will start with the given replace char.
*

View file

@ -1,26 +1,41 @@
package buttondevteam.lib.chat;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.player.ChromaGamerBase;
import com.mojang.brigadier.arguments.*;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import lombok.val;
import me.lucko.commodore.Commodore;
import me.lucko.commodore.CommodoreProvider;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.OfflinePlayer;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.server.TabCompleteEvent;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.javatuples.Triplet;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implements Listener {
/**
@ -30,7 +45,19 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
*/
@Override
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(' ', '.');
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
Bukkit.getPluginManager().addPermission(new Permission(perm,
@ -46,9 +73,9 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
}
String pg = permGroup(command, method);
if (pg.length() == 0) continue;
perm = "chroma." + pg;
if (Bukkit.getPluginManager().getPermission(perm) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(new Permission(perm,
String permGroup = "chroma." + pg;
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
Bukkit.getPluginManager().addPermission(new Permission(permGroup,
PermissionDefault.OP)); //Do not allow any commands that belong to a group
}
}
@ -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);
else
p = false; //Use sender's method
//System.out.println("playerHas " + perm + ": " + p);
//System.out.println("hasPermission: " + sender.hasPermission(perm));
if (!p) p = sender.hasPermission(perm);
} else break; //If any of the permissions aren't granted then don't allow
}
@ -129,8 +154,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
* {@see super#addParamConverter}
*/
@Override
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
super.addParamConverter(cl, converter, "§c" + errormsg);
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, Supplier<Iterable<String>> allSupplier) {
super.addParamConverter(cl, converter, "§c" + errormsg, allSupplier);
}
public void unregisterCommands(ButtonPlugin plugin) {
@ -148,111 +173,310 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false));
}
@EventHandler
private void handleTabComplete(TabCompleteEvent event) {
String commandline = event.getBuffer();
CommandSender sender = event.getSender();
//System.out.println("tab");
for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) {
String subcommand = commandline.substring(0, i).toLowerCase();
if (subcommand.length() == 0 || subcommand.charAt(0) != '/') subcommand = '/' + subcommand; //Console
//System.out.println("Subcommand: " + subcommand);
SubcommandData<ICommand2MC> sd = subcommands.get(subcommand); //O(1)
if (sd == null) continue;
//System.out.println("ht: " + Arrays.toString(sd.helpText));
Arrays.stream(sd.helpText).skip(1).map(ht -> new HashMap.SimpleEntry<>(ht, subcommands.get(ht))).filter(e -> e.getValue() != null)
.filter(kv -> kv.getKey().startsWith(commandline))
.filter(kv -> hasPermission(sender, kv.getValue().command, kv.getValue().method))
.forEach(kv -> event.getCompletions().add((kv.getKey()).substring(kv.getKey().lastIndexOf(' ', commandline.length()) + 1)));
if (sd.method == null || sd.command == null)
return;
/*if (!hasPermission(sender, sd.command, sd.method)) { - TODO: Arguments
sender.sendMessage("§cYou don't have permission to use this command");
return true;
/*@EventHandler
public void onTabComplete(TabCompleteEvent event) {
try {
event.getCompletions().clear(); //Remove player names
} catch (UnsupportedOperationException e) {
//System.out.println("Tabcomplete: " + event.getBuffer());
//System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions"));
//System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners()));
}
}*/
@Override
public boolean handleCommand(Command2MCSender sender, String commandline) {
return handleCommand(sender, commandline, true);
}
private boolean handleCommand(Command2MCSender sender, String commandline, boolean checkPlugin) {
int i = commandline.indexOf(' ');
String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash
PluginCommand pcmd;
if (!checkPlugin
|| MainPlugin.Instance.prioritizeCustomCommands.get()
|| (pcmd = Bukkit.getPluginCommand(mainpath)) == null //Our commands aren't PluginCommands
|| pcmd.getPlugin() instanceof ButtonPlugin) //Unless it's specified in the plugin.yml
return super.handleCommand(sender, commandline);
else
return false;
}
private boolean shouldRegisterOfficially = true;
private Command registerOfficially(ICommand2MC command, List<SubcommandData<ICommand2MC>> subcmds) {
if (!shouldRegisterOfficially || command.getPlugin() == null) return null;
try {
var cmdmap = (SimpleCommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer());
var path = command.getCommandPath();
int x = path.indexOf(' ');
var mainPath = path.substring(0, x == -1 ? path.length() : x);
Command bukkitCommand;
{ //Commands conflicting with Essentials have to be registered in plugin.yml
var oldcmd = cmdmap.getCommand(command.getPlugin().getName() + ":" + mainPath); //The label with the fallback prefix is always registered
if (oldcmd == null) {
bukkitCommand = new BukkitCommand(mainPath);
cmdmap.register(command.getPlugin().getName(), bukkitCommand);
} else {
bukkitCommand = oldcmd;
if (bukkitCommand instanceof PluginCommand)
((PluginCommand) bukkitCommand).setExecutor(this::executeCommand);
}
bukkitCommand = oldcmd == null ? new BukkitCommand(mainPath) : oldcmd;
}
val params = new ArrayList<Object>(sd.method.getParameterCount());
int j = subcommand.length(), pj;
Class<?>[] parameterTypes = sd.method.getParameterTypes();
if (parameterTypes.length == 0)
throw new Exception("No sender parameter for method '" + sd.method + "'");
val sendertype = parameterTypes[0];
final ChromaGamerBase cg;
if (sendertype.isAssignableFrom(sender.getClass()))
params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type
else if (sender instanceof Command2MCSender
&& sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass()))
params.add(((Command2MCSender) sender).getSender());
else if (ChromaGamerBase.class.isAssignableFrom(sendertype)
&& sender instanceof Command2MCSender
&& (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null
&& cg.getClass() == sendertype) //The command expects a user of our system
params.add(cg);
else {
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());
}*/
if (CommodoreProvider.isSupported())
TabcompleteHelper.registerTabcomplete(command, subcmds, bukkitCommand);
return bukkitCommand;
} catch (Exception e) {
if (command.getComponent() == null)
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getPlugin());
else
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getComponent());
shouldRegisterOfficially = false;
return null;
}
}
private boolean executeCommand(CommandSender sender, Command command, String label, String[] args) {
var user = ChromaGamerBase.getFromSender(sender);
if (user == null) {
TBMCCoreAPI.SendException("Failed to run Bukkit command for user!", new Throwable("No Chroma user found"), MainPlugin.Instance);
sender.sendMessage("§cAn internal error occurred.");
return true;
}
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;
import buttondevteam.core.component.channel.Channel;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.command.CommandSender;
@ -7,6 +8,8 @@ import org.bukkit.command.CommandSender;
@RequiredArgsConstructor
public class Command2MCSender implements Command2Sender {
private @Getter final CommandSender sender;
private @Getter final Channel channel;
private @Getter final CommandSender permCheck;
@Override
public void sendMessage(String message) {

View file

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

View file

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

View file

@ -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;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel;
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 org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -12,37 +15,60 @@ import javax.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ChromaGamerEnforcer
public abstract class ChromaGamerBase implements AutoCloseable {
public static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final HashBiMap<Class<? extends ChromaGamerBase>, String> playerTypes = HashBiMap.create();
public abstract class ChromaGamerBase {
private static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/**
* Holds data per user class
*/
private static final HashMap<Class<? extends ChromaGamerBase>, StaticUserData<?>> staticDataMap = new HashMap<>();
/**
* 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) {
if (userclass.isAnnotationPresent(UserClass.class))
playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername());
else if (userclass.isAnnotationPresent(AbstractUserClass.class))
playerTypes.put(userclass.getAnnotation(AbstractUserClass.class).prototype(),
userclass.getAnnotation(AbstractUserClass.class).foldername());
else // <-- Really important
//protected YamlConfiguration plugindata;
@Getter
protected final IHaveConfig config = new IHaveConfig(this::save);
protected CommonUserData<?> commonUserData;
/**
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) and to init the configs.
*/
public static <T extends ChromaGamerBase> void RegisterPluginUserClass(Class<T> userclass, Supplier<T> constructor) {
Class<? extends T> cl;
String folderName;
if (userclass.isAnnotationPresent(UserClass.class)) {
cl = userclass;
folderName = userclass.getAnnotation(UserClass.class).foldername();
} else if (userclass.isAnnotationPresent(AbstractUserClass.class)) {
var ucl = userclass.getAnnotation(AbstractUserClass.class).prototype();
if (!userclass.isAssignableFrom(ucl))
throw new RuntimeException("The prototype class (" + ucl.getSimpleName() + ") must be a subclass of the userclass parameter (" + userclass.getSimpleName() + ")!");
//noinspection unchecked
cl = (Class<? extends T>) ucl;
folderName = userclass.getAnnotation(AbstractUserClass.class).foldername();
} else // <-- Really important
throw new RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase");
var sud = new StaticUserData<T>(folderName);
sud.getConstructors().put(cl, constructor);
sud.getConstructors().put(userclass, constructor); // Alawys register abstract and prototype class (TBMCPlayerBase and TBMCPlayer)
staticDataMap.put(userclass, sud);
}
/**
* Returns the folder name for the given player class.
*
* @param cl
* The class to get the folder from (like {@link TBMCPlayerBase} or one of it's subclasses)
*
* @param cl The class to get the folder from (like {@link TBMCPlayerBase} or one of it's subclasses)
* @return The folder name for the given type
* @throws RuntimeException
* If the class doesn't have the {@link UserClass} annotation.
* @throws RuntimeException If the class doesn't have the {@link UserClass} annotation.
*/
public static <T extends ChromaGamerBase> String getFolderForType(Class<T> cl) {
if (cl.isAnnotationPresent(UserClass.class))
@ -54,52 +80,63 @@ public abstract class ChromaGamerBase implements AutoCloseable {
/**
* Returns the player class for the given folder name.
*
* @param foldername
* The folder to get the class from (like "minecraft")
*
* @param foldername The folder to get the class from (like "minecraft")
* @return The type for the given folder name or null if not found
*/
public static Class<? extends ChromaGamerBase> getTypeForFolder(String foldername) {
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 cl User class
* @return The user object
* @param fname Filename without .yml, the user's identifier for that type
* @param cl User class
* @return The user object
*/
public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
try {
T obj = cl.newInstance();
final String folder = getFolderForType(cl);
public static synchronized <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
StaticUserData<?> staticUserData = null;
for (var sud : staticDataMap.entrySet()) {
if (sud.getKey().isAssignableFrom(cl)) {
staticUserData = sud.getValue();
break;
}
}
if (staticUserData == null)
throw new RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass");
var commonUserData = staticUserData.getUserDataMap().get(fname);
if (commonUserData == null) {
final String folder = staticUserData.getFolder();
final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml");
file.getParentFile().mkdirs();
obj.plugindata = YamlConfiguration.loadConfiguration(file);
obj.plugindata.set(folder + "_id", fname);
return obj;
} catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while loading a " + cl.getSimpleName() + "!", e);
var playerData = YamlConfiguration.loadConfiguration(file);
commonUserData = new CommonUserData<>(playerData);
playerData.set(staticUserData.getFolder() + "_id", fname);
staticUserData.getUserDataMap().put(fname, commonUserData);
}
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.
*
@ -124,88 +161,110 @@ public abstract class ChromaGamerBase implements AutoCloseable {
return null;
}
/**
* Saves the player. It'll pass all exceptions to the caller. To automatically handle the exception, use {@link #save()} instead.
*/
@Override
public void close() throws Exception {
if (plugindata.getKeys(false).size() > 0)
plugindata.save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
public static void saveUsers() {
synchronized (staticDataMap) {
for (var sud : staticDataMap.values())
for (var cud : sud.getUserDataMap().values())
ConfigData.saveNow(cud.getPlayerData()); //Calls save()
}
}
protected void init() {
config.reset(commonUserData.getPlayerData());
}
/**
* Saves the player. It'll 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 {
close();
if (commonUserData.getPlayerData().getKeys(false).size() > 0)
commonUserData.getPlayerData().save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
} 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
*
* @param user
* The account to connect with
* Removes the user from the cache. This will be called automatically after some time by default.
*/
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
if (!playerTypes.containsKey(getClass()))
throw new RuntimeException("Class not registered as a user class! Use TBMCCoreAPI.RegisterUserClass");
final String ownFolder = getFolder();
final String userFolder = user.getFolder();
if (ownFolder.equalsIgnoreCase(userFolder))
throw new RuntimeException("Do not connect two accounts of the same type! Type: "+ownFolder);
user.plugindata.set(ownFolder + "_id", plugindata.getString(ownFolder + "_id"));
plugindata.set(userFolder + "_id", user.plugindata.getString(userFolder + "_id"));
throw new RuntimeException("Do not connect two accounts of the same type! Type: " + ownFolder);
var ownData = commonUserData.getPlayerData();
var userData = user.commonUserData.getPlayerData();
userData.set(ownFolder + "_id", ownData.getString(ownFolder + "_id"));
ownData.set(userFolder + "_id", userData.getString(userFolder + "_id"));
config.signalChange();
user.config.signalChange();
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");
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())
continue;
final String otherid = sourcedata.getString(entry.getValue() + "_id");
var entryFolder = entry.getValue().getFolder();
final String otherid = sourcedata.getString(entryFolder + "_id");
if (otherid == null)
continue;
try (ChromaGamerBase cg = getUser(otherid, entry.getKey())) {
cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs
for (val item : playerTypes.entrySet())
if (sourcedata.contains(item.getValue() + "_id"))
cg.plugindata.set(item.getValue() + "_id", sourcedata.getString(item.getValue() + "_id")); // Set all existing IDs
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to update " + sourcefolder + " ID in player files for " + id
+ " in folder with " + entry.getValue() + " id " + otherid + "!", e);
ChromaGamerBase cg = getUser(otherid, entry.getKey());
var cgData = cg.commonUserData.getPlayerData();
cgData.set(sourcefolder + "_id", id); // Set new IDs
for (val item : staticDataMap.entrySet()) {
var itemFolder = item.getValue().getFolder();
if (sourcedata.contains(itemFolder + "_id")) {
cgData.set(itemFolder + "_id", sourcedata.getString(itemFolder + "_id")); // Set all existing IDs
}
}
cg.config.signalChange();
}
};
sync.accept(plugindata);
sync.accept(user.plugindata);
sync.accept(ownData);
sync.accept(userData);
}
/**
* Retunrs the ID for the T typed player object connected with this one or null if no connection found.
*
* @param cl
* The player class to get the ID from
* Returns the ID for the T typed player object connected with this one or null if no connection found.
*
* @param cl The player class to get the ID from
* @return The ID or null if not found
*/
public <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return plugindata.getString(getFolderForType(cl) + "_id");
public final <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
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>
* Make sure to close both the returned and this object. A try-with-resources block or two can help.<br>
*
* @param cl
* The target player class
* @return The player as a {@link T} object or null if not having an account there
* Returns a player instance of the given type that represents the same player. This will return a new instance unless the player is cached.<br>
* If the class is a subclass of the current class then the same ID is used, otherwise, a connected ID is used, if found.
*
* @param cl The target player class
* @return The player as a {@link T} object or null if the user doesn't have an account there
*/
@SuppressWarnings("unchecked")
@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()))
return (T) this;
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!");
if (newfolder.equals(getFolder())) // If in the same folder, the same filename is used
return getUser(getFileName(), cl);
if (!plugindata.contains(newfolder + "_id"))
var playerData = commonUserData.getPlayerData();
if (!playerData.contains(newfolder + "_id"))
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());
}
private void ThrowIfNoUser() {
if (!getClass().isAnnotationPresent(UserClass.class)
&& !getClass().isAnnotationPresent(AbstractUserClass.class))
throw new RuntimeException("Class not registered as a user class! Use @UserClass");
}
@SuppressWarnings("rawtypes")
private final HashMap<String, PlayerData> datamap = new HashMap<>();
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(String sectionname, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
@SuppressWarnings("rawtypes")
private final HashMap<String, EnumPlayerData> dataenummap = new HashMap<>();
private ChannelPlayerData datachannel;
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(String sectionname, Class<T> cl, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(Class<T> cl, T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Channel
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected ChannelPlayerData dataChannel(Channel def) { //TODO: Make interface with fromString() method and require use of that for player data types
ThrowIfNoUser();
if (datachannel == null)
datachannel = new ChannelPlayerData("channel", plugindata, def);
return datachannel;
}
/**
* Get player information. This method calls the {@link TBMCPlayerGetInfoEvent} to get all the player information across the TBMC plugins.
*
* @param target
* The {@link InfoTarget} to return the info for.
*
* @param target The {@link InfoTarget} to return the info for.
* @return The player information.
*/
public String getInfo(InfoTarget target) {
public final String getInfo(InfoTarget target) {
TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target);
Bukkit.getServer().getPluginManager().callEvent(event);
return event.getResult();
@ -323,7 +311,6 @@ public abstract class ChromaGamerBase implements AutoCloseable {
//-----------------------------------------------------------------
public ChannelPlayerData channel() {
return dataChannel(Channel.GlobalChat);
}
public final ConfigData<Channel> channel = config.getData("channel", Channel.GlobalChat,
id -> Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID);
}

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

View file

@ -1,28 +1,21 @@
package buttondevteam.lib.player;
import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer.class)
@TBMCPlayerEnforcer
public abstract class TBMCPlayerBase extends ChromaGamerBase {
protected UUID uuid;
private String pluginname;
protected TBMCPlayerBase() {
if (getClass().isAnnotationPresent(PlayerClass.class))
pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
else
throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
}
@Getter
private final IHaveConfig config = new IHaveConfig(this::save);
public UUID getUUID() {
if (uuid == null)
@ -30,156 +23,61 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase {
return uuid;
}
public PlayerData<String> PlayerName() {
return super.data(null);
}
public final ConfigData<String> PlayerName = super.config.getData("PlayerName", "");
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@Override
protected <T> PlayerData<T> data(T def) {
return super.data(pluginname, def);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@Override
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(Class<T> cl, T def) {
return super.dataEnum(pluginname, cl, def);
}
/**
* Get player as a plugin player
* Get player as a plugin player.
*
* @param uuid The UUID of the player to get
* @param cl The type of the player
* @return The requested player object
*/
@SuppressWarnings("unchecked")
public static <T extends TBMCPlayerBase> T getPlayer(UUID uuid, Class<T> cl) {
if (playermap.containsKey(uuid + "-" + cl.getSimpleName()))
return (T) playermap.get(uuid + "-" + cl.getSimpleName());
try {
T player;
if (playermap.containsKey(uuid + "-" + TBMCPlayer.class.getSimpleName())) {
player = cl.newInstance();
player.plugindata = playermap.get(uuid + "-" + TBMCPlayer.class.getSimpleName()).plugindata;
playermap.put(uuid + "-" + cl.getSimpleName(), player); // It will get removed on player quit
} else
player = ChromaGamerBase.getUser(uuid.toString(), cl);
player.uuid = uuid;
return player;
} catch (Exception e) {
TBMCCoreAPI.SendException(
"Failed to get player with UUID " + uuid + " and class " + cl.getSimpleName() + "!", e);
return null;
}
var player = ChromaGamerBase.getUser(uuid.toString(), cl);
if (!player.getUUID().equals(uuid)) //It will be set from the filename because we check it for scheduling the uncache.
throw new IllegalStateException("Player UUID differs after converting from and to string...");
return player;
}
@Override
public void init() {
super.init();
String pluginname;
if (getClass().isAnnotationPresent(PlayerClass.class))
pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
else
throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
var playerData = commonUserData.getPlayerData();
var section = playerData.getConfigurationSection(pluginname);
if (section == null) section = playerData.createSection(pluginname);
config.reset(section);
}
@Override
protected void scheduleUncache() { //Don't schedule it, it will happen on quit - if the player is online
var p = Bukkit.getPlayer(getUUID());
if (p == null || !p.isOnline())
super.scheduleUncache();
}
/**
* Key: UUID-Class
*/
static final ConcurrentHashMap<String, TBMCPlayerBase> playermap = new ConcurrentHashMap<>();
/**
* Gets the TBMCPlayer object as a specific plugin player, keeping it's data<br>
* Make sure to use try-with-resources with this to save the data, as it may need to load the file
*
* @param cl The TBMCPlayer subclass
*/
public <T extends TBMCPlayerBase> T asPluginPlayer(Class<T> cl) {
return getPlayer(uuid, cl);
}
/**
* Only intended to use from ButtonCore
*/
public static void savePlayer(TBMCPlayerBase player) {
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerSaveEvent(player));
try {
player.close();
} catch (Exception e) {
new Exception("Failed to save player data for " + player.PlayerName().get(), e).printStackTrace();
}
}
/**
* Only intended to use from ButtonCore
*/
public static void joinPlayer(Player p) {
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class);
Bukkit.getLogger().info("Loaded player: " + player.PlayerName().get());
if (player.PlayerName().get() == null) {
player.PlayerName().set(p.getName());
Bukkit.getLogger().info("Player name saved: " + player.PlayerName().get());
} else if (!p.getName().equals(player.PlayerName().get())) {
TownyComponent.renameInTowny(player.PlayerName().get(), p.getName());
player.PlayerName().set(p.getName());
Bukkit.getLogger().info("Renamed to " + p.getName());
}
playermap.put(p.getUniqueId() + "-" + TBMCPlayer.class.getSimpleName(), player);
// Load in other plugins
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerLoadEvent(player));
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerJoinEvent(player, p));
player.save();
}
/**
* Only intended to use from ButtonCore
*/
public static void quitPlayer(Player p) {
final TBMCPlayerBase player = playermap.get(p.getUniqueId() + "-" + TBMCPlayer.class.getSimpleName());
player.save();
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerQuitEvent(player, p));
playermap.entrySet().removeIf(entry -> entry.getKey().startsWith(p.getUniqueId().toString()));
}
public static void savePlayers() {
playermap.values().forEach(p -> {
try {
p.close();
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while saving player " + p.PlayerName().get() + " (" + p.getFolder()
+ "/" + p.getFileName() + ")!", e);
}
});
}
/**
* This method returns a TBMC player from their name. Calling this method may return an offline player which will load it, therefore it's highly recommended to use {@link #close()} to unload the
* player data. Using try-with-resources may be the easiest way to achieve this. Example:
*
* <pre>
* {@code
* try(TBMCPlayer player = getFromName(p))
* {
* ...
* }
* </pre>
* This method returns a TBMC player from their name. See {@link Bukkit#getOfflinePlayer(String)}.
*
* @param name The player's name
* @return The {@link TBMCPlayer} object for the player
*/
@SuppressWarnings("deprecation")
public static <T extends TBMCPlayerBase> T getFromName(String name, Class<T> cl) {
@SuppressWarnings("deprecation")
OfflinePlayer p = Bukkit.getOfflinePlayer(name);
if (p != null)
return getPlayer(p.getUniqueId(), cl);
else
return null;
return getPlayer(p.getUniqueId(), cl);
}
@Override
public void close() throws Exception {
Set<String> keys = plugindata.getKeys(false);
protected void save() {
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
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
version: 1.0
version: '${noprefix.version}'
author: NorbiPeti
commands:
updateplugin:
@ -9,10 +9,6 @@ commands:
description: Schedules a restart for a given time.
primerestart:
description: Restarts the server as soon as nobody is online.
randomtp:
description: teleport player to random location within world border. Every five players teleport to the same general area, and then a new general area is randomly selected for the next five players.
member:
description: Add or remove a member
component:
description: Enable or disable or list components
dontrunthiscmd:
@ -22,4 +18,5 @@ softdepend:
- Towny
- Votifier
- 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"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="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/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<packaging>pom</packaging>
<version>master-SNAPSHOT</version>
<properties>
<lombok.version>1.18.10</lombok.version>
</properties>
<name>Core POM for Chroma</name>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<packaging>pom</packaging>
<version>master-SNAPSHOT</version>
<properties>
<lombok.version>1.18.10</lombok.version>
</properties>
<name>Core POM for Chroma</name>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>com.github.bsideup.jabel</groupId>
<artifactId>jabel-javac-plugin</artifactId>
<version>0.2.0</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors> <!-- Order is important, so these lines are needed -->
<annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
</annotationProcessor>
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>com.github.bsideup.jabel</groupId>
<artifactId>jabel-javac-plugin</artifactId>
<version>0.2.0</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors> <!-- Order is important, so these lines are needed -->
<annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
</annotationProcessor>
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration>
</plugin>
<!-- <plugin>
<groupId>io.github.1tchy</groupId>
<artifactId>variable-search-replace-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<text>${project.version}</text>
<search>^v</search>
<variableName>noprefix.version</variableName>
</configuration>
</execution>
</executions>
</plugin> -->
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>intellij-idea-only</id>
<activation>
<property>
<name>idea.maven.embedder.version</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>11</release>
<!--
<compilerArgs>
<arg>HYPHENHYPHENenable-preview</arg>
</compilerArgs>
-->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<profiles>
<profile>
<id>intellij-idea-only</id>
<activation>
<property>
<name>idea.maven.embedder.version</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>11</release>
<!--
<compilerArgs>
<arg>HYPHENHYPHENenable-preview</arg>
</compilerArgs>
-->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

5
pom.xml Executable file → Normal file
View file

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