Compare commits
481 commits
Author | SHA1 | Date | |
---|---|---|---|
66c1b1b14f | |||
496af97e1c | |||
6ec63b5ae7 | |||
ea7520d99a | |||
|
47461d49c2 | ||
7120e3c40e | |||
|
c6b2dc3443 | ||
4e58ddd475 | |||
5c83b923da | |||
83b5f1fec4 | |||
|
59ccc55f98 | ||
f92cc773c1 | |||
2a008906f4 | |||
9fb35eb6cc | |||
66be5ab0dc | |||
5b5a8877cc | |||
09074f1a2e | |||
ff0d54e00b | |||
5199482053 | |||
c12a24895e | |||
51e0ca4f4c | |||
267a350473 | |||
132eba7db6 | |||
83ecf7717f | |||
893b24ed5f | |||
af0aed65e0 | |||
47dbb987eb | |||
|
28e26ad3d1 | ||
|
8e664d073f | ||
1aa9cd3552 | |||
731065fe2a | |||
01dd8a477e | |||
ab4dd75684 | |||
5b27af8925 | |||
28e44d5179 | |||
cedeca2f61 | |||
784ad5e1c7 | |||
9923f26698 | |||
b89391f84c | |||
8f610c9935 | |||
4310e45a6f | |||
43b7bd442a | |||
ef96ec2604 | |||
8815877bea | |||
58fcd4c145 | |||
1139f832b6 | |||
9dae442950 | |||
82858b0a41 | |||
7b505bb8e9 | |||
6b68bdab0f | |||
8344adff1a | |||
f5406a8c0e | |||
95a8e92b51 | |||
792a127bdd | |||
a3160c4040 | |||
b77871bb8c | |||
e676ea516c | |||
0e88c61b77 | |||
425cbcd9d2 | |||
00738fe820 | |||
3c4f9f6c7a | |||
2cca2e2096 | |||
59aa13cd74 | |||
05720afdf8 | |||
393c9c9b08 | |||
a0c211f4be | |||
def4a846b2 | |||
b9781f19f0 | |||
b7260d318c | |||
dd1f42d420 | |||
c947c887a1 | |||
23f3c0f133 | |||
d2ab3511c2 | |||
8f6ee349de | |||
220829a206 | |||
9aa320e663 | |||
d48a2d17d3 | |||
6491a4e732 | |||
a1919c04d0 | |||
1a4829e894 | |||
5da07565b0 | |||
cecd8df991 | |||
fa18e8f22a | |||
89c27a10c6 | |||
fb9c108c52 | |||
34637ac536 | |||
dea1a46f55 | |||
8f75998b58 | |||
0e4eee55ac | |||
65419ac20d | |||
7de1d0575c | |||
2f62d2b962 | |||
0c5345d3ff | |||
d100b86d72 | |||
dd64062fba | |||
7b29f00105 | |||
383e9dc8d5 | |||
1a6bd29741 | |||
ed4a4da37f | |||
17ac28b9b7 | |||
bb10f9fb0f | |||
0967095f59 | |||
317b5a3bda | |||
a17923602f | |||
feee6a0ebe | |||
2ad7a757c6 | |||
14bcebabcd | |||
fa6c453a8d | |||
773277cb27 | |||
a39fd083be | |||
db8dfa79bc | |||
0523ebe214 | |||
b1ea027d1c | |||
c24b8074ab | |||
cc3ed7cf52 | |||
5872117d28 | |||
a75397f2b0 | |||
497c761cc0 | |||
0d4f3b09dd | |||
4dade43f82 | |||
ce29422177 | |||
4132a33037 | |||
aec9682b2a | |||
87189eb1ad | |||
c8067257f9 | |||
03eb8d480a | |||
f8fd481f46 | |||
b9ce3dc0e5 | |||
9ea811ba40 | |||
0d5ca5a9af | |||
70cabfaa81 | |||
c62424389f | |||
25b9caa903 | |||
7289385a33 | |||
9c15d0ffff | |||
9620ae44f6 | |||
138259412f | |||
7fd422136b | |||
f875985aa9 | |||
fab2b5819d | |||
0cdfcb9ff4 | |||
e4f5850ad6 | |||
d19936309f | |||
9022d87dc4 | |||
95796a1b3e | |||
a91786cf0d | |||
d245d21a84 | |||
6b99bc22d7 | |||
e3df62af7e | |||
e32805c1fc | |||
ab24480f89 | |||
98b73aaac3 | |||
ab603276d3 | |||
a4e96b0ed7 | |||
7331568886 | |||
41d40fa415 | |||
a6effaaf43 | |||
5b75c2fa02 | |||
4ee76a97bf | |||
2beb6662ea | |||
cbbb5572b8 | |||
788c2e8a87 | |||
1b152d8b4e | |||
a6d1122f7f | |||
245f6bbf59 | |||
bb31f4e378 | |||
814a5c7eb9 | |||
7f385df3b1 | |||
ff246b8c8b | |||
df5befe1d9 | |||
dbce36cd4e | |||
a417d0ea3f | |||
9b097b7858 | |||
f8a850df76 | |||
652cdfc131 | |||
f63443bf44 | |||
81cb364ddc | |||
5ef5bf47a2 | |||
248b0d8d0a | |||
af412a40fd | |||
5def9344e8 | |||
e13efa5e65 | |||
7ab997ddad | |||
ae84994025 | |||
8d3b6b642a | |||
718d8d073c | |||
c523ff7a5b | |||
806550a625 | |||
9bb8713714 | |||
cdcf8b9b2e | |||
4a6f3df783 | |||
07b52a5b54 | |||
be6e4acb7e | |||
ca7c56501f | |||
bc24df3840 | |||
4de1466e7f | |||
54ce0b91fb | |||
6cf28e5c9a | |||
86a2a41511 | |||
a691278ea9 | |||
c94eaebf5a | |||
d7accf5885 | |||
4519d5f64a | |||
7e63ae12e6 | |||
5d91747e22 | |||
eb8dff3a41 | |||
739276e797 | |||
75ab715c6e | |||
a24c4a5e54 | |||
bdf824e20e | |||
eb9deb39b4 | |||
ca80b3f535 | |||
945151518a | |||
509fe99294 | |||
c1751b8f20 | |||
e25b3740b6 | |||
0899205dcb | |||
89fb273714 | |||
59d8cd970e | |||
d997042e1a | |||
6bbc571393 | |||
1a4f1e5f27 | |||
847e940388 | |||
48d0f76a99 | |||
fe51136f30 | |||
89d88ef952 | |||
1a975e0d8e | |||
1ddfd1e3cc | |||
cf74a1cb3c | |||
e08952a056 | |||
9dcaaccc58 | |||
|
c23e4db5e7 | ||
20d0e7f0fe | |||
|
4ea2343cf4 | ||
fc11b1fa7e | |||
4788c81270 | |||
da6feb43e9 | |||
07a060e479 | |||
6006e995b9 | |||
39ee9ba498 | |||
765610e6e3 | |||
1943f7ce0c | |||
cbc0a40733 | |||
6fefbe6362 | |||
2f712a6ce5 | |||
aaa7e7b7ee | |||
c939d1e752 | |||
3a836aae15 | |||
6cb1112e2f | |||
995357d1ee | |||
c2d13a5fc8 | |||
d6c3ec7c7e | |||
313a568304 | |||
e4813e08fe | |||
9475b30f3e | |||
a34bada348 | |||
86a65899d9 | |||
8a08176638 | |||
8724a1f5dd | |||
91d7c1278e | |||
606263622e | |||
21eefcec45 | |||
|
e3385c7897 | ||
b2ecc27f86 | |||
6a2113bda4 | |||
2d3ed0345f | |||
4c83816dc0 | |||
a80e8a808d | |||
1c7df17d9d | |||
ed637dec16 | |||
|
0b3743cb01 | ||
b8e9810e18 | |||
|
442c32f17b | ||
|
d5edd0cfe6 | ||
29cfa66ae3 | |||
|
9cf9f5f72e | ||
|
193dbb9365 | ||
943f32c9ae | |||
25f770849f | |||
c0e4a9e065 | |||
36b3c1895d | |||
4a5479b027 | |||
acebf37359 | |||
f5ce88faf6 | |||
9a61047cd2 | |||
308006d624 | |||
bcf8cc4e99 | |||
672fab2d1b | |||
3af6063c99 | |||
afd1b8dfa2 | |||
3bab090fc7 | |||
0160b4146f | |||
7c8a47e4b2 | |||
1497a487a4 | |||
ced3bd10a4 | |||
f2931ed2fd | |||
b9738837a1 | |||
153425be5e | |||
11f13af8cc | |||
ef42d811b0 | |||
159cef01ff | |||
5a559aa4c3 | |||
5f62df97de | |||
5e4128afe7 | |||
88f85d2b15 | |||
28fcb2f8d8 | |||
4aa7cccb5c | |||
27a92b164e | |||
201ae7c8f6 | |||
37ef10da2a | |||
a187170fdd | |||
9ea151596a | |||
735e4612c5 | |||
7fff1ca655 | |||
7f45f08b32 | |||
ed4cb11b87 | |||
b4d4023e9c | |||
6a5dda7028 | |||
e82f8c9eb3 | |||
ef0b9caa86 | |||
e793571845 | |||
48edb00d75 | |||
60d6a03236 | |||
222a1225bc | |||
8ad5504527 | |||
bceb57c522 | |||
6d2b253633 | |||
acb874804e | |||
7c3971aa59 | |||
3285380ece | |||
908b62217a | |||
ab4097c669 | |||
e54b54bac0 | |||
|
7719f016e5 | ||
|
7c6d0214f9 | ||
f30dda28fa | |||
1c1d42df4b | |||
2cf12399ec | |||
d5c9882009 | |||
62f783e695 | |||
9661cd3405 | |||
0d38f9b85b | |||
89c707332f | |||
381739456c | |||
55ac426554 | |||
773bb66808 | |||
1e4c0df93d | |||
c155eb761b | |||
e636738acb | |||
|
9afc2f8a96 | ||
|
cc365d5555 | ||
|
88cab094ab | ||
|
7ca344e4f9 | ||
a9b0e98e07 | |||
d91da7f306 | |||
|
85d2506da7 | ||
|
e5bb0e43dd | ||
|
1adc2f1b4c | ||
|
c74a772dbc | ||
|
272068d39f | ||
|
9b645bce07 | ||
|
fb41e090b8 | ||
282adbc098 | |||
aabc2b057f | |||
|
60730ac994 | ||
|
34529c76a6 | ||
995f40c9ab | |||
a713fb987e | |||
64faa95b3b | |||
1748bc0461 | |||
96daaf032d | |||
ba95f628a0 | |||
5b13548fa4 | |||
c01063ccfa | |||
656e136b49 | |||
8dfea2032f | |||
e2698179c7 | |||
a6502b6a1f | |||
b5fd777474 | |||
b336904fe5 | |||
|
5c96d23080 | ||
4c10dfbf33 | |||
09072e41e4 | |||
4f8b1020b4 | |||
f9c764fed6 | |||
d99f163500 | |||
5da1359682 | |||
86d0125f34 | |||
2feafd057f | |||
b4f74f993b | |||
90dd6d190e | |||
7131021a86 | |||
16ec9a3788 | |||
c452c1a375 | |||
1c2b373bd7 | |||
dded17bf70 | |||
f48c1ca9cc | |||
4424eb551d | |||
8e5d371a9f | |||
3173c62cc5 | |||
d2a3991abb | |||
43aefc0639 | |||
fef61b1801 | |||
c439a34294 | |||
77aca9d5c1 | |||
e6179f447f | |||
347c6d40a4 | |||
076ee4ffd0 | |||
da8e278c15 | |||
a6477f5831 | |||
be8685725c | |||
44109db61f | |||
0b12aebfe2 | |||
d3963ab5f4 | |||
daa7d29224 | |||
2ebadbe5d4 | |||
981b2e70db | |||
ee40f7b582 | |||
24d778e69d | |||
12a42f8aba | |||
0b35637a01 | |||
4ebb526d87 | |||
b0c145af96 | |||
3cc9709955 | |||
c2329ff33d | |||
435ebaac98 | |||
59bd3ac523 | |||
e388f929f7 | |||
3148bfb746 | |||
ca14105a16 | |||
cbf091d31c | |||
3a793b76db | |||
6d7f85ffc7 | |||
a31c353de6 | |||
59a75c4553 | |||
e892d60564 | |||
b012956afa | |||
ab84dcbfdd | |||
c2bbf14fe0 | |||
7387013788 | |||
cf4fcf420d | |||
|
24fe5320bc | ||
|
3fcd3d3776 | ||
|
eb86f37ea4 | ||
|
04ae633924 | ||
49fa5fa407 | |||
a1020fbad1 | |||
7dc531aebb | |||
3c3652653e | |||
0f7e08b304 | |||
ac42ad2272 | |||
a9d1cd8bde | |||
3c57b73a43 | |||
b3f960ad7a | |||
12fe150596 | |||
8f1faf1d82 | |||
f56d0f7fd8 | |||
06f1c662a9 | |||
1c6e35fcec | |||
01dd44fd72 | |||
1e13da9e22 | |||
f3537f0ed1 | |||
fdbeed3dc6 | |||
e8b6b11db6 | |||
c194f52c7e | |||
68e5e116bb | |||
c0aaf0db0e | |||
6228d5d7d8 | |||
a4356e884d | |||
eb72e4d2c9 | |||
832070b31e | |||
5403e574e9 | |||
dacdcdbf76 | |||
fa59e5d1b7 | |||
34152141d7 | |||
afe29d25a7 | |||
4ef52c1936 | |||
470dcbcc40 | |||
45f941cf04 | |||
8633175bd3 | |||
4bf9d504a3 |
127 changed files with 8468 additions and 6597 deletions
17
.editorconfig
Normal file
17
.editorconfig
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[*]
|
||||||
|
charset=utf-8
|
||||||
|
end_of_line=lf
|
||||||
|
insert_final_newline=false
|
||||||
|
indent_style=space
|
||||||
|
indent_size=4
|
||||||
|
|
||||||
|
[*.java]
|
||||||
|
indent_style=tab
|
||||||
|
tab_width=4
|
||||||
|
|
||||||
|
[{*.yml,*.yaml}]
|
||||||
|
indent_style=space
|
||||||
|
indent_size=2
|
||||||
|
|
||||||
|
[*.xml]
|
||||||
|
indent_style = tab
|
75
.github/workflows/codeql-analysis.yml
vendored
Normal file
75
.github/workflows/codeql-analysis.yml
vendored
Normal 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
|
9
.gitignore
vendored
Normal file → Executable file
9
.gitignore
vendored
Normal file → Executable file
|
@ -132,7 +132,7 @@ publish/
|
||||||
*.publishproj
|
*.publishproj
|
||||||
|
|
||||||
# NuGet Packages Directory
|
# NuGet Packages Directory
|
||||||
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
## TO!DO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||||
#packages/
|
#packages/
|
||||||
|
|
||||||
# Windows Azure Build Output
|
# Windows Azure Build Output
|
||||||
|
@ -220,5 +220,8 @@ pip-log.txt
|
||||||
TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar
|
TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar
|
||||||
*.iml
|
*.iml
|
||||||
*.name
|
*.name
|
||||||
.idea/compiler.xml
|
.idea
|
||||||
*.xml
|
dependency-reduced-pom.xml
|
||||||
|
|
||||||
|
TBMC/
|
||||||
|
/.apt_generated/
|
||||||
|
|
8
.project
Normal file → Executable file
8
.project
Normal file → Executable file
|
@ -1,15 +1,10 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<projectDescription>
|
<projectDescription>
|
||||||
<name>ButtonPluginBucket</name>
|
<name>ButtonCore</name>
|
||||||
<comment></comment>
|
<comment></comment>
|
||||||
<projects>
|
<projects>
|
||||||
</projects>
|
</projects>
|
||||||
<buildSpec>
|
<buildSpec>
|
||||||
<buildCommand>
|
|
||||||
<name>org.eclipse.jdt.core.javabuilder</name>
|
|
||||||
<arguments>
|
|
||||||
</arguments>
|
|
||||||
</buildCommand>
|
|
||||||
<buildCommand>
|
<buildCommand>
|
||||||
<name>org.eclipse.m2e.core.maven2Builder</name>
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
<arguments>
|
<arguments>
|
||||||
|
@ -18,6 +13,5 @@
|
||||||
</buildSpec>
|
</buildSpec>
|
||||||
<natures>
|
<natures>
|
||||||
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
<nature>org.eclipse.jdt.core.javanature</nature>
|
|
||||||
</natures>
|
</natures>
|
||||||
</projectDescription>
|
</projectDescription>
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
eclipse.preferences.version=1
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
|
||||||
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
|
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
|
||||||
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
|
|
||||||
org.eclipse.jdt.core.compiler.debug.localVariable=generate
|
|
||||||
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
|
|
||||||
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
|
|
||||||
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
|
|
||||||
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
|
||||||
org.eclipse.jdt.core.compiler.source=1.8
|
|
4
.settings/org.eclipse.m2e.core.prefs
Executable file
4
.settings/org.eclipse.m2e.core.prefs
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
activeProfiles=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
resolveWorkspaceProjects=true
|
||||||
|
version=1
|
21
.travis.yml
Executable file
21
.travis.yml
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.m2/repository/org/
|
||||||
|
before_install: | # Wget BuildTools and run if cached folder not found
|
||||||
|
if [ ! -d "$HOME/.m2/repository/org/spigotmc/spigot/1.12.2-R0.1-SNAPSHOT" ]; then
|
||||||
|
wget -O BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar
|
||||||
|
# grep so that download counts don't appear in log files
|
||||||
|
java -jar BuildTools.jar --rev 1.12.2 | grep -vE "[^/ ]*/[^/ ]*\s*KB\s*$" | grep -v "^\s*$"
|
||||||
|
fi
|
||||||
|
language: java
|
||||||
|
jdk:
|
||||||
|
- oraclejdk11
|
||||||
|
sudo: true
|
||||||
|
deploy:
|
||||||
|
- provider: releases
|
||||||
|
api_key:
|
||||||
|
secure: "T/ITeRn4JSx4HS4n44P7vy03HWmJGVpYeR6tABu8qOLrwz2MUCFu8xvYqoq4MiqIKqFIKtZHgCSWiB1WsTIJPHm9S2xYs0BFocoQDthE+eCtT2wgCHj/xdo0wBm8uktXr2hxRF5Nd2R/mfSLkoVxAM6otOoSa1SidRGKgDCJxRnsgssk5igQR+tpahZYPvuzM5xJ4G6J07cC+HHM7RLYXkkKrsK3LFugTsnNtctwmxQkm6SqzDcc8YPwoJGhj8PLbXc5S8/KaWT0//LNdypnzeIaRv+aY+ky/zLntXlw3EFOk4erTthAEMSu2x6PgX5B/paMawdwnqKul/L4ynMGojrLH8ha9KM7p/lze2bxCaf08RFVFvTQt6yzmvM1suL4KH6gAF4mKhyaU0kBeOsnTTskoxyGxvbZgdo0Y1V+Yd7w58eu8zfxnauaX7f2DEz7gH4qkSl+hC7HAFoY7m7IXkgVN6Vuv3lGdFO5S15lbGoihOPqt9T79lBxi/efiXEE4BmA1g8MeLsyN8N/EO+LqVr6xXAKJet6YtOAe/iIAqxpNDSuME1szusc6+4q3jOy6cPmB1TKsfnIKQmbT98SHc9wE4/76VLd2PlNPppa0AIG/g+qnjhjpFQxB7LnWdP+D6io9N1FYrvoOX8UVAp4XYhwUMNLUInDT/6P6j8lL7A="
|
||||||
|
file: 'Chroma-Core/target/Chroma-Core.jar'
|
||||||
|
on:
|
||||||
|
tags: true
|
||||||
|
skip_cleanup: true
|
31
ButtonProcessor/.classpath
Executable file
31
ButtonProcessor/.classpath
Executable file
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<classpath>
|
||||||
|
<classpathentry kind="src" output="target/classes" path="src/main/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="optional" value="true"/>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
|
||||||
|
<attributes>
|
||||||
|
<attribute name="maven.pomderived" value="true"/>
|
||||||
|
</attributes>
|
||||||
|
</classpathentry>
|
||||||
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
|
</classpath>
|
1
ButtonProcessor/.gitignore
vendored
Executable file
1
ButtonProcessor/.gitignore
vendored
Executable file
|
@ -0,0 +1 @@
|
||||||
|
/target/
|
23
ButtonProcessor/.project
Executable file
23
ButtonProcessor/.project
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>ButtonProcessor</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.jdt.core.javabuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.eclipse.m2e.core.maven2Builder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.eclipse.jdt.core.javanature</nature>
|
||||||
|
<nature>org.eclipse.m2e.core.maven2Nature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
5
ButtonProcessor/.settings/org.eclipse.core.resources.prefs
Executable file
5
ButtonProcessor/.settings/org.eclipse.core.resources.prefs
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
encoding//src/main/java=UTF-8
|
||||||
|
encoding//src/main/resources=UTF-8
|
||||||
|
encoding//src/test/java=UTF-8
|
||||||
|
encoding/<project>=UTF-8
|
5
ButtonProcessor/.settings/org.eclipse.jdt.core.prefs
Executable file
5
ButtonProcessor/.settings/org.eclipse.jdt.core.prefs
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||||
|
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||||
|
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
|
||||||
|
org.eclipse.jdt.core.compiler.source=1.8
|
4
ButtonProcessor/.settings/org.eclipse.m2e.core.prefs
Executable file
4
ButtonProcessor/.settings/org.eclipse.m2e.core.prefs
Executable file
|
@ -0,0 +1,4 @@
|
||||||
|
activeProfiles=
|
||||||
|
eclipse.preferences.version=1
|
||||||
|
resolveWorkspaceProjects=true
|
||||||
|
version=1
|
674
ButtonProcessor/License.md
Executable file
674
ButtonProcessor/License.md
Executable file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
{one line to give the program's name and a brief idea of what it does.}
|
||||||
|
Copyright (C) {year} {name of author}
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
{project} Copyright (C) {year} {fullname}
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
60
ButtonProcessor/pom.xml
Normal file
60
ButtonProcessor/pom.xml
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<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>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spigot-repo</id>
|
||||||
|
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.yaml</groupId>
|
||||||
|
<artifactId>snakeyaml</artifactId>
|
||||||
|
<version>1.32</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.spigotmc</groupId>
|
||||||
|
<artifactId>spigot-api</artifactId>
|
||||||
|
<version>1.12.2-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<!-- 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>
|
||||||
|
<url>http://maven.apache.org</url>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
<version>3.0.0-M3</version>
|
||||||
|
<configuration>
|
||||||
|
<useSystemClassLoader>false
|
||||||
|
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
</project>
|
85
ButtonProcessor/src/main/java/buttondevteam/buttonproc/ButtonProcessor.java
Executable file
85
ButtonProcessor/src/main/java/buttondevteam/buttonproc/ButtonProcessor.java
Executable file
|
@ -0,0 +1,85 @@
|
||||||
|
package buttondevteam.buttonproc;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
|
import javax.annotation.processing.RoundEnvironment;
|
||||||
|
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||||
|
import javax.lang.model.SourceVersion;
|
||||||
|
import javax.lang.model.element.*;
|
||||||
|
import javax.tools.Diagnostic.Kind;
|
||||||
|
import javax.tools.FileObject;
|
||||||
|
import javax.tools.StandardLocation;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@SupportedAnnotationTypes("buttondevteam.*")
|
||||||
|
public class ButtonProcessor extends AbstractProcessor {
|
||||||
|
@Override
|
||||||
|
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) {
|
||||||
|
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");
|
||||||
|
yc.save(new File(fo.toUri()));
|
||||||
|
found = false;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return true; // claim the annotations
|
||||||
|
}
|
||||||
|
|
||||||
|
private final YamlConfiguration yc = new YamlConfiguration();
|
||||||
|
private boolean found = false;
|
||||||
|
private ConfigProcessor configProcessor;
|
||||||
|
|
||||||
|
private void processSubcommands(Element method, List<? extends AnnotationMirror> annotationMirrors) {
|
||||||
|
if (!(method instanceof ExecutableElement))
|
||||||
|
return;
|
||||||
|
if (annotationMirrors.stream().noneMatch(an -> an.getAnnotationType().toString().endsWith("Subcommand")))
|
||||||
|
return;
|
||||||
|
ConfigurationSection cs = yc.createSection(method.getEnclosingElement().toString()
|
||||||
|
+ "." + method.getSimpleName().toString()); //Need to do the 2 config sections at once so it doesn't overwrite the class section
|
||||||
|
System.out.println("Found subcommand: " + method);
|
||||||
|
cs.set("method", method.toString());
|
||||||
|
cs.set("params", ((ExecutableElement) method).getParameters().stream().skip(1).map(p -> {
|
||||||
|
boolean optional = p.getAnnotationMirrors().stream().anyMatch(am -> am.getAnnotationType().toString().endsWith("OptionalArg"));
|
||||||
|
if (optional)
|
||||||
|
return "[" + p.getSimpleName() + "]";
|
||||||
|
return "<" + p.getSimpleName() + ">";
|
||||||
|
}).collect(Collectors.joining(" ")));
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SourceVersion getSupportedSourceVersion() {
|
||||||
|
return SourceVersion.latestSupported();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
package buttondevteam.buttonproc;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import javax.annotation.processing.ProcessingEnvironment;
|
||||||
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
import javax.lang.model.element.Modifier;
|
||||||
|
import javax.lang.model.type.DeclaredType;
|
||||||
|
import javax.lang.model.type.TypeKind;
|
||||||
|
import javax.lang.model.type.TypeMirror;
|
||||||
|
import javax.tools.FileObject;
|
||||||
|
import javax.tools.StandardLocation;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class ConfigProcessor {
|
||||||
|
private final ProcessingEnvironment procEnv;
|
||||||
|
private final YamlConfiguration yc = new YamlConfiguration();
|
||||||
|
private final FileObject fo;
|
||||||
|
|
||||||
|
public ConfigProcessor(ProcessingEnvironment procEnv) {
|
||||||
|
FileObject fo1;
|
||||||
|
this.procEnv = procEnv;
|
||||||
|
try {
|
||||||
|
fo1 = procEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "configHelp.yml");
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
fo1 = null;
|
||||||
|
}
|
||||||
|
this.fo = fo1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void process(Element targetcl) {
|
||||||
|
if (targetcl.getModifiers().contains(Modifier.ABSTRACT)) return;
|
||||||
|
HasConfig hasConfig = targetcl.getAnnotation(HasConfig.class);
|
||||||
|
if (hasConfig == null) {
|
||||||
|
System.out.println("That's not our HasConfig annotation...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final String path = hasConfig.global() ? "global" : "components." + targetcl.getSimpleName();
|
||||||
|
File file = new File(fo.toUri());
|
||||||
|
try {
|
||||||
|
if (file.exists())
|
||||||
|
yc.load(file);
|
||||||
|
} catch (IOException | InvalidConfigurationException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
for (Element e : targetcl.getEnclosedElements()) {
|
||||||
|
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().toString().contains("ConfigData"))
|
||||||
|
continue; //Ahhha! There was a return here! (MinecraftChatModule getListener())
|
||||||
|
|
||||||
|
String doc = procEnv.getElementUtils().getDocComment(e);
|
||||||
|
if (doc == null) continue;
|
||||||
|
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("Adding docs for class: " + targetcl.getSimpleName());
|
||||||
|
yc.set(path + ".generalDescriptionInsteadOfAConfig", javadoc.trim());
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
yc.save(file);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package buttondevteam.buttonproc;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to generate documentation for the config
|
||||||
|
*/
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
public @interface HasConfig {
|
||||||
|
boolean global();
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
buttondevteam.buttonproc.ButtonProcessor
|
38
ButtonProcessor/src/test/java/buttondevteam/ButtonProcessor/AppTest.java
Executable file
38
ButtonProcessor/src/test/java/buttondevteam/ButtonProcessor/AppTest.java
Executable file
|
@ -0,0 +1,38 @@
|
||||||
|
package buttondevteam.ButtonProcessor;
|
||||||
|
|
||||||
|
import junit.framework.Test;
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
import junit.framework.TestSuite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit test for simple App.
|
||||||
|
*/
|
||||||
|
public class AppTest
|
||||||
|
extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Create the test case
|
||||||
|
*
|
||||||
|
* @param testName name of the test case
|
||||||
|
*/
|
||||||
|
public AppTest( String testName )
|
||||||
|
{
|
||||||
|
super( testName );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the suite of tests being tested
|
||||||
|
*/
|
||||||
|
public static Test suite()
|
||||||
|
{
|
||||||
|
return new TestSuite( AppTest.class );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rigourous Test :-)
|
||||||
|
*/
|
||||||
|
public void testApp()
|
||||||
|
{
|
||||||
|
assertTrue( true );
|
||||||
|
}
|
||||||
|
}
|
674
Chroma-Core/License.md
Executable file
674
Chroma-Core/License.md
Executable file
|
@ -0,0 +1,674 @@
|
||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
{one line to give the program's name and a brief idea of what it does.}
|
||||||
|
Copyright (C) {year} {name of author}
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
{project} Copyright (C) {year} {fullname}
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
1
Chroma-Core/lombok.config
Normal file
1
Chroma-Core/lombok.config
Normal file
|
@ -0,0 +1 @@
|
||||||
|
lombok.var.flagUsage = ALLOW
|
226
Chroma-Core/pom.xml
Executable file
226
Chroma-Core/pom.xml
Executable file
|
@ -0,0 +1,226 @@
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>*.properties</include>
|
||||||
|
<include>*.yml</include>
|
||||||
|
<include>*.csv</include>
|
||||||
|
<include>*.txt</include>
|
||||||
|
</includes>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<finalName>Chroma-Core</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-shade-plugin</artifactId>
|
||||||
|
<version>3.2.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>shade</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<artifactSet>
|
||||||
|
<includes>
|
||||||
|
<include>me.lucko:commodore</include>
|
||||||
|
<include>org.javatuples:javatuples</include>
|
||||||
|
</includes>
|
||||||
|
</artifactSet>
|
||||||
|
<relocations>
|
||||||
|
<relocation>
|
||||||
|
<pattern>me.lucko.commodore</pattern>
|
||||||
|
<!-- vvv Replace with the package of your plugin vvv -->
|
||||||
|
<shadedPattern>buttondevteam.core.commodore</shadedPattern>
|
||||||
|
</relocation>
|
||||||
|
</relocations>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<version>3.0.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>target</outputDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<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>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
|
<version>3.0.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>attach-sources</id>
|
||||||
|
<goals>
|
||||||
|
<goal>jar</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>spigot-repo</id>
|
||||||
|
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>jitpack.io</id>
|
||||||
|
<url>https://jitpack.io/</url>
|
||||||
|
</repository>
|
||||||
|
<!-- <repository>
|
||||||
|
<id>vault-repo</id>
|
||||||
|
<url>http://nexus.hc.to/content/repositories/pub_releases</url>
|
||||||
|
</repository> -->
|
||||||
|
<repository>
|
||||||
|
<id>ess-repo</id>
|
||||||
|
<url>https://ci.ender.zone/plugin/repository/everything/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>Multiverse-Core</id>
|
||||||
|
<url>https://repo.onarandombox.com/content/groups/public/</url>
|
||||||
|
</repository>
|
||||||
|
<repository>
|
||||||
|
<id>minecraft-repo</id>
|
||||||
|
<url>https://libraries.minecraft.net/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.reflections</groupId>
|
||||||
|
<artifactId>reflections</artifactId>
|
||||||
|
<version>0.10.2</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.spigotmc</groupId>
|
||||||
|
<artifactId>spigot-api</artifactId>
|
||||||
|
<version>1.12.2-R0.1-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.TownyAdvanced</groupId>
|
||||||
|
<artifactId>Towny</artifactId>
|
||||||
|
<version>0.94.0.9</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.milkbowl</groupId> <!-- net.milkbowl.vault -->
|
||||||
|
<artifactId>VaultAPI</artifactId>
|
||||||
|
<version>master-SNAPSHOT</version> <!-- 1.6 -->
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.javassist</groupId>
|
||||||
|
<artifactId>javassist</artifactId>
|
||||||
|
<version>3.28.0-GA</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito</groupId>
|
||||||
|
<artifactId>mockito-core</artifactId>
|
||||||
|
<version>4.2.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
|
||||||
|
<artifactId>ButtonProcessor</artifactId>
|
||||||
|
<version>master-SNAPSHOT</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.ess3</groupId>
|
||||||
|
<artifactId>EssentialsX</artifactId>
|
||||||
|
<version>2.17.1</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/com.github.NuVotifier.NuVotifier/nuvotifier-bukkit -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.NuVotifier.NuVotifier</groupId>
|
||||||
|
<artifactId>nuvotifier-bukkit</artifactId>
|
||||||
|
<version>v2.7.1</version>
|
||||||
|
<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>
|
||||||
|
<url>https://github.com/TBMCPlugins</url>
|
||||||
|
</organization>
|
||||||
|
<distributionManagement>
|
||||||
|
<repository>
|
||||||
|
<id>internal.repo</id>
|
||||||
|
<name>Temporary Staging Repository</name>
|
||||||
|
<url>file://${project.build.directory}/mvn-repo/${project.name}</url>
|
||||||
|
</repository>
|
||||||
|
</distributionManagement>
|
||||||
|
<properties>
|
||||||
|
<!-- 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>
|
||||||
|
<connection>scm:git:https://github.com/TBMCPlugins/mvn-repo.git</connection>
|
||||||
|
<developerConnection>scm:git:https://github.com/TBMCPlugins/mvn-repo.git</developerConnection>
|
||||||
|
</scm>
|
||||||
|
</project>
|
|
@ -0,0 +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 {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
package buttondevteam.core;
|
||||||
|
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||||
|
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",
|
||||||
|
"Can be used to enable/disable/list components"
|
||||||
|
})
|
||||||
|
public class ComponentCommand extends ICommand2MC {
|
||||||
|
public ComponentCommand() {
|
||||||
|
getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!",
|
||||||
|
() -> Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(Plugin::getName)::iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subcommand(helpText = {
|
||||||
|
"Enable component",
|
||||||
|
"Temporarily or permanently enables a 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.");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable
|
||||||
|
return enable_disable(sender, plugin, component, true, permanent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subcommand(helpText = {
|
||||||
|
"Disable component",
|
||||||
|
"Temporarily or permanently disables a component."
|
||||||
|
})
|
||||||
|
public boolean disable(CommandSender sender, Plugin plugin, String component, @Command2.OptionalArg boolean permanent) {
|
||||||
|
return enable_disable(sender, plugin, component, false, permanent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subcommand(helpText = {
|
||||||
|
"List components",
|
||||||
|
"Lists all of the registered Chroma components"
|
||||||
|
})
|
||||||
|
public boolean list(CommandSender sender, @Command2.OptionalArg String plugin) {
|
||||||
|
sender.sendMessage("§6List of components:");
|
||||||
|
Component.getComponents().values().stream().filter(c -> plugin == null || c.getPlugin().getName().equalsIgnoreCase(plugin)) //If plugin is null, don't check
|
||||||
|
.map(c -> c.getPlugin().getName() + " - " + c.getClass().getSimpleName() + " - " + (c.isEnabled() ? "en" : "dis") + "abled").forEach(sender::sendMessage);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CustomTabCompleteMethod(param = "component", subcommand = {"enable", "disable"})
|
||||||
|
public Iterable<String> componentTabcomplete(Plugin plugin) {
|
||||||
|
return getPluginComponents(plugin).map(c -> c.getClass().getSimpleName())::iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CustomTabCompleteMethod(param = "plugin", subcommand = {"list", "enable", "disable"}, ignoreTypeCompletion = true)
|
||||||
|
public Iterable<String> pluginTabcomplete() {
|
||||||
|
return Component.getComponents().values().stream().map(Component::getPlugin)
|
||||||
|
.distinct().map(Plugin::getName)::iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean enable_disable(CommandSender sender, Plugin plugin, String component, boolean enable, boolean permanent) {
|
||||||
|
try {
|
||||||
|
val oc = getComponentOrError(plugin, component, sender);
|
||||||
|
if (!oc.isPresent())
|
||||||
|
return true;
|
||||||
|
Component.setComponentEnabled(oc.get(), enable);
|
||||||
|
if (permanent)
|
||||||
|
oc.get().shouldBeEnabled.set(enable);
|
||||||
|
sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled " + (permanent ? "permanently" : "temporarily") + ".");
|
||||||
|
} catch (Exception 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 = 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package buttondevteam.core;
|
||||||
|
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import lombok.val;
|
||||||
|
|
||||||
|
public final class ComponentManager {
|
||||||
|
private ComponentManager() {}
|
||||||
|
|
||||||
|
private static boolean componentsEnabled = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This flag is used to enable components registered after the others were enabled.
|
||||||
|
* @return Whether already registered components have been enabled
|
||||||
|
*/
|
||||||
|
public static boolean areComponentsEnabled() { return componentsEnabled; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables components based on a configuration - any component registered afterwards will be also enabled
|
||||||
|
*/
|
||||||
|
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 -> {
|
||||||
|
try {
|
||||||
|
Component.setComponentEnabled(c, true);
|
||||||
|
} catch (Exception | NoClassDefFoundError e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to enable one of the components: " + c.getClass().getSimpleName(), e, c);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
componentsEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregister all components of a plugin that are enabled - called on {@link ButtonPlugin} disable
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T extends ButtonPlugin> void unregComponents(T plugin) {
|
||||||
|
while (!plugin.getComponentStack().empty()) //Unregister in reverse order
|
||||||
|
Component.unregisterComponent(plugin, (Component<T>) plugin.getComponentStack().pop()); //Components are pushed on register
|
||||||
|
//componentsEnabled = false; - continue enabling new components after a plugin gets disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will also return false if the component is not registered.
|
||||||
|
*
|
||||||
|
* @param cl The component class
|
||||||
|
* @return Whether the component is registered and enabled
|
||||||
|
*/
|
||||||
|
public static boolean isEnabled(Class<? extends Component> cl) {
|
||||||
|
val c = Component.getComponents().get(cl);
|
||||||
|
return c != null && c.isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Will also return null if the component is not registered.
|
||||||
|
*
|
||||||
|
* @param cl The component class
|
||||||
|
* @return The component if it's registered and enabled
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T extends Component> T getIfEnabled(Class<T> cl) {
|
||||||
|
val c = Component.getComponents().get(cl);
|
||||||
|
return c != null && c.isEnabled() ? (T) c : null;
|
||||||
|
}
|
||||||
|
}
|
173
Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java
Executable file
173
Chroma-Core/src/main/java/buttondevteam/core/MainPlugin.java
Executable file
|
@ -0,0 +1,173 @@
|
||||||
|
package buttondevteam.core;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import buttondevteam.core.component.channel.ChannelComponent;
|
||||||
|
import buttondevteam.core.component.channel.ChatRoom;
|
||||||
|
import buttondevteam.core.component.members.MemberComponent;
|
||||||
|
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.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import buttondevteam.lib.architecture.ConfigData;
|
||||||
|
import buttondevteam.lib.chat.Color;
|
||||||
|
import buttondevteam.lib.chat.TBMCChatAPI;
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
|
import buttondevteam.lib.player.TBMCPlayer;
|
||||||
|
import buttondevteam.lib.player.TBMCPlayerBase;
|
||||||
|
import com.earth2me.essentials.Essentials;
|
||||||
|
import lombok.Getter;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
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 {
|
||||||
|
public static MainPlugin Instance;
|
||||||
|
public static Permission permission;
|
||||||
|
@Nullable
|
||||||
|
public static Essentials ess;
|
||||||
|
|
||||||
|
private Logger logger;
|
||||||
|
@Nullable
|
||||||
|
private Economy economy;
|
||||||
|
/**
|
||||||
|
* Whether the Core's chat handler should be enabled.
|
||||||
|
* Other chat plugins handling messages from other platforms should set this to false.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private boolean chatHandlerEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether the plugin should write a list of installed plugins in a txt file.
|
||||||
|
* It can be useful if some other software needs to know the plugins.
|
||||||
|
*/
|
||||||
|
private final ConfigData<Boolean> writePluginList = getIConfig().getData("writePluginList", false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The chat format to use for messages from other platforms if Chroma-Chat is not installed.
|
||||||
|
*/
|
||||||
|
ConfigData<String> chatFormat = getIConfig().getData("chatFormat", "[{origin}|" +
|
||||||
|
"{channel}] <{name}> {message}");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print some debug information.
|
||||||
|
*/
|
||||||
|
public final ConfigData<Boolean> test = getIConfig().getData("test", false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If a Chroma command clashes with another plugin's command, this setting determines whether the Chroma command should be executed or the other plugin's.
|
||||||
|
*/
|
||||||
|
public final ConfigData<Boolean> prioritizeCustomCommands = getIConfig().getData("prioritizeCustomCommands", false);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pluginEnable() {
|
||||||
|
Instance = this;
|
||||||
|
PluginDescriptionFile pdf = getDescription();
|
||||||
|
logger = getLogger();
|
||||||
|
if (!setupPermissions())
|
||||||
|
throw new NullPointerException("No permission plugin found!");
|
||||||
|
if (!setupEconomy()) //Though Essentials always provides economy, but we don't require Essentials
|
||||||
|
getLogger().warning("No economy plugin found! Components using economy will not be registered.");
|
||||||
|
saveConfig();
|
||||||
|
Component.registerComponent(this, new RestartComponent());
|
||||||
|
Component.registerComponent(this, new ChannelComponent());
|
||||||
|
Component.registerComponent(this, new RandomTPComponent());
|
||||||
|
Component.registerComponent(this, new MemberComponent());
|
||||||
|
if (Bukkit.getPluginManager().isPluginEnabled("Multiverse-Core"))
|
||||||
|
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));*/
|
||||||
|
ComponentManager.enableComponents();
|
||||||
|
registerCommand(new ComponentCommand());
|
||||||
|
registerCommand(new ChromaCommand());
|
||||||
|
TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this);
|
||||||
|
TBMCCoreAPI.RegisterEventsForExceptions(getCommand2MC(), this);
|
||||||
|
ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender
|
||||||
|
? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks
|
||||||
|
ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player
|
||||||
|
? TBMCPlayer.getPlayer(((Player) sender).getUniqueId(), TBMCPlayer.class) : null)); //Players, has higher priority
|
||||||
|
TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class, TBMCPlayer::new);
|
||||||
|
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)));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(
|
||||||
|
Channel.ModChat = new Channel("§9MOD§f", Color.Blue, "mod", Channel.inGroupFilter("mod")));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(new Channel("§6DEV§f", Color.Gold, "dev", Channel.inGroupFilter("developer")));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§cRED§f", Color.DarkRed, "red"));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§6ORANGE§f", Color.Gold, "orange"));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§eYELLOW§f", Color.Yellow, "yellow"));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§aGREEN§f", Color.Green, "green"));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§bBLUE§f", Color.Blue, "blue"));
|
||||||
|
TBMCChatAPI.RegisterChatChannel(new ChatRoom("§5PURPLE§f", Color.DarkPurple, "purple"));
|
||||||
|
Supplier<Iterable<String>> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator;
|
||||||
|
getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier);
|
||||||
|
getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier);
|
||||||
|
if (writePluginList.get()) {
|
||||||
|
try {
|
||||||
|
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator);
|
||||||
|
} catch (IOException e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to write plugin list!", e, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getServer().getPluginManager().isPluginEnabled("Essentials"))
|
||||||
|
ess = Essentials.getPlugin(Essentials.class);
|
||||||
|
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test.get() + ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void pluginDisable() {
|
||||||
|
logger.info("Saving player data...");
|
||||||
|
ChromaGamerBase.saveUsers();
|
||||||
|
logger.info("Player data saved.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setupPermissions() {
|
||||||
|
permission = setupProvider(Permission.class);
|
||||||
|
return (permission != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setupEconomy() {
|
||||||
|
economy = setupProvider(Economy.class);
|
||||||
|
return (economy != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> T setupProvider(Class<T> cl) {
|
||||||
|
RegisteredServiceProvider<T> provider = getServer().getServicesManager()
|
||||||
|
.getRegistration(cl);
|
||||||
|
if (provider != null)
|
||||||
|
return provider.getProvider();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
if (command.getName().equals("dontrunthiscmd")) return true; //Used in chat preprocess for console
|
||||||
|
sender.sendMessage("§cThis command isn't available."); //In theory, unregistered commands use this method
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
116
Chroma-Core/src/main/java/buttondevteam/core/PlayerListener.java
Executable file
116
Chroma-Core/src/main/java/buttondevteam/core/PlayerListener.java
Executable file
|
@ -0,0 +1,116 @@
|
||||||
|
package buttondevteam.core;
|
||||||
|
|
||||||
|
import buttondevteam.lib.*;
|
||||||
|
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;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.EventPriority;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.AsyncPlayerChatEvent;
|
||||||
|
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
import org.bukkit.event.server.ServerCommandEvent;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
public class PlayerListener implements Listener {
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL)
|
||||||
|
public void OnPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
var p = event.getPlayer();
|
||||||
|
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class);
|
||||||
|
String pname = player.PlayerName.get();
|
||||||
|
if (pname.length() == 0) {
|
||||||
|
player.PlayerName.set(p.getName());
|
||||||
|
MainPlugin.Instance.getLogger().info("Player name saved: " + player.PlayerName.get());
|
||||||
|
} else if (!p.getName().equals(pname)) {
|
||||||
|
MainPlugin.Instance.getLogger().info(pname + " renamed to " + p.getName());
|
||||||
|
player.PlayerName.set(p.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.NORMAL)
|
||||||
|
public void OnPlayerLeave(PlayerQuitEvent event) {
|
||||||
|
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 (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()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerChatPreprocess(PlayerCommandPreprocessEvent event) {
|
||||||
|
handlePreprocess(event.getPlayer(), event.getMessage(), event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onSystemChatPreprocess(ServerCommandEvent event) {
|
||||||
|
handlePreprocess(event.getSender(), "/" + event.getCommand(), event);
|
||||||
|
if (event.isCancelled()) event.setCommand("dontrunthiscmd"); //Bugfix
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePreprocess(CommandSender sender, String message, Cancellable event) {
|
||||||
|
if (event.isCancelled()) return;
|
||||||
|
val cg = ChromaGamerBase.getFromSender(sender);
|
||||||
|
if (cg == null) throw new RuntimeException("Couldn't get user from sender for " + sender.getName() + "!");
|
||||||
|
val ev = new TBMCCommandPreprocessEvent(sender, cg.channel.get(), message, sender);
|
||||||
|
Bukkit.getPluginManager().callEvent(ev);
|
||||||
|
if (ev.isCancelled())
|
||||||
|
event.setCancelled(true); //Cancel the original event
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) {
|
||||||
|
if (event.isCancelled()) return;
|
||||||
|
try {
|
||||||
|
event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender(), event.getChannel(), event.getPermCheck()), event.getMessage()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e, MainPlugin.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest
|
||||||
|
public void onPlayerChat(AsyncPlayerChatEvent event) {
|
||||||
|
if (event.isCancelled())
|
||||||
|
return; //The chat plugin should cancel it after this handler
|
||||||
|
val cp = TBMCPlayer.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class);
|
||||||
|
TBMCChatAPI.SendChatMessage(ChatMessage.builder(event.getPlayer(), cp, event.getMessage()).build());
|
||||||
|
//Not cancelling the original event here, it's cancelled in the chat plugin
|
||||||
|
//This way other plugins can deal with the MC formatting if the chat plugin isn't present, but other platforms still get the message
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest
|
||||||
|
public void onPlayerChat(TBMCChatEvent event) {
|
||||||
|
if (event.isCancelled())
|
||||||
|
return;
|
||||||
|
if (!MainPlugin.Instance.isChatHandlerEnabled()) return;
|
||||||
|
if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages
|
||||||
|
var channel = event.getChannel();
|
||||||
|
String msg = MainPlugin.Instance.chatFormat.get()
|
||||||
|
.replace("{channel}", channel.DisplayName.get())
|
||||||
|
.replace("{origin}", event.getOrigin().substring(0, 1))
|
||||||
|
.replace("{name}", ChromaUtils.getDisplayName(event.getSender()))
|
||||||
|
.replace("{message}", String.format("§%x%s", channel.Color.get().ordinal(), event.getMessage()));
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers())
|
||||||
|
if (event.shouldSendTo(player))
|
||||||
|
player.sendMessage(msg);
|
||||||
|
Bukkit.getConsoleSender().sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
50
Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java
Executable file
50
Chroma-Core/src/main/java/buttondevteam/core/TestPrepare.java
Executable file
|
@ -0,0 +1,50 @@
|
||||||
|
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;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Server;
|
||||||
|
import org.bukkit.plugin.PluginManager;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
import org.bukkit.scheduler.BukkitScheduler;
|
||||||
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
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) {
|
||||||
|
if (returns(invocation, String.class))
|
||||||
|
return "test";
|
||||||
|
if (returns(invocation, Logger.class))
|
||||||
|
return Logger.getAnonymousLogger();
|
||||||
|
if (returns(invocation, PluginManager.class))
|
||||||
|
return Mockito.mock(PluginManager.class);
|
||||||
|
if (returns(invocation, Collection.class))
|
||||||
|
return Collections.EMPTY_LIST;
|
||||||
|
if (returns(invocation, BukkitScheduler.class))
|
||||||
|
return Mockito.mock(BukkitScheduler.class);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean returns(InvocationOnMock invocation, Class<?> cl) {
|
||||||
|
return cl.isAssignableFrom(invocation.getMethod().getReturnType());
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent());
|
||||||
|
TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null));
|
||||||
|
}
|
||||||
|
}
|
237
Chroma-Core/src/main/java/buttondevteam/core/component/channel/Channel.java
Executable file
237
Chroma-Core/src/main/java/buttondevteam/core/component/channel/Channel.java
Executable file
|
@ -0,0 +1,237 @@
|
||||||
|
package buttondevteam.core.component.channel;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
import java.util.function.BiPredicate;
|
||||||
|
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";
|
||||||
|
|
||||||
|
private static ChannelComponent component;
|
||||||
|
|
||||||
|
private String defDisplayName;
|
||||||
|
private Color defColor;
|
||||||
|
|
||||||
|
private IHaveConfig config;
|
||||||
|
|
||||||
|
public final ConfigData<Boolean> Enabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Must start with a color code
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
init();
|
||||||
|
Enabled = component.getConfig().getData(ID + ".enabled", true);
|
||||||
|
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
|
||||||
|
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
|
||||||
|
//noinspection unchecked
|
||||||
|
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void init() {
|
||||||
|
if (component == null)
|
||||||
|
component = (ChannelComponent) Component.getComponents().get(ChannelComponent.class);
|
||||||
|
if (component == null)
|
||||||
|
throw new RuntimeException("Attempting to create a channel before the component is registered!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGlobal() {
|
||||||
|
return filteranderrormsg == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a stream of the enabled channels
|
||||||
|
*
|
||||||
|
* @return Only the enabled channels
|
||||||
|
*/
|
||||||
|
public static Stream<Channel> getChannels() {
|
||||||
|
return channels.stream().filter(ch -> ch.Enabled.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all channels whether they're enabled or not
|
||||||
|
*
|
||||||
|
* @return A list of all channels
|
||||||
|
*/
|
||||||
|
public static List<Channel> getChannelList() {
|
||||||
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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)
|
||||||
|
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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
package buttondevteam.core.component.channel;
|
||||||
|
|
||||||
|
import buttondevteam.lib.ChromaUtils;
|
||||||
|
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import buttondevteam.lib.chat.*;
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages chat channels. If disabled, only global channels will be registered.
|
||||||
|
*/
|
||||||
|
public class ChannelComponent extends Component<JavaPlugin> {
|
||||||
|
static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void register(JavaPlugin plugin) {
|
||||||
|
super.register(plugin);
|
||||||
|
roomJoinLeave = TBMCSystemChatEvent.BroadcastTarget.add("roomJoinLeave"); //Even if it's disabled, global channels continue to work
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void unregister(JavaPlugin plugin) {
|
||||||
|
super.unregister(plugin);
|
||||||
|
TBMCSystemChatEvent.BroadcastTarget.remove(roomJoinLeave);
|
||||||
|
roomJoinLeave = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void enable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
package buttondevteam.bucket.core;
|
package buttondevteam.core.component.channel;
|
||||||
|
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
import org.bukkit.event.HandlerList;
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
public class TBMCPlayerQuitEvent extends Event {
|
public class ChatChannelRegisterEvent extends Event {
|
||||||
private static final HandlerList handlers = new HandlerList();
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
private TBMCPlayer player;
|
private final Channel channel;
|
||||||
|
|
||||||
public TBMCPlayerQuitEvent(TBMCPlayer player) {
|
public ChatChannelRegisterEvent(Channel channel) {
|
||||||
this.player = player;
|
this.channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TBMCPlayer GetPlayer() {
|
public Channel getChannel() {
|
||||||
return player;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
28
Chroma-Core/src/main/java/buttondevteam/core/component/channel/ChatRoom.java
Executable file
28
Chroma-Core/src/main/java/buttondevteam/core/component/channel/ChatRoom.java
Executable file
|
@ -0,0 +1,28 @@
|
||||||
|
package buttondevteam.core.component.channel;
|
||||||
|
|
||||||
|
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||||
|
import buttondevteam.lib.chat.Color;
|
||||||
|
import buttondevteam.lib.chat.TBMCChatAPI;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ChatRoom extends Channel {
|
||||||
|
private final List<CommandSender> usersInRoom = new ArrayList<>();
|
||||||
|
|
||||||
|
public ChatRoom(String displayname, Color color, String command) {
|
||||||
|
<ChatRoom>super(displayname, color, command, noScoreResult((this_, s) -> this_.usersInRoom.contains(s),
|
||||||
|
"Not implemented yet. Please report it to the devs along with which platform you're trying to talk from."));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void joinRoom(CommandSender sender) {
|
||||||
|
usersInRoom.add(sender);
|
||||||
|
TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " joined the room", TBMCSystemChatEvent.BroadcastTarget.ALL); //Always show message in the same kind of channel
|
||||||
|
}
|
||||||
|
|
||||||
|
public void leaveRoom(CommandSender sender) {
|
||||||
|
usersInRoom.remove(sender);
|
||||||
|
TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " left the room", ChannelComponent.roomJoinLeave);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package buttondevteam.core.component.members;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.chat.Command2;
|
||||||
|
import buttondevteam.lib.chat.CommandClass;
|
||||||
|
import buttondevteam.lib.chat.ICommand2MC;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@CommandClass(path = "member", helpText = { //
|
||||||
|
"Member command", //
|
||||||
|
"Add or remove server members.", //
|
||||||
|
})
|
||||||
|
public class MemberCommand extends ICommand2MC {
|
||||||
|
private final MemberComponent component;
|
||||||
|
|
||||||
|
public MemberCommand(MemberComponent component) {
|
||||||
|
this.component = component;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP)
|
||||||
|
public boolean add(CommandSender sender, OfflinePlayer player) {
|
||||||
|
return addRemove(sender, player, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand(permGroup = Command2.Subcommand.MOD_GROUP)
|
||||||
|
public boolean remove(CommandSender sender, OfflinePlayer player) {
|
||||||
|
return addRemove(sender, player, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addRemove(CommandSender sender, OfflinePlayer op, boolean add) {
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
|
||||||
|
if (!op.hasPlayedBefore()) {
|
||||||
|
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()))
|
||||||
|
sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!");
|
||||||
|
else
|
||||||
|
sender.sendMessage("§cFailed to " + (add ? "add" : "remove") + " " + op.getName() + " as a member!");
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Command2.Subcommand
|
||||||
|
public void def(Player player) {
|
||||||
|
String msg;
|
||||||
|
if (!component.checkNotMember(player))
|
||||||
|
msg = "You are a member.";
|
||||||
|
else {
|
||||||
|
double pt = component.getPlayTime(player);
|
||||||
|
long rt = component.getRegTime(player);
|
||||||
|
if (pt == -1 || rt == -1) {
|
||||||
|
Boolean result = component.addPlayerAsMember(player);
|
||||||
|
if (result == null)
|
||||||
|
msg = "Can't assign member group because groups are not supported by the permissions plugin.";
|
||||||
|
else if (result)
|
||||||
|
msg = "You meet all the requirements.";
|
||||||
|
else
|
||||||
|
msg = "You should be a member but failed to add you to the group.";
|
||||||
|
} else
|
||||||
|
msg = String.format("You need to play for %.2f hours total or play for %d more days to become a member.",
|
||||||
|
pt, TimeUnit.MILLISECONDS.toDays(rt));
|
||||||
|
}
|
||||||
|
player.sendMessage(msg);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package buttondevteam.core.component.members;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import buttondevteam.lib.architecture.ComponentMetadata;
|
||||||
|
import buttondevteam.lib.architecture.ConfigData;
|
||||||
|
import org.bukkit.Statistic;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.AbstractMap;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import static buttondevteam.core.MainPlugin.permission;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows giving a 'member' group over some time elapsed OR played.
|
||||||
|
*/
|
||||||
|
@ComponentMetadata(enabledByDefault = false)
|
||||||
|
public class MemberComponent extends Component<MainPlugin> implements Listener {
|
||||||
|
/**
|
||||||
|
* The permission group to give to the player
|
||||||
|
*/
|
||||||
|
final ConfigData<String> memberGroup = getConfig().getData("memberGroup", "member");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of hours needed to play before promotion
|
||||||
|
*/
|
||||||
|
private final ConfigData<Integer> playedHours = getConfig().getData("playedHours", 12);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The amount of days passed since first login
|
||||||
|
*/
|
||||||
|
private final ConfigData<Integer> registeredForDays = getConfig().getData("registeredForDays", 7);
|
||||||
|
|
||||||
|
private AbstractMap.SimpleEntry<Statistic, Integer> playtime;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void enable() {
|
||||||
|
registerListener(this);
|
||||||
|
registerCommand(new MemberCommand(this));
|
||||||
|
try {
|
||||||
|
playtime = new AbstractMap.SimpleEntry<>(Statistic.valueOf("PLAY_ONE_MINUTE"), 60); //1.14
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
playtime = new AbstractMap.SimpleEntry<>(Statistic.valueOf("PLAY_ONE_TICK"), 20 * 3600); //1.12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void disable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||||
|
if (checkNotMember(event.getPlayer()) && (checkRegTime(event.getPlayer()) || checkPlayTime(event.getPlayer()))) {
|
||||||
|
addPlayerAsMember(event.getPlayer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean addPlayerAsMember(Player player) {
|
||||||
|
try {
|
||||||
|
if (permission.playerAddGroup(null, player, memberGroup.get())) {
|
||||||
|
player.sendMessage("§bYou are a member now!");
|
||||||
|
log("Added " + player.getName() + " as a member.");
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
logWarn("Failed to assign the member role! Please make sure the member group exists or disable the component if it's unused.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkRegTime(Player player) {
|
||||||
|
return getRegTime(player) == -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkPlayTime(Player player) {
|
||||||
|
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);
|
||||||
|
if (date.isAfter(Instant.now()))
|
||||||
|
return date.toEpochMilli() - Instant.now().toEpochMilli();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getPlayTimeTotal(Player player) {
|
||||||
|
return player.getStatistic(playtime.getKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns hours
|
||||||
|
*/
|
||||||
|
public double getPlayTime(Player player) {
|
||||||
|
double pt = playedHours.get() - (double) getPlayTimeTotal(player) / playtime.getValue();
|
||||||
|
if (pt < 0) return -1;
|
||||||
|
return pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,244 @@
|
||||||
|
package buttondevteam.core.component.randomtp;
|
||||||
|
|
||||||
|
import buttondevteam.lib.chat.Command2;
|
||||||
|
import buttondevteam.lib.chat.CommandClass;
|
||||||
|
import buttondevteam.lib.chat.ICommand2MC;
|
||||||
|
import org.bukkit.*;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
@SuppressWarnings("FieldCanBeLocal")@CommandClass(helpText = {
|
||||||
|
"§6---- Random Teleport ----",
|
||||||
|
"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."
|
||||||
|
})
|
||||||
|
public class RandomTP extends ICommand2MC
|
||||||
|
{
|
||||||
|
private final int radius = 70; //set how far apart the five teleport positions are
|
||||||
|
|
||||||
|
private World world;
|
||||||
|
private WorldBorder border;
|
||||||
|
private double size,
|
||||||
|
usableSize,
|
||||||
|
borderCenterX,
|
||||||
|
borderCenterZ,
|
||||||
|
|
||||||
|
x,z;
|
||||||
|
|
||||||
|
private int centerX, centerZ, centerY,
|
||||||
|
northZ, southZ, eastX, westX,
|
||||||
|
northY, southY, eastY, westY;
|
||||||
|
|
||||||
|
private Material centerGroundMaterial, centerFeetMaterial, centerHeadMaterial,
|
||||||
|
northGroundMaterial, northFeetMaterial, northHeadMaterial,
|
||||||
|
southGroundMaterial, southFeetMaterial, southHeadMaterial,
|
||||||
|
eastGroundMaterial, eastFeetMaterial, eastHeadMaterial,
|
||||||
|
westGroundMaterial, westFeetMaterial, westHeadMaterial;
|
||||||
|
|
||||||
|
private Location center,
|
||||||
|
north,
|
||||||
|
south,
|
||||||
|
east,
|
||||||
|
west;
|
||||||
|
|
||||||
|
private boolean centerUsed,
|
||||||
|
northUsed,
|
||||||
|
southUsed,
|
||||||
|
eastUsed,
|
||||||
|
westUsed;
|
||||||
|
|
||||||
|
private StringBuilder availableDirections = new StringBuilder(5);
|
||||||
|
private char[] chars = {1,2,3,4,5};
|
||||||
|
private int dir;
|
||||||
|
|
||||||
|
/*================================================================================================*/
|
||||||
|
|
||||||
|
public void onEnable(RandomTPComponent component)
|
||||||
|
{
|
||||||
|
world = Bukkit.getWorld("World");
|
||||||
|
border = world.getWorldBorder();
|
||||||
|
component.log("Getting new location");
|
||||||
|
if(border.getSize() > 100000)
|
||||||
|
component.logWarn("World border is wide, it may take a minute...");
|
||||||
|
component.log("Success: "+newLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*================================================================================================*/
|
||||||
|
|
||||||
|
@Command2.Subcommand
|
||||||
|
public boolean def(CommandSender sender, Player player)
|
||||||
|
{
|
||||||
|
if (sender.isOp()) return rtp(player);
|
||||||
|
|
||||||
|
else sender.sendMessage("§7 hmm, " + sender.getName() + "... " + sender.getName() + "... nope, no operator permissions.");
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*================================================================================================*/
|
||||||
|
|
||||||
|
public synchronized boolean rtp(Player player)
|
||||||
|
{
|
||||||
|
if (player == null)
|
||||||
|
return false; //Pretend it rtp'd an imaginary player successfully
|
||||||
|
|
||||||
|
//if border has changed, or no positions available, find new location
|
||||||
|
if ((centerUsed && northUsed && southUsed && eastUsed && westUsed) ||
|
||||||
|
|
||||||
|
(borderCenterX != border.getCenter().getX() ||
|
||||||
|
borderCenterZ != border.getCenter().getZ() ||
|
||||||
|
size != border.getSize())
|
||||||
|
|
||||||
|
&& !newLocation())
|
||||||
|
{
|
||||||
|
//if unable to find new location, message player and return false
|
||||||
|
player.sendMessage("§c could not find a location in 10,000 attempts");
|
||||||
|
player.sendMessage("§c (sorry bud... I did try!)");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//randomly select one of the open positions and teleport the player there, then remove the position
|
||||||
|
switch(availableDirections.charAt(dir = (int) Math.floor(Math.random() * availableDirections.length())))
|
||||||
|
{
|
||||||
|
case (char) 1: player.teleport(center); centerUsed = true; break;
|
||||||
|
case (char) 2: player.teleport(north ); northUsed = true; break;
|
||||||
|
case (char) 3: player.teleport(south ); southUsed = true; break;
|
||||||
|
case (char) 4: player.teleport(east ); eastUsed = true; break;
|
||||||
|
case (char) 5: player.teleport(west ); westUsed = true; break;
|
||||||
|
}
|
||||||
|
availableDirections.deleteCharAt(dir);
|
||||||
|
|
||||||
|
//imply that our server has a personality
|
||||||
|
player.sendMessage("§7 *poof*");
|
||||||
|
|
||||||
|
//if all 5 positions have been teleported to, choose a new location
|
||||||
|
if (centerUsed && northUsed && southUsed && eastUsed && westUsed)
|
||||||
|
newLocation();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*================================================================================================*/
|
||||||
|
|
||||||
|
public synchronized boolean newLocation()
|
||||||
|
{
|
||||||
|
size = border.getSize();
|
||||||
|
usableSize = size - (radius * 2);
|
||||||
|
borderCenterX = border.getCenter().getX();
|
||||||
|
borderCenterZ = border.getCenter().getZ();
|
||||||
|
|
||||||
|
//maximum ten thousand attempts
|
||||||
|
for (int i = 0; i < 10000; i++)
|
||||||
|
{
|
||||||
|
//choose an x and z inside the current world border, allowing a margin for the outer positions
|
||||||
|
centerX = (int) (Math.floor((Math.random() - 0.5) * usableSize) + border.getCenter().getX());
|
||||||
|
centerZ = (int) (Math.floor((Math.random() - 0.5) * usableSize) + border.getCenter().getZ());
|
||||||
|
|
||||||
|
//get center of block
|
||||||
|
x = centerX + .5;
|
||||||
|
z = centerZ + .5;
|
||||||
|
|
||||||
|
//get other coordinates
|
||||||
|
northZ = centerZ - radius;
|
||||||
|
southZ = centerZ + radius;
|
||||||
|
eastX = centerX + radius;
|
||||||
|
westX = centerX - radius;
|
||||||
|
|
||||||
|
centerY = world.getHighestBlockYAt( centerX , centerZ );
|
||||||
|
northY = world.getHighestBlockYAt( centerX , northZ );
|
||||||
|
southY = world.getHighestBlockYAt( centerX , southZ );
|
||||||
|
eastY = world.getHighestBlockYAt( eastX , centerZ );
|
||||||
|
westY = world.getHighestBlockYAt( westX , centerZ );
|
||||||
|
|
||||||
|
//get materials for ground, feet-height and head-height blocks at each of the five positions
|
||||||
|
centerGroundMaterial = world.getBlockAt( centerX , centerY -1 , centerZ ).getType();
|
||||||
|
northGroundMaterial = world.getBlockAt( centerX , northY -1 , northZ ).getType();
|
||||||
|
southGroundMaterial = world.getBlockAt( centerX , southY -1 , southZ ).getType();
|
||||||
|
eastGroundMaterial = world.getBlockAt( eastX , eastY -1 , centerZ ).getType();
|
||||||
|
westGroundMaterial = world.getBlockAt( westX , westY -1 , centerZ ).getType();
|
||||||
|
|
||||||
|
centerFeetMaterial = world.getBlockAt( centerX , centerY , centerZ ).getType();
|
||||||
|
northFeetMaterial = world.getBlockAt( centerX , northY , northZ ).getType();
|
||||||
|
southFeetMaterial = world.getBlockAt( centerX , southY , southZ ).getType();
|
||||||
|
eastFeetMaterial = world.getBlockAt( eastX , eastY , centerZ ).getType();
|
||||||
|
westFeetMaterial = world.getBlockAt( westX , westY , centerZ ).getType();
|
||||||
|
|
||||||
|
centerHeadMaterial = world.getBlockAt( centerX , centerY +1 , centerZ ).getType();
|
||||||
|
northHeadMaterial = world.getBlockAt( centerX , northY +1 , northZ ).getType();
|
||||||
|
southHeadMaterial = world.getBlockAt( centerX , southY +1 , southZ ).getType();
|
||||||
|
eastHeadMaterial = world.getBlockAt( eastX , eastY +1 , centerZ ).getType();
|
||||||
|
westHeadMaterial = world.getBlockAt( westX , westY +1 , centerZ ).getType();
|
||||||
|
|
||||||
|
//test that all five positions are on solid ground with air at head height
|
||||||
|
if (centerHeadMaterial == Material.AIR &&
|
||||||
|
northHeadMaterial == Material.AIR &&
|
||||||
|
southHeadMaterial == Material.AIR &&
|
||||||
|
eastHeadMaterial == Material.AIR &&
|
||||||
|
westHeadMaterial == Material.AIR &&
|
||||||
|
|
||||||
|
centerFeetMaterial == Material.AIR &&
|
||||||
|
northFeetMaterial == Material.AIR &&
|
||||||
|
southFeetMaterial == Material.AIR &&
|
||||||
|
eastFeetMaterial == Material.AIR &&
|
||||||
|
westFeetMaterial == Material.AIR &&
|
||||||
|
|
||||||
|
centerGroundMaterial != Material.AIR &&
|
||||||
|
northGroundMaterial != Material.AIR &&
|
||||||
|
southGroundMaterial != Material.AIR &&
|
||||||
|
eastGroundMaterial != Material.AIR &&
|
||||||
|
westGroundMaterial != Material.AIR &&
|
||||||
|
|
||||||
|
centerGroundMaterial != Material.STATIONARY_WATER &&
|
||||||
|
northGroundMaterial != Material.STATIONARY_WATER &&
|
||||||
|
southGroundMaterial != Material.STATIONARY_WATER &&
|
||||||
|
eastGroundMaterial != Material.STATIONARY_WATER &&
|
||||||
|
westGroundMaterial != Material.STATIONARY_WATER &&
|
||||||
|
|
||||||
|
centerGroundMaterial != Material.WATER &&
|
||||||
|
northGroundMaterial != Material.WATER &&
|
||||||
|
southGroundMaterial != Material.WATER &&
|
||||||
|
eastGroundMaterial != Material.WATER &&
|
||||||
|
westGroundMaterial != Material.WATER &&
|
||||||
|
|
||||||
|
centerGroundMaterial != Material.STATIONARY_LAVA &&
|
||||||
|
northGroundMaterial != Material.STATIONARY_LAVA &&
|
||||||
|
southGroundMaterial != Material.STATIONARY_LAVA &&
|
||||||
|
eastGroundMaterial != Material.STATIONARY_LAVA &&
|
||||||
|
westGroundMaterial != Material.STATIONARY_LAVA &&
|
||||||
|
|
||||||
|
centerGroundMaterial != Material.LAVA &&
|
||||||
|
northGroundMaterial != Material.LAVA &&
|
||||||
|
southGroundMaterial != Material.LAVA &&
|
||||||
|
eastGroundMaterial != Material.LAVA &&
|
||||||
|
westGroundMaterial != Material.LAVA)
|
||||||
|
{
|
||||||
|
//set new positions and reset
|
||||||
|
center = new Location( world, x , (double) centerY , z );
|
||||||
|
north = new Location( world, x , (double) northY , northZ + .5 );
|
||||||
|
south = new Location( world, x , (double) southY , southZ + .5 );
|
||||||
|
east = new Location( world, eastX + .5 , (double) eastY , z );
|
||||||
|
west = new Location( world, westX + .5 , (double) westY , z );
|
||||||
|
|
||||||
|
availableDirections.setLength(0);
|
||||||
|
availableDirections.append(chars);
|
||||||
|
|
||||||
|
centerUsed =
|
||||||
|
northUsed =
|
||||||
|
southUsed =
|
||||||
|
eastUsed =
|
||||||
|
westUsed = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
centerUsed =
|
||||||
|
northUsed =
|
||||||
|
southUsed =
|
||||||
|
eastUsed =
|
||||||
|
westUsed = true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package buttondevteam.core.component.randomtp;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import buttondevteam.lib.architecture.ComponentMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* Author: github.com/iiegit
|
||||||
|
*/
|
||||||
|
@ComponentMetadata(enabledByDefault = false)
|
||||||
|
public class RandomTPComponent extends Component<MainPlugin> {
|
||||||
|
@Override
|
||||||
|
protected void enable() {
|
||||||
|
var rtp = new RandomTP();
|
||||||
|
registerCommand(rtp);
|
||||||
|
rtp.onEnable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void disable() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package buttondevteam.core.component.restart;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import buttondevteam.lib.chat.Command2;
|
||||||
|
import buttondevteam.lib.chat.CommandClass;
|
||||||
|
import buttondevteam.lib.chat.ICommand2MC;
|
||||||
|
import buttondevteam.lib.chat.TBMCChatAPI;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
@CommandClass(path = "primerestart", modOnly = true, helpText = {
|
||||||
|
"§6---- Prime restart ----", //
|
||||||
|
"Restarts the server as soon as nobody is online.", //
|
||||||
|
"To be loud, type something after, like /primerestart lol (it doesn't matter what you write)", //
|
||||||
|
"To be silent, don't type anything" //
|
||||||
|
})
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
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) {
|
||||||
|
sender.sendMessage("§bPlayers online, restart delayed.");
|
||||||
|
if (loud)
|
||||||
|
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", component.getRestartBroadcast());
|
||||||
|
plsrestart = true;
|
||||||
|
} else {
|
||||||
|
sender.sendMessage("§bNobody is online. Restarting now.");
|
||||||
|
if (loud)
|
||||||
|
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online. Restarting server.", component.getRestartBroadcast());
|
||||||
|
Bukkit.spigot().restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private static boolean plsrestart = false;
|
||||||
|
@Getter
|
||||||
|
private static boolean loud = false;
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
package buttondevteam.core.component.restart;
|
||||||
|
|
||||||
|
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;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.ChatColor;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerQuitEvent;
|
||||||
|
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides commands such as /schrestart (restart after a countdown) and /primerestart (restart when nobody is online).
|
||||||
|
* Also can automatically restart at a given time.
|
||||||
|
*/
|
||||||
|
@ComponentMetadata(enabledByDefault = false)
|
||||||
|
public class RestartComponent extends Component<MainPlugin> implements Listener {
|
||||||
|
@Override
|
||||||
|
public void enable() {
|
||||||
|
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
|
||||||
|
public void disable() {
|
||||||
|
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()
|
||||||
|
&& !event.getQuitMessage().equalsIgnoreCase("Server closed")
|
||||||
|
&& !event.getQuitMessage().equalsIgnoreCase("Server is restarting")) {
|
||||||
|
if (Bukkit.getOnlinePlayers().size() <= 1) {
|
||||||
|
if (PrimeRestartCommand.isLoud())
|
||||||
|
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online anymore. Restarting.", restartBroadcast);
|
||||||
|
Bukkit.spigot().restart();
|
||||||
|
} else if (!(event.getPlayer() instanceof IFakePlayer) && System.nanoTime() - 10 * 60 * 1000000000L - lasttime > 0) { //10 minutes passed since last reminder
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package buttondevteam.core.component.restart;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import buttondevteam.lib.ScheduledServerRestartEvent;
|
||||||
|
import buttondevteam.lib.chat.Command2;
|
||||||
|
import buttondevteam.lib.chat.CommandClass;
|
||||||
|
import buttondevteam.lib.chat.ICommand2MC;
|
||||||
|
import buttondevteam.lib.chat.TBMCChatAPI;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.boss.BarColor;
|
||||||
|
import org.bukkit.boss.BarFlag;
|
||||||
|
import org.bukkit.boss.BarStyle;
|
||||||
|
import org.bukkit.boss.BossBar;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
@CommandClass(modOnly = true, path = "schrestart", helpText = {
|
||||||
|
"Scheduled restart", //
|
||||||
|
"This command restarts the server 1 minute after it's executed, warning players every 10 seconds.", //
|
||||||
|
"You can optionally set the amount of seconds to wait before the restart." //
|
||||||
|
})
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ScheduledRestartCommand extends ICommand2MC {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private int restartCounter;
|
||||||
|
private BukkitTask restarttask;
|
||||||
|
private volatile BossBar restartbar;
|
||||||
|
@Getter
|
||||||
|
@Nonnull
|
||||||
|
private final RestartComponent component;
|
||||||
|
|
||||||
|
@Command2.Subcommand
|
||||||
|
public boolean def(CommandSender sender, @Command2.OptionalArg int seconds) {
|
||||||
|
if (seconds == 0) seconds = 60;
|
||||||
|
if (seconds < 10) {
|
||||||
|
sender.sendMessage("§cError: Seconds must be at least 10.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final int restarttime = restartCounter = seconds * 20;
|
||||||
|
restartbar = Bukkit.createBossBar("Server restart in " + seconds + "s", BarColor.RED, BarStyle.SOLID,
|
||||||
|
BarFlag.DARKEN_SKY);
|
||||||
|
restartbar.setProgress(1);
|
||||||
|
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));
|
||||||
|
Bukkit.spigot().restart();
|
||||||
|
}
|
||||||
|
if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size() > 0)
|
||||||
|
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds!", component.getRestartBroadcast());
|
||||||
|
restartbar.setProgress(restartCounter / (double) restarttime);
|
||||||
|
restartbar.setTitle(String.format("Server restart in %.2f", restartCounter / 20f));
|
||||||
|
restartCounter--;
|
||||||
|
}, 1, 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
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;
|
||||||
|
import buttondevteam.lib.chat.ICommand2MC;
|
||||||
|
import com.earth2me.essentials.Trade;
|
||||||
|
import com.google.common.io.ByteArrayDataInput;
|
||||||
|
import com.google.common.io.ByteArrayDataOutput;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import com.onarandombox.MultiverseCore.MultiverseCore;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||||
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
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) {
|
||||||
|
spawnloc = MultiverseCore.getPlugin(MultiverseCore.class).getMVWorldManager().getFirstSpawnWorld()
|
||||||
|
.getSpawnLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
Bukkit.getServer().getMessenger().registerOutgoingPluginChannel(getPlugin(), "BungeeCord");
|
||||||
|
Bukkit.getServer().getMessenger().registerIncomingPluginChannel(getPlugin(), "BungeeCord", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void disable() {
|
||||||
|
Bukkit.getServer().getMessenger().unregisterIncomingPluginChannel(getPlugin(), "BungeeCord");
|
||||||
|
Bukkit.getServer().getMessenger().unregisterOutgoingPluginChannel(getPlugin(), "BungeeCord");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
||||||
|
if (!channel.equals("BungeeCord")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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.
|
||||||
|
short len = in.readShort();
|
||||||
|
byte[] msgbytes = new byte[len];
|
||||||
|
in.readFully(msgbytes);
|
||||||
|
|
||||||
|
try {
|
||||||
|
DataInputStream msgin = new DataInputStream(new ByteArrayInputStream(msgbytes));
|
||||||
|
String somedata = msgin.readUTF(); // Read the data in the same way you wrote it
|
||||||
|
if (!"SendToSpawn".equals(somedata)) {
|
||||||
|
System.out.println("somedata: " + somedata);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.teleport(spawnloc);
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
System.out.println("Subchannel: " + subchannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BungeeCord server that has the spawn. Set to empty if this server is the target.
|
||||||
|
*/
|
||||||
|
private final ConfigData<String> targetServer = getConfig().getData("targetServer", "");
|
||||||
|
|
||||||
|
private Location spawnloc;
|
||||||
|
|
||||||
|
@CommandClass(helpText = {
|
||||||
|
"Spawn",
|
||||||
|
"Teleport to spawn."
|
||||||
|
})
|
||||||
|
public class SpawnCommand extends ICommand2MC {
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
|
@Command2.Subcommand
|
||||||
|
public void def(Player player) {
|
||||||
|
if (targetServer.get().length() == 0) {
|
||||||
|
player.sendMessage("§bTeleporting to spawn...");
|
||||||
|
try {
|
||||||
|
if (MainPlugin.ess != null)
|
||||||
|
MainPlugin.ess.getUser(player).getTeleport()
|
||||||
|
.teleport(spawnloc, new Trade(BigDecimal.ZERO, MainPlugin.ess), PlayerTeleportEvent.TeleportCause.COMMAND);
|
||||||
|
else
|
||||||
|
player.teleport(spawnloc);
|
||||||
|
} catch (Exception e) {
|
||||||
|
player.sendMessage("§cFailed to teleport: " + e);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ByteArrayDataOutput out = ByteStreams.newDataOutput();
|
||||||
|
out.writeUTF("Connect");
|
||||||
|
out.writeUTF(targetServer.get());
|
||||||
|
|
||||||
|
player.sendPluginMessage(getPlugin(), "BungeeCord", out.toByteArray());
|
||||||
|
|
||||||
|
Bukkit.getScheduler().runTask(getPlugin(), () -> { //Delay it a bit
|
||||||
|
ByteArrayDataOutput outt = ByteStreams.newDataOutput();
|
||||||
|
outt.writeUTF("ForwardToPlayer"); // So BungeeCord knows to forward it
|
||||||
|
outt.writeUTF(player.getName());
|
||||||
|
outt.writeUTF("ChromaCore-Spawn"); // The channel name to check if this your data
|
||||||
|
|
||||||
|
ByteArrayOutputStream msgbytes = new ByteArrayOutputStream();
|
||||||
|
DataOutputStream msgout = new DataOutputStream(msgbytes);
|
||||||
|
try {
|
||||||
|
msgout.writeUTF("SendToSpawn"); // You can do anything you want with msgout
|
||||||
|
} catch (IOException exception) {
|
||||||
|
exception.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
outt.writeShort(msgbytes.toByteArray().length);
|
||||||
|
outt.write(msgbytes.toByteArray());
|
||||||
|
|
||||||
|
player.sendPluginMessage(getPlugin(), "BungeeCord", outt.toByteArray());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package buttondevteam.core.component.towny;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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() {
|
||||||
|
}
|
||||||
|
}
|
101
Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.java
Normal file
101
Chroma-Core/src/main/java/buttondevteam/lib/ChromaUtils.java
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public final class ChromaUtils {
|
||||||
|
private ChromaUtils() {}
|
||||||
|
|
||||||
|
public static String getDisplayName(CommandSender sender) {
|
||||||
|
if (sender instanceof IHaveFancyName)
|
||||||
|
return ((IHaveFancyName) sender).getFancyName();
|
||||||
|
if (sender instanceof Player)
|
||||||
|
return ((Player) sender).getDisplayName();
|
||||||
|
return sender.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getFullDisplayName(CommandSender sender) {
|
||||||
|
if (sender instanceof IHaveFancyName)
|
||||||
|
return ((IHaveFancyName) sender).getFancyFullName();
|
||||||
|
return getDisplayName(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IHaveFancyName {
|
||||||
|
/**
|
||||||
|
* May not be null.
|
||||||
|
*
|
||||||
|
* @return The name to be displayed in most places.
|
||||||
|
*/
|
||||||
|
String getFancyName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* May return null.
|
||||||
|
*
|
||||||
|
* @return The full name that can be used to uniquely identify the user.
|
||||||
|
*/
|
||||||
|
String getFancyFullName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Number convertNumber(Number number, Class<? extends Number> targetcl) {
|
||||||
|
if (targetcl == long.class || Long.class.isAssignableFrom(targetcl))
|
||||||
|
return number.longValue();
|
||||||
|
else if (targetcl == int.class || Integer.class.isAssignableFrom(targetcl))
|
||||||
|
return number.intValue(); //Needed because the parser can get longs
|
||||||
|
else if (targetcl == short.class || Short.class.isAssignableFrom(targetcl))
|
||||||
|
return number.shortValue();
|
||||||
|
else if (targetcl == byte.class || Byte.class.isAssignableFrom(targetcl))
|
||||||
|
return number.byteValue();
|
||||||
|
else if (targetcl == float.class || Float.class.isAssignableFrom(targetcl))
|
||||||
|
return number.floatValue();
|
||||||
|
else if (targetcl == double.class || Double.class.isAssignableFrom(targetcl))
|
||||||
|
return number.doubleValue();
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the event always asynchronously. The return value is always false if async.
|
||||||
|
*
|
||||||
|
* @param event The event to call
|
||||||
|
* @return The event cancelled state or false if async.
|
||||||
|
*/
|
||||||
|
public static <T extends Event & Cancellable> boolean callEventAsync(T event) {
|
||||||
|
Supplier<Boolean> task = () -> {
|
||||||
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
|
return event.isCancelled();
|
||||||
|
};
|
||||||
|
return doItAsync(task, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does something always asynchronously. It will execute in the same thread if it's not the server thread.
|
||||||
|
*
|
||||||
|
* @param what What to do
|
||||||
|
* @param def Default 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())
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, what::get);
|
||||||
|
else
|
||||||
|
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; }
|
||||||
|
}
|
14
Chroma-Core/src/main/java/buttondevteam/lib/EventExceptionCoreHandler.java
Executable file
14
Chroma-Core/src/main/java/buttondevteam/lib/EventExceptionCoreHandler.java
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
147
Chroma-Core/src/main/java/buttondevteam/lib/EventExceptionHandler.java
Executable file
147
Chroma-Core/src/main/java/buttondevteam/lib/EventExceptionHandler.java
Executable file
|
@ -0,0 +1,147 @@
|
||||||
|
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.apache.commons.lang.Validate;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.EventException;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.plugin.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
abstract class EventExceptionHandler { // https://gist.github.com/aadnk/5430459
|
||||||
|
// For wrapping a registered listener
|
||||||
|
private static class ExceptionRegisteredListener extends RegisteredListener {
|
||||||
|
/**
|
||||||
|
* Represents an event executor that does nothing. This is not really necessary in the current
|
||||||
|
* implementation of CraftBukkit, but we will take no chances.
|
||||||
|
*/
|
||||||
|
private static final EventExecutor NULL_EXECUTOR = (listener, event) -> {
|
||||||
|
// Do nothing
|
||||||
|
};
|
||||||
|
|
||||||
|
private final RegisteredListener delegate;
|
||||||
|
private final EventExceptionHandler handler;
|
||||||
|
|
||||||
|
public ExceptionRegisteredListener(RegisteredListener delegate, EventExceptionHandler handler) {
|
||||||
|
super(delegate.getListener(), NULL_EXECUTOR, delegate.getPriority(),
|
||||||
|
delegate.getPlugin(), delegate.isIgnoringCancelled());
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.handler = handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void callEvent(Event event) throws EventException {
|
||||||
|
try {
|
||||||
|
delegate.callEvent(event);
|
||||||
|
} catch (EventException e) {
|
||||||
|
if (!handler.handle(e.getCause(), event)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
if (!handler.handle(e, event)) {
|
||||||
|
doThrow(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WARNING: HORRIBLE, HORRIBLE HACK to get around checked exceptions
|
||||||
|
private static void doThrow(Throwable e) {
|
||||||
|
ExceptionRegisteredListener.<RuntimeException> doThrowInner(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <E extends Throwable> void doThrowInner(Throwable e) throws E {
|
||||||
|
throw (E) e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register Bukkit event handlers with a given exception handler.
|
||||||
|
* @param listener - a class of event handlers.
|
||||||
|
* @param plugin - the current plugin.
|
||||||
|
* @param handler - exception handler.
|
||||||
|
*/
|
||||||
|
public static void registerEvents(Listener listener, Plugin plugin, EventExceptionHandler handler) {
|
||||||
|
Validate.notNull(plugin, "Plugin cannot be NULL.");
|
||||||
|
|
||||||
|
registerEvents(plugin.getServer().getPluginManager(), listener, plugin, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register Bukkit event handlers with a given exception handler.
|
||||||
|
* @param manager - the current plugin manager.
|
||||||
|
* @param listener - a class of event handlers.
|
||||||
|
* @param plugin - the current plugin.
|
||||||
|
* @param handler - exception handler.
|
||||||
|
*/
|
||||||
|
public static void registerEvents(PluginManager manager, Listener listener, Plugin plugin, EventExceptionHandler handler) {
|
||||||
|
Validate.notNull(manager, "Manager cannot be NULL.");
|
||||||
|
Validate.notNull(listener, "Listener cannot be NULL.");
|
||||||
|
Validate.notNull(plugin, "Plugin cannot be NULL.");
|
||||||
|
Validate.notNull(handler, "Handler cannot be NULL.");
|
||||||
|
|
||||||
|
if (!plugin.isEnabled()) {
|
||||||
|
throw new IllegalPluginAccessException("Plugin attempted to register " + listener + " while not enabled");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create normal listeners
|
||||||
|
for (Map.Entry<Class<? extends Event>, Set<RegisteredListener>> entry :
|
||||||
|
plugin.getPluginLoader().createRegisteredListeners(listener, plugin).entrySet()) {
|
||||||
|
|
||||||
|
// Wrap these listeners in our exception handler
|
||||||
|
getHandlerList(entry.getKey()).registerAll(wrapAll(entry.getValue(), handler));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap every listener in the given collection around an exception handler.
|
||||||
|
* @param listeners - the listeners to wrap.
|
||||||
|
* @param handler - the exception handler to add.
|
||||||
|
* @return The wrapped listeners.
|
||||||
|
*/
|
||||||
|
private static Collection<RegisteredListener> wrapAll(Collection<RegisteredListener> listeners, EventExceptionHandler handler) {
|
||||||
|
List<RegisteredListener> output = Lists.newArrayList();
|
||||||
|
|
||||||
|
for (RegisteredListener listener : listeners) {
|
||||||
|
output.add(new ExceptionRegisteredListener(listener, handler));
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the handler list associated with the given class.
|
||||||
|
* @param clazz - given event class.
|
||||||
|
* @return Associated handler list.
|
||||||
|
*/
|
||||||
|
private static HandlerList getHandlerList(Class<? extends Event> clazz) {
|
||||||
|
// Class must have Event as its superclass
|
||||||
|
while (clazz.getSuperclass() != null && Event.class.isAssignableFrom(clazz.getSuperclass())) {
|
||||||
|
try {
|
||||||
|
Method method = clazz.getDeclaredMethod("getHandlerList");
|
||||||
|
method.setAccessible(true);
|
||||||
|
return (HandlerList) method.invoke(null);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
// Keep on searching
|
||||||
|
clazz = clazz.getSuperclass().asSubclass(Event.class);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalPluginAccessException(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IllegalPluginAccessException("Unable to find handler list for event " + clazz.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a given exception.
|
||||||
|
* @param ex - the exception to handle.
|
||||||
|
* @param event - the event that was being handled.
|
||||||
|
* @return TRUE to indicate that the exception has been handled, FALSE to rethrow it.
|
||||||
|
*/
|
||||||
|
public abstract boolean handle(Throwable ex, Event event);
|
||||||
|
}
|
25
Chroma-Core/src/main/java/buttondevteam/lib/ScheduledServerRestartEvent.java
Executable file
25
Chroma-Core/src/main/java/buttondevteam/lib/ScheduledServerRestartEvent.java
Executable file
|
@ -0,0 +1,25 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.restart.ScheduledRestartCommand;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ScheduledServerRestartEvent extends Event {
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
private final int restartTicks;
|
||||||
|
private final ScheduledRestartCommand command;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
73
Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEvent.java
Executable file
73
Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEvent.java
Executable file
|
@ -0,0 +1,73 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import buttondevteam.lib.chat.ChatMessage;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.Delegate;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure to only send the message to users where {@link #shouldSendTo(CommandSender)} returns true.
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class TBMCChatEvent extends TBMCChatEventBase {
|
||||||
|
public TBMCChatEvent(Channel channel, ChatMessage cm, Channel.RecipientTestResult rtr) {
|
||||||
|
super(channel, cm.getMessage(), rtr.score, rtr.groupID);
|
||||||
|
this.cm = cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
@Delegate //<-- Backwards compatibility
|
||||||
|
private ChatMessage cm;
|
||||||
|
|
||||||
|
private boolean isIgnoreSenderPermissions() {
|
||||||
|
return cm.getPermCheck() != cm.getSender();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will allow the sender of the message if {@link #isIgnoreSenderPermissions()} is true.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean shouldSendTo(CommandSender sender) {
|
||||||
|
if (isIgnoreSenderPermissions() && sender.equals(this.cm.getSender()))
|
||||||
|
return true; //Allow sending the message no matter what
|
||||||
|
return super.shouldSendTo(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will allow the sender of the message if {@link #isIgnoreSenderPermissions()} is true.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int getMCScore(CommandSender sender) {
|
||||||
|
if (isIgnoreSenderPermissions() && sender.equals(this.cm.getSender()))
|
||||||
|
return getScore(); //Send in the correct group no matter what
|
||||||
|
return super.getMCScore(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This will allow the sender of the message if {@link #isIgnoreSenderPermissions()} is true.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getGroupID(CommandSender sender) {
|
||||||
|
if (isIgnoreSenderPermissions() && sender.equals(this.cm.getSender()))
|
||||||
|
return getGroupID(); //Send in the correct group no matter what
|
||||||
|
return super.getGroupID(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
59
Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEventBase.java
Executable file
59
Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatEventBase.java
Executable file
|
@ -0,0 +1,59 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public abstract class TBMCChatEventBase extends Event implements Cancellable {
|
||||||
|
private final Channel channel;
|
||||||
|
private @NonNull String message;
|
||||||
|
private @Setter boolean cancelled;
|
||||||
|
/**
|
||||||
|
* The sender's score.
|
||||||
|
*/
|
||||||
|
private final int score;
|
||||||
|
/**
|
||||||
|
* The sender's group ID.
|
||||||
|
*/
|
||||||
|
private final String groupID;
|
||||||
|
|
||||||
|
@java.beans.ConstructorProperties({"channel", "message", "score", "groupID"})
|
||||||
|
public TBMCChatEventBase(Channel channel, String message, int score, String groupID) {
|
||||||
|
super(true);
|
||||||
|
this.channel = channel;
|
||||||
|
this.message = message;
|
||||||
|
this.score = score;
|
||||||
|
this.groupID = groupID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Errors are sent to the sender automatically
|
||||||
|
*/
|
||||||
|
public boolean shouldSendTo(CommandSender sender) {
|
||||||
|
return channel.shouldSendTo(sender, score);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Errors are sent to the sender automatically
|
||||||
|
*/
|
||||||
|
public int getMCScore(CommandSender sender) {
|
||||||
|
return channel.getMCScore(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Errors are sent to the sender automatically<br>
|
||||||
|
*
|
||||||
|
* Null means don't send
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String getGroupID(CommandSender sender) {
|
||||||
|
return channel.getGroupID(sender);
|
||||||
|
}
|
||||||
|
}
|
44
Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatPreprocessEvent.java
Executable file
44
Chroma-Core/src/main/java/buttondevteam/lib/TBMCChatPreprocessEvent.java
Executable file
|
@ -0,0 +1,44 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to change messages before it's sent.
|
||||||
|
* <b>Only called before sending messages with SendChatMessage.</b>
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class TBMCChatPreprocessEvent extends Event implements Cancellable {
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
private final Channel channel;
|
||||||
|
private final CommandSender sender;
|
||||||
|
@Setter
|
||||||
|
private String message;
|
||||||
|
@Setter
|
||||||
|
private boolean cancelled;
|
||||||
|
|
||||||
|
public TBMCChatPreprocessEvent(CommandSender sender, Channel channel, String message) {
|
||||||
|
super(true);
|
||||||
|
this.sender = sender;
|
||||||
|
this.channel = channel;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
39
Chroma-Core/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java
Executable file
39
Chroma-Core/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java
Executable file
|
@ -0,0 +1,39 @@
|
||||||
|
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;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Can be used to change or handle commands before they're sent.
|
||||||
|
* <b>Called on using player, console and Discord commands.</b>
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TBMCCommandPreprocessEvent extends Event implements Cancellable {
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
private final CommandSender sender;
|
||||||
|
private final Channel channel;
|
||||||
|
@Setter
|
||||||
|
private final String message;
|
||||||
|
private final CommandSender permCheck;
|
||||||
|
@Setter
|
||||||
|
private boolean cancelled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
176
Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java
Executable file
176
Chroma-Core/src/main/java/buttondevteam/lib/TBMCCoreAPI.java
Executable file
|
@ -0,0 +1,176 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
|
import buttondevteam.lib.potato.DebugPotato;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
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;
|
||||||
|
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>() {
|
||||||
|
private static final long serialVersionUID = -4462159250738367334L;
|
||||||
|
|
||||||
|
{
|
||||||
|
add("Alisolarflare");
|
||||||
|
add("NorbiPeti");
|
||||||
|
add("iie");
|
||||||
|
add("thewindmillman");
|
||||||
|
add("mayskam1995");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static String DownloadString(String urlstr) throws IOException {
|
||||||
|
URL url = new URL(urlstr);
|
||||||
|
URLConnection con = url.openConnection();
|
||||||
|
con.setRequestProperty("User-Agent", "TBMCPlugins");
|
||||||
|
InputStream in = con.getInputStream();
|
||||||
|
String encoding = con.getContentEncoding();
|
||||||
|
encoding = encoding == null ? "UTF-8" : encoding;
|
||||||
|
Scanner s = new Scanner(in).useDelimiter("\\A");
|
||||||
|
String body = s.hasNext() ? s.next() : "";
|
||||||
|
in.close();
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final HashMap<String, Throwable> exceptionsToSend = new HashMap<>();
|
||||||
|
private static final List<String> debugMessagesToSend = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, Component<?> component) {
|
||||||
|
SendException(sourcemsg, e, false, component::logWarn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
|
synchronized (exceptionsToSend) {
|
||||||
|
if (!event.isHandled())
|
||||||
|
exceptionsToSend.put(sourcemsg, e);
|
||||||
|
}
|
||||||
|
logWarn.accept(sourcemsg);
|
||||||
|
e.printStackTrace();
|
||||||
|
if (debugPotato) {
|
||||||
|
List<Player> devsOnline = new ArrayList<>();
|
||||||
|
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||||
|
if (coders.contains(player.getName())) {
|
||||||
|
devsOnline.add(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!devsOnline.isEmpty()) {
|
||||||
|
DebugPotato potato = new DebugPotato()
|
||||||
|
.setMessage(new String[]{ //
|
||||||
|
"§b§o" + e.getClass().getSimpleName(), //
|
||||||
|
"§c§o" + sourcemsg, //
|
||||||
|
"§a§oFind a dev to fix this issue"})
|
||||||
|
.setType(e instanceof IOException ? "Throwable Potato"
|
||||||
|
: e instanceof ClassCastException ? "Squished Potato"
|
||||||
|
: e instanceof NullPointerException ? "Plain Potato"
|
||||||
|
: e instanceof StackOverflowError ? "Chips" : "Error Potato");
|
||||||
|
for (Player dev : devsOnline) {
|
||||||
|
potato.Send(dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception ee) {
|
||||||
|
System.err.println("Failed to send exception!");
|
||||||
|
ee.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendDebugMessage(String debugMessage) {
|
||||||
|
SendUnsentDebugMessages();
|
||||||
|
TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(debugMessage);
|
||||||
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
|
synchronized (debugMessagesToSend) {
|
||||||
|
if (!event.isSent())
|
||||||
|
debugMessagesToSend.add(debugMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static EventExceptionCoreHandler eventExceptionCoreHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers Bukkit events, handling the exceptions occurring in those events
|
||||||
|
*
|
||||||
|
* @param listener The class that handles the events
|
||||||
|
* @param plugin The plugin which the listener belongs to
|
||||||
|
*/
|
||||||
|
public static void RegisterEventsForExceptions(Listener listener, Plugin plugin) {
|
||||||
|
if (eventExceptionCoreHandler == null) eventExceptionCoreHandler = new EventExceptionCoreHandler();
|
||||||
|
EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass, Supplier<T> constructor) {
|
||||||
|
ChromaGamerBase.RegisterPluginUserClass(userclass, constructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send exceptions that haven't been sent (their events didn't get handled). This method is used by the DiscordPlugin's ready event
|
||||||
|
*/
|
||||||
|
public static void SendUnsentExceptions() {
|
||||||
|
synchronized (exceptionsToSend) {
|
||||||
|
if (exceptionsToSend.size() > 20) {
|
||||||
|
exceptionsToSend.clear(); // Don't call more and more events if all the handler plugins are unloaded
|
||||||
|
Bukkit.getLogger().warning("Unhandled exception list is over 20! Clearing!");
|
||||||
|
}
|
||||||
|
for (Iterator<Entry<String, Throwable>> iterator = exceptionsToSend.entrySet().iterator(); iterator.hasNext(); ) {
|
||||||
|
Entry<String, Throwable> entry = iterator.next();
|
||||||
|
TBMCExceptionEvent event = new TBMCExceptionEvent(entry.getKey(), entry.getValue());
|
||||||
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
|
if (event.isHandled())
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void SendUnsentDebugMessages() {
|
||||||
|
synchronized (debugMessagesToSend) {
|
||||||
|
if (debugMessagesToSend.size() > 20) {
|
||||||
|
debugMessagesToSend.clear(); // Don't call more and more DebugMessages if all the handler plugins are unloaded
|
||||||
|
Bukkit.getLogger().warning("Unhandled Debug Message list is over 20! Clearing!");
|
||||||
|
}
|
||||||
|
for (Iterator<String> iterator = debugMessagesToSend.iterator(); iterator.hasNext(); ) {
|
||||||
|
String message = iterator.next();
|
||||||
|
TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(message);
|
||||||
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
|
if (event.isSent())
|
||||||
|
iterator.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean IsTestServer() {
|
||||||
|
if (MainPlugin.Instance == null) return true;
|
||||||
|
return MainPlugin.Instance.test.get();
|
||||||
|
}
|
||||||
|
}
|
51
Chroma-Core/src/main/java/buttondevteam/lib/TBMCDebugMessageEvent.java
Executable file
51
Chroma-Core/src/main/java/buttondevteam/lib/TBMCDebugMessageEvent.java
Executable file
|
@ -0,0 +1,51 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
public class TBMCDebugMessageEvent extends Event {
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
private final String message;
|
||||||
|
private boolean sent;
|
||||||
|
|
||||||
|
public TBMCDebugMessageEvent(String message) {
|
||||||
|
super(!Bukkit.isPrimaryThread());
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the message (where did this exception occur, etc.)
|
||||||
|
*
|
||||||
|
* @return The message
|
||||||
|
*/
|
||||||
|
public String getDebugMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets if this event was handled
|
||||||
|
*
|
||||||
|
* @return True if it was handled
|
||||||
|
*/
|
||||||
|
public boolean isSent() {
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flags the event as handled
|
||||||
|
*/
|
||||||
|
public void setSent() {
|
||||||
|
this.sent = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
43
Chroma-Core/src/main/java/buttondevteam/lib/TBMCExceptionEvent.java
Executable file
43
Chroma-Core/src/main/java/buttondevteam/lib/TBMCExceptionEvent.java
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* This event gets called (ideally) each time an exception occurs in a TBMC plugin. To call it, use {@link TBMCCoreAPI#SendException(String, Throwable)}.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Norbi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class TBMCExceptionEvent extends Event {
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
private final String sourceMessage;
|
||||||
|
private final Throwable exception;
|
||||||
|
private boolean handled;
|
||||||
|
|
||||||
|
@java.beans.ConstructorProperties({"sourceMessage", "exception"})
|
||||||
|
public TBMCExceptionEvent(String sourceMessage, Throwable exception) {
|
||||||
|
super(!Bukkit.isPrimaryThread());
|
||||||
|
this.sourceMessage = sourceMessage;
|
||||||
|
this.exception = exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHandled() {
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
74
Chroma-Core/src/main/java/buttondevteam/lib/TBMCSystemChatEvent.java
Executable file
74
Chroma-Core/src/main/java/buttondevteam/lib/TBMCSystemChatEvent.java
Executable file
|
@ -0,0 +1,74 @@
|
||||||
|
package buttondevteam.lib;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.val;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure to only send the message to users who {@link #shouldSendTo(CommandSender)} returns true.
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public class TBMCSystemChatEvent extends TBMCChatEventBase {
|
||||||
|
private final String[] exceptions;
|
||||||
|
private final BroadcastTarget target;
|
||||||
|
private boolean handled;
|
||||||
|
|
||||||
|
public void setHandled() {
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TBMCSystemChatEvent(Channel channel, String message, int score, String groupid, String[] exceptions, BroadcastTarget target) { // TODO: Rich message
|
||||||
|
super(channel, message, score, groupid);
|
||||||
|
this.exceptions = exceptions;
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class BroadcastTarget {
|
||||||
|
private final @Getter String name;
|
||||||
|
private static final HashSet<BroadcastTarget> targets = new HashSet<>();
|
||||||
|
public static final BroadcastTarget ALL = new BroadcastTarget("ALL");
|
||||||
|
|
||||||
|
public static BroadcastTarget add(String name) {
|
||||||
|
val bt = new BroadcastTarget(Objects.requireNonNull(name));
|
||||||
|
targets.add(bt);
|
||||||
|
return bt;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void remove(BroadcastTarget target) {
|
||||||
|
targets.remove(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static BroadcastTarget get(String name) {
|
||||||
|
return targets.stream().filter(bt -> bt.name.equalsIgnoreCase(name)).findAny().orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Stream<BroadcastTarget> stream() {
|
||||||
|
return targets.stream();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
package buttondevteam.lib.architecture;
|
||||||
|
|
||||||
|
import buttondevteam.buttonproc.HasConfig;
|
||||||
|
import buttondevteam.core.ComponentManager;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.chat.Command2MC;
|
||||||
|
import buttondevteam.lib.chat.ICommand2MC;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
|
@HasConfig(global = true)
|
||||||
|
public abstract class ButtonPlugin extends JavaPlugin {
|
||||||
|
@Getter //Needs to be static as we don't know the plugin when a command is handled
|
||||||
|
private static final Command2MC command2MC = new Command2MC();
|
||||||
|
@Getter(AccessLevel.PROTECTED)
|
||||||
|
private final IHaveConfig iConfig = new IHaveConfig(this::saveConfig);
|
||||||
|
private CommentedConfiguration yaml;
|
||||||
|
@Getter(AccessLevel.PROTECTED)
|
||||||
|
private IHaveConfig data; //TODO
|
||||||
|
/**
|
||||||
|
* Used to unregister components in the right order - and to reload configs
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private final Stack<Component<?>> componentStack = new Stack<>();
|
||||||
|
|
||||||
|
protected abstract void pluginEnable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called after the components are unregistered
|
||||||
|
*/
|
||||||
|
protected abstract void pluginDisable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before the components are unregistered
|
||||||
|
*/
|
||||||
|
protected void pluginPreDisable() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onEnable() {
|
||||||
|
if (!loadConfig()) {
|
||||||
|
getLogger().warning("Please fix the issues and restart the server to load the plugin.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pluginEnable();
|
||||||
|
} catch (Exception 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean loadConfig() {
|
||||||
|
var config = getConfig();
|
||||||
|
if (config == null)
|
||||||
|
return false;
|
||||||
|
var section = config.getConfigurationSection("global");
|
||||||
|
if (section == null) section = config.createSection("global");
|
||||||
|
iConfig.reset(section);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onDisable() {
|
||||||
|
try {
|
||||||
|
pluginPreDisable();
|
||||||
|
ComponentManager.unregComponents(this);
|
||||||
|
pluginDisable();
|
||||||
|
if (ConfigData.saveNow(getConfig()))
|
||||||
|
getLogger().info("Saved configuration changes.");
|
||||||
|
getCommand2MC().unregisterCommands(this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reloadConfig() {
|
||||||
|
tryReloadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean tryReloadConfig() {
|
||||||
|
if (!justReload()) return false;
|
||||||
|
loadConfig();
|
||||||
|
componentStack.forEach(c -> Component.updateConfig(this, c));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean justReload() {
|
||||||
|
if (yaml != null && ConfigData.saveNow(getConfig())) {
|
||||||
|
getLogger().warning("Saved pending configuration changes to the file, didn't reload. Apply your changes again.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var file = new File(getDataFolder(), "config.yml");
|
||||||
|
var yaml = new CommentedConfiguration(file);
|
||||||
|
if (file.exists()) {
|
||||||
|
try {
|
||||||
|
yaml.load(file);
|
||||||
|
} catch (IOException | InvalidConfigurationException e) {
|
||||||
|
getLogger().warning("Failed to load config! Check for syntax errors.");
|
||||||
|
e.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.yaml = yaml;
|
||||||
|
var res = getTextResource("configHelp.yml");
|
||||||
|
if (res == null)
|
||||||
|
return true;
|
||||||
|
var yc = YamlConfiguration.loadConfiguration(res);
|
||||||
|
for (var kv : yc.getValues(true).entrySet())
|
||||||
|
if (kv.getValue() instanceof String)
|
||||||
|
yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""),
|
||||||
|
Arrays.stream(((String) kv.getValue()).split("\n"))
|
||||||
|
.map(str -> "# " + str.trim()).toArray(String[]::new));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FileConfiguration getConfig() {
|
||||||
|
if (yaml == null)
|
||||||
|
justReload();
|
||||||
|
if (yaml == null) return new YamlConfiguration(); //Return a temporary instance
|
||||||
|
return yaml;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveConfig() {
|
||||||
|
try {
|
||||||
|
if (yaml != null)
|
||||||
|
yaml.save();
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to save config", e, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers command and sets its plugin.
|
||||||
|
*
|
||||||
|
* @param command The command to register
|
||||||
|
*/
|
||||||
|
protected void registerCommand(ICommand2MC command) {
|
||||||
|
command.registerToPlugin(this);
|
||||||
|
getCommand2MC().registerCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface ConfigOpts {
|
||||||
|
boolean disableConfigGen() default false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean configGenAllowed(Object obj) {
|
||||||
|
return !Optional.ofNullable(obj.getClass().getAnnotation(ConfigOpts.class))
|
||||||
|
.map(ConfigOpts::disableConfigGen).orElse(false);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,229 @@
|
||||||
|
package buttondevteam.lib.architecture;
|
||||||
|
|
||||||
|
import org.bukkit.configuration.InvalidConfigurationException;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.configuration.file.YamlConstructor;
|
||||||
|
import org.bukkit.configuration.file.YamlRepresenter;
|
||||||
|
import org.yaml.snakeyaml.DumperOptions;
|
||||||
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A copy of Towny's CommentedConfiguration: https://github.com/TownyAdvanced/Towny/blob/master/src/com/palmergames/bukkit/config/CommentedConfiguration.java
|
||||||
|
* Modified to remove dependency on the FileMgmt class
|
||||||
|
*
|
||||||
|
* @author dumptruckman & Articdive
|
||||||
|
*/
|
||||||
|
public class CommentedConfiguration extends YamlConfiguration {
|
||||||
|
private HashMap<String, String> comments;
|
||||||
|
private File file;
|
||||||
|
|
||||||
|
private final DumperOptions yamlOptions = new DumperOptions();
|
||||||
|
private final Representer yamlRepresenter = new YamlRepresenter();
|
||||||
|
private final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions);
|
||||||
|
|
||||||
|
public CommentedConfiguration(File file) {
|
||||||
|
|
||||||
|
super();
|
||||||
|
comments = new HashMap<>();
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean load() {
|
||||||
|
|
||||||
|
boolean loaded = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.load(file);
|
||||||
|
} catch (InvalidConfigurationException | IOException e) {
|
||||||
|
loaded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() throws IOException {
|
||||||
|
|
||||||
|
boolean saved = true;
|
||||||
|
|
||||||
|
// Save the config just like normal
|
||||||
|
try {
|
||||||
|
this.save(file);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
saved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's comments to add and it saved fine, we need to add comments
|
||||||
|
if (!comments.isEmpty() && saved) {
|
||||||
|
// String array of each line in the config file
|
||||||
|
String[] yamlContents = Files.readAllLines(file.toPath()).toArray(new String[0]);
|
||||||
|
|
||||||
|
// This will hold the newly formatted line
|
||||||
|
StringBuilder newContents = new StringBuilder();
|
||||||
|
// This holds the current path the lines are at in the config
|
||||||
|
String currentPath = "";
|
||||||
|
// This flags if the line is a node or unknown text.
|
||||||
|
boolean node;
|
||||||
|
// The depth of the path. (number of words separated by periods - 1)
|
||||||
|
int depth = 0;
|
||||||
|
|
||||||
|
// Loop through the config lines
|
||||||
|
for (String line : yamlContents) {
|
||||||
|
// If the line is a node (and not something like a list value)
|
||||||
|
if (line.contains(": ") || (line.length() > 1 && line.charAt(line.length() - 1) == ':')) {
|
||||||
|
|
||||||
|
// This is a node so flag it as one
|
||||||
|
node = true;
|
||||||
|
|
||||||
|
// Grab the index of the end of the node name
|
||||||
|
int index;
|
||||||
|
index = line.indexOf(": ");
|
||||||
|
if (index < 0) {
|
||||||
|
index = line.length() - 1;
|
||||||
|
}
|
||||||
|
// If currentPath is empty, store the node name as the currentPath. (this is only on the first iteration, i think)
|
||||||
|
if (currentPath.isEmpty()) {
|
||||||
|
currentPath = line.substring(0, index);
|
||||||
|
} else {
|
||||||
|
// Calculate the whitespace preceding the node name
|
||||||
|
int whiteSpace = 0;
|
||||||
|
for (int n = 0; n < line.length(); n++) {
|
||||||
|
if (line.charAt(n) == ' ') {
|
||||||
|
whiteSpace++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Find out if the current depth (whitespace * 2) is greater/lesser/equal to the previous depth
|
||||||
|
if (whiteSpace / 2 > depth) {
|
||||||
|
// Path is deeper. Add a . and the node name
|
||||||
|
currentPath += "." + line.substring(whiteSpace, index);
|
||||||
|
depth++;
|
||||||
|
} else if (whiteSpace / 2 < depth) {
|
||||||
|
// Path is shallower, calculate current depth from whitespace (whitespace / 2) and subtract that many levels from the currentPath
|
||||||
|
int newDepth = whiteSpace / 2;
|
||||||
|
for (int i = 0; i < depth - newDepth; i++) {
|
||||||
|
currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
|
||||||
|
}
|
||||||
|
// Grab the index of the final period
|
||||||
|
int lastIndex = currentPath.lastIndexOf(".");
|
||||||
|
if (lastIndex < 0) {
|
||||||
|
// if there isn't a final period, set the current path to nothing because we're at root
|
||||||
|
currentPath = "";
|
||||||
|
} else {
|
||||||
|
// If there is a final period, replace everything after it with nothing
|
||||||
|
currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
|
||||||
|
currentPath += ".";
|
||||||
|
}
|
||||||
|
// Add the new node name to the path
|
||||||
|
currentPath += line.substring(whiteSpace, index);
|
||||||
|
// Reset the depth
|
||||||
|
depth = newDepth;
|
||||||
|
} else {
|
||||||
|
// Path is same depth, replace the last path node name to the current node name
|
||||||
|
int lastIndex = currentPath.lastIndexOf(".");
|
||||||
|
if (lastIndex < 0) {
|
||||||
|
// if there isn't a final period, set the current path to nothing because we're at root
|
||||||
|
currentPath = "";
|
||||||
|
} else {
|
||||||
|
// If there is a final period, replace everything after it with nothing
|
||||||
|
currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
|
||||||
|
currentPath += ".";
|
||||||
|
}
|
||||||
|
//currentPath = currentPath.replace(currentPath.substring(currentPath.lastIndexOf(".")), "");
|
||||||
|
currentPath += line.substring(whiteSpace, index);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
node = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
// If there's a comment for the current path, retrieve it and flag that path as already commented
|
||||||
|
String comment = comments.get(currentPath);
|
||||||
|
|
||||||
|
if (comment != null) {
|
||||||
|
// Add the comment to the beginning of the current line
|
||||||
|
line = comment + System.getProperty("line.separator") + line + System.getProperty("line.separator");
|
||||||
|
} else {
|
||||||
|
// Add a new line as it is a node, but has no comment
|
||||||
|
line += System.getProperty("line.separator");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the (modified) line to the total config String
|
||||||
|
if (!node) {
|
||||||
|
newContents.append(line).append(System.getProperty("line.separator"));
|
||||||
|
} else {
|
||||||
|
newContents.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Due to a Bukkit Bug with the Configuration
|
||||||
|
* we just need to remove any extra comments at the start of a file.
|
||||||
|
*/
|
||||||
|
while (newContents.toString().startsWith(" " + System.getProperty("line.separator"))) {
|
||||||
|
newContents = new StringBuilder(newContents.toString().replaceFirst(" " + System.getProperty("line.separator"), ""));
|
||||||
|
}
|
||||||
|
Files.write(file.toPath(), newContents.toString().getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a comment just before the specified path. The comment can be
|
||||||
|
* multiple lines. An empty string will indicate a blank line.
|
||||||
|
*
|
||||||
|
* @param path Configuration path to add comment.
|
||||||
|
* @param commentLines Comments to add. One String per line.
|
||||||
|
*/
|
||||||
|
public void addComment(String path, String... commentLines) {
|
||||||
|
|
||||||
|
StringBuilder commentstring = new StringBuilder();
|
||||||
|
StringBuilder leadingSpaces = new StringBuilder();
|
||||||
|
for (int n = 0; n < path.length(); n++) {
|
||||||
|
if (path.charAt(n) == '.') {
|
||||||
|
leadingSpaces.append(" ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (String line : commentLines) {
|
||||||
|
if (!line.isEmpty()) {
|
||||||
|
line = leadingSpaces + line;
|
||||||
|
} else {
|
||||||
|
line = " ";
|
||||||
|
}
|
||||||
|
if (commentstring.length() > 0) {
|
||||||
|
commentstring.append(System.getProperty("line.separator"));
|
||||||
|
}
|
||||||
|
commentstring.append(line);
|
||||||
|
}
|
||||||
|
comments.put(path, commentstring.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String saveToString() {
|
||||||
|
yamlOptions.setIndent(options().indent());
|
||||||
|
yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||||
|
yamlOptions.setWidth(10000);
|
||||||
|
yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||||
|
|
||||||
|
|
||||||
|
String dump = yaml.dump(getValues(false));
|
||||||
|
|
||||||
|
|
||||||
|
if (dump.equals(BLANK_CONFIG)) {
|
||||||
|
dump = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return dump;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
package buttondevteam.lib.architecture;
|
||||||
|
|
||||||
|
import buttondevteam.buttonproc.HasConfig;
|
||||||
|
import buttondevteam.core.ComponentManager;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException;
|
||||||
|
import buttondevteam.lib.chat.ICommand2MC;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.val;
|
||||||
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration is based on class name
|
||||||
|
*/
|
||||||
|
@HasConfig(global = false) //Used for obtaining javadoc
|
||||||
|
public abstract class Component<TP extends JavaPlugin> {
|
||||||
|
@SuppressWarnings("rawtypes") private static HashMap<Class<? extends Component>, Component<? extends JavaPlugin>> components = new HashMap<>();
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private boolean enabled = false;
|
||||||
|
@Getter
|
||||||
|
@NonNull
|
||||||
|
private TP plugin;
|
||||||
|
private @Getter final IHaveConfig config = new IHaveConfig(null);
|
||||||
|
private @Getter IHaveConfig data; //TODO
|
||||||
|
|
||||||
|
public final ConfigData<Boolean> shouldBeEnabled = config.getData("enabled",
|
||||||
|
Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.<br>
|
||||||
|
* Make sure to register the dependencies first.<br>
|
||||||
|
* The component will be enabled automatically, regardless of when it was registered.<br>
|
||||||
|
* <b>If not using {@link ButtonPlugin}, call {@link ComponentManager#unregComponents(ButtonPlugin)} on plugin disable.</b>
|
||||||
|
*
|
||||||
|
* @param component The component to register
|
||||||
|
* @return Whether the component is registered successfully (it may have failed to enable)
|
||||||
|
*/
|
||||||
|
public static <T extends JavaPlugin> boolean registerComponent(T plugin, Component<T> component) {
|
||||||
|
return registerUnregisterComponent(plugin, component, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters a component by calling {@link #unregister(JavaPlugin)}.<br>
|
||||||
|
* Make sure to unregister the dependencies last.<br>
|
||||||
|
* <b>Components will be unregistered in opposite order of registering by default by {@link ButtonPlugin} or {@link ComponentManager#unregComponents(ButtonPlugin)}.</b>
|
||||||
|
*
|
||||||
|
* @param component The component to unregister
|
||||||
|
* @return Whether the component is unregistered successfully (it also got disabled)
|
||||||
|
*/
|
||||||
|
public static <T extends JavaPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
|
||||||
|
return registerUnregisterComponent(plugin, component, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T extends JavaPlugin> boolean registerUnregisterComponent(T plugin, Component<T> component, boolean register) {
|
||||||
|
try {
|
||||||
|
val metaAnn = component.getClass().getAnnotation(ComponentMetadata.class);
|
||||||
|
if (metaAnn != null) {
|
||||||
|
@SuppressWarnings("rawtypes") Class<? extends Component>[] dependencies = metaAnn.depends();
|
||||||
|
for (val dep : dependencies) { //TODO: Support dependencies at enable/disable as well
|
||||||
|
if (!components.containsKey(dep)) {
|
||||||
|
plugin.getLogger().warning("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + " as a required dependency is missing/disabled: " + dep.getSimpleName());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (register) {
|
||||||
|
if (components.containsKey(component.getClass())) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to register component " + component.getClassName(), new IllegalArgumentException("The component is already registered!"), plugin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
component.plugin = plugin;
|
||||||
|
component.config.setSaveAction(plugin::saveConfig);
|
||||||
|
updateConfig(plugin, component);
|
||||||
|
component.register(plugin);
|
||||||
|
components.put(component.getClass(), component);
|
||||||
|
if (plugin instanceof ButtonPlugin)
|
||||||
|
((ButtonPlugin) plugin).getComponentStack().push(component);
|
||||||
|
if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) {
|
||||||
|
try { //Enable components registered after the previous ones getting enabled
|
||||||
|
setComponentEnabled(component, true);
|
||||||
|
return true;
|
||||||
|
} catch (Exception | NoClassDefFoundError e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to enable component " + component.getClassName() + "!", e, component);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!components.containsKey(component.getClass()))
|
||||||
|
return true; //Already unregistered
|
||||||
|
if (component.enabled) {
|
||||||
|
try {
|
||||||
|
setComponentEnabled(component, false);
|
||||||
|
} catch (Exception | NoClassDefFoundError e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to disable component " + component.getClassName() + "!", e, component);
|
||||||
|
return false; //If failed to disable, won't unregister either
|
||||||
|
}
|
||||||
|
}
|
||||||
|
component.unregister(plugin);
|
||||||
|
components.remove(component.getClass());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to " + (register ? "" : "un") + "register component " + component.getClassName() + "!", e, plugin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
try {
|
||||||
|
updateConfig(component.getPlugin(), component);
|
||||||
|
component.enable();
|
||||||
|
if (ButtonPlugin.configGenAllowed(component)) {
|
||||||
|
IHaveConfig.pregenConfig(component, null);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
try { //Automatically disable components that fail to enable properly
|
||||||
|
setComponentEnabled(component, false);
|
||||||
|
throw e;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Throwable t = ex;
|
||||||
|
for (var th = t; th != null; th = th.getCause())
|
||||||
|
t = th; //Set if not null
|
||||||
|
if (t != e)
|
||||||
|
t.initCause(e);
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
component.disable();
|
||||||
|
ButtonPlugin.getCommand2MC().unregisterCommands(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void updateConfig(JavaPlugin plugin, Component<?> component) {
|
||||||
|
if (plugin.getConfig() != null) { //Production
|
||||||
|
var compconf = plugin.getConfig().getConfigurationSection("components");
|
||||||
|
if (compconf == null) compconf = plugin.getConfig().createSection("components");
|
||||||
|
var configSect = compconf.getConfigurationSection(component.getClassName());
|
||||||
|
if (configSect == null)
|
||||||
|
configSect = compconf.createSection(component.getClassName());
|
||||||
|
component.config.reset(configSect);
|
||||||
|
} //Testing: it's already set
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the currently registered components<br>
|
||||||
|
*
|
||||||
|
* @return The currently registered components
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
public static Map<Class<? extends Component>, Component<? extends JavaPlugin>> getComponents() {
|
||||||
|
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"})
|
||||||
|
protected void register(JavaPlugin plugin) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters the module, when called by the JavaPlugin class.
|
||||||
|
* This gets fired when the plugin is disabled.
|
||||||
|
* Do any cleanups needed within this method.
|
||||||
|
*
|
||||||
|
* @param plugin Plugin object
|
||||||
|
*/
|
||||||
|
@SuppressWarnings({"unused"})
|
||||||
|
protected void unregister(JavaPlugin plugin) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enables the module, when called by the JavaPlugin class. Call
|
||||||
|
* registerCommand() and registerListener() within this method.<br>
|
||||||
|
* To access the plugin, use {@link #getPlugin()}.
|
||||||
|
*/
|
||||||
|
protected abstract void enable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the module, when called by the JavaPlugin class. Do
|
||||||
|
* any cleanups needed within this method.
|
||||||
|
* To access the plugin, use {@link #getPlugin()}.
|
||||||
|
*/
|
||||||
|
protected abstract void disable();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a command to the component. Make sure to use {@link buttondevteam.lib.chat.CommandClass} and {@link buttondevteam.lib.chat.Command2.Subcommand}.
|
||||||
|
* You don't need to register the command in plugin.yml.
|
||||||
|
*
|
||||||
|
* @param command Custom coded command class
|
||||||
|
*/
|
||||||
|
protected final void registerCommand(ICommand2MC command) {
|
||||||
|
if (plugin instanceof ButtonPlugin)
|
||||||
|
command.registerToPlugin((ButtonPlugin) plugin);
|
||||||
|
command.registerToComponent(this);
|
||||||
|
ButtonPlugin.getCommand2MC().registerCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a Listener to this component
|
||||||
|
*
|
||||||
|
* @param listener The event listener to register
|
||||||
|
* @return The provided listener
|
||||||
|
*/
|
||||||
|
protected final Listener registerListener(Listener listener) {
|
||||||
|
TBMCCoreAPI.RegisterEventsForExceptions(listener, plugin);
|
||||||
|
return listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a map of configs that are under the given key.
|
||||||
|
*
|
||||||
|
* @param key The key to use
|
||||||
|
* @param defaultProvider A mapping between config paths and config generators
|
||||||
|
* @return A map containing configs
|
||||||
|
*/
|
||||||
|
protected Map<String, IHaveConfig> getConfigMap(String key, Map<String, Consumer<IHaveConfig>> defaultProvider) {
|
||||||
|
val c = getConfig().getConfig();
|
||||||
|
var cs = c.getConfigurationSection(key);
|
||||||
|
if (cs == null) cs = c.createSection(key);
|
||||||
|
val res = cs.getValues(false).entrySet().stream().filter(e -> e.getValue() instanceof ConfigurationSection)
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, kv -> {
|
||||||
|
var conf = new IHaveConfig(getPlugin()::saveConfig);
|
||||||
|
conf.reset((ConfigurationSection) kv.getValue());
|
||||||
|
return conf;
|
||||||
|
}));
|
||||||
|
if (res.size() == 0) {
|
||||||
|
for (val entry : defaultProvider.entrySet()) {
|
||||||
|
val conf = new IHaveConfig(getPlugin()::saveConfig);
|
||||||
|
conf.reset(cs.createSection(entry.getKey()));
|
||||||
|
entry.getValue().accept(conf);
|
||||||
|
res.put(entry.getKey(), conf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getClassName() {
|
||||||
|
return getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package buttondevteam.lib.architecture;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface ComponentMetadata {
|
||||||
|
Class<? extends Component>[] depends() default {};
|
||||||
|
|
||||||
|
boolean enabledByDefault() default true;
|
||||||
|
}
|
|
@ -0,0 +1,252 @@
|
||||||
|
package buttondevteam.lib.architecture;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.ChromaUtils;
|
||||||
|
import lombok.*;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.Configuration;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.bukkit.scheduler.BukkitTask;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.BiFunction;
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
public class ConfigData<T> {
|
||||||
|
private static final HashMap<Configuration, SaveTask> saveTasks = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* May be null for testing
|
||||||
|
*/
|
||||||
|
private IHaveConfig config;
|
||||||
|
@Getter
|
||||||
|
@Setter(AccessLevel.PACKAGE)
|
||||||
|
private String path;
|
||||||
|
protected final T def;
|
||||||
|
private final Object primitiveDef;
|
||||||
|
/**
|
||||||
|
* The parameter is of a primitive type as returned by {@link YamlConfiguration#get(String)}
|
||||||
|
*/
|
||||||
|
private final Function<Object, T> getter;
|
||||||
|
/**
|
||||||
|
* The result should be a primitive type or string that can be retrieved correctly later
|
||||||
|
*/
|
||||||
|
private final Function<T, Object> setter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The config value should not change outside this instance
|
||||||
|
*/
|
||||||
|
private T value;
|
||||||
|
|
||||||
|
ConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
|
||||||
|
if (def == null) {
|
||||||
|
if (primitiveDef == null)
|
||||||
|
throw new IllegalArgumentException("Either def or primitiveDef must be set.");
|
||||||
|
if (getter == null)
|
||||||
|
throw new IllegalArgumentException("A getter and setter must be present when using primitiveDef.");
|
||||||
|
def = getter.apply(primitiveDef);
|
||||||
|
} else if (primitiveDef == null)
|
||||||
|
if (setter == null)
|
||||||
|
primitiveDef = def;
|
||||||
|
else
|
||||||
|
primitiveDef = setter.apply(def);
|
||||||
|
if ((getter == null) != (setter == null))
|
||||||
|
throw new IllegalArgumentException("Both setters and getters must be present (or none if def is primitive).");
|
||||||
|
this.config = config;
|
||||||
|
this.path = path;
|
||||||
|
this.def = def;
|
||||||
|
this.primitiveDef = primitiveDef;
|
||||||
|
this.getter = getter;
|
||||||
|
this.setter = setter;
|
||||||
|
get(); //Generate config automatically
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
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;
|
||||||
|
if ((def == null || this instanceof ReadOnlyConfigData) && config != null) //In Discord's case def may be null
|
||||||
|
setInternal(primitiveDef); //If read-only then we still need to save the default value so it can be set
|
||||||
|
else
|
||||||
|
set(def); //Save default value - def is always set
|
||||||
|
} else
|
||||||
|
val = config.get(path); //config==null: testing
|
||||||
|
if (val == null) //If it's set to null explicitly
|
||||||
|
val = primitiveDef;
|
||||||
|
BiFunction<Object, Object, Object> convert = (_val, _def) -> {
|
||||||
|
if (_def instanceof Number) //If we expect a number
|
||||||
|
if (_val instanceof Number)
|
||||||
|
_val = ChromaUtils.convertNumber((Number) _val,
|
||||||
|
(Class<? extends Number>) _def.getClass());
|
||||||
|
else
|
||||||
|
_val = _def; //If we didn't get a number, return default (which is a number)
|
||||||
|
else if (_val instanceof List && _def != null && _def.getClass().isArray())
|
||||||
|
_val = ((List<T>) _val).toArray((T[]) Array.newInstance(_def.getClass().getComponentType(), 0));
|
||||||
|
return _val;
|
||||||
|
};
|
||||||
|
if (getter != null) {
|
||||||
|
val = convert.apply(val, primitiveDef);
|
||||||
|
T hmm = getter.apply(val);
|
||||||
|
if (hmm == null) hmm = def; //Set if the getter returned null
|
||||||
|
return hmm;
|
||||||
|
}
|
||||||
|
val = convert.apply(val, def);
|
||||||
|
return value = (T) val; //Always cache, if not cached yet
|
||||||
|
}
|
||||||
|
|
||||||
|
public void set(T value) {
|
||||||
|
if (this instanceof ReadOnlyConfigData)
|
||||||
|
return; //Safety for Discord channel/role data
|
||||||
|
Object val;
|
||||||
|
if (setter != null && value != null)
|
||||||
|
val = setter.apply(value);
|
||||||
|
else val = value;
|
||||||
|
if (config.getConfig() != null)
|
||||||
|
setInternal(val);
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setInternal(Object val) {
|
||||||
|
config.getConfig().set(path, val);
|
||||||
|
signalChange(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void signalChange(IHaveConfig config) {
|
||||||
|
var cc = config.getConfig();
|
||||||
|
var sa = config.getSaveAction();
|
||||||
|
if (!saveTasks.containsKey(cc.getRoot())) {
|
||||||
|
synchronized (saveTasks) {
|
||||||
|
saveTasks.put(cc.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> {
|
||||||
|
synchronized (saveTasks) {
|
||||||
|
saveTasks.remove(cc.getRoot());
|
||||||
|
sa.run();
|
||||||
|
}
|
||||||
|
}, 100), sa));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
private static class SaveTask {
|
||||||
|
BukkitTask task;
|
||||||
|
Runnable saveAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean saveNow(Configuration config) {
|
||||||
|
synchronized (saveTasks) {
|
||||||
|
SaveTask st = saveTasks.get(config);
|
||||||
|
if (st != null) {
|
||||||
|
st.task.cancel();
|
||||||
|
saveTasks.remove(config);
|
||||||
|
st.saveAction.run();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 + ")";}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,256 @@
|
||||||
|
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;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A config system
|
||||||
|
*/
|
||||||
|
public final class IHaveConfig {
|
||||||
|
private final HashMap<String, ConfigData<?>> datamap = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* Returns the Bukkit ConfigurationSection. Use {@link #signalChange()} after changing it.
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
private ConfigurationSection config;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private Runnable saveAction;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* May be used in testing.
|
||||||
|
*
|
||||||
|
* @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null.
|
||||||
|
*/
|
||||||
|
public IHaveConfig(Runnable saveAction) {
|
||||||
|
this.saveAction = saveAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a config object for the given path. The def or primitiveDef must be set. If a getter is present, a setter must be present as well.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
* @param <T> The type of this variable (only use primitives or String)
|
||||||
|
* @return The data object that can be used to get or set the value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> ConfigData<T> getData(String path, T def) {
|
||||||
|
ConfigData<?> data = datamap.get(path);
|
||||||
|
if (data == null) datamap.put(path, data = new ConfigData<>(this, path, def, def, null, null));
|
||||||
|
return (ConfigData<T>) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method overload may be used with any class.
|
||||||
|
*
|
||||||
|
* @param path The path in config to use
|
||||||
|
* @param def The value to use by default
|
||||||
|
* @param getter A function that converts a primitive representation to the correct value
|
||||||
|
* @param setter A function that converts a value to a primitive representation
|
||||||
|
* @param <T> The type of this variable (can be any class)
|
||||||
|
* @return The data object that can be used to get or set the value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> ConfigData<T> getData(String path, T def, Function<Object, T> getter, Function<T, Object> setter) {
|
||||||
|
ConfigData<?> data = datamap.get(path);
|
||||||
|
if (data == null)
|
||||||
|
datamap.put(path, data = new ConfigData<>(this, path, def, setter.apply(def), getter, setter));
|
||||||
|
return (ConfigData<T>) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method overload may be used with any class. The given default value will be run through the getter.
|
||||||
|
*
|
||||||
|
* @param path The path in config to use
|
||||||
|
* @param primitiveDef The <b>primitive</b> value to use by default
|
||||||
|
* @param getter A function that converts a primitive representation to the correct value
|
||||||
|
* @param setter A function that converts a value to a primitive representation
|
||||||
|
* @param <T> The type of this variable (can be any class)
|
||||||
|
* @return The data object that can be used to get or set the value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> ConfigData<T> getDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
|
||||||
|
ConfigData<?> data = datamap.get(path);
|
||||||
|
if (data == null)
|
||||||
|
datamap.put(path, data = new ConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
|
||||||
|
return (ConfigData<T>) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method overload may be used with any class. The given default value will be run through the getter.
|
||||||
|
*
|
||||||
|
* @param path The path in config to use
|
||||||
|
* @param primitiveDef The <b>primitive</b> value to use by default
|
||||||
|
* @param getter A function that converts a primitive representation to the correct value
|
||||||
|
* @param setter A function that converts a value to a primitive representation
|
||||||
|
* @param <T> The type of this variable (can be any class)
|
||||||
|
* @return The data object that can be used to get or set the value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> ReadOnlyConfigData<T> getReadOnlyDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
|
||||||
|
ConfigData<?> data = datamap.get(path);
|
||||||
|
if (data == null)
|
||||||
|
datamap.put(path, data = new ReadOnlyConfigData<>(this, path, getter.apply(primitiveDef), primitiveDef, getter, setter));
|
||||||
|
return (ReadOnlyConfigData<T>) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method overload should only be used with primitves or String.
|
||||||
|
*
|
||||||
|
* @param path The path in config to use
|
||||||
|
* @param def The value to use by default
|
||||||
|
* @param <T> The type of this variable (only use primitives or String)
|
||||||
|
* @return The data object that can be used to get or set the value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> ConfigData<T> getData(String path, Supplier<T> def) {
|
||||||
|
ConfigData<?> data = datamap.get(path);
|
||||||
|
if (data == null) {
|
||||||
|
val defval = def.get();
|
||||||
|
datamap.put(path, data = new ConfigData<>(this, path, defval, defval, null, null));
|
||||||
|
}
|
||||||
|
return (ConfigData<T>) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method overload may be used with any class.
|
||||||
|
*
|
||||||
|
* @param path The path in config to use
|
||||||
|
* @param def The value to use by default
|
||||||
|
* @param getter A function that converts a primitive representation to the correct value
|
||||||
|
* @param setter A function that converts a value to a primitive representation
|
||||||
|
* @param <T> The type of this variable (can be any class)
|
||||||
|
* @return The data object that can be used to get or set the value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> ConfigData<T> getData(String path, Supplier<T> def, Function<Object, T> getter, Function<T, Object> setter) {
|
||||||
|
ConfigData<?> data = datamap.get(path);
|
||||||
|
if (data == null) {
|
||||||
|
val defval = def.get();
|
||||||
|
datamap.put(path, data = new ConfigData<>(this, path, defval, setter.apply(defval), getter, setter));
|
||||||
|
}
|
||||||
|
return (ConfigData<T>) data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method overload should only be used with primitves or String.
|
||||||
|
*
|
||||||
|
* @param path The path in config to use
|
||||||
|
* @param <T> The type of this variable (only use primitives or String)
|
||||||
|
* @return The data object that can be used to get or set the value
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> ListConfigData<T> getListData(String path) {
|
||||||
|
ConfigData<?> data = datamap.get(path);
|
||||||
|
if (data == null)
|
||||||
|
datamap.put(path, data = new ListConfigData<>(this, path, new ListConfigData.List<T>()));
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @param obj The object which has config methods
|
||||||
|
* @param configMap The result from {@link Component#getConfigMap(String, Map)}. May be null.
|
||||||
|
*/
|
||||||
|
public static void pregenConfig(Object obj, @Nullable Map<String, IHaveConfig> configMap) {
|
||||||
|
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;
|
||||||
|
if (m.getParameterCount() == 0) {
|
||||||
|
configList = Collections.singletonList((ConfigData<?>) m.invoke(obj));
|
||||||
|
} else if (m.getParameterCount() == 1 && m.getParameterTypes()[0] == IHaveConfig.class) {
|
||||||
|
if (configMap == null) continue; //Hope it will get called with the param later
|
||||||
|
configList = configMap.entrySet().stream().map(kv ->
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return (ConfigData<?>) m.invoke(obj, kv.getValue());
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!";
|
||||||
|
if (obj instanceof Component<?>)
|
||||||
|
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
|
||||||
|
else if (obj instanceof JavaPlugin)
|
||||||
|
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj);
|
||||||
|
else
|
||||||
|
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||||
|
} else {
|
||||||
|
if (TBMCCoreAPI.IsTestServer())
|
||||||
|
MainPlugin.Instance.getLogger().warning("Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes()));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (val c : configList) {
|
||||||
|
if (c.getPath().length() == 0)
|
||||||
|
c.setPath(mName);
|
||||||
|
else if (!c.getPath().equals(mName))
|
||||||
|
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + mName);
|
||||||
|
c.get(); //Saves the default value if needed - also checks validity
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
String msg = "Failed to pregenerate " + mName + " for " + obj + "!";
|
||||||
|
if (obj instanceof Component<?>)
|
||||||
|
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
|
||||||
|
else if (obj instanceof JavaPlugin)
|
||||||
|
TBMCCoreAPI.SendException(msg, e, (JavaPlugin) obj);
|
||||||
|
else
|
||||||
|
TBMCCoreAPI.SendException(msg, e, false, Bukkit.getLogger()::warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
package buttondevteam.lib.architecture;
|
||||||
|
|
||||||
|
import lombok.val;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
|
public class ListConfigData<T> extends ConfigData<ListConfigData.List<T>> {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
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);
|
||||||
|
def.listConfig = this; //Can't make the List class non-static or pass this in the super() constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class List<T> extends ArrayList<T> {
|
||||||
|
private ListConfigData<T> listConfig;
|
||||||
|
|
||||||
|
public List(@NotNull Collection<? extends T> c) {
|
||||||
|
super(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List() {
|
||||||
|
}
|
||||||
|
|
||||||
|
private void update() {
|
||||||
|
listConfig.set(this); //Update the config model and start save task if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T set(int index, T element) {
|
||||||
|
T ret = super.set(index, element);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean add(T t) {
|
||||||
|
val ret = super.add(t);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void add(int index, T element) {
|
||||||
|
super.add(index, element);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T remove(int index) {
|
||||||
|
T ret = super.remove(index);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(Object o) {
|
||||||
|
val ret = super.remove(o);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(Collection<? extends T> c) {
|
||||||
|
val ret = super.addAll(c);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addAll(int index, Collection<? extends T> c) {
|
||||||
|
val ret = super.addAll(index, c);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void removeRange(int fromIndex, int toIndex) {
|
||||||
|
super.removeRange(fromIndex, toIndex);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeAll(Collection<?> c) {
|
||||||
|
val ret = super.removeAll(c);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean retainAll(Collection<?> c) {
|
||||||
|
val ret = super.retainAll(c);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeIf(Predicate<? super T> filter) {
|
||||||
|
val ret = super.removeIf(filter);
|
||||||
|
update();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceAll(UnaryOperator<T> operator) {
|
||||||
|
super.replaceAll(operator);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sort(Comparator<? super T> c) {
|
||||||
|
super.sort(c);
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package buttondevteam.lib.architecture;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class ReadOnlyConfigData<T> extends ConfigData<T> {
|
||||||
|
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> setter) {
|
||||||
|
super(config, path, def, primitiveDef, getter, setter);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlyConfigData(IHaveConfig config, String path, T def, Object primitiveDef) {
|
||||||
|
super(config, path, def, primitiveDef, null, null);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package buttondevteam.lib.architecture.exceptions;
|
||||||
|
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
|
||||||
|
public class UnregisteredComponentException extends Exception {
|
||||||
|
|
||||||
|
public UnregisteredComponentException(Component component) {
|
||||||
|
super("The component '" + component.getClass().getSimpleName() + "' isn't registered!");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Getter
|
||||||
|
public class ChatMessage {
|
||||||
|
/**
|
||||||
|
* The sender which sends the message.
|
||||||
|
*/
|
||||||
|
private final CommandSender sender;
|
||||||
|
/**
|
||||||
|
* The Chroma user which sends the message.
|
||||||
|
*/
|
||||||
|
private final ChromaGamerBase user;
|
||||||
|
/**
|
||||||
|
* The message to send as the user.
|
||||||
|
*/
|
||||||
|
@Setter
|
||||||
|
private String message;
|
||||||
|
/**
|
||||||
|
* Indicates whether the message comes from running a command (like /tableflip). Implemented to be used from Discord.
|
||||||
|
*/
|
||||||
|
private boolean fromCommand;
|
||||||
|
/**
|
||||||
|
* The sender which we should check for permissions. Same as {@link #sender} by default.
|
||||||
|
*/
|
||||||
|
private CommandSender permCheck;
|
||||||
|
/**
|
||||||
|
* The origin of the message, "Minecraft" or "Discord" for example. May be displayed to the user.<br>
|
||||||
|
* <b>This is the user class capitalized folder name.</b>
|
||||||
|
*/
|
||||||
|
private final String origin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The sender which we should check for permissions. Same as {@link #sender} by default.
|
||||||
|
*
|
||||||
|
* @return The perm check or the sender
|
||||||
|
*/
|
||||||
|
public CommandSender getPermCheck() {
|
||||||
|
return permCheck == null ? sender : permCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ChatMessageBuilder builder() {
|
||||||
|
return new ChatMessageBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static ChatMessageBuilder builder(CommandSender sender, ChromaGamerBase user, String message) {
|
||||||
|
return builder().sender(sender).user(user).message(message).origin(user.getFolder().substring(0, 1).toUpperCase() + user.getFolder().substring(1));
|
||||||
|
}
|
||||||
|
}
|
30
Chroma-Core/src/main/java/buttondevteam/lib/chat/Color.java
Executable file
30
Chroma-Core/src/main/java/buttondevteam/lib/chat/Color.java
Executable file
|
@ -0,0 +1,30 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
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);
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final int red;
|
||||||
|
private final int green;
|
||||||
|
private final int blue;
|
||||||
|
}
|
440
Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java
Normal file
440
Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2.java
Normal file
|
@ -0,0 +1,440 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.ChromaUtils;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
|
import com.google.common.base.Defaults;
|
||||||
|
import com.google.common.primitives.Primitives;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.val;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The method name is the subcommand, use underlines (_) to add further subcommands.
|
||||||
|
* The args may be null if the conversion failed and it's optional.
|
||||||
|
*/
|
||||||
|
public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Sender> {
|
||||||
|
protected Command2() {
|
||||||
|
commandHelp.add("§6---- Commands ----");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parameters annotated with this receive all of the remaining arguments
|
||||||
|
*/
|
||||||
|
@Target(ElementType.PARAMETER)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface TextArg {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Methods annotated with this will be recognised as subcommands
|
||||||
|
*/
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Subcommand {
|
||||||
|
/**
|
||||||
|
* Allowed for OPs only by default
|
||||||
|
*/
|
||||||
|
String MOD_GROUP = "mod";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Help text to show players. A usage message will be also shown below it.
|
||||||
|
*/
|
||||||
|
String[] helpText() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 "";
|
||||||
|
|
||||||
|
String[] aliases() default {};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Target(ElementType.PARAMETER)
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface OptionalArg {
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
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> {
|
||||||
|
private final TreeSet<String> ht = new TreeSet<>();
|
||||||
|
private BukkitTask task;
|
||||||
|
|
||||||
|
public SubcommandHelpData(Method method, T command, String[] helpText) {
|
||||||
|
super(method, command, helpText);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSubcommand(String command) {
|
||||||
|
ht.add(command);
|
||||||
|
if (task == null)
|
||||||
|
task = Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> {
|
||||||
|
helpText = new String[ht.size() + 1]; //This will only run after the server is started List<E> list = new ArrayList<E>(size());
|
||||||
|
helpText[0] = "§6---- Subcommands ----"; //TODO: There may be more to the help text
|
||||||
|
int i = 1;
|
||||||
|
for (Iterator<String> iterator = ht.iterator();
|
||||||
|
iterator.hasNext() && i < helpText.length; i++) {
|
||||||
|
String e = iterator.next();
|
||||||
|
helpText[i] = e;
|
||||||
|
}
|
||||||
|
task = null; //Run again, if needed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
protected static class ParamConverter<T> {
|
||||||
|
public final Function<String, T> converter;
|
||||||
|
public final String errormsg;
|
||||||
|
public final Supplier<Iterable<String>> allSupplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
|
||||||
|
protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
|
||||||
|
|
||||||
|
private final ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
|
||||||
|
|
||||||
|
private char commandChar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a param converter that obtains a specific object from a string parameter.
|
||||||
|
* The converter may return null.
|
||||||
|
*
|
||||||
|
* @param <T> The type of the result
|
||||||
|
* @param cl The class of the result object
|
||||||
|
* @param converter The converter to use
|
||||||
|
* @param allSupplier The supplier of all possible values (ideally)
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
if (sd == null) continue;
|
||||||
|
boolean sync = Bukkit.isPrimaryThread();
|
||||||
|
Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> {
|
||||||
|
try {
|
||||||
|
handleCommandAsync(sender, commandline, sd, subcommand, sync);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Command execution failed for sender " + sender.getName() + "(" + sender.getClass().getCanonicalName() + ") and message " + commandline, e, MainPlugin.Instance);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true; //We found a method
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Needed because permission checking may load the (perhaps offline) sender's file which is disallowed on the main thread
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles a command asynchronously
|
||||||
|
*
|
||||||
|
* @param sender The command sender
|
||||||
|
* @param commandline The command line the sender sent
|
||||||
|
* @param sd The subcommand data
|
||||||
|
* @param subcommand The subcommand text
|
||||||
|
* @param sync Whether the command was originally sync
|
||||||
|
* @throws Exception If something's not right
|
||||||
|
*/
|
||||||
|
private void handleCommandAsync(TP sender, String commandline, SubcommandData<TC> sd, String subcommand, boolean sync) throws Exception {
|
||||||
|
if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands
|
||||||
|
sender.sendMessage(sd.helpText);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!hasPermission(sender, sd.command, sd.method)) {
|
||||||
|
sender.sendMessage("§cYou don't have permission to use this command");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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.");
|
||||||
|
sender.sendMessage(sd.helpText); //Send what the command is about, could be useful for commands like /member where some subcommands aren't player-only
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (paramArr[i1].isVarArgs()) {
|
||||||
|
params.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 {
|
||||||
|
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);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
sender.sendMessage("§c'" + param + "' is not a number.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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 + "'");
|
||||||
|
val cparam = conv.converter.apply(param);
|
||||||
|
if (cparam == null) {
|
||||||
|
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
params.add(cparam);
|
||||||
|
}
|
||||||
|
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
|
||||||
|
sender.sendMessage(sd.helpText);
|
||||||
|
} else if (ret != null)
|
||||||
|
throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
TBMCCoreAPI.SendException("An error occurred in a command handler for " + subcommand + "!", e.getCause(), MainPlugin.Instance);
|
||||||
|
} catch (Exception e) {
|
||||||
|
TBMCCoreAPI.SendException("Command handling failed for sender " + sender + " and subcommand " + subcommand, e, MainPlugin.Instance);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (sync)
|
||||||
|
Bukkit.getScheduler().runTask(MainPlugin.Instance, lol);
|
||||||
|
else
|
||||||
|
lol.run();
|
||||||
|
} //TODO: Add to the help
|
||||||
|
|
||||||
|
public abstract void registerCommand(TC command);
|
||||||
|
|
||||||
|
protected List<SubcommandData<TC>> registerCommand(TC command, char commandChar) {
|
||||||
|
return registerCommand(command, command.getCommandPath(), commandChar);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<SubcommandData<TC>> registerCommand(TC command, String path, @SuppressWarnings("SameParameterValue") char commandChar) {
|
||||||
|
this.commandChar = commandChar;
|
||||||
|
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
|
||||||
|
val scmdHelpList = new ArrayList<String>();
|
||||||
|
Method mainMethod = null;
|
||||||
|
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);
|
||||||
|
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)
|
||||||
|
ht[0] = "§6---- " + ht[0] + " ----";
|
||||||
|
scmdHelpList.addAll(Arrays.asList(ht));
|
||||||
|
if (!isSubcommand)
|
||||||
|
scmdHelpList.add("§6Subcommands:");
|
||||||
|
if (!commandHelp.contains(mainPath))
|
||||||
|
commandHelp.add(mainPath);
|
||||||
|
} catch (Exception 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) { //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'
|
||||||
|
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
|
||||||
|
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, String[] parameters) {
|
||||||
|
val str = method.getDeclaringClass().getResourceAsStream("/commands.yml");
|
||||||
|
if (str == null)
|
||||||
|
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().replace('$', '.'));
|
||||||
|
if (ccs != null) {
|
||||||
|
val cs = ccs.getConfigurationSection(method.getName());
|
||||||
|
if (cs != null) {
|
||||||
|
val mname = cs.getString("method");
|
||||||
|
val params = cs.getString("params");
|
||||||
|
//val goodname = method.getName() + "(" + Arrays.stream(method.getGenericParameterTypes()).map(cl -> cl.getTypeName()).collect(Collectors.joining(",")) + ")";
|
||||||
|
int i = mname.indexOf('('); //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful
|
||||||
|
if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) {
|
||||||
|
String[] both = Arrays.copyOf(ht, ht.length + 1);
|
||||||
|
both[ht.length] = "§6Usage:§r " + subcommand + " " + params;
|
||||||
|
ht = both;
|
||||||
|
var paramArray = params.split(" ");
|
||||||
|
for (int j = 0; j < paramArray.length && j < parameters.length; j++)
|
||||||
|
parameters[j] = paramArray[j];
|
||||||
|
} else
|
||||||
|
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance);
|
||||||
|
} else
|
||||||
|
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (cs is null)! Make sure to use 'clean install' when building the project.");
|
||||||
|
} else
|
||||||
|
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (ccs is null)! Make sure to use 'clean install' when building the project.");
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
return commandHelp.toArray(new String[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String[] getHelpText(String path) {
|
||||||
|
val scmd = subcommands.get(path);
|
||||||
|
if (scmd == null) return null;
|
||||||
|
return scmd.helpText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public Set<String> getAllSubcommands() {
|
||||||
|
return Collections.unmodifiableSet(subcommands.keySet());
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters all of the subcommands in the given command.
|
||||||
|
*
|
||||||
|
* @param command The command object
|
||||||
|
*/
|
||||||
|
public void unregisterCommand(ICommand2<TP> command) {
|
||||||
|
var path = command.getCommandPath();
|
||||||
|
for (val method : command.getClass().getMethods()) {
|
||||||
|
val ann = method.getAnnotation(Subcommand.class);
|
||||||
|
if (ann == null) continue;
|
||||||
|
unregisterCommand(path, method.getName(), ann);
|
||||||
|
for (String p : command.getCommandPaths())
|
||||||
|
unregisterCommand(p, method.getName(), ann);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void unregisterCommand(String path, String methodName, Subcommand ann) {
|
||||||
|
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
|
||||||
|
subcommands.remove(subcommand);
|
||||||
|
for (String alias : ann.aliases())
|
||||||
|
subcommands.remove(commandChar + path + alias);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It will start with the given replace char.
|
||||||
|
*
|
||||||
|
* @param methodName The method's name, method.getName()
|
||||||
|
* @param replaceChar The character to use between subcommands
|
||||||
|
* @return The command path starting with the replace char.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public String getCommandPath(String methodName, char replaceChar) {
|
||||||
|
return methodName.equals("def") ? "" : replaceChar + methodName.replace('_', replaceChar).toLowerCase();
|
||||||
|
}
|
||||||
|
}
|
482
Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java
Normal file
482
Chroma-Core/src/main/java/buttondevteam/lib/chat/Command2MC.java
Normal file
|
@ -0,0 +1,482 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase;
|
||||||
|
import com.mojang.brigadier.arguments.*;
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
|
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
|
||||||
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
|
import lombok.val;
|
||||||
|
import me.lucko.commodore.Commodore;
|
||||||
|
import me.lucko.commodore.CommodoreProvider;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
import org.bukkit.command.*;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.permissions.Permission;
|
||||||
|
import org.bukkit.permissions.PermissionDefault;
|
||||||
|
import org.javatuples.Triplet;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Parameter;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.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 {
|
||||||
|
/**
|
||||||
|
* Don't use directly, use the method in Component and ButtonPlugin to automatically unregister the command when needed.
|
||||||
|
*
|
||||||
|
* @param command The command to register
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void registerCommand(ICommand2MC command) {
|
||||||
|
/*String mainpath;
|
||||||
|
var plugin = command.getPlugin();
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
PermissionDefault.TRUE)); //Allow commands by default, it will check mod-only
|
||||||
|
for (val method : command.getClass().getMethods()) {
|
||||||
|
if (!method.isAnnotationPresent(Subcommand.class)) continue;
|
||||||
|
var path = getCommandPath(method.getName(), '.');
|
||||||
|
if (path.length() > 0) {
|
||||||
|
var subperm = perm + path;
|
||||||
|
if (Bukkit.getPluginManager().getPermission(subperm) == null) //Check needed for plugin reset
|
||||||
|
Bukkit.getPluginManager().addPermission(new Permission(subperm,
|
||||||
|
PermissionDefault.TRUE)); //Allow commands by default, it will check mod-only
|
||||||
|
}
|
||||||
|
String pg = permGroup(command, method);
|
||||||
|
if (pg.length() == 0) continue;
|
||||||
|
String permGroup = "chroma." + pg;
|
||||||
|
if (Bukkit.getPluginManager().getPermission(permGroup) == null) //It may occur multiple times
|
||||||
|
Bukkit.getPluginManager().addPermission(new Permission(permGroup,
|
||||||
|
PermissionDefault.OP)); //Do not allow any commands that belong to a group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPermission(Command2MCSender sender, ICommand2MC command, Method method) {
|
||||||
|
return hasPermission(sender.getSender(), command, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPermission(CommandSender sender, ICommand2MC command, Method method) {
|
||||||
|
if (sender instanceof ConsoleCommandSender) return true; //Always allow the console
|
||||||
|
if (command == null) return true; //Allow viewing the command - it doesn't do anything anyway
|
||||||
|
String pg;
|
||||||
|
boolean p = true;
|
||||||
|
var cmdperm = "chroma.command." + command.getCommandPath().replace(' ', '.');
|
||||||
|
var path = getCommandPath(method.getName(), '.');
|
||||||
|
String[] perms = {
|
||||||
|
path.length() > 0 ? cmdperm + path : null,
|
||||||
|
cmdperm,
|
||||||
|
(pg = permGroup(command, method)).length() > 0 ? "chroma." + pg : null
|
||||||
|
};
|
||||||
|
for (String perm : perms) {
|
||||||
|
if (perm != null) {
|
||||||
|
if (p) { //Use OfflinePlayer to avoid fetching player data
|
||||||
|
if (sender instanceof OfflinePlayer)
|
||||||
|
p = MainPlugin.permission.playerHas(sender instanceof Player ? ((Player) sender).getLocation().getWorld().getName() : null, (OfflinePlayer) sender, perm);
|
||||||
|
else
|
||||||
|
p = false; //Use sender's method
|
||||||
|
if (!p) p = sender.hasPermission(perm);
|
||||||
|
} else break; //If any of the permissions aren't granted then don't allow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first group found in the hierarchy starting from the command method <b>or</b> the mod group if <i>any</i></i> of the superclasses are mod only.
|
||||||
|
*
|
||||||
|
* @param method The subcommand to check
|
||||||
|
* @return The permission group for the subcommand or empty string
|
||||||
|
*/
|
||||||
|
private String permGroup(ICommand2MC command, Method method) {
|
||||||
|
if (method != null) {
|
||||||
|
val sc = method.getAnnotation(Subcommand.class);
|
||||||
|
if (sc != null && sc.permGroup().length() > 0) {
|
||||||
|
return sc.permGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (getAnnForValue(command.getClass(), CommandClass.class, CommandClass::modOnly, false))
|
||||||
|
return Subcommand.MOD_GROUP;
|
||||||
|
return getAnnForValue(command.getClass(), CommandClass.class, CommandClass::permGroup, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loops until it finds a value that is <b>not</b> the same as def
|
||||||
|
*
|
||||||
|
* @param sourceCl The class which has the annotation
|
||||||
|
* @param annCl The annotation to get
|
||||||
|
* @param annMethod The annotation method to check
|
||||||
|
* @param def The value to ignore when looking for the result
|
||||||
|
* @param <T> The annotation type
|
||||||
|
* @param <V> The type of the value
|
||||||
|
* @return The value returned by the first superclass or def
|
||||||
|
*/
|
||||||
|
private <T extends Annotation, V> V getAnnForValue(Class<?> sourceCl, Class<T> annCl, Function<T, V> annMethod, V def) {
|
||||||
|
for (Class<?> cl = sourceCl; cl != null; cl = cl.getSuperclass()) {
|
||||||
|
val cc = cl.getAnnotation(annCl);
|
||||||
|
V r;
|
||||||
|
if (cc != null && (r = annMethod.apply(cc)) != def) return r;
|
||||||
|
}
|
||||||
|
return def;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Automatically colors the message red.
|
||||||
|
* {@see super#addParamConverter}
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg, Supplier<Iterable<String>> allSupplier) {
|
||||||
|
super.addParamConverter(cl, converter, "§c" + errormsg, allSupplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterCommands(ButtonPlugin plugin) {
|
||||||
|
/*var cmds = subcommands.values().stream().map(sd -> sd.command).filter(cmd -> plugin.equals(cmd.getPlugin())).toArray(ICommand2MC[]::new);
|
||||||
|
for (var cmd : cmds)
|
||||||
|
unregisterCommand(cmd);*/
|
||||||
|
subcommands.values().removeIf(sd -> Optional.ofNullable(sd.command).map(ICommand2MC::getPlugin).map(plugin::equals).orElse(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterCommands(Component<?> component) {
|
||||||
|
/*var cmds = subcommands.values().stream().map(sd -> sd.command).filter(cmd -> component.equals(cmd.getComponent())).toArray(ICommand2MC[]::new);
|
||||||
|
for (var cmd : cmds)
|
||||||
|
unregisterCommand(cmd);*/
|
||||||
|
subcommands.values().removeIf(sd -> Optional.ofNullable(sd.command).map(ICommand2MC::getComponent)
|
||||||
|
.map(comp -> component.getClass().getSimpleName().equals(comp.getClass().getSimpleName())).orElse(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@EventHandler
|
||||||
|
public void onTabComplete(TabCompleteEvent event) {
|
||||||
|
try {
|
||||||
|
event.getCompletions().clear(); //Remove player names
|
||||||
|
} catch (UnsupportedOperationException e) {
|
||||||
|
//System.out.println("Tabcomplete: " + event.getBuffer());
|
||||||
|
//System.out.println("First completion: " + event.getCompletions().stream().findFirst().orElse("no completions"));
|
||||||
|
//System.out.println("Listeners: " + Arrays.toString(event.getHandlers().getRegisteredListeners()));
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleCommand(Command2MCSender sender, String commandline) {
|
||||||
|
return handleCommand(sender, commandline, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleCommand(Command2MCSender sender, String commandline, boolean checkPlugin) {
|
||||||
|
int i = commandline.indexOf(' ');
|
||||||
|
String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash
|
||||||
|
PluginCommand pcmd;
|
||||||
|
if (!checkPlugin
|
||||||
|
|| MainPlugin.Instance.prioritizeCustomCommands.get()
|
||||||
|
|| (pcmd = Bukkit.getPluginCommand(mainpath)) == null //Our commands aren't PluginCommands
|
||||||
|
|| pcmd.getPlugin() instanceof ButtonPlugin) //Unless it's specified in the plugin.yml
|
||||||
|
return super.handleCommand(sender, commandline);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldRegisterOfficially = true;
|
||||||
|
|
||||||
|
private Command registerOfficially(ICommand2MC command, List<SubcommandData<ICommand2MC>> subcmds) {
|
||||||
|
if (!shouldRegisterOfficially || command.getPlugin() == null) return null;
|
||||||
|
try {
|
||||||
|
var cmdmap = (SimpleCommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer());
|
||||||
|
var path = command.getCommandPath();
|
||||||
|
int x = path.indexOf(' ');
|
||||||
|
var mainPath = path.substring(0, x == -1 ? path.length() : x);
|
||||||
|
Command bukkitCommand;
|
||||||
|
{ //Commands conflicting with Essentials have to be registered in plugin.yml
|
||||||
|
var oldcmd = cmdmap.getCommand(command.getPlugin().getName() + ":" + mainPath); //The label with the fallback prefix is always registered
|
||||||
|
if (oldcmd == null) {
|
||||||
|
bukkitCommand = new BukkitCommand(mainPath);
|
||||||
|
cmdmap.register(command.getPlugin().getName(), bukkitCommand);
|
||||||
|
} else {
|
||||||
|
bukkitCommand = oldcmd;
|
||||||
|
if (bukkitCommand instanceof PluginCommand)
|
||||||
|
((PluginCommand) bukkitCommand).setExecutor(this::executeCommand);
|
||||||
|
}
|
||||||
|
bukkitCommand = oldcmd == null ? new BukkitCommand(mainPath) : oldcmd;
|
||||||
|
}
|
||||||
|
if (CommodoreProvider.isSupported())
|
||||||
|
TabcompleteHelper.registerTabcomplete(command, subcmds, bukkitCommand);
|
||||||
|
return bukkitCommand;
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (command.getComponent() == null)
|
||||||
|
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getPlugin());
|
||||||
|
else
|
||||||
|
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getComponent());
|
||||||
|
shouldRegisterOfficially = false;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean executeCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||||
|
var user = ChromaGamerBase.getFromSender(sender);
|
||||||
|
if (user == null) {
|
||||||
|
TBMCCoreAPI.SendException("Failed to run Bukkit command for user!", new Throwable("No Chroma user found"), MainPlugin.Instance);
|
||||||
|
sender.sendMessage("§cAn internal error occurred.");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
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) {
|
||||||
|
sender.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String[] message) {
|
||||||
|
sender.sendMessage(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return sender.getName();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
public interface Command2Sender { //We don't need the 'extras' of CommandSender on Discord
|
||||||
|
void sendMessage(String message);
|
||||||
|
|
||||||
|
void sendMessage(String[] message);
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
}
|
53
Chroma-Core/src/main/java/buttondevteam/lib/chat/CommandClass.java
Executable file
53
Chroma-Core/src/main/java/buttondevteam/lib/chat/CommandClass.java
Executable file
|
@ -0,0 +1,53 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <b>Abstract classes with no {@link CommandClass} annotations will be ignored.</b> Classes that are not abstract or have the annotation will be included in the command path unless
|
||||||
|
* {@link #excludeFromPath()} is true.<br>
|
||||||
|
* <i>All commands with no modOnly set will <u>not be mod only</u></i>
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
public @interface CommandClass {
|
||||||
|
/**
|
||||||
|
* Determines whether the command can only be used by mods and above or regular players can use it as well.<br>
|
||||||
|
* <b>If not set, the command will <u>not</u> be mod only</b>
|
||||||
|
*
|
||||||
|
* @return If the command is mod only
|
||||||
|
*/
|
||||||
|
boolean modOnly() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command's path, or name if top-level command.<br>
|
||||||
|
* For example:<br>
|
||||||
|
* "u admin updateplugin" or "u" for the top level one<br>
|
||||||
|
* <u>The path must be lowercase!</u><br>
|
||||||
|
*
|
||||||
|
* @return The command path, <i>which is the command class name by default</i> (removing any "command" from it)
|
||||||
|
*/
|
||||||
|
String path() default "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exclude this class from the path. Useful if more commands share some property but aren't subcommands of a common command. See {@link CommandClass} for more details.
|
||||||
|
*/
|
||||||
|
boolean excludeFromPath() default false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The help text to show for the players. A usage message will be also shown below it.<br>
|
||||||
|
* <b>The fist line will be converted to a header.</b>
|
||||||
|
*
|
||||||
|
* @return The help text
|
||||||
|
*/
|
||||||
|
String[] helpText() default {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main permission which allows using this command (individual access can be still granted with "chroma.command.X").
|
||||||
|
* Used to be "tbmc.admin"
|
||||||
|
*/
|
||||||
|
String permGroup() default ""; //TODO: A single annotation instead of these two
|
||||||
|
}
|
|
@ -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();
|
||||||
|
}
|
|
@ -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}<{@link String}> 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;
|
||||||
|
}
|
102
Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java
Normal file
102
Chroma-Core/src/main/java/buttondevteam/lib/chat/ICommand2.java
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.val;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
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
|
||||||
|
* @return The success of the command
|
||||||
|
*/
|
||||||
|
public boolean def(TP sender) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method. Return with this.
|
||||||
|
*
|
||||||
|
* @param sender The sender of the command
|
||||||
|
* @param message The message to send to the sender
|
||||||
|
* @return Always true so that the usage isn't shown
|
||||||
|
*/
|
||||||
|
protected boolean respond(TP sender, String message) {
|
||||||
|
sender.sendMessage(message);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return null to not add any help text, return an empty array to only print subcommands.<br>
|
||||||
|
* By default, returns null if the Subcommand annotation is not present and returns an empty array if no help text can be found.
|
||||||
|
*
|
||||||
|
* @param method The method of the subcommand
|
||||||
|
* @return The help text, empty array or null
|
||||||
|
*/
|
||||||
|
public String[] getHelpText(Method method, Command2.Subcommand ann) {
|
||||||
|
val cc = getClass().getAnnotation(CommandClass.class);
|
||||||
|
return ann.helpText().length != 0 || cc == null ? ann.helpText() : cc.helpText(); //If cc is null then it's empty array
|
||||||
|
}
|
||||||
|
|
||||||
|
private final String path;
|
||||||
|
@Getter
|
||||||
|
private final Command2<?, TP> manager; //TIL that if I use a raw type on a variable then none of the type args will work (including what's defined on a method, not on the type)
|
||||||
|
|
||||||
|
public <T extends ICommand2<TP>> ICommand2(Command2<T, TP> manager) {
|
||||||
|
path = getcmdpath();
|
||||||
|
this.manager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The command's path, or name if top-level command.<br>
|
||||||
|
* For example:<br>
|
||||||
|
* "u admin updateplugin" or "u" for the top level one<br>
|
||||||
|
* <u>The path must be lowercase!</u><br>
|
||||||
|
*
|
||||||
|
* @return The command path, <i>which is the command class name by default</i> (removing any "command" from it) - Change via the {@link CommandClass} annotation
|
||||||
|
*/
|
||||||
|
public String getCommandPath() {
|
||||||
|
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(
|
||||||
|
"No @CommandClass annotation on command class " + getClass().getSimpleName() + "!");
|
||||||
|
Function<Class<?>, String> getFromClass = cl -> cl.getSimpleName().toLowerCase().replace("commandbase", "") // <-- ...
|
||||||
|
.replace("command", "");
|
||||||
|
String path = getClass().getAnnotation(CommandClass.class).path(),
|
||||||
|
prevpath = path = path.length() == 0 ? getFromClass.apply(getClass()) : path;
|
||||||
|
for (Class<?> cl = getClass().getSuperclass(); cl != null
|
||||||
|
&& !cl.getPackage().getName().equals(ICommand2MC.class.getPackage().getName()); cl = cl
|
||||||
|
.getSuperclass()) { //
|
||||||
|
String newpath;
|
||||||
|
if (!cl.isAnnotationPresent(CommandClass.class)
|
||||||
|
|| (newpath = cl.getAnnotation(CommandClass.class).path()).length() == 0
|
||||||
|
|| newpath.equals(prevpath)) {
|
||||||
|
if ((Modifier.isAbstract(cl.getModifiers()) && !cl.isAnnotationPresent(CommandClass.class))
|
||||||
|
|| cl.getAnnotation(CommandClass.class).excludeFromPath()) // <--
|
||||||
|
continue;
|
||||||
|
newpath = getFromClass.apply(cl);
|
||||||
|
}
|
||||||
|
path = (prevpath = newpath) + " " + path;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import buttondevteam.lib.architecture.ButtonPlugin;
|
||||||
|
import buttondevteam.lib.architecture.Component;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@SuppressWarnings("JavadocReference")
|
||||||
|
public abstract class ICommand2MC extends ICommand2<Command2MCSender> {
|
||||||
|
@Getter
|
||||||
|
private ButtonPlugin plugin;
|
||||||
|
@Getter
|
||||||
|
@Nullable
|
||||||
|
private Component<?> component;
|
||||||
|
|
||||||
|
public ICommand2MC() {
|
||||||
|
super(ButtonPlugin.getCommand2MC());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from {@link buttondevteam.lib.architecture.Component#registerCommand(ICommand2MC)} and {@link ButtonPlugin#registerCommand(ICommand2MC)}
|
||||||
|
*/
|
||||||
|
public void registerToPlugin(ButtonPlugin plugin) {
|
||||||
|
if (this.plugin == null)
|
||||||
|
this.plugin = plugin;
|
||||||
|
else
|
||||||
|
throw new IllegalStateException("The command is already assigned to a plugin!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from {@link buttondevteam.lib.architecture.Component#registerCommand(ICommand2MC)}
|
||||||
|
*/
|
||||||
|
public void registerToComponent(Component<?> component) {
|
||||||
|
if (this.component == null)
|
||||||
|
this.component = component;
|
||||||
|
else
|
||||||
|
throw new IllegalStateException("The command is already assigned to a component!");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@Override
|
||||||
|
public <TX extends ICommand2<Command2MCSender>> void onRegister(Command2<TX, Command2MCSender> manager) {
|
||||||
|
super.onRegister(manager);
|
||||||
|
onRegister((Command2MC) manager); //If ICommand2 is inherited with the same type arg, this would fail but I don't want to add another type param to ICommand2
|
||||||
|
} //For example: class IOffender extends ICommand2<Command2MCSender>*/
|
||||||
|
}
|
12
Chroma-Core/src/main/java/buttondevteam/lib/chat/IFakePlayer.java
Executable file
12
Chroma-Core/src/main/java/buttondevteam/lib/chat/IFakePlayer.java
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DiscordSenderBase implements it
|
||||||
|
* @author Norbi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface IFakePlayer extends CommandSender {
|
||||||
|
|
||||||
|
}
|
14
Chroma-Core/src/main/java/buttondevteam/lib/chat/Priority.java
Executable file
14
Chroma-Core/src/main/java/buttondevteam/lib/chat/Priority.java
Executable file
|
@ -0,0 +1,14 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
public enum Priority {
|
||||||
|
Low(0), Normal(1), High(2);
|
||||||
|
private final int val;
|
||||||
|
|
||||||
|
Priority(int v) {
|
||||||
|
val = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetValue() {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
97
Chroma-Core/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java
Executable file
97
Chroma-Core/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java
Executable file
|
@ -0,0 +1,97 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import buttondevteam.core.component.channel.Channel.RecipientTestResult;
|
||||||
|
import buttondevteam.lib.ChromaUtils;
|
||||||
|
import buttondevteam.lib.TBMCChatEvent;
|
||||||
|
import buttondevteam.lib.TBMCChatPreprocessEvent;
|
||||||
|
import buttondevteam.lib.TBMCSystemChatEvent;
|
||||||
|
import lombok.val;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class TBMCChatAPI {
|
||||||
|
/**
|
||||||
|
* Sends a chat message to Minecraft. Make sure that the channel is registered with {@link #RegisterChatChannel(Channel)}.<br>
|
||||||
|
* This will also send the error message to the sender, if they can't send the message.
|
||||||
|
*
|
||||||
|
* @param cm The message to send
|
||||||
|
* @return The event cancelled state
|
||||||
|
*/
|
||||||
|
public static boolean SendChatMessage(ChatMessage cm) {
|
||||||
|
return SendChatMessage(cm, cm.getUser().channel.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a chat message to Minecraft. Make sure that the channel is registered with {@link #RegisterChatChannel(Channel)}.<br>
|
||||||
|
* This will also send the error message to the sender, if they can't send the message.
|
||||||
|
*
|
||||||
|
* @param cm The message to send
|
||||||
|
* @param channel The MC channel to send in
|
||||||
|
* @return The event cancelled state
|
||||||
|
*/
|
||||||
|
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!");
|
||||||
|
return true; //Cancel sending if channel is disabled
|
||||||
|
}
|
||||||
|
Supplier<Boolean> task = () -> {
|
||||||
|
val permcheck = cm.getPermCheck();
|
||||||
|
RecipientTestResult rtr = getScoreOrSendError(channel, permcheck);
|
||||||
|
int score = rtr.score;
|
||||||
|
if (score == Channel.SCORE_SEND_NOPE || rtr.groupID == null)
|
||||||
|
return true;
|
||||||
|
TBMCChatPreprocessEvent eventPre = new TBMCChatPreprocessEvent(cm.getSender(), channel, cm.getMessage());
|
||||||
|
Bukkit.getPluginManager().callEvent(eventPre);
|
||||||
|
if (eventPre.isCancelled())
|
||||||
|
return true;
|
||||||
|
cm.setMessage(eventPre.getMessage());
|
||||||
|
TBMCChatEvent event;
|
||||||
|
event = new TBMCChatEvent(channel, cm, rtr);
|
||||||
|
Bukkit.getPluginManager().callEvent(event);
|
||||||
|
return event.isCancelled();
|
||||||
|
};
|
||||||
|
return ChromaUtils.doItAsync(task, false); //Not cancelled if async
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a regular message to Minecraft. Make sure that the channel is registered with {@link #RegisterChatChannel(Channel)}.
|
||||||
|
*
|
||||||
|
* @param channel The channel to send to
|
||||||
|
* @param rtr The score&group to use to find the group - use {@link RecipientTestResult#ALL} if the channel doesn't have scores
|
||||||
|
* @param message The message to send
|
||||||
|
* @param exceptions Platforms where this message shouldn't be sent (same as {@link ChatMessage#getOrigin()}
|
||||||
|
* @return The event cancelled state
|
||||||
|
*/
|
||||||
|
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())
|
||||||
|
return true; //Cancel sending
|
||||||
|
if (!Arrays.asList(exceptions).contains("Minecraft"))
|
||||||
|
Bukkit.getConsoleSender().sendMessage("[" + channel.DisplayName.get() + "] " + message);
|
||||||
|
TBMCSystemChatEvent event = new TBMCSystemChatEvent(channel, message, rtr.score, rtr.groupID, exceptions, target);
|
||||||
|
return ChromaUtils.callEventAsync(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RecipientTestResult getScoreOrSendError(Channel channel, CommandSender sender) {
|
||||||
|
RecipientTestResult result = channel.getRTR(sender);
|
||||||
|
if (result.errormessage != null)
|
||||||
|
sender.sendMessage("§c" + result.errormessage);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a chat channel. See {@link Channel#Channel(String, Color, String, java.util.function.Function)} for details.
|
||||||
|
*
|
||||||
|
* @param channel A new {@link Channel} to register
|
||||||
|
*/
|
||||||
|
public static void RegisterChatChannel(Channel channel) {
|
||||||
|
Channel.RegisterChannel(channel);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package buttondevteam.lib.chat;
|
||||||
|
|
||||||
|
public interface TellrawSerializableEnum {
|
||||||
|
String getName();
|
||||||
|
}
|
24
Chroma-Core/src/main/java/buttondevteam/lib/player/AbstractUserClass.java
Executable file
24
Chroma-Core/src/main/java/buttondevteam/lib/player/AbstractUserClass.java
Executable file
|
@ -0,0 +1,24 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a {@link ChromaGamerBase} direct subclass which's abstract. For Minecraft data, use {@link PlayerClass}
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
public @interface AbstractUserClass {
|
||||||
|
/**
|
||||||
|
* Indicates which folder should the player files be saved in.
|
||||||
|
*/
|
||||||
|
String foldername();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the class to create when connecting accounts.
|
||||||
|
*/
|
||||||
|
Class<? extends ChromaGamerBase> prototype();
|
||||||
|
}
|
316
Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java
Executable file
316
Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java
Executable file
|
@ -0,0 +1,316 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import buttondevteam.core.MainPlugin;
|
||||||
|
import buttondevteam.core.component.channel.Channel;
|
||||||
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.lib.architecture.ConfigData;
|
||||||
|
import buttondevteam.lib.architecture.IHaveConfig;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.val;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.configuration.file.YamlConfiguration;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@ChromaGamerEnforcer
|
||||||
|
public abstract class ChromaGamerBase {
|
||||||
|
private static final String TBMC_PLAYERS_DIR = "TBMC/players/";
|
||||||
|
private static final ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
|
||||||
|
/**
|
||||||
|
* Holds data per user class
|
||||||
|
*/
|
||||||
|
private static final HashMap<Class<? extends ChromaGamerBase>, StaticUserData<?>> staticDataMap = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use {@link #getConfig()} where possible; the 'id' must be always set
|
||||||
|
*/
|
||||||
|
//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)
|
||||||
|
* @return The folder name for the given type
|
||||||
|
* @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))
|
||||||
|
return cl.getAnnotation(UserClass.class).foldername();
|
||||||
|
else if (cl.isAnnotationPresent(AbstractUserClass.class))
|
||||||
|
return cl.getAnnotation(AbstractUserClass.class).foldername();
|
||||||
|
throw new RuntimeException("Class not registered as a user class! Use @UserClass or @AbstractUserClass");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the player class for the given folder name.
|
||||||
|
*
|
||||||
|
* @param foldername The folder to get the class from (like "minecraft")
|
||||||
|
* @return The type for the given folder name or null if not found
|
||||||
|
*/
|
||||||
|
public static Class<? extends ChromaGamerBase> getTypeForFolder(String foldername) {
|
||||||
|
synchronized (staticDataMap) {
|
||||||
|
return staticDataMap.entrySet().stream().filter(e -> e.getValue().getFolder().equalsIgnoreCase(foldername))
|
||||||
|
.map(Map.Entry::getKey).findAny().orElse(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* Retrieves a user from cache or loads it from disk.
|
||||||
|
*
|
||||||
|
* @param fname Filename without .yml, the user's identifier for that type
|
||||||
|
* @param cl User class
|
||||||
|
* @return The user object
|
||||||
|
*/
|
||||||
|
public static synchronized <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
|
||||||
|
StaticUserData<?> staticUserData = null;
|
||||||
|
for (var sud : staticDataMap.entrySet()) {
|
||||||
|
if (sud.getKey().isAssignableFrom(cl)) {
|
||||||
|
staticUserData = sud.getValue();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (staticUserData == null)
|
||||||
|
throw new RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass");
|
||||||
|
var commonUserData = staticUserData.getUserDataMap().get(fname);
|
||||||
|
if (commonUserData == null) {
|
||||||
|
final String folder = staticUserData.getFolder();
|
||||||
|
final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml");
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
var playerData = YamlConfiguration.loadConfiguration(file);
|
||||||
|
commonUserData = new CommonUserData<>(playerData);
|
||||||
|
playerData.set(staticUserData.getFolder() + "_id", fname);
|
||||||
|
staticUserData.getUserDataMap().put(fname, commonUserData);
|
||||||
|
}
|
||||||
|
if (commonUserData.getUserCache().containsKey(cl))
|
||||||
|
return (T) commonUserData.getUserCache().get(cl);
|
||||||
|
T obj;
|
||||||
|
if (staticUserData.getConstructors().containsKey(cl))
|
||||||
|
//noinspection unchecked
|
||||||
|
obj = (T) staticUserData.getConstructors().get(cl).get();
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
obj = cl.getConstructor().newInstance();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Failed to create new instance of user of type " + cl.getSimpleName() + "!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
obj.commonUserData = commonUserData;
|
||||||
|
obj.init();
|
||||||
|
obj.scheduleUncache();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a converter to the start of the list.
|
||||||
|
*
|
||||||
|
* @param converter The converter that returns an object corresponding to the sender or null, if it's not the right type.
|
||||||
|
*/
|
||||||
|
public static <T extends ChromaGamerBase> void addConverter(Function<CommandSender, Optional<T>> converter) {
|
||||||
|
senderConverters.add(0, converter);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get from the given sender. the object's type will depend on the sender's type. May be null, but shouldn't be.
|
||||||
|
*
|
||||||
|
* @param sender The sender to use
|
||||||
|
* @return A user as returned by a converter or null if none can supply it
|
||||||
|
*/
|
||||||
|
public static ChromaGamerBase getFromSender(CommandSender sender) {
|
||||||
|
for (val converter : senderConverters) {
|
||||||
|
val ocg = converter.apply(sender);
|
||||||
|
if (ocg.isPresent())
|
||||||
|
return ocg.get();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void saveUsers() {
|
||||||
|
synchronized (staticDataMap) {
|
||||||
|
for (var sud : staticDataMap.values())
|
||||||
|
for (var cud : sud.getUserDataMap().values())
|
||||||
|
ConfigData.saveNow(cud.getPlayerData()); //Calls save()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void init() {
|
||||||
|
config.reset(commonUserData.getPlayerData());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the player. It'll handle all exceptions that may happen. Called automatically.
|
||||||
|
*/
|
||||||
|
protected void save() {
|
||||||
|
try {
|
||||||
|
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, MainPlugin.Instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the user from the cache. This will be called automatically after some time by default.
|
||||||
|
*/
|
||||||
|
public void uncache() {
|
||||||
|
final var userCache = commonUserData.getUserCache();
|
||||||
|
//noinspection SynchronizationOnLocalVariableOrMethodParameter
|
||||||
|
synchronized (userCache) {
|
||||||
|
if (userCache.containsKey(getClass()))
|
||||||
|
if (userCache.remove(getClass()) != this)
|
||||||
|
throw new IllegalStateException("A different player instance was cached!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void scheduleUncache() {
|
||||||
|
Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, this::uncache, 2 * 60 * 60 * 20); //2 hours
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set.
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
final String ownFolder = getFolder();
|
||||||
|
final String userFolder = user.getFolder();
|
||||||
|
if (ownFolder.equalsIgnoreCase(userFolder))
|
||||||
|
throw new RuntimeException("Do not connect two accounts of the same type! Type: " + ownFolder);
|
||||||
|
var ownData = commonUserData.getPlayerData();
|
||||||
|
var userData = user.commonUserData.getPlayerData();
|
||||||
|
userData.set(ownFolder + "_id", ownData.getString(ownFolder + "_id"));
|
||||||
|
ownData.set(userFolder + "_id", userData.getString(userFolder + "_id"));
|
||||||
|
config.signalChange();
|
||||||
|
user.config.signalChange();
|
||||||
|
Consumer<YamlConfiguration> sync = sourcedata -> {
|
||||||
|
final String sourcefolder = sourcedata == ownData ? ownFolder : userFolder;
|
||||||
|
final String id = sourcedata.getString(sourcefolder + "_id");
|
||||||
|
for (val entry : staticDataMap.entrySet()) { // Set our ID in all files we can find, both from our connections and the new ones
|
||||||
|
if (entry.getKey() == getClass() || entry.getKey() == user.getClass())
|
||||||
|
continue;
|
||||||
|
var entryFolder = entry.getValue().getFolder();
|
||||||
|
final String otherid = sourcedata.getString(entryFolder + "_id");
|
||||||
|
if (otherid == null)
|
||||||
|
continue;
|
||||||
|
ChromaGamerBase cg = getUser(otherid, entry.getKey());
|
||||||
|
var cgData = cg.commonUserData.getPlayerData();
|
||||||
|
cgData.set(sourcefolder + "_id", id); // Set new IDs
|
||||||
|
for (val item : staticDataMap.entrySet()) {
|
||||||
|
var itemFolder = item.getValue().getFolder();
|
||||||
|
if (sourcedata.contains(itemFolder + "_id")) {
|
||||||
|
cgData.set(itemFolder + "_id", sourcedata.getString(itemFolder + "_id")); // Set all existing IDs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cg.config.signalChange();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
sync.accept(ownData);
|
||||||
|
sync.accept(userData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 final <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
|
||||||
|
return commonUserData.getPlayerData().getString(getFolderForType(cl) + "_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a player instance of the given type that represents the same player. This will return a new instance unless the player is cached.<br>
|
||||||
|
* If the class is a subclass of the current class then the same ID is used, otherwise, a connected ID is used, if found.
|
||||||
|
*
|
||||||
|
* @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 final <T extends ChromaGamerBase> T getAs(Class<T> cl) {
|
||||||
|
if (cl.getSimpleName().equals(getClass().getSimpleName()))
|
||||||
|
return (T) this;
|
||||||
|
String newfolder = getFolderForType(cl);
|
||||||
|
if (newfolder == null)
|
||||||
|
throw new RuntimeException("The specified class " + cl.getSimpleName() + " isn't registered!");
|
||||||
|
if (newfolder.equals(getFolder())) // If in the same folder, the same filename is used
|
||||||
|
return getUser(getFileName(), cl);
|
||||||
|
var playerData = commonUserData.getPlayerData();
|
||||||
|
if (!playerData.contains(newfolder + "_id"))
|
||||||
|
return null;
|
||||||
|
return getUser(playerData.getString(newfolder + "_id"), cl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, Discord IDs, etc.<br>
|
||||||
|
* <b>Does not include .yml</b>
|
||||||
|
*/
|
||||||
|
public final String getFileName() {
|
||||||
|
return commonUserData.getPlayerData().getString(getFolder() + "_id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method returns the folder that this player data is stored in. For example: "minecraft".
|
||||||
|
*/
|
||||||
|
public final String getFolder() {
|
||||||
|
return getFolderForType(getClass());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @return The player information.
|
||||||
|
*/
|
||||||
|
public final String getInfo(InfoTarget target) {
|
||||||
|
TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target);
|
||||||
|
Bukkit.getServer().getPluginManager().callEvent(event);
|
||||||
|
return event.getResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum InfoTarget {
|
||||||
|
MCHover, MCCommand, Discord
|
||||||
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
10
Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerEnforcer.java
Executable file
10
Chroma-Core/src/main/java/buttondevteam/lib/player/ChromaGamerEnforcer.java
Executable file
|
@ -0,0 +1,10 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
public @interface ChromaGamerEnforcer {
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
21
Chroma-Core/src/main/java/buttondevteam/lib/player/PlayerClass.java
Executable file
21
Chroma-Core/src/main/java/buttondevteam/lib/player/PlayerClass.java
Executable file
|
@ -0,0 +1,21 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a {@link TBMCPlayerBase} direct subclass. For Minecraft data, use {@link UserClass}
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
public @interface PlayerClass {
|
||||||
|
/**
|
||||||
|
* Indicates the plugin's name which this player class belongs to. Used to create a section for each plugin.
|
||||||
|
*/
|
||||||
|
String pluginname();
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
6
Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayer.java
Executable file
6
Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayer.java
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
@PlayerClass(pluginname = "Chroma-Core")
|
||||||
|
public final class TBMCPlayer extends TBMCPlayerBase {
|
||||||
|
|
||||||
|
}
|
83
Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java
Executable file
83
Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java
Executable file
|
@ -0,0 +1,83 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import buttondevteam.lib.architecture.ConfigData;
|
||||||
|
import buttondevteam.lib.architecture.IHaveConfig;
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.OfflinePlayer;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer.class)
|
||||||
|
@TBMCPlayerEnforcer
|
||||||
|
public abstract class TBMCPlayerBase extends ChromaGamerBase {
|
||||||
|
protected UUID uuid;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final IHaveConfig config = new IHaveConfig(this::save);
|
||||||
|
|
||||||
|
public UUID getUUID() {
|
||||||
|
if (uuid == null)
|
||||||
|
uuid = UUID.fromString(getFileName());
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final ConfigData<String> PlayerName = super.config.getData("PlayerName", "");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
public static <T extends TBMCPlayerBase> T getPlayer(UUID uuid, Class<T> cl) {
|
||||||
|
var player = ChromaGamerBase.getUser(uuid.toString(), cl);
|
||||||
|
if (!player.getUUID().equals(uuid)) //It will be set from the filename because we check it for scheduling the uncache.
|
||||||
|
throw new IllegalStateException("Player UUID differs after converting from and to string...");
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
||||||
|
OfflinePlayer p = Bukkit.getOfflinePlayer(name);
|
||||||
|
return getPlayer(p.getUniqueId(), cl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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.save();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Inherited;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
public @interface TBMCPlayerEnforcer {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import buttondevteam.lib.player.ChromaGamerBase.InfoTarget;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* This event gets called when player information is requested. It can be used to give more per-plugin information about a player.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author Norbi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TBMCPlayerGetInfoEvent extends Event {
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
|
||||||
|
private final ChromaGamerBase player;
|
||||||
|
private final List<String> infolines;
|
||||||
|
private final InfoTarget target;
|
||||||
|
|
||||||
|
TBMCPlayerGetInfoEvent(ChromaGamerBase player, InfoTarget target) {
|
||||||
|
super(true);
|
||||||
|
this.player = player;
|
||||||
|
infolines = new ArrayList<>();
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link TBMCPlayer} object
|
||||||
|
*
|
||||||
|
* @return A player object
|
||||||
|
*/
|
||||||
|
public ChromaGamerBase getPlayer() {
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a line to the information text. The line should be in the format of <i>key: value</i> .
|
||||||
|
*
|
||||||
|
* @param infoline
|
||||||
|
* The line to add
|
||||||
|
*/
|
||||||
|
public void addInfo(String infoline) {
|
||||||
|
infolines.add(infoline);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the {@link InfoTarget} we want the information for. Use this to format the returned string.
|
||||||
|
*
|
||||||
|
* @return The target of the information.
|
||||||
|
*/
|
||||||
|
public InfoTarget getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getResult() {
|
||||||
|
return infolines.stream().collect(Collectors.joining("\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
27
Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCYEEHAWEvent.java
Executable file
27
Chroma-Core/src/main/java/buttondevteam/lib/player/TBMCYEEHAWEvent.java
Executable file
|
@ -0,0 +1,27 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import org.bukkit.command.CommandSender;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
|
||||||
|
public class TBMCYEEHAWEvent extends Event {
|
||||||
|
private static final HandlerList handlers = new HandlerList();
|
||||||
|
private final CommandSender sender;
|
||||||
|
|
||||||
|
public TBMCYEEHAWEvent(CommandSender sender) {
|
||||||
|
this.sender = sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommandSender getSender() {
|
||||||
|
return sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerList getHandlers() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
}
|
19
Chroma-Core/src/main/java/buttondevteam/lib/player/UserClass.java
Executable file
19
Chroma-Core/src/main/java/buttondevteam/lib/player/UserClass.java
Executable file
|
@ -0,0 +1,19 @@
|
||||||
|
package buttondevteam.lib.player;
|
||||||
|
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies a {@link ChromaGamerBase} direct subclass which can be instantiated. For Minecraft data, use {@link PlayerClass}
|
||||||
|
*
|
||||||
|
* @author NorbiPeti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.TYPE)
|
||||||
|
@Inherited
|
||||||
|
public @interface UserClass {
|
||||||
|
/**
|
||||||
|
* Indicates which folder should the player files be saved in. Must be lowercase.
|
||||||
|
*/
|
||||||
|
String foldername();
|
||||||
|
}
|
145
Chroma-Core/src/main/java/buttondevteam/lib/potato/DebugPotato.java
Executable file
145
Chroma-Core/src/main/java/buttondevteam/lib/potato/DebugPotato.java
Executable file
|
@ -0,0 +1,145 @@
|
||||||
|
package buttondevteam.lib.potato;
|
||||||
|
|
||||||
|
import org.bukkit.Material;
|
||||||
|
import org.bukkit.enchantments.Enchantment;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.ItemMeta;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class DebugPotato {
|
||||||
|
private List<String> message;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the debug potato to a player
|
||||||
|
*
|
||||||
|
* @param player
|
||||||
|
* The player
|
||||||
|
*/
|
||||||
|
public void Send(Player player){
|
||||||
|
player.getInventory().addItem(this.toItemStack());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the message (lore of the potato).
|
||||||
|
*
|
||||||
|
* @return The message
|
||||||
|
*/
|
||||||
|
public List<String> getMessage() {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the message (lore of the potato).
|
||||||
|
* @param message The message you wish to set
|
||||||
|
* @param forceWordWrap Word Wraps the whole String List, without preserving previous line breaks. Preserves line breaks if false
|
||||||
|
* @return This potato
|
||||||
|
*/
|
||||||
|
|
||||||
|
public DebugPotato setMessage(List<String> message, boolean forceWordWrap) {
|
||||||
|
if (forceWordWrap){
|
||||||
|
this.message = WordWrap(message.toString());
|
||||||
|
}else{
|
||||||
|
List<String> outputList = new ArrayList<String>();
|
||||||
|
List<String> tempList = new ArrayList<String>();
|
||||||
|
for(String line: message){
|
||||||
|
tempList = WordWrap(line.toString());
|
||||||
|
outputList.addAll(tempList);
|
||||||
|
}
|
||||||
|
this.message = outputList;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Sets the message (lore of the potato). It will be word wrapped automatically.
|
||||||
|
* @return This potato
|
||||||
|
*/
|
||||||
|
public DebugPotato setMessage(List<String> message) {
|
||||||
|
return setMessage(message, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the message (lore of the potato). It will be word wrapped automatically.
|
||||||
|
* @return This potato
|
||||||
|
*/
|
||||||
|
public DebugPotato setMessage(String message) {
|
||||||
|
return setMessage(Arrays.asList(message));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the message (lore of the potato). It will be word wrapped automatically.
|
||||||
|
* @return This potato
|
||||||
|
*/
|
||||||
|
public DebugPotato setMessage(String[] message) {
|
||||||
|
return setMessage(Arrays.asList(message));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the type (potato name).
|
||||||
|
*
|
||||||
|
* @return The type
|
||||||
|
*/
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the type (potato name).
|
||||||
|
*
|
||||||
|
* @param type
|
||||||
|
* The type
|
||||||
|
* @return This potato
|
||||||
|
*/
|
||||||
|
public DebugPotato setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<String> WordWrap(String message) {
|
||||||
|
String[] splitString = message.split("\\s+");
|
||||||
|
List<String> newMessage = new ArrayList<String>();
|
||||||
|
String currentLine = "";
|
||||||
|
int currentLineLength = 0;
|
||||||
|
int wordlength;
|
||||||
|
int maxLineLength = 40;
|
||||||
|
if (message.length() <= maxLineLength){
|
||||||
|
newMessage.add(message);
|
||||||
|
return newMessage;
|
||||||
|
}
|
||||||
|
for (String word : splitString) {
|
||||||
|
wordlength = word.length();
|
||||||
|
if (currentLineLength == 0 || (currentLineLength + wordlength) < maxLineLength) {
|
||||||
|
currentLine += word + " ";
|
||||||
|
currentLineLength += wordlength + 1;
|
||||||
|
} else {
|
||||||
|
newMessage.add(currentLine);
|
||||||
|
currentLine = word + " ";
|
||||||
|
currentLineLength = word.length();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newMessage.add(currentLine);
|
||||||
|
return newMessage;
|
||||||
|
}
|
||||||
|
public ItemStack toItemStack() {
|
||||||
|
ItemStack potato = new ItemStack(Material.BAKED_POTATO);
|
||||||
|
ItemMeta meta = potato.getItemMeta();
|
||||||
|
meta.setDisplayName(this.getType() == null ? "Null Flavoured Debug Potato" : this.getType());
|
||||||
|
if (this.getMessage() == null){
|
||||||
|
List<String> message = new ArrayList<String>();
|
||||||
|
message.add("nullMessage");
|
||||||
|
meta.setLore(message);
|
||||||
|
}else{
|
||||||
|
meta.setLore(this.getMessage());
|
||||||
|
}
|
||||||
|
potato.setItemMeta(meta);
|
||||||
|
potato.addUnsafeEnchantment(Enchantment.ARROW_FIRE, 10);
|
||||||
|
return potato;
|
||||||
|
}
|
||||||
|
}
|
43
Chroma-Core/src/main/java/buttondevteam/lib/potato/Gary.java
Executable file
43
Chroma-Core/src/main/java/buttondevteam/lib/potato/Gary.java
Executable file
|
@ -0,0 +1,43 @@
|
||||||
|
package buttondevteam.lib.potato;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class Gary extends DebugPotato {
|
||||||
|
|
||||||
|
public Gary() {
|
||||||
|
super.setMessage("I'M A POTATO");
|
||||||
|
super.setType("Gary");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gary has a fixed message, therefore this method has no effect.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DebugPotato setMessage(List<String> message) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gary has a fixed message, therefore this method has no effect.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DebugPotato setMessage(String message) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gary has a fixed message, therefore this method has no effect.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DebugPotato setMessage(String[] message) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gary has it's name already, therefore this method has no effect.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public DebugPotato setType(String type) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
22
Chroma-Core/src/main/resources/plugin.yml
Executable file
22
Chroma-Core/src/main/resources/plugin.yml
Executable file
|
@ -0,0 +1,22 @@
|
||||||
|
name: Chroma-Core
|
||||||
|
main: buttondevteam.core.MainPlugin
|
||||||
|
version: '${noprefix.version}'
|
||||||
|
author: NorbiPeti
|
||||||
|
commands:
|
||||||
|
updateplugin:
|
||||||
|
description: Update a TBMC plugin
|
||||||
|
schrestart:
|
||||||
|
description: Schedules a restart for a given time.
|
||||||
|
primerestart:
|
||||||
|
description: Restarts the server as soon as nobody is online.
|
||||||
|
component:
|
||||||
|
description: Enable or disable or list components
|
||||||
|
dontrunthiscmd:
|
||||||
|
depend:
|
||||||
|
- Vault
|
||||||
|
softdepend:
|
||||||
|
- Towny
|
||||||
|
- Votifier
|
||||||
|
- Multiverse-Core
|
||||||
|
- Essentials
|
||||||
|
api-version: '1.13'
|
127
CorePOM/pom.xml
Normal file
127
CorePOM/pom.xml
Normal file
|
@ -0,0 +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
|
||||||
|
http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</project>
|
|
@ -1,2 +1,2 @@
|
||||||
# ButtonPluginBucket
|
# ChromaCore
|
||||||
A place where finished projects go, that are too small to deserve their own plugin
|
A plugin that manages users, messages, exceptions and others across different platforms (Minecraft, Discord, ...)
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
name: ButtonPluginBucket
|
|
||||||
main: buttondevteam.bucket.MainPlugin
|
|
||||||
version: 1.0
|
|
||||||
author: TBMCPlugins
|
|
58
pom.xml
Normal file
58
pom.xml
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
<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/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.github.TBMCPlugins</groupId>
|
||||||
|
<artifactId>ChromaCore</artifactId>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<version>master-SNAPSHOT</version>
|
||||||
|
<properties>
|
||||||
|
<lombok.version>1.18.12</lombok.version>
|
||||||
|
</properties>
|
||||||
|
<name>Chroma Parent</name>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>CorePOM</module>
|
||||||
|
<module>Chroma-Core</module>
|
||||||
|
<module>ButtonProcessor</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<pluginManagement>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<release>8</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<useSystemClassLoader>false</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</pluginManagement>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
</project>
|
|
@ -1,51 +0,0 @@
|
||||||
package buttondevteam.bucket;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
|
||||||
import org.bukkit.plugin.PluginDescriptionFile;
|
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
|
||||||
|
|
||||||
import buttondevteam.bucket.alisolarflare.aliarrow.AliArrowSubPlugin;
|
|
||||||
import buttondevteam.bucket.core.CoreSubPlugin;
|
|
||||||
|
|
||||||
public class MainPlugin extends JavaPlugin {
|
|
||||||
public static MainPlugin Instance;
|
|
||||||
|
|
||||||
private PluginDescriptionFile pdfFile;
|
|
||||||
private Logger logger;
|
|
||||||
private AliArrowSubPlugin aliArrowSubPlugin;
|
|
||||||
private CoreSubPlugin playerSubPlugin;
|
|
||||||
|
|
||||||
public void onEnable() {
|
|
||||||
// Logs "Plugin Enabled", registers commands
|
|
||||||
Instance = this;
|
|
||||||
pdfFile = getDescription();
|
|
||||||
logger = getLogger();
|
|
||||||
logger.info(pdfFile.getName() + " has been started (V." + pdfFile.getVersion() + ").");
|
|
||||||
|
|
||||||
registerSubPlugins();
|
|
||||||
registerCommands();
|
|
||||||
registerEvents();
|
|
||||||
|
|
||||||
logger.info(pdfFile.getName() + " has been Enabled (V." + pdfFile.getVersion() + ").");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerSubPlugins() {
|
|
||||||
aliArrowSubPlugin = new AliArrowSubPlugin(this);
|
|
||||||
aliArrowSubPlugin.register();
|
|
||||||
playerSubPlugin = new CoreSubPlugin(this);
|
|
||||||
playerSubPlugin.register();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerCommands() {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void registerEvents() {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
package buttondevteam.bucket.alisolarflare.aliarrow;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Arrow;
|
|
||||||
import org.bukkit.entity.EntityType;
|
|
||||||
import org.bukkit.entity.Projectile;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
import org.bukkit.event.Listener;
|
|
||||||
import org.bukkit.event.entity.ProjectileLaunchEvent;
|
|
||||||
|
|
||||||
import buttondevteam.bucket.MainPlugin;
|
|
||||||
|
|
||||||
public class AliArrowListener implements Listener {
|
|
||||||
private final MainPlugin plugin;
|
|
||||||
|
|
||||||
public AliArrowListener(MainPlugin plugin){
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@EventHandler
|
|
||||||
public void onProjectileLaunch(ProjectileLaunchEvent event){
|
|
||||||
try{
|
|
||||||
if(!(event.getEntity().getType() == EntityType.ARROW)){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Projectile projectile = event.getEntity();
|
|
||||||
if (!(projectile.getShooter().equals(plugin.getServer().getPlayer("Ali")))){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Arrow arrow = (Arrow) projectile;
|
|
||||||
if (!(arrow.isCritical())){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
AliArrowTask aliArrowTask = new AliArrowTask(plugin,arrow);
|
|
||||||
aliArrowTask.runTaskTimer(plugin, 2, 1);
|
|
||||||
}catch(Exception e){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue