mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-21 15:49:20 +00:00
Compare commits
118 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
12e882fe22 | ||
|
|
4ed8b94d55 | ||
|
|
854bf37186 | ||
|
|
8bab2a8123 | ||
|
|
97ad608d56 | ||
|
|
f7419f7277 | ||
|
|
f0497f61f0 | ||
|
|
f6aab54d4d | ||
|
|
c306d700ce | ||
|
|
bbcb091daf | ||
|
|
eb9e2491e5 | ||
|
|
0250ad80c8 | ||
|
|
516b163e07 | ||
|
|
c123b15708 | ||
|
|
29f8d8bf50 | ||
|
|
4f65cf49ef | ||
|
|
27f9e24dfb | ||
|
|
2cbe39f158 | ||
|
|
5840f61571 | ||
|
|
6bd12dc000 | ||
|
|
1afbd3753a | ||
|
|
38a063420b | ||
|
|
0fae3484a1 | ||
|
|
7bb4bff485 | ||
|
|
c5f5cd702e | ||
|
|
3ced2cc0af | ||
|
|
73e1840574 | ||
|
|
d1ca56af1f | ||
|
|
55211e30c5 | ||
|
|
2c1a38f2c9 | ||
|
|
0c7e052d44 | ||
|
|
54cc11fce0 | ||
|
|
3645fa01ec | ||
|
|
e20a0da845 | ||
|
|
7ed7d0a29e | ||
|
|
e7e7995e4e | ||
|
|
af57cfcf70 | ||
|
|
f1ac9b5e04 | ||
|
|
a9b1070725 | ||
|
|
5a000add98 | ||
|
|
aec2836d1e | ||
|
|
84e2ea3904 | ||
|
|
4f669170c2 | ||
|
|
8ea8c7b7ba | ||
|
|
acab4ae58a | ||
|
|
ea822b0f4b | ||
|
|
24a9974ff7 | ||
|
|
222a9871e0 | ||
|
|
0ce9d2ce74 | ||
|
|
cde500a123 | ||
|
|
f15790030f | ||
|
|
ce3350c6fa | ||
|
|
4288742052 | ||
|
|
8205b9c169 | ||
|
|
1d7f6a8d8b | ||
|
|
3425c97245 | ||
|
|
2d1d8f1ab6 | ||
|
|
f322d31b03 | ||
|
|
368e665ac3 | ||
|
|
922eb2f19a | ||
|
|
23e0123004 | ||
|
|
e98bac844a | ||
|
|
d6d9a55f72 | ||
|
|
e3070a65ab | ||
|
|
a8b4696604 | ||
|
|
7f5ca6206b | ||
|
|
ad885a9a15 | ||
|
|
fe89e7b770 | ||
|
|
17ea62ed0b | ||
|
|
94717637ba | ||
|
|
f6663f0c09 | ||
|
|
33588c2345 | ||
|
|
c2c5a424fb | ||
|
|
ce41053e87 | ||
|
|
5817de83e5 | ||
|
|
30dd48ce88 | ||
|
|
cf7912a89e | ||
|
|
9900b44858 | ||
|
|
9019181208 | ||
|
|
99483387f1 | ||
|
|
42177f2582 | ||
|
|
e4e0743205 | ||
|
|
105927a57f | ||
|
|
71706bf9ae | ||
|
|
101e0c11d7 | ||
|
|
70323fb2e2 | ||
|
|
9dc5577175 | ||
|
|
117d5edea2 | ||
|
|
3f0f518037 | ||
|
|
2017ecc20f | ||
|
|
ded89ad343 | ||
|
|
c4b194f8d6 | ||
|
|
d682e6e6c6 | ||
|
|
6fef9c4eae | ||
|
|
16eee05065 | ||
|
|
b664e2586d | ||
|
|
d594c9c257 | ||
|
|
532a65eca8 | ||
|
|
5af8ae0da5 | ||
|
|
c0709f82bd | ||
|
|
945b65e1bc | ||
|
|
efcb36d345 | ||
|
|
30cd89c578 | ||
|
|
bb3753b8e4 | ||
|
|
d5569ad3ed | ||
|
|
d8386fd2a2 | ||
|
|
3bfea58f35 | ||
|
|
51cf7beeb8 | ||
|
|
df247b41f4 | ||
|
|
bac760165e | ||
|
|
dd39482ed1 | ||
|
|
c05f165278 | ||
|
|
c888759d33 | ||
|
|
089ea5b63a | ||
|
|
9020e9d906 | ||
|
|
7584ea0070 | ||
|
|
9c243c2893 | ||
|
|
8ba90fadc4 |
7
.github/dependabot.yml
vendored
Normal file
7
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Dependabot configuration file for GitHub
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "gradle" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
3
.github/funding.yml
vendored
Normal file
3
.github/funding.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# Funding metadata for GitHub
|
||||
github: WiIIiam278
|
||||
custom: https://buymeacoff.ee/william278
|
||||
37
.github/workflows/ci.yml
vendored
Normal file
37
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Builds, tests the project with Gradle
|
||||
name: CI Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'master' ]
|
||||
paths-ignore:
|
||||
- 'docs/**'
|
||||
- 'workflows/**'
|
||||
- 'README.md'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: build test publish
|
||||
env:
|
||||
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
if: success() || failure() # always run even if the previous step fails
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
32
.github/workflows/java_ci.yml
vendored
32
.github/workflows/java_ci.yml
vendored
@@ -1,32 +0,0 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
|
||||
|
||||
name: Java CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "master" ]
|
||||
pull_request:
|
||||
branches: [ "master" ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 16
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '16'
|
||||
distribution: 'temurin'
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1
|
||||
with:
|
||||
arguments: test
|
||||
24
.github/workflows/pr_tests.yml
vendored
Normal file
24
.github/workflows/pr_tests.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Carry out tests on pull requests
|
||||
name: PR Tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ 'master' ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Test Pull Request
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: test
|
||||
33
.github/workflows/release.yml
vendored
Normal file
33
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
# Builds, tests and publishes to maven when a release is published
|
||||
name: Release Tests
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
checks: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Build with Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
with:
|
||||
arguments: build test publish
|
||||
env:
|
||||
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
if: success() || failure() # always run even if the previous step fails
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
28
.github/workflows/update_docs.yml
vendored
Normal file
28
.github/workflows/update_docs.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
# Update the GitHub Wiki documentation when a push is made to docs/
|
||||
name: Update Docs
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ 'master' ]
|
||||
paths:
|
||||
- 'docs/**'
|
||||
- 'workflows/**'
|
||||
tags-ignore:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
deploy-wiki:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Code'
|
||||
uses: actions/checkout@v3
|
||||
- name: 'Push Changes to Wiki'
|
||||
uses: Andrew-Chen-Wang/github-wiki-action@v3
|
||||
env:
|
||||
WIKI_DIR: 'docs/'
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
GH_MAIL: 'actions@github.com'
|
||||
GH_NAME: 'github-actions[bot]'
|
||||
16
HEADER
Normal file
16
HEADER
Normal file
@@ -0,0 +1,16 @@
|
||||
This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
|
||||
Copyright (c) William278 <will27528@gmail.com>
|
||||
Copyright (c) contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
217
LICENSE
217
LICENSE
@@ -1,21 +1,202 @@
|
||||
Copyright © William278 2022. All rights reserved
|
||||
|
||||
LICENSE
|
||||
This source code is provided as reference to licensed individuals that have purchased the HuskSync
|
||||
plugin once from any of the official sources it is provided. The availability of this code does
|
||||
not grant you the rights to modify, re-distribute, compile or redistribute this source code or
|
||||
"plugin" outside this intended purpose. This license does not cover libraries developed by third
|
||||
parties that are utilised in the plugin.
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
CONTRIBUTOR AGREEMENT
|
||||
By contributing code to this repository, contributors agree that they forfeit their contributions
|
||||
to the copyright holder and only the copyright holder.
|
||||
In exchange for contributing, the copyright holder may give, at their discretion, permission to use
|
||||
the plugin in commercial contexts
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
DEFINITIONS
|
||||
"plugin"; the jar file compiled from this source code
|
||||
"source code"; the java source code and gradle configurations provided in this repository, however
|
||||
excludes libraries
|
||||
"copyright holder"; William278
|
||||
"contributor(s)"; person(s) who submit (contribute) code through a pull request to this repository
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
99
README.md
99
README.md
@@ -1,64 +1,85 @@
|
||||
# [](https://github.com/WiIIiam278/HuskSync)
|
||||
[](https://github.com/WiIIiam278/HuskSync/actions/workflows/java_ci.yml)
|
||||
[](https://jitpack.io/#net.william278/HuskSync)
|
||||
[](https://discord.gg/tVYhJfyDWG)
|
||||
|
||||
[Documentation, Guides & API](https://william278.net/docs/husksync) · [Resource Page](https://www.spigotmc.org/resources/husksync.97144/) · [Bug Reports](https://github.com/WiIIiam278/HuskSync/issues)
|
||||
<!--suppress ALL -->
|
||||
<p align="center">
|
||||
<img src="images/banner.png" alt="HuskSync" />
|
||||
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci.yml">
|
||||
<img src="https://img.shields.io/github/actions/workflow/status/WiIIiam278/HuskSync/ci.yml?branch=master&logo=github"/>
|
||||
</a>
|
||||
<a href="https://jitpack.io/#net.william278/HuskSync">
|
||||
<img src="https://img.shields.io/jitpack/version/net.william278/HuskSync?color=%2300fb9a&label=api&logo=gradle" />
|
||||
</a>
|
||||
<a href="https://discord.gg/tVYhJfyDWG">
|
||||
<img src="https://img.shields.io/discord/818135932103557162.svg?label=&logo=discord&logoColor=fff&color=7389D8&labelColor=6A7EC2" />
|
||||
</a>
|
||||
<br/>
|
||||
<b>
|
||||
<a href="https://www.spigotmc.org/resources/husksync.97144/">Spigot</a>
|
||||
</b> —
|
||||
<b>
|
||||
<a href="https://william278.net/docs/husksync/setup">Setup</a>
|
||||
</b> —
|
||||
<b>
|
||||
<a href="https://william278.net/docs/husksync/">Docs</a>
|
||||
</b> —
|
||||
<b>
|
||||
<a href="https://github.com/WiIIiam278/HuskSync/issues">Issues</a>
|
||||
</b>
|
||||
</p>
|
||||
<br/>
|
||||
|
||||
**HuskSync** is a modern, cross-server player data synchronisation system that enables the comprehensive synchronisation of your user's data across multiple proxied servers. It does this by making use of Redis and MySQL to optimally cache data while players change servers.
|
||||
|
||||
## Features
|
||||

|
||||
**⭐ Seamless synchronisation** — Utilises optimised Redis caching when players change server to sync player data super quickly for a seamless experience.
|
||||
|
||||
- Synchronise inventories, ender chests, advancements, statistics, experience points, health, max health, hunger, saturation, potion effects, persistent data container tags, game mode, location and more across multiple proxied servers.
|
||||
- Create and manage "snapshot" backups of user data and roll back users to previous states on-the-fly. (`/userdata`)
|
||||
- Preview, list, delete, restore & pin user data snapshots in-game with an intuitive menu.
|
||||
- Examine the contents of player's inventories and ender chests on-the-fly. (`/inventory`, `/enderchest`)
|
||||
- Hooks with your [Player Analytics](https://github.com/plan-player-analytics/Plan) web panel to provide an overview of user data.
|
||||
- Supports segregating synchronisation across multiple distinct clusters on one network.
|
||||
**⭐ Complete player synchronisation** — Sync inventories, Ender Chests, health, hunger, effects, advancements, statistics, locked maps & [more](https://william278.net/docs/husksync/sync-features)—no data left behind!
|
||||
|
||||
## Requirements
|
||||
* A MySQL Database (v8.0+).
|
||||
* A Redis Database (v5.0+)
|
||||
* Any number of proxied Spigot servers (Minecraft v1.16.5+)
|
||||
**⭐ Backup, restore & rotate** — Something gone wrong? Restore players back to a previous data state. Rotate and manage data snapshots in-game!
|
||||
|
||||
**⭐ Import existing data** — Import your MySQLPlayerDataBridge data—or from your existing world data! No server reset needed!
|
||||
|
||||
**⭐ Works great with Plan** — Stay in touch with your community through HuskSync analytics on your Plan web panel.
|
||||
|
||||
**⭐ Extensible API & open-source** — Need more? Extend the plugin with the Developer API. Or, submit a pull request through our code bounty system!
|
||||
|
||||
**Ready?** [It's syncing time!](https://william278.net/docs/husksync/setup)
|
||||
|
||||
## Setup
|
||||
1. Place the plugin jar file in the `/plugins/` directory of each Spigot server. You do not need to install HuskSync as a proxy plugin.
|
||||
Requires a MySQL (v8.0+) database, a Redis (v5.0+) server and any number of Spigot-based 1.16.5+ Minecraft servers, running Java 16+.
|
||||
|
||||
1. Place the plugin jar file in the /plugins/ directory of each Spigot server. You do not need to install HuskSync as a proxy plugin.
|
||||
2. Start, then stop every server to let HuskSync generate the config file.
|
||||
3. Navigate to the HuskSync config file on each server (`~/plugins/HuskSync/config.yml`) and fill in both the MySQL and Redis database credentials.
|
||||
3. Navigate to the HuskSync config file on each server (~/plugins/HuskSync/config.yml) and fill in both the MySQL and Redis database credentials.
|
||||
4. Start every server again and synchronization will begin.
|
||||
|
||||
## Building
|
||||
## Development
|
||||
To build HuskSync, simply run the following in the root of the repository:
|
||||
```
|
||||
|
||||
```bash
|
||||
./gradlew clean build
|
||||
```
|
||||
|
||||
## License
|
||||
HuskSync is a premium resource. This source code is provided as reference only for those who have purchased the resource from an official source.
|
||||
### License
|
||||
HuskSync is licensed under the Apache 2.0 license.
|
||||
|
||||
- [License](https://github.com/WiIIiam278/HuskSync/blob/master/LICENSE)
|
||||
|
||||
## Contributing
|
||||
A code bounty program is in place for HuskSync, where developers making significant code contributions to HuskSync may be entitled to a license at my discretion to use HuskSync in commercial contexts without having to purchase the resource. Please read the information for contributors in the LICENSE file before submitting a pull request.
|
||||
Contributions to the project are welcome—feel free to open a pull request with new features, improvements and/or fixes!
|
||||
|
||||
## Translation
|
||||
### Support
|
||||
Due to its complexity, official support for HuskSync is provided through a paid model. This means that support is only available to users who have purchased a license to the plugin from Spigot, Polymart, or Craftaro and have provided proof of purchase. Please join our Discord server if you have done so and need help!
|
||||
|
||||
### Translations
|
||||
Translations of the plugin locales are welcome to help make the plugin more accessible. Please submit a pull request with your translations as a `.yml` file.
|
||||
|
||||
- [Locales Directory](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales)
|
||||
- [English Locales](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales/en-gb.yml)
|
||||
|
||||
## bStats
|
||||
This plugin uses bStats to provide me with metrics about its usage:
|
||||
- [bStats Metrics](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140)
|
||||
|
||||
You can turn metric collection off by navigating to `~/plugins/bStats/config.yml` and editing the config to disable plugin metrics.
|
||||
- [Locales Directory](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/languages)
|
||||
- [English Locales](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/languages/en-gb.yml)
|
||||
|
||||
## Links
|
||||
- [Documentation, Guides & API](https://william278.net/docs/husksync)
|
||||
- [Resource Page](https://www.spigotmc.org/resources/husksync.97144/)
|
||||
- [Bug Reports](https://github.com/WiIIiam278/HuskSync/issues)
|
||||
- [Discord Support](https://discord.gg/tVYhJfyDWG) (Proof of purchase required)
|
||||
- [Docs](https://william278.net/docs/husksync/) — Read the plugin documentation!
|
||||
- [Spigot](https://www.spigotmc.org/resources/husksync.97144/) — View the Spigot resource page (Also: [Polymart](https://polymart.org/resource/husksync.1634), [Craftaro](https://craftaro.com/marketplace/product/husksync.758))
|
||||
- [Issues](https://github.com/WiIIiam278/HuskSync/issues) — File a bug report or feature request
|
||||
- [Discord](https://discord.gg/tVYhJfyDWG) — Get help, ask questions (Purchase required)
|
||||
- [bStats](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140) — View plugin metrics
|
||||
|
||||
---
|
||||
© [William278](https://william278.net/), 2022. All rights reserved.
|
||||
© [William278](https://william278.net/), 2023. Licensed under the Apache-2.0 License.
|
||||
|
||||
72
build.gradle
72
build.gradle
@@ -1,17 +1,23 @@
|
||||
plugins {
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||
id 'org.ajoberstar.grgit' version '5.0.0'
|
||||
id 'java'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||
id 'org.ajoberstar.grgit' version '5.2.0'
|
||||
id 'maven-publish'
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group 'net.william278'
|
||||
version "$ext.plugin_version-${versionMetadata()}"
|
||||
version "$ext.plugin_version${versionMetadata()}"
|
||||
description "$ext.plugin_description"
|
||||
defaultTasks 'licenseFormat', 'build'
|
||||
|
||||
ext {
|
||||
set 'version', version.toString()
|
||||
set 'description', description.toString()
|
||||
|
||||
set 'jedis_version', jedis_version.toString()
|
||||
set 'mysql_driver_version', mysql_driver_version.toString()
|
||||
set 'mariadb_driver_version', mariadb_driver_version.toString()
|
||||
set 'snappy_version', snappy_version.toString()
|
||||
set 'commons_text_version', commons_text_version.toString()
|
||||
}
|
||||
@@ -20,6 +26,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
||||
|
||||
allprojects {
|
||||
apply plugin: 'com.github.johnrengelman.shadow'
|
||||
apply plugin: 'org.cadixdev.licenser'
|
||||
apply plugin: 'java'
|
||||
|
||||
compileJava.options.encoding = 'UTF-8'
|
||||
@@ -37,17 +44,24 @@ allprojects {
|
||||
maven { url 'https://repo.mattstudios.me/artifactory/public/' }
|
||||
maven { url 'https://jitpack.io' }
|
||||
maven { url 'https://libraries.minecraft.net/' }
|
||||
maven { url 'https://william278.net/releases/' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
license {
|
||||
header = rootProject.file('HEADER')
|
||||
include '**/*.java'
|
||||
newLine = true
|
||||
}
|
||||
|
||||
processResources {
|
||||
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
||||
tokens: rootProject.ext.properties
|
||||
@@ -58,6 +72,10 @@ subprojects {
|
||||
version rootProject.version
|
||||
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
|
||||
|
||||
jar {
|
||||
from '../LICENSE'
|
||||
}
|
||||
|
||||
if (['bukkit', 'plugin'].contains(project.name)) {
|
||||
shadowJar {
|
||||
destinationDirectory.set(file("$rootDir/target"))
|
||||
@@ -79,6 +97,35 @@ subprojects {
|
||||
shadowJar.dependsOn(sourcesJar, javadocJar)
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
if (System.getenv("RELEASES_MAVEN_USERNAME") != null) {
|
||||
maven {
|
||||
name = "william278-releases"
|
||||
url = "https://repo.william278.net/releases"
|
||||
credentials {
|
||||
username = System.getenv("RELEASES_MAVEN_USERNAME")
|
||||
password = System.getenv("RELEASES_MAVEN_PASSWORD")
|
||||
}
|
||||
authentication {
|
||||
basic(BasicAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (System.getenv("SNAPSHOTS_MAVEN_USERNAME") != null) {
|
||||
maven {
|
||||
name = "william278-snapshots"
|
||||
url = "https://repo.william278.net/snapshots"
|
||||
credentials {
|
||||
username = System.getenv("SNAPSHOTS_MAVEN_USERNAME")
|
||||
password = System.getenv("SNAPSHOTS_MAVEN_PASSWORD")
|
||||
}
|
||||
authentication {
|
||||
basic(BasicAuthentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
publications {
|
||||
mavenJava(MavenPublication) {
|
||||
groupId = 'net.william278'
|
||||
@@ -101,8 +148,15 @@ logger.lifecycle("Building HuskSync ${version} by William278")
|
||||
|
||||
@SuppressWarnings('GrMethodMayBeStatic')
|
||||
def versionMetadata() {
|
||||
if (grgit == null) {
|
||||
return System.getenv("GITHUB_RUN_NUMBER") ? 'build.' + System.getenv("GITHUB_RUN_NUMBER") : 'unknown'
|
||||
// Get if there is a tag for this commit
|
||||
def tag = grgit.tag.list().find { it.commit.id == grgit.head().id }
|
||||
if (tag != null) {
|
||||
return ''
|
||||
}
|
||||
return grgit.head().abbreviatedId + (grgit.status().clean ? '' : '-indev')
|
||||
|
||||
// Otherwise, get the last commit hash and if it's a clean head
|
||||
if (grgit == null) {
|
||||
return '-' + System.getenv("GITHUB_RUN_NUMBER") ? 'build.' + System.getenv("GITHUB_RUN_NUMBER") : 'unknown'
|
||||
}
|
||||
return '-' + grgit.head().abbreviatedId + (grgit.status().clean ? '' : '-indev')
|
||||
}
|
||||
@@ -1,19 +1,23 @@
|
||||
dependencies {
|
||||
implementation project(path: ':common')
|
||||
implementation 'org.bstats:bstats-bukkit:3.0.0'
|
||||
implementation 'org.bstats:bstats-bukkit:3.0.2'
|
||||
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
||||
implementation 'net.william278:hsldataconverter:1.0'
|
||||
implementation 'net.william278:MapDataAPI:1.0.2'
|
||||
implementation 'net.william278:AndJam:1.0.2'
|
||||
implementation 'me.lucko:commodore:2.2'
|
||||
implementation 'net.kyori:adventure-platform-bukkit:4.1.2'
|
||||
implementation 'dev.triumphteam:triumph-gui:3.1.3'
|
||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.0'
|
||||
implementation 'dev.triumphteam:triumph-gui:3.1.5'
|
||||
|
||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
||||
compileOnly 'commons-io:commons-io:2.11.0'
|
||||
compileOnly 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
||||
compileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
||||
compileOnly 'commons-io:commons-io:2.13.0'
|
||||
compileOnly 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||
compileOnly 'dev.dejvokep:boosted-yaml:1.3.1'
|
||||
compileOnly 'com.zaxxer:HikariCP:5.0.1'
|
||||
compileOnly 'net.william278:DesertWell:1.1'
|
||||
compileOnly 'net.william278:Annotaml:2.0'
|
||||
compileOnly 'redis.clients:jedis:' + jedis_version
|
||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||
compileOnly 'net.william278:Annotaml:2.0.1'
|
||||
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
@@ -31,6 +35,10 @@ shadowJar {
|
||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||
relocate 'net.william278.andjam', 'net.william278.husksync.libraries.andjam'
|
||||
relocate 'net.querz', 'net.william278.husksync.libraries.nbt'
|
||||
relocate 'net.roxeez', 'net.william278.husksync.libraries'
|
||||
|
||||
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
||||
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync;
|
||||
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import net.william278.annotaml.Annotaml;
|
||||
import net.william278.desertwell.Version;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.husksync.command.BukkitCommand;
|
||||
import net.william278.husksync.command.BukkitCommandType;
|
||||
import net.william278.husksync.command.Permission;
|
||||
@@ -24,10 +43,6 @@ import net.william278.husksync.migrator.MpdbMigrator;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import net.william278.husksync.util.BukkitLogger;
|
||||
import net.william278.husksync.util.BukkitResourceReader;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.ResourceReader;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
@@ -54,8 +69,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
private static final int METRICS_ID = 13140;
|
||||
private Database database;
|
||||
private RedisManager redisManager;
|
||||
private Logger logger;
|
||||
private ResourceReader resourceReader;
|
||||
private EventListener eventListener;
|
||||
private DataAdapter dataAdapter;
|
||||
private EventCannon eventCannon;
|
||||
@@ -85,25 +98,20 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
// Initialize HuskSync
|
||||
final AtomicBoolean initialized = new AtomicBoolean(true);
|
||||
try {
|
||||
// Set the logging adapter and resource reader
|
||||
this.logger = new BukkitLogger(this.getLogger());
|
||||
this.resourceReader = new BukkitResourceReader(this);
|
||||
|
||||
// Create adventure audience
|
||||
this.audiences = BukkitAudiences.create(this);
|
||||
|
||||
// Load settings and locales
|
||||
getLoggingAdapter().log(Level.INFO, "Loading plugin configuration settings & locales...");
|
||||
log(Level.INFO, "Loading plugin configuration settings & locales...");
|
||||
initialized.set(reload().join());
|
||||
if (initialized.get()) {
|
||||
logger.showDebugLogs(settings.debugLogging);
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
|
||||
log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
|
||||
} else {
|
||||
throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales");
|
||||
}
|
||||
|
||||
// Prepare data adapter
|
||||
if (settings.compressData) {
|
||||
if (settings.doCompressData()) {
|
||||
dataAdapter = new CompressedDataAdapter();
|
||||
} else {
|
||||
dataAdapter = new JsonDataAdapter();
|
||||
@@ -121,34 +129,34 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
}
|
||||
|
||||
// Prepare database connection
|
||||
this.database = new MySqlDatabase(settings, resourceReader, logger, dataAdapter, eventCannon);
|
||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the database...");
|
||||
initialized.set(this.database.initialize());
|
||||
this.database = new MySqlDatabase(this);
|
||||
log(Level.INFO, "Attempting to establish connection to the " + settings.getDatabaseType().getDisplayName() + " database...");
|
||||
this.database.initialize();
|
||||
if (initialized.get()) {
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the database");
|
||||
log(Level.INFO, "Successfully established a connection to the database");
|
||||
} else {
|
||||
throw new HuskSyncInitializationException("Failed to establish a connection to the database. " +
|
||||
"Please check the supplied database credentials in the config file");
|
||||
"Please check the supplied database credentials in the config file");
|
||||
}
|
||||
|
||||
// Prepare redis connection
|
||||
this.redisManager = new RedisManager(this);
|
||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
||||
initialized.set(this.redisManager.initialize().join());
|
||||
log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
||||
initialized.set(this.redisManager.initialize());
|
||||
if (initialized.get()) {
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully established a connection to the Redis server");
|
||||
log(Level.INFO, "Successfully established a connection to the Redis server");
|
||||
} else {
|
||||
throw new HuskSyncInitializationException("Failed to establish a connection to the Redis server. " +
|
||||
"Please check the supplied Redis credentials in the config file");
|
||||
"Please check the supplied Redis credentials in the config file");
|
||||
}
|
||||
|
||||
// Register events
|
||||
getLoggingAdapter().log(Level.INFO, "Registering events...");
|
||||
log(Level.INFO, "Registering events...");
|
||||
this.eventListener = new BukkitEventListener(this);
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully registered events listener");
|
||||
log(Level.INFO, "Successfully registered events listener");
|
||||
|
||||
// Register permissions
|
||||
getLoggingAdapter().log(Level.INFO, "Registering permissions & commands...");
|
||||
log(Level.INFO, "Registering permissions & commands...");
|
||||
Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager()
|
||||
.addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) {
|
||||
case EVERYONE -> PermissionDefault.TRUE;
|
||||
@@ -163,32 +171,32 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand);
|
||||
}
|
||||
}
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully registered permissions & commands");
|
||||
log(Level.INFO, "Successfully registered permissions & commands");
|
||||
|
||||
// Hook into plan
|
||||
if (Bukkit.getPluginManager().getPlugin("Plan") != null) {
|
||||
getLoggingAdapter().log(Level.INFO, "Enabling Plan integration...");
|
||||
new PlanHook(database, logger).hookIntoPlan();
|
||||
getLoggingAdapter().log(Level.INFO, "Plan integration enabled!");
|
||||
log(Level.INFO, "Enabling Plan integration...");
|
||||
new PlanHook(this).hookIntoPlan();
|
||||
log(Level.INFO, "Plan integration enabled!");
|
||||
}
|
||||
|
||||
// Hook into bStats metrics
|
||||
try {
|
||||
new Metrics(this, METRICS_ID);
|
||||
} catch (final Exception e) {
|
||||
getLoggingAdapter().log(Level.WARNING, "Skipped bStats metrics initialization due to an exception.");
|
||||
log(Level.WARNING, "Skipped bStats metrics initialization due to an exception.");
|
||||
}
|
||||
|
||||
// Check for updates
|
||||
if (settings.checkForUpdates) {
|
||||
getLoggingAdapter().log(Level.INFO, "Checking for updates...");
|
||||
if (settings.doCheckForUpdates()) {
|
||||
log(Level.INFO, "Checking for updates...");
|
||||
getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
||||
newestVersion.ifPresent(newVersion -> getLoggingAdapter().log(Level.WARNING,
|
||||
newestVersion.ifPresent(newVersion -> log(Level.WARNING,
|
||||
"An update is available for HuskSync, v" + newVersion
|
||||
+ " (Currently running v" + getPluginVersion() + ")")));
|
||||
+ " (Currently running v" + getPluginVersion() + ")")));
|
||||
}
|
||||
} catch (HuskSyncInitializationException exception) {
|
||||
getLoggingAdapter().log(Level.SEVERE, """
|
||||
} catch (IllegalStateException exception) {
|
||||
log(Level.SEVERE, """
|
||||
***************************************************
|
||||
|
||||
Failed to initialize HuskSync!
|
||||
@@ -203,14 +211,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
.replaceAll("%error_message%", exception.getMessage()));
|
||||
initialized.set(false);
|
||||
} catch (Exception exception) {
|
||||
getLoggingAdapter().log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception);
|
||||
log(Level.SEVERE, "An unhandled exception occurred initializing HuskSync!", exception);
|
||||
initialized.set(false);
|
||||
} finally {
|
||||
// Validate initialization
|
||||
if (initialized.get()) {
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion());
|
||||
log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion());
|
||||
} else {
|
||||
getLoggingAdapter().log(Level.SEVERE, "Failed to initialize HuskSync. The plugin will now be disabled");
|
||||
log(Level.SEVERE, "Failed to initialize HuskSync. The plugin will now be disabled");
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
}
|
||||
}
|
||||
@@ -221,7 +229,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
if (this.eventListener != null) {
|
||||
this.eventListener.handlePluginDisable();
|
||||
}
|
||||
getLoggingAdapter().log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
||||
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -275,14 +283,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Logger getLoggingAdapter() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public ResourceReader getResourceReader() {
|
||||
return resourceReader;
|
||||
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
||||
if (throwable.length > 0) {
|
||||
getLogger().log(level, message, throwable[0]);
|
||||
} else {
|
||||
getLogger().log(level, message);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -294,7 +300,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
@NotNull
|
||||
@Override
|
||||
public Version getMinecraftVersion() {
|
||||
return Version.fromMinecraftVersionString(Bukkit.getBukkitVersion());
|
||||
return Version.fromString(Bukkit.getBukkitVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -307,6 +313,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
return audiences;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getLockedPlayers() {
|
||||
return this.eventListener.getLockedPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> reload() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
@@ -316,13 +327,13 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
||||
|
||||
// Load locales from language preset default
|
||||
final Locales languagePresets = Annotaml.create(Locales.class,
|
||||
Objects.requireNonNull(getResource("locales/" + settings.language + ".yml"))).get();
|
||||
this.locales = Annotaml.create(new File(getDataFolder(), "messages_" + settings.language + ".yml"),
|
||||
Objects.requireNonNull(getResource("locales/" + settings.getLanguage() + ".yml"))).get();
|
||||
this.locales = Annotaml.create(new File(getDataFolder(), "messages_" + settings.getLanguage() + ".yml"),
|
||||
languagePresets).get();
|
||||
return true;
|
||||
} catch (IOException | NullPointerException | InvocationTargetException | IllegalAccessException |
|
||||
InstantiationException e) {
|
||||
getLoggingAdapter().log(Level.SEVERE, "Failed to load data from the config", e);
|
||||
log(Level.SEVERE, "Failed to load data from the config", e);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.api;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
@@ -18,13 +37,12 @@ public class BrigadierUtil {
|
||||
protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand,
|
||||
@NotNull CommandBase command) {
|
||||
// Register command descriptions via commodore (brigadier wrapper)
|
||||
try (InputStream pluginFile = plugin.getResourceReader()
|
||||
.getResource("commodore/" + command.command + ".commodore")) {
|
||||
try (InputStream pluginFile = plugin.getResource("commodore/" + command.command + ".commodore")) {
|
||||
CommodoreProvider.getCommodore(plugin).register(pluginCommand,
|
||||
CommodoreFileReader.INSTANCE.parse(pluginFile),
|
||||
player -> player.hasPermission(command.permission));
|
||||
} catch (IOException e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
||||
plugin.log(Level.SEVERE,
|
||||
"Failed to load " + command.command + ".commodore command definitions", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import me.lucko.commodore.CommodoreProvider;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.mapdataapi.MapData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.MapMeta;
|
||||
import org.bukkit.map.*;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Handles the persistence of {@link MapData} into {@link ItemStack}s.
|
||||
*/
|
||||
public class BukkitMapHandler {
|
||||
|
||||
private static final BukkitHuskSync plugin = BukkitHuskSync.getInstance();
|
||||
private static final NamespacedKey MAP_DATA_KEY = new NamespacedKey(plugin, "map_data");
|
||||
|
||||
/**
|
||||
* Get the {@link MapData} from the given {@link ItemStack} and persist it in its' data container
|
||||
*
|
||||
* @param itemStack the {@link ItemStack} to get the {@link MapData} from
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
public static void persistMapData(@Nullable ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() != Material.FILLED_MAP) {
|
||||
return;
|
||||
}
|
||||
final MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
|
||||
if (mapMeta == null || !mapMeta.hasMapView()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the map view from the map
|
||||
final MapView mapView;
|
||||
try {
|
||||
mapView = Bukkit.getScheduler().callSyncMethod(plugin, mapMeta::getMapView).get();
|
||||
if (mapView == null || !mapView.isLocked() || mapView.isVirtual()) {
|
||||
return;
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to save map data for a player", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the map data
|
||||
plugin.debug("Rendering map view onto canvas for locked map");
|
||||
final LockedMapCanvas canvas = new LockedMapCanvas(mapView);
|
||||
for (MapRenderer renderer : mapView.getRenderers()) {
|
||||
renderer.render(mapView, canvas, Bukkit.getServer()
|
||||
.getOnlinePlayers().stream()
|
||||
.findAny()
|
||||
.orElse(null));
|
||||
}
|
||||
|
||||
// Save the extracted rendered map data
|
||||
plugin.debug("Saving pixel canvas data for locked map");
|
||||
if (!mapMeta.getPersistentDataContainer().has(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY)) {
|
||||
mapMeta.getPersistentDataContainer().set(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY,
|
||||
canvas.extractMapData().toBytes());
|
||||
itemStack.setItemMeta(mapMeta);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the map data of the given {@link ItemStack} to the given {@link MapData}, applying a map view to the item stack
|
||||
*
|
||||
* @param itemStack the {@link ItemStack} to set the map data of
|
||||
*/
|
||||
public static void setMapRenderer(@Nullable ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType() != Material.FILLED_MAP) {
|
||||
return;
|
||||
}
|
||||
|
||||
final MapMeta mapMeta = (MapMeta) itemStack.getItemMeta();
|
||||
if (mapMeta == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!itemStack.getItemMeta().getPersistentDataContainer().has(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final byte[] serializedData = itemStack.getItemMeta().getPersistentDataContainer()
|
||||
.get(MAP_DATA_KEY, PersistentDataType.BYTE_ARRAY);
|
||||
final MapData mapData = MapData.fromByteArray(Objects.requireNonNull(serializedData));
|
||||
plugin.debug("Setting deserialized map data for an item stack");
|
||||
|
||||
// Create a new map view renderer with the map data color at each pixel
|
||||
final MapView view = Bukkit.createMap(Bukkit.getWorlds().get(0));
|
||||
view.getRenderers().clear();
|
||||
view.addRenderer(new PersistentMapRenderer(mapData));
|
||||
view.setLocked(true);
|
||||
view.setScale(MapView.Scale.NORMAL);
|
||||
view.setTrackingPosition(false);
|
||||
view.setUnlimitedTracking(false);
|
||||
mapMeta.setMapView(view);
|
||||
itemStack.setItemMeta(mapMeta);
|
||||
plugin.debug("Successfully applied renderer to map item stack");
|
||||
} catch (IOException | NullPointerException e) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to deserialize map data for a player", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MapRenderer} that can be used to render persistently serialized {@link MapData} to a {@link MapView}
|
||||
*/
|
||||
public static class PersistentMapRenderer extends MapRenderer {
|
||||
|
||||
private final MapData mapData;
|
||||
|
||||
private PersistentMapRenderer(@NotNull MapData mapData) {
|
||||
super(false);
|
||||
this.mapData = mapData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void render(@NotNull MapView map, @NotNull MapCanvas canvas, @NotNull Player player) {
|
||||
for (int i = 0; i < 128; i++) {
|
||||
for (int j = 0; j < 128; j++) {
|
||||
// We set the pixels in this order to avoid the map being rendered upside down
|
||||
canvas.setPixel(j, i, (byte) mapData.getColorAt(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MapCanvas} implementation used for pre-rendering maps to be converted into {@link MapData}
|
||||
*/
|
||||
public static class LockedMapCanvas implements MapCanvas {
|
||||
|
||||
private final MapView mapView;
|
||||
private final int[][] pixels = new int[128][128];
|
||||
private MapCursorCollection cursors;
|
||||
|
||||
private LockedMapCanvas(@NotNull MapView mapView) {
|
||||
this.mapView = mapView;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MapView getMapView() {
|
||||
return mapView;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public MapCursorCollection getCursors() {
|
||||
return cursors == null ? (cursors = new MapCursorCollection()) : cursors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCursors(@NotNull MapCursorCollection cursors) {
|
||||
this.cursors = cursors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPixel(int x, int y, byte color) {
|
||||
pixels[x][y] = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getPixel(int x, int y) {
|
||||
return (byte) pixels[x][y];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getBasePixel(int x, int y) {
|
||||
return getPixel(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawImage(int x, int y, @NotNull Image image) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawText(int x, int y, @NotNull MapFont font, @NotNull String text) {
|
||||
// Not implemented
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getDimension() {
|
||||
return mapView.getWorld() == null ? "minecraft:overworld"
|
||||
: switch (mapView.getWorld().getEnvironment()) {
|
||||
case NETHER -> "minecraft:the_nether";
|
||||
case THE_END -> "minecraft:the_end";
|
||||
default -> "minecraft:overworld";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the map data from the canvas. Must be rendered first
|
||||
* @return the extracted map data
|
||||
*/
|
||||
@NotNull
|
||||
private MapData extractMapData() {
|
||||
return MapData.fromPixels(pixels, getDimension(), (byte) 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public record BukkitPersistentTypeMapping<T, Z>(PersistentDataTagType type, PersistentDataType<T, Z> bukkitType) {
|
||||
|
||||
public static final BukkitPersistentTypeMapping<?, ?>[] PRIMITIVE_TYPE_MAPPINGS = new BukkitPersistentTypeMapping<?, ?>[]{
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.BYTE, PersistentDataType.BYTE),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.SHORT, PersistentDataType.SHORT),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.INTEGER, PersistentDataType.INTEGER),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.LONG, PersistentDataType.LONG),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.FLOAT, PersistentDataType.FLOAT),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.DOUBLE, PersistentDataType.DOUBLE),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.STRING, PersistentDataType.STRING),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.BYTE_ARRAY, PersistentDataType.BYTE_ARRAY),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.INTEGER_ARRAY, PersistentDataType.INTEGER_ARRAY),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.LONG_ARRAY, PersistentDataType.LONG_ARRAY),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.TAG_CONTAINER_ARRAY, PersistentDataType.TAG_CONTAINER_ARRAY),
|
||||
new BukkitPersistentTypeMapping<>(PersistentDataTagType.TAG_CONTAINER, PersistentDataType.TAG_CONTAINER)
|
||||
};
|
||||
|
||||
public BukkitPersistentTypeMapping(@NotNull PersistentDataTagType type, @NotNull PersistentDataType<T, Z> bukkitType) {
|
||||
this.type = type;
|
||||
this.bukkitType = bukkitType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PersistentDataTag<Z> getContainerValue(@NotNull PersistentDataContainer container, @NotNull NamespacedKey key) throws NullPointerException {
|
||||
return new PersistentDataTag<>(type, Objects.requireNonNull(container.get(key, bukkitType)));
|
||||
}
|
||||
|
||||
public void setContainerValue(@NotNull PersistentDataContainerData container, @NotNull Player player, @NotNull NamespacedKey key) throws NullPointerException {
|
||||
container.getTagValue(key.toString(), bukkitType.getComplexType())
|
||||
.ifPresent(value -> player.getPersistentDataContainer().set(key, bukkitType, value));
|
||||
}
|
||||
|
||||
public static Optional<BukkitPersistentTypeMapping<?, ?>> getMapping(@NotNull PersistentDataTagType type) {
|
||||
for (BukkitPersistentTypeMapping<?, ?> mapping : PRIMITIVE_TYPE_MAPPINGS) {
|
||||
if (mapping.type().equals(type)) {
|
||||
return Optional.of(mapping);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,6 +1,26 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||
@@ -40,14 +60,18 @@ public class BukkitSerializer {
|
||||
bukkitOutputStream.writeInt(inventoryContents.length);
|
||||
|
||||
// Write each serialize each ItemStack to the output stream
|
||||
final boolean persistLockedMaps = BukkitHuskSync.getInstance().getSettings().getSynchronizationFeature(Settings.SynchronizationFeature.LOCKED_MAPS);
|
||||
for (ItemStack inventoryItem : inventoryContents) {
|
||||
if (persistLockedMaps) {
|
||||
BukkitMapHandler.persistMapData(inventoryItem);
|
||||
}
|
||||
bukkitOutputStream.writeObject(serializeItemStack(inventoryItem));
|
||||
}
|
||||
|
||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to serialize item stack data", e);
|
||||
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize item stack data", e);
|
||||
throw new DataSerializationException("Failed to serialize item stack data", e);
|
||||
}
|
||||
});
|
||||
@@ -89,8 +113,13 @@ public class BukkitSerializer {
|
||||
|
||||
// Set the ItemStacks in the array from deserialized ItemStack data
|
||||
int slotIndex = 0;
|
||||
final boolean persistLockedMaps = BukkitHuskSync.getInstance().getSettings().getSynchronizationFeature(Settings.SynchronizationFeature.LOCKED_MAPS);
|
||||
for (ItemStack ignored : inventoryContents) {
|
||||
inventoryContents[slotIndex] = deserializeItemStack(bukkitInputStream.readObject());
|
||||
final ItemStack deserialized = deserializeItemStack(bukkitInputStream.readObject());
|
||||
if (persistLockedMaps) {
|
||||
BukkitMapHandler.setMapRenderer(deserialized);
|
||||
}
|
||||
inventoryContents[slotIndex] = deserialized;
|
||||
slotIndex++;
|
||||
}
|
||||
|
||||
@@ -98,7 +127,7 @@ public class BukkitSerializer {
|
||||
return inventoryContents;
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to deserialize item stack data", e);
|
||||
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize item stack data", e);
|
||||
throw new DataSerializationException("Failed to deserialize item stack data", e);
|
||||
}
|
||||
});
|
||||
@@ -155,7 +184,7 @@ public class BukkitSerializer {
|
||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||
} catch (IOException e) {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to serialize potion effect data", e);
|
||||
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize potion effect data", e);
|
||||
throw new DataSerializationException("Failed to serialize potion effect data", e);
|
||||
}
|
||||
});
|
||||
@@ -191,7 +220,7 @@ public class BukkitSerializer {
|
||||
return potionEffects;
|
||||
}
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.SEVERE, "Failed to deserialize potion effect data", e);
|
||||
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize potion effect data", e);
|
||||
throw new DataSerializationException("Failed to deserialize potion effects", e);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.UserData;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.listener;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface BukkitDeathEventListener extends Listener {
|
||||
|
||||
boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority);
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
default void onPlayerDeathHighest(@NotNull PlayerDeathEvent event) {
|
||||
if (handleEvent(Settings.EventType.DEATH_LISTENER, Settings.EventPriority.HIGHEST)) {
|
||||
handlePlayerDeath(event);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
default void onPlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||
if (handleEvent(Settings.EventType.DEATH_LISTENER, Settings.EventPriority.NORMAL)) {
|
||||
handlePlayerDeath(event);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
default void onPlayerDeathLowest(@NotNull PlayerDeathEvent event) {
|
||||
if (handleEvent(Settings.EventType.DEATH_LISTENER, Settings.EventPriority.LOWEST)) {
|
||||
handlePlayerDeath(event);
|
||||
}
|
||||
}
|
||||
|
||||
void handlePlayerDeath(@NotNull PlayerDeathEvent player);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,26 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.listener;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.BukkitInventoryMap;
|
||||
import net.william278.husksync.data.BukkitSerializer;
|
||||
import net.william278.husksync.data.ItemData;
|
||||
@@ -8,6 +28,7 @@ import net.william278.husksync.player.BukkitPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.entity.Projectile;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -16,57 +37,65 @@ import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||
import org.bukkit.event.entity.ProjectileLaunchEvent;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||
import org.bukkit.event.inventory.PrepareItemCraftEvent;
|
||||
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.world.WorldSaveEvent;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class BukkitEventListener extends EventListener implements Listener {
|
||||
public class BukkitEventListener extends EventListener implements BukkitJoinEventListener, BukkitQuitEventListener,
|
||||
BukkitDeathEventListener, Listener {
|
||||
protected final List<String> blacklistedCommands;
|
||||
|
||||
public BukkitEventListener(@NotNull BukkitHuskSync huskSync) {
|
||||
super(huskSync);
|
||||
this.blacklistedCommands = huskSync.getSettings().getBlacklistedCommandsWhileLocked();
|
||||
Bukkit.getServer().getPluginManager().registerEvents(this, huskSync);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
super.handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
||||
@Override
|
||||
public boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority) {
|
||||
return plugin.getSettings().getEventPriority(type).equals(priority);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
super.handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
||||
@Override
|
||||
public void handlePlayerQuit(@NotNull BukkitPlayer bukkitPlayer) {
|
||||
final Player player = bukkitPlayer.getPlayer();
|
||||
if (!bukkitPlayer.isLocked() && !player.getItemOnCursor().getType().isAir()) {
|
||||
player.getWorld().dropItem(player.getLocation(), player.getItemOnCursor());
|
||||
player.setItemOnCursor(null);
|
||||
}
|
||||
super.handlePlayerQuit(bukkitPlayer);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onWorldSave(@NotNull WorldSaveEvent event) {
|
||||
// Handle saving player data snapshots when the world saves
|
||||
if (!plugin.getSettings().saveOnWorldSave) return;
|
||||
|
||||
CompletableFuture.runAsync(() -> super.saveOnWorldSave(event.getWorld().getPlayers()
|
||||
.stream().map(BukkitPlayer::adapt)
|
||||
.collect(Collectors.toList())));
|
||||
@Override
|
||||
public void handlePlayerJoin(@NotNull BukkitPlayer bukkitPlayer) {
|
||||
super.handlePlayerJoin(bukkitPlayer);
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
||||
@Override
|
||||
public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||
final OnlineUser user = BukkitPlayer.adapt(event.getEntity());
|
||||
|
||||
// If the player is locked or the plugin disabling, clear their drops
|
||||
if (cancelPlayerEvent(user)) {
|
||||
if (cancelPlayerEvent(user.uuid)) {
|
||||
event.getDrops().clear();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle saving player data snapshots on death
|
||||
if (!plugin.getSettings().saveOnDeath) return;
|
||||
if (!plugin.getSettings().doSaveOnDeath()) return;
|
||||
|
||||
// Truncate the drops list to the inventory size and save the player's inventory
|
||||
final int maxInventorySize = BukkitInventoryMap.INVENTORY_SLOT_COUNT;
|
||||
@@ -77,50 +106,91 @@ public class BukkitEventListener extends EventListener implements Listener {
|
||||
.thenAccept(serializedDrops -> super.saveOnPlayerDeath(user, new ItemData(serializedDrops)));
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onWorldSave(@NotNull WorldSaveEvent event) {
|
||||
// Handle saving player data snapshots when the world saves
|
||||
if (!plugin.getSettings().doSaveOnWorldSave()) return;
|
||||
|
||||
CompletableFuture.runAsync(() -> super.saveOnWorldSave(event.getWorld().getPlayers()
|
||||
.stream().map(BukkitPlayer::adapt)
|
||||
.collect(Collectors.toList())));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Events to cancel if the player has not been set yet
|
||||
*/
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onProjectileLaunch(@NotNull ProjectileLaunchEvent event) {
|
||||
final Projectile projectile = event.getEntity();
|
||||
if (projectile.getShooter() instanceof Player player) {
|
||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onDropItem(@NotNull PlayerDropItemEvent event) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
|
||||
if (event.getEntity() instanceof Player player) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerInteractEntity(@NotNull PlayerInteractEntityEvent event) {
|
||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onBlockBreak(@NotNull BlockBreakEvent event) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(event.getPlayer())));
|
||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
||||
if (event.getPlayer() instanceof Player player) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onInventoryClick(@NotNull InventoryClickEvent event) {
|
||||
event.setCancelled(cancelPlayerEvent(event.getWhoClicked().getUniqueId()));
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onCraftItem(@NotNull PrepareItemCraftEvent event) {
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
||||
if (event.getEntity() instanceof Player player) {
|
||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
||||
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||
public void onPermissionCommand(@NotNull PlayerCommandPreprocessEvent event) {
|
||||
String[] commandArgs = event.getMessage().substring(1).split(" ");
|
||||
String commandLabel = commandArgs[0].toLowerCase(Locale.ENGLISH);
|
||||
|
||||
if (blacklistedCommands.contains("*") || blacklistedCommands.contains(commandLabel)) {
|
||||
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.listener;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface BukkitJoinEventListener extends Listener {
|
||||
|
||||
boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority);
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
default void onPlayerJoinHighest(@NotNull PlayerJoinEvent event) {
|
||||
if (handleEvent(Settings.EventType.JOIN_LISTENER, Settings.EventPriority.HIGHEST)) {
|
||||
handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
default void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
||||
if (handleEvent(Settings.EventType.JOIN_LISTENER, Settings.EventPriority.NORMAL)) {
|
||||
handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
default void onPlayerJoinLowest(@NotNull PlayerJoinEvent event) {
|
||||
if (handleEvent(Settings.EventType.JOIN_LISTENER, Settings.EventPriority.LOWEST)) {
|
||||
handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
||||
}
|
||||
}
|
||||
|
||||
void handlePlayerJoin(@NotNull BukkitPlayer player);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.listener;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.player.BukkitPlayer;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface BukkitQuitEventListener extends Listener {
|
||||
|
||||
boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority);
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
default void onPlayerQuitHighest(@NotNull PlayerQuitEvent event) {
|
||||
if (handleEvent(Settings.EventType.QUIT_LISTENER, Settings.EventPriority.HIGHEST)) {
|
||||
handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
|
||||
default void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
||||
if (handleEvent(Settings.EventType.QUIT_LISTENER, Settings.EventPriority.NORMAL)) {
|
||||
handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true)
|
||||
default void onPlayerQuitLowest(@NotNull PlayerQuitEvent event) {
|
||||
if (handleEvent(Settings.EventType.QUIT_LISTENER, Settings.EventPriority.LOWEST)) {
|
||||
handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
||||
}
|
||||
}
|
||||
|
||||
void handlePlayerQuit(@NotNull BukkitPlayer player);
|
||||
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.migrator;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
@@ -37,11 +56,11 @@ public class LegacyMigrator extends Migrator {
|
||||
public LegacyMigrator(@NotNull HuskSync plugin) {
|
||||
super(plugin);
|
||||
this.hslConverter = HSLConverter.getInstance();
|
||||
this.sourceHost = plugin.getSettings().mySqlHost;
|
||||
this.sourcePort = plugin.getSettings().mySqlPort;
|
||||
this.sourceUsername = plugin.getSettings().mySqlUsername;
|
||||
this.sourcePassword = plugin.getSettings().mySqlPassword;
|
||||
this.sourceDatabase = plugin.getSettings().mySqlDatabase;
|
||||
this.sourceHost = plugin.getSettings().getMySqlHost();
|
||||
this.sourcePort = plugin.getSettings().getMySqlPort();
|
||||
this.sourceUsername = plugin.getSettings().getMySqlUsername();
|
||||
this.sourcePassword = plugin.getSettings().getMySqlPassword();
|
||||
this.sourceDatabase = plugin.getSettings().getMySqlDatabase();
|
||||
this.sourcePlayersTable = "husksync_players";
|
||||
this.sourceDataTable = "husksync_data";
|
||||
this.minecraftVersion = plugin.getMinecraftVersion().toString();
|
||||
@@ -49,26 +68,26 @@ public class LegacyMigrator extends Migrator {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> start() {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Starting migration of legacy HuskSync v1.x data...");
|
||||
plugin.log(Level.INFO, "Starting migration of legacy HuskSync v1.x data...");
|
||||
final long startTime = System.currentTimeMillis();
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
// Wipe the existing database, preparing it for data import
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Preparing existing database (wiping)...");
|
||||
plugin.log(Level.INFO, "Preparing existing database (wiping)...");
|
||||
plugin.getDatabase().wipeDatabase().join();
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
plugin.log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
|
||||
// Create jdbc driver connection url
|
||||
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
||||
|
||||
// Create a new data source for the mpdb converter
|
||||
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Establishing connection to legacy database...");
|
||||
plugin.log(Level.INFO, "Establishing connection to legacy database...");
|
||||
connectionPool.setJdbcUrl(jdbcUrl);
|
||||
connectionPool.setUsername(sourceUsername);
|
||||
connectionPool.setPassword(sourcePassword);
|
||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase(Locale.ENGLISH));
|
||||
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)...");
|
||||
plugin.log(Level.INFO, "Downloading raw data from the legacy database (this might take a while)...");
|
||||
final List<LegacyData> dataToMigrate = new ArrayList<>();
|
||||
try (final Connection connection = connectionPool.getConnection()) {
|
||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||
@@ -106,33 +125,33 @@ public class LegacyMigrator extends Migrator {
|
||||
));
|
||||
playersMigrated++;
|
||||
if (playersMigrated % 50 == 0) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded legacy data for " + playersMigrated + " players...");
|
||||
plugin.log(Level.INFO, "Downloaded legacy data for " + playersMigrated + " players...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the legacy database!");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converting HuskSync 1.x data to the new user data format (this might take a while)...");
|
||||
plugin.log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the legacy database!");
|
||||
plugin.log(Level.INFO, "Converting HuskSync 1.x data to the new user data format (this might take a while)...");
|
||||
|
||||
final AtomicInteger playersConverted = new AtomicInteger();
|
||||
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION)
|
||||
.exceptionally(exception -> {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().username + ": " + exception.getMessage());
|
||||
plugin.log(Level.SEVERE, "Failed to migrate legacy data for " + data.user().username + ": " + exception.getMessage());
|
||||
return null;
|
||||
})).join();
|
||||
|
||||
playersConverted.getAndIncrement();
|
||||
if (playersConverted.get() % 50 == 0) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converted legacy data for " + playersConverted + " players...");
|
||||
plugin.log(Level.INFO, "Converted legacy data for " + playersConverted + " players...");
|
||||
}
|
||||
}).join());
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||
plugin.log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Error while migrating legacy data: " + e.getMessage() + " - are your source database credentials correct?");
|
||||
plugin.log(Level.SEVERE, "Error while migrating legacy data: " + e.getMessage() + " - are your source database credentials correct?");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -141,7 +160,7 @@ public class LegacyMigrator extends Migrator {
|
||||
@Override
|
||||
public void handleConfigurationCommand(@NotNull String[] args) {
|
||||
if (args.length == 2) {
|
||||
if (switch (args[0].toLowerCase()) {
|
||||
if (switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||
case "host" -> {
|
||||
this.sourceHost = args[1];
|
||||
yield true;
|
||||
@@ -176,15 +195,15 @@ public class LegacyMigrator extends Migrator {
|
||||
}
|
||||
default -> false;
|
||||
}) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||
plugin.log(Level.INFO, getHelpMenu());
|
||||
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||
obfuscateDataString(args[1]));
|
||||
} else {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||
}
|
||||
} else {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
||||
plugin.log(Level.INFO, getHelpMenu());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.migrator;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
@@ -17,6 +36,7 @@ import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -42,11 +62,11 @@ public class MpdbMigrator extends Migrator {
|
||||
public MpdbMigrator(@NotNull BukkitHuskSync plugin, @NotNull Plugin mySqlPlayerDataBridge) {
|
||||
super(plugin);
|
||||
this.mpdbConverter = MPDBConverter.getInstance(mySqlPlayerDataBridge);
|
||||
this.sourceHost = plugin.getSettings().mySqlHost;
|
||||
this.sourcePort = plugin.getSettings().mySqlPort;
|
||||
this.sourceUsername = plugin.getSettings().mySqlUsername;
|
||||
this.sourcePassword = plugin.getSettings().mySqlPassword;
|
||||
this.sourceDatabase = plugin.getSettings().mySqlDatabase;
|
||||
this.sourceHost = plugin.getSettings().getMySqlHost();
|
||||
this.sourcePort = plugin.getSettings().getMySqlPort();
|
||||
this.sourceUsername = plugin.getSettings().getMySqlUsername();
|
||||
this.sourcePassword = plugin.getSettings().getMySqlPassword();
|
||||
this.sourceDatabase = plugin.getSettings().getMySqlDatabase();
|
||||
this.sourceInventoryTable = "mpdb_inventory";
|
||||
this.sourceEnderChestTable = "mpdb_enderchest";
|
||||
this.sourceExperienceTable = "mpdb_experience";
|
||||
@@ -56,26 +76,26 @@ public class MpdbMigrator extends Migrator {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> start() {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Starting migration from MySQLPlayerDataBridge to HuskSync...");
|
||||
plugin.log(Level.INFO, "Starting migration from MySQLPlayerDataBridge to HuskSync...");
|
||||
final long startTime = System.currentTimeMillis();
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
// Wipe the existing database, preparing it for data import
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Preparing existing database (wiping)...");
|
||||
plugin.log(Level.INFO, "Preparing existing database (wiping)...");
|
||||
plugin.getDatabase().wipeDatabase().join();
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
plugin.log(Level.INFO, "Successfully wiped user data database (took " + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
|
||||
// Create jdbc driver connection url
|
||||
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
||||
|
||||
// Create a new data source for the mpdb converter
|
||||
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Establishing connection to MySQLPlayerDataBridge database...");
|
||||
plugin.log(Level.INFO, "Establishing connection to MySQLPlayerDataBridge database...");
|
||||
connectionPool.setJdbcUrl(jdbcUrl);
|
||||
connectionPool.setUsername(sourceUsername);
|
||||
connectionPool.setPassword(sourcePassword);
|
||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase());
|
||||
connectionPool.setPoolName((getIdentifier() + "_migrator_pool").toUpperCase(Locale.ENGLISH));
|
||||
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)...");
|
||||
plugin.log(Level.INFO, "Downloading raw data from the MySQLPlayerDataBridge database (this might take a while)...");
|
||||
final List<MpdbData> dataToMigrate = new ArrayList<>();
|
||||
try (final Connection connection = connectionPool.getConnection()) {
|
||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||
@@ -103,32 +123,32 @@ public class MpdbMigrator extends Migrator {
|
||||
));
|
||||
playersMigrated++;
|
||||
if (playersMigrated % 25 == 0) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Downloaded MySQLPlayerDataBridge data for " + playersMigrated + " players...");
|
||||
plugin.log(Level.INFO, "Downloaded MySQLPlayerDataBridge data for " + playersMigrated + " players...");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the MySQLPlayerDataBridge database!");
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converting raw MySQLPlayerDataBridge data to HuskSync user data (this might take a while)...");
|
||||
plugin.log(Level.INFO, "Completed download of " + dataToMigrate.size() + " entries from the MySQLPlayerDataBridge database!");
|
||||
plugin.log(Level.INFO, "Converting raw MySQLPlayerDataBridge data to HuskSync user data (this might take a while)...");
|
||||
|
||||
final AtomicInteger playersConverted = new AtomicInteger();
|
||||
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))
|
||||
.exceptionally(exception -> {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to migrate MySQLPlayerDataBridge data for " + data.user().username + ": " + exception.getMessage());
|
||||
plugin.log(Level.SEVERE, "Failed to migrate MySQLPlayerDataBridge data for " + data.user().username + ": " + exception.getMessage());
|
||||
return null;
|
||||
}).join();
|
||||
playersConverted.getAndIncrement();
|
||||
if (playersConverted.get() % 50 == 0) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players...");
|
||||
plugin.log(Level.INFO, "Converted MySQLPlayerDataBridge data for " + playersConverted + " players...");
|
||||
}
|
||||
}).join());
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||
plugin.log(Level.INFO, "Migration complete for " + dataToMigrate.size() + " users in " + ((System.currentTimeMillis() - startTime) / 1000) + " seconds!");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Error while migrating data: " + e.getMessage() + " - are your source database credentials correct?");
|
||||
plugin.log(Level.SEVERE, "Error while migrating data: " + e.getMessage() + " - are your source database credentials correct?");
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -137,7 +157,7 @@ public class MpdbMigrator extends Migrator {
|
||||
@Override
|
||||
public void handleConfigurationCommand(@NotNull String[] args) {
|
||||
if (args.length == 2) {
|
||||
if (switch (args[0].toLowerCase()) {
|
||||
if (switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||
case "host" -> {
|
||||
this.sourceHost = args[1];
|
||||
yield true;
|
||||
@@ -176,15 +196,15 @@ public class MpdbMigrator extends Migrator {
|
||||
}
|
||||
default -> false;
|
||||
}) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||
plugin.log(Level.INFO, getHelpMenu());
|
||||
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||
obfuscateDataString(args[1]));
|
||||
} else {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||
}
|
||||
} else {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
||||
plugin.log(Level.INFO, getHelpMenu());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.player;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import de.themoep.minedown.adventure.MineDownParser;
|
||||
import dev.triumphteam.gui.builder.gui.StorageBuilder;
|
||||
import dev.triumphteam.gui.guis.Gui;
|
||||
import dev.triumphteam.gui.guis.StorageGui;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.william278.desertwell.Version;
|
||||
import net.roxeez.advancement.display.FrameType;
|
||||
import net.william278.andjam.Toast;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.*;
|
||||
@@ -16,10 +36,12 @@ import org.bukkit.advancement.AdvancementProgress;
|
||||
import org.bukkit.attribute.Attribute;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.PlayerInventory;
|
||||
import org.bukkit.persistence.PersistentDataContainer;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.potion.PotionEffect;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -35,29 +57,16 @@ import java.util.logging.Level;
|
||||
*/
|
||||
public class BukkitPlayer extends OnlineUser {
|
||||
|
||||
private static final PersistentDataType<?, ?>[] PRIMITIVE_PERSISTENT_DATA_TYPES = new PersistentDataType<?, ?>[]{
|
||||
PersistentDataType.BYTE,
|
||||
PersistentDataType.SHORT,
|
||||
PersistentDataType.INTEGER,
|
||||
PersistentDataType.LONG,
|
||||
PersistentDataType.FLOAT,
|
||||
PersistentDataType.DOUBLE,
|
||||
PersistentDataType.STRING,
|
||||
PersistentDataType.BYTE_ARRAY,
|
||||
PersistentDataType.INTEGER_ARRAY,
|
||||
PersistentDataType.LONG_ARRAY,
|
||||
PersistentDataType.TAG_CONTAINER_ARRAY,
|
||||
PersistentDataType.TAG_CONTAINER};
|
||||
|
||||
private final BukkitHuskSync plugin;
|
||||
private final Player player;
|
||||
private final Audience audience;
|
||||
|
||||
private BukkitPlayer(@NotNull Player player) {
|
||||
super(player.getUniqueId(), player.getName());
|
||||
this.plugin = BukkitHuskSync.getInstance();
|
||||
this.player = player;
|
||||
this.audience = BukkitHuskSync.getInstance().getAudiences().player(player);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static BukkitPlayer adapt(@NotNull Player player) {
|
||||
return new BukkitPlayer(player);
|
||||
}
|
||||
@@ -88,8 +97,8 @@ public class BukkitPlayer extends OnlineUser {
|
||||
@Override
|
||||
public CompletableFuture<Void> setStatus(@NotNull StatusData statusData, @NotNull Settings settings) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
||||
.getBaseValue();
|
||||
// Set max health
|
||||
double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue();
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.MAX_HEALTH)) {
|
||||
if (statusData.maxHealth != 0d) {
|
||||
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
||||
@@ -98,22 +107,33 @@ public class BukkitPlayer extends OnlineUser {
|
||||
}
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HEALTH)) {
|
||||
// Set health
|
||||
final double currentHealth = player.getHealth();
|
||||
if (statusData.health != currentHealth) {
|
||||
final double healthToSet = currentHealth > currentMaxHealth ? currentMaxHealth : statusData.health;
|
||||
if (healthToSet < 1) {
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> player.setHealth(healthToSet));
|
||||
} else {
|
||||
player.setHealth(healthToSet);
|
||||
}
|
||||
final double maxHealth = currentMaxHealth;
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
try {
|
||||
player.setHealth(Math.min(healthToSet, maxHealth));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().log(Level.WARNING,
|
||||
"Failed to set health of player " + player.getName() + " to " + healthToSet);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (statusData.healthScale != 0d) {
|
||||
player.setHealthScale(statusData.healthScale);
|
||||
} else {
|
||||
player.setHealthScale(statusData.maxHealth);
|
||||
// Set health scale
|
||||
try {
|
||||
if (statusData.healthScale != 0d) {
|
||||
player.setHealthScale(statusData.healthScale);
|
||||
} else {
|
||||
player.setHealthScale(statusData.maxHealth);
|
||||
}
|
||||
player.setHealthScaled(statusData.healthScale != 0D);
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().log(Level.WARNING,
|
||||
"Failed to set health scale of player " + player.getName() + " to " + statusData.healthScale);
|
||||
}
|
||||
player.setHealthScaled(statusData.healthScale != 0D);
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HUNGER)) {
|
||||
player.setFoodLevel(statusData.hunger);
|
||||
@@ -129,11 +149,11 @@ public class BukkitPlayer extends OnlineUser {
|
||||
player.setExp(statusData.expProgress);
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.GAME_MODE)) {
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () ->
|
||||
Bukkit.getScheduler().runTask(plugin, () ->
|
||||
player.setGameMode(GameMode.valueOf(statusData.gameMode)));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.LOCATION)) {
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (statusData.isFlying) {
|
||||
player.setAllowFlight(true);
|
||||
player.setFlying(true);
|
||||
@@ -146,7 +166,11 @@ public class BukkitPlayer extends OnlineUser {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ItemData> getInventory() {
|
||||
return BukkitSerializer.serializeItemStackArray(player.getInventory().getContents())
|
||||
final PlayerInventory inventory = player.getInventory();
|
||||
if (inventory.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(ItemData.empty());
|
||||
}
|
||||
return BukkitSerializer.serializeItemStackArray(inventory.getContents())
|
||||
.thenApply(ItemData::new);
|
||||
}
|
||||
|
||||
@@ -154,17 +178,34 @@ public class BukkitPlayer extends OnlineUser {
|
||||
public CompletableFuture<Void> setInventory(@NotNull ItemData itemData) {
|
||||
return BukkitSerializer.deserializeInventory(itemData.serializedItems).thenApplyAsync(contents -> {
|
||||
final CompletableFuture<Void> inventorySetFuture = new CompletableFuture<>();
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
this.clearInventoryCraftingSlots();
|
||||
player.setItemOnCursor(null);
|
||||
player.getInventory().setContents(contents.getContents());
|
||||
player.updateInventory();
|
||||
inventorySetFuture.complete(null);
|
||||
});
|
||||
return inventorySetFuture.join();
|
||||
});
|
||||
}
|
||||
|
||||
// Clears any items the player may have in the crafting slots of their inventory
|
||||
private void clearInventoryCraftingSlots() {
|
||||
final Inventory inventory = player.getOpenInventory().getTopInventory();
|
||||
if (inventory.getType() == InventoryType.CRAFTING) {
|
||||
for (int slot = 0; slot < 5; slot++) {
|
||||
inventory.setItem(slot, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ItemData> getEnderChest() {
|
||||
return BukkitSerializer.serializeItemStackArray(player.getEnderChest().getContents())
|
||||
final Inventory enderChest = player.getEnderChest();
|
||||
if (enderChest.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(ItemData.empty());
|
||||
}
|
||||
return BukkitSerializer.serializeItemStackArray(enderChest.getContents())
|
||||
.thenApply(ItemData::new);
|
||||
}
|
||||
|
||||
@@ -172,7 +213,7 @@ public class BukkitPlayer extends OnlineUser {
|
||||
public CompletableFuture<Void> setEnderChest(@NotNull ItemData enderChestData) {
|
||||
return BukkitSerializer.deserializeItemStackArray(enderChestData.serializedItems).thenApplyAsync(contents -> {
|
||||
final CompletableFuture<Void> enderChestSetFuture = new CompletableFuture<>();
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.getEnderChest().setContents(contents);
|
||||
enderChestSetFuture.complete(null);
|
||||
});
|
||||
@@ -191,7 +232,7 @@ public class BukkitPlayer extends OnlineUser {
|
||||
return BukkitSerializer.deserializePotionEffectArray(potionEffectData.serializedPotionEffects)
|
||||
.thenApplyAsync(effects -> {
|
||||
final CompletableFuture<Void> potionEffectsSetFuture = new CompletableFuture<>();
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
for (PotionEffect effect : player.getActivePotionEffects()) {
|
||||
player.removePotionEffect(effect.getType());
|
||||
}
|
||||
@@ -229,7 +270,7 @@ public class BukkitPlayer extends OnlineUser {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setAdvancements(@NotNull List<AdvancementData> advancementData) {
|
||||
return CompletableFuture.runAsync(() -> Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
return CompletableFuture.runAsync(() -> Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
|
||||
// Temporarily disable advancement announcing if needed
|
||||
boolean announceAdvancementUpdate = false;
|
||||
@@ -261,20 +302,20 @@ public class BukkitPlayer extends OnlineUser {
|
||||
record.completedCriteria.keySet().stream()
|
||||
.filter(criterion -> !playerProgress.getAwardedCriteria().contains(criterion))
|
||||
.forEach(criterion -> {
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
|
||||
Bukkit.getScheduler().runTask(plugin,
|
||||
() -> player.getAdvancementProgress(advancement).awardCriteria(criterion));
|
||||
correctExperience.set(true);
|
||||
});
|
||||
|
||||
// Revoke all criteria that the player does have but should not
|
||||
new ArrayList<>(playerProgress.getAwardedCriteria()).stream().filter(criterion -> !record.completedCriteria.containsKey(criterion))
|
||||
.forEach(criterion -> Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
|
||||
.forEach(criterion -> Bukkit.getScheduler().runTask(plugin,
|
||||
() -> player.getAdvancementProgress(advancement).revokeCriteria(criterion)));
|
||||
|
||||
},
|
||||
// Revoke the criteria as the player shouldn't have any
|
||||
() -> new ArrayList<>(playerProgress.getAwardedCriteria()).forEach(criterion ->
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
|
||||
Bukkit.getScheduler().runTask(plugin,
|
||||
() -> player.getAdvancementProgress(advancement).revokeCriteria(criterion))));
|
||||
|
||||
// Update the player's experience in case the advancement changed that
|
||||
@@ -286,7 +327,7 @@ public class BukkitPlayer extends OnlineUser {
|
||||
}
|
||||
|
||||
// Re-enable announcing advancements (back on main thread again)
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
if (finalAnnounceAdvancementUpdate) {
|
||||
player.getWorld().setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, true);
|
||||
}
|
||||
@@ -351,32 +392,52 @@ public class BukkitPlayer extends OnlineUser {
|
||||
@Override
|
||||
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
// Set untyped statistics
|
||||
// Set generic statistics
|
||||
for (String statistic : statisticsData.untypedStatistics.keySet()) {
|
||||
player.setStatistic(Statistic.valueOf(statistic), statisticsData.untypedStatistics.get(statistic));
|
||||
try {
|
||||
player.setStatistic(Statistic.valueOf(statistic), statisticsData.untypedStatistics.get(statistic));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().log(Level.WARNING,
|
||||
"Failed to set generic statistic " + statistic + " for " + username);
|
||||
}
|
||||
}
|
||||
|
||||
// Set block statistics
|
||||
for (String statistic : statisticsData.blockStatistics.keySet()) {
|
||||
for (String blockMaterial : statisticsData.blockStatistics.get(statistic).keySet()) {
|
||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(blockMaterial),
|
||||
statisticsData.blockStatistics.get(statistic).get(blockMaterial));
|
||||
try {
|
||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(blockMaterial),
|
||||
statisticsData.blockStatistics.get(statistic).get(blockMaterial));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().log(Level.WARNING,
|
||||
"Failed to set " + blockMaterial + " statistic " + statistic + " for " + username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set item statistics
|
||||
for (String statistic : statisticsData.itemStatistics.keySet()) {
|
||||
for (String itemMaterial : statisticsData.itemStatistics.get(statistic).keySet()) {
|
||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(itemMaterial),
|
||||
statisticsData.itemStatistics.get(statistic).get(itemMaterial));
|
||||
try {
|
||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(itemMaterial),
|
||||
statisticsData.itemStatistics.get(statistic).get(itemMaterial));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().log(Level.WARNING,
|
||||
"Failed to set " + itemMaterial + " statistic " + statistic + " for " + username);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set entity statistics
|
||||
for (String statistic : statisticsData.entityStatistics.keySet()) {
|
||||
for (String entityType : statisticsData.entityStatistics.get(statistic).keySet()) {
|
||||
player.setStatistic(Statistic.valueOf(statistic), EntityType.valueOf(entityType),
|
||||
statisticsData.entityStatistics.get(statistic).get(entityType));
|
||||
try {
|
||||
player.setStatistic(Statistic.valueOf(statistic), EntityType.valueOf(entityType),
|
||||
statisticsData.entityStatistics.get(statistic).get(entityType));
|
||||
} catch (IllegalArgumentException e) {
|
||||
plugin.getLogger().log(Level.WARNING,
|
||||
"Failed to set " + entityType + " statistic " + statistic + " for " + username);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -402,7 +463,7 @@ public class BukkitPlayer extends OnlineUser {
|
||||
.valueOf(locationData.worldEnvironment)).findFirst().ifPresent(bukkitWorld::set);
|
||||
}
|
||||
if (bukkitWorld.get() != null) {
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
player.teleport(new Location(bukkitWorld.get(),
|
||||
locationData.x, locationData.y, locationData.z,
|
||||
locationData.yaw, locationData.pitch), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||
@@ -414,65 +475,24 @@ public class BukkitPlayer extends OnlineUser {
|
||||
|
||||
@Override
|
||||
public CompletableFuture<PersistentDataContainerData> getPersistentDataContainer() {
|
||||
final Map<String, PersistentDataTag<?>> persistentDataMap = new HashMap<>();
|
||||
final PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
final PersistentDataContainer container = player.getPersistentDataContainer();
|
||||
if (container.isEmpty()) {
|
||||
return new PersistentDataContainerData(new HashMap<>());
|
||||
}
|
||||
final HashMap<String, PersistentDataTag<?>> persistentDataMap = new HashMap<>();
|
||||
for (final NamespacedKey key : container.getKeys()) {
|
||||
PersistentDataType<?, ?> type = null;
|
||||
for (PersistentDataType<?, ?> dataType : PRIMITIVE_PERSISTENT_DATA_TYPES) {
|
||||
if (container.has(key, dataType)) {
|
||||
container.getKeys().forEach(key -> {
|
||||
BukkitPersistentTypeMapping<?, ?> type = null;
|
||||
for (BukkitPersistentTypeMapping<?, ?> dataType : BukkitPersistentTypeMapping.PRIMITIVE_TYPE_MAPPINGS) {
|
||||
if (container.has(key, dataType.bukkitType())) {
|
||||
type = dataType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (type != null) {
|
||||
// This is absolutely disgusting code and needs to be swiftly put out of its misery with a refactor
|
||||
final Class<?> primitiveType = type.getPrimitiveType();
|
||||
if (String.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.STRING,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.STRING))));
|
||||
} else if (int.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.INTEGER,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.INTEGER))));
|
||||
} else if (double.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.DOUBLE,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.DOUBLE))));
|
||||
} else if (float.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.FLOAT,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.FLOAT))));
|
||||
} else if (long.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.LONG,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.LONG))));
|
||||
} else if (short.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.SHORT,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.SHORT))));
|
||||
} else if (byte.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.BYTE,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.BYTE))));
|
||||
} else if (byte[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.BYTE_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.BYTE_ARRAY))));
|
||||
} else if (int[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.INTEGER_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.INTEGER_ARRAY))));
|
||||
} else if (long[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.LONG_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.LONG_ARRAY))));
|
||||
} else if (PersistentDataContainer.class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.TAG_CONTAINER,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.TAG_CONTAINER))));
|
||||
} else if (PersistentDataContainer[].class.equals(primitiveType)) {
|
||||
persistentDataMap.put(key.toString(), new PersistentDataTag<>(BukkitPersistentDataTagType.TAG_CONTAINER_ARRAY,
|
||||
Objects.requireNonNull(container.get(key, PersistentDataType.TAG_CONTAINER_ARRAY))));
|
||||
}
|
||||
persistentDataMap.put(key.toString(), type.getContainerValue(container, key));
|
||||
}
|
||||
}
|
||||
});
|
||||
return new PersistentDataContainerData(persistentDataMap);
|
||||
}).exceptionally(throwable -> {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
||||
plugin.log(Level.WARNING,
|
||||
"Could not read " + player.getName() + "'s persistent data map, skipping!");
|
||||
throwable.printStackTrace();
|
||||
return new PersistentDataContainerData(new HashMap<>());
|
||||
@@ -480,71 +500,36 @@ public class BukkitPlayer extends OnlineUser {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData) {
|
||||
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData container) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
player.getPersistentDataContainer().getKeys().forEach(namespacedKey ->
|
||||
player.getPersistentDataContainer().remove(namespacedKey));
|
||||
persistentDataContainerData.getTags().forEach(keyString -> {
|
||||
container.getTags().forEach(keyString -> {
|
||||
final NamespacedKey key = NamespacedKey.fromString(keyString);
|
||||
if (key != null) {
|
||||
// Set a tag with the given key and value. This is crying out for a refactor.
|
||||
persistentDataContainerData.getTagType(keyString).ifPresentOrElse(dataType -> {
|
||||
switch (dataType) {
|
||||
case BYTE -> persistentDataContainerData.getTagValue(keyString, byte.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.BYTE, value));
|
||||
case SHORT -> persistentDataContainerData.getTagValue(keyString, short.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.SHORT, value));
|
||||
case INTEGER -> persistentDataContainerData.getTagValue(keyString, int.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.INTEGER, value));
|
||||
case LONG -> persistentDataContainerData.getTagValue(keyString, long.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.LONG, value));
|
||||
case FLOAT -> persistentDataContainerData.getTagValue(keyString, float.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.FLOAT, value));
|
||||
case DOUBLE -> persistentDataContainerData.getTagValue(keyString, double.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.DOUBLE, value));
|
||||
case STRING -> persistentDataContainerData.getTagValue(keyString, String.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.STRING, value));
|
||||
case BYTE_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, byte[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.BYTE_ARRAY, value));
|
||||
case INTEGER_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, int[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.INTEGER_ARRAY, value));
|
||||
case LONG_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, long[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.LONG_ARRAY, value));
|
||||
case TAG_CONTAINER ->
|
||||
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer.class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.TAG_CONTAINER, value));
|
||||
case TAG_CONTAINER_ARRAY ->
|
||||
persistentDataContainerData.getTagValue(keyString, PersistentDataContainer[].class).ifPresent(
|
||||
value -> player.getPersistentDataContainer().set(key,
|
||||
PersistentDataType.TAG_CONTAINER_ARRAY, value));
|
||||
}
|
||||
}, () -> BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
||||
"Could not set " + player.getName() + "'s persistent data key " + keyString +
|
||||
" as it has an invalid type. Skipping!"));
|
||||
container.getTagType(keyString)
|
||||
.flatMap(BukkitPersistentTypeMapping::getMapping)
|
||||
.ifPresentOrElse(mapping -> mapping.setContainerValue(container, player, key),
|
||||
() -> plugin.log(Level.WARNING,
|
||||
"Could not set " + player.getName() + "'s persistent data key " + keyString +
|
||||
" as it has an invalid type. Skipping!"));
|
||||
}
|
||||
});
|
||||
}).exceptionally(throwable -> {
|
||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
||||
plugin.log(Level.WARNING,
|
||||
"Could not write " + player.getName() + "'s persistent data map, skipping!");
|
||||
throwable.printStackTrace();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Audience getAudience() {
|
||||
return plugin.getAudiences().player(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOffline() {
|
||||
try {
|
||||
@@ -558,7 +543,7 @@ public class BukkitPlayer extends OnlineUser {
|
||||
@NotNull
|
||||
@Override
|
||||
public Version getMinecraftVersion() {
|
||||
return Version.fromMinecraftVersionString(Bukkit.getBukkitVersion());
|
||||
return Version.fromString(Bukkit.getBukkitVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -573,66 +558,69 @@ public class BukkitPlayer extends OnlineUser {
|
||||
|
||||
// Deserialize the item data to be shown and show it in a triumph GUI
|
||||
BukkitSerializer.deserializeItemStackArray(itemData.serializedItems).thenAccept(items -> {
|
||||
try {
|
||||
// Build the GUI and populate with items
|
||||
final int itemCount = items.length;
|
||||
final StorageBuilder guiBuilder = Gui.storage()
|
||||
.title(title.toComponent())
|
||||
.rows(Math.max(minimumRows, (int) Math.ceil(itemCount / 9.0)))
|
||||
.disableAllInteractions()
|
||||
.enableOtherActions();
|
||||
final StorageGui gui = editable ? guiBuilder.enableAllInteractions().create() : guiBuilder.create();
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (items[i] != null) {
|
||||
gui.getInventory().setItem(i, items[i]);
|
||||
}
|
||||
// Build the GUI and populate with items
|
||||
final int itemCount = items.length;
|
||||
final StorageBuilder guiBuilder = Gui.storage()
|
||||
.title(title.toComponent())
|
||||
.rows(Math.max(minimumRows, (int) Math.ceil(itemCount / 9.0)))
|
||||
.disableAllInteractions()
|
||||
.enableOtherActions();
|
||||
final StorageGui gui = editable ? guiBuilder.enableAllInteractions().create() : guiBuilder.create();
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
if (items[i] != null) {
|
||||
gui.getInventory().setItem(i, items[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the future with updated data (if editable) when the GUI is closed
|
||||
gui.setCloseGuiAction(event -> {
|
||||
if (!editable) {
|
||||
updatedData.complete(Optional.empty());
|
||||
return;
|
||||
}
|
||||
|
||||
// Complete the future with updated data (if editable) when the GUI is closed
|
||||
gui.setCloseGuiAction(event -> {
|
||||
if (!editable) {
|
||||
// Get and save the updated items
|
||||
final ItemStack[] updatedItems = Arrays.copyOf(event.getPlayer().getOpenInventory()
|
||||
.getTopInventory().getContents().clone(), itemCount);
|
||||
BukkitSerializer.serializeItemStackArray(updatedItems).thenAccept(serializedItems -> {
|
||||
if (serializedItems.equals(itemData.serializedItems)) {
|
||||
updatedData.complete(Optional.empty());
|
||||
return;
|
||||
}
|
||||
|
||||
// Get and save the updated items
|
||||
final ItemStack[] updatedItems = Arrays.copyOf(event.getPlayer().getOpenInventory()
|
||||
.getTopInventory().getContents().clone(), itemCount);
|
||||
BukkitSerializer.serializeItemStackArray(updatedItems).thenAccept(serializedItems -> {
|
||||
if (serializedItems.equals(itemData.serializedItems)) {
|
||||
updatedData.complete(Optional.empty());
|
||||
return;
|
||||
}
|
||||
updatedData.complete(Optional.of(new ItemData(serializedItems)));
|
||||
});
|
||||
updatedData.complete(Optional.of(new ItemData(serializedItems)));
|
||||
});
|
||||
});
|
||||
|
||||
// Display the GUI (synchronously; on the main server thread)
|
||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> gui.open(player));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
// Display the GUI (synchronously; on the main server thread)
|
||||
Bukkit.getScheduler().runTask(plugin, () -> gui.open(player));
|
||||
}).exceptionally(throwable -> {
|
||||
// Handle exceptions
|
||||
updatedData.completeExceptionally(throwable);
|
||||
return null;
|
||||
});
|
||||
return updatedData;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDead() {
|
||||
return player.getHealth() < 1;
|
||||
return player.getHealth() <= 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendActionBar(@NotNull MineDown mineDown) {
|
||||
audience.sendActionBar(mineDown
|
||||
.disable(MineDownParser.Option.SIMPLE_FORMATTING)
|
||||
.replace().toComponent());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull MineDown mineDown) {
|
||||
audience.sendMessage(mineDown
|
||||
.disable(MineDownParser.Option.SIMPLE_FORMATTING)
|
||||
.replace().toComponent());
|
||||
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
||||
try {
|
||||
final Material material = Material.matchMaterial(iconMaterial);
|
||||
Toast.builder(plugin)
|
||||
.setTitle(title.toComponent())
|
||||
.setDescription(description.toComponent())
|
||||
.setIcon(material != null ? material : Material.BARRIER)
|
||||
.setFrameType(FrameType.valueOf(backgroundType))
|
||||
.build()
|
||||
.show(player);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -654,4 +642,14 @@ public class BukkitPlayer extends OnlineUser {
|
||||
return maxHealth;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLocked() {
|
||||
return plugin.getLockedPlayers().contains(player.getUniqueId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNpc() {
|
||||
return player.hasMetadata("NPC");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class BukkitLogger extends Logger {
|
||||
|
||||
private final java.util.logging.Logger logger;
|
||||
|
||||
public BukkitLogger(@NotNull java.util.logging.Logger logger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(@NotNull Level level, @NotNull String message, @NotNull Exception e) {
|
||||
logger.log(level, message, e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(@NotNull Level level, @NotNull String message) {
|
||||
logger.log(level, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void info(@NotNull String message) {
|
||||
logger.info(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void severe(@NotNull String message) {
|
||||
logger.severe(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void config(@NotNull String message) {
|
||||
logger.config(message);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import net.william278.husksync.BukkitHuskSync;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Objects;
|
||||
|
||||
public class BukkitResourceReader implements ResourceReader {
|
||||
|
||||
private final BukkitHuskSync plugin;
|
||||
|
||||
public BukkitResourceReader(BukkitHuskSync plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull InputStream getResource(String fileName) {
|
||||
return Objects.requireNonNull(plugin.getResource(fileName));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +1,30 @@
|
||||
name: HuskSync
|
||||
version: ${version}
|
||||
main: net.william278.husksync.BukkitHuskSync
|
||||
name: 'HuskSync'
|
||||
version: '${version}'
|
||||
main: 'net.william278.husksync.BukkitHuskSync'
|
||||
api-version: 1.16
|
||||
author: William278
|
||||
description: 'A modern, cross-server player data synchronization system'
|
||||
author: 'William278'
|
||||
description: '${description}'
|
||||
website: 'https://william278.net'
|
||||
softdepend:
|
||||
- MysqlPlayerDataBridge
|
||||
- Plan
|
||||
- 'MysqlPlayerDataBridge'
|
||||
- 'Plan'
|
||||
libraries:
|
||||
- 'redis.clients:jedis:${jedis_version}'
|
||||
- 'mysql:mysql-connector-java:${mysql_driver_version}'
|
||||
- 'com.mysql:mysql-connector-j:${mysql_driver_version}'
|
||||
- 'org.mariadb.jdbc:mariadb-java-client:${mariadb_driver_version}'
|
||||
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
||||
- 'org.apache.commons:commons-text:${commons_text_version}'
|
||||
|
||||
commands:
|
||||
husksync:
|
||||
usage: '/husksync <update/info/reload/migrate>'
|
||||
usage: '/<command> <update/info/reload/migrate>'
|
||||
description: 'Manage the HuskSync plugin'
|
||||
userdata:
|
||||
usage: '/userdata <view/list/delete/restore/pin/dump> <username> [version_uuid]'
|
||||
usage: '/<command> <view/list/delete/restore/pin/dump> <username> [version_uuid]'
|
||||
description: 'View, manage & restore player userdata'
|
||||
inventory:
|
||||
usage: '/inventory <username> [version_uuid]'
|
||||
usage: '/<command> <username> [version_uuid]'
|
||||
description: 'View & edit a player''s inventory'
|
||||
enderchest:
|
||||
usage: '/enderchest <username> [version_uuid]'
|
||||
usage: '/<command> <username> [version_uuid]'
|
||||
description: 'View & edit a player''s Ender Chest'
|
||||
@@ -1,26 +1,28 @@
|
||||
dependencies {
|
||||
implementation 'commons-io:commons-io:2.11.0'
|
||||
implementation 'de.themoep:minedown-adventure:1.7.1-SNAPSHOT'
|
||||
implementation 'net.kyori:adventure-api:4.11.0'
|
||||
implementation 'com.google.code.gson:gson:2.9.0'
|
||||
implementation 'dev.dejvokep:boosted-yaml:1.3'
|
||||
implementation 'net.william278:Annotaml:2.0'
|
||||
implementation 'net.william278:DesertWell:1.1'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||
implementation 'net.kyori:adventure-api:4.14.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
implementation 'dev.dejvokep:boosted-yaml:1.3.1'
|
||||
implementation 'net.william278:Annotaml:2.0.1'
|
||||
implementation 'net.william278:DesertWell:2.0.4'
|
||||
implementation 'net.william278:PagineDown:1.1'
|
||||
implementation('com.zaxxer:HikariCP:5.0.1') {
|
||||
exclude module: 'slf4j-api'
|
||||
}
|
||||
|
||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
compileOnly 'com.github.plan-player-analytics:Plan:5.4.1690'
|
||||
compileOnly 'org.jetbrains:annotations:24.0.1'
|
||||
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||
compileOnly 'redis.clients:jedis:' + jedis_version
|
||||
compileOnly 'org.xerial.snappy:snappy-java:' + snappy_version
|
||||
compileOnly 'org.apache.commons:commons-text:' + commons_text_version
|
||||
|
||||
testImplementation 'org.xerial.snappy:snappy-java:1.1.8.4'
|
||||
testImplementation 'com.github.plan-player-analytics:Plan:5.4.1690'
|
||||
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
||||
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||
testImplementation 'redis.clients:jedis:' + jedis_version
|
||||
testImplementation 'org.xerial.snappy:snappy-java:' + snappy_version
|
||||
testImplementation 'org.apache.commons:commons-text:' + commons_text_version
|
||||
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3.1'
|
||||
testCompileOnly 'org.jetbrains:annotations:24.0.1'
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync;
|
||||
|
||||
import net.william278.desertwell.UpdateChecker;
|
||||
import net.william278.desertwell.util.UpdateChecker;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.husksync.config.Locales;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
@@ -9,17 +29,16 @@ import net.william278.husksync.event.EventCannon;
|
||||
import net.william278.husksync.migrator.Migrator;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.ResourceReader;
|
||||
import net.william278.desertwell.Version;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Abstract implementation of the HuskSync plugin.
|
||||
@@ -103,20 +122,33 @@ public interface HuskSync {
|
||||
Locales getLocales();
|
||||
|
||||
/**
|
||||
* Returns the plugin {@link Logger}
|
||||
* Get a resource as an {@link InputStream} from the plugin jar
|
||||
*
|
||||
* @return the {@link Logger}
|
||||
* @param name the path to the resource
|
||||
* @return the {@link InputStream} of the resource
|
||||
*/
|
||||
@NotNull
|
||||
Logger getLoggingAdapter();
|
||||
InputStream getResource(@NotNull String name);
|
||||
|
||||
/**
|
||||
* Returns the plugin resource file reader
|
||||
* Log a message to the console
|
||||
*
|
||||
* @return the {@link ResourceReader}
|
||||
* @param level the level of the message
|
||||
* @param message the message to log
|
||||
* @param throwable a throwable to log
|
||||
*/
|
||||
@NotNull
|
||||
ResourceReader getResourceReader();
|
||||
void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable);
|
||||
|
||||
/**
|
||||
* Send a debug message to the console, if debug logging is enabled
|
||||
*
|
||||
* @param message the message to log
|
||||
* @param throwable a throwable to log
|
||||
*/
|
||||
default void debug(@NotNull String message, @NotNull Throwable... throwable) {
|
||||
if (getSettings().doDebugLogging()) {
|
||||
log(Level.INFO, "[DEBUG] " + message, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the plugin version
|
||||
@@ -140,14 +172,14 @@ public interface HuskSync {
|
||||
* @return a {@link CompletableFuture} returning the latest {@link Version} if the current one is out-of-date
|
||||
*/
|
||||
default CompletableFuture<Optional<Version>> getLatestVersionIfOutdated() {
|
||||
final UpdateChecker updateChecker = UpdateChecker.create(getPluginVersion(), SPIGOT_RESOURCE_ID);
|
||||
return updateChecker.isUpToDate().thenApply(upToDate -> {
|
||||
if (upToDate) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return Optional.of(updateChecker.getLatestVersion().join());
|
||||
}
|
||||
});
|
||||
return UpdateChecker.builder()
|
||||
.currentVersion(getPluginVersion())
|
||||
.endpoint(UpdateChecker.Endpoint.SPIGOT)
|
||||
.resource(Integer.toString(SPIGOT_RESOURCE_ID)).build()
|
||||
.check()
|
||||
.thenApply(checked -> checked.isUpToDate()
|
||||
? Optional.empty()
|
||||
: Optional.of(checked.getLatestVersion()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,4 +197,6 @@ public interface HuskSync {
|
||||
*/
|
||||
CompletableFuture<Boolean> reload();
|
||||
|
||||
Set<UUID> getLockedPlayers();
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,30 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Indicates an exception occurred while initialising the HuskSync plugin
|
||||
* Indicates an exception occurred while initializing the HuskSync plugin
|
||||
*/
|
||||
public class HuskSyncInitializationException extends RuntimeException {
|
||||
public class HuskSyncInitializationException extends IllegalStateException {
|
||||
public HuskSyncInitializationException(@NotNull String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.api;
|
||||
|
||||
import net.william278.husksync.HuskSync;
|
||||
@@ -72,7 +91,7 @@ public abstract class BaseHuskSyncAPI {
|
||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (user instanceof OnlineUser) {
|
||||
return ((OnlineUser) user).getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join();
|
||||
return ((OnlineUser) user).getUserData(plugin).join();
|
||||
} else {
|
||||
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
|
||||
}
|
||||
@@ -103,7 +122,7 @@ public abstract class BaseHuskSyncAPI {
|
||||
* @since 2.0
|
||||
*/
|
||||
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
|
||||
return CompletableFuture.runAsync(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings())
|
||||
return CompletableFuture.runAsync(() -> user.getUserData(plugin)
|
||||
.thenAccept(optionalUserData -> optionalUserData.ifPresent(
|
||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import net.william278.husksync.HuskSync;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
@@ -12,8 +31,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class EnderChestCommand extends CommandBase implements TabCompletable {
|
||||
@@ -29,7 +51,7 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
||||
.ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAccept(optionalUser ->
|
||||
plugin.getDatabase().getUserByName(args[0].toLowerCase(Locale.ENGLISH)).thenAccept(optionalUser ->
|
||||
optionalUser.ifPresentOrElse(user -> {
|
||||
if (args.length == 2) {
|
||||
// View user data by specified UUID
|
||||
@@ -44,9 +66,10 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
||||
"/enderchest <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||
}
|
||||
} else {
|
||||
// View latest user data
|
||||
// View (and edit) the latest user data
|
||||
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
||||
versionedUserData -> showEnderChestMenu(player, versionedUserData, user, true),
|
||||
versionedUserData -> showEnderChestMenu(player, versionedUserData, user,
|
||||
player.hasPermission(Permission.COMMAND_ENDER_CHEST_EDIT.node)),
|
||||
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||
.ifPresent(player::sendMessage)));
|
||||
}
|
||||
@@ -67,28 +90,34 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
||||
|
||||
// Show inventory menu
|
||||
player.showMenu(itemData, allowEdit, 3, plugin.getLocales()
|
||||
.getLocale("ender_chest_viewer_menu_title", dataOwner.username)
|
||||
.orElse(new MineDown("Ender Chest Viewer"))).thenAccept(dataOnClose -> {
|
||||
if (dataOnClose.isEmpty() || !allowEdit) {
|
||||
return;
|
||||
}
|
||||
.getLocale("ender_chest_viewer_menu_title", dataOwner.username)
|
||||
.orElse(new MineDown("Ender Chest Viewer")))
|
||||
.exceptionally(throwable -> {
|
||||
plugin.log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable);
|
||||
return Optional.empty();
|
||||
})
|
||||
.thenAccept(dataOnClose -> {
|
||||
if (dataOnClose.isEmpty() || !allowEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the updated data
|
||||
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
|
||||
data.getStatus().ifPresent(builder::setStatus);
|
||||
data.getAdvancements().ifPresent(builder::setAdvancements);
|
||||
data.getLocation().ifPresent(builder::setLocation);
|
||||
data.getPersistentDataContainer().ifPresent(builder::setPersistentDataContainer);
|
||||
data.getStatistics().ifPresent(builder::setStatistics);
|
||||
data.getPotionEffects().ifPresent(builder::setPotionEffects);
|
||||
data.getInventory().ifPresent(builder::setInventory);
|
||||
builder.setEnderChest(dataOnClose.get());
|
||||
// Create the updated data
|
||||
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
|
||||
data.getStatus().ifPresent(builder::setStatus);
|
||||
data.getAdvancements().ifPresent(builder::setAdvancements);
|
||||
data.getLocation().ifPresent(builder::setLocation);
|
||||
data.getPersistentDataContainer().ifPresent(builder::setPersistentDataContainer);
|
||||
data.getStatistics().ifPresent(builder::setStatistics);
|
||||
data.getPotionEffects().ifPresent(builder::setPotionEffects);
|
||||
data.getInventory().ifPresent(builder::setInventory);
|
||||
builder.setEnderChest(dataOnClose.get());
|
||||
|
||||
// Set the updated data
|
||||
final UserData updatedUserData = builder.build();
|
||||
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND).join();
|
||||
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
||||
});
|
||||
// Set the updated data
|
||||
final UserData updatedUserData = builder.build();
|
||||
plugin.getDatabase()
|
||||
.setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND)
|
||||
.thenRun(() -> plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData));
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,16 +1,34 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import net.william278.desertwell.AboutMenu;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.william278.desertwell.about.AboutMenu;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.migrator.Migrator;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -21,30 +39,32 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
|
||||
public HuskSyncCommand(@NotNull HuskSync implementor) {
|
||||
super("husksync", Permission.COMMAND_HUSKSYNC, implementor);
|
||||
this.aboutMenu = AboutMenu.create("HuskSync")
|
||||
.withDescription("A modern, cross-server player data synchronization system")
|
||||
.withVersion(implementor.getPluginVersion())
|
||||
.addAttribution("Author",
|
||||
AboutMenu.Credit.of("William278").withDescription("Click to visit website").withUrl("https://william278.net"))
|
||||
.addAttribution("Contributors",
|
||||
AboutMenu.Credit.of("HarvelsX").withDescription("Code"),
|
||||
AboutMenu.Credit.of("HookWoods").withDescription("Code"))
|
||||
.addAttribution("Translators",
|
||||
AboutMenu.Credit.of("Namiu").withDescription("Japanese (ja-jp)"),
|
||||
AboutMenu.Credit.of("anchelthe").withDescription("Spanish (es-es)"),
|
||||
AboutMenu.Credit.of("Melonzio").withDescription("Spanish (es-es)"),
|
||||
AboutMenu.Credit.of("Ceddix").withDescription("German (de-de)"),
|
||||
AboutMenu.Credit.of("Pukejoy_1").withDescription("Bulgarian (bg-bg)"),
|
||||
AboutMenu.Credit.of("mateusneresrb").withDescription("Brazilian Portuguese (pt-br)"),
|
||||
AboutMenu.Credit.of("小蔡").withDescription("Traditional Chinese (zh-tw)"),
|
||||
AboutMenu.Credit.of("Ghost-chu").withDescription("Simplified Chinese (zh-cn)"),
|
||||
AboutMenu.Credit.of("DJelly4K").withDescription("Simplified Chinese (zh-cn)"),
|
||||
AboutMenu.Credit.of("Thourgard").withDescription("Ukrainian (uk-ua)"),
|
||||
AboutMenu.Credit.of("xF3d3").withDescription("Italian (it-it)"))
|
||||
.addButtons(
|
||||
AboutMenu.Link.of("https://william278.net/docs/husksync").withText("Documentation").withIcon("⛏"),
|
||||
AboutMenu.Link.of("https://github.com/WiIIiam278/HuskSync/issues").withText("Issues").withIcon("❌").withColor("#ff9f0f"),
|
||||
AboutMenu.Link.of("https://discord.gg/tVYhJfyDWG").withText("Discord").withIcon("⭐").withColor("#6773f5"));
|
||||
this.aboutMenu = AboutMenu.builder()
|
||||
.title(Component.text("HuskSync"))
|
||||
.description(Component.text("A modern, cross-server player data synchronization system"))
|
||||
.version(implementor.getPluginVersion())
|
||||
.credits("Author",
|
||||
AboutMenu.Credit.of("William278").description("Click to visit website").url("https://william278.net"))
|
||||
.credits("Contributors",
|
||||
AboutMenu.Credit.of("HarvelsX").description("Code"),
|
||||
AboutMenu.Credit.of("HookWoods").description("Code"))
|
||||
.credits("Translators",
|
||||
AboutMenu.Credit.of("Namiu").description("Japanese (ja-jp)"),
|
||||
AboutMenu.Credit.of("anchelthe").description("Spanish (es-es)"),
|
||||
AboutMenu.Credit.of("Melonzio").description("Spanish (es-es)"),
|
||||
AboutMenu.Credit.of("Ceddix").description("German (de-de)"),
|
||||
AboutMenu.Credit.of("Pukejoy_1").description("Bulgarian (bg-bg)"),
|
||||
AboutMenu.Credit.of("mateusneresrb").description("Brazilian Portuguese (pt-br)"),
|
||||
AboutMenu.Credit.of("小蔡").description("Traditional Chinese (zh-tw)"),
|
||||
AboutMenu.Credit.of("Ghost-chu").description("Simplified Chinese (zh-cn)"),
|
||||
AboutMenu.Credit.of("DJelly4K").description("Simplified Chinese (zh-cn)"),
|
||||
AboutMenu.Credit.of("Thourgard").description("Ukrainian (uk-ua)"),
|
||||
AboutMenu.Credit.of("xF3d3").description("Italian (it-it)"))
|
||||
.buttons(
|
||||
AboutMenu.Link.of("https://william278.net/docs/husksync").text("Documentation").icon("⛏"),
|
||||
AboutMenu.Link.of("https://github.com/WiIIiam278/HuskSync/issues").text("Issues").icon("❌").color(TextColor.color(0xff9f0f)),
|
||||
AboutMenu.Link.of("https://discord.gg/tVYhJfyDWG").text("Discord").icon("⭐").color(TextColor.color(0x6773f5)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,7 +73,7 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
sendAboutMenu(player);
|
||||
return;
|
||||
}
|
||||
switch (args[0].toLowerCase()) {
|
||||
switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||
case "update", "version" -> {
|
||||
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_UPDATE.node)) {
|
||||
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||
@@ -63,10 +83,10 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
newestVersion.ifPresentOrElse(
|
||||
newVersion -> player.sendMessage(
|
||||
new MineDown("[HuskSync](#00fb9a bold) [| A new version of HuskSync is available!"
|
||||
+ " (v" + newVersion + " (Running: v" + plugin.getPluginVersion() + ")](#00fb9a)")),
|
||||
+ " (v" + newVersion + " (Running: v" + plugin.getPluginVersion() + ")](#00fb9a)")),
|
||||
() -> player.sendMessage(
|
||||
new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date."
|
||||
+ " (Running: v" + plugin.getPluginVersion() + ")](#00fb9a)"))));
|
||||
+ " (Running: v" + plugin.getPluginVersion() + ")](#00fb9a)"))));
|
||||
}
|
||||
case "about", "info" -> sendAboutMenu(player);
|
||||
case "reload" -> {
|
||||
@@ -88,25 +108,25 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
@Override
|
||||
public void onConsoleExecute(@NotNull String[] args) {
|
||||
if (args.length < 1) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Console usage: \"husksync <update/about/reload/migrate>\"");
|
||||
plugin.log(Level.INFO, "Console usage: \"husksync <update/about/reload/migrate>\"");
|
||||
return;
|
||||
}
|
||||
switch (args[0].toLowerCase()) {
|
||||
switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||
case "update", "version" -> plugin.getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
||||
newestVersion.ifPresentOrElse(newVersion -> plugin.getLoggingAdapter().log(Level.WARNING,
|
||||
newestVersion.ifPresentOrElse(newVersion -> plugin.log(Level.WARNING,
|
||||
"An update is available for HuskSync, v" + newVersion
|
||||
+ " (Running v" + plugin.getPluginVersion() + ")"),
|
||||
() -> plugin.getLoggingAdapter().log(Level.INFO,
|
||||
+ " (Running v" + plugin.getPluginVersion() + ")"),
|
||||
() -> plugin.log(Level.INFO,
|
||||
"HuskSync is up to date" +
|
||||
" (Running v" + plugin.getPluginVersion() + ")")));
|
||||
case "about", "info" -> aboutMenu.toString().lines().forEach(plugin.getLoggingAdapter()::info);
|
||||
" (Running v" + plugin.getPluginVersion() + ")")));
|
||||
case "about", "info" -> aboutMenu.toString().lines().forEach(line -> plugin.log(Level.INFO, line));
|
||||
case "reload" -> {
|
||||
plugin.reload();
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Reloaded config & message files.");
|
||||
plugin.log(Level.INFO, "Reloaded config & message files.");
|
||||
}
|
||||
case "migrate" -> {
|
||||
if (args.length < 2) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO,
|
||||
plugin.log(Level.INFO,
|
||||
"Please choose a migrator, then run \"husksync migrate <migrator>\"");
|
||||
logMigratorsList();
|
||||
return;
|
||||
@@ -115,39 +135,39 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst();
|
||||
selectedMigrator.ifPresentOrElse(migrator -> {
|
||||
if (args.length < 3) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, migrator.getHelpMenu());
|
||||
plugin.log(Level.INFO, migrator.getHelpMenu());
|
||||
return;
|
||||
}
|
||||
switch (args[2]) {
|
||||
case "start" -> migrator.start().thenAccept(succeeded -> {
|
||||
if (succeeded) {
|
||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration completed successfully!");
|
||||
plugin.log(Level.INFO, "Migration completed successfully!");
|
||||
} else {
|
||||
plugin.getLoggingAdapter().log(Level.WARNING, "Migration failed!");
|
||||
plugin.log(Level.WARNING, "Migration failed!");
|
||||
}
|
||||
});
|
||||
case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
|
||||
default -> plugin.getLoggingAdapter().log(Level.INFO,
|
||||
default -> plugin.log(Level.INFO,
|
||||
"Invalid syntax. Console usage: \"husksync migrate " + args[1] + " <start/set>");
|
||||
}
|
||||
}, () -> {
|
||||
plugin.getLoggingAdapter().log(Level.INFO,
|
||||
plugin.log(Level.INFO,
|
||||
"Please specify a valid migrator.\n" +
|
||||
"If a migrator is not available, please verify that you meet the prerequisites to use it.");
|
||||
"If a migrator is not available, please verify that you meet the prerequisites to use it.");
|
||||
logMigratorsList();
|
||||
});
|
||||
}
|
||||
default -> plugin.getLoggingAdapter().log(Level.INFO,
|
||||
default -> plugin.log(Level.INFO,
|
||||
"Invalid syntax. Console usage: \"husksync <update/about/reload/migrate>\"");
|
||||
}
|
||||
}
|
||||
|
||||
private void logMigratorsList() {
|
||||
plugin.getLoggingAdapter().log(Level.INFO,
|
||||
plugin.log(Level.INFO,
|
||||
"List of available migrators:\nMigrator ID / Migrator Name:\n" +
|
||||
plugin.getAvailableMigrators().stream()
|
||||
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())
|
||||
.collect(Collectors.joining("\n")));
|
||||
plugin.getAvailableMigrators().stream()
|
||||
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())
|
||||
.collect(Collectors.joining("\n")));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -165,6 +185,6 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
||||
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
player.sendMessage(aboutMenu.toMineDown());
|
||||
player.sendMessage(aboutMenu.toComponent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
@@ -12,8 +31,11 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class InventoryCommand extends CommandBase implements TabCompletable {
|
||||
@@ -29,7 +51,7 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
||||
.ifPresent(player::sendMessage);
|
||||
return;
|
||||
}
|
||||
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAccept(optionalUser ->
|
||||
plugin.getDatabase().getUserByName(args[0].toLowerCase(Locale.ENGLISH)).thenAccept(optionalUser ->
|
||||
optionalUser.ifPresentOrElse(user -> {
|
||||
if (args.length == 2) {
|
||||
// View user data by specified UUID
|
||||
@@ -44,9 +66,10 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
||||
"/inventory <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||
}
|
||||
} else {
|
||||
// View latest user data
|
||||
// View (and edit) the latest user data
|
||||
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
||||
versionedUserData -> showInventoryMenu(player, versionedUserData, user, true),
|
||||
versionedUserData -> showInventoryMenu(player, versionedUserData, user,
|
||||
player.hasPermission(Permission.COMMAND_INVENTORY_EDIT.node)),
|
||||
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||
.ifPresent(player::sendMessage)));
|
||||
}
|
||||
@@ -69,13 +92,15 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
||||
player.showMenu(itemData, allowEdit, 5, plugin.getLocales()
|
||||
.getLocale("inventory_viewer_menu_title", dataOwner.username)
|
||||
.orElse(new MineDown("Inventory Viewer")))
|
||||
.exceptionally(throwable -> {
|
||||
plugin.log(Level.WARNING, "Exception displaying inventory menu to " + player.username, throwable);
|
||||
return Optional.empty();
|
||||
})
|
||||
.thenAccept(dataOnClose -> {
|
||||
if (dataOnClose.isEmpty() || !allowEdit) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLoggingAdapter().debug("Inventory data changed, updating user, etc!");
|
||||
|
||||
// Create the updated data
|
||||
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
|
||||
data.getStatus().ifPresent(builder::setStatus);
|
||||
@@ -89,8 +114,9 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
||||
|
||||
// Set the updated data
|
||||
final UserData updatedUserData = builder.build();
|
||||
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND).join();
|
||||
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
||||
plugin.getDatabase()
|
||||
.setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND)
|
||||
.thenRun(() -> plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,18 +1,34 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.command;
|
||||
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.util.DataSnapshotList;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.util.DataDumper;
|
||||
import net.william278.husksync.util.DataSnapshotList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -34,7 +50,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (args[0].toLowerCase()) {
|
||||
switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||
case "view" -> {
|
||||
if (args.length < 2) {
|
||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||
@@ -47,7 +63,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
try {
|
||||
final UUID versionUuid = UUID.fromString(args[2]);
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||
.getUserByName(username.toLowerCase())
|
||||
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||
.thenAccept(optionalUser -> optionalUser
|
||||
.ifPresentOrElse(user -> plugin.getDatabase().getUserData(user, versionUuid)
|
||||
.thenAccept(data -> data.ifPresentOrElse(
|
||||
@@ -63,7 +79,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
}
|
||||
} else {
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||
.getUserByName(username.toLowerCase())
|
||||
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||
.thenAccept(optionalUser -> optionalUser
|
||||
.ifPresentOrElse(user -> plugin.getDatabase().getCurrentUserData(user)
|
||||
.thenAccept(latestData -> latestData.ifPresentOrElse(
|
||||
@@ -87,7 +103,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
}
|
||||
final String username = args[1];
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||
.getUserByName(username.toLowerCase())
|
||||
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||
user -> plugin.getDatabase().getUserData(user).thenAccept(dataList -> {
|
||||
// Check if there is data to display
|
||||
@@ -133,7 +149,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
try {
|
||||
final UUID versionUuid = UUID.fromString(args[2]);
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||
.getUserByName(username.toLowerCase())
|
||||
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||
user -> plugin.getDatabase().deleteUserData(user, versionUuid).thenAccept(deleted -> {
|
||||
if (deleted) {
|
||||
@@ -172,7 +188,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
try {
|
||||
final UUID versionUuid = UUID.fromString(args[2]);
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||
.getUserByName(username.toLowerCase())
|
||||
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(data -> {
|
||||
if (data.isEmpty()) {
|
||||
@@ -219,7 +235,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
try {
|
||||
final UUID versionUuid = UUID.fromString(args[2]);
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||
.getUserByName(username.toLowerCase())
|
||||
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(
|
||||
optionalUserData -> optionalUserData.ifPresentOrElse(userData -> {
|
||||
@@ -267,7 +283,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
try {
|
||||
final UUID versionUuid = UUID.fromString(args[2]);
|
||||
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||
.getUserByName(username.toLowerCase())
|
||||
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(
|
||||
optionalUserData -> optionalUserData.ifPresentOrElse(userData -> {
|
||||
@@ -278,7 +294,7 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||
.split("-")[0], user.username, result)
|
||||
.ifPresent(player::sendMessage);
|
||||
} catch (IOException e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to dump user data", e);
|
||||
plugin.log(Level.SEVERE, "Failed to dump user data", e);
|
||||
}
|
||||
}, () -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||
.ifPresent(player::sendMessage))),
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.config;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
@@ -16,7 +35,7 @@ import java.util.Optional;
|
||||
*/
|
||||
@YamlFile(rootedMap = true, header = """
|
||||
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
┃ HuskHomes Locales ┃
|
||||
┃ HuskSync Locales ┃
|
||||
┃ Developed by William278 ┃
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
┣╸ See plugin about menu for international locale credits
|
||||
@@ -121,6 +140,21 @@ public class Locales {
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncates a String to a specified length, and appends an ellipsis if it is longer than the specified length
|
||||
*
|
||||
* @param string The string to truncate
|
||||
* @param length The maximum length of the string
|
||||
* @return The truncated string
|
||||
*/
|
||||
@NotNull
|
||||
public static String truncate(@NotNull String string, int length) {
|
||||
if (string.length() > length) {
|
||||
return string.substring(0, length) + "…";
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base list options to use for a paginated chat list
|
||||
*
|
||||
|
||||
@@ -1,13 +1,31 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.config;
|
||||
|
||||
import net.william278.annotaml.YamlComment;
|
||||
import net.william278.annotaml.YamlFile;
|
||||
import net.william278.annotaml.YamlKey;
|
||||
import net.william278.husksync.database.Database;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Plugin settings, read from config.yml
|
||||
@@ -19,113 +37,276 @@ import java.util.Optional;
|
||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
┣╸ Information: https://william278.net/project/husksync
|
||||
┗╸ Documentation: https://william278.net/docs/husksync""",
|
||||
|
||||
versionField = "config_version", versionNumber = 2)
|
||||
versionField = "config_version", versionNumber = 4)
|
||||
public class Settings {
|
||||
|
||||
// Top-level settings
|
||||
public String language = "en-gb";
|
||||
@YamlKey("language")
|
||||
private String language = "en-gb";
|
||||
|
||||
@YamlKey("check_for_updates")
|
||||
public boolean checkForUpdates = true;
|
||||
private boolean checkForUpdates = true;
|
||||
|
||||
@YamlKey("cluster_id")
|
||||
public String clusterId = "";
|
||||
private String clusterId = "";
|
||||
|
||||
@YamlKey("debug_logging")
|
||||
public boolean debugLogging = false;
|
||||
private boolean debugLogging = false;
|
||||
|
||||
|
||||
// Database settings
|
||||
@YamlComment("Type of database to use (MYSQL, SQLITE)")
|
||||
@YamlKey("database.type")
|
||||
private Database.Type databaseType = Database.Type.MYSQL;
|
||||
|
||||
@YamlComment("Database connection settings")
|
||||
@YamlKey("database.credentials.host")
|
||||
public String mySqlHost = "localhost";
|
||||
private String mySqlHost = "localhost";
|
||||
|
||||
@YamlKey("database.credentials.port")
|
||||
public int mySqlPort = 3306;
|
||||
private int mySqlPort = 3306;
|
||||
|
||||
@YamlKey("database.credentials.database")
|
||||
public String mySqlDatabase = "HuskSync";
|
||||
private String mySqlDatabase = "HuskSync";
|
||||
|
||||
@YamlKey("database.mysql.credentials.username")
|
||||
public String mySqlUsername = "root";
|
||||
@YamlKey("database.credentials.username")
|
||||
private String mySqlUsername = "root";
|
||||
|
||||
@YamlKey("database.credentials.password")
|
||||
public String mySqlPassword = "pa55w0rd";
|
||||
private String mySqlPassword = "pa55w0rd";
|
||||
|
||||
@YamlKey("database.credentials.parameters")
|
||||
public String mySqlConnectionParameters = "?autoReconnect=true&useSSL=false";
|
||||
private String mySqlConnectionParameters = "?autoReconnect=true&useSSL=false";
|
||||
|
||||
@YamlComment("MySQL connection pool properties")
|
||||
@YamlKey("database.connection_pool.maximum_pool_size")
|
||||
public int mySqlConnectionPoolSize = 10;
|
||||
private int mySqlConnectionPoolSize = 10;
|
||||
|
||||
@YamlKey("database.connection_pool.minimum_idle")
|
||||
public int mySqlConnectionPoolIdle = 10;
|
||||
private int mySqlConnectionPoolIdle = 10;
|
||||
|
||||
@YamlKey("database.connection_pool.maximum_lifetime")
|
||||
public long mySqlConnectionPoolLifetime = 1800000;
|
||||
private long mySqlConnectionPoolLifetime = 1800000;
|
||||
|
||||
@YamlKey("database.connection_pool.keepalive_time")
|
||||
public long mySqlConnectionPoolKeepAlive = 0;
|
||||
private long mySqlConnectionPoolKeepAlive = 0;
|
||||
|
||||
@YamlKey("database.connection_pool.connection_timeout")
|
||||
public long mySqlConnectionPoolTimeout = 5000;
|
||||
private long mySqlConnectionPoolTimeout = 5000;
|
||||
|
||||
@YamlKey("database.table_names")
|
||||
public Map<String, String> tableNames = TableName.getDefaults();
|
||||
|
||||
@NotNull
|
||||
public String getTableName(@NotNull TableName tableName) {
|
||||
return Optional.ofNullable(tableNames.get(tableName.name().toLowerCase()))
|
||||
.orElse(tableName.defaultName);
|
||||
}
|
||||
private Map<String, String> tableNames = TableName.getDefaults();
|
||||
|
||||
|
||||
// Redis settings
|
||||
@YamlComment("Redis connection settings")
|
||||
@YamlKey("redis.credentials.host")
|
||||
public String redisHost = "localhost";
|
||||
private String redisHost = "localhost";
|
||||
|
||||
@YamlKey("redis.credentials.port")
|
||||
public int redisPort = 6379;
|
||||
private int redisPort = 6379;
|
||||
|
||||
@YamlKey("redis.credentials.password")
|
||||
public String redisPassword = "";
|
||||
private String redisPassword = "";
|
||||
|
||||
@YamlKey("redis.use_ssl")
|
||||
public boolean redisUseSsl = false;
|
||||
private boolean redisUseSsl = false;
|
||||
|
||||
|
||||
// Synchronization settings
|
||||
@YamlComment("Synchronization settings")
|
||||
@YamlKey("synchronization.max_user_data_snapshots")
|
||||
public int maxUserDataSnapshots = 5;
|
||||
private int maxUserDataSnapshots = 5;
|
||||
|
||||
@YamlKey("synchronization.save_on_world_save")
|
||||
public boolean saveOnWorldSave = true;
|
||||
private boolean saveOnWorldSave = true;
|
||||
|
||||
@YamlKey("synchronization.save_on_death")
|
||||
public boolean saveOnDeath = false;
|
||||
private boolean saveOnDeath = false;
|
||||
|
||||
@YamlKey("synchronization.save_empty_drops_on_death")
|
||||
private boolean saveEmptyDropsOnDeath = true;
|
||||
|
||||
@YamlKey("synchronization.compress_data")
|
||||
public boolean compressData = true;
|
||||
private boolean compressData = true;
|
||||
|
||||
@YamlKey("synchronization.save_dead_player_inventories")
|
||||
public boolean saveDeadPlayerInventories = true;
|
||||
@YamlKey("synchronization.notification_display_slot")
|
||||
private NotificationDisplaySlot notificationDisplaySlot = NotificationDisplaySlot.ACTION_BAR;
|
||||
|
||||
@YamlKey("synchronization.synchronise_dead_players_changing_server")
|
||||
private boolean synchroniseDeadPlayersChangingServer = true;
|
||||
|
||||
@YamlKey("synchronization.network_latency_milliseconds")
|
||||
public int networkLatencyMilliseconds = 500;
|
||||
private int networkLatencyMilliseconds = 500;
|
||||
|
||||
@YamlKey("synchronization.features")
|
||||
public Map<String, Boolean> synchronizationFeatures = SynchronizationFeature.getDefaults();
|
||||
private Map<String, Boolean> synchronizationFeatures = SynchronizationFeature.getDefaults();
|
||||
|
||||
public boolean getSynchronizationFeature(@NotNull SynchronizationFeature feature) {
|
||||
return Optional.ofNullable(synchronizationFeatures.get(feature.name().toLowerCase()))
|
||||
.orElse(feature.enabledByDefault);
|
||||
@YamlKey("synchronization.blacklisted_commands_while_locked")
|
||||
private List<String> blacklistedCommandsWhileLocked = new ArrayList<>(List.of("*"));
|
||||
|
||||
@YamlKey("synchronization.event_priorities")
|
||||
private Map<String, String> synchronizationEventPriorities = EventType.getDefaults();
|
||||
|
||||
|
||||
// Zero-args constructor for instantiation via Annotaml
|
||||
public Settings() {
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public String getLanguage() {
|
||||
return language;
|
||||
}
|
||||
|
||||
public boolean doCheckForUpdates() {
|
||||
return checkForUpdates;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getClusterId() {
|
||||
return clusterId;
|
||||
}
|
||||
|
||||
public boolean doDebugLogging() {
|
||||
return debugLogging;
|
||||
}
|
||||
|
||||
|
||||
@NotNull
|
||||
public Database.Type getDatabaseType() {
|
||||
return databaseType;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMySqlHost() {
|
||||
return mySqlHost;
|
||||
}
|
||||
|
||||
public int getMySqlPort() {
|
||||
return mySqlPort;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMySqlDatabase() {
|
||||
return mySqlDatabase;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMySqlUsername() {
|
||||
return mySqlUsername;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMySqlPassword() {
|
||||
return mySqlPassword;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMySqlConnectionParameters() {
|
||||
return mySqlConnectionParameters;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getTableName(@NotNull TableName tableName) {
|
||||
return tableNames.getOrDefault(tableName.name().toLowerCase(Locale.ENGLISH), tableName.defaultName);
|
||||
}
|
||||
|
||||
public int getMySqlConnectionPoolSize() {
|
||||
return mySqlConnectionPoolSize;
|
||||
}
|
||||
|
||||
public int getMySqlConnectionPoolIdle() {
|
||||
return mySqlConnectionPoolIdle;
|
||||
}
|
||||
|
||||
public long getMySqlConnectionPoolLifetime() {
|
||||
return mySqlConnectionPoolLifetime;
|
||||
}
|
||||
|
||||
public long getMySqlConnectionPoolKeepAlive() {
|
||||
return mySqlConnectionPoolKeepAlive;
|
||||
}
|
||||
|
||||
public long getMySqlConnectionPoolTimeout() {
|
||||
return mySqlConnectionPoolTimeout;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRedisHost() {
|
||||
return redisHost;
|
||||
}
|
||||
|
||||
public int getRedisPort() {
|
||||
return redisPort;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getRedisPassword() {
|
||||
return redisPassword;
|
||||
}
|
||||
|
||||
public boolean isRedisUseSsl() {
|
||||
return redisUseSsl;
|
||||
}
|
||||
|
||||
public int getMaxUserDataSnapshots() {
|
||||
return maxUserDataSnapshots;
|
||||
}
|
||||
|
||||
public boolean doSaveOnWorldSave() {
|
||||
return saveOnWorldSave;
|
||||
}
|
||||
|
||||
public boolean doSaveOnDeath() {
|
||||
return saveOnDeath;
|
||||
}
|
||||
|
||||
public boolean doSaveEmptyDropsOnDeath() {
|
||||
return saveEmptyDropsOnDeath;
|
||||
}
|
||||
|
||||
public boolean doCompressData() {
|
||||
return compressData;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public NotificationDisplaySlot getNotificationDisplaySlot() {
|
||||
return notificationDisplaySlot;
|
||||
}
|
||||
|
||||
public boolean isSynchroniseDeadPlayersChangingServer() {
|
||||
return synchroniseDeadPlayersChangingServer;
|
||||
}
|
||||
|
||||
public int getNetworkLatencyMilliseconds() {
|
||||
return networkLatencyMilliseconds;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Map<String, Boolean> getSynchronizationFeatures() {
|
||||
return synchronizationFeatures;
|
||||
}
|
||||
|
||||
public boolean getSynchronizationFeature(@NotNull SynchronizationFeature feature) {
|
||||
return getSynchronizationFeatures().getOrDefault(feature.name().toLowerCase(Locale.ENGLISH), feature.enabledByDefault);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<String> getBlacklistedCommandsWhileLocked() {
|
||||
return blacklistedCommandsWhileLocked;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public EventPriority getEventPriority(@NotNull Settings.EventType eventType) {
|
||||
try {
|
||||
return EventPriority.valueOf(synchronizationEventPriorities.get(eventType.name().toLowerCase(Locale.ENGLISH)));
|
||||
} catch (IllegalArgumentException e) {
|
||||
e.printStackTrace();
|
||||
return EventPriority.NORMAL;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the names of tables in the database
|
||||
*/
|
||||
@@ -139,11 +320,13 @@ public class Settings {
|
||||
this.defaultName = defaultName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map.Entry<String, String> toEntry() {
|
||||
return Map.entry(name().toLowerCase(), defaultName);
|
||||
return Map.entry(name().toLowerCase(Locale.ENGLISH), defaultName);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
private static Map<String, String> getDefaults() {
|
||||
return Map.ofEntries(Arrays.stream(values())
|
||||
.map(TableName::toEntry)
|
||||
@@ -151,11 +334,32 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the slot a system notification should be displayed in
|
||||
*/
|
||||
public enum NotificationDisplaySlot {
|
||||
/**
|
||||
* Displays the notification in the action bar
|
||||
*/
|
||||
ACTION_BAR,
|
||||
/**
|
||||
* Displays the notification in the chat
|
||||
*/
|
||||
CHAT,
|
||||
/**
|
||||
* Displays the notification in an advancement toast
|
||||
*/
|
||||
TOAST,
|
||||
/**
|
||||
* Does not display the notification
|
||||
*/
|
||||
NONE
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents enabled synchronisation features
|
||||
*/
|
||||
public enum SynchronizationFeature {
|
||||
|
||||
INVENTORIES(true),
|
||||
ENDER_CHESTS(true),
|
||||
HEALTH(true),
|
||||
@@ -167,6 +371,7 @@ public class Settings {
|
||||
GAME_MODE(true),
|
||||
STATISTICS(true),
|
||||
PERSISTENT_DATA_CONTAINER(false),
|
||||
LOCKED_MAPS(false),
|
||||
LOCATION(false);
|
||||
|
||||
private final boolean enabledByDefault;
|
||||
@@ -175,11 +380,13 @@ public class Settings {
|
||||
this.enabledByDefault = enabledByDefault;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map.Entry<String, Boolean> toEntry() {
|
||||
return Map.entry(name().toLowerCase(), enabledByDefault);
|
||||
return Map.entry(name().toLowerCase(Locale.ENGLISH), enabledByDefault);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
private static Map<String, Boolean> getDefaults() {
|
||||
return Map.ofEntries(Arrays.stream(values())
|
||||
.map(SynchronizationFeature::toEntry)
|
||||
@@ -187,4 +394,51 @@ public class Settings {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents events that HuskSync listens to, with a configurable priority listener
|
||||
*/
|
||||
public enum EventType {
|
||||
JOIN_LISTENER(EventPriority.LOWEST),
|
||||
QUIT_LISTENER(EventPriority.LOWEST),
|
||||
DEATH_LISTENER(EventPriority.NORMAL);
|
||||
|
||||
private final EventPriority defaultPriority;
|
||||
|
||||
EventType(@NotNull EventPriority defaultPriority) {
|
||||
this.defaultPriority = defaultPriority;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map.Entry<String, String> toEntry() {
|
||||
return Map.entry(name().toLowerCase(Locale.ENGLISH), defaultPriority.name());
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
private static Map<String, String> getDefaults() {
|
||||
return Map.ofEntries(Arrays.stream(values())
|
||||
.map(EventType::toEntry)
|
||||
.toArray(Map.Entry[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents priorities for events that HuskSync listens to
|
||||
*/
|
||||
public enum EventPriority {
|
||||
/**
|
||||
* Listens and processes the event execution last
|
||||
*/
|
||||
HIGHEST,
|
||||
/**
|
||||
* Listens in between {@link #HIGHEST} and {@link #LOWEST} priority marked
|
||||
*/
|
||||
NORMAL,
|
||||
/**
|
||||
* Listens and processes the event execution first
|
||||
*/
|
||||
LOWEST
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the type of a {@link PersistentDataTag}
|
||||
*/
|
||||
public enum BukkitPersistentDataTagType {
|
||||
|
||||
BYTE,
|
||||
SHORT,
|
||||
INTEGER,
|
||||
LONG,
|
||||
FLOAT,
|
||||
DOUBLE,
|
||||
STRING,
|
||||
BYTE_ARRAY,
|
||||
INTEGER_ARRAY,
|
||||
LONG_ARRAY,
|
||||
TAG_CONTAINER_ARRAY,
|
||||
TAG_CONTAINER;
|
||||
|
||||
|
||||
public static Optional<BukkitPersistentDataTagType> getDataType(@NotNull String typeName) {
|
||||
for (BukkitPersistentDataTagType type : values()) {
|
||||
if (type.name().equalsIgnoreCase(typeName)) {
|
||||
return Optional.of(type);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,32 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.api.BaseHuskSyncAPI;
|
||||
import net.william278.husksync.config.Locales;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Identifies the cause of a player data save.
|
||||
*
|
||||
@@ -100,4 +122,9 @@ public enum DataSaveCause {
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return Locales.truncate(name().toLowerCase(Locale.ENGLISH), 10);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
@@ -32,4 +51,13 @@ public class ItemData {
|
||||
protected ItemData() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item data is empty
|
||||
*
|
||||
* @return {@code true} if the item data is empty; {@code false} otherwise
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return serializedItems.isEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
@@ -18,7 +37,7 @@ public class PersistentDataContainerData {
|
||||
@SerializedName("persistent_data_map")
|
||||
protected Map<String, PersistentDataTag<?>> persistentDataMap;
|
||||
|
||||
public PersistentDataContainerData(@NotNull final Map<String, PersistentDataTag<?>> persistentDataMap) {
|
||||
public PersistentDataContainerData(@NotNull Map<String, PersistentDataTag<?>> persistentDataMap) {
|
||||
this.persistentDataMap = persistentDataMap;
|
||||
}
|
||||
|
||||
@@ -26,17 +45,23 @@ public class PersistentDataContainerData {
|
||||
protected PersistentDataContainerData() {
|
||||
}
|
||||
|
||||
|
||||
public <T> Optional<T> getTagValue(@NotNull final String tagName, @NotNull Class<T> tagClass) {
|
||||
if (persistentDataMap.containsKey(tagName)) {
|
||||
return Optional.of(tagClass.cast(persistentDataMap.get(tagName).value));
|
||||
public <T> Optional<T> getTagValue(@NotNull String tagName, @NotNull Class<T> tagClass) {
|
||||
if (!persistentDataMap.containsKey(tagName)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.empty();
|
||||
|
||||
// If the tag cannot be cast to the specified class, return an empty optional
|
||||
final boolean canCast = tagClass.isAssignableFrom(persistentDataMap.get(tagName).value.getClass());
|
||||
if (!canCast) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(tagClass.cast(persistentDataMap.get(tagName).value));
|
||||
}
|
||||
|
||||
public Optional<BukkitPersistentDataTagType> getTagType(@NotNull final String tagType) {
|
||||
public Optional<PersistentDataTagType> getTagType(@NotNull String tagType) {
|
||||
if (persistentDataMap.containsKey(tagType)) {
|
||||
return BukkitPersistentDataTagType.getDataType(persistentDataMap.get(tagType).type);
|
||||
return PersistentDataTagType.getDataType(persistentDataMap.get(tagType).type);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -19,16 +38,17 @@ public class PersistentDataTag<T> {
|
||||
*/
|
||||
public T value;
|
||||
|
||||
public PersistentDataTag(@NotNull BukkitPersistentDataTagType type, @NotNull T value) {
|
||||
public PersistentDataTag(@NotNull PersistentDataTagType type, @NotNull T value) {
|
||||
this.type = type.name();
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private PersistentDataTag() {
|
||||
}
|
||||
|
||||
public Optional<BukkitPersistentDataTagType> getType() {
|
||||
return BukkitPersistentDataTagType.getDataType(type);
|
||||
public Optional<PersistentDataTagType> getType() {
|
||||
return PersistentDataTagType.getDataType(type);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Represents the type of a {@link PersistentDataTag}
|
||||
*/
|
||||
public enum PersistentDataTagType {
|
||||
|
||||
BYTE,
|
||||
SHORT,
|
||||
INTEGER,
|
||||
LONG,
|
||||
FLOAT,
|
||||
DOUBLE,
|
||||
STRING,
|
||||
BYTE_ARRAY,
|
||||
INTEGER_ARRAY,
|
||||
LONG_ARRAY,
|
||||
TAG_CONTAINER_ARRAY,
|
||||
TAG_CONTAINER;
|
||||
|
||||
|
||||
public static Optional<PersistentDataTagType> getDataType(@NotNull String typeName) {
|
||||
for (PersistentDataTagType type : values()) {
|
||||
if (type.name().equalsIgnoreCase(typeName)) {
|
||||
return Optional.of(type);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
@@ -11,7 +30,7 @@ import java.util.Map;
|
||||
public class StatisticsData {
|
||||
|
||||
/**
|
||||
* Map of untyped statistic names to their values
|
||||
* Map of generic statistic names to their values
|
||||
*/
|
||||
@SerializedName("untyped_statistics")
|
||||
public Map<String, Integer> untypedStatistics;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
|
||||
@@ -1,7 +1,26 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
import net.william278.desertwell.Version;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.command.Permission;
|
||||
@@ -7,10 +26,7 @@ import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Represents a uniquely versioned and timestamped snapshot of a user's data, including why it was saved.
|
||||
@@ -57,7 +73,7 @@ public record UserDataSnapshot(@NotNull UUID versionUUID, @NotNull Date versionT
|
||||
if (pinned()) {
|
||||
locales.getLocale("data_manager_pinned").ifPresent(user::sendMessage);
|
||||
}
|
||||
locales.getLocale("data_manager_cause", cause().name().toLowerCase().replaceAll("_", " "))
|
||||
locales.getLocale("data_manager_cause", cause().name().toLowerCase(Locale.ENGLISH).replaceAll("_", " "))
|
||||
.ifPresent(user::sendMessage);
|
||||
|
||||
// User status data, if present in the snapshot
|
||||
@@ -67,7 +83,7 @@ public record UserDataSnapshot(@NotNull UUID versionUUID, @NotNull Date versionT
|
||||
Integer.toString((int) statusData.maxHealth),
|
||||
Integer.toString(statusData.hunger),
|
||||
Integer.toString(statusData.expLevel),
|
||||
statusData.gameMode.toLowerCase()))
|
||||
statusData.gameMode.toLowerCase(Locale.ENGLISH)))
|
||||
.ifPresent(user::sendMessage);
|
||||
|
||||
// Advancement and statistic data, if both are present in the snapshot
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.database;
|
||||
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.data.UserDataSnapshot;
|
||||
import net.william278.husksync.event.EventCannon;
|
||||
import net.william278.husksync.migrator.Migrator;
|
||||
import net.william278.husksync.player.User;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.ResourceReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -26,78 +43,10 @@ import java.util.concurrent.CompletableFuture;
|
||||
*/
|
||||
public abstract class Database {
|
||||
|
||||
/**
|
||||
* Name of the table that stores player information
|
||||
*/
|
||||
protected final String playerTableName;
|
||||
protected final HuskSync plugin;
|
||||
|
||||
/**
|
||||
* Name of the table that stores data
|
||||
*/
|
||||
protected final String dataTableName;
|
||||
|
||||
/**
|
||||
* The maximum number of user records to store in the database at once per user
|
||||
*/
|
||||
protected final int maxUserDataRecords;
|
||||
|
||||
/**
|
||||
* {@link DataAdapter} implementation used for adapting {@link UserData} to and from JSON
|
||||
*/
|
||||
private final DataAdapter dataAdapter;
|
||||
|
||||
/**
|
||||
* Returns the {@link DataAdapter} used to adapt {@link UserData} to and from JSON
|
||||
*
|
||||
* @return instance of the {@link DataAdapter} implementation
|
||||
*/
|
||||
protected DataAdapter getDataAdapter() {
|
||||
return dataAdapter;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link EventCannon} implementation used for firing events
|
||||
*/
|
||||
private final EventCannon eventCannon;
|
||||
|
||||
/**
|
||||
* Returns the {@link EventCannon} used to fire events
|
||||
*
|
||||
* @return instance of the {@link EventCannon} implementation
|
||||
*/
|
||||
protected EventCannon getEventCannon() {
|
||||
return eventCannon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logger instance used for database error logging
|
||||
*/
|
||||
private final Logger logger;
|
||||
|
||||
/**
|
||||
* Returns the {@link Logger} used to log database errors
|
||||
*
|
||||
* @return the {@link Logger} instance
|
||||
*/
|
||||
protected Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ResourceReader} used to read internal resource files by name
|
||||
*/
|
||||
private final ResourceReader resourceReader;
|
||||
|
||||
protected Database(@NotNull String playerTableName, @NotNull String dataTableName, final int maxUserDataRecords,
|
||||
@NotNull ResourceReader resourceReader, @NotNull DataAdapter dataAdapter,
|
||||
@NotNull EventCannon eventCannon, @NotNull Logger logger) {
|
||||
this.playerTableName = playerTableName;
|
||||
this.dataTableName = dataTableName;
|
||||
this.maxUserDataRecords = maxUserDataRecords;
|
||||
this.resourceReader = resourceReader;
|
||||
this.dataAdapter = dataAdapter;
|
||||
this.eventCannon = eventCannon;
|
||||
this.logger = logger;
|
||||
protected Database(@NotNull HuskSync plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,7 +58,7 @@ public abstract class Database {
|
||||
*/
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
|
||||
return formatStatementTables(new String(Objects.requireNonNull(resourceReader.getResource(schemaFileName))
|
||||
return formatStatementTables(new String(Objects.requireNonNull(plugin.getResource(schemaFileName))
|
||||
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
||||
}
|
||||
|
||||
@@ -120,16 +69,14 @@ public abstract class Database {
|
||||
* @return the formatted statement, with table placeholders replaced with the correct names
|
||||
*/
|
||||
protected final String formatStatementTables(@NotNull String sql) {
|
||||
return sql.replaceAll("%users_table%", playerTableName)
|
||||
.replaceAll("%user_data_table%", dataTableName);
|
||||
return sql.replaceAll("%users_table%", plugin.getSettings().getTableName(Settings.TableName.USERS))
|
||||
.replaceAll("%user_data_table%", plugin.getSettings().getTableName(Settings.TableName.USER_DATA));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the database and ensure tables are present; create tables if they do not exist.
|
||||
*
|
||||
* @return A future returning boolean - if the connection could be established.
|
||||
*/
|
||||
public abstract boolean initialize();
|
||||
public abstract void initialize();
|
||||
|
||||
/**
|
||||
* Ensure a {@link User} has an entry in the database and that their username is up-to-date
|
||||
@@ -184,10 +131,9 @@ public abstract class Database {
|
||||
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured.
|
||||
*
|
||||
* @param user The user to prune data for
|
||||
* @return A future returning void when complete
|
||||
* @implNote Data snapshots marked as {@code pinned} are exempt from rotation
|
||||
*/
|
||||
protected abstract CompletableFuture<Void> rotateUserData(@NotNull User user);
|
||||
protected abstract void rotateUserData(@NotNull User user);
|
||||
|
||||
/**
|
||||
* Deletes a specific {@link UserDataSnapshot} entry for a user from the database, by its UUID.
|
||||
@@ -243,4 +189,30 @@ public abstract class Database {
|
||||
*/
|
||||
public abstract void close();
|
||||
|
||||
/**
|
||||
* Identifies types of databases
|
||||
*/
|
||||
public enum Type {
|
||||
MYSQL("MySQL", "mysql"),
|
||||
MARIADB("MariaDB", "mariadb");
|
||||
|
||||
private final String displayName;
|
||||
private final String protocol;
|
||||
|
||||
Type(@NotNull String displayName, @NotNull String protocol) {
|
||||
this.displayName = displayName;
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,13 +1,33 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.database;
|
||||
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.*;
|
||||
import net.william278.husksync.data.DataAdaptionException;
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.data.UserDataSnapshot;
|
||||
import net.william278.husksync.event.DataSaveEvent;
|
||||
import net.william278.husksync.event.EventCannon;
|
||||
import net.william278.husksync.player.User;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.util.ResourceReader;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
@@ -20,54 +40,13 @@ import java.util.logging.Level;
|
||||
|
||||
public class MySqlDatabase extends Database {
|
||||
|
||||
/**
|
||||
* MySQL server hostname
|
||||
*/
|
||||
private final String mySqlHost;
|
||||
|
||||
/**
|
||||
* MySQL server port
|
||||
*/
|
||||
private final int mySqlPort;
|
||||
|
||||
/**
|
||||
* Database to use on the MySQL server
|
||||
*/
|
||||
private final String mySqlDatabaseName;
|
||||
private final String mySqlUsername;
|
||||
private final String mySqlPassword;
|
||||
private final String mySqlConnectionParameters;
|
||||
|
||||
private final int hikariMaximumPoolSize;
|
||||
private final int hikariMinimumIdle;
|
||||
private final long hikariMaximumLifetime;
|
||||
private final long hikariKeepAliveTime;
|
||||
private final long hikariConnectionTimeOut;
|
||||
|
||||
private static final String DATA_POOL_NAME = "HuskSyncHikariPool";
|
||||
private final String protocol;
|
||||
private HikariDataSource dataSource;
|
||||
|
||||
/**
|
||||
* The Hikari data source - a pool of database connections that can be fetched on-demand
|
||||
*/
|
||||
private HikariDataSource connectionPool;
|
||||
|
||||
public MySqlDatabase(@NotNull Settings settings, @NotNull ResourceReader resourceReader, @NotNull Logger logger,
|
||||
@NotNull DataAdapter dataAdapter, @NotNull EventCannon eventCannon) {
|
||||
super(settings.getTableName(Settings.TableName.USERS),
|
||||
settings.getTableName(Settings.TableName.USER_DATA),
|
||||
Math.max(1, Math.min(20, settings.maxUserDataSnapshots)),
|
||||
resourceReader, dataAdapter, eventCannon, logger);
|
||||
this.mySqlHost = settings.mySqlHost;
|
||||
this.mySqlPort = settings.mySqlPort;
|
||||
this.mySqlDatabaseName = settings.mySqlDatabase;
|
||||
this.mySqlUsername = settings.mySqlUsername;
|
||||
this.mySqlPassword = settings.mySqlPassword;
|
||||
this.mySqlConnectionParameters = settings.mySqlConnectionParameters;
|
||||
this.hikariMaximumPoolSize = settings.mySqlConnectionPoolSize;
|
||||
this.hikariMinimumIdle = settings.mySqlConnectionPoolIdle;
|
||||
this.hikariMaximumLifetime = settings.mySqlConnectionPoolLifetime;
|
||||
this.hikariKeepAliveTime = settings.mySqlConnectionPoolKeepAlive;
|
||||
this.hikariConnectionTimeOut = settings.mySqlConnectionPoolTimeout;
|
||||
public MySqlDatabase(@NotNull HuskSync plugin) {
|
||||
super(plugin);
|
||||
this.protocol = plugin.getSettings().getDatabaseType().getProtocol();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,46 +56,68 @@ public class MySqlDatabase extends Database {
|
||||
* @throws SQLException if the connection fails for some reason
|
||||
*/
|
||||
private Connection getConnection() throws SQLException {
|
||||
return connectionPool.getConnection();
|
||||
return dataSource.getConnection();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
try {
|
||||
// Create jdbc driver connection url
|
||||
final String jdbcUrl = "jdbc:mysql://" + mySqlHost + ":" + mySqlPort + "/" + mySqlDatabaseName + mySqlConnectionParameters;
|
||||
connectionPool = new HikariDataSource();
|
||||
connectionPool.setJdbcUrl(jdbcUrl);
|
||||
public void initialize() throws IllegalStateException {
|
||||
// Initialize the Hikari pooled connection
|
||||
dataSource = new HikariDataSource();
|
||||
dataSource.setJdbcUrl(String.format("jdbc:%s://%s:%s/%s%s",
|
||||
protocol,
|
||||
plugin.getSettings().getMySqlHost(),
|
||||
plugin.getSettings().getMySqlPort(),
|
||||
plugin.getSettings().getMySqlDatabase(),
|
||||
plugin.getSettings().getMySqlConnectionParameters()
|
||||
));
|
||||
|
||||
// Authenticate
|
||||
connectionPool.setUsername(mySqlUsername);
|
||||
connectionPool.setPassword(mySqlPassword);
|
||||
// Authenticate with the database
|
||||
dataSource.setUsername(plugin.getSettings().getMySqlUsername());
|
||||
dataSource.setPassword(plugin.getSettings().getMySqlPassword());
|
||||
|
||||
// Set various additional parameters
|
||||
connectionPool.setMaximumPoolSize(hikariMaximumPoolSize);
|
||||
connectionPool.setMinimumIdle(hikariMinimumIdle);
|
||||
connectionPool.setMaxLifetime(hikariMaximumLifetime);
|
||||
connectionPool.setKeepaliveTime(hikariKeepAliveTime);
|
||||
connectionPool.setConnectionTimeout(hikariConnectionTimeOut);
|
||||
connectionPool.setPoolName(DATA_POOL_NAME);
|
||||
// Set connection pool options
|
||||
dataSource.setMaximumPoolSize(plugin.getSettings().getMySqlConnectionPoolSize());
|
||||
dataSource.setMinimumIdle(plugin.getSettings().getMySqlConnectionPoolIdle());
|
||||
dataSource.setMaxLifetime(plugin.getSettings().getMySqlConnectionPoolLifetime());
|
||||
dataSource.setKeepaliveTime(plugin.getSettings().getMySqlConnectionPoolKeepAlive());
|
||||
dataSource.setConnectionTimeout(plugin.getSettings().getMySqlConnectionPoolTimeout());
|
||||
dataSource.setPoolName(DATA_POOL_NAME);
|
||||
|
||||
// Prepare database schema; make tables if they don't exist
|
||||
try (Connection connection = connectionPool.getConnection()) {
|
||||
// Load database schema CREATE statements from schema file
|
||||
final String[] databaseSchema = getSchemaStatements("database/mysql_schema.sql");
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
for (String tableCreationStatement : databaseSchema) {
|
||||
statement.execute(tableCreationStatement);
|
||||
}
|
||||
// Set additional connection pool properties
|
||||
final Properties properties = new Properties();
|
||||
properties.putAll(
|
||||
Map.of("cachePrepStmts", "true",
|
||||
"prepStmtCacheSize", "250",
|
||||
"prepStmtCacheSqlLimit", "2048",
|
||||
"useServerPrepStmts", "true",
|
||||
"useLocalSessionState", "true",
|
||||
"useLocalTransactionState", "true"
|
||||
));
|
||||
properties.putAll(
|
||||
Map.of(
|
||||
"rewriteBatchedStatements", "true",
|
||||
"cacheResultSetMetadata", "true",
|
||||
"cacheServerConfiguration", "true",
|
||||
"elideSetAutoCommits", "true",
|
||||
"maintainTimeStats", "false")
|
||||
);
|
||||
dataSource.setDataSourceProperties(properties);
|
||||
|
||||
// Prepare database schema; make tables if they don't exist
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
final String[] databaseSchema = getSchemaStatements(String.format("database/%s_schema.sql", protocol));
|
||||
try (Statement statement = connection.createStatement()) {
|
||||
for (String tableCreationStatement : databaseSchema) {
|
||||
statement.execute(tableCreationStatement);
|
||||
}
|
||||
return true;
|
||||
} catch (SQLException | IOException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage());
|
||||
} catch (SQLException e) {
|
||||
throw new IllegalStateException("Failed to create database tables. Please ensure you are running MySQL v8.0+ " +
|
||||
"and that your connecting user account has privileges to create tables.", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLogger().log(Level.SEVERE, "An unhandled exception occurred during database setup!", e);
|
||||
} catch (SQLException | IOException e) {
|
||||
throw new IllegalStateException("Failed to establish a connection to the MySQL database. " +
|
||||
"Please check the supplied database credentials in the config file", e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,9 +136,9 @@ public class MySqlDatabase extends Database {
|
||||
statement.setString(2, existingUser.uuid.toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
getLogger().log(Level.INFO, "Updated " + user.username + "'s name in the database (" + existingUser.username + " -> " + user.username + ")");
|
||||
plugin.log(Level.INFO, "Updated " + user.username + "'s name in the database (" + existingUser.username + " -> " + user.username + ")");
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to update a user's name on the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to update a user's name on the database", e);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -153,7 +154,7 @@ public class MySqlDatabase extends Database {
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -176,7 +177,7 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user from uuid from the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to fetch a user from uuid from the database", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
@@ -199,7 +200,7 @@ public class MySqlDatabase extends Database {
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to fetch a user by name from the database", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
@@ -226,11 +227,11 @@ public class MySqlDatabase extends Database {
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||
resultSet.getBoolean("pinned"),
|
||||
getDataAdapter().fromBytes(dataByteArray)));
|
||||
plugin.getDataAdapter().fromBytes(dataByteArray)));
|
||||
}
|
||||
}
|
||||
} catch (SQLException | DataAdaptionException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
@@ -257,13 +258,13 @@ public class MySqlDatabase extends Database {
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||
resultSet.getBoolean("pinned"),
|
||||
getDataAdapter().fromBytes(dataByteArray));
|
||||
plugin.getDataAdapter().fromBytes(dataByteArray));
|
||||
retrievedData.add(data);
|
||||
}
|
||||
return retrievedData;
|
||||
}
|
||||
} catch (SQLException | DataAdaptionException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to fetch a user's current user data from the database", e);
|
||||
}
|
||||
return retrievedData;
|
||||
});
|
||||
@@ -291,38 +292,36 @@ public class MySqlDatabase extends Database {
|
||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||
resultSet.getBoolean("pinned"),
|
||||
getDataAdapter().fromBytes(dataByteArray)));
|
||||
plugin.getDataAdapter().fromBytes(dataByteArray)));
|
||||
}
|
||||
}
|
||||
} catch (SQLException | DataAdaptionException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to fetch specific user data by UUID from the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to fetch specific user data by UUID from the database", e);
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CompletableFuture<Void> rotateUserData(@NotNull User user) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
|
||||
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
|
||||
if (unpinnedUserData.size() > maxUserDataRecords) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
DELETE FROM `%user_data_table%`
|
||||
WHERE `player_uuid`=?
|
||||
AND `pinned` IS FALSE
|
||||
ORDER BY `timestamp` ASC
|
||||
LIMIT %entry_count%;""".replace("%entry_count%",
|
||||
Integer.toString(unpinnedUserData.size() - maxUserDataRecords))))) {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to prune user data from the database", e);
|
||||
protected void rotateUserData(@NotNull User user) {
|
||||
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
|
||||
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
|
||||
if (unpinnedUserData.size() > plugin.getSettings().getMaxUserDataSnapshots()) {
|
||||
try (Connection connection = getConnection()) {
|
||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||
DELETE FROM `%user_data_table%`
|
||||
WHERE `player_uuid`=?
|
||||
AND `pinned` IS FALSE
|
||||
ORDER BY `timestamp` ASC
|
||||
LIMIT %entry_count%;""".replace("%entry_count%",
|
||||
Integer.toString(unpinnedUserData.size() - plugin.getSettings().getMaxUserDataSnapshots()))))) {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.log(Level.SEVERE, "Failed to prune user data from the database", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -338,7 +337,7 @@ public class MySqlDatabase extends Database {
|
||||
return statement.executeUpdate() > 0;
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to delete specific user data from the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to delete specific user data from the database", e);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
@@ -348,7 +347,7 @@ public class MySqlDatabase extends Database {
|
||||
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData,
|
||||
@NotNull DataSaveCause saveCause) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
final DataSaveEvent dataSaveEvent = (DataSaveEvent) getEventCannon().fireDataSaveEvent(user,
|
||||
final DataSaveEvent dataSaveEvent = (DataSaveEvent) plugin.getEventCannon().fireDataSaveEvent(user,
|
||||
userData, saveCause).join();
|
||||
if (!dataSaveEvent.isCancelled()) {
|
||||
final UserData finalData = dataSaveEvent.getUserData();
|
||||
@@ -360,14 +359,15 @@ public class MySqlDatabase extends Database {
|
||||
statement.setString(1, user.uuid.toString());
|
||||
statement.setString(2, saveCause.name());
|
||||
statement.setBlob(3, new ByteArrayInputStream(
|
||||
getDataAdapter().toBytes(finalData)));
|
||||
plugin.getDataAdapter().toBytes(finalData)));
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException | DataAdaptionException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to set user data in the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to set user data in the database", e);
|
||||
}
|
||||
}
|
||||
}).thenRun(() -> rotateUserData(user).join());
|
||||
this.rotateUserData(user);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -384,7 +384,7 @@ public class MySqlDatabase extends Database {
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to pin user data in the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to pin user data in the database", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -403,7 +403,7 @@ public class MySqlDatabase extends Database {
|
||||
statement.executeUpdate();
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to unpin user data in the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to unpin user data in the database", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -416,16 +416,16 @@ public class MySqlDatabase extends Database {
|
||||
statement.executeUpdate(formatStatementTables("DELETE FROM `%user_data_table%`;"));
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
getLogger().log(Level.SEVERE, "Failed to wipe the database", e);
|
||||
plugin.log(Level.SEVERE, "Failed to wipe the database", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (connectionPool != null) {
|
||||
if (!connectionPool.isClosed()) {
|
||||
connectionPool.close();
|
||||
if (dataSource != null) {
|
||||
if (!dataSource.isClosed()) {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
public interface CancellableEvent extends Event {
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
import net.william278.husksync.data.UserData;
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.event;
|
||||
|
||||
public interface SyncCompleteEvent extends PlayerEvent {
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.hook;
|
||||
|
||||
import com.djrapitops.plan.extension.CallEvents;
|
||||
@@ -10,12 +29,13 @@ import com.djrapitops.plan.extension.icon.Family;
|
||||
import com.djrapitops.plan.extension.icon.Icon;
|
||||
import com.djrapitops.plan.extension.table.Table;
|
||||
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.data.UserDataSnapshot;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.player.User;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
@@ -43,14 +63,14 @@ import java.util.regex.Pattern;
|
||||
@SuppressWarnings("unused")
|
||||
public class PlanDataExtension implements DataExtension {
|
||||
|
||||
private Database database;
|
||||
private HuskSync plugin;
|
||||
|
||||
private static final String UNKNOWN_STRING = "N/A";
|
||||
|
||||
private static final String PINNED_HTML_STRING = "📍 ";
|
||||
|
||||
protected PlanDataExtension(@NotNull Database database) {
|
||||
this.database = database;
|
||||
protected PlanDataExtension(@NotNull HuskSync plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
protected PlanDataExtension() {
|
||||
@@ -66,9 +86,9 @@ public class PlanDataExtension implements DataExtension {
|
||||
|
||||
private CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull UUID uuid) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
final Optional<User> optionalUser = database.getUser(uuid).join();
|
||||
final Optional<User> optionalUser = plugin.getDatabase().getUser(uuid).join();
|
||||
if (optionalUser.isPresent()) {
|
||||
return database.getCurrentUserData(optionalUser.get()).join();
|
||||
return plugin.getDatabase().getCurrentUserData(optionalUser.get()).join();
|
||||
}
|
||||
return Optional.empty();
|
||||
});
|
||||
@@ -208,11 +228,11 @@ public class PlanDataExtension implements DataExtension {
|
||||
.columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE))
|
||||
.columnThree("Cause", new Icon(Family.SOLID, "flag", Color.NONE))
|
||||
.columnFour("Pinned", new Icon(Family.SOLID, "thumbtack", Color.NONE));
|
||||
database.getUser(playerUUID).join().ifPresent(user ->
|
||||
database.getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow(
|
||||
plugin.getDatabase().getUser(playerUUID).join().ifPresent(user ->
|
||||
plugin.getDatabase().getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow(
|
||||
versionedUserData.versionTimestamp().getTime(),
|
||||
versionedUserData.versionUUID().toString().split("-")[0],
|
||||
versionedUserData.cause().name().toLowerCase().replaceAll("_", " "),
|
||||
versionedUserData.cause().name().toLowerCase(Locale.ENGLISH).replaceAll("_", " "),
|
||||
versionedUserData.pinned() ? PINNED_HTML_STRING + "Pinned" : "Unpinned"
|
||||
)));
|
||||
return dataSnapshotsTable.build();
|
||||
|
||||
@@ -1,21 +1,37 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.hook;
|
||||
|
||||
import com.djrapitops.plan.capability.CapabilityService;
|
||||
import com.djrapitops.plan.extension.ExtensionService;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class PlanHook {
|
||||
|
||||
private final Database database;
|
||||
private final Logger logger;
|
||||
private final HuskSync plugin;
|
||||
|
||||
public PlanHook(@NotNull Database database, @NotNull Logger logger) {
|
||||
this.database = database;
|
||||
this.logger = logger;
|
||||
public PlanHook(@NotNull HuskSync plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
public void hookIntoPlan() {
|
||||
@@ -33,13 +49,9 @@ public class PlanHook {
|
||||
|
||||
private void registerDataExtension() {
|
||||
try {
|
||||
ExtensionService.getInstance().register(new PlanDataExtension(database));
|
||||
} catch (IllegalStateException planIsNotEnabled) {
|
||||
logger.log(Level.SEVERE, "Plan extension hook failed to register. Plan is not enabled.", planIsNotEnabled);
|
||||
// Plan is not enabled, handle exception
|
||||
} catch (IllegalArgumentException dataExtensionImplementationIsInvalid) {
|
||||
logger.log(Level.SEVERE, "Plan extension hook failed to register. Data hook implementation is invalid.", dataExtensionImplementationIsInvalid);
|
||||
// The DataExtension implementation has an implementation error, handle exception
|
||||
ExtensionService.getInstance().register(new PlanDataExtension(plugin));
|
||||
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||
plugin.log(Level.WARNING, "Failed to register Plan data extension: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.listener;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.data.DataSaveCause;
|
||||
import net.william278.husksync.data.ItemData;
|
||||
@@ -51,13 +71,17 @@ public abstract class EventListener {
|
||||
* @param user The {@link OnlineUser} to handle
|
||||
*/
|
||||
protected final void handlePlayerJoin(@NotNull OnlineUser user) {
|
||||
if (user.isNpc()) {
|
||||
return;
|
||||
}
|
||||
|
||||
lockedPlayers.add(user.uuid);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
// Hold reading data for the network latency threshold, to ensure the source server has set the redis key
|
||||
Thread.sleep(Math.max(0, plugin.getSettings().networkLatencyMilliseconds));
|
||||
Thread.sleep(Math.max(0, plugin.getSettings().getNetworkLatencyMilliseconds()));
|
||||
} catch (InterruptedException e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "An exception occurred handling a player join", e);
|
||||
plugin.log(Level.SEVERE, "An exception occurred handling a player join", e);
|
||||
} finally {
|
||||
plugin.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> {
|
||||
if (!changingServers) {
|
||||
@@ -83,8 +107,7 @@ public abstract class EventListener {
|
||||
}
|
||||
plugin.getRedisManager().getUserData(user).thenAccept(redisUserData ->
|
||||
redisUserData.ifPresent(redisData -> {
|
||||
user.setData(redisData, plugin.getSettings(), plugin.getEventCannon(),
|
||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion())
|
||||
user.setData(redisData, plugin)
|
||||
.thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)).join();
|
||||
executor.shutdown();
|
||||
})).join();
|
||||
@@ -106,8 +129,7 @@ public abstract class EventListener {
|
||||
private CompletableFuture<Boolean> setUserFromDatabase(@NotNull OnlineUser user) {
|
||||
return plugin.getDatabase().getCurrentUserData(user).thenApply(databaseUserData -> {
|
||||
if (databaseUserData.isPresent()) {
|
||||
return user.setData(databaseUserData.get().userData(), plugin.getSettings(), plugin.getEventCannon(),
|
||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).join();
|
||||
return user.setData(databaseUserData.get().userData(), plugin).join();
|
||||
}
|
||||
return true;
|
||||
});
|
||||
@@ -121,9 +143,17 @@ public abstract class EventListener {
|
||||
*/
|
||||
private void handleSynchronisationCompletion(@NotNull OnlineUser user, boolean succeeded) {
|
||||
if (succeeded) {
|
||||
plugin.getLocales().getLocale("synchronisation_complete").ifPresent(user::sendActionBar);
|
||||
lockedPlayers.remove(user.uuid);
|
||||
switch (plugin.getSettings().getNotificationDisplaySlot()) {
|
||||
case CHAT -> plugin.getLocales().getLocale("synchronisation_complete")
|
||||
.ifPresent(user::sendMessage);
|
||||
case ACTION_BAR -> plugin.getLocales().getLocale("synchronisation_complete")
|
||||
.ifPresent(user::sendActionBar);
|
||||
case TOAST -> plugin.getLocales().getLocale("synchronisation_complete")
|
||||
.ifPresent(locale -> user.sendToast(locale, new MineDown(""),
|
||||
"minecraft:bell", "TASK"));
|
||||
}
|
||||
plugin.getDatabase().ensureUser(user).join();
|
||||
lockedPlayers.remove(user.uuid);
|
||||
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||
} else {
|
||||
plugin.getLocales().getLocale("synchronisation_failed")
|
||||
@@ -143,19 +173,19 @@ public abstract class EventListener {
|
||||
return;
|
||||
}
|
||||
// Don't sync players awaiting synchronization
|
||||
if (lockedPlayers.contains(user.uuid)) {
|
||||
if (lockedPlayers.contains(user.uuid) || user.isNpc()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle asynchronous disconnection
|
||||
lockedPlayers.add(user.uuid);
|
||||
CompletableFuture.runAsync(() -> plugin.getRedisManager().setUserServerSwitch(user)
|
||||
.thenRun(() -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).thenAccept(
|
||||
.thenRun(() -> user.getUserData(plugin).thenAccept(
|
||||
optionalUserData -> optionalUserData.ifPresent(userData -> plugin.getRedisManager()
|
||||
.setUserData(user, userData).thenRun(() -> plugin.getDatabase()
|
||||
.setUserData(user, userData, DataSaveCause.DISCONNECT)))))
|
||||
.thenRun(() -> lockedPlayers.remove(user.uuid)).exceptionally(throwable -> {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
||||
.exceptionally(throwable -> {
|
||||
plugin.log(Level.SEVERE,
|
||||
"An exception occurred handling a player disconnection");
|
||||
throwable.printStackTrace();
|
||||
return null;
|
||||
@@ -168,11 +198,14 @@ public abstract class EventListener {
|
||||
* @param usersInWorld a list of users in the world that is being saved
|
||||
*/
|
||||
protected final void saveOnWorldSave(@NotNull List<OnlineUser> usersInWorld) {
|
||||
if (disabling || !plugin.getSettings().saveOnWorldSave) {
|
||||
if (disabling || !plugin.getSettings().doSaveOnWorldSave()) {
|
||||
return;
|
||||
}
|
||||
usersInWorld.forEach(user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.WORLD_SAVE).join()));
|
||||
usersInWorld.stream()
|
||||
.filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc())
|
||||
.forEach(user -> user.getUserData(plugin)
|
||||
.thenAccept(data -> data.ifPresent(userData -> plugin.getDatabase()
|
||||
.setUserData(user, userData, DataSaveCause.WORLD_SAVE))));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,11 +215,12 @@ public abstract class EventListener {
|
||||
* @param drops The items that this user would have dropped
|
||||
*/
|
||||
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull ItemData drops) {
|
||||
if (disabling || !plugin.getSettings().saveOnDeath) {
|
||||
if (disabling || !plugin.getSettings().doSaveOnDeath() || lockedPlayers.contains(user.uuid) || user.isNpc()
|
||||
|| (!plugin.getSettings().doSaveEmptyDropsOnDeath() && drops.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
|
||||
user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings())
|
||||
user.getUserData(plugin)
|
||||
.thenAccept(data -> data.ifPresent(userData -> {
|
||||
userData.getInventory().orElse(ItemData.empty()).serializedItems = drops.serializedItems;
|
||||
plugin.getDatabase().setUserData(user, userData, DataSaveCause.DEATH);
|
||||
@@ -196,11 +230,11 @@ public abstract class EventListener {
|
||||
/**
|
||||
* Determine whether a player event should be cancelled
|
||||
*
|
||||
* @param user {@link OnlineUser} performing the event
|
||||
* @param userUuid The UUID of the user to check
|
||||
* @return Whether the event should be cancelled
|
||||
*/
|
||||
protected final boolean cancelPlayerEvent(@NotNull OnlineUser user) {
|
||||
return disabling || lockedPlayers.contains(user.uuid);
|
||||
protected final boolean cancelPlayerEvent(@NotNull UUID userUuid) {
|
||||
return disabling || lockedPlayers.contains(userUuid);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,12 +243,23 @@ public abstract class EventListener {
|
||||
public final void handlePluginDisable() {
|
||||
disabling = true;
|
||||
|
||||
plugin.getOnlineUsers().stream().filter(user -> !lockedPlayers.contains(user.uuid)).forEach(
|
||||
user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join()));
|
||||
// Save data for all online users
|
||||
plugin.getOnlineUsers().stream()
|
||||
.filter(user -> !lockedPlayers.contains(user.uuid) && !user.isNpc())
|
||||
.forEach(user -> {
|
||||
lockedPlayers.add(user.uuid);
|
||||
user.getUserData(plugin).join()
|
||||
.ifPresent(userData -> plugin.getDatabase()
|
||||
.setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join());
|
||||
});
|
||||
|
||||
// Close outstanding connections
|
||||
plugin.getDatabase().close();
|
||||
plugin.getRedisManager().close();
|
||||
}
|
||||
|
||||
public final Set<UUID> getLockedPlayers() {
|
||||
return this.lockedPlayers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,27 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.migrator;
|
||||
|
||||
import net.william278.husksync.HuskSync;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.player;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import net.william278.desertwell.Version;
|
||||
import de.themoep.minedown.adventure.MineDownParser;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.*;
|
||||
import net.william278.husksync.event.EventCannon;
|
||||
import net.william278.husksync.event.PreSyncEvent;
|
||||
import net.william278.husksync.util.Logger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -44,12 +62,12 @@ public abstract class OnlineUser extends User {
|
||||
public final CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
|
||||
@NotNull List<StatusDataFlag> statusDataFlags) {
|
||||
final Settings settings = new Settings();
|
||||
settings.synchronizationFeatures.put(Settings.SynchronizationFeature.HEALTH.name().toLowerCase(), statusDataFlags.contains(StatusDataFlag.SET_HEALTH));
|
||||
settings.synchronizationFeatures.put(Settings.SynchronizationFeature.MAX_HEALTH.name().toLowerCase(), statusDataFlags.contains(StatusDataFlag.SET_MAX_HEALTH));
|
||||
settings.synchronizationFeatures.put(Settings.SynchronizationFeature.HUNGER.name().toLowerCase(), statusDataFlags.contains(StatusDataFlag.SET_HUNGER));
|
||||
settings.synchronizationFeatures.put(Settings.SynchronizationFeature.EXPERIENCE.name().toLowerCase(), statusDataFlags.contains(StatusDataFlag.SET_EXPERIENCE));
|
||||
settings.synchronizationFeatures.put(Settings.SynchronizationFeature.INVENTORIES.name().toLowerCase(), statusDataFlags.contains(StatusDataFlag.SET_SELECTED_ITEM_SLOT));
|
||||
settings.synchronizationFeatures.put(Settings.SynchronizationFeature.LOCATION.name().toLowerCase(), statusDataFlags.contains(StatusDataFlag.SET_GAME_MODE) || statusDataFlags.contains(StatusDataFlag.SET_FLYING));
|
||||
settings.getSynchronizationFeatures().put(Settings.SynchronizationFeature.HEALTH.name().toLowerCase(Locale.ENGLISH), statusDataFlags.contains(StatusDataFlag.SET_HEALTH));
|
||||
settings.getSynchronizationFeatures().put(Settings.SynchronizationFeature.MAX_HEALTH.name().toLowerCase(Locale.ENGLISH), statusDataFlags.contains(StatusDataFlag.SET_MAX_HEALTH));
|
||||
settings.getSynchronizationFeatures().put(Settings.SynchronizationFeature.HUNGER.name().toLowerCase(Locale.ENGLISH), statusDataFlags.contains(StatusDataFlag.SET_HUNGER));
|
||||
settings.getSynchronizationFeatures().put(Settings.SynchronizationFeature.EXPERIENCE.name().toLowerCase(Locale.ENGLISH), statusDataFlags.contains(StatusDataFlag.SET_EXPERIENCE));
|
||||
settings.getSynchronizationFeatures().put(Settings.SynchronizationFeature.INVENTORIES.name().toLowerCase(Locale.ENGLISH), statusDataFlags.contains(StatusDataFlag.SET_SELECTED_ITEM_SLOT));
|
||||
settings.getSynchronizationFeatures().put(Settings.SynchronizationFeature.LOCATION.name().toLowerCase(Locale.ENGLISH), statusDataFlags.contains(StatusDataFlag.SET_GAME_MODE) || statusDataFlags.contains(StatusDataFlag.SET_FLYING));
|
||||
return setStatus(statusData, settings);
|
||||
}
|
||||
|
||||
@@ -60,8 +78,7 @@ public abstract class OnlineUser extends User {
|
||||
* @param settings settings, containing information about which features should be synced
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public abstract CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
|
||||
@NotNull Settings settings);
|
||||
public abstract CompletableFuture<Void> setStatus(@NotNull StatusData statusData, @NotNull Settings settings);
|
||||
|
||||
/**
|
||||
* Get the player's inventory {@link ItemData} contents
|
||||
@@ -184,19 +201,55 @@ public abstract class OnlineUser extends User {
|
||||
@NotNull
|
||||
public abstract Version getMinecraftVersion();
|
||||
|
||||
/**
|
||||
* Get the player's adventure {@link Audience}
|
||||
*
|
||||
* @return the player's {@link Audience}
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Audience getAudience();
|
||||
|
||||
/**
|
||||
* Send a message to this player
|
||||
*
|
||||
* @param component the {@link Component} message to send
|
||||
*/
|
||||
public void sendMessage(@NotNull Component component) {
|
||||
getAudience().sendMessage(component);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a MineDown-formatted message to this player
|
||||
*
|
||||
* @param mineDown the parsed {@link MineDown} to send
|
||||
*/
|
||||
public abstract void sendMessage(@NotNull MineDown mineDown);
|
||||
public void sendMessage(@NotNull MineDown mineDown) {
|
||||
sendMessage(mineDown
|
||||
.disable(MineDownParser.Option.SIMPLE_FORMATTING)
|
||||
.replace().toComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a MineDown-formatted action bar message to this player
|
||||
*
|
||||
* @param mineDown the parsed {@link MineDown} to send
|
||||
*/
|
||||
public abstract void sendActionBar(@NotNull MineDown mineDown);
|
||||
public void sendActionBar(@NotNull MineDown mineDown) {
|
||||
getAudience().sendActionBar(mineDown
|
||||
.disable(MineDownParser.Option.SIMPLE_FORMATTING)
|
||||
.replace().toComponent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch a toast message to this player
|
||||
*
|
||||
* @param title the title of the toast
|
||||
* @param description the description of the toast
|
||||
* @param iconMaterial the namespace-keyed material to use as an icon of the toast
|
||||
* @param backgroundType the background ("ToastType") of the toast
|
||||
*/
|
||||
public abstract void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||
@NotNull String iconMaterial, @NotNull String backgroundType);
|
||||
|
||||
/**
|
||||
* Returns if the player has the permission node
|
||||
@@ -232,37 +285,32 @@ public abstract class OnlineUser extends User {
|
||||
* This will only set data that is enabled as per the enabled settings in the config file.
|
||||
* Data present in the {@link UserData} object, but not enabled to be set in the config, will be ignored.
|
||||
*
|
||||
* @param data The {@link UserData} to set to the player
|
||||
* @param settings The plugin {@link Settings} to determine which data to set
|
||||
* @param eventCannon The {@link EventCannon} to fire the synchronisation events
|
||||
* @param logger The {@link Logger} for debug and error logging
|
||||
* @param serverMinecraftVersion The server's Minecraft version, for validating the format of the {@link UserData}
|
||||
* @param plugin The plugin instance
|
||||
* @return a future returning a boolean when complete; if the sync was successful, the future will return {@code true}.
|
||||
*/
|
||||
public final CompletableFuture<Boolean> setData(@NotNull UserData data, @NotNull Settings settings,
|
||||
@NotNull EventCannon eventCannon, @NotNull Logger logger,
|
||||
@NotNull Version serverMinecraftVersion) {
|
||||
public final CompletableFuture<Boolean> setData(@NotNull UserData data, @NotNull HuskSync plugin) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
// Prevent synchronising user data from newer versions of Minecraft
|
||||
if (Version.fromMinecraftVersionString(data.getMinecraftVersion()).compareTo(serverMinecraftVersion) > 0) {
|
||||
logger.log(Level.SEVERE, "Cannot set data for " + username +
|
||||
if (Version.fromString(data.getMinecraftVersion()).compareTo(plugin.getMinecraftVersion()) > 0) {
|
||||
plugin.log(Level.SEVERE, "Cannot set data for " + username +
|
||||
" because the Minecraft version of their user data (" + data.getMinecraftVersion() +
|
||||
") is newer than the server's Minecraft version (" + serverMinecraftVersion + ").");
|
||||
") is newer than the server's Minecraft version (" + plugin.getMinecraftVersion() + ").");
|
||||
return false;
|
||||
}
|
||||
// Prevent synchronising user data from newer versions of the plugin
|
||||
if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) {
|
||||
logger.log(Level.SEVERE, "Cannot set data for " + username +
|
||||
plugin.log(Level.SEVERE, "Cannot set data for " + username +
|
||||
" because the format version of their user data (v" + data.getFormatVersion() +
|
||||
") is newer than the current format version (v" + UserData.CURRENT_FORMAT_VERSION + ").");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fire the PreSyncEvent
|
||||
final PreSyncEvent preSyncEvent = (PreSyncEvent) eventCannon.firePreSyncEvent(this, data).join();
|
||||
final PreSyncEvent preSyncEvent = (PreSyncEvent) plugin.getEventCannon().firePreSyncEvent(this, data).join();
|
||||
final UserData finalData = preSyncEvent.getUserData();
|
||||
final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{
|
||||
if (!isOffline() && !preSyncEvent.isCancelled()) {
|
||||
final Settings settings = plugin.getSettings();
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
||||
finalData.getInventory().ifPresent(itemData -> add(setInventory(itemData)));
|
||||
}
|
||||
@@ -292,7 +340,7 @@ public abstract class OnlineUser extends User {
|
||||
return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true)
|
||||
.exceptionally(exception -> {
|
||||
// Handle synchronisation exceptions
|
||||
logger.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
|
||||
plugin.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
|
||||
exception.printStackTrace();
|
||||
return false;
|
||||
}).join();
|
||||
@@ -311,52 +359,65 @@ public abstract class OnlineUser extends User {
|
||||
* <p>
|
||||
* If the user data could not be returned due to an exception, the optional will return empty
|
||||
*
|
||||
* @param logger The logger to use for handling exceptions
|
||||
* @return the player's current {@link UserData} in an optional; empty if an exception occurs
|
||||
* @param plugin The plugin instance
|
||||
*/
|
||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull Logger logger, @NotNull Settings settings) {
|
||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull HuskSync plugin) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
final UserDataBuilder builder = UserData.builder(getMinecraftVersion());
|
||||
final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{
|
||||
if (!isOffline()) {
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
||||
if (isDead() && settings.saveDeadPlayerInventories) {
|
||||
add(CompletableFuture.runAsync(() -> builder.setInventory(ItemData.empty())));
|
||||
} else {
|
||||
add(getInventory().thenAccept(builder::setInventory));
|
||||
}
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.ENDER_CHESTS)) {
|
||||
add(getEnderChest().thenAccept(builder::setEnderChest));
|
||||
}
|
||||
add(getStatus().thenAccept(builder::setStatus));
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.POTION_EFFECTS)) {
|
||||
add(getPotionEffects().thenAccept(builder::setPotionEffects));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.ADVANCEMENTS)) {
|
||||
add(getAdvancements().thenAccept(builder::setAdvancements));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.STATISTICS)) {
|
||||
add(getStatistics().thenAccept(builder::setStatistics));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.LOCATION)) {
|
||||
add(getLocation().thenAccept(builder::setLocation));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.PERSISTENT_DATA_CONTAINER)) {
|
||||
add(getPersistentDataContainer().thenAccept(builder::setPersistentDataContainer));
|
||||
}
|
||||
final UserDataBuilder builder = UserData.builder(getMinecraftVersion());
|
||||
final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{
|
||||
if (!isOffline()) {
|
||||
final Settings settings = plugin.getSettings();
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
||||
if (isDead() && settings.isSynchroniseDeadPlayersChangingServer()) {
|
||||
plugin.debug("Player " + username + " is dead, so their inventory will be set to empty.");
|
||||
add(CompletableFuture.runAsync(() -> builder.setInventory(ItemData.empty())));
|
||||
} else {
|
||||
add(getInventory().thenAccept(builder::setInventory));
|
||||
}
|
||||
}};
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.ENDER_CHESTS)) {
|
||||
add(getEnderChest().thenAccept(builder::setEnderChest));
|
||||
}
|
||||
add(getStatus().thenAccept(builder::setStatus));
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.POTION_EFFECTS)) {
|
||||
add(getPotionEffects().thenAccept(builder::setPotionEffects));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.ADVANCEMENTS)) {
|
||||
add(getAdvancements().thenAccept(builder::setAdvancements));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.STATISTICS)) {
|
||||
add(getStatistics().thenAccept(builder::setStatistics));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.LOCATION)) {
|
||||
add(getLocation().thenAccept(builder::setLocation));
|
||||
}
|
||||
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.PERSISTENT_DATA_CONTAINER)) {
|
||||
add(getPersistentDataContainer().thenAccept(builder::setPersistentDataContainer));
|
||||
}
|
||||
}
|
||||
}};
|
||||
|
||||
// Apply operations in parallel, join when complete
|
||||
CompletableFuture.allOf(dataGetOperations.toArray(new CompletableFuture[0])).join();
|
||||
return Optional.of(builder.build());
|
||||
})
|
||||
.exceptionally(exception -> {
|
||||
logger.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
|
||||
exception.printStackTrace();
|
||||
return Optional.empty();
|
||||
});
|
||||
// Apply operations in parallel, join when complete
|
||||
CompletableFuture.allOf(dataGetOperations.toArray(new CompletableFuture[0])).join();
|
||||
return Optional.of(builder.build());
|
||||
}).exceptionally(exception -> {
|
||||
plugin.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
|
||||
exception.printStackTrace();
|
||||
return Optional.empty();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get if the player is locked
|
||||
*
|
||||
* @return the player's locked status
|
||||
*/
|
||||
public abstract boolean isLocked();
|
||||
|
||||
/**
|
||||
* Get if the player is a NPC
|
||||
*
|
||||
* @return if the player is a NPC with metadata
|
||||
*/
|
||||
public abstract boolean isNpc();
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.player;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.redis;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public enum RedisKeyType {
|
||||
CACHE(60 * 60 * 24),
|
||||
DATA_UPDATE(10),
|
||||
@@ -15,6 +36,6 @@ public enum RedisKeyType {
|
||||
|
||||
@NotNull
|
||||
public String getKeyPrefix() {
|
||||
return RedisManager.KEY_NAMESPACE.toLowerCase() + ":" + RedisManager.clusterId.toLowerCase() + ":" + name().toLowerCase();
|
||||
return RedisManager.KEY_NAMESPACE.toLowerCase(Locale.ENGLISH) + ":" + RedisManager.clusterId.toLowerCase(Locale.ENGLISH) + ":" + name().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.redis;
|
||||
|
||||
import de.themoep.minedown.adventure.MineDown;
|
||||
import net.william278.husksync.HuskSync;
|
||||
import net.william278.husksync.data.UserData;
|
||||
import net.william278.husksync.player.User;
|
||||
@@ -18,7 +38,7 @@ import java.util.concurrent.CompletableFuture;
|
||||
/**
|
||||
* Manages the connection to the Redis server, handling the caching of user data
|
||||
*/
|
||||
public class RedisManager {
|
||||
public class RedisManager extends JedisPubSub {
|
||||
|
||||
protected static final String KEY_NAMESPACE = "husksync:";
|
||||
protected static String clusterId = "";
|
||||
@@ -32,13 +52,13 @@ public class RedisManager {
|
||||
|
||||
public RedisManager(@NotNull HuskSync plugin) {
|
||||
this.plugin = plugin;
|
||||
clusterId = plugin.getSettings().clusterId;
|
||||
clusterId = plugin.getSettings().getClusterId();
|
||||
|
||||
// Set redis credentials
|
||||
this.redisHost = plugin.getSettings().redisHost;
|
||||
this.redisPort = plugin.getSettings().redisPort;
|
||||
this.redisPassword = plugin.getSettings().redisPassword;
|
||||
this.redisUseSsl = plugin.getSettings().redisUseSsl;
|
||||
this.redisHost = plugin.getSettings().getRedisHost();
|
||||
this.redisPort = plugin.getSettings().getRedisPort();
|
||||
this.redisPassword = plugin.getSettings().getRedisPassword();
|
||||
this.redisUseSsl = plugin.getSettings().isRedisUseSsl();
|
||||
|
||||
// Configure the jedis pool
|
||||
this.jedisPoolConfig = new JedisPoolConfig();
|
||||
@@ -52,21 +72,19 @@ public class RedisManager {
|
||||
*
|
||||
* @return a future returning void when complete
|
||||
*/
|
||||
public CompletableFuture<Boolean> initialize() {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (redisPassword.isBlank()) {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisUseSsl);
|
||||
} else {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisPassword, redisUseSsl);
|
||||
}
|
||||
try {
|
||||
jedisPool.getResource().ping();
|
||||
} catch (JedisException e) {
|
||||
return false;
|
||||
}
|
||||
CompletableFuture.runAsync(this::subscribe);
|
||||
return true;
|
||||
});
|
||||
public boolean initialize() {
|
||||
if (redisPassword.isBlank()) {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisUseSsl);
|
||||
} else {
|
||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisPassword, redisUseSsl);
|
||||
}
|
||||
try {
|
||||
jedisPool.getResource().ping();
|
||||
} catch (JedisException e) {
|
||||
return false;
|
||||
}
|
||||
CompletableFuture.runAsync(this::subscribe);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void subscribe() {
|
||||
@@ -74,33 +92,42 @@ public class RedisManager {
|
||||
new Jedis(redisHost, redisPort, DefaultJedisClientConfig.builder()
|
||||
.password(redisPassword).timeoutMillis(0).ssl(redisUseSsl).build())) {
|
||||
subscriber.connect();
|
||||
subscriber.subscribe(new JedisPubSub() {
|
||||
@Override
|
||||
public void onMessage(@NotNull String channel, @NotNull String message) {
|
||||
RedisMessageType.getTypeFromChannel(channel).ifPresent(messageType -> {
|
||||
if (messageType == RedisMessageType.UPDATE_USER_DATA) {
|
||||
final RedisMessage redisMessage = RedisMessage.fromJson(message);
|
||||
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
|
||||
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
|
||||
user.setData(userData, plugin.getSettings(), plugin.getEventCannon(),
|
||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).thenAccept(succeeded -> {
|
||||
if (succeeded) {
|
||||
plugin.getLocales().getLocale("data_update_complete")
|
||||
.ifPresent(user::sendActionBar);
|
||||
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||
} else {
|
||||
plugin.getLocales().getLocale("data_update_failed")
|
||||
.ifPresent(user::sendMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}, Arrays.stream(RedisMessageType.values()).map(RedisMessageType::getMessageChannel).toArray(String[]::new));
|
||||
subscriber.subscribe(this, Arrays.stream(RedisMessageType.values())
|
||||
.map(RedisMessageType::getMessageChannel)
|
||||
.toArray(String[]::new));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(@NotNull String channel, @NotNull String message) {
|
||||
final RedisMessageType messageType = RedisMessageType.getTypeFromChannel(channel).orElse(null);
|
||||
if (messageType != RedisMessageType.UPDATE_USER_DATA) {
|
||||
return;
|
||||
}
|
||||
|
||||
final RedisMessage redisMessage = RedisMessage.fromJson(message);
|
||||
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
|
||||
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
|
||||
user.setData(userData, plugin).thenAccept(succeeded -> {
|
||||
if (succeeded) {
|
||||
switch (plugin.getSettings().getNotificationDisplaySlot()) {
|
||||
case CHAT -> plugin.getLocales().getLocale("data_update_complete")
|
||||
.ifPresent(user::sendMessage);
|
||||
case ACTION_BAR -> plugin.getLocales().getLocale("data_update_complete")
|
||||
.ifPresent(user::sendActionBar);
|
||||
case TOAST -> plugin.getLocales().getLocale("data_update_complete")
|
||||
.ifPresent(locale -> user.sendToast(locale, new MineDown(""),
|
||||
"minecraft:bell", "TASK"));
|
||||
}
|
||||
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||
} else {
|
||||
plugin.getLocales().getLocale("data_update_failed")
|
||||
.ifPresent(user::sendMessage);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
protected void sendMessage(@NotNull String channel, @NotNull String message) {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.publish(channel, message);
|
||||
@@ -131,7 +158,7 @@ public class RedisManager {
|
||||
plugin.getDataAdapter().toBytes(userData));
|
||||
|
||||
// Debug logging
|
||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
|
||||
plugin.debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
|
||||
+ " key to redis at: " +
|
||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||
}
|
||||
@@ -147,7 +174,7 @@ public class RedisManager {
|
||||
try (Jedis jedis = jedisPool.getResource()) {
|
||||
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
|
||||
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
|
||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name()
|
||||
plugin.debug("[" + user.username + "] Set " + RedisKeyType.SERVER_SWITCH.name()
|
||||
+ " key to redis at: " +
|
||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||
} catch (Exception e) {
|
||||
@@ -168,12 +195,12 @@ public class RedisManager {
|
||||
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
||||
final byte[] dataByteArray = jedis.get(key);
|
||||
if (dataByteArray == null) {
|
||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " +
|
||||
plugin.debug("[" + user.username + "] Could not read " +
|
||||
RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||
return Optional.empty();
|
||||
}
|
||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
|
||||
plugin.debug("[" + user.username + "] Successfully read "
|
||||
+ RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||
|
||||
@@ -195,12 +222,12 @@ public class RedisManager {
|
||||
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
|
||||
final byte[] readData = jedis.get(key);
|
||||
if (readData == null) {
|
||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Could not read " +
|
||||
plugin.debug("[" + user.username + "] Could not read " +
|
||||
RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||
return false;
|
||||
}
|
||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
|
||||
plugin.debug("[" + user.username + "] Successfully read "
|
||||
+ RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.redis;
|
||||
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
@@ -1,8 +1,28 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.redis;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
public enum RedisMessageType {
|
||||
@@ -11,8 +31,8 @@ public enum RedisMessageType {
|
||||
|
||||
@NotNull
|
||||
public String getMessageChannel() {
|
||||
return RedisManager.KEY_NAMESPACE.toLowerCase() + ":" + RedisManager.clusterId.toLowerCase()
|
||||
+ ":" + name().toLowerCase();
|
||||
return RedisManager.KEY_NAMESPACE.toLowerCase(Locale.ENGLISH) + ":" + RedisManager.clusterId.toLowerCase(Locale.ENGLISH)
|
||||
+ ":" + name().toLowerCase(Locale.ENGLISH);
|
||||
}
|
||||
|
||||
public static Optional<RedisMessageType> getTypeFromChannel(@NotNull String messageChannel) {
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -13,6 +32,7 @@ import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Locale;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.logging.Level;
|
||||
|
||||
@@ -97,7 +117,7 @@ public class DataDumper {
|
||||
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
|
||||
}
|
||||
} catch (Exception e) {
|
||||
plugin.getLoggingAdapter().log(Level.SEVERE, "Failed to upload data to logs site", e);
|
||||
plugin.log(Level.SEVERE, "Failed to upload data to logs site", e);
|
||||
}
|
||||
return "(Failed to upload to logs site)";
|
||||
}
|
||||
@@ -164,7 +184,7 @@ public class DataDumper {
|
||||
return new StringJoiner("_")
|
||||
.add(user.username)
|
||||
.add(new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(dataSnapshot.versionTimestamp()))
|
||||
.add(dataSnapshot.cause().name().toLowerCase())
|
||||
.add(dataSnapshot.cause().name().toLowerCase(Locale.ENGLISH))
|
||||
.add(dataSnapshot.versionUUID().toString().split("-")[0])
|
||||
+ ".json";
|
||||
}
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import net.william278.husksync.config.Locales;
|
||||
@@ -32,7 +51,7 @@ public class DataSnapshotList {
|
||||
.format(snapshot.versionTimestamp()),
|
||||
snapshot.versionUUID().toString().split("-")[0],
|
||||
snapshot.versionUUID().toString(),
|
||||
snapshot.cause().name().toLowerCase().replaceAll("_", " "),
|
||||
snapshot.cause().getDisplayName(),
|
||||
dataOwner.username,
|
||||
snapshot.pinned() ? "※" : " ")
|
||||
.orElse("• " + snapshot.versionUUID())).toList(),
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* An abstract, cross-platform representation of a logger
|
||||
*/
|
||||
public abstract class Logger {
|
||||
|
||||
private boolean debug;
|
||||
|
||||
public abstract void log(@NotNull Level level, @NotNull String message, @NotNull Exception e);
|
||||
|
||||
public abstract void log(@NotNull Level level, @NotNull String message);
|
||||
|
||||
public abstract void info(@NotNull String message);
|
||||
|
||||
public abstract void severe(@NotNull String message);
|
||||
|
||||
public final void debug(@NotNull String message) {
|
||||
if (debug) {
|
||||
log(Level.INFO, "[DEBUG] " + message);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void config(@NotNull String message);
|
||||
|
||||
public final void showDebugLogs(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package net.william278.husksync.util;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Abstract representation of a reader that reads internal resource files by name
|
||||
*/
|
||||
public interface ResourceReader {
|
||||
|
||||
/**
|
||||
* Gets the resource with given filename and reads it as an {@link InputStream}
|
||||
*
|
||||
* @param fileName Name of the resource file to read
|
||||
* @return The resource, read as an {@link InputStream}; or {@code null} if the resource was not found
|
||||
*/
|
||||
@Nullable InputStream getResource(String fileName);
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
synchronisation_complete: '[⏵ Daten synchronisiert!](#00fb9a)'
|
||||
synchronisation_failed: '[⏵ Ein Fehler ist beim Synchronisieren deiner Daten aufgetreten! Bitte kontaktiere einen Administrator.](#ff7e5e)'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Sprachdateien wurden neu geladen.'
|
||||
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Sprachdateien wurden neu geladen.](#00fb9a)'
|
||||
error_invalid_syntax: '[Fehler:](#ff3300) [Falsche Syntax. Nutze: %1%](#ff7e5e)'
|
||||
error_invalid_player: '[Fehler:](#ff3300) [Es konnte kein Spieler mit diesem Namen gefunden werden.](#ff7e5e)'
|
||||
error_no_permission: '[Fehler:](#ff3300) [Du hast nicht die benötigten Berechtigungen um diesen Befehl auszuführen](#ff7e5e)'
|
||||
@@ -14,28 +14,28 @@ inventory_viewer_opened: '[Du siehst den Schnappschuss des Inventares von](#00fb
|
||||
ender_chest_viewer_opened: '[Du siehst den Schnappschuss der Endertruhe von](#00fb9a) [%1%](#00fb9a bold) [von ⌚ %2%](#00fb9a)'
|
||||
data_update_complete: '[🔔 Deine Daten wurden aktualisiert!](#00fb9a)'
|
||||
data_update_failed: '[🔔 Ein Fehler ist beim Aktualisieren deiner Daten aufgetreten! Bitte kontaktiere einen Administrator.](#ff7e5e)'
|
||||
data_manager_title: '[Du siehst den Nutzerdaten-Schnappschuss](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für [%3%](#00fb9a bold show_text=&7Spieler-UUID:\n&8%4%)[:](#00fb9a)'
|
||||
data_manager_title: '[Du siehst den Nutzerdaten-Schnappschuss](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für %3%](#00fb9a bold show_text=&7Spieler-UUID:\n&8%4%)[:](#00fb9a)'
|
||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Versions-Zeitstempel:\n&8Zeitpunkt der Speicherung der Daten)'
|
||||
data_manager_pinned: '[※ Schnappschuss angeheftet](#d8ff2b show_text=&7Angeheftet:\n&8Dieser Nutzerdaten-Schnappschuss wird nicht automatisch rotiert.)'
|
||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Speicherungsgrund:\n&8Der Grund für das Speichern der Daten)\n'
|
||||
data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Lebenspunkte) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hungerpunkte) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP-Level) [🏹 %5%](dark_aqua show_text=&7Spielmodus)'
|
||||
data_manager_advancements_statistics: '[⭐ Erfolge: %1%](color=#ffc43b-#f5c962 show_text=&7Erfolge in denen du Fortschritt gemacht hast:\n&8%2%) [⌛ Spielzeit: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Deine verbrachte Zeit im Spiel\n&8⚠ Basierend auf Spielstatistiken)\n'
|
||||
data_manager_item_buttons: '[View:](gray) [[🪣 Inventar…]](color=#a17b5f-#f5b98c show_text=&7Klicke zum Ansehen run_command=/inventory %1% %2%) [[⌀ Endertruhe…]](#b649c4-#d254ff show_text=&7Klicke zum Ansehen run_command=/enderchest %1% %2%)'
|
||||
data_manager_management_buttons: '[Verwalte:](gray) [[❌ Löschen…]](#ff3300 show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss zu löschen.\n&8Dies betrifft nicht die aktuellen Nutzerdaten.\n&#ff3300&⚠ Dieser Schritt kann nicht rückgängig gemacht werden! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Wiederherstellen…]](#00fb9a show_text=&7Klicke, um die Nutzerdaten wiederherzustellen.\n&8Dies wird die Nutzerdaten auf den Stand des Schnappschusses setzen.\n&#ff3300&⚠ Die aktuellen Nutzerdaten von %1% werden überschrieben! suggest_command=/husksync:userdata restore %1% %2%) [[※ Anheften/Loslösen…]](#d8ff2b show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss anzuheften oder loszulösen\n&8Angeheftete Nutzerdaten-Schnappschüsse werden nicht automatisch rotiert run_command=/userdata pin %1% %2%)'
|
||||
data_manager_system_buttons: '[System:](gray) [[⏷ File Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to a file.\n&8Data dumps can be found in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to the mc-logs service\n&8You will be provided with a URL containing the data. run_command=/husksync:userdata dump %1% %2% web)'
|
||||
data_manager_item_buttons: '[Sehen:](gray) [[🪣 Inventar…]](color=#a17b5f-#f5b98c show_text=&7Klicke zum Ansehen run_command=/inventory %1% %2%) [[⌀ Endertruhe…]](#b649c4-#d254ff show_text=&7Klicke zum Ansehen run_command=/enderchest %1% %2%)'
|
||||
data_manager_management_buttons: '[Verwalten:](gray) [[❌ Löschen…]](#ff3300 show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss zu löschen.\n&8Dies betrifft nicht die aktuellen Nutzerdaten.\n&#ff3300&⚠ Dieser Schritt kann nicht rückgängig gemacht werden! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Wiederherstellen…]](#00fb9a show_text=&7Klicke, um die Nutzerdaten wiederherzustellen.\n&8Dies wird die Nutzerdaten auf den Stand des Schnappschusses setzen.\n&#ff3300&⚠ Die aktuellen Nutzerdaten von %1% werden überschrieben! suggest_command=/husksync:userdata restore %1% %2%) [[※ Anheften/Loslösen…]](#d8ff2b show_text=&7Klicke, um diesen Nutzerdaten-Schnappschuss anzuheften oder loszulösen\n&8Angeheftete Nutzerdaten-Schnappschüsse werden nicht automatisch rotiert run_command=/userdata pin %1% %2%)'
|
||||
data_manager_system_buttons: '[System:](gray) [[⏷ Daten-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss in eine Datei zu speichern.\n&8Daten-Dumps können unter ~/plugins/HuskSync/dumps/ gefunden werden. run_command=/husksync:userdata dump %1% %2% file) [[☂ Web-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss auf den mc-logs Service hochzuladen.\n&8Du erhältst dann eine URL, die die Daten enthält. run_command=/husksync:userdata dump %1% %2% web)'
|
||||
data_manager_advancements_preview_remaining: '&7und %1% weitere…'
|
||||
data_list_title: '[%1%''s user data snapshots:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||
data_list_title: '[Nutzerdaten-Schnappschüsse von %1%:](#00fb9a) [(%2%-%3% von](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||
data_list_item: '[%1%](gray show_text=&7Daten-Schnappschuss %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7Angeheftet:\n&8Angeheftete Schnappschüsse werden nicht automatisch rotiert. run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Versions-Zeitstempel:&7\n&8Zeitpunkt der Speicherung der Daten run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Versions-UUID:&7\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Speicherungsgrund:\n&8Der Grund für das Speichern der Daten run_command=/userdata view %6% %4%)'
|
||||
data_deleted: '[❌ Nutzerdaten-Schnappschuss erfolgreich gelöscht](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||
data_deleted: '[❌ Nutzerdaten-Schnappschuss erfolgreich gelöscht](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||
data_restored: '[⏪ Erfgreich wiederhergestellt](#00fb9a) [Aktuelle Nutzerdaten des Schnappschusses von %1%](#00fb9a show_text=&7Spieler-UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versions-UUID:\n&8%4%)'
|
||||
data_pinned: '[※ Nutzerdaten-Schnappschuss erfolgreich angepinnt](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
||||
data_unpinned: '[※ Nutzerdaten-Schnappschuss erfolgreich losgelöst](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
||||
data_dumped: '[☂ Successfully dumped the user data snapshot %1% for %2% to:](#00fb9a) &7%3%'
|
||||
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||
list_previous_page_button: '[◀](white show_text=&7View previous page run_command=%2% %1%) '
|
||||
list_next_page_button: ' [▶](white show_text=&7View next page run_command=%2% %1%)'
|
||||
data_dumped: '[☂ Nutzerdaten-Schnappschuss %1% für %2% erfolgreich gedumpt nach:](#00fb9a) &7%3%'
|
||||
list_footer: '\n%1%[Seite](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||
list_previous_page_button: '[◀](white show_text=&7Siehe vorherige Seite run_command=%2% %1%) '
|
||||
list_next_page_button: ' [▶](white show_text=&7Siehe nächste Seite run_command=%2% %1%)'
|
||||
list_page_jumpers: '(%1%)'
|
||||
list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%)'
|
||||
list_page_jumper_button: '[%1%](show_text=&7Springe zu Seite %1% run_command=%2% %1%)'
|
||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||
list_page_jumper_separator: ' '
|
||||
list_page_jumper_group_separator: '…'
|
||||
list_page_jumper_group_separator: '…'
|
||||
|
||||
@@ -22,20 +22,20 @@ data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text
|
||||
data_manager_advancements_statistics: '[⭐ Progressi: %1%](color=#ffc43b-#f5c962 show_text=&7Progressi compiuti in:\n&8%2%) [⌛ Tempo di gioco: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Tempo di gioco\n&8⚠ Basato sulle statistiche di gioco)\n'
|
||||
data_manager_item_buttons: '[View:](gray) [[🪣 Inventario…]](color=#a17b5f-#f5b98c show_text=&7Clicca per visualizzare run_command=/inventory %1% %2%) [[⌀ Ender Chest…]](#b649c4-#d254ff show_text=&7Clicca per visualizzare run_command=/enderchest %1% %2%)'
|
||||
data_manager_management_buttons: '[Gestisci:](gray) [[❌ Cancella…]](#ff3300 show_text=&7Fare clic per eliminare questa istantanea.\n&8Questo non influisce sui dati attuali dell''utente.\n&#ff3300&⚠ Questo non può essere annullato! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Ripristina…]](#00fb9a show_text=&7Clicca per ripristinare i dati dell''utente.\n&8I dati dell''utente saranno ripristinati a quest''istantanea.\n&#ff3300&⚠ I dati di %1% saranno sovrascritti! suggest_command=/husksync:userdata restore %1% %2%) [[※ fissa/sblocca...]](#d8ff2b show_text=&7Clicca per fissare o sbloccare quest''istantanea\n&8Le istantanee fissate non saranno cancellate automaticamente run_command=/userdata pin %1% %2%)'
|
||||
data_manager_system_buttons: '[System:](gray) [[⏷ File Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to a file.\n&8Data dumps can be found in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to the mc-logs service\n&8You will be provided with a URL containing the data. run_command=/husksync:userdata dump %1% %2% web)'
|
||||
data_manager_system_buttons: '[Sistema:](gray) [[⏷ Dump del File…]](dark_gray show_text=&7Clicca per ottenere il dump dei dati del giocatore.\n&8I dati salvati sono posizioanti nella cartella ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Dump su Web…]](dark_gray show_text=&7Clicca per ottenere il dump del file su mc-logs\n&8 Ti verrà consegnato l''url per visionare il dump. run_command=/husksync:userdata dump %1% %2% web)'
|
||||
data_manager_advancements_preview_remaining: '&7e %1% altro…'
|
||||
data_list_title: '[%1%''s user data snapshots:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||
data_list_title: '[Lista delle istantanee di %1%:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||
data_list_item: '[%1%](gray show_text=&7Istantanea dei dati %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7Fissato:\n&8Le istantanee fissate non saranno cancellate automaticamente. run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7Timestamp della versione:&7\n&8Quando i dati sono stati salvati run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Versione di UUID:&7\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7Causa del salvataggio:\n&8Cosa ha causato il salvataggio dei dati run_command=/userdata view %6% %4%)'
|
||||
data_deleted: '[❌ Istantanea eliminata con successo](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||
data_restored: '[⏪ Ripristato con successo](#00fb9a) [Dati dall''istantanea di](#00fb9a)[%1%](#00fb9a show_text=&7Player UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versione di UUID:\n&8%4%)'
|
||||
data_pinned: '[※ Instantanea fissata](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||
data_unpinned: '[※ L''istantanea dei dati utente è stata sbloccata con successo](#00fb9a) [%1%](#00fb9a show_text=&7Versione di UUID:\n&8%2%) [per](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||
data_dumped: '[☂ Successfully dumped the user data snapshot %1% for %2% to:](#00fb9a) &7%3%'
|
||||
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||
list_previous_page_button: '[◀](white show_text=&7View previous page run_command=%2% %1%) '
|
||||
list_next_page_button: ' [▶](white show_text=&7View next page run_command=%2% %1%)'
|
||||
data_dumped: '[☂ Hai ottenuto il dump dell''istantanea %1% di %2% nel formato:](#00fb9a) &7%3%'
|
||||
list_footer: '\n%1%[Pagina](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||
list_previous_page_button: '[◀](white show_text=&7Visualizza pagina precedente run_command=%2% %1%) '
|
||||
list_next_page_button: ' [▶](white show_text=&7Visualizza pagina successiva run_command=%2% %1%)'
|
||||
list_page_jumpers: '(%1%)'
|
||||
list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%)'
|
||||
list_page_jumper_button: '[%1%](show_text=&7Vai alla pagina %1% run_command=%2% %1%)'
|
||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||
list_page_jumper_separator: ' '
|
||||
list_page_jumper_group_separator: '…'
|
||||
list_page_jumper_group_separator: '…'
|
||||
|
||||
@@ -20,22 +20,22 @@ data_manager_pinned: '[※ 被標記的快照](#d8ff2b show_text=&7標記:\n&8
|
||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7保存原因:\n&8保存此快照的原因)\n'
|
||||
data_manager_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7血量) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7飽食度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7經驗等級) [🏹 %5%](dark_aqua show_text=&7遊戲模式)'
|
||||
data_manager_advancements_statistics: '[⭐ 成就: %1%](color=#ffc43b-#f5c962 show_text=&7已獲得的成就:\n&8%2%) [⌛ 遊戲時間: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7遊戲內的遊玩時間\n&8⚠ 根據遊戲內統計)\n'
|
||||
data_manager_item_buttons: '[View:](gray) [[🪣 背包…]](color=#a17b5f-#f5b98c show_text=&7點擊查看 run_command=/inventory %1% %2%) [[⌀ 終界箱…]](#b649c4-#d254ff show_text=&7點擊查看 run_command=/enderchest %1% %2%)'
|
||||
data_manager_item_buttons: '[查看:](gray) [[🪣 背包…]](color=#a17b5f-#f5b98c show_text=&7點擊查看 run_command=/inventory %1% %2%) [[⌀ 終界箱…]](#b649c4-#d254ff show_text=&7點擊查看 run_command=/enderchest %1% %2%)'
|
||||
data_manager_management_buttons: '[管理:](gray) [[❌ 刪除…]](#ff3300 show_text=&7點擊刪除這個快照\n&8這不會影像目前玩家的資料\n&#ff3300&⚠ 此操作不能取消! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ 恢復…]](#00fb9a show_text=&7點擊將玩家資料覆蓋為此快照\n&8這將導致玩家的資料會被此快照覆蓋\n&#ff3300&⚠ %1% 當前的資料將被覆蓋! suggest_command=/husksync:userdata restore %1% %2%) [[※ 標記…]](#d8ff2b show_text=&7點擊切換標記狀態\n&8被標記的快照將不會自動輪換更新 run_command=/userdata pin %1% %2%)'
|
||||
data_manager_system_buttons: '[System:](gray) [[⏷ File Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to a file.\n&8Data dumps can be found in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to the mc-logs service\n&8You will be provided with a URL containing the data. run_command=/husksync:userdata dump %1% %2% web)'
|
||||
data_manager_system_buttons: '[系統:](gray) [[⏷ 本地轉存…]](dark_gray show_text=&7點擊將此玩家資料快照轉存到本地文件中\n&8轉存的資料可以在以下路徑找到 ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ 雲端轉存…]](dark_gray show_text=&7點擊將此玩家資料快照轉存到 mc-logs 服務\n&8您將獲得一個包含資料的 URL. run_command=/husksync:userdata dump %1% %2% web)'
|
||||
data_manager_advancements_preview_remaining: '&7還有 %1% …'
|
||||
data_list_title: '[%1%''s user data snapshots:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||
data_list_title: '[%1% 的玩家資料快照:](#00fb9a) [(%2%-%3% 共](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||
data_list_item: '[%1%](gray show_text=&7快照名稱: %3% run_command=/userdata view %6% %4%) [%7%](#d8ff2b show_text=&7標記:\n&8被標記的快照不會自動輪換更新 run_command=/userdata view %6% %4%) [%2%](color=#ffc43b-#f5c962 show_text=&7時間戳:&7\n&8資料保存時間 run_command=/userdata view %6% %4%) [⚡ %3%](color=#62a9f5-#7ab8fa show_text=&7Version UUID:&7\n&8%4% run_command=/userdata view %6% %4%) [⚑ %5%](#23a825-#36f539 show_text=&7保存原因:\n&8保存此快照的原因 run_command=/userdata view %6% %4%)'
|
||||
data_deleted: '[❌ 成功刪除:](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
||||
data_restored: '[⏪ 成功將玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&8%2%)[的資料恢復為 快照:](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\n&8%4%)'
|
||||
data_pinned: '[※ 成功標記](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
||||
data_unpinned: '[※ 成功解除](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [的標記](#00fb9a)'
|
||||
data_dumped: '[☂ Successfully dumped the user data snapshot %1% for %2% to:](#00fb9a) &7%3%'
|
||||
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||
list_previous_page_button: '[◀](white show_text=&7View previous page run_command=%2% %1%) '
|
||||
list_next_page_button: ' [▶](white show_text=&7View next page run_command=%2% %1%)'
|
||||
list_page_jumpers: '(%1%)'
|
||||
list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%)'
|
||||
data_dumped: '[☂ 成功將 %2% 資料快照 %1% 儲存至:](#00fb9a) &7%3%'
|
||||
list_footer: \n%1%[頁面](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%
|
||||
list_previous_page_button: '[◀](white show_text=&7查看上一頁 run_command=%2% %1%) '
|
||||
list_next_page_button: ' [▶](white show_text=&7查看下一頁 run_command=%2% %1%)'
|
||||
list_page_jumpers: (%1%)
|
||||
list_page_jumper_button: '[%1%](show_text=&7跳至第 %1% 頁 run_command=%2% %1%)'
|
||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||
list_page_jumper_separator: ' '
|
||||
list_page_jumper_group_separator: '…'
|
||||
list_page_jumper_group_separator: …
|
||||
|
||||
134
common/src/test/java/net/william278/husksync/DummyHuskSync.java
Normal file
134
common/src/test/java/net/william278/husksync/DummyHuskSync.java
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync;
|
||||
|
||||
import net.william278.desertwell.util.Version;
|
||||
import net.william278.husksync.config.Locales;
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.data.DataAdapter;
|
||||
import net.william278.husksync.database.Database;
|
||||
import net.william278.husksync.event.EventCannon;
|
||||
import net.william278.husksync.migrator.Migrator;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import net.william278.husksync.redis.RedisManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class DummyHuskSync implements HuskSync {
|
||||
@Override
|
||||
@NotNull
|
||||
public Set<OnlineUser> getOnlineUsers() {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Database getDatabase() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public RedisManager getRedisManager() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public DataAdapter getDataAdapter() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public EventCannon getEventCannon() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<Migrator> getAvailableMigrators() {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Settings getSettings() {
|
||||
return new Settings();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Locales getLocales() {
|
||||
return new Locales();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getResource(@NotNull String name) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Version getPluginVersion() {
|
||||
return Version.fromString("1.0.0");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return new File(".");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Version getMinecraftVersion() {
|
||||
return Version.fromString("1.16.2");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> reload() {
|
||||
return CompletableFuture.supplyAsync(() -> true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<UUID> getLockedPlayers() {
|
||||
return Set.of();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,25 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.data;
|
||||
|
||||
import net.william278.husksync.config.Settings;
|
||||
import net.william278.husksync.logger.DummyLogger;
|
||||
import net.william278.husksync.DummyHuskSync;
|
||||
import net.william278.husksync.player.DummyPlayer;
|
||||
import net.william278.husksync.player.OnlineUser;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
@@ -19,7 +37,7 @@ public class DataAdaptionTests {
|
||||
@Test
|
||||
public void testJsonDataAdapter() {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
dummyUser.getUserData(new DummyLogger(), new Settings()).join().ifPresent(dummyUserData -> {
|
||||
dummyUser.getUserData(new DummyHuskSync()).join().ifPresent(dummyUserData -> {
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||
@@ -37,7 +55,7 @@ public class DataAdaptionTests {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
final String expectedJson = "{\"status\":{\"health\":20.0,\"max_health\":20.0,\"health_scale\":0.0,\"hunger\":20,\"saturation\":5.0,\"saturation_exhaustion\":5.0,\"selected_item_slot\":1,\"total_experience\":100,\"experience_level\":1,\"experience_progress\":1.0,\"game_mode\":\"SURVIVAL\",\"is_flying\":false},\"inventory\":{\"serialized_items\":\"\"},\"ender_chest\":{\"serialized_items\":\"\"},\"potion_effects\":{\"serialized_potion_effects\":\"\"},\"advancements\":[],\"statistics\":{\"untyped_statistics\":{},\"block_statistics\":{},\"item_statistics\":{},\"entity_statistics\":{}},\"minecraft_version\":\"1.19\",\"format_version\":3}";
|
||||
AtomicReference<String> json = new AtomicReference<>();
|
||||
dummyUser.getUserData(new DummyLogger(), new Settings()).join().ifPresent(dummyUserData -> {
|
||||
dummyUser.getUserData(new DummyHuskSync()).join().ifPresent(dummyUserData -> {
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
json.set(new String(data, StandardCharsets.UTF_8));
|
||||
@@ -48,7 +66,7 @@ public class DataAdaptionTests {
|
||||
@Test
|
||||
public void testCompressedDataAdapter() {
|
||||
final OnlineUser dummyUser = DummyPlayer.create();
|
||||
dummyUser.getUserData(new DummyLogger(), new Settings()).join().ifPresent(dummyUserData -> {
|
||||
dummyUser.getUserData(new DummyHuskSync()).join().ifPresent(dummyUserData -> {
|
||||
final DataAdapter dataAdapter = new CompressedDataAdapter();
|
||||
final byte[] data = dataAdapter.toBytes(dummyUserData);
|
||||
final UserData deserializedUserData = dataAdapter.fromBytes(data);
|
||||
@@ -63,13 +81,13 @@ public class DataAdaptionTests {
|
||||
|
||||
private String getTestSerializedPersistentDataContainer() {
|
||||
final HashMap<String, PersistentDataTag<?>> persistentDataTest = new HashMap<>();
|
||||
persistentDataTest.put("husksync:byte_test", new PersistentDataTag<>(BukkitPersistentDataTagType.BYTE, 0x01));
|
||||
persistentDataTest.put("husksync:double_test", new PersistentDataTag<>(BukkitPersistentDataTagType.DOUBLE, 2d));
|
||||
persistentDataTest.put("husksync:string_test", new PersistentDataTag<>(BukkitPersistentDataTagType.STRING, "test"));
|
||||
persistentDataTest.put("husksync:int_test", new PersistentDataTag<>(BukkitPersistentDataTagType.INTEGER, 3));
|
||||
persistentDataTest.put("husksync:long_test", new PersistentDataTag<>(BukkitPersistentDataTagType.LONG, 4L));
|
||||
persistentDataTest.put("husksync:float_test", new PersistentDataTag<>(BukkitPersistentDataTagType.FLOAT, 5f));
|
||||
persistentDataTest.put("husksync:short_test", new PersistentDataTag<>(BukkitPersistentDataTagType.SHORT, 6));
|
||||
persistentDataTest.put("husksync:byte_test", new PersistentDataTag<>(PersistentDataTagType.BYTE, 0x01));
|
||||
persistentDataTest.put("husksync:double_test", new PersistentDataTag<>(PersistentDataTagType.DOUBLE, 2d));
|
||||
persistentDataTest.put("husksync:string_test", new PersistentDataTag<>(PersistentDataTagType.STRING, "test"));
|
||||
persistentDataTest.put("husksync:int_test", new PersistentDataTag<>(PersistentDataTagType.INTEGER, 3));
|
||||
persistentDataTest.put("husksync:long_test", new PersistentDataTag<>(PersistentDataTagType.LONG, 4L));
|
||||
persistentDataTest.put("husksync:float_test", new PersistentDataTag<>(PersistentDataTagType.FLOAT, 5f));
|
||||
persistentDataTest.put("husksync:short_test", new PersistentDataTag<>(PersistentDataTagType.SHORT, 6));
|
||||
final PersistentDataContainerData persistentDataContainerData = new PersistentDataContainerData(persistentDataTest);
|
||||
|
||||
final DataAdapter dataAdapter = new JsonDataAdapter();
|
||||
|
||||
@@ -1,3 +1,22 @@
|
||||
/*
|
||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||
*
|
||||
* Copyright (c) William278 <will27528@gmail.com>
|
||||
* Copyright (c) contributors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package net.william278.husksync.hook;
|
||||
|
||||
import com.djrapitops.plan.extension.extractor.ExtensionExtractor;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user