mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-21 15:49:20 +00:00
Compare commits
141 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
480796fbee | ||
|
|
9b186ec97a | ||
|
|
d828631dea | ||
|
|
5b8de7967b | ||
|
|
4fddbc2b32 | ||
|
|
43cd367ca3 | ||
|
|
19ca504bab | ||
|
|
394b8ff1d1 | ||
|
|
4577da3336 | ||
|
|
c3b339b3dd | ||
|
|
2b91154ca2 | ||
|
|
2351be31e3 | ||
|
|
624543b93d | ||
|
|
c13f4b2a05 | ||
|
|
00b8d335d8 | ||
|
|
89d8b79ae3 | ||
|
|
acd97a1cb0 | ||
|
|
8c0f7a295f | ||
|
|
7536bfaaf5 | ||
|
|
cbf5d9c24e | ||
|
|
b9e474d946 | ||
|
|
3d232f97fb | ||
|
|
6d649d0889 | ||
|
|
0754837820 | ||
|
|
8f44dbb296 | ||
|
|
049cd8ecca | ||
|
|
2ed7705903 | ||
|
|
6bc6749e38 | ||
|
|
97a02b7a05 | ||
|
|
abc41a0aca | ||
|
|
31a14b2de7 | ||
|
|
59a0002c16 | ||
|
|
61020e04d9 | ||
|
|
ff1ace8342 | ||
|
|
847790c514 |
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]'
|
||||||
10
HEADER
Normal file
10
HEADER
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
|
||||||
|
Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
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.
|
||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright © William278 2022. All rights reserved
|
Copyright © William278 2023. All rights reserved
|
||||||
|
|
||||||
LICENSE
|
LICENSE
|
||||||
This source code is provided as reference to licensed individuals that have purchased the HuskSync
|
This source code is provided as reference to licensed individuals that have purchased the HuskSync
|
||||||
|
|||||||
88
README.md
88
README.md
@@ -1,33 +1,60 @@
|
|||||||
# [](https://github.com/WiIIiam278/HuskSync)
|
<!--suppress ALL -->
|
||||||

|
<p align="center">
|
||||||
[](https://discord.gg/tVYhJfyDWG)
|
<img src="images/banner.png" alt="HuskSync" />
|
||||||
|
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci.yml">
|
||||||
[Documentation, Guides & API](https://william278.net/docs/husksync/Home) · [Resource Page](https://www.spigotmc.org/resources/husksync.97144/) · [Bug Reports](https://github.com/WiIIiam278/HuskSync/issues)
|
<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.
|
**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
|
## Features
|
||||||
- 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.
|
**⭐ Seamless synchronisation** — Utilises optimised Redis caching when players change server to sync player data super quickly for a seamless experience.
|
||||||
- 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.
|
|
||||||
|
|
||||||
## Requirements
|
**⭐ 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!
|
||||||
* A MySQL Database (v8.0+).
|
|
||||||
* A Redis Database (v5.0+)
|
**⭐ Backup, restore & rotate** — Something gone wrong? Restore players back to a previous data state. Rotate and manage data snapshots in-game!
|
||||||
* Any number of proxied Spigot servers (Minecraft v1.16.5+)
|
|
||||||
|
**⭐ 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
|
## 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.
|
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 synchronistaion will begin.
|
4. Start every server again and synchronization will begin.
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
To build HuskSync, simply run the following in the root of the repository:
|
To build HuskSync, simply run the following in the root of the repository:
|
||||||
```
|
|
||||||
|
```bash
|
||||||
./gradlew clean build
|
./gradlew clean build
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -39,23 +66,18 @@ HuskSync is a premium resource. This source code is provided as reference only f
|
|||||||
## Contributing
|
## 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.
|
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.
|
||||||
|
|
||||||
## Translation
|
## 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.
|
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)
|
- [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/locales/en-gb.yml)
|
- [English Locales](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/languages/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.
|
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
- [Documentation, Guides & API](https://william278.net/docs/husksync/Home)
|
- [Docs](https://william278.net/docs/husksync/) — Read the plugin documentation!
|
||||||
- [Resource Page](https://www.spigotmc.org/resources/husksync.97144/)
|
- [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.406))
|
||||||
- [Bug Reports](https://github.com/WiIIiam278/HuskSync/issues)
|
- [Issues](https://github.com/WiIIiam278/HuskSync/issues) — File a bug report or feature request
|
||||||
- [Discord Support](https://discord.gg/tVYhJfyDWG) (Proof of purchase required)
|
- [Discord](https://discord.gg/tVYhJfyDWG) — Get help, ask questions (Proof of 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. All rights reserved.
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
dependencies {
|
|
||||||
implementation project(path: ':bukkit')
|
|
||||||
compileOnly project(path: ':common')
|
|
||||||
|
|
||||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
|
||||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
|
||||||
}
|
|
||||||
|
|
||||||
shadowJar {
|
|
||||||
dependencies {
|
|
||||||
exclude(dependency('com.mojang:brigadier'))
|
|
||||||
}
|
|
||||||
|
|
||||||
relocate 'org.apache', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'com.google', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'redis.clients', 'net.william278.husksync.libraries'
|
|
||||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
|
||||||
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
|
||||||
|
|
||||||
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
|
||||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
|
||||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
|
||||||
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
|
||||||
}
|
|
||||||
|
|
||||||
java {
|
|
||||||
withSourcesJar()
|
|
||||||
withJavadocJar()
|
|
||||||
}
|
|
||||||
74
build.gradle
74
build.gradle
@@ -1,24 +1,30 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
id 'org.ajoberstar.grgit' version '5.0.0'
|
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||||
id 'java'
|
id 'org.ajoberstar.grgit' version '5.2.0'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
|
id 'java'
|
||||||
}
|
}
|
||||||
|
|
||||||
group 'net.william278'
|
group 'net.william278'
|
||||||
version "$ext.plugin_version+${versionMetadata()}"
|
version "$ext.plugin_version${versionMetadata()}"
|
||||||
|
description "$ext.plugin_description"
|
||||||
|
defaultTasks 'licenseFormat', 'build'
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
set 'version', version.toString()
|
set 'version', version.toString()
|
||||||
|
set 'description', description.toString()
|
||||||
set 'jedis_version', jedis_version.toString()
|
set 'jedis_version', jedis_version.toString()
|
||||||
set 'mysql_driver_version', mysql_driver_version.toString()
|
set 'mysql_driver_version', mysql_driver_version.toString()
|
||||||
set 'snappy_version', snappy_version.toString()
|
set 'snappy_version', snappy_version.toString()
|
||||||
|
set 'commons_text_version', commons_text_version.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
import org.apache.tools.ant.filters.ReplaceTokens
|
import org.apache.tools.ant.filters.ReplaceTokens
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
|
apply plugin: 'org.cadixdev.licenser'
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
|
|
||||||
compileJava.options.encoding = 'UTF-8'
|
compileJava.options.encoding = 'UTF-8'
|
||||||
@@ -33,19 +39,27 @@ allprojects {
|
|||||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||||
maven { url 'https://repo.minebench.de/' }
|
maven { url 'https://repo.minebench.de/' }
|
||||||
maven { url 'https://repo.alessiodp.com/releases/' }
|
maven { url 'https://repo.alessiodp.com/releases/' }
|
||||||
|
maven { url 'https://repo.mattstudios.me/artifactory/public/' }
|
||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
maven { url 'https://libraries.minecraft.net/' }
|
maven { url 'https://libraries.minecraft.net/' }
|
||||||
|
maven { url 'https://william278.net/releases/' }
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.0'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.3'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.0'
|
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
license {
|
||||||
|
header = rootProject.file('HEADER')
|
||||||
|
include '**/*.java'
|
||||||
|
newLine = true
|
||||||
|
}
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
||||||
tokens: rootProject.ext.properties
|
tokens: rootProject.ext.properties
|
||||||
@@ -56,14 +70,18 @@ subprojects {
|
|||||||
version rootProject.version
|
version rootProject.version
|
||||||
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
|
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
|
||||||
|
|
||||||
if (['bukkit', 'api', 'plugin'].contains(project.name)) {
|
jar {
|
||||||
|
from '../LICENSE'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['bukkit', 'plugin'].contains(project.name)) {
|
||||||
shadowJar {
|
shadowJar {
|
||||||
destinationDirectory.set(file("$rootDir/target"))
|
destinationDirectory.set(file("$rootDir/target"))
|
||||||
archiveClassifier.set('')
|
archiveClassifier.set('')
|
||||||
}
|
}
|
||||||
|
|
||||||
// API publishing
|
// API publishing
|
||||||
if ('api'.contains(project.name)) {
|
if ('bukkit'.contains(project.name)) {
|
||||||
java {
|
java {
|
||||||
withSourcesJar()
|
withSourcesJar()
|
||||||
withJavadocJar()
|
withJavadocJar()
|
||||||
@@ -77,6 +95,35 @@ subprojects {
|
|||||||
shadowJar.dependsOn(sourcesJar, javadocJar)
|
shadowJar.dependsOn(sourcesJar, javadocJar)
|
||||||
|
|
||||||
publishing {
|
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 {
|
publications {
|
||||||
mavenJava(MavenPublication) {
|
mavenJava(MavenPublication) {
|
||||||
groupId = 'net.william278'
|
groupId = 'net.william278'
|
||||||
@@ -99,8 +146,15 @@ logger.lifecycle("Building HuskSync ${version} by William278")
|
|||||||
|
|
||||||
@SuppressWarnings('GrMethodMayBeStatic')
|
@SuppressWarnings('GrMethodMayBeStatic')
|
||||||
def versionMetadata() {
|
def versionMetadata() {
|
||||||
|
// 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 ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, get the last commit hash and if it's a clean head
|
||||||
if (grgit == null) {
|
if (grgit == null) {
|
||||||
return System.getenv("GITHUB_RUN_NUMBER") ? 'build.' + System.getenv("GITHUB_RUN_NUMBER") : 'unknown'
|
return '-' + System.getenv("GITHUB_RUN_NUMBER") ? 'build.' + System.getenv("GITHUB_RUN_NUMBER") : 'unknown'
|
||||||
}
|
}
|
||||||
return 'rev.' + grgit.head().abbreviatedId + (grgit.status().clean ? '' : '-indev')
|
return '-' + grgit.head().abbreviatedId + (grgit.status().clean ? '' : '-indev')
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,23 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':common')
|
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:mpdbdataconverter:1.0.1'
|
||||||
implementation 'net.william278:hsldataconverter:1.0'
|
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 'me.lucko:commodore:2.2'
|
||||||
|
implementation 'net.kyori:adventure-platform-bukkit:4.3.0'
|
||||||
|
implementation 'dev.triumphteam:triumph-gui:3.1.5'
|
||||||
|
|
||||||
compileOnly 'commons-io:commons-io:2.11.0'
|
|
||||||
compileOnly 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
|
||||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.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 'com.zaxxer:HikariCP:5.0.1'
|
||||||
|
compileOnly 'redis.clients:jedis:' + jedis_version
|
||||||
testImplementation 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
|
compileOnly 'net.william278:Annotaml:2.0.1'
|
||||||
|
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
@@ -22,14 +28,23 @@ shadowJar {
|
|||||||
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
||||||
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
||||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'net.kyori', 'net.william278.husksync.libraries'
|
||||||
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
||||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
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 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
||||||
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
||||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||||
|
relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui'
|
||||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||||
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
||||||
|
relocate 'net.william278.annotaml', 'net.william278.husksync.libraries.annotaml'
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync;
|
package net.william278.husksync;
|
||||||
|
|
||||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||||
import dev.dejvokep.boostedyaml.dvs.versioning.BasicVersioning;
|
import net.william278.annotaml.Annotaml;
|
||||||
import dev.dejvokep.boostedyaml.settings.dumper.DumperSettings;
|
import net.william278.desertwell.util.Version;
|
||||||
import dev.dejvokep.boostedyaml.settings.general.GeneralSettings;
|
|
||||||
import dev.dejvokep.boostedyaml.settings.loader.LoaderSettings;
|
|
||||||
import dev.dejvokep.boostedyaml.settings.updater.UpdaterSettings;
|
|
||||||
import net.william278.husksync.command.BukkitCommand;
|
import net.william278.husksync.command.BukkitCommand;
|
||||||
import net.william278.husksync.command.BukkitCommandType;
|
import net.william278.husksync.command.BukkitCommandType;
|
||||||
import net.william278.husksync.command.Permission;
|
import net.william278.husksync.command.Permission;
|
||||||
@@ -16,7 +26,6 @@ import net.william278.husksync.data.DataAdapter;
|
|||||||
import net.william278.husksync.data.JsonDataAdapter;
|
import net.william278.husksync.data.JsonDataAdapter;
|
||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
import net.william278.husksync.database.MySqlDatabase;
|
import net.william278.husksync.database.MySqlDatabase;
|
||||||
import net.william278.husksync.editor.DataEditor;
|
|
||||||
import net.william278.husksync.event.BukkitEventCannon;
|
import net.william278.husksync.event.BukkitEventCannon;
|
||||||
import net.william278.husksync.event.EventCannon;
|
import net.william278.husksync.event.EventCannon;
|
||||||
import net.william278.husksync.hook.PlanHook;
|
import net.william278.husksync.hook.PlanHook;
|
||||||
@@ -28,7 +37,6 @@ import net.william278.husksync.migrator.MpdbMigrator;
|
|||||||
import net.william278.husksync.player.BukkitPlayer;
|
import net.william278.husksync.player.BukkitPlayer;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.redis.RedisManager;
|
import net.william278.husksync.redis.RedisManager;
|
||||||
import net.william278.husksync.util.*;
|
|
||||||
import org.bstats.bukkit.Metrics;
|
import org.bstats.bukkit.Metrics;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.PluginCommand;
|
import org.bukkit.command.PluginCommand;
|
||||||
@@ -40,6 +48,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
@@ -54,15 +63,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
private static final int METRICS_ID = 13140;
|
private static final int METRICS_ID = 13140;
|
||||||
private Database database;
|
private Database database;
|
||||||
private RedisManager redisManager;
|
private RedisManager redisManager;
|
||||||
private Logger logger;
|
|
||||||
private ResourceReader resourceReader;
|
|
||||||
private EventListener eventListener;
|
private EventListener eventListener;
|
||||||
private DataAdapter dataAdapter;
|
private DataAdapter dataAdapter;
|
||||||
private DataEditor dataEditor;
|
|
||||||
private EventCannon eventCannon;
|
private EventCannon eventCannon;
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
private Locales locales;
|
private Locales locales;
|
||||||
private List<Migrator> availableMigrators;
|
private List<Migrator> availableMigrators;
|
||||||
|
|
||||||
|
private BukkitAudiences audiences;
|
||||||
private static BukkitHuskSync instance;
|
private static BukkitHuskSync instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -84,22 +92,20 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
// Initialize HuskSync
|
// Initialize HuskSync
|
||||||
final AtomicBoolean initialized = new AtomicBoolean(true);
|
final AtomicBoolean initialized = new AtomicBoolean(true);
|
||||||
try {
|
try {
|
||||||
// Set the logging adapter and resource reader
|
// Create adventure audience
|
||||||
this.logger = new BukkitLogger(this.getLogger());
|
this.audiences = BukkitAudiences.create(this);
|
||||||
this.resourceReader = new BukkitResourceReader(this);
|
|
||||||
|
|
||||||
// Load settings and locales
|
// 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());
|
initialized.set(reload().join());
|
||||||
if (initialized.get()) {
|
if (initialized.get()) {
|
||||||
logger.showDebugLogs(settings.getBooleanValue(Settings.ConfigOption.DEBUG_LOGGING));
|
log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully loaded plugin configuration settings & locales");
|
|
||||||
} else {
|
} else {
|
||||||
throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales");
|
throw new HuskSyncInitializationException("Failed to load plugin configuration settings and/or locales");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare data adapter
|
// Prepare data adapter
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_COMPRESS_DATA)) {
|
if (settings.doCompressData()) {
|
||||||
dataAdapter = new CompressedDataAdapter();
|
dataAdapter = new CompressedDataAdapter();
|
||||||
} else {
|
} else {
|
||||||
dataAdapter = new JsonDataAdapter();
|
dataAdapter = new JsonDataAdapter();
|
||||||
@@ -108,9 +114,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
// Prepare event cannon
|
// Prepare event cannon
|
||||||
eventCannon = new BukkitEventCannon();
|
eventCannon = new BukkitEventCannon();
|
||||||
|
|
||||||
// Prepare data editor
|
|
||||||
dataEditor = new DataEditor(locales);
|
|
||||||
|
|
||||||
// Prepare migrators
|
// Prepare migrators
|
||||||
availableMigrators = new ArrayList<>();
|
availableMigrators = new ArrayList<>();
|
||||||
availableMigrators.add(new LegacyMigrator(this));
|
availableMigrators.add(new LegacyMigrator(this));
|
||||||
@@ -120,11 +123,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Prepare database connection
|
// Prepare database connection
|
||||||
this.database = new MySqlDatabase(settings, resourceReader, logger, dataAdapter, eventCannon);
|
this.database = new MySqlDatabase(this);
|
||||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the database...");
|
log(Level.INFO, "Attempting to establish connection to the database...");
|
||||||
initialized.set(this.database.initialize());
|
initialized.set(this.database.initialize());
|
||||||
if (initialized.get()) {
|
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 {
|
} else {
|
||||||
throw new HuskSyncInitializationException("Failed to establish a connection to the database. " +
|
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");
|
||||||
@@ -132,22 +135,22 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
|
|
||||||
// Prepare redis connection
|
// Prepare redis connection
|
||||||
this.redisManager = new RedisManager(this);
|
this.redisManager = new RedisManager(this);
|
||||||
getLoggingAdapter().log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
log(Level.INFO, "Attempting to establish connection to the Redis server...");
|
||||||
initialized.set(this.redisManager.initialize().join());
|
initialized.set(this.redisManager.initialize());
|
||||||
if (initialized.get()) {
|
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 {
|
} else {
|
||||||
throw new HuskSyncInitializationException("Failed to establish a connection to the Redis server. " +
|
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
|
// Register events
|
||||||
getLoggingAdapter().log(Level.INFO, "Registering events...");
|
log(Level.INFO, "Registering events...");
|
||||||
this.eventListener = new BukkitEventListener(this);
|
this.eventListener = new BukkitEventListener(this);
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully registered events listener");
|
log(Level.INFO, "Successfully registered events listener");
|
||||||
|
|
||||||
// Register permissions
|
// Register permissions
|
||||||
getLoggingAdapter().log(Level.INFO, "Registering permissions & commands...");
|
log(Level.INFO, "Registering permissions & commands...");
|
||||||
Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager()
|
Arrays.stream(Permission.values()).forEach(permission -> getServer().getPluginManager()
|
||||||
.addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) {
|
.addPermission(new org.bukkit.permissions.Permission(permission.node, switch (permission.defaultAccess) {
|
||||||
case EVERYONE -> PermissionDefault.TRUE;
|
case EVERYONE -> PermissionDefault.TRUE;
|
||||||
@@ -162,29 +165,32 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
new BukkitCommand(bukkitCommandType.commandBase, this).register(pluginCommand);
|
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
|
// Hook into plan
|
||||||
if (Bukkit.getPluginManager().getPlugin("Plan") != null) {
|
if (Bukkit.getPluginManager().getPlugin("Plan") != null) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Enabling Plan integration...");
|
log(Level.INFO, "Enabling Plan integration...");
|
||||||
new PlanHook(database, logger).hookIntoPlan();
|
new PlanHook(this).hookIntoPlan();
|
||||||
getLoggingAdapter().log(Level.INFO, "Plan integration enabled!");
|
log(Level.INFO, "Plan integration enabled!");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook into bStats metrics
|
// Hook into bStats metrics
|
||||||
try {
|
try {
|
||||||
new Metrics(this, METRICS_ID);
|
new Metrics(this, METRICS_ID);
|
||||||
} catch (final Exception e) {
|
} 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
|
// Check for updates
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.CHECK_FOR_UPDATES)) {
|
if (settings.doCheckForUpdates()) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Checking for updates...");
|
log(Level.INFO, "Checking for updates...");
|
||||||
CompletableFuture.runAsync(() -> new UpdateChecker(getPluginVersion(), getLoggingAdapter()).logToConsole());
|
getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
||||||
|
newestVersion.ifPresent(newVersion -> log(Level.WARNING,
|
||||||
|
"An update is available for HuskSync, v" + newVersion
|
||||||
|
+ " (Currently running v" + getPluginVersion() + ")")));
|
||||||
}
|
}
|
||||||
} catch (HuskSyncInitializationException exception) {
|
} catch (HuskSyncInitializationException exception) {
|
||||||
getLoggingAdapter().log(Level.SEVERE, """
|
log(Level.SEVERE, """
|
||||||
***************************************************
|
***************************************************
|
||||||
|
|
||||||
Failed to initialize HuskSync!
|
Failed to initialize HuskSync!
|
||||||
@@ -199,14 +205,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
.replaceAll("%error_message%", exception.getMessage()));
|
.replaceAll("%error_message%", exception.getMessage()));
|
||||||
initialized.set(false);
|
initialized.set(false);
|
||||||
} catch (Exception exception) {
|
} 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);
|
initialized.set(false);
|
||||||
} finally {
|
} finally {
|
||||||
// Validate initialization
|
// Validate initialization
|
||||||
if (initialized.get()) {
|
if (initialized.get()) {
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion());
|
log(Level.INFO, "Successfully enabled HuskSync v" + getPluginVersion());
|
||||||
} else {
|
} 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);
|
getServer().getPluginManager().disablePlugin(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -217,7 +223,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
if (this.eventListener != null) {
|
if (this.eventListener != null) {
|
||||||
this.eventListener.handlePluginDisable();
|
this.eventListener.handlePluginDisable();
|
||||||
}
|
}
|
||||||
getLoggingAdapter().log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -249,11 +255,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
return dataAdapter;
|
return dataAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull DataEditor getDataEditor() {
|
|
||||||
return dataEditor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull EventCannon getEventCannon() {
|
public @NotNull EventCannon getEventCannon() {
|
||||||
return eventCannon;
|
return eventCannon;
|
||||||
@@ -276,36 +277,57 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Logger getLoggingAdapter() {
|
public void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable) {
|
||||||
return logger;
|
if (throwable.length > 0) {
|
||||||
|
getLogger().log(level, message, throwable[0]);
|
||||||
|
} else {
|
||||||
|
getLogger().log(level, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public ResourceReader getResourceReader() {
|
public Version getPluginVersion() {
|
||||||
return resourceReader;
|
return Version.fromString(getDescription().getVersion(), "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Version getMinecraftVersion() {
|
||||||
|
return Version.fromString(Bukkit.getBukkitVersion());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the adventure Bukkit audiences
|
||||||
|
*
|
||||||
|
* @return The adventure Bukkit audiences
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public BukkitAudiences getAudiences() {
|
||||||
|
return audiences;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Version getPluginVersion() {
|
public Set<UUID> getLockedPlayers() {
|
||||||
return Version.pluginVersion(getDescription().getVersion());
|
return this.eventListener.getLockedPlayers();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull Version getMinecraftVersion() {
|
|
||||||
return Version.minecraftVersion(Bukkit.getBukkitVersion());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> reload() {
|
public CompletableFuture<Boolean> reload() {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
try {
|
try {
|
||||||
this.settings = Settings.load(YamlDocument.create(new File(getDataFolder(), "config.yml"), Objects.requireNonNull(resourceReader.getResource("config.yml")), GeneralSettings.builder().setUseDefaults(false).build(), LoaderSettings.builder().setAutoUpdate(true).build(), DumperSettings.builder().setEncoding(DumperSettings.Encoding.UNICODE).build(), UpdaterSettings.builder().setVersioning(new BasicVersioning("config_version")).build()));
|
// Load plugin settings
|
||||||
|
this.settings = Annotaml.create(new File(getDataFolder(), "config.yml"), new Settings()).get();
|
||||||
|
|
||||||
this.locales = Locales.load(YamlDocument.create(new File(getDataFolder(), "messages-" + settings.getStringValue(Settings.ConfigOption.LANGUAGE) + ".yml"), Objects.requireNonNull(resourceReader.getResource("locales/" + settings.getStringValue(Settings.ConfigOption.LANGUAGE) + ".yml"))));
|
// Load locales from language preset default
|
||||||
|
final Locales languagePresets = Annotaml.create(Locales.class,
|
||||||
|
Objects.requireNonNull(getResource("locales/" + settings.getLanguage() + ".yml"))).get();
|
||||||
|
this.locales = Annotaml.create(new File(getDataFolder(), "messages_" + settings.getLanguage() + ".yml"),
|
||||||
|
languagePresets).get();
|
||||||
return true;
|
return true;
|
||||||
} catch (IOException | NullPointerException e) {
|
} catch (IOException | NullPointerException | InvocationTargetException | IllegalAccessException |
|
||||||
getLoggingAdapter().log(Level.SEVERE, "Failed to load data from the config", e);
|
InstantiationException e) {
|
||||||
|
log(Level.SEVERE, "Failed to load data from the config", e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.api;
|
package net.william278.husksync.api;
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
@@ -66,7 +79,7 @@ public class HuskSyncAPI extends BaseHuskSyncAPI {
|
|||||||
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(userData ->
|
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(userData ->
|
||||||
userData.ifPresent(data -> serializeItemStackArray(inventoryContents)
|
userData.ifPresent(data -> serializeItemStackArray(inventoryContents)
|
||||||
.thenAccept(serializedInventory -> {
|
.thenAccept(serializedInventory -> {
|
||||||
data.getInventoryData().serializedItems = serializedInventory;
|
data.getInventory().orElse(ItemData.empty()).serializedItems = serializedInventory;
|
||||||
setUserData(user, data).join();
|
setUserData(user, data).join();
|
||||||
}))));
|
}))));
|
||||||
}
|
}
|
||||||
@@ -95,7 +108,7 @@ public class HuskSyncAPI extends BaseHuskSyncAPI {
|
|||||||
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(userData ->
|
return CompletableFuture.runAsync(() -> getUserData(user).thenAccept(userData ->
|
||||||
userData.ifPresent(data -> serializeItemStackArray(enderChestContents)
|
userData.ifPresent(data -> serializeItemStackArray(enderChestContents)
|
||||||
.thenAccept(serializedInventory -> {
|
.thenAccept(serializedInventory -> {
|
||||||
data.getEnderChestData().serializedItems = serializedInventory;
|
data.getEnderChest().orElse(ItemData.empty()).serializedItems = serializedInventory;
|
||||||
setUserData(user, data).join();
|
setUserData(user, data).join();
|
||||||
}))));
|
}))));
|
||||||
}
|
}
|
||||||
@@ -106,12 +119,14 @@ public class HuskSyncAPI extends BaseHuskSyncAPI {
|
|||||||
* @param user the {@link User} to get the {@link BukkitInventoryMap} for
|
* @param user the {@link User} to get the {@link BukkitInventoryMap} for
|
||||||
* @return future returning the {@link BukkitInventoryMap} for the given {@link User} if they exist,
|
* @return future returning the {@link BukkitInventoryMap} for the given {@link User} if they exist,
|
||||||
* otherwise an empty {@link Optional}
|
* otherwise an empty {@link Optional}
|
||||||
|
* @apiNote If the {@link UserData} does not contain an inventory (i.e. inventory synchronisation is disabled), the
|
||||||
|
* returned {@link BukkitInventoryMap} will be equivalent an empty inventory.
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Optional<BukkitInventoryMap>> getPlayerInventory(@NotNull User user) {
|
public CompletableFuture<Optional<BukkitInventoryMap>> getPlayerInventory(@NotNull User user) {
|
||||||
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
||||||
.map(userData -> deserializeInventory(userData
|
.map(userData -> deserializeInventory(userData.getInventory()
|
||||||
.getInventoryData().serializedItems).join()));
|
.orElse(ItemData.empty()).serializedItems).join()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,12 +135,14 @@ public class HuskSyncAPI extends BaseHuskSyncAPI {
|
|||||||
* @param user the {@link User} to get the Ender Chest contents of
|
* @param user the {@link User} to get the Ender Chest contents of
|
||||||
* @return future returning the {@link ItemStack} array of Ender Chest items for the user if they exist,
|
* @return future returning the {@link ItemStack} array of Ender Chest items for the user if they exist,
|
||||||
* otherwise an empty {@link Optional}
|
* otherwise an empty {@link Optional}
|
||||||
|
* @apiNote If the {@link UserData} does not contain an Ender Chest (i.e. Ender Chest synchronisation is disabled),
|
||||||
|
* the returned {@link BukkitInventoryMap} will be equivalent to an empty inventory.
|
||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Optional<ItemStack[]>> getPlayerEnderChest(@NotNull User user) {
|
public CompletableFuture<Optional<ItemStack[]>> getPlayerEnderChest(@NotNull User user) {
|
||||||
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
return CompletableFuture.supplyAsync(() -> getUserData(user).join()
|
||||||
.map(userData -> deserializeItemStackArray(userData
|
.map(userData -> deserializeItemStackArray(userData.getEnderChest()
|
||||||
.getEnderChestData().serializedItems).join()));
|
.orElse(ItemData.empty()).serializedItems).join()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import me.lucko.commodore.CommodoreProvider;
|
import me.lucko.commodore.CommodoreProvider;
|
||||||
@@ -18,13 +31,12 @@ public class BrigadierUtil {
|
|||||||
protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand,
|
protected static void registerCommodore(@NotNull BukkitHuskSync plugin, @NotNull PluginCommand pluginCommand,
|
||||||
@NotNull CommandBase command) {
|
@NotNull CommandBase command) {
|
||||||
// Register command descriptions via commodore (brigadier wrapper)
|
// Register command descriptions via commodore (brigadier wrapper)
|
||||||
try (InputStream pluginFile = plugin.getResourceReader()
|
try (InputStream pluginFile = plugin.getResource("commodore/" + command.command + ".commodore")) {
|
||||||
.getResource("commodore/" + command.command + ".commodore")) {
|
|
||||||
CommodoreProvider.getCommodore(plugin).register(pluginCommand,
|
CommodoreProvider.getCommodore(plugin).register(pluginCommand,
|
||||||
CommodoreFileReader.INSTANCE.parse(pluginFile),
|
CommodoreFileReader.INSTANCE.parse(pluginFile),
|
||||||
player -> player.hasPermission(command.permission));
|
player -> player.hasPermission(command.permission));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
plugin.log(Level.SEVERE,
|
||||||
"Failed to load " + command.command + ".commodore command definitions", e);
|
"Failed to load " + command.command + ".commodore command definitions", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import me.lucko.commodore.CommodoreProvider;
|
import me.lucko.commodore.CommodoreProvider;
|
||||||
@@ -55,8 +68,9 @@ public class BukkitCommand implements CommandExecutor, TabExecutor {
|
|||||||
if (this.command instanceof ConsoleExecutable consoleExecutable) {
|
if (this.command instanceof ConsoleExecutable consoleExecutable) {
|
||||||
consoleExecutable.onConsoleExecute(args);
|
consoleExecutable.onConsoleExecute(args);
|
||||||
} else {
|
} else {
|
||||||
plugin.getLocales().getLocale("error_in_game_command_only").
|
plugin.getLocales().getLocale("error_in_game_command_only")
|
||||||
ifPresent(locale -> sender.spigot().sendMessage(locale.toComponent()));
|
.ifPresent(locale -> plugin.getAudiences().sender(sender)
|
||||||
|
.sendMessage(locale.toComponent()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
@@ -11,6 +24,8 @@ import java.util.Optional;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class BukkitInventoryMap {
|
public class BukkitInventoryMap {
|
||||||
|
|
||||||
|
public static final int INVENTORY_SLOT_COUNT = 41;
|
||||||
|
|
||||||
private ItemStack[] contents;
|
private ItemStack[] contents;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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.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 = mapMeta.getMapView();
|
||||||
|
if (mapView == null || !mapView.isLocked() || mapView.isVirtual()) {
|
||||||
|
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,67 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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,5 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
import org.bukkit.util.io.BukkitObjectInputStream;
|
import org.bukkit.util.io.BukkitObjectInputStream;
|
||||||
@@ -13,6 +28,7 @@ import java.io.ByteArrayOutputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class BukkitSerializer {
|
public class BukkitSerializer {
|
||||||
|
|
||||||
@@ -38,13 +54,18 @@ public class BukkitSerializer {
|
|||||||
bukkitOutputStream.writeInt(inventoryContents.length);
|
bukkitOutputStream.writeInt(inventoryContents.length);
|
||||||
|
|
||||||
// Write each serialize each ItemStack to the output stream
|
// Write each serialize each ItemStack to the output stream
|
||||||
|
final boolean persistLockedMaps = BukkitHuskSync.getInstance().getSettings().getSynchronizationFeature(Settings.SynchronizationFeature.LOCKED_MAPS);
|
||||||
for (ItemStack inventoryItem : inventoryContents) {
|
for (ItemStack inventoryItem : inventoryContents) {
|
||||||
|
if (persistLockedMaps) {
|
||||||
|
BukkitMapHandler.persistMapData(inventoryItem);
|
||||||
|
}
|
||||||
bukkitOutputStream.writeObject(serializeItemStack(inventoryItem));
|
bukkitOutputStream.writeObject(serializeItemStack(inventoryItem));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize item stack data", e);
|
||||||
throw new DataSerializationException("Failed to serialize item stack data", e);
|
throw new DataSerializationException("Failed to serialize item stack data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -86,8 +107,13 @@ public class BukkitSerializer {
|
|||||||
|
|
||||||
// Set the ItemStacks in the array from deserialized ItemStack data
|
// Set the ItemStacks in the array from deserialized ItemStack data
|
||||||
int slotIndex = 0;
|
int slotIndex = 0;
|
||||||
|
final boolean persistLockedMaps = BukkitHuskSync.getInstance().getSettings().getSynchronizationFeature(Settings.SynchronizationFeature.LOCKED_MAPS);
|
||||||
for (ItemStack ignored : inventoryContents) {
|
for (ItemStack ignored : inventoryContents) {
|
||||||
inventoryContents[slotIndex] = deserializeItemStack(bukkitInputStream.readObject());
|
final ItemStack deserialized = deserializeItemStack(bukkitInputStream.readObject());
|
||||||
|
if (persistLockedMaps) {
|
||||||
|
BukkitMapHandler.setMapRenderer(deserialized);
|
||||||
|
}
|
||||||
|
inventoryContents[slotIndex] = deserialized;
|
||||||
slotIndex++;
|
slotIndex++;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,6 +121,7 @@ public class BukkitSerializer {
|
|||||||
return inventoryContents;
|
return inventoryContents;
|
||||||
}
|
}
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize item stack data", e);
|
||||||
throw new DataSerializationException("Failed to deserialize item stack data", e);
|
throw new DataSerializationException("Failed to deserialize item stack data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -151,6 +178,7 @@ public class BukkitSerializer {
|
|||||||
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
// Return encoded data, using the encoder from SnakeYaml to get a ByteArray conversion
|
||||||
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
return Base64Coder.encodeLines(byteOutputStream.toByteArray());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to serialize potion effect data", e);
|
||||||
throw new DataSerializationException("Failed to serialize potion effect data", e);
|
throw new DataSerializationException("Failed to serialize potion effect data", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -186,6 +214,7 @@ public class BukkitSerializer {
|
|||||||
return potionEffects;
|
return potionEffects;
|
||||||
}
|
}
|
||||||
} catch (IOException | ClassNotFoundException e) {
|
} catch (IOException | ClassNotFoundException e) {
|
||||||
|
BukkitHuskSync.getInstance().log(Level.SEVERE, "Failed to deserialize potion effect data", e);
|
||||||
throw new DataSerializationException("Failed to deserialize potion effects", e);
|
throw new DataSerializationException("Failed to deserialize potion effects", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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,14 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.listener;
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
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.BukkitSerializer;
|
||||||
import net.william278.husksync.data.DataSerializationException;
|
|
||||||
import net.william278.husksync.data.ItemData;
|
import net.william278.husksync.data.ItemData;
|
||||||
import net.william278.husksync.editor.ItemEditorMenuType;
|
|
||||||
import net.william278.husksync.player.BukkitPlayer;
|
import net.william278.husksync.player.BukkitPlayer;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.entity.Projectile;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.EventPriority;
|
import org.bukkit.event.EventPriority;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@@ -17,122 +31,155 @@ import org.bukkit.event.block.BlockPlaceEvent;
|
|||||||
import org.bukkit.event.entity.EntityDamageEvent;
|
import org.bukkit.event.entity.EntityDamageEvent;
|
||||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||||
import org.bukkit.event.entity.PlayerDeathEvent;
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.event.entity.ProjectileLaunchEvent;
|
||||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||||
import org.bukkit.event.inventory.InventoryCloseEvent;
|
|
||||||
import org.bukkit.event.inventory.InventoryOpenEvent;
|
import org.bukkit.event.inventory.InventoryOpenEvent;
|
||||||
|
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
|
||||||
import org.bukkit.event.player.PlayerDropItemEvent;
|
import org.bukkit.event.player.PlayerDropItemEvent;
|
||||||
|
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
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.event.world.WorldSaveEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.stream.Collectors;
|
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) {
|
public BukkitEventListener(@NotNull BukkitHuskSync huskSync) {
|
||||||
super(huskSync);
|
super(huskSync);
|
||||||
|
this.blacklistedCommands = huskSync.getSettings().getBlacklistedCommandsWhileLocked();
|
||||||
Bukkit.getServer().getPluginManager().registerEvents(this, huskSync);
|
Bukkit.getServer().getPluginManager().registerEvents(this, huskSync);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@Override
|
||||||
public void onPlayerJoin(@NotNull PlayerJoinEvent event) {
|
public boolean handleEvent(@NotNull Settings.EventType type, @NotNull Settings.EventPriority priority) {
|
||||||
super.handlePlayerJoin(BukkitPlayer.adapt(event.getPlayer()));
|
return plugin.getSettings().getEventPriority(type).equals(priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.LOWEST)
|
@Override
|
||||||
public void onPlayerQuit(@NotNull PlayerQuitEvent event) {
|
public void handlePlayerQuit(@NotNull BukkitPlayer bukkitPlayer) {
|
||||||
super.handlePlayerQuit(BukkitPlayer.adapt(event.getPlayer()));
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlePlayerJoin(@NotNull BukkitPlayer bukkitPlayer) {
|
||||||
|
super.handlePlayerJoin(bukkitPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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.uuid)) {
|
||||||
|
event.getDrops().clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle saving player data snapshots on death
|
||||||
|
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;
|
||||||
|
if (event.getDrops().size() > maxInventorySize) {
|
||||||
|
event.getDrops().subList(maxInventorySize, event.getDrops().size()).clear();
|
||||||
|
}
|
||||||
|
BukkitSerializer.serializeItemStackArray(event.getDrops().toArray(new ItemStack[0]))
|
||||||
|
.thenAccept(serializedDrops -> super.saveOnPlayerDeath(user, new ItemData(serializedDrops)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
@EventHandler(ignoreCancelled = true)
|
||||||
public void onWorldSave(@NotNull WorldSaveEvent event) {
|
public void onWorldSave(@NotNull WorldSaveEvent event) {
|
||||||
CompletableFuture.runAsync(() -> super.handleAsyncWorldSave(event.getWorld().getPlayers().stream()
|
// Handle saving player data snapshots when the world saves
|
||||||
.map(BukkitPlayer::adapt).collect(Collectors.toList())));
|
if (!plugin.getSettings().doSaveOnWorldSave()) return;
|
||||||
|
|
||||||
|
CompletableFuture.runAsync(() -> super.saveOnWorldSave(event.getWorld().getPlayers()
|
||||||
|
.stream().map(BukkitPlayer::adapt)
|
||||||
|
.collect(Collectors.toList())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
|
||||||
public void onInventoryClose(@NotNull InventoryCloseEvent event) {
|
|
||||||
CompletableFuture.runAsync(() -> {
|
|
||||||
if (event.getPlayer() instanceof Player player) {
|
|
||||||
final OnlineUser user = BukkitPlayer.adapt(player);
|
|
||||||
plugin.getDataEditor().getEditingInventoryData(user).ifPresent(menu -> {
|
|
||||||
try {
|
|
||||||
BukkitSerializer.serializeItemStackArray(Arrays.copyOf(event.getInventory().getContents(),
|
|
||||||
menu.itemEditorMenuType == ItemEditorMenuType.INVENTORY_VIEWER
|
|
||||||
? player.getInventory().getSize()
|
|
||||||
: player.getEnderChest().getSize())).thenAccept(
|
|
||||||
serializedInventory -> super.handleMenuClose(user, new ItemData(serializedInventory)));
|
|
||||||
} catch (DataSerializationException e) {
|
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
|
||||||
"Failed to serialize inventory data during menu close", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Events to cancel if the player has not been set yet
|
* 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)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onDropItem(@NotNull PlayerDropItemEvent event) {
|
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)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
|
public void onPickupItem(@NotNull EntityPickupItemEvent event) {
|
||||||
if (event.getEntity() instanceof Player player) {
|
if (event.getEntity() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPlayerInteract(@NotNull PlayerInteractEvent event) {
|
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)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onBlockPlace(@NotNull BlockPlaceEvent event) {
|
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)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onBlockBreak(@NotNull BlockBreakEvent event) {
|
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 onInventoryClick(@NotNull InventoryClickEvent event) {
|
|
||||||
if (event.getWhoClicked() instanceof Player player) {
|
|
||||||
event.setCancelled(cancelInventoryClick(BukkitPlayer.adapt(player)));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
public void onInventoryOpen(@NotNull InventoryOpenEvent event) {
|
||||||
if (event.getPlayer() instanceof Player player) {
|
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)
|
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||||
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
public void onPlayerTakeDamage(@NotNull EntityDamageEvent event) {
|
||||||
if (event.getEntity() instanceof Player player) {
|
if (event.getEntity() instanceof Player player) {
|
||||||
event.setCancelled(cancelPlayerEvent(BukkitPlayer.adapt(player)));
|
event.setCancelled(cancelPlayerEvent(player.getUniqueId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler(ignoreCancelled = true)
|
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
|
||||||
public void onPlayerDeath(PlayerDeathEvent event) {
|
public void onPermissionCommand(@NotNull PlayerCommandPreprocessEvent event) {
|
||||||
if (cancelPlayerEvent(BukkitPlayer.adapt(event.getEntity()))) {
|
String[] commandArgs = event.getMessage().substring(1).split(" ");
|
||||||
event.getDrops().clear();
|
String commandLabel = commandArgs[0].toLowerCase(Locale.ENGLISH);
|
||||||
|
|
||||||
|
if (blacklistedCommands.contains(commandLabel)) {
|
||||||
|
event.setCancelled(cancelPlayerEvent(event.getPlayer().getUniqueId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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,51 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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,10 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.migrator;
|
package net.william278.husksync.migrator;
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
import me.william278.husksync.bukkit.data.DataSerializer;
|
import me.william278.husksync.bukkit.data.DataSerializer;
|
||||||
import net.william278.hslmigrator.HSLConverter;
|
import net.william278.hslmigrator.HSLConverter;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
@@ -38,11 +50,11 @@ public class LegacyMigrator extends Migrator {
|
|||||||
public LegacyMigrator(@NotNull HuskSync plugin) {
|
public LegacyMigrator(@NotNull HuskSync plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
this.hslConverter = HSLConverter.getInstance();
|
this.hslConverter = HSLConverter.getInstance();
|
||||||
this.sourceHost = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_HOST);
|
this.sourceHost = plugin.getSettings().getMySqlHost();
|
||||||
this.sourcePort = plugin.getSettings().getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
|
this.sourcePort = plugin.getSettings().getMySqlPort();
|
||||||
this.sourceUsername = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_USERNAME);
|
this.sourceUsername = plugin.getSettings().getMySqlUsername();
|
||||||
this.sourcePassword = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_PASSWORD);
|
this.sourcePassword = plugin.getSettings().getMySqlPassword();
|
||||||
this.sourceDatabase = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_NAME);
|
this.sourceDatabase = plugin.getSettings().getMySqlDatabase();
|
||||||
this.sourcePlayersTable = "husksync_players";
|
this.sourcePlayersTable = "husksync_players";
|
||||||
this.sourceDataTable = "husksync_data";
|
this.sourceDataTable = "husksync_data";
|
||||||
this.minecraftVersion = plugin.getMinecraftVersion().toString();
|
this.minecraftVersion = plugin.getMinecraftVersion().toString();
|
||||||
@@ -50,26 +62,26 @@ public class LegacyMigrator extends Migrator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> start() {
|
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();
|
final long startTime = System.currentTimeMillis();
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Wipe the existing database, preparing it for data import
|
// 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.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
|
// Create jdbc driver connection url
|
||||||
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
||||||
|
|
||||||
// Create a new data source for the mpdb converter
|
// Create a new data source for the mpdb converter
|
||||||
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
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.setJdbcUrl(jdbcUrl);
|
||||||
connectionPool.setUsername(sourceUsername);
|
connectionPool.setUsername(sourceUsername);
|
||||||
connectionPool.setPassword(sourcePassword);
|
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<>();
|
final List<LegacyData> dataToMigrate = new ArrayList<>();
|
||||||
try (final Connection connection = connectionPool.getConnection()) {
|
try (final Connection connection = connectionPool.getConnection()) {
|
||||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||||
@@ -107,33 +119,33 @@ public class LegacyMigrator extends Migrator {
|
|||||||
));
|
));
|
||||||
playersMigrated++;
|
playersMigrated++;
|
||||||
if (playersMigrated % 50 == 0) {
|
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.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, "Converting HuskSync 1.x data to the new user data format (this might take a while)...");
|
||||||
|
|
||||||
final AtomicInteger playersConverted = new AtomicInteger();
|
final AtomicInteger playersConverted = new AtomicInteger();
|
||||||
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> {
|
dataToMigrate.forEach(data -> data.toUserData(hslConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION)
|
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.LEGACY_MIGRATION)
|
||||||
.exceptionally(exception -> {
|
.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;
|
return null;
|
||||||
})).join();
|
})).join();
|
||||||
|
|
||||||
playersConverted.getAndIncrement();
|
playersConverted.getAndIncrement();
|
||||||
if (playersConverted.get() % 50 == 0) {
|
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());
|
}).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;
|
return true;
|
||||||
} catch (Exception e) {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -142,7 +154,7 @@ public class LegacyMigrator extends Migrator {
|
|||||||
@Override
|
@Override
|
||||||
public void handleConfigurationCommand(@NotNull String[] args) {
|
public void handleConfigurationCommand(@NotNull String[] args) {
|
||||||
if (args.length == 2) {
|
if (args.length == 2) {
|
||||||
if (switch (args[0].toLowerCase()) {
|
if (switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||||
case "host" -> {
|
case "host" -> {
|
||||||
this.sourceHost = args[1];
|
this.sourceHost = args[1];
|
||||||
yield true;
|
yield true;
|
||||||
@@ -177,15 +189,15 @@ public class LegacyMigrator extends Migrator {
|
|||||||
}
|
}
|
||||||
default -> false;
|
default -> false;
|
||||||
}) {
|
}) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} 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?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,13 +299,16 @@ public class LegacyMigrator extends Migrator {
|
|||||||
legacyLocationData == null ? 90f : legacyLocationData.yaw(),
|
legacyLocationData == null ? 90f : legacyLocationData.yaw(),
|
||||||
legacyLocationData == null ? 180f : legacyLocationData.pitch());
|
legacyLocationData == null ? 180f : legacyLocationData.pitch());
|
||||||
|
|
||||||
return new UserData(new StatusData(health, maxHealth, healthScale, hunger, saturation,
|
return UserData.builder(minecraftVersion)
|
||||||
saturationExhaustion, selectedSlot, totalExp, expLevel, expProgress, gameMode, isFlying),
|
.setStatus(new StatusData(health, maxHealth, healthScale, hunger, saturation,
|
||||||
new ItemData(serializedInventory), new ItemData(serializedEnderChest),
|
saturationExhaustion, selectedSlot, totalExp, expLevel, expProgress, gameMode, isFlying))
|
||||||
new PotionEffectData(serializedPotionEffects), convertedAdvancements,
|
.setInventory(new ItemData(serializedInventory))
|
||||||
convertedStatisticData, convertedLocationData,
|
.setEnderChest(new ItemData(serializedEnderChest))
|
||||||
new PersistentDataContainerData(new HashMap<>()),
|
.setPotionEffects(new PotionEffectData(serializedPotionEffects))
|
||||||
minecraftVersion);
|
.setAdvancements(convertedAdvancements)
|
||||||
|
.setStatistics(convertedStatisticData)
|
||||||
|
.setLocation(convertedLocationData)
|
||||||
|
.build();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.migrator;
|
package net.william278.husksync.migrator;
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import net.william278.mpdbconverter.MPDBConverter;
|
import net.william278.mpdbconverter.MPDBConverter;
|
||||||
@@ -16,7 +28,10 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.util.*;
|
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.CompletableFuture;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -41,11 +56,11 @@ public class MpdbMigrator extends Migrator {
|
|||||||
public MpdbMigrator(@NotNull BukkitHuskSync plugin, @NotNull Plugin mySqlPlayerDataBridge) {
|
public MpdbMigrator(@NotNull BukkitHuskSync plugin, @NotNull Plugin mySqlPlayerDataBridge) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
this.mpdbConverter = MPDBConverter.getInstance(mySqlPlayerDataBridge);
|
this.mpdbConverter = MPDBConverter.getInstance(mySqlPlayerDataBridge);
|
||||||
this.sourceHost = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_HOST);
|
this.sourceHost = plugin.getSettings().getMySqlHost();
|
||||||
this.sourcePort = plugin.getSettings().getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
|
this.sourcePort = plugin.getSettings().getMySqlPort();
|
||||||
this.sourceUsername = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_USERNAME);
|
this.sourceUsername = plugin.getSettings().getMySqlUsername();
|
||||||
this.sourcePassword = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_PASSWORD);
|
this.sourcePassword = plugin.getSettings().getMySqlPassword();
|
||||||
this.sourceDatabase = plugin.getSettings().getStringValue(Settings.ConfigOption.DATABASE_NAME);
|
this.sourceDatabase = plugin.getSettings().getMySqlDatabase();
|
||||||
this.sourceInventoryTable = "mpdb_inventory";
|
this.sourceInventoryTable = "mpdb_inventory";
|
||||||
this.sourceEnderChestTable = "mpdb_enderchest";
|
this.sourceEnderChestTable = "mpdb_enderchest";
|
||||||
this.sourceExperienceTable = "mpdb_experience";
|
this.sourceExperienceTable = "mpdb_experience";
|
||||||
@@ -55,26 +70,26 @@ public class MpdbMigrator extends Migrator {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Boolean> start() {
|
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();
|
final long startTime = System.currentTimeMillis();
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
// Wipe the existing database, preparing it for data import
|
// 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.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
|
// Create jdbc driver connection url
|
||||||
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
final String jdbcUrl = "jdbc:mysql://" + sourceHost + ":" + sourcePort + "/" + sourceDatabase;
|
||||||
|
|
||||||
// Create a new data source for the mpdb converter
|
// Create a new data source for the mpdb converter
|
||||||
try (final HikariDataSource connectionPool = new HikariDataSource()) {
|
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.setJdbcUrl(jdbcUrl);
|
||||||
connectionPool.setUsername(sourceUsername);
|
connectionPool.setUsername(sourceUsername);
|
||||||
connectionPool.setPassword(sourcePassword);
|
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<>();
|
final List<MpdbData> dataToMigrate = new ArrayList<>();
|
||||||
try (final Connection connection = connectionPool.getConnection()) {
|
try (final Connection connection = connectionPool.getConnection()) {
|
||||||
try (final PreparedStatement statement = connection.prepareStatement("""
|
try (final PreparedStatement statement = connection.prepareStatement("""
|
||||||
@@ -102,32 +117,32 @@ public class MpdbMigrator extends Migrator {
|
|||||||
));
|
));
|
||||||
playersMigrated++;
|
playersMigrated++;
|
||||||
if (playersMigrated % 25 == 0) {
|
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.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, "Converting raw MySQLPlayerDataBridge data to HuskSync user data (this might take a while)...");
|
||||||
|
|
||||||
final AtomicInteger playersConverted = new AtomicInteger();
|
final AtomicInteger playersConverted = new AtomicInteger();
|
||||||
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> {
|
dataToMigrate.forEach(data -> data.toUserData(mpdbConverter, minecraftVersion).thenAccept(convertedData -> {
|
||||||
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
plugin.getDatabase().ensureUser(data.user()).thenRun(() ->
|
||||||
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))
|
plugin.getDatabase().setUserData(data.user(), convertedData, DataSaveCause.MPDB_MIGRATION))
|
||||||
.exceptionally(exception -> {
|
.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;
|
return null;
|
||||||
}).join();
|
}).join();
|
||||||
playersConverted.getAndIncrement();
|
playersConverted.getAndIncrement();
|
||||||
if (playersConverted.get() % 50 == 0) {
|
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());
|
}).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;
|
return true;
|
||||||
} catch (Exception e) {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -136,7 +151,7 @@ public class MpdbMigrator extends Migrator {
|
|||||||
@Override
|
@Override
|
||||||
public void handleConfigurationCommand(@NotNull String[] args) {
|
public void handleConfigurationCommand(@NotNull String[] args) {
|
||||||
if (args.length == 2) {
|
if (args.length == 2) {
|
||||||
if (switch (args[0].toLowerCase()) {
|
if (switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||||
case "host" -> {
|
case "host" -> {
|
||||||
this.sourceHost = args[1];
|
this.sourceHost = args[1];
|
||||||
yield true;
|
yield true;
|
||||||
@@ -175,15 +190,15 @@ public class MpdbMigrator extends Migrator {
|
|||||||
}
|
}
|
||||||
default -> false;
|
default -> false;
|
||||||
}) {
|
}) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} 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?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,18 +295,14 @@ public class MpdbMigrator extends Migrator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create user data record
|
// Create user data record
|
||||||
return new UserData(new StatusData(20, 20, 0, 20, 10,
|
return UserData.builder(minecraftVersion)
|
||||||
|
.setStatus(new StatusData(20, 20, 0, 20, 10,
|
||||||
1, 0, totalExp, expLevel, expProgress, "SURVIVAL",
|
1, 0, totalExp, expLevel, expProgress, "SURVIVAL",
|
||||||
false),
|
false))
|
||||||
new ItemData(BukkitSerializer.serializeItemStackArray(inventory.getContents()).join()),
|
.setInventory(new ItemData(BukkitSerializer.serializeItemStackArray(inventory.getContents()).join()))
|
||||||
new ItemData(BukkitSerializer.serializeItemStackArray(converter
|
.setEnderChest(new ItemData(BukkitSerializer.serializeItemStackArray(converter
|
||||||
.getItemStackFromSerializedData(serializedEnderChest)).join()),
|
.getItemStackFromSerializedData(serializedEnderChest)).join()))
|
||||||
new PotionEffectData(""), new ArrayList<>(),
|
.build();
|
||||||
new StatisticsData(new HashMap<>(), new HashMap<>(), new HashMap<>(), new HashMap<>()),
|
|
||||||
new LocationData("world", UUID.randomUUID(), "NORMAL", 0, 0, 0,
|
|
||||||
0f, 0f),
|
|
||||||
new PersistentDataContainerData(new HashMap<>()),
|
|
||||||
minecraftVersion);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.player;
|
package net.william278.husksync.player;
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.md_5.bungee.api.ChatMessageType;
|
import dev.triumphteam.gui.builder.gui.StorageBuilder;
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import dev.triumphteam.gui.guis.Gui;
|
||||||
|
import dev.triumphteam.gui.guis.StorageGui;
|
||||||
|
import net.kyori.adventure.audience.Audience;
|
||||||
|
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.BukkitHuskSync;
|
||||||
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.editor.ItemEditorMenu;
|
|
||||||
import net.william278.husksync.util.Version;
|
|
||||||
import org.bukkit.*;
|
import org.bukkit.*;
|
||||||
import org.bukkit.advancement.Advancement;
|
import org.bukkit.advancement.Advancement;
|
||||||
import org.bukkit.advancement.AdvancementProgress;
|
import org.bukkit.advancement.AdvancementProgress;
|
||||||
@@ -15,8 +32,9 @@ import org.bukkit.entity.EntityType;
|
|||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.player.PlayerTeleportEvent;
|
import org.bukkit.event.player.PlayerTeleportEvent;
|
||||||
import org.bukkit.inventory.Inventory;
|
import org.bukkit.inventory.Inventory;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.PlayerInventory;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
import org.bukkit.persistence.PersistentDataType;
|
|
||||||
import org.bukkit.potion.PotionEffect;
|
import org.bukkit.potion.PotionEffect;
|
||||||
import org.bukkit.potion.PotionEffectType;
|
import org.bukkit.potion.PotionEffectType;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -32,27 +50,16 @@ import java.util.logging.Level;
|
|||||||
*/
|
*/
|
||||||
public class BukkitPlayer extends OnlineUser {
|
public class BukkitPlayer extends OnlineUser {
|
||||||
|
|
||||||
private static final PersistentDataType<?, ?>[] PRIMITIVE_PERSISTENT_DATA_TYPES = new PersistentDataType<?, ?>[]{
|
private final BukkitHuskSync plugin;
|
||||||
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 Player player;
|
private final Player player;
|
||||||
|
|
||||||
private BukkitPlayer(@NotNull Player player) {
|
private BukkitPlayer(@NotNull Player player) {
|
||||||
super(player.getUniqueId(), player.getName());
|
super(player.getUniqueId(), player.getName());
|
||||||
|
this.plugin = BukkitHuskSync.getInstance();
|
||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
public static BukkitPlayer adapt(@NotNull Player player) {
|
public static BukkitPlayer adapt(@NotNull Player player) {
|
||||||
return new BukkitPlayer(player);
|
return new BukkitPlayer(player);
|
||||||
}
|
}
|
||||||
@@ -81,55 +88,65 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
|
public CompletableFuture<Void> setStatus(@NotNull StatusData statusData, @NotNull Settings settings) {
|
||||||
@NotNull List<StatusDataFlag> statusDataFlags) {
|
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
// Set max health
|
||||||
.getBaseValue();
|
double currentMaxHealth = Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH)).getBaseValue();
|
||||||
if (statusDataFlags.contains(StatusDataFlag.SET_MAX_HEALTH)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.MAX_HEALTH)) {
|
||||||
if (statusData.maxHealth != 0d) {
|
if (statusData.maxHealth != 0d) {
|
||||||
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
Objects.requireNonNull(player.getAttribute(Attribute.GENERIC_MAX_HEALTH))
|
||||||
.setBaseValue(statusData.maxHealth);
|
.setBaseValue(statusData.maxHealth);
|
||||||
currentMaxHealth = statusData.maxHealth;
|
currentMaxHealth = statusData.maxHealth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (statusDataFlags.contains(StatusDataFlag.SET_HEALTH)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HEALTH)) {
|
||||||
|
// Set health
|
||||||
final double currentHealth = player.getHealth();
|
final double currentHealth = player.getHealth();
|
||||||
if (statusData.health != currentHealth) {
|
if (statusData.health != currentHealth) {
|
||||||
final double healthToSet = currentHealth > currentMaxHealth ? currentMaxHealth : statusData.health;
|
final double healthToSet = currentHealth > currentMaxHealth ? currentMaxHealth : statusData.health;
|
||||||
if (healthToSet < 1) {
|
final double maxHealth = currentMaxHealth;
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> player.setHealth(healthToSet));
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
} else {
|
try {
|
||||||
player.setHealth(healthToSet);
|
player.setHealth(Math.min(healthToSet, maxHealth));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set health of player " + player.getName() + " to " + healthToSet);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set health scale
|
||||||
|
try {
|
||||||
if (statusData.healthScale != 0d) {
|
if (statusData.healthScale != 0d) {
|
||||||
player.setHealthScale(statusData.healthScale);
|
player.setHealthScale(statusData.healthScale);
|
||||||
} else {
|
} else {
|
||||||
player.setHealthScale(statusData.maxHealth);
|
player.setHealthScale(statusData.maxHealth);
|
||||||
}
|
}
|
||||||
player.setHealthScaled(statusData.healthScale != 0D);
|
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);
|
||||||
}
|
}
|
||||||
if (statusDataFlags.contains(StatusDataFlag.SET_HUNGER)) {
|
}
|
||||||
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.HUNGER)) {
|
||||||
player.setFoodLevel(statusData.hunger);
|
player.setFoodLevel(statusData.hunger);
|
||||||
player.setSaturation(statusData.saturation);
|
player.setSaturation(statusData.saturation);
|
||||||
player.setExhaustion(statusData.saturationExhaustion);
|
player.setExhaustion(statusData.saturationExhaustion);
|
||||||
}
|
}
|
||||||
if (statusDataFlags.contains(StatusDataFlag.SET_SELECTED_ITEM_SLOT)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
||||||
player.getInventory().setHeldItemSlot(statusData.selectedItemSlot);
|
player.getInventory().setHeldItemSlot(statusData.selectedItemSlot);
|
||||||
}
|
}
|
||||||
if (statusDataFlags.contains(StatusDataFlag.SET_EXPERIENCE)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.EXPERIENCE)) {
|
||||||
player.setTotalExperience(statusData.totalExperience);
|
player.setTotalExperience(statusData.totalExperience);
|
||||||
player.setLevel(statusData.expLevel);
|
player.setLevel(statusData.expLevel);
|
||||||
player.setExp(statusData.expProgress);
|
player.setExp(statusData.expProgress);
|
||||||
}
|
}
|
||||||
if (statusDataFlags.contains(StatusDataFlag.SET_GAME_MODE)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.GAME_MODE)) {
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () ->
|
Bukkit.getScheduler().runTask(plugin, () ->
|
||||||
player.setGameMode(GameMode.valueOf(statusData.gameMode)));
|
player.setGameMode(GameMode.valueOf(statusData.gameMode)));
|
||||||
}
|
}
|
||||||
if (statusDataFlags.contains(StatusDataFlag.SET_FLYING)) {
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.LOCATION)) {
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
if (statusData.isFlying) {
|
if (statusData.isFlying) {
|
||||||
player.setAllowFlight(true);
|
player.setAllowFlight(true);
|
||||||
player.setFlying(true);
|
player.setFlying(true);
|
||||||
@@ -142,7 +159,11 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<ItemData> getInventory() {
|
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);
|
.thenApply(ItemData::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,8 +171,10 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
public CompletableFuture<Void> setInventory(@NotNull ItemData itemData) {
|
public CompletableFuture<Void> setInventory(@NotNull ItemData itemData) {
|
||||||
return BukkitSerializer.deserializeInventory(itemData.serializedItems).thenApplyAsync(contents -> {
|
return BukkitSerializer.deserializeInventory(itemData.serializedItems).thenApplyAsync(contents -> {
|
||||||
final CompletableFuture<Void> inventorySetFuture = new CompletableFuture<>();
|
final CompletableFuture<Void> inventorySetFuture = new CompletableFuture<>();
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
|
player.setItemOnCursor(null);
|
||||||
player.getInventory().setContents(contents.getContents());
|
player.getInventory().setContents(contents.getContents());
|
||||||
|
player.updateInventory();
|
||||||
inventorySetFuture.complete(null);
|
inventorySetFuture.complete(null);
|
||||||
});
|
});
|
||||||
return inventorySetFuture.join();
|
return inventorySetFuture.join();
|
||||||
@@ -160,7 +183,11 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<ItemData> getEnderChest() {
|
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);
|
.thenApply(ItemData::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +195,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
public CompletableFuture<Void> setEnderChest(@NotNull ItemData enderChestData) {
|
public CompletableFuture<Void> setEnderChest(@NotNull ItemData enderChestData) {
|
||||||
return BukkitSerializer.deserializeItemStackArray(enderChestData.serializedItems).thenApplyAsync(contents -> {
|
return BukkitSerializer.deserializeItemStackArray(enderChestData.serializedItems).thenApplyAsync(contents -> {
|
||||||
final CompletableFuture<Void> enderChestSetFuture = new CompletableFuture<>();
|
final CompletableFuture<Void> enderChestSetFuture = new CompletableFuture<>();
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
player.getEnderChest().setContents(contents);
|
player.getEnderChest().setContents(contents);
|
||||||
enderChestSetFuture.complete(null);
|
enderChestSetFuture.complete(null);
|
||||||
});
|
});
|
||||||
@@ -187,7 +214,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
return BukkitSerializer.deserializePotionEffectArray(potionEffectData.serializedPotionEffects)
|
return BukkitSerializer.deserializePotionEffectArray(potionEffectData.serializedPotionEffects)
|
||||||
.thenApplyAsync(effects -> {
|
.thenApplyAsync(effects -> {
|
||||||
final CompletableFuture<Void> potionEffectsSetFuture = new CompletableFuture<>();
|
final CompletableFuture<Void> potionEffectsSetFuture = new CompletableFuture<>();
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
for (PotionEffect effect : player.getActivePotionEffects()) {
|
for (PotionEffect effect : player.getActivePotionEffects()) {
|
||||||
player.removePotionEffect(effect.getType());
|
player.removePotionEffect(effect.getType());
|
||||||
}
|
}
|
||||||
@@ -225,7 +252,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setAdvancements(@NotNull List<AdvancementData> advancementData) {
|
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
|
// Temporarily disable advancement announcing if needed
|
||||||
boolean announceAdvancementUpdate = false;
|
boolean announceAdvancementUpdate = false;
|
||||||
@@ -257,20 +284,20 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
record.completedCriteria.keySet().stream()
|
record.completedCriteria.keySet().stream()
|
||||||
.filter(criterion -> !playerProgress.getAwardedCriteria().contains(criterion))
|
.filter(criterion -> !playerProgress.getAwardedCriteria().contains(criterion))
|
||||||
.forEach(criterion -> {
|
.forEach(criterion -> {
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
|
Bukkit.getScheduler().runTask(plugin,
|
||||||
() -> player.getAdvancementProgress(advancement).awardCriteria(criterion));
|
() -> player.getAdvancementProgress(advancement).awardCriteria(criterion));
|
||||||
correctExperience.set(true);
|
correctExperience.set(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Revoke all criteria that the player does have but should not
|
// Revoke all criteria that the player does have but should not
|
||||||
new ArrayList<>(playerProgress.getAwardedCriteria()).stream().filter(criterion -> !record.completedCriteria.containsKey(criterion))
|
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)));
|
() -> player.getAdvancementProgress(advancement).revokeCriteria(criterion)));
|
||||||
|
|
||||||
},
|
},
|
||||||
// Revoke the criteria as the player shouldn't have any
|
// Revoke the criteria as the player shouldn't have any
|
||||||
() -> new ArrayList<>(playerProgress.getAwardedCriteria()).forEach(criterion ->
|
() -> new ArrayList<>(playerProgress.getAwardedCriteria()).forEach(criterion ->
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(),
|
Bukkit.getScheduler().runTask(plugin,
|
||||||
() -> player.getAdvancementProgress(advancement).revokeCriteria(criterion))));
|
() -> player.getAdvancementProgress(advancement).revokeCriteria(criterion))));
|
||||||
|
|
||||||
// Update the player's experience in case the advancement changed that
|
// Update the player's experience in case the advancement changed that
|
||||||
@@ -282,7 +309,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Re-enable announcing advancements (back on main thread again)
|
// Re-enable announcing advancements (back on main thread again)
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
if (finalAnnounceAdvancementUpdate) {
|
if (finalAnnounceAdvancementUpdate) {
|
||||||
player.getWorld().setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, true);
|
player.getWorld().setGameRule(GameRule.ANNOUNCE_ADVANCEMENTS, true);
|
||||||
}
|
}
|
||||||
@@ -347,32 +374,52 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
public CompletableFuture<Void> setStatistics(@NotNull StatisticsData statisticsData) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
// Set untyped statistics
|
// Set generic statistics
|
||||||
for (String statistic : statisticsData.untypedStatistics.keySet()) {
|
for (String statistic : statisticsData.untypedStatistics.keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), statisticsData.untypedStatistics.get(statistic));
|
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
|
// Set block statistics
|
||||||
for (String statistic : statisticsData.blockStatistics.keySet()) {
|
for (String statistic : statisticsData.blockStatistics.keySet()) {
|
||||||
for (String blockMaterial : statisticsData.blockStatistics.get(statistic).keySet()) {
|
for (String blockMaterial : statisticsData.blockStatistics.get(statistic).keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(blockMaterial),
|
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(blockMaterial),
|
||||||
statisticsData.blockStatistics.get(statistic).get(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
|
// Set item statistics
|
||||||
for (String statistic : statisticsData.itemStatistics.keySet()) {
|
for (String statistic : statisticsData.itemStatistics.keySet()) {
|
||||||
for (String itemMaterial : statisticsData.itemStatistics.get(statistic).keySet()) {
|
for (String itemMaterial : statisticsData.itemStatistics.get(statistic).keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(itemMaterial),
|
player.setStatistic(Statistic.valueOf(statistic), Material.valueOf(itemMaterial),
|
||||||
statisticsData.itemStatistics.get(statistic).get(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
|
// Set entity statistics
|
||||||
for (String statistic : statisticsData.entityStatistics.keySet()) {
|
for (String statistic : statisticsData.entityStatistics.keySet()) {
|
||||||
for (String entityType : statisticsData.entityStatistics.get(statistic).keySet()) {
|
for (String entityType : statisticsData.entityStatistics.get(statistic).keySet()) {
|
||||||
|
try {
|
||||||
player.setStatistic(Statistic.valueOf(statistic), EntityType.valueOf(entityType),
|
player.setStatistic(Statistic.valueOf(statistic), EntityType.valueOf(entityType),
|
||||||
statisticsData.entityStatistics.get(statistic).get(entityType));
|
statisticsData.entityStatistics.get(statistic).get(entityType));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLogger().log(Level.WARNING,
|
||||||
|
"Failed to set " + entityType + " statistic " + statistic + " for " + username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -398,7 +445,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
.valueOf(locationData.worldEnvironment)).findFirst().ifPresent(bukkitWorld::set);
|
.valueOf(locationData.worldEnvironment)).findFirst().ifPresent(bukkitWorld::set);
|
||||||
}
|
}
|
||||||
if (bukkitWorld.get() != null) {
|
if (bukkitWorld.get() != null) {
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> {
|
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||||
player.teleport(new Location(bukkitWorld.get(),
|
player.teleport(new Location(bukkitWorld.get(),
|
||||||
locationData.x, locationData.y, locationData.z,
|
locationData.x, locationData.y, locationData.z,
|
||||||
locationData.yaw, locationData.pitch), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
locationData.yaw, locationData.pitch), PlayerTeleportEvent.TeleportCause.PLUGIN);
|
||||||
@@ -410,65 +457,24 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<PersistentDataContainerData> getPersistentDataContainer() {
|
public CompletableFuture<PersistentDataContainerData> getPersistentDataContainer() {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
final Map<String, PersistentDataTag<?>> persistentDataMap = new HashMap<>();
|
||||||
final PersistentDataContainer container = player.getPersistentDataContainer();
|
final PersistentDataContainer container = player.getPersistentDataContainer();
|
||||||
if (container.isEmpty()) {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
return new PersistentDataContainerData(new HashMap<>());
|
container.getKeys().forEach(key -> {
|
||||||
}
|
BukkitPersistentTypeMapping<?, ?> type = null;
|
||||||
final HashMap<String, PersistentDataTag<?>> persistentDataMap = new HashMap<>();
|
for (BukkitPersistentTypeMapping<?, ?> dataType : BukkitPersistentTypeMapping.PRIMITIVE_TYPE_MAPPINGS) {
|
||||||
for (final NamespacedKey key : container.getKeys()) {
|
if (container.has(key, dataType.bukkitType())) {
|
||||||
PersistentDataType<?, ?> type = null;
|
|
||||||
for (PersistentDataType<?, ?> dataType : PRIMITIVE_PERSISTENT_DATA_TYPES) {
|
|
||||||
if (container.has(key, dataType)) {
|
|
||||||
type = dataType;
|
type = dataType;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type != null) {
|
if (type != null) {
|
||||||
// This is absolutely disgusting code and needs to be swiftly put out of its misery with a refactor
|
persistentDataMap.put(key.toString(), type.getContainerValue(container, key));
|
||||||
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))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
return new PersistentDataContainerData(persistentDataMap);
|
return new PersistentDataContainerData(persistentDataMap);
|
||||||
}).exceptionally(throwable -> {
|
}).exceptionally(throwable -> {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
plugin.log(Level.WARNING,
|
||||||
"Could not read " + player.getName() + "'s persistent data map, skipping!");
|
"Could not read " + player.getName() + "'s persistent data map, skipping!");
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
return new PersistentDataContainerData(new HashMap<>());
|
return new PersistentDataContainerData(new HashMap<>());
|
||||||
@@ -476,71 +482,36 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData persistentDataContainerData) {
|
public CompletableFuture<Void> setPersistentDataContainer(@NotNull PersistentDataContainerData container) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
player.getPersistentDataContainer().getKeys().forEach(namespacedKey ->
|
player.getPersistentDataContainer().getKeys().forEach(namespacedKey ->
|
||||||
player.getPersistentDataContainer().remove(namespacedKey));
|
player.getPersistentDataContainer().remove(namespacedKey));
|
||||||
persistentDataContainerData.getTags().forEach(keyString -> {
|
container.getTags().forEach(keyString -> {
|
||||||
final NamespacedKey key = NamespacedKey.fromString(keyString);
|
final NamespacedKey key = NamespacedKey.fromString(keyString);
|
||||||
if (key != null) {
|
if (key != null) {
|
||||||
// Set a tag with the given key and value. This is crying out for a refactor.
|
container.getTagType(keyString)
|
||||||
persistentDataContainerData.getTagType(keyString).ifPresentOrElse(dataType -> {
|
.flatMap(BukkitPersistentTypeMapping::getMapping)
|
||||||
switch (dataType) {
|
.ifPresentOrElse(mapping -> mapping.setContainerValue(container, player, key),
|
||||||
case BYTE -> persistentDataContainerData.getTagValue(keyString, byte.class).ifPresent(
|
() -> plugin.log(Level.WARNING,
|
||||||
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 +
|
"Could not set " + player.getName() + "'s persistent data key " + keyString +
|
||||||
" as it has an invalid type. Skipping!"));
|
" as it has an invalid type. Skipping!"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).exceptionally(throwable -> {
|
}).exceptionally(throwable -> {
|
||||||
BukkitHuskSync.getInstance().getLoggingAdapter().log(Level.WARNING,
|
plugin.log(Level.WARNING,
|
||||||
"Could not write " + player.getName() + "'s persistent data map, skipping!");
|
"Could not write " + player.getName() + "'s persistent data map, skipping!");
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Audience getAudience() {
|
||||||
|
return plugin.getAudiences().player(player);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isOffline() {
|
public boolean isOffline() {
|
||||||
try {
|
try {
|
||||||
@@ -554,7 +525,7 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Version getMinecraftVersion() {
|
public Version getMinecraftVersion() {
|
||||||
return Version.minecraftVersion(Bukkit.getBukkitVersion());
|
return Version.fromString(Bukkit.getBukkitVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -563,28 +534,75 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void showMenu(@NotNull ItemEditorMenu menu) {
|
public CompletableFuture<Optional<ItemData>> showMenu(@NotNull ItemData itemData, boolean editable,
|
||||||
BukkitSerializer.deserializeItemStackArray(menu.itemData.serializedItems).thenAccept(inventoryContents -> {
|
int minimumRows, @NotNull MineDown title) {
|
||||||
final Inventory inventory = Bukkit.createInventory(player, menu.itemEditorMenuType.slotCount,
|
final CompletableFuture<Optional<ItemData>> updatedData = new CompletableFuture<>();
|
||||||
BaseComponent.toLegacyText(menu.menuTitle.toComponent()));
|
|
||||||
inventory.setContents(inventoryContents);
|
// Deserialize the item data to be shown and show it in a triumph GUI
|
||||||
Bukkit.getScheduler().runTask(BukkitHuskSync.getInstance(), () -> player.openInventory(inventory));
|
BukkitSerializer.deserializeItemStackArray(itemData.serializedItems).thenAccept(items -> {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)));
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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
|
@Override
|
||||||
public boolean isDead() {
|
public boolean isDead() {
|
||||||
return player.getHealth() < 1;
|
return player.getHealth() <= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void sendActionBar(@NotNull MineDown mineDown) {
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
player.spigot().sendMessage(ChatMessageType.ACTION_BAR, mineDown.replace().toComponent());
|
@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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendMessage(@NotNull MineDown mineDown) {
|
|
||||||
player.spigot().sendMessage(mineDown.replace().toComponent());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -606,4 +624,14 @@ public class BukkitPlayer extends OnlineUser {
|
|||||||
return maxHealth;
|
return maxHealth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isLocked() {
|
||||||
|
return plugin.getLockedPlayers().contains(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isNpc() {
|
||||||
|
return player.hasMetadata("NPC");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
package net.william278.husksync.util;
|
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
|
||||||
import net.md_5.bungee.api.chat.TextComponent;
|
|
||||||
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 log(@NotNull Level level, @NotNull MineDown mineDown) {
|
|
||||||
logger.log(level, TextComponent.toLegacyText(mineDown.toComponent()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,9 @@ userdata {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
list {
|
list {
|
||||||
name brigadier:string single_word;
|
name brigadier:string single_word {
|
||||||
|
page brigadier:integer;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
delete {
|
delete {
|
||||||
name brigadier:string single_word {
|
name brigadier:string single_word {
|
||||||
@@ -22,4 +24,12 @@ userdata {
|
|||||||
version brigadier:string single_word;
|
version brigadier:string single_word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dump {
|
||||||
|
name brigadier:string single_word {
|
||||||
|
version brigadier:string single_word {
|
||||||
|
web;
|
||||||
|
file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,29 @@
|
|||||||
name: HuskSync
|
name: 'HuskSync'
|
||||||
version: ${version}
|
version: '${version}'
|
||||||
main: net.william278.husksync.BukkitHuskSync
|
main: 'net.william278.husksync.BukkitHuskSync'
|
||||||
api-version: 1.16
|
api-version: 1.16
|
||||||
author: William278
|
author: 'William278'
|
||||||
description: 'A modern, cross-server player data synchronization system'
|
description: '${description}'
|
||||||
website: 'https://william278.net'
|
website: 'https://william278.net'
|
||||||
softdepend:
|
softdepend:
|
||||||
- MysqlPlayerDataBridge
|
- 'MysqlPlayerDataBridge'
|
||||||
- Plan
|
- 'Plan'
|
||||||
libraries:
|
libraries:
|
||||||
- 'redis.clients:jedis:${jedis_version}'
|
- 'redis.clients:jedis:${jedis_version}'
|
||||||
- 'mysql:mysql-connector-java:${mysql_driver_version}'
|
- 'com.mysql:mysql-connector-j:${mysql_driver_version}'
|
||||||
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
||||||
|
- 'org.apache.commons:commons-text:${commons_text_version}'
|
||||||
|
|
||||||
commands:
|
commands:
|
||||||
husksync:
|
husksync:
|
||||||
usage: '/husksync <update/info/reload/migrate>'
|
usage: '/<command> <update/info/reload/migrate>'
|
||||||
description: 'Manage the HuskSync plugin'
|
description: 'Manage the HuskSync plugin'
|
||||||
userdata:
|
userdata:
|
||||||
usage: '/userdata <view/list/delete/restore/pin> <username> [version_uuid]'
|
usage: '/<command> <view/list/delete/restore/pin/dump> <username> [version_uuid]'
|
||||||
description: 'View, manage & restore player userdata'
|
description: 'View, manage & restore player userdata'
|
||||||
inventory:
|
inventory:
|
||||||
usage: '/inventory <username> [version_uuid]'
|
usage: '/<command> <username> [version_uuid]'
|
||||||
description: 'View & edit a player''s inventory'
|
description: 'View & edit a player''s inventory'
|
||||||
enderchest:
|
enderchest:
|
||||||
usage: '/enderchest <username> [version_uuid]'
|
usage: '/<command> <username> [version_uuid]'
|
||||||
description: 'View & edit a player''s Ender Chest'
|
description: 'View & edit a player''s Ender Chest'
|
||||||
@@ -1,29 +1,40 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation 'commons-io:commons-io:2.11.0'
|
implementation 'commons-io:commons-io:2.13.0'
|
||||||
implementation 'de.themoep:minedown:1.7.1-SNAPSHOT'
|
implementation 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||||
implementation 'com.google.code.gson:gson:2.9.0'
|
implementation 'net.kyori:adventure-api:4.14.0'
|
||||||
implementation 'dev.dejvokep:boosted-yaml:1.3'
|
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') {
|
implementation('com.zaxxer:HikariCP:5.0.1') {
|
||||||
exclude module: 'slf4j-api'
|
exclude module: 'slf4j-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileOnly 'org.jetbrains:annotations:24.0.1'
|
||||||
|
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||||
compileOnly 'redis.clients:jedis:' + jedis_version
|
compileOnly 'redis.clients:jedis:' + jedis_version
|
||||||
compileOnly 'org.xerial.snappy:snappy-java:' + snappy_version
|
compileOnly 'org.xerial.snappy:snappy-java:' + snappy_version
|
||||||
compileOnly 'org.jetbrains:annotations:23.0.0'
|
compileOnly 'org.apache.commons:commons-text:' + commons_text_version
|
||||||
compileOnly 'com.github.plan-player-analytics:Plan:5.4.1690'
|
|
||||||
|
|
||||||
testImplementation 'org.xerial.snappy:snappy-java:1.1.8.4'
|
testImplementation 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||||
testImplementation 'com.github.plan-player-analytics:Plan:5.4.1690'
|
testImplementation 'redis.clients:jedis:' + jedis_version
|
||||||
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3'
|
testImplementation 'org.xerial.snappy:snappy-java:' + snappy_version
|
||||||
testCompileOnly 'org.jetbrains:annotations:23.0.0'
|
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 {
|
shadowJar {
|
||||||
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
||||||
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
||||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'net.kyori', 'net.william278.husksync.libraries'
|
||||||
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
relocate 'org.jetbrains', 'net.william278.husksync.libraries'
|
||||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||||
relocate 'dev.dejvokep', 'net.william278.husksync.libraries'
|
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.annotaml', 'net.william278.husksync.libraries.annotaml'
|
||||||
}
|
}
|
||||||
@@ -1,30 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync;
|
package net.william278.husksync;
|
||||||
|
|
||||||
|
import net.william278.desertwell.util.UpdateChecker;
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.config.Locales;
|
import net.william278.husksync.config.Locales;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.DataAdapter;
|
import net.william278.husksync.data.DataAdapter;
|
||||||
import net.william278.husksync.editor.DataEditor;
|
|
||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
import net.william278.husksync.event.EventCannon;
|
import net.william278.husksync.event.EventCannon;
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.redis.RedisManager;
|
import net.william278.husksync.redis.RedisManager;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.ResourceReader;
|
|
||||||
import net.william278.husksync.util.Version;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract implementation of the HuskSync plugin.
|
* Abstract implementation of the HuskSync plugin.
|
||||||
*/
|
*/
|
||||||
public interface HuskSync {
|
public interface HuskSync {
|
||||||
|
|
||||||
|
int SPIGOT_RESOURCE_ID = 97144;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a set of online players.
|
* Returns a set of online players.
|
||||||
*
|
*
|
||||||
@@ -67,14 +83,6 @@ public interface HuskSync {
|
|||||||
@NotNull
|
@NotNull
|
||||||
DataAdapter getDataAdapter();
|
DataAdapter getDataAdapter();
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data editor implementation
|
|
||||||
*
|
|
||||||
* @return the {@link DataEditor} implementation
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
DataEditor getDataEditor();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the event firing cannon
|
* Returns the event firing cannon
|
||||||
*
|
*
|
||||||
@@ -108,20 +116,33 @@ public interface HuskSync {
|
|||||||
Locales getLocales();
|
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
|
InputStream getResource(@NotNull String name);
|
||||||
Logger getLoggingAdapter();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
void log(@NotNull Level level, @NotNull String message, @NotNull Throwable... throwable);
|
||||||
ResourceReader getResourceReader();
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Returns the plugin version
|
||||||
@@ -131,6 +152,30 @@ public interface HuskSync {
|
|||||||
@NotNull
|
@NotNull
|
||||||
Version getPluginVersion();
|
Version getPluginVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the plugin data folder
|
||||||
|
*
|
||||||
|
* @return the plugin data folder as a {@link File}
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
File getDataFolder();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a future returning the latest plugin {@link Version} if the plugin is out-of-date
|
||||||
|
*
|
||||||
|
* @return a {@link CompletableFuture} returning the latest {@link Version} if the current one is out-of-date
|
||||||
|
*/
|
||||||
|
default CompletableFuture<Optional<Version>> getLatestVersionIfOutdated() {
|
||||||
|
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()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the Minecraft version implementation
|
* Returns the Minecraft version implementation
|
||||||
*
|
*
|
||||||
@@ -146,4 +191,6 @@ public interface HuskSync {
|
|||||||
*/
|
*/
|
||||||
CompletableFuture<Boolean> reload();
|
CompletableFuture<Boolean> reload();
|
||||||
|
|
||||||
|
Set<UUID> getLockedPlayers();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync;
|
package net.william278.husksync;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.api;
|
package net.william278.husksync.api;
|
||||||
|
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
@@ -72,7 +85,7 @@ public abstract class BaseHuskSyncAPI {
|
|||||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull User user) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
if (user instanceof OnlineUser) {
|
if (user instanceof OnlineUser) {
|
||||||
return ((OnlineUser) user).getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join();
|
return ((OnlineUser) user).getUserData(plugin).join();
|
||||||
} else {
|
} else {
|
||||||
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
|
return plugin.getDatabase().getCurrentUserData(user).join().map(UserDataSnapshot::userData);
|
||||||
}
|
}
|
||||||
@@ -103,7 +116,7 @@ public abstract class BaseHuskSyncAPI {
|
|||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
public final CompletableFuture<Void> saveUserData(@NotNull OnlineUser user) {
|
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(
|
.thenAccept(optionalUserData -> optionalUserData.ifPresent(
|
||||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
|
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.API).join())));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
@@ -52,7 +65,7 @@ public abstract class CommandBase {
|
|||||||
*/
|
*/
|
||||||
public String getDescription() {
|
public String getDescription() {
|
||||||
return plugin.getLocales().getRawLocale(command + "_command_description")
|
return plugin.getLocales().getRawLocale(command + "_command_description")
|
||||||
.orElse("A HuskHomes command");
|
.orElse("A HuskSync command");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|||||||
@@ -1,19 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
|
import net.william278.husksync.data.UserDataBuilder;
|
||||||
import net.william278.husksync.data.UserDataSnapshot;
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
import net.william278.husksync.editor.ItemEditorMenu;
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class EnderChestCommand extends CommandBase implements TabCompletable {
|
public class EnderChestCommand extends CommandBase implements TabCompletable {
|
||||||
@@ -29,7 +45,7 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
|||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAccept(optionalUser ->
|
plugin.getDatabase().getUserByName(args[0].toLowerCase(Locale.ENGLISH)).thenAccept(optionalUser ->
|
||||||
optionalUser.ifPresentOrElse(user -> {
|
optionalUser.ifPresentOrElse(user -> {
|
||||||
if (args.length == 2) {
|
if (args.length == 2) {
|
||||||
// View user data by specified UUID
|
// View user data by specified UUID
|
||||||
@@ -44,9 +60,10 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
|||||||
"/enderchest <player> [version_uuid]").ifPresent(player::sendMessage);
|
"/enderchest <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// View latest user data
|
// View (and edit) the latest user data
|
||||||
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
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")
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(player::sendMessage)));
|
.ifPresent(player::sendMessage)));
|
||||||
}
|
}
|
||||||
@@ -55,29 +72,48 @@ public class EnderChestCommand extends CommandBase implements TabCompletable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void showEnderChestMenu(@NotNull OnlineUser player, @NotNull UserDataSnapshot userDataSnapshot,
|
private void showEnderChestMenu(@NotNull OnlineUser player, @NotNull UserDataSnapshot userDataSnapshot,
|
||||||
@NotNull User dataOwner, final boolean allowEdit) {
|
@NotNull User dataOwner, boolean allowEdit) {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
final UserData data = userDataSnapshot.userData();
|
final UserData data = userDataSnapshot.userData();
|
||||||
final ItemEditorMenu menu = ItemEditorMenu.createEnderChestMenu(data.getEnderChestData(),
|
data.getEnderChest().ifPresent(itemData -> {
|
||||||
dataOwner, player, plugin.getLocales(), allowEdit);
|
// Show message
|
||||||
plugin.getLocales().getLocale("viewing_ender_chest_of", dataOwner.username,
|
plugin.getLocales().getLocale("ender_chest_viewer_opened", dataOwner.username,
|
||||||
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
|
new SimpleDateFormat("MMM dd yyyy, HH:mm:ss.sss")
|
||||||
.format(userDataSnapshot.versionTimestamp()))
|
.format(userDataSnapshot.versionTimestamp()))
|
||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
plugin.getDataEditor().openItemEditorMenu(player, menu).thenAccept(enderChestDataOnClose -> {
|
|
||||||
if (!menu.canEdit) {
|
// Show inventory menu
|
||||||
|
player.showMenu(itemData, allowEdit, 3, plugin.getLocales()
|
||||||
|
.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;
|
return;
|
||||||
}
|
}
|
||||||
final UserData updatedUserData = new UserData(data.getStatusData(), data.getInventoryData(),
|
|
||||||
enderChestDataOnClose, data.getPotionEffectsData(), data.getAdvancementData(),
|
|
||||||
data.getStatisticsData(), data.getLocationData(),
|
|
||||||
data.getPersistentDataContainerData(),
|
|
||||||
plugin.getMinecraftVersion().toString());
|
|
||||||
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.ENDERCHEST_COMMAND).join();
|
|
||||||
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
.thenRun(() -> plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -1,52 +1,88 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
|
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.HuskSync;
|
||||||
import net.william278.husksync.config.Locales;
|
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.util.UpdateChecker;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class HuskSyncCommand extends CommandBase implements TabCompletable, ConsoleExecutable {
|
public class HuskSyncCommand extends CommandBase implements TabCompletable, ConsoleExecutable {
|
||||||
|
|
||||||
private final String[] COMMAND_ARGUMENTS = {"update", "about", "reload", "migrate"};
|
private final String[] SUB_COMMANDS = {"update", "about", "reload", "migrate"};
|
||||||
|
private final AboutMenu aboutMenu;
|
||||||
|
|
||||||
public HuskSyncCommand(@NotNull HuskSync implementor) {
|
public HuskSyncCommand(@NotNull HuskSync implementor) {
|
||||||
super("husksync", Permission.COMMAND_HUSKSYNC, implementor);
|
super("husksync", Permission.COMMAND_HUSKSYNC, implementor);
|
||||||
|
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
|
@Override
|
||||||
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
displayPluginInformation(player);
|
sendAboutMenu(player);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (args[0].toLowerCase()) {
|
switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||||
case "update", "version" -> {
|
case "update", "version" -> {
|
||||||
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_UPDATE.node)) {
|
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_UPDATE.node)) {
|
||||||
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final UpdateChecker updateChecker = new UpdateChecker(plugin.getPluginVersion(), plugin.getLoggingAdapter());
|
plugin.getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
||||||
updateChecker.fetchLatestVersion().thenAccept(latestVersion -> {
|
newestVersion.ifPresentOrElse(
|
||||||
if (updateChecker.isUpdateAvailable(latestVersion)) {
|
newVersion -> player.sendMessage(
|
||||||
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| A new update is available:](#00fb9a) [HuskSync " + latestVersion + "](#00fb9a bold)" +
|
new MineDown("[HuskSync](#00fb9a bold) [| A new version of HuskSync is available!"
|
||||||
"[•](white) [Currently running:](#00fb9a) [Version " + updateChecker.getCurrentVersion() + "](gray)" +
|
+ " (v" + newVersion + " (Running: v" + plugin.getPluginVersion() + ")](#00fb9a)")),
|
||||||
"[•](white) [Download links:](#00fb9a) [[⏩ Spigot]](gray open_url=https://www.spigotmc.org/resources/husksync.97144/updates) [•](#262626) [[⏩ Polymart]](gray open_url=https://polymart.org/resource/husksync.1634/updates) [•](#262626) [[⏩ Songoda]](gray open_url=https://songoda.com/marketplace/product/husksync-a-modern-cross-server-player-data-synchronization-system.758)"));
|
() -> player.sendMessage(
|
||||||
} else {
|
new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date."
|
||||||
player.sendMessage(new MineDown("[HuskSync](#00fb9a bold) [| HuskSync is up-to-date, running version " + updateChecker.getCurrentVersion() + "](#00fb9a)"));
|
+ " (Running: v" + plugin.getPluginVersion() + ")](#00fb9a)"))));
|
||||||
}
|
}
|
||||||
});
|
case "about", "info" -> sendAboutMenu(player);
|
||||||
}
|
|
||||||
case "info", "about" -> displayPluginInformation(player);
|
|
||||||
case "reload" -> {
|
case "reload" -> {
|
||||||
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_RELOAD.node)) {
|
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_RELOAD.node)) {
|
||||||
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||||
@@ -66,22 +102,25 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
|||||||
@Override
|
@Override
|
||||||
public void onConsoleExecute(@NotNull String[] args) {
|
public void onConsoleExecute(@NotNull String[] args) {
|
||||||
if (args.length < 1) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
switch (args[0].toLowerCase()) {
|
switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||||
case "update", "version" ->
|
case "update", "version" -> plugin.getLatestVersionIfOutdated().thenAccept(newestVersion ->
|
||||||
new UpdateChecker(plugin.getPluginVersion(), plugin.getLoggingAdapter()).logToConsole();
|
newestVersion.ifPresentOrElse(newVersion -> plugin.log(Level.WARNING,
|
||||||
case "info", "about" ->
|
"An update is available for HuskSync, v" + newVersion
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, new MineDown(plugin.getLocales().stripMineDown(
|
+ " (Running v" + plugin.getPluginVersion() + ")"),
|
||||||
Locales.PLUGIN_INFORMATION.replace("%version%", plugin.getPluginVersion().toString()))));
|
() -> plugin.log(Level.INFO,
|
||||||
|
"HuskSync is up to date" +
|
||||||
|
" (Running v" + plugin.getPluginVersion() + ")")));
|
||||||
|
case "about", "info" -> aboutMenu.toString().lines().forEach(line -> plugin.log(Level.INFO, line));
|
||||||
case "reload" -> {
|
case "reload" -> {
|
||||||
plugin.reload();
|
plugin.reload();
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Reloaded config & message files.");
|
plugin.log(Level.INFO, "Reloaded config & message files.");
|
||||||
}
|
}
|
||||||
case "migrate" -> {
|
case "migrate" -> {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO,
|
plugin.log(Level.INFO,
|
||||||
"Please choose a migrator, then run \"husksync migrate <migrator>\"");
|
"Please choose a migrator, then run \"husksync migrate <migrator>\"");
|
||||||
logMigratorsList();
|
logMigratorsList();
|
||||||
return;
|
return;
|
||||||
@@ -90,35 +129,35 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
|||||||
availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst();
|
availableMigrator.getIdentifier().equalsIgnoreCase(args[1])).findFirst();
|
||||||
selectedMigrator.ifPresentOrElse(migrator -> {
|
selectedMigrator.ifPresentOrElse(migrator -> {
|
||||||
if (args.length < 3) {
|
if (args.length < 3) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, migrator.getHelpMenu());
|
plugin.log(Level.INFO, migrator.getHelpMenu());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (args[2]) {
|
switch (args[2]) {
|
||||||
case "start" -> migrator.start().thenAccept(succeeded -> {
|
case "start" -> migrator.start().thenAccept(succeeded -> {
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO, "Migration completed successfully!");
|
plugin.log(Level.INFO, "Migration completed successfully!");
|
||||||
} else {
|
} else {
|
||||||
plugin.getLoggingAdapter().log(Level.WARNING, "Migration failed!");
|
plugin.log(Level.WARNING, "Migration failed!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
|
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>");
|
"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" +
|
"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();
|
logMigratorsList();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
default -> plugin.getLoggingAdapter().log(Level.INFO,
|
default -> plugin.log(Level.INFO,
|
||||||
"Invalid syntax. Console usage: \"husksync <update/about/reload/migrate>\"");
|
"Invalid syntax. Console usage: \"husksync <update/about/reload/migrate>\"");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void logMigratorsList() {
|
private void logMigratorsList() {
|
||||||
plugin.getLoggingAdapter().log(Level.INFO,
|
plugin.log(Level.INFO,
|
||||||
"List of available migrators:\nMigrator ID / Migrator Name:\n" +
|
"List of available migrators:\nMigrator ID / Migrator Name:\n" +
|
||||||
plugin.getAvailableMigrators().stream()
|
plugin.getAvailableMigrators().stream()
|
||||||
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())
|
.map(migrator -> migrator.getIdentifier() + " - " + migrator.getName())
|
||||||
@@ -128,18 +167,18 @@ public class HuskSyncCommand extends CommandBase implements TabCompletable, Cons
|
|||||||
@Override
|
@Override
|
||||||
public List<String> onTabComplete(@NotNull String[] args) {
|
public List<String> onTabComplete(@NotNull String[] args) {
|
||||||
if (args.length <= 1) {
|
if (args.length <= 1) {
|
||||||
return Arrays.stream(COMMAND_ARGUMENTS)
|
return Arrays.stream(SUB_COMMANDS)
|
||||||
.filter(argument -> argument.startsWith(args.length >= 1 ? args[0] : ""))
|
.filter(argument -> argument.startsWith(args.length == 1 ? args[0] : ""))
|
||||||
.sorted().collect(Collectors.toList());
|
.sorted().collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayPluginInformation(@NotNull OnlineUser player) {
|
private void sendAboutMenu(@NotNull OnlineUser player) {
|
||||||
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_INFO.node)) {
|
if (!player.hasPermission(Permission.COMMAND_HUSKSYNC_ABOUT.node)) {
|
||||||
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
player.sendMessage(new MineDown(Locales.PLUGIN_INFORMATION.replace("%version%", plugin.getPluginVersion().toString())));
|
player.sendMessage(aboutMenu.toComponent());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
|
import net.william278.husksync.data.UserDataBuilder;
|
||||||
import net.william278.husksync.data.UserDataSnapshot;
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
import net.william278.husksync.editor.ItemEditorMenu;
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.text.DateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class InventoryCommand extends CommandBase implements TabCompletable {
|
public class InventoryCommand extends CommandBase implements TabCompletable {
|
||||||
@@ -29,7 +45,7 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
|||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.getDatabase().getUserByName(args[0].toLowerCase()).thenAccept(optionalUser ->
|
plugin.getDatabase().getUserByName(args[0].toLowerCase(Locale.ENGLISH)).thenAccept(optionalUser ->
|
||||||
optionalUser.ifPresentOrElse(user -> {
|
optionalUser.ifPresentOrElse(user -> {
|
||||||
if (args.length == 2) {
|
if (args.length == 2) {
|
||||||
// View user data by specified UUID
|
// View user data by specified UUID
|
||||||
@@ -44,9 +60,10 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
|||||||
"/inventory <player> [version_uuid]").ifPresent(player::sendMessage);
|
"/inventory <player> [version_uuid]").ifPresent(player::sendMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// View latest user data
|
// View (and edit) the latest user data
|
||||||
plugin.getDatabase().getCurrentUserData(user).thenAccept(optionalData -> optionalData.ifPresentOrElse(
|
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")
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(player::sendMessage)));
|
.ifPresent(player::sendMessage)));
|
||||||
}
|
}
|
||||||
@@ -58,23 +75,43 @@ public class InventoryCommand extends CommandBase implements TabCompletable {
|
|||||||
@NotNull User dataOwner, boolean allowEdit) {
|
@NotNull User dataOwner, boolean allowEdit) {
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
final UserData data = userDataSnapshot.userData();
|
final UserData data = userDataSnapshot.userData();
|
||||||
final ItemEditorMenu menu = ItemEditorMenu.createInventoryMenu(data.getInventoryData(),
|
data.getInventory().ifPresent(itemData -> {
|
||||||
dataOwner, player, plugin.getLocales(), allowEdit);
|
// Show message
|
||||||
plugin.getLocales().getLocale("viewing_inventory_of", dataOwner.username,
|
plugin.getLocales().getLocale("inventory_viewer_opened", dataOwner.username,
|
||||||
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
|
new SimpleDateFormat("MMM dd yyyy, HH:mm:ss.sss")
|
||||||
.format(userDataSnapshot.versionTimestamp()))
|
.format(userDataSnapshot.versionTimestamp()))
|
||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
plugin.getDataEditor().openItemEditorMenu(player, menu).thenAccept(inventoryDataOnClose -> {
|
|
||||||
if (!menu.canEdit) {
|
// Show inventory menu
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
final UserData updatedUserData = new UserData(data.getStatusData(), inventoryDataOnClose,
|
|
||||||
data.getEnderChestData(), data.getPotionEffectsData(), data.getAdvancementData(),
|
// Create the updated data
|
||||||
data.getStatisticsData(), data.getLocationData(),
|
final UserDataBuilder builder = UserData.builder(plugin.getMinecraftVersion());
|
||||||
data.getPersistentDataContainerData(),
|
data.getStatus().ifPresent(builder::setStatus);
|
||||||
plugin.getMinecraftVersion().toString());
|
data.getAdvancements().ifPresent(builder::setAdvancements);
|
||||||
plugin.getDatabase().setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND).join();
|
data.getLocation().ifPresent(builder::setLocation);
|
||||||
plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData).join();
|
data.getPersistentDataContainer().ifPresent(builder::setPersistentDataContainer);
|
||||||
|
data.getStatistics().ifPresent(builder::setStatistics);
|
||||||
|
data.getPotionEffects().ifPresent(builder::setPotionEffects);
|
||||||
|
data.getEnderChest().ifPresent(builder::setEnderChest);
|
||||||
|
builder.setInventory(dataOnClose.get());
|
||||||
|
|
||||||
|
// Set the updated data
|
||||||
|
final UserData updatedUserData = builder.build();
|
||||||
|
plugin.getDatabase()
|
||||||
|
.setUserData(dataOwner, updatedUserData, DataSaveCause.INVENTORY_COMMAND)
|
||||||
|
.thenRun(() -> plugin.getRedisManager().sendUserDataUpdate(dataOwner, updatedUserData));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -18,7 +31,7 @@ public enum Permission {
|
|||||||
/**
|
/**
|
||||||
* Lets the user view plugin info {@code /husksync info}
|
* Lets the user view plugin info {@code /husksync info}
|
||||||
*/
|
*/
|
||||||
COMMAND_HUSKSYNC_INFO("husksync.command.husksync.info", DefaultAccess.EVERYONE),
|
COMMAND_HUSKSYNC_ABOUT("husksync.command.husksync.info", DefaultAccess.EVERYONE),
|
||||||
/**
|
/**
|
||||||
* Lets the user reload the plugin {@code /husksync reload}
|
* Lets the user reload the plugin {@code /husksync reload}
|
||||||
*/
|
*/
|
||||||
@@ -41,6 +54,11 @@ public enum Permission {
|
|||||||
*/
|
*/
|
||||||
COMMAND_USER_DATA_MANAGE("husksync.command.userdata.manage", DefaultAccess.OPERATORS),
|
COMMAND_USER_DATA_MANAGE("husksync.command.userdata.manage", DefaultAccess.OPERATORS),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets the user dump user data to a file or the web {@code /userdata dump (player) (version_uuid)}
|
||||||
|
*/
|
||||||
|
COMMAND_USER_DATA_DUMP("husksync.command.userdata.dump", DefaultAccess.NOBODY),
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* /inventory command permissions
|
* /inventory command permissions
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|||||||
@@ -1,20 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
import net.william278.husksync.data.UserData;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.util.DataDumper;
|
||||||
|
import net.william278.husksync.util.DataSnapshotList;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.io.IOException;
|
||||||
import java.util.Collections;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class UserDataCommand extends CommandBase implements TabCompletable {
|
public class UserDataCommand extends CommandBase implements TabCompletable {
|
||||||
|
|
||||||
private final String[] COMMAND_ARGUMENTS = {"view", "list", "delete", "restore", "pin"};
|
private final String[] COMMAND_ARGUMENTS = {"view", "list", "delete", "restore", "pin", "dump"};
|
||||||
|
|
||||||
public UserDataCommand(@NotNull HuskSync implementor) {
|
public UserDataCommand(@NotNull HuskSync implementor) {
|
||||||
super("userdata", Permission.COMMAND_USER_DATA, implementor, "playerdata");
|
super("userdata", Permission.COMMAND_USER_DATA, implementor, "playerdata");
|
||||||
@@ -24,12 +39,12 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
public void onExecute(@NotNull OnlineUser player, @NotNull String[] args) {
|
||||||
if (args.length < 1) {
|
if (args.length < 1) {
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
"/userdata <view/list/delete/restore/pin> <username> [version_uuid]")
|
"/userdata <view/list/delete/restore/pin/dump> <username> [version_uuid]")
|
||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (args[0].toLowerCase()) {
|
switch (args[0].toLowerCase(Locale.ENGLISH)) {
|
||||||
case "view" -> {
|
case "view" -> {
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
@@ -41,11 +56,12 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
if (args.length >= 3) {
|
if (args.length >= 3) {
|
||||||
try {
|
try {
|
||||||
final UUID versionUuid = UUID.fromString(args[2]);
|
final UUID versionUuid = UUID.fromString(args[2]);
|
||||||
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||||
optionalUser -> optionalUser.ifPresentOrElse(
|
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||||
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(data ->
|
.thenAccept(optionalUser -> optionalUser
|
||||||
data.ifPresentOrElse(userData -> plugin.getDataEditor()
|
.ifPresentOrElse(user -> plugin.getDatabase().getUserData(user, versionUuid)
|
||||||
.displayDataOverview(player, userData, user),
|
.thenAccept(data -> data.ifPresentOrElse(
|
||||||
|
userData -> userData.displayDataOverview(player, user, plugin.getLocales()),
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
() -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
.ifPresent(player::sendMessage))),
|
.ifPresent(player::sendMessage))),
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_player")
|
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
@@ -56,12 +72,12 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||||
optionalUser -> optionalUser.ifPresentOrElse(
|
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||||
user -> plugin.getDatabase().getCurrentUserData(user).thenAccept(
|
.thenAccept(optionalUser -> optionalUser
|
||||||
latestData -> latestData.ifPresentOrElse(
|
.ifPresentOrElse(user -> plugin.getDatabase().getCurrentUserData(user)
|
||||||
userData -> plugin.getDataEditor()
|
.thenAccept(latestData -> latestData.ifPresentOrElse(
|
||||||
.displayDataOverview(player, userData, user),
|
userData -> userData.displayDataOverview(player, user, plugin.getLocales()),
|
||||||
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
() -> plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(player::sendMessage))),
|
.ifPresent(player::sendMessage))),
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_player")
|
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
@@ -75,20 +91,38 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
}
|
}
|
||||||
if (args.length < 2) {
|
if (args.length < 2) {
|
||||||
plugin.getLocales().getLocale("error_invalid_syntax",
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
"/userdata list <username>")
|
"/userdata list <username> [page]")
|
||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final String username = args[1];
|
final String username = args[1];
|
||||||
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||||
optionalUser -> optionalUser.ifPresentOrElse(
|
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||||
|
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
user -> plugin.getDatabase().getUserData(user).thenAccept(dataList -> {
|
user -> plugin.getDatabase().getUserData(user).thenAccept(dataList -> {
|
||||||
|
// Check if there is data to display
|
||||||
if (dataList.isEmpty()) {
|
if (dataList.isEmpty()) {
|
||||||
plugin.getLocales().getLocale("error_no_data_to_display")
|
plugin.getLocales().getLocale("error_no_data_to_display")
|
||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.getDataEditor().displayDataList(player, dataList, user);
|
|
||||||
|
// Determine page to display
|
||||||
|
int page = 1;
|
||||||
|
if (args.length >= 3) {
|
||||||
|
try {
|
||||||
|
page = Integer.parseInt(args[2]);
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/userdata list <username> [page]")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the list to the player
|
||||||
|
DataSnapshotList.create(dataList, user, plugin.getLocales())
|
||||||
|
.displayPage(player, page);
|
||||||
}),
|
}),
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_player")
|
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
.ifPresent(player::sendMessage))));
|
.ifPresent(player::sendMessage))));
|
||||||
@@ -108,8 +142,9 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
final String username = args[1];
|
final String username = args[1];
|
||||||
try {
|
try {
|
||||||
final UUID versionUuid = UUID.fromString(args[2]);
|
final UUID versionUuid = UUID.fromString(args[2]);
|
||||||
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||||
optionalUser -> optionalUser.ifPresentOrElse(
|
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||||
|
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
user -> plugin.getDatabase().deleteUserData(user, versionUuid).thenAccept(deleted -> {
|
user -> plugin.getDatabase().deleteUserData(user, versionUuid).thenAccept(deleted -> {
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
plugin.getLocales().getLocale("data_deleted",
|
plugin.getLocales().getLocale("data_deleted",
|
||||||
@@ -146,16 +181,22 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
final String username = args[1];
|
final String username = args[1];
|
||||||
try {
|
try {
|
||||||
final UUID versionUuid = UUID.fromString(args[2]);
|
final UUID versionUuid = UUID.fromString(args[2]);
|
||||||
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||||
optionalUser -> optionalUser.ifPresentOrElse(
|
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||||
|
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(data -> {
|
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(data -> {
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
plugin.getLocales().getLocale("error_invalid_version_uuid")
|
plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.getDatabase().setUserData(user, data.get().userData(),
|
|
||||||
DataSaveCause.BACKUP_RESTORE);
|
// Restore users with a minimum of one health (prevent restoring players with <=0 health)
|
||||||
|
final UserData userData = data.get().userData();
|
||||||
|
userData.getStatus().ifPresent(status -> status.health = Math.max(1, status.health));
|
||||||
|
|
||||||
|
// Set the users data and send a message
|
||||||
|
plugin.getDatabase().setUserData(user, userData, DataSaveCause.BACKUP_RESTORE);
|
||||||
plugin.getRedisManager().sendUserDataUpdate(user, data.get().userData()).join();
|
plugin.getRedisManager().sendUserDataUpdate(user, data.get().userData()).join();
|
||||||
plugin.getLocales().getLocale("data_restored",
|
plugin.getLocales().getLocale("data_restored",
|
||||||
user.username,
|
user.username,
|
||||||
@@ -183,11 +224,13 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final String username = args[1];
|
final String username = args[1];
|
||||||
try {
|
try {
|
||||||
final UUID versionUuid = UUID.fromString(args[2]);
|
final UUID versionUuid = UUID.fromString(args[2]);
|
||||||
CompletableFuture.runAsync(() -> plugin.getDatabase().getUserByName(username.toLowerCase()).thenAccept(
|
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||||
optionalUser -> optionalUser.ifPresentOrElse(
|
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||||
|
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(
|
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(
|
||||||
optionalUserData -> optionalUserData.ifPresentOrElse(userData -> {
|
optionalUserData -> optionalUserData.ifPresentOrElse(userData -> {
|
||||||
if (userData.pinned()) {
|
if (userData.pinned()) {
|
||||||
@@ -217,6 +260,46 @@ public class UserDataCommand extends CommandBase implements TabCompletable {
|
|||||||
.ifPresent(player::sendMessage);
|
.ifPresent(player::sendMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case "dump" -> {
|
||||||
|
if (!player.hasPermission(Permission.COMMAND_USER_DATA_DUMP.node)) {
|
||||||
|
plugin.getLocales().getLocale("error_no_permission").ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.length < 3) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/userdata dump <username> <version_uuid>")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean toWeb = args.length > 3 && args[3].equalsIgnoreCase("web");
|
||||||
|
final String username = args[1];
|
||||||
|
try {
|
||||||
|
final UUID versionUuid = UUID.fromString(args[2]);
|
||||||
|
CompletableFuture.runAsync(() -> plugin.getDatabase()
|
||||||
|
.getUserByName(username.toLowerCase(Locale.ENGLISH))
|
||||||
|
.thenAccept(optionalUser -> optionalUser.ifPresentOrElse(
|
||||||
|
user -> plugin.getDatabase().getUserData(user, versionUuid).thenAccept(
|
||||||
|
optionalUserData -> optionalUserData.ifPresentOrElse(userData -> {
|
||||||
|
try {
|
||||||
|
final DataDumper dumper = DataDumper.create(userData, user, plugin);
|
||||||
|
final String result = toWeb ? dumper.toWeb() : dumper.toFile();
|
||||||
|
plugin.getLocales().getLocale("data_dumped", versionUuid.toString()
|
||||||
|
.split("-")[0], user.username, result)
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
} catch (IOException e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to dump user data", e);
|
||||||
|
}
|
||||||
|
}, () -> plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
|
.ifPresent(player::sendMessage))),
|
||||||
|
() -> plugin.getLocales().getLocale("error_invalid_player")
|
||||||
|
.ifPresent(player::sendMessage))));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
plugin.getLocales().getLocale("error_invalid_syntax",
|
||||||
|
"/userdata dump <username> <version_uuid>")
|
||||||
|
.ifPresent(player::sendMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,54 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.config;
|
package net.william278.husksync.config;
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
import net.william278.annotaml.YamlFile;
|
||||||
|
import net.william278.paginedown.ListOptions;
|
||||||
|
import org.apache.commons.text.StringEscapeUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loaded locales used by the plugin to display various locales
|
* Loaded locales used by the plugin to display styled messages
|
||||||
*/
|
*/
|
||||||
|
@YamlFile(rootedMap = true, header = """
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ HuskHomes Locales ┃
|
||||||
|
┃ Developed by William278 ┃
|
||||||
|
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
|
┣╸ See plugin about menu for international locale credits
|
||||||
|
┣╸ Formatted in MineDown: https://github.com/Phoenix616/MineDown
|
||||||
|
┗╸ Translate HuskSync: https://william278.net/docs/husksync/Translations""")
|
||||||
public class Locales {
|
public class Locales {
|
||||||
|
|
||||||
public static final String PLUGIN_INFORMATION = """
|
/**
|
||||||
[HuskSync](#00fb9a bold) [| Version %version%](#00fb9a)
|
* The raw set of locales loaded from yaml
|
||||||
[A modern, cross-server player data synchronization system](gray)
|
*/
|
||||||
[• Author:](white) [William278](gray show_text=&7Click to visit website open_url=https://william278.net)
|
|
||||||
[• Contributors:](white) [HarvelsX](gray show_text=&7Code), [HookWoods](gray show_text=&7Code)
|
|
||||||
[• Translators:](white) [Namiu](gray show_text=&7\\(うにたろう\\) - Japanese, ja-jp), [anchelthe](gray show_text=&7Spanish, es-es), [Melonzio](gray show_text=&7Spanish, es-es), [Ceddix](gray show_text=&7German, de-de), [Pukejoy_1](gray show_text=&7Bulgarian, bg-bg), [mateusneresrb](gray show_text=&7Brazilian Portuguese, pt-br], [小蔡](gray show_text=&7Traditional Chinese, zh-tw), [Ghost-chu](gray show_text=&7Simplified Chinese, zh-cn), [DJelly4K](gray show_text=&7Simplified Chinese, zh-cn), [Thourgard](gray show_text=&7Ukrainian, uk-ua), [xF3d3](gray show_text=&7Italian, it-it)
|
|
||||||
[• Documentation:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://william278.net/docs/husksync/Home/)
|
|
||||||
[• Bug reporting:](white) [[Link]](#00fb9a show_text=&7Click to open link open_url=https://github.com/WiIIiam278/HuskSync/issues)
|
|
||||||
[• Discord support:](white) [[Link]](#00fb9a show_text=&7Click to join open_url=https://discord.gg/tVYhJfyDWG)""";
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private final HashMap<String, String> rawLocales;
|
public Map<String, String> rawLocales = new HashMap<>();
|
||||||
|
|
||||||
private Locales(@NotNull YamlDocument localesConfig) {
|
|
||||||
this.rawLocales = new HashMap<>();
|
|
||||||
for (String localeId : localesConfig.getRoutesAsStrings(false)) {
|
|
||||||
rawLocales.put(localeId, localesConfig.getString(localeId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an un-formatted locale loaded from the locales file
|
* Returns a raw, un-formatted locale loaded from the locales file
|
||||||
*
|
*
|
||||||
* @param localeId String identifier of the locale, corresponding to a key in the file
|
* @param localeId String identifier of the locale, corresponding to a key in the file
|
||||||
* @return An {@link Optional} containing the locale corresponding to the id, if it exists
|
* @return An {@link Optional} containing the locale corresponding to the id, if it exists
|
||||||
*/
|
*/
|
||||||
public Optional<String> getRawLocale(@NotNull String localeId) {
|
public Optional<String> getRawLocale(@NotNull String localeId) {
|
||||||
if (rawLocales.containsKey(localeId)) {
|
return Optional.ofNullable(rawLocales.get(localeId)).map(StringEscapeUtils::unescapeJava);
|
||||||
return Optional.of(rawLocales.get(localeId).replaceAll(Pattern.quote("\\n"), "\n"));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an un-formatted locale loaded from the locales file, with replacements applied
|
* Returns a raw, un-formatted locale loaded from the locales file, with replacements applied
|
||||||
|
* <p>
|
||||||
|
* Note that replacements will not be MineDown-escaped; use {@link #escapeMineDown(String)} to escape replacements
|
||||||
*
|
*
|
||||||
* @param localeId String identifier of the locale, corresponding to a key in the file
|
* @param localeId String identifier of the locale, corresponding to a key in the file
|
||||||
* @param replacements Ordered array of replacement strings to fill in placeholders with
|
* @param replacements Ordered array of replacement strings to fill in placeholders with
|
||||||
@@ -70,13 +78,16 @@ public class Locales {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a MineDown-formatted locale from the locales file, with replacements applied
|
* Returns a MineDown-formatted locale from the locales file, with replacements applied
|
||||||
|
* <p>
|
||||||
|
* Note that replacements will be MineDown-escaped before application
|
||||||
*
|
*
|
||||||
* @param localeId String identifier of the locale, corresponding to a key in the file
|
* @param localeId String identifier of the locale, corresponding to a key in the file
|
||||||
* @param replacements Ordered array of replacement strings to fill in placeholders with
|
* @param replacements Ordered array of replacement strings to fill in placeholders with
|
||||||
* @return An {@link Optional} containing the replacement-applied, formatted locale corresponding to the id, if it exists
|
* @return An {@link Optional} containing the replacement-applied, formatted locale corresponding to the id, if it exists
|
||||||
*/
|
*/
|
||||||
public Optional<MineDown> getLocale(@NotNull String localeId, @NotNull String... replacements) {
|
public Optional<MineDown> getLocale(@NotNull String localeId, @NotNull String... replacements) {
|
||||||
return getRawLocale(localeId, replacements).map(MineDown::new);
|
return getRawLocale(localeId, Arrays.stream(replacements).map(Locales::escapeMineDown)
|
||||||
|
.toArray(String[]::new)).map(MineDown::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,54 +97,90 @@ public class Locales {
|
|||||||
* @param replacements Ordered array of replacement strings to fill in placeholders with
|
* @param replacements Ordered array of replacement strings to fill in placeholders with
|
||||||
* @return the raw locale, with inserted placeholders
|
* @return the raw locale, with inserted placeholders
|
||||||
*/
|
*/
|
||||||
|
@NotNull
|
||||||
private String applyReplacements(@NotNull String rawLocale, @NotNull String... replacements) {
|
private String applyReplacements(@NotNull String rawLocale, @NotNull String... replacements) {
|
||||||
int replacementIndexer = 1;
|
int replacementIndexer = 1;
|
||||||
for (String replacement : replacements) {
|
for (String replacement : replacements) {
|
||||||
String replacementString = "%" + replacementIndexer + "%";
|
String replacementString = "%" + replacementIndexer + "%";
|
||||||
rawLocale = rawLocale.replace(replacementString, replacement);
|
rawLocale = rawLocale.replace(replacementString, replacement);
|
||||||
replacementIndexer = replacementIndexer + 1;
|
replacementIndexer += 1;
|
||||||
}
|
}
|
||||||
return rawLocale;
|
return rawLocale;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the locales from a BoostedYaml {@link YamlDocument} locales file
|
* Escape a string from {@link MineDown} formatting for use in a MineDown-formatted locale
|
||||||
|
* <p>
|
||||||
|
* Although MineDown provides {@link MineDown#escape(String)}, that method fails to escape events
|
||||||
|
* properly when using the escaped string in a replacement, so this is used instead
|
||||||
*
|
*
|
||||||
* @param localesConfig The loaded {@link YamlDocument} locales.yml file
|
* @param string The string to escape
|
||||||
* @return the loaded {@link Locales}
|
* @return The escaped string
|
||||||
*/
|
*/
|
||||||
public static Locales load(@NotNull YamlDocument localesConfig) {
|
@NotNull
|
||||||
return new Locales(localesConfig);
|
public static String escapeMineDown(@NotNull String string) {
|
||||||
|
final StringBuilder value = new StringBuilder();
|
||||||
|
for (int i = 0; i < string.length(); ++i) {
|
||||||
|
char c = string.charAt(i);
|
||||||
|
boolean isEscape = c == '\\';
|
||||||
|
boolean isColorCode = i + 1 < string.length() && (c == 167 || c == '&');
|
||||||
|
boolean isEvent = c == '[' || c == ']' || c == '(' || c == ')';
|
||||||
|
if (isEscape || isColorCode || isEvent) {
|
||||||
|
value.append('\\');
|
||||||
|
}
|
||||||
|
|
||||||
|
value.append(c);
|
||||||
|
}
|
||||||
|
return value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips a string of basic MineDown formatting, used for displaying plugin info to console
|
* Truncates a String to a specified length, and appends an ellipsis if it is longer than the specified length
|
||||||
*
|
*
|
||||||
* @param string The string to strip
|
* @param string The string to truncate
|
||||||
* @return The MineDown-stripped string
|
* @param length The maximum length of the string
|
||||||
|
* @return The truncated string
|
||||||
*/
|
*/
|
||||||
public String stripMineDown(@NotNull String string) {
|
@NotNull
|
||||||
final String[] in = string.split("\n");
|
public static String truncate(@NotNull String string, int length) {
|
||||||
final StringBuilder out = new StringBuilder();
|
if (string.length() > length) {
|
||||||
String regex = "[^\\[\\]() ]*\\[([^()]+)]\\([^()]+open_url=(\\S+).*\\)";
|
return string.substring(0, length) + "…";
|
||||||
|
}
|
||||||
for (int i = 0; i < in.length; i++) {
|
return string;
|
||||||
Pattern pattern = Pattern.compile(regex);
|
|
||||||
Matcher m = pattern.matcher(in[i]);
|
|
||||||
|
|
||||||
if (m.find()) {
|
|
||||||
out.append(in[i].replace(m.group(0), ""));
|
|
||||||
out.append(m.group(2));
|
|
||||||
} else {
|
|
||||||
out.append(in[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i + 1 != in.length) {
|
/**
|
||||||
out.append("\n");
|
* Returns the base list options to use for a paginated chat list
|
||||||
}
|
*
|
||||||
|
* @param itemsPerPage The number of items to display per page
|
||||||
|
* @return The list options
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public ListOptions.Builder getBaseChatList(int itemsPerPage) {
|
||||||
|
return new ListOptions.Builder()
|
||||||
|
.setFooterFormat(getRawLocale("list_footer",
|
||||||
|
"%previous_page_button%", "%current_page%",
|
||||||
|
"%total_pages%", "%next_page_button%", "%page_jumpers%").orElse(""))
|
||||||
|
.setNextButtonFormat(getRawLocale("list_next_page_button",
|
||||||
|
"%next_page_index%", "%command%").orElse(""))
|
||||||
|
.setPreviousButtonFormat(getRawLocale("list_previous_page_button",
|
||||||
|
"%previous_page_index%", "%command%").orElse(""))
|
||||||
|
.setPageJumpersFormat(getRawLocale("list_page_jumpers",
|
||||||
|
"%page_jump_buttons%").orElse(""))
|
||||||
|
.setPageJumperPageFormat(getRawLocale("list_page_jumper_button",
|
||||||
|
"%target_page_index%", "%command%").orElse(""))
|
||||||
|
.setPageJumperCurrentPageFormat(getRawLocale("list_page_jumper_current_page",
|
||||||
|
"%current_page%").orElse(""))
|
||||||
|
.setPageJumperPageSeparator(getRawLocale("list_page_jumper_separator").orElse(""))
|
||||||
|
.setPageJumperGroupSeparator(getRawLocale("list_page_jumper_group_separator").orElse(""))
|
||||||
|
.setItemsPerPage(itemsPerPage)
|
||||||
|
.setEscapeItemsMineDown(false)
|
||||||
|
.setSpaceAfterHeader(false)
|
||||||
|
.setSpaceBeforeFooter(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return out.toString();
|
@SuppressWarnings("unused")
|
||||||
|
public Locales() {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,276 +1,427 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.config;
|
package net.william278.husksync.config;
|
||||||
|
|
||||||
import dev.dejvokep.boostedyaml.YamlDocument;
|
import net.william278.annotaml.YamlComment;
|
||||||
|
import net.william278.annotaml.YamlFile;
|
||||||
|
import net.william278.annotaml.YamlKey;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Settings used for the plugin, as read from the config file
|
* Plugin settings, read from config.yml
|
||||||
*/
|
*/
|
||||||
|
@YamlFile(header = """
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ HuskSync Config ┃
|
||||||
|
┃ Developed by William278 ┃
|
||||||
|
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
|
┣╸ Information: https://william278.net/project/husksync
|
||||||
|
┗╸ Documentation: https://william278.net/docs/husksync""",
|
||||||
|
versionField = "config_version", versionNumber = 4)
|
||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
/**
|
// Top-level settings
|
||||||
* Map of {@link ConfigOption}s read from the config file
|
@YamlKey("language")
|
||||||
*/
|
private String language = "en-gb";
|
||||||
private final Map<ConfigOption, Object> configOptions;
|
|
||||||
|
|
||||||
// Load the settings from the document
|
@YamlKey("check_for_updates")
|
||||||
private Settings(@NotNull YamlDocument config) {
|
private boolean checkForUpdates = true;
|
||||||
this.configOptions = new HashMap<>();
|
|
||||||
Arrays.stream(ConfigOption.values()).forEach(configOption -> configOptions
|
@YamlKey("cluster_id")
|
||||||
.put(configOption, switch (configOption.optionType) {
|
private String clusterId = "";
|
||||||
case BOOLEAN -> configOption.getBooleanValue(config);
|
|
||||||
case STRING -> configOption.getStringValue(config);
|
@YamlKey("debug_logging")
|
||||||
case DOUBLE -> configOption.getDoubleValue(config);
|
private boolean debugLogging = false;
|
||||||
case FLOAT -> configOption.getFloatValue(config);
|
|
||||||
case INTEGER -> configOption.getIntValue(config);
|
|
||||||
case STRING_LIST -> configOption.getStringListValue(config);
|
// Database settings
|
||||||
}));
|
@YamlComment("Database connection settings")
|
||||||
|
@YamlKey("database.credentials.host")
|
||||||
|
private String mySqlHost = "localhost";
|
||||||
|
|
||||||
|
@YamlKey("database.credentials.port")
|
||||||
|
private int mySqlPort = 3306;
|
||||||
|
|
||||||
|
@YamlKey("database.credentials.database")
|
||||||
|
private String mySqlDatabase = "HuskSync";
|
||||||
|
|
||||||
|
@YamlKey("database.credentials.username")
|
||||||
|
private String mySqlUsername = "root";
|
||||||
|
|
||||||
|
@YamlKey("database.credentials.password")
|
||||||
|
private String mySqlPassword = "pa55w0rd";
|
||||||
|
|
||||||
|
@YamlKey("database.credentials.parameters")
|
||||||
|
private String mySqlConnectionParameters = "?autoReconnect=true&useSSL=false";
|
||||||
|
|
||||||
|
@YamlComment("MySQL connection pool properties")
|
||||||
|
@YamlKey("database.connection_pool.maximum_pool_size")
|
||||||
|
private int mySqlConnectionPoolSize = 10;
|
||||||
|
|
||||||
|
@YamlKey("database.connection_pool.minimum_idle")
|
||||||
|
private int mySqlConnectionPoolIdle = 10;
|
||||||
|
|
||||||
|
@YamlKey("database.connection_pool.maximum_lifetime")
|
||||||
|
private long mySqlConnectionPoolLifetime = 1800000;
|
||||||
|
|
||||||
|
@YamlKey("database.connection_pool.keepalive_time")
|
||||||
|
private long mySqlConnectionPoolKeepAlive = 0;
|
||||||
|
|
||||||
|
@YamlKey("database.connection_pool.connection_timeout")
|
||||||
|
private long mySqlConnectionPoolTimeout = 5000;
|
||||||
|
|
||||||
|
@YamlKey("database.table_names")
|
||||||
|
private Map<String, String> tableNames = TableName.getDefaults();
|
||||||
|
|
||||||
|
|
||||||
|
// Redis settings
|
||||||
|
@YamlComment("Redis connection settings")
|
||||||
|
@YamlKey("redis.credentials.host")
|
||||||
|
private String redisHost = "localhost";
|
||||||
|
|
||||||
|
@YamlKey("redis.credentials.port")
|
||||||
|
private int redisPort = 6379;
|
||||||
|
|
||||||
|
@YamlKey("redis.credentials.password")
|
||||||
|
private String redisPassword = "";
|
||||||
|
|
||||||
|
@YamlKey("redis.use_ssl")
|
||||||
|
private boolean redisUseSsl = false;
|
||||||
|
|
||||||
|
|
||||||
|
// Synchronization settings
|
||||||
|
@YamlComment("Synchronization settings")
|
||||||
|
@YamlKey("synchronization.max_user_data_snapshots")
|
||||||
|
private int maxUserDataSnapshots = 5;
|
||||||
|
|
||||||
|
@YamlKey("synchronization.save_on_world_save")
|
||||||
|
private boolean saveOnWorldSave = true;
|
||||||
|
|
||||||
|
@YamlKey("synchronization.save_on_death")
|
||||||
|
private boolean saveOnDeath = false;
|
||||||
|
|
||||||
|
@YamlKey("synchronization.save_empty_drops_on_death")
|
||||||
|
private boolean saveEmptyDropsOnDeath = true;
|
||||||
|
|
||||||
|
@YamlKey("synchronization.compress_data")
|
||||||
|
private boolean compressData = 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")
|
||||||
|
private int networkLatencyMilliseconds = 500;
|
||||||
|
|
||||||
|
@YamlKey("synchronization.features")
|
||||||
|
private Map<String, Boolean> synchronizationFeatures = SynchronizationFeature.getDefaults();
|
||||||
|
|
||||||
|
@YamlKey("synchronization.blacklisted_commands_while_locked")
|
||||||
|
private List<String> blacklistedCommandsWhileLocked = new ArrayList<>();
|
||||||
|
|
||||||
|
@YamlKey("synchronization.event_priorities")
|
||||||
|
private Map<String, String> synchronizationEventPriorities = EventType.getDefaults();
|
||||||
|
|
||||||
|
|
||||||
|
// Zero-args constructor for instantiation via Annotaml
|
||||||
|
public Settings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default constructor for empty settings
|
|
||||||
protected Settings(@NotNull Map<ConfigOption, Object> configOptions) {
|
@NotNull
|
||||||
this.configOptions = configOptions;
|
public String getLanguage() {
|
||||||
|
return language;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean doCheckForUpdates() {
|
||||||
|
return checkForUpdates;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getClusterId() {
|
||||||
|
return clusterId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean doDebugLogging() {
|
||||||
|
return debugLogging;
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value of the specified {@link ConfigOption}
|
* Represents the names of tables in the database
|
||||||
*
|
|
||||||
* @param option the {@link ConfigOption} to check
|
|
||||||
* @return the value of the {@link ConfigOption} as a boolean
|
|
||||||
* @throws ClassCastException if the option is not a boolean
|
|
||||||
*/
|
*/
|
||||||
public boolean getBooleanValue(@NotNull ConfigOption option) throws ClassCastException {
|
public enum TableName {
|
||||||
return (Boolean) configOptions.get(option);
|
USERS("husksync_users"),
|
||||||
|
USER_DATA("husksync_user_data");
|
||||||
|
|
||||||
|
private final String defaultName;
|
||||||
|
|
||||||
|
TableName(@NotNull String defaultName) {
|
||||||
|
this.defaultName = defaultName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@NotNull
|
||||||
* Get the value of the specified {@link ConfigOption}
|
private Map.Entry<String, String> toEntry() {
|
||||||
*
|
return Map.entry(name().toLowerCase(Locale.ENGLISH), defaultName);
|
||||||
* @param option the {@link ConfigOption} to check
|
|
||||||
* @return the value of the {@link ConfigOption} as a string
|
|
||||||
* @throws ClassCastException if the option is not a string
|
|
||||||
*/
|
|
||||||
public String getStringValue(@NotNull ConfigOption option) throws ClassCastException {
|
|
||||||
return (String) configOptions.get(option);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of the specified {@link ConfigOption}
|
|
||||||
*
|
|
||||||
* @param option the {@link ConfigOption} to check
|
|
||||||
* @return the value of the {@link ConfigOption} as a double
|
|
||||||
* @throws ClassCastException if the option is not a double
|
|
||||||
*/
|
|
||||||
public double getDoubleValue(@NotNull ConfigOption option) throws ClassCastException {
|
|
||||||
return (Double) configOptions.get(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of the specified {@link ConfigOption}
|
|
||||||
*
|
|
||||||
* @param option the {@link ConfigOption} to check
|
|
||||||
* @return the value of the {@link ConfigOption} as a float
|
|
||||||
* @throws ClassCastException if the option is not a float
|
|
||||||
*/
|
|
||||||
public double getFloatValue(@NotNull ConfigOption option) throws ClassCastException {
|
|
||||||
return (Float) configOptions.get(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of the specified {@link ConfigOption}
|
|
||||||
*
|
|
||||||
* @param option the {@link ConfigOption} to check
|
|
||||||
* @return the value of the {@link ConfigOption} as an integer
|
|
||||||
* @throws ClassCastException if the option is not an integer
|
|
||||||
*/
|
|
||||||
public int getIntegerValue(@NotNull ConfigOption option) throws ClassCastException {
|
|
||||||
return (Integer) configOptions.get(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value of the specified {@link ConfigOption}
|
|
||||||
*
|
|
||||||
* @param option the {@link ConfigOption} to check
|
|
||||||
* @return the value of the {@link ConfigOption} as a string {@link List}
|
|
||||||
* @throws ClassCastException if the option is not a string list
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public List<String> getStringListValue(@NotNull ConfigOption option) throws ClassCastException {
|
|
||||||
return (List<String>) configOptions.get(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the settings from a BoostedYaml {@link YamlDocument} config file
|
|
||||||
*
|
|
||||||
* @param config The loaded {@link YamlDocument} config.yml file
|
|
||||||
* @return the loaded {@link Settings}
|
|
||||||
*/
|
|
||||||
public static Settings load(@NotNull YamlDocument config) {
|
|
||||||
return new Settings(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an option stored by a path in config.yml
|
|
||||||
*/
|
|
||||||
public enum ConfigOption {
|
|
||||||
LANGUAGE("language", OptionType.STRING, "en-gb"),
|
|
||||||
CHECK_FOR_UPDATES("check_for_updates", OptionType.BOOLEAN, true),
|
|
||||||
|
|
||||||
CLUSTER_ID("cluster_id", OptionType.STRING, ""),
|
|
||||||
DEBUG_LOGGING("debug_logging", OptionType.BOOLEAN, false),
|
|
||||||
|
|
||||||
DATABASE_HOST("database.credentials.host", OptionType.STRING, "localhost"),
|
|
||||||
DATABASE_PORT("database.credentials.port", OptionType.INTEGER, 3306),
|
|
||||||
DATABASE_NAME("database.credentials.database", OptionType.STRING, "HuskSync"),
|
|
||||||
DATABASE_USERNAME("database.credentials.username", OptionType.STRING, "root"),
|
|
||||||
DATABASE_PASSWORD("database.credentials.password", OptionType.STRING, "pa55w0rd"),
|
|
||||||
DATABASE_CONNECTION_PARAMS("database.credentials.params", OptionType.STRING, "?autoReconnect=true&useSSL=false"),
|
|
||||||
DATABASE_CONNECTION_POOL_MAX_SIZE("database.connection_pool.maximum_pool_size", OptionType.INTEGER, 10),
|
|
||||||
DATABASE_CONNECTION_POOL_MIN_IDLE("database.connection_pool.minimum_idle", OptionType.INTEGER, 10),
|
|
||||||
DATABASE_CONNECTION_POOL_MAX_LIFETIME("database.connection_pool.maximum_lifetime", OptionType.INTEGER, 1800000),
|
|
||||||
DATABASE_CONNECTION_POOL_KEEPALIVE("database.connection_pool.keepalive_time", OptionType.INTEGER, 0),
|
|
||||||
DATABASE_CONNECTION_POOL_TIMEOUT("database.connection_pool.connection_timeout", OptionType.INTEGER, 5000),
|
|
||||||
DATABASE_USERS_TABLE_NAME("database.table_names.users_table", OptionType.STRING, "husksync_users"),
|
|
||||||
DATABASE_USER_DATA_TABLE_NAME("database.table_names.user_data_table", OptionType.STRING, "husksync_user_data"),
|
|
||||||
|
|
||||||
REDIS_HOST("redis.credentials.host", OptionType.STRING, "localhost"),
|
|
||||||
REDIS_PORT("redis.credentials.port", OptionType.INTEGER, 6379),
|
|
||||||
REDIS_PASSWORD("redis.credentials.password", OptionType.STRING, ""),
|
|
||||||
REDIS_USE_SSL("redis.use_ssl", OptionType.BOOLEAN, false),
|
|
||||||
|
|
||||||
SYNCHRONIZATION_MAX_USER_DATA_SNAPSHOTS("synchronization.max_user_data_snapshots", OptionType.INTEGER, 5),
|
|
||||||
SYNCHRONIZATION_SAVE_ON_WORLD_SAVE("synchronization.save_on_world_save", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_COMPRESS_DATA("synchronization.compress_data", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_NETWORK_LATENCY_MILLISECONDS("synchronization.network_latency_milliseconds", OptionType.INTEGER, 500),
|
|
||||||
SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES("synchronization.save_dead_player_inventories", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_INVENTORIES("synchronization.features.inventories", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_ENDER_CHESTS("synchronization.features.ender_chests", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_HEALTH("synchronization.features.health", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_MAX_HEALTH("synchronization.features.max_health", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_HUNGER("synchronization.features.hunger", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_EXPERIENCE("synchronization.features.experience", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_POTION_EFFECTS("synchronization.features.potion_effects", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_ADVANCEMENTS("synchronization.features.advancements", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_GAME_MODE("synchronization.features.game_mode", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_STATISTICS("synchronization.features.statistics", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER("synchronization.features.persistent_data_container", OptionType.BOOLEAN, true),
|
|
||||||
SYNCHRONIZATION_SYNC_LOCATION("synchronization.features.location", OptionType.BOOLEAN, true);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The path in the config.yml file to the value
|
|
||||||
*/
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public final String configPath;
|
private static Map<String, String> getDefaults() {
|
||||||
|
return Map.ofEntries(Arrays.stream(values())
|
||||||
|
.map(TableName::toEntry)
|
||||||
|
.toArray(Map.Entry[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link OptionType} of this option
|
* 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),
|
||||||
|
MAX_HEALTH(true),
|
||||||
|
HUNGER(true),
|
||||||
|
EXPERIENCE(true),
|
||||||
|
POTION_EFFECTS(true),
|
||||||
|
ADVANCEMENTS(true),
|
||||||
|
GAME_MODE(true),
|
||||||
|
STATISTICS(true),
|
||||||
|
PERSISTENT_DATA_CONTAINER(false),
|
||||||
|
LOCKED_MAPS(false),
|
||||||
|
LOCATION(false);
|
||||||
|
|
||||||
|
private final boolean enabledByDefault;
|
||||||
|
|
||||||
|
SynchronizationFeature(boolean enabledByDefault) {
|
||||||
|
this.enabledByDefault = enabledByDefault;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public final OptionType optionType;
|
private Map.Entry<String, Boolean> toEntry() {
|
||||||
|
return Map.entry(name().toLowerCase(Locale.ENGLISH), enabledByDefault);
|
||||||
/**
|
|
||||||
* The default value of this option if not set in config
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
private final Object defaultValue;
|
|
||||||
|
|
||||||
ConfigOption(@NotNull String configPath, @NotNull OptionType optionType, @Nullable Object defaultValue) {
|
|
||||||
this.configPath = configPath;
|
|
||||||
this.optionType = optionType;
|
|
||||||
this.defaultValue = defaultValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigOption(@NotNull String configPath, @NotNull OptionType optionType) {
|
@SuppressWarnings("unchecked")
|
||||||
this.configPath = configPath;
|
@NotNull
|
||||||
this.optionType = optionType;
|
private static Map<String, Boolean> getDefaults() {
|
||||||
this.defaultValue = null;
|
return Map.ofEntries(Arrays.stream(values())
|
||||||
|
.map(SynchronizationFeature::toEntry)
|
||||||
|
.toArray(Map.Entry[]::new));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value at the path specified (or return default if set), as a string
|
* Represents events that HuskSync listens to, with a configurable priority listener
|
||||||
*
|
|
||||||
* @param config The {@link YamlDocument} config file
|
|
||||||
* @return the value defined in the config, as a string
|
|
||||||
*/
|
*/
|
||||||
public String getStringValue(@NotNull YamlDocument config) {
|
public enum EventType {
|
||||||
return defaultValue != null
|
JOIN_LISTENER(EventPriority.LOWEST),
|
||||||
? config.getString(configPath, (String) defaultValue)
|
QUIT_LISTENER(EventPriority.LOWEST),
|
||||||
: config.getString(configPath);
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value at the path specified (or return default if set), as a boolean
|
* Represents priorities for events that HuskSync listens to
|
||||||
*
|
|
||||||
* @param config The {@link YamlDocument} config file
|
|
||||||
* @return the value defined in the config, as a boolean
|
|
||||||
*/
|
*/
|
||||||
public boolean getBooleanValue(@NotNull YamlDocument config) {
|
public enum EventPriority {
|
||||||
return defaultValue != null
|
|
||||||
? config.getBoolean(configPath, (Boolean) defaultValue)
|
|
||||||
: config.getBoolean(configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value at the path specified (or return default if set), as a double
|
* Listens and processes the event execution last
|
||||||
*
|
|
||||||
* @param config The {@link YamlDocument} config file
|
|
||||||
* @return the value defined in the config, as a double
|
|
||||||
*/
|
*/
|
||||||
public double getDoubleValue(@NotNull YamlDocument config) {
|
HIGHEST,
|
||||||
return defaultValue != null
|
|
||||||
? config.getDouble(configPath, (Double) defaultValue)
|
|
||||||
: config.getDouble(configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value at the path specified (or return default if set), as a float
|
* Listens in between {@link #HIGHEST} and {@link #LOWEST} priority marked
|
||||||
*
|
|
||||||
* @param config The {@link YamlDocument} config file
|
|
||||||
* @return the value defined in the config, as a float
|
|
||||||
*/
|
*/
|
||||||
public float getFloatValue(@NotNull YamlDocument config) {
|
NORMAL,
|
||||||
return defaultValue != null
|
|
||||||
? config.getFloat(configPath, (Float) defaultValue)
|
|
||||||
: config.getFloat(configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the value at the path specified (or return default if set), as an int
|
* Listens and processes the event execution first
|
||||||
*
|
|
||||||
* @param config The {@link YamlDocument} config file
|
|
||||||
* @return the value defined in the config, as an int
|
|
||||||
*/
|
*/
|
||||||
public int getIntValue(@NotNull YamlDocument config) {
|
LOWEST
|
||||||
return defaultValue != null
|
|
||||||
? config.getInt(configPath, (Integer) defaultValue)
|
|
||||||
: config.getInt(configPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the value at the path specified (or return default if set), as a string {@link List}
|
|
||||||
*
|
|
||||||
* @param config The {@link YamlDocument} config file
|
|
||||||
* @return the value defined in the config, as a string {@link List}
|
|
||||||
*/
|
|
||||||
public List<String> getStringListValue(@NotNull YamlDocument config) {
|
|
||||||
return config.getStringList(configPath, new ArrayList<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the type of the object
|
|
||||||
*/
|
|
||||||
public enum OptionType {
|
|
||||||
BOOLEAN,
|
|
||||||
STRING,
|
|
||||||
DOUBLE,
|
|
||||||
FLOAT,
|
|
||||||
INTEGER,
|
|
||||||
STRING_LIST
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
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,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,10 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
|
||||||
import net.william278.husksync.api.BaseHuskSyncAPI;
|
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 net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Identifies the cause of a player data save.
|
* Identifies the cause of a player data save.
|
||||||
*
|
*
|
||||||
@@ -26,6 +42,12 @@ public enum DataSaveCause {
|
|||||||
* @since 2.0
|
* @since 2.0
|
||||||
*/
|
*/
|
||||||
WORLD_SAVE,
|
WORLD_SAVE,
|
||||||
|
/**
|
||||||
|
* Indicates data saved when the user died
|
||||||
|
*
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
DEATH,
|
||||||
/**
|
/**
|
||||||
* Indicates data saved when the server shut down
|
* Indicates data saved when the server shut down
|
||||||
*
|
*
|
||||||
@@ -94,4 +116,9 @@ public enum DataSaveCause {
|
|||||||
return UNKNOWN;
|
return UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getDisplayName() {
|
||||||
|
return Locales.truncate(name().toLowerCase(Locale.ENGLISH), 10);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
@@ -14,6 +27,16 @@ public class ItemData {
|
|||||||
@SerializedName("serialized_items")
|
@SerializedName("serialized_items")
|
||||||
public String serializedItems;
|
public String serializedItems;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an empty item data object, representing an empty inventory or Ender Chest
|
||||||
|
*
|
||||||
|
* @return an empty item data object
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static ItemData empty() {
|
||||||
|
return new ItemData("");
|
||||||
|
}
|
||||||
|
|
||||||
public ItemData(@NotNull final String serializedItems) {
|
public ItemData(@NotNull final String serializedItems) {
|
||||||
this.serializedItems = serializedItems;
|
this.serializedItems = serializedItems;
|
||||||
}
|
}
|
||||||
@@ -22,4 +45,13 @@ public class ItemData {
|
|||||||
protected 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,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
@@ -18,7 +31,7 @@ public class PersistentDataContainerData {
|
|||||||
@SerializedName("persistent_data_map")
|
@SerializedName("persistent_data_map")
|
||||||
protected Map<String, PersistentDataTag<?>> persistentDataMap;
|
protected Map<String, PersistentDataTag<?>> persistentDataMap;
|
||||||
|
|
||||||
public PersistentDataContainerData(@NotNull final Map<String, PersistentDataTag<?>> persistentDataMap) {
|
public PersistentDataContainerData(@NotNull Map<String, PersistentDataTag<?>> persistentDataMap) {
|
||||||
this.persistentDataMap = persistentDataMap;
|
this.persistentDataMap = persistentDataMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -26,17 +39,23 @@ public class PersistentDataContainerData {
|
|||||||
protected PersistentDataContainerData() {
|
protected PersistentDataContainerData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public <T> Optional<T> getTagValue(@NotNull String tagName, @NotNull Class<T> tagClass) {
|
||||||
public <T> Optional<T> getTagValue(@NotNull final String tagName, @NotNull Class<T> tagClass) {
|
if (!persistentDataMap.containsKey(tagName)) {
|
||||||
if (persistentDataMap.containsKey(tagName)) {
|
|
||||||
return Optional.of(tagClass.cast(persistentDataMap.get(tagName).value));
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<BukkitPersistentDataTagType> getTagType(@NotNull final String tagType) {
|
// 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<PersistentDataTagType> getTagType(@NotNull String tagType) {
|
||||||
if (persistentDataMap.containsKey(tagType)) {
|
if (persistentDataMap.containsKey(tagType)) {
|
||||||
return BukkitPersistentDataTagType.getDataType(persistentDataMap.get(tagType).type);
|
return PersistentDataTagType.getDataType(persistentDataMap.get(tagType).type);
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -19,16 +32,17 @@ public class PersistentDataTag<T> {
|
|||||||
*/
|
*/
|
||||||
public T value;
|
public T value;
|
||||||
|
|
||||||
public PersistentDataTag(@NotNull BukkitPersistentDataTagType type, @NotNull T value) {
|
public PersistentDataTag(@NotNull PersistentDataTagType type, @NotNull T value) {
|
||||||
this.type = type.name();
|
this.type = type.name();
|
||||||
this.value = value;
|
this.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
private PersistentDataTag() {
|
private PersistentDataTag() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<BukkitPersistentDataTagType> getType() {
|
public Optional<PersistentDataTagType> getType() {
|
||||||
return BukkitPersistentDataTagType.getDataType(type);
|
return PersistentDataTagType.getDataType(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
@@ -11,7 +24,7 @@ import java.util.Map;
|
|||||||
public class StatisticsData {
|
public class StatisticsData {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of untyped statistic names to their values
|
* Map of generic statistic names to their values
|
||||||
*/
|
*/
|
||||||
@SerializedName("untyped_statistics")
|
@SerializedName("untyped_statistics")
|
||||||
public Map<String, Integer> untypedStatistics;
|
public Map<String, Integer> untypedStatistics;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
@@ -8,29 +21,34 @@ import java.util.List;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags for setting {@link StatusData}, indicating which elements should be synced
|
* Flags for setting {@link StatusData}, indicating which elements should be synced
|
||||||
|
*
|
||||||
|
* @deprecated Use the more direct {@link Settings#getSynchronizationFeature(Settings.SynchronizationFeature)} instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public enum StatusDataFlag {
|
public enum StatusDataFlag {
|
||||||
|
|
||||||
SET_HEALTH(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HEALTH),
|
SET_HEALTH(Settings.SynchronizationFeature.HEALTH),
|
||||||
SET_MAX_HEALTH(Settings.ConfigOption.SYNCHRONIZATION_SYNC_MAX_HEALTH),
|
SET_MAX_HEALTH(Settings.SynchronizationFeature.MAX_HEALTH),
|
||||||
SET_HUNGER(Settings.ConfigOption.SYNCHRONIZATION_SYNC_HUNGER),
|
SET_HUNGER(Settings.SynchronizationFeature.HUNGER),
|
||||||
SET_EXPERIENCE(Settings.ConfigOption.SYNCHRONIZATION_SYNC_EXPERIENCE),
|
SET_EXPERIENCE(Settings.SynchronizationFeature.EXPERIENCE),
|
||||||
SET_GAME_MODE(Settings.ConfigOption.SYNCHRONIZATION_SYNC_GAME_MODE),
|
SET_GAME_MODE(Settings.SynchronizationFeature.GAME_MODE),
|
||||||
SET_FLYING(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION),
|
SET_FLYING(Settings.SynchronizationFeature.LOCATION),
|
||||||
SET_SELECTED_ITEM_SLOT(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES);
|
SET_SELECTED_ITEM_SLOT(Settings.SynchronizationFeature.INVENTORIES);
|
||||||
|
|
||||||
private final Settings.ConfigOption configOption;
|
private final Settings.SynchronizationFeature feature;
|
||||||
|
|
||||||
StatusDataFlag(@NotNull Settings.ConfigOption configOption) {
|
StatusDataFlag(@NotNull Settings.SynchronizationFeature feature) {
|
||||||
this.configOption = configOption;
|
this.feature = feature;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all status data flags
|
* Returns all status data flags
|
||||||
*
|
*
|
||||||
* @return all status data flags as a list
|
* @return all status data flags as a list
|
||||||
|
* @deprecated Use {@link Settings#getSynchronizationFeature(Settings.SynchronizationFeature)} instead
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static List<StatusDataFlag> getAll() {
|
public static List<StatusDataFlag> getAll() {
|
||||||
return Arrays.stream(StatusDataFlag.values()).toList();
|
return Arrays.stream(StatusDataFlag.values()).toList();
|
||||||
@@ -41,11 +59,13 @@ public enum StatusDataFlag {
|
|||||||
*
|
*
|
||||||
* @param settings the settings to use for determining which flags are enabled
|
* @param settings the settings to use for determining which flags are enabled
|
||||||
* @return all status data flags that are enabled for setting
|
* @return all status data flags that are enabled for setting
|
||||||
|
* @deprecated Use {@link Settings#getSynchronizationFeature(Settings.SynchronizationFeature)} instead
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public static List<StatusDataFlag> getFromSettings(@NotNull Settings settings) {
|
public static List<StatusDataFlag> getFromSettings(@NotNull Settings settings) {
|
||||||
return Arrays.stream(StatusDataFlag.values()).filter(
|
return Arrays.stream(StatusDataFlag.values()).filter(
|
||||||
flag -> settings.getBooleanValue(flag.configOption)).toList();
|
flag -> settings.getSynchronizationFeature(flag.feature)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/***
|
/**
|
||||||
* Stores data about a user
|
* Stores data about a user
|
||||||
*/
|
*/
|
||||||
public class UserData {
|
public class UserData {
|
||||||
@@ -15,72 +31,97 @@ public class UserData {
|
|||||||
* </p>
|
* </p>
|
||||||
* This value is to be incremented whenever the format changes.
|
* This value is to be incremented whenever the format changes.
|
||||||
*/
|
*/
|
||||||
public static final int CURRENT_FORMAT_VERSION = 2;
|
public static final int CURRENT_FORMAT_VERSION = 3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the user's status data, including health, food, etc.
|
* Stores the user's status data, including health, food, etc.
|
||||||
*/
|
*/
|
||||||
@SerializedName("status")
|
@SerializedName("status")
|
||||||
|
@Nullable
|
||||||
protected StatusData statusData;
|
protected StatusData statusData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the user's inventory contents
|
* Stores the user's inventory contents
|
||||||
*/
|
*/
|
||||||
@SerializedName("inventory")
|
@SerializedName("inventory")
|
||||||
|
@Nullable
|
||||||
protected ItemData inventoryData;
|
protected ItemData inventoryData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the user's ender chest contents
|
* Stores the user's ender chest contents
|
||||||
*/
|
*/
|
||||||
@SerializedName("ender_chest")
|
@SerializedName("ender_chest")
|
||||||
|
@Nullable
|
||||||
protected ItemData enderChestData;
|
protected ItemData enderChestData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store's the user's potion effects
|
* Store's the user's potion effects
|
||||||
*/
|
*/
|
||||||
@SerializedName("potion_effects")
|
@SerializedName("potion_effects")
|
||||||
|
@Nullable
|
||||||
protected PotionEffectData potionEffectData;
|
protected PotionEffectData potionEffectData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the set of this user's advancements
|
* Stores the set of this user's advancements
|
||||||
*/
|
*/
|
||||||
@SerializedName("advancements")
|
@SerializedName("advancements")
|
||||||
|
@Nullable
|
||||||
protected List<AdvancementData> advancementData;
|
protected List<AdvancementData> advancementData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the user's set of statistics
|
* Stores the user's set of statistics
|
||||||
*/
|
*/
|
||||||
@SerializedName("statistics")
|
@SerializedName("statistics")
|
||||||
|
@Nullable
|
||||||
protected StatisticsData statisticData;
|
protected StatisticsData statisticData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store's the user's world location and coordinates
|
* Store's the user's world location and coordinates
|
||||||
*/
|
*/
|
||||||
@SerializedName("location")
|
@SerializedName("location")
|
||||||
|
@Nullable
|
||||||
protected LocationData locationData;
|
protected LocationData locationData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the user's serialized persistent data container, which contains metadata keys applied by other plugins
|
* Stores the user's serialized persistent data container, which contains metadata keys applied by other plugins
|
||||||
*/
|
*/
|
||||||
@SerializedName("persistent_data_container")
|
@SerializedName("persistent_data_container")
|
||||||
|
@Nullable
|
||||||
protected PersistentDataContainerData persistentDataContainerData;
|
protected PersistentDataContainerData persistentDataContainerData;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the version of Minecraft this data was generated in
|
* Stores the version of Minecraft this data was generated in
|
||||||
*/
|
*/
|
||||||
@SerializedName("minecraft_version")
|
@SerializedName("minecraft_version")
|
||||||
|
@NotNull
|
||||||
protected String minecraftVersion;
|
protected String minecraftVersion;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores the version of the data format being used
|
* Stores the version of the data format being used
|
||||||
*/
|
*/
|
||||||
@SerializedName("format_version")
|
@SerializedName("format_version")
|
||||||
protected int formatVersion;
|
protected int formatVersion = CURRENT_FORMAT_VERSION;
|
||||||
|
|
||||||
public UserData(@NotNull StatusData statusData, @NotNull ItemData inventoryData,
|
/**
|
||||||
@NotNull ItemData enderChestData, @NotNull PotionEffectData potionEffectData,
|
* Create a new {@link UserData} object with the provided data
|
||||||
@NotNull List<AdvancementData> advancementData, @NotNull StatisticsData statisticData,
|
*
|
||||||
@NotNull LocationData locationData, @NotNull PersistentDataContainerData persistentDataContainerData,
|
* @param statusData the user's status data ({@link StatusData})
|
||||||
|
* @param inventoryData the user's inventory data ({@link ItemData})
|
||||||
|
* @param enderChestData the user's ender chest data ({@link ItemData})
|
||||||
|
* @param potionEffectData the user's potion effect data ({@link PotionEffectData})
|
||||||
|
* @param advancementData the user's advancement data ({@link AdvancementData})
|
||||||
|
* @param statisticData the user's statistic data ({@link StatisticsData})
|
||||||
|
* @param locationData the user's location data ({@link LocationData})
|
||||||
|
* @param persistentDataContainerData the user's persistent data container data ({@link PersistentDataContainerData})
|
||||||
|
* @param minecraftVersion the version of Minecraft this data was generated in (e.g. {@code "1.19.2"})
|
||||||
|
* @deprecated see {@link #builder(String)} or {@link #builder(Version)} to create a {@link UserDataBuilder}, which
|
||||||
|
* you can use to {@link UserDataBuilder#build()} a {@link UserData} instance with
|
||||||
|
*/
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
|
public UserData(@Nullable StatusData statusData, @Nullable ItemData inventoryData,
|
||||||
|
@Nullable ItemData enderChestData, @Nullable PotionEffectData potionEffectData,
|
||||||
|
@Nullable List<AdvancementData> advancementData, @Nullable StatisticsData statisticData,
|
||||||
|
@Nullable LocationData locationData, @Nullable PersistentDataContainerData persistentDataContainerData,
|
||||||
@NotNull String minecraftVersion) {
|
@NotNull String minecraftVersion) {
|
||||||
this.statusData = statusData;
|
this.statusData = statusData;
|
||||||
this.inventoryData = inventoryData;
|
this.inventoryData = inventoryData;
|
||||||
@@ -91,7 +132,6 @@ public class UserData {
|
|||||||
this.locationData = locationData;
|
this.locationData = locationData;
|
||||||
this.persistentDataContainerData = persistentDataContainerData;
|
this.persistentDataContainerData = persistentDataContainerData;
|
||||||
this.minecraftVersion = minecraftVersion;
|
this.minecraftVersion = minecraftVersion;
|
||||||
this.formatVersion = CURRENT_FORMAT_VERSION;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty constructor to facilitate json serialization
|
// Empty constructor to facilitate json serialization
|
||||||
@@ -99,45 +139,232 @@ public class UserData {
|
|||||||
protected UserData() {
|
protected UserData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link StatusData} from this user data
|
||||||
|
*
|
||||||
|
* @return the {@link StatusData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getStatus()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public StatusData getStatusData() {
|
public StatusData getStatusData() {
|
||||||
return statusData;
|
return statusData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link StatusData} from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing the {@link StatusData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<StatusData> getStatus() {
|
||||||
|
return Optional.ofNullable(statusData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link ItemData} representing the player's inventory from this user data
|
||||||
|
*
|
||||||
|
* @return the inventory {@link ItemData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getInventory()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public ItemData getInventoryData() {
|
public ItemData getInventoryData() {
|
||||||
return inventoryData;
|
return inventoryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link ItemData} representing the player's inventory from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing the inventory {@link ItemData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<ItemData> getInventory() {
|
||||||
|
return Optional.ofNullable(inventoryData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link ItemData} representing the player's ender chest from this user data
|
||||||
|
*
|
||||||
|
* @return the ender chest {@link ItemData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getEnderChest()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public ItemData getEnderChestData() {
|
public ItemData getEnderChestData() {
|
||||||
return enderChestData;
|
return enderChestData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link ItemData} representing the player's ender chest from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing the ender chest {@link ItemData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<ItemData> getEnderChest() {
|
||||||
|
return Optional.ofNullable(enderChestData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link PotionEffectData} representing player status effects from this user data
|
||||||
|
*
|
||||||
|
* @return the {@link PotionEffectData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getPotionEffects()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public PotionEffectData getPotionEffectsData() {
|
public PotionEffectData getPotionEffectsData() {
|
||||||
return potionEffectData;
|
return potionEffectData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link PotionEffectData} representing the player's potion effects from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing {@link PotionEffectData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<PotionEffectData> getPotionEffects() {
|
||||||
|
return Optional.ofNullable(potionEffectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of {@link AdvancementData} from this user data
|
||||||
|
*
|
||||||
|
* @return the {@link AdvancementData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getAdvancements()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public List<AdvancementData> getAdvancementData() {
|
public List<AdvancementData> getAdvancementData() {
|
||||||
return advancementData;
|
return advancementData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of {@link AdvancementData} representing the player's advancements from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing a {@link List} of {@link AdvancementData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<List<AdvancementData>> getAdvancements() {
|
||||||
|
return Optional.ofNullable(advancementData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link StatisticsData} representing player statistics from this user data
|
||||||
|
*
|
||||||
|
* @return the {@link StatisticsData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getStatistics()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public StatisticsData getStatisticsData() {
|
public StatisticsData getStatisticsData() {
|
||||||
return statisticData;
|
return statisticData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets {@link StatisticsData} representing player statistics from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing player {@link StatisticsData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<StatisticsData> getStatistics() {
|
||||||
|
return Optional.ofNullable(statisticData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link LocationData} representing the player location from this user data
|
||||||
|
*
|
||||||
|
* @return the inventory {@link LocationData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getLocation()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public LocationData getLocationData() {
|
public LocationData getLocationData() {
|
||||||
return locationData;
|
return locationData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets {@link LocationData} representing the player location from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing player {@link LocationData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<LocationData> getLocation() {
|
||||||
|
return Optional.ofNullable(locationData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the {@link PersistentDataContainerData} from this user data
|
||||||
|
*
|
||||||
|
* @return the {@link PersistentDataContainerData} of this user data
|
||||||
|
* @since 2.0
|
||||||
|
* @deprecated Use {@link #getPersistentDataContainer()}, which returns an optional instead
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@Deprecated(since = "2.1")
|
||||||
public PersistentDataContainerData getPersistentDataContainerData() {
|
public PersistentDataContainerData getPersistentDataContainerData() {
|
||||||
return persistentDataContainerData;
|
return persistentDataContainerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets {@link PersistentDataContainerData} from this user data
|
||||||
|
*
|
||||||
|
* @return an optional containing the player's {@link PersistentDataContainerData} if it is present in this user data
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
public Optional<PersistentDataContainerData> getPersistentDataContainer() {
|
||||||
|
return Optional.ofNullable(persistentDataContainerData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the version of Minecraft this data was generated in
|
||||||
|
*
|
||||||
|
* @return the version of Minecraft this data was generated in
|
||||||
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public String getMinecraftVersion() {
|
public String getMinecraftVersion() {
|
||||||
return minecraftVersion;
|
return minecraftVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the version of the data format being used
|
||||||
|
*
|
||||||
|
* @return the version of the data format being used
|
||||||
|
*/
|
||||||
public int getFormatVersion() {
|
public int getFormatVersion() {
|
||||||
return formatVersion;
|
return formatVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new {@link UserDataBuilder} for creating {@link UserData}
|
||||||
|
*
|
||||||
|
* @param minecraftVersion the version of Minecraft this data was generated in (e.g. {@code "1.19.2"})
|
||||||
|
* @return a UserData {@link UserDataBuilder} instance
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static UserDataBuilder builder(@NotNull String minecraftVersion) {
|
||||||
|
return new UserDataBuilder(minecraftVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a new {@link UserDataBuilder} for creating {@link UserData}
|
||||||
|
*
|
||||||
|
* @param minecraftVersion a {@link Version} object, representing the Minecraft version this data was generated in
|
||||||
|
* @return a UserData {@link UserDataBuilder} instance
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public static UserDataBuilder builder(@NotNull Version minecraftVersion) {
|
||||||
|
return builder(minecraftVersion.toStringWithoutMetadata());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A builder utility for creating {@link UserData} instances
|
||||||
|
*
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("UnusedReturnValue")
|
||||||
|
public class UserDataBuilder {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final UserData userData;
|
||||||
|
|
||||||
|
protected UserDataBuilder(@NotNull String minecraftVersion) {
|
||||||
|
this.userData = new UserData();
|
||||||
|
this.userData.minecraftVersion = minecraftVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link StatusData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param status the {@link StatusData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setStatus(@NotNull StatusData status) {
|
||||||
|
this.userData.statusData = status;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the inventory {@link ItemData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param inventoryData the inventory {@link ItemData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setInventory(@Nullable ItemData inventoryData) {
|
||||||
|
this.userData.inventoryData = inventoryData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the ender chest {@link ItemData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param enderChestData the ender chest {@link ItemData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setEnderChest(@Nullable ItemData enderChestData) {
|
||||||
|
this.userData.enderChestData = enderChestData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link List} of {@link ItemData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param potionEffectData the {@link List} of {@link ItemData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setPotionEffects(@Nullable PotionEffectData potionEffectData) {
|
||||||
|
this.userData.potionEffectData = potionEffectData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link List} of {@link ItemData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param advancementData the {@link List} of {@link ItemData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setAdvancements(@Nullable List<AdvancementData> advancementData) {
|
||||||
|
this.userData.advancementData = advancementData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link StatisticsData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param statisticData the {@link StatisticsData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setStatistics(@Nullable StatisticsData statisticData) {
|
||||||
|
this.userData.statisticData = statisticData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link LocationData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param locationData the {@link LocationData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setLocation(@Nullable LocationData locationData) {
|
||||||
|
this.userData.locationData = locationData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link PersistentDataContainerData} to this {@link UserData}
|
||||||
|
*
|
||||||
|
* @param persistentDataContainerData the {@link PersistentDataContainerData} to set
|
||||||
|
* @return this {@link UserDataBuilder}
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserDataBuilder setPersistentDataContainer(@Nullable PersistentDataContainerData persistentDataContainerData) {
|
||||||
|
this.userData.persistentDataContainerData = persistentDataContainerData;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build and get the {@link UserData} instance
|
||||||
|
*
|
||||||
|
* @return the {@link UserData} instance
|
||||||
|
* @since 2.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public UserData build() {
|
||||||
|
return this.userData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.william278.husksync.command.Permission;
|
||||||
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.UUID;
|
import java.util.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a uniquely versioned and timestamped snapshot of a user's data, including why it was saved.
|
* Represents a uniquely versioned and timestamped snapshot of a user's data, including why it was saved.
|
||||||
@@ -32,6 +49,82 @@ public record UserDataSnapshot(@NotNull UUID versionUUID, @NotNull Date versionT
|
|||||||
DataSaveCause.API, false, userData);
|
DataSaveCause.API, false, userData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a menu in chat to an {@link OnlineUser} about this {@link UserDataSnapshot} for a {@link User dataOwner}
|
||||||
|
*
|
||||||
|
* @param user The {@link OnlineUser} to display the menu to
|
||||||
|
* @param dataOwner The {@link User} whose data this snapshot captures a state of
|
||||||
|
* @param locales The {@link Locales} to use for displaying the menu
|
||||||
|
*/
|
||||||
|
public void displayDataOverview(@NotNull OnlineUser user, @NotNull User dataOwner, @NotNull Locales locales) {
|
||||||
|
// Title message, timestamp, owner and cause.
|
||||||
|
locales.getLocale("data_manager_title", versionUUID().toString().split("-")[0],
|
||||||
|
versionUUID().toString(), dataOwner.username, dataOwner.uuid.toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
locales.getLocale("data_manager_timestamp",
|
||||||
|
new SimpleDateFormat("MMM dd yyyy, HH:mm:ss.sss").format(versionTimestamp()))
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
if (pinned()) {
|
||||||
|
locales.getLocale("data_manager_pinned").ifPresent(user::sendMessage);
|
||||||
|
}
|
||||||
|
locales.getLocale("data_manager_cause", cause().name().toLowerCase(Locale.ENGLISH).replaceAll("_", " "))
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
|
||||||
|
// User status data, if present in the snapshot
|
||||||
|
userData().getStatus()
|
||||||
|
.flatMap(statusData -> locales.getLocale("data_manager_status",
|
||||||
|
Integer.toString((int) statusData.health),
|
||||||
|
Integer.toString((int) statusData.maxHealth),
|
||||||
|
Integer.toString(statusData.hunger),
|
||||||
|
Integer.toString(statusData.expLevel),
|
||||||
|
statusData.gameMode.toLowerCase(Locale.ENGLISH)))
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
|
||||||
|
// Advancement and statistic data, if both are present in the snapshot
|
||||||
|
userData().getAdvancements()
|
||||||
|
.flatMap(advancementData -> userData().getStatistics()
|
||||||
|
.flatMap(statisticsData -> locales.getLocale("data_manager_advancements_statistics",
|
||||||
|
Integer.toString(advancementData.size()),
|
||||||
|
generateAdvancementPreview(advancementData, locales),
|
||||||
|
String.format("%.2f", (((statisticsData.untypedStatistics.getOrDefault(
|
||||||
|
"PLAY_ONE_MINUTE", 0)) / 20d) / 60d) / 60d))))
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
|
||||||
|
if (user.hasPermission(Permission.COMMAND_INVENTORY.node)
|
||||||
|
&& user.hasPermission(Permission.COMMAND_ENDER_CHEST.node)) {
|
||||||
|
locales.getLocale("data_manager_item_buttons", dataOwner.username, versionUUID().toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
}
|
||||||
|
if (user.hasPermission(Permission.COMMAND_USER_DATA_MANAGE.node)) {
|
||||||
|
locales.getLocale("data_manager_management_buttons", dataOwner.username, versionUUID().toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
}
|
||||||
|
if (user.hasPermission(Permission.COMMAND_USER_DATA_DUMP.node)) {
|
||||||
|
locales.getLocale("data_manager_system_buttons", dataOwner.username, versionUUID().toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private String generateAdvancementPreview(@NotNull List<AdvancementData> advancementData, @NotNull Locales locales) {
|
||||||
|
final StringJoiner joiner = new StringJoiner("\n");
|
||||||
|
final List<AdvancementData> advancementsToPreview = advancementData.stream().filter(dataItem ->
|
||||||
|
!dataItem.key.startsWith("minecraft:recipes/")).toList();
|
||||||
|
final int PREVIEW_SIZE = 8;
|
||||||
|
for (int i = 0; i < advancementsToPreview.size(); i++) {
|
||||||
|
joiner.add(advancementsToPreview.get(i).key);
|
||||||
|
if (i >= PREVIEW_SIZE) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final int remainingAdvancements = advancementsToPreview.size() - PREVIEW_SIZE;
|
||||||
|
if (remainingAdvancements > 0) {
|
||||||
|
joiner.add(locales.getRawLocale("data_manager_advancements_preview_remaining",
|
||||||
|
Integer.toString(remainingAdvancements)).orElse("+" + remainingAdvancements + "…"));
|
||||||
|
}
|
||||||
|
return joiner.toString();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare UserData by creation timestamp
|
* Compare UserData by creation timestamp
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.database;
|
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.DataSaveCause;
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
import net.william278.husksync.data.UserDataSnapshot;
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
import net.william278.husksync.event.EventCannon;
|
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.ResourceReader;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -26,78 +37,10 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
*/
|
*/
|
||||||
public abstract class Database {
|
public abstract class Database {
|
||||||
|
|
||||||
/**
|
protected final HuskSync plugin;
|
||||||
* Name of the table that stores player information
|
|
||||||
*/
|
|
||||||
protected final String playerTableName;
|
|
||||||
|
|
||||||
/**
|
protected Database(@NotNull HuskSync plugin) {
|
||||||
* Name of the table that stores data
|
this.plugin = plugin;
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,7 +52,7 @@ public abstract class Database {
|
|||||||
*/
|
*/
|
||||||
@SuppressWarnings("SameParameterValue")
|
@SuppressWarnings("SameParameterValue")
|
||||||
protected final String[] getSchemaStatements(@NotNull String schemaFileName) throws IOException {
|
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(";");
|
.readAllBytes(), StandardCharsets.UTF_8)).split(";");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,8 +63,8 @@ public abstract class Database {
|
|||||||
* @return the formatted statement, with table placeholders replaced with the correct names
|
* @return the formatted statement, with table placeholders replaced with the correct names
|
||||||
*/
|
*/
|
||||||
protected final String formatStatementTables(@NotNull String sql) {
|
protected final String formatStatementTables(@NotNull String sql) {
|
||||||
return sql.replaceAll("%users_table%", playerTableName)
|
return sql.replaceAll("%users_table%", plugin.getSettings().getTableName(Settings.TableName.USERS))
|
||||||
.replaceAll("%user_data_table%", dataTableName);
|
.replaceAll("%user_data_table%", plugin.getSettings().getTableName(Settings.TableName.USER_DATA));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -184,10 +127,9 @@ public abstract class Database {
|
|||||||
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured.
|
* <b>(Internal)</b> Prune user data for a given user to the maximum value as configured.
|
||||||
*
|
*
|
||||||
* @param user The user to prune data for
|
* @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
|
* @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.
|
* Deletes a specific {@link UserDataSnapshot} entry for a user from the database, by its UUID.
|
||||||
|
|||||||
@@ -1,13 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.database;
|
package net.william278.husksync.database;
|
||||||
|
|
||||||
import com.zaxxer.hikari.HikariDataSource;
|
import com.zaxxer.hikari.HikariDataSource;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
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.DataSaveEvent;
|
||||||
import net.william278.husksync.event.EventCannon;
|
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.ResourceReader;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
@@ -40,9 +54,9 @@ public class MySqlDatabase extends Database {
|
|||||||
|
|
||||||
private final int hikariMaximumPoolSize;
|
private final int hikariMaximumPoolSize;
|
||||||
private final int hikariMinimumIdle;
|
private final int hikariMinimumIdle;
|
||||||
private final int hikariMaximumLifetime;
|
private final long hikariMaximumLifetime;
|
||||||
private final int hikariKeepAliveTime;
|
private final long hikariKeepAliveTime;
|
||||||
private final int hikariConnectionTimeOut;
|
private final long hikariConnectionTimeOut;
|
||||||
|
|
||||||
private static final String DATA_POOL_NAME = "HuskSyncHikariPool";
|
private static final String DATA_POOL_NAME = "HuskSyncHikariPool";
|
||||||
|
|
||||||
@@ -51,23 +65,20 @@ public class MySqlDatabase extends Database {
|
|||||||
*/
|
*/
|
||||||
private HikariDataSource connectionPool;
|
private HikariDataSource connectionPool;
|
||||||
|
|
||||||
public MySqlDatabase(@NotNull Settings settings, @NotNull ResourceReader resourceReader, @NotNull Logger logger,
|
public MySqlDatabase(@NotNull HuskSync plugin) {
|
||||||
@NotNull DataAdapter dataAdapter, @NotNull EventCannon eventCannon) {
|
super(plugin);
|
||||||
super(settings.getStringValue(Settings.ConfigOption.DATABASE_USERS_TABLE_NAME),
|
final Settings settings = plugin.getSettings();
|
||||||
settings.getStringValue(Settings.ConfigOption.DATABASE_USER_DATA_TABLE_NAME),
|
this.mySqlHost = settings.getMySqlHost();
|
||||||
Math.max(1, Math.min(20, settings.getIntegerValue(Settings.ConfigOption.SYNCHRONIZATION_MAX_USER_DATA_SNAPSHOTS))),
|
this.mySqlPort = settings.getMySqlPort();
|
||||||
resourceReader, dataAdapter, eventCannon, logger);
|
this.mySqlDatabaseName = settings.getMySqlDatabase();
|
||||||
this.mySqlHost = settings.getStringValue(Settings.ConfigOption.DATABASE_HOST);
|
this.mySqlUsername = settings.getMySqlUsername();
|
||||||
this.mySqlPort = settings.getIntegerValue(Settings.ConfigOption.DATABASE_PORT);
|
this.mySqlPassword = settings.getMySqlPassword();
|
||||||
this.mySqlDatabaseName = settings.getStringValue(Settings.ConfigOption.DATABASE_NAME);
|
this.mySqlConnectionParameters = settings.getMySqlConnectionParameters();
|
||||||
this.mySqlUsername = settings.getStringValue(Settings.ConfigOption.DATABASE_USERNAME);
|
this.hikariMaximumPoolSize = settings.getMySqlConnectionPoolSize();
|
||||||
this.mySqlPassword = settings.getStringValue(Settings.ConfigOption.DATABASE_PASSWORD);
|
this.hikariMinimumIdle = settings.getMySqlConnectionPoolIdle();
|
||||||
this.mySqlConnectionParameters = settings.getStringValue(Settings.ConfigOption.DATABASE_CONNECTION_PARAMS);
|
this.hikariMaximumLifetime = settings.getMySqlConnectionPoolLifetime();
|
||||||
this.hikariMaximumPoolSize = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MAX_SIZE);
|
this.hikariKeepAliveTime = settings.getMySqlConnectionPoolKeepAlive();
|
||||||
this.hikariMinimumIdle = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MIN_IDLE);
|
this.hikariConnectionTimeOut = settings.getMySqlConnectionPoolTimeout();
|
||||||
this.hikariMaximumLifetime = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_MAX_LIFETIME);
|
|
||||||
this.hikariKeepAliveTime = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_KEEPALIVE);
|
|
||||||
this.hikariConnectionTimeOut = settings.getIntegerValue(Settings.ConfigOption.DATABASE_CONNECTION_POOL_TIMEOUT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -111,10 +122,10 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IOException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage());
|
plugin.log(Level.SEVERE, "Failed to perform database setup: " + e.getMessage());
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
getLogger().log(Level.SEVERE, "An unhandled exception occurred during database setup!", e);
|
plugin.log(Level.SEVERE, "An unhandled exception occurred during database setup!", e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -135,9 +146,9 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.setString(2, existingUser.uuid.toString());
|
statement.setString(2, existingUser.uuid.toString());
|
||||||
statement.executeUpdate();
|
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) {
|
} 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 +164,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} 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 +187,7 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} 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();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -199,7 +210,7 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} 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();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -226,11 +237,11 @@ public class MySqlDatabase extends Database {
|
|||||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||||
resultSet.getBoolean("pinned"),
|
resultSet.getBoolean("pinned"),
|
||||||
getDataAdapter().fromBytes(dataByteArray)));
|
plugin.getDataAdapter().fromBytes(dataByteArray)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} 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();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -257,13 +268,13 @@ public class MySqlDatabase extends Database {
|
|||||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||||
resultSet.getBoolean("pinned"),
|
resultSet.getBoolean("pinned"),
|
||||||
getDataAdapter().fromBytes(dataByteArray));
|
plugin.getDataAdapter().fromBytes(dataByteArray));
|
||||||
retrievedData.add(data);
|
retrievedData.add(data);
|
||||||
}
|
}
|
||||||
return retrievedData;
|
return retrievedData;
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} 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;
|
return retrievedData;
|
||||||
});
|
});
|
||||||
@@ -291,22 +302,21 @@ public class MySqlDatabase extends Database {
|
|||||||
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
Date.from(resultSet.getTimestamp("timestamp").toInstant()),
|
||||||
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
DataSaveCause.getCauseByName(resultSet.getString("save_cause")),
|
||||||
resultSet.getBoolean("pinned"),
|
resultSet.getBoolean("pinned"),
|
||||||
getDataAdapter().fromBytes(dataByteArray)));
|
plugin.getDataAdapter().fromBytes(dataByteArray)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} 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();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected CompletableFuture<Void> rotateUserData(@NotNull User user) {
|
protected void rotateUserData(@NotNull User user) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
|
||||||
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
|
final List<UserDataSnapshot> unpinnedUserData = getUserData(user).join().stream()
|
||||||
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
|
.filter(dataSnapshot -> !dataSnapshot.pinned()).toList();
|
||||||
if (unpinnedUserData.size() > maxUserDataRecords) {
|
if (unpinnedUserData.size() > plugin.getSettings().getMaxUserDataSnapshots()) {
|
||||||
try (Connection connection = getConnection()) {
|
try (Connection connection = getConnection()) {
|
||||||
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
try (PreparedStatement statement = connection.prepareStatement(formatStatementTables("""
|
||||||
DELETE FROM `%user_data_table%`
|
DELETE FROM `%user_data_table%`
|
||||||
@@ -314,15 +324,14 @@ public class MySqlDatabase extends Database {
|
|||||||
AND `pinned` IS FALSE
|
AND `pinned` IS FALSE
|
||||||
ORDER BY `timestamp` ASC
|
ORDER BY `timestamp` ASC
|
||||||
LIMIT %entry_count%;""".replace("%entry_count%",
|
LIMIT %entry_count%;""".replace("%entry_count%",
|
||||||
Integer.toString(unpinnedUserData.size() - maxUserDataRecords))))) {
|
Integer.toString(unpinnedUserData.size() - plugin.getSettings().getMaxUserDataSnapshots()))))) {
|
||||||
statement.setString(1, user.uuid.toString());
|
statement.setString(1, user.uuid.toString());
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to prune user data from the database", e);
|
plugin.log(Level.SEVERE, "Failed to prune user data from the database", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -338,7 +347,7 @@ public class MySqlDatabase extends Database {
|
|||||||
return statement.executeUpdate() > 0;
|
return statement.executeUpdate() > 0;
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} 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;
|
return false;
|
||||||
});
|
});
|
||||||
@@ -348,7 +357,7 @@ public class MySqlDatabase extends Database {
|
|||||||
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData,
|
public CompletableFuture<Void> setUserData(@NotNull User user, @NotNull UserData userData,
|
||||||
@NotNull DataSaveCause saveCause) {
|
@NotNull DataSaveCause saveCause) {
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
final DataSaveEvent dataSaveEvent = (DataSaveEvent) getEventCannon().fireDataSaveEvent(user,
|
final DataSaveEvent dataSaveEvent = (DataSaveEvent) plugin.getEventCannon().fireDataSaveEvent(user,
|
||||||
userData, saveCause).join();
|
userData, saveCause).join();
|
||||||
if (!dataSaveEvent.isCancelled()) {
|
if (!dataSaveEvent.isCancelled()) {
|
||||||
final UserData finalData = dataSaveEvent.getUserData();
|
final UserData finalData = dataSaveEvent.getUserData();
|
||||||
@@ -360,14 +369,15 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.setString(1, user.uuid.toString());
|
statement.setString(1, user.uuid.toString());
|
||||||
statement.setString(2, saveCause.name());
|
statement.setString(2, saveCause.name());
|
||||||
statement.setBlob(3, new ByteArrayInputStream(
|
statement.setBlob(3, new ByteArrayInputStream(
|
||||||
getDataAdapter().toBytes(finalData)));
|
plugin.getDataAdapter().toBytes(finalData)));
|
||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException | DataAdaptionException e) {
|
} 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
|
@Override
|
||||||
@@ -384,7 +394,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} 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 +413,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} 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,7 +426,7 @@ public class MySqlDatabase extends Database {
|
|||||||
statement.executeUpdate(formatStatementTables("DELETE FROM `%user_data_table%`;"));
|
statement.executeUpdate(formatStatementTables("DELETE FROM `%user_data_table%`;"));
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
getLogger().log(Level.SEVERE, "Failed to wipe the database", e);
|
plugin.log(Level.SEVERE, "Failed to wipe the database", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,184 +0,0 @@
|
|||||||
package net.william278.husksync.editor;
|
|
||||||
|
|
||||||
import net.william278.husksync.command.Permission;
|
|
||||||
import net.william278.husksync.config.Locales;
|
|
||||||
import net.william278.husksync.data.AdvancementData;
|
|
||||||
import net.william278.husksync.data.ItemData;
|
|
||||||
import net.william278.husksync.data.UserDataSnapshot;
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
|
||||||
import net.william278.husksync.player.User;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.text.DateFormat;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides methods for displaying and editing user data
|
|
||||||
*/
|
|
||||||
public class DataEditor {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of currently open inventory and ender chest data editors
|
|
||||||
*/
|
|
||||||
@NotNull
|
|
||||||
protected final HashMap<UUID, ItemEditorMenu> openInventoryMenus;
|
|
||||||
|
|
||||||
private final Locales locales;
|
|
||||||
|
|
||||||
public DataEditor(@NotNull Locales locales) {
|
|
||||||
this.openInventoryMenus = new HashMap<>();
|
|
||||||
this.locales = locales;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open an inventory or ender chest editor menu
|
|
||||||
*
|
|
||||||
* @param user The online user to open the editor for
|
|
||||||
* @param itemEditorMenu The {@link ItemEditorMenu} to open
|
|
||||||
* @see ItemEditorMenu#createInventoryMenu(ItemData, User, OnlineUser, Locales, boolean)
|
|
||||||
* @see ItemEditorMenu#createEnderChestMenu(ItemData, User, OnlineUser, Locales, boolean)
|
|
||||||
*/
|
|
||||||
public CompletableFuture<ItemData> openItemEditorMenu(@NotNull OnlineUser user,
|
|
||||||
@NotNull ItemEditorMenu itemEditorMenu) {
|
|
||||||
this.openInventoryMenus.put(user.uuid, itemEditorMenu);
|
|
||||||
return itemEditorMenu.showInventory(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close an inventory or ender chest editor menu
|
|
||||||
*
|
|
||||||
* @param user The online user to close the editor for
|
|
||||||
* @param itemData the {@link ItemData} contained within the menu at the time of closing
|
|
||||||
*/
|
|
||||||
public void closeInventoryMenu(@NotNull OnlineUser user, @NotNull ItemData itemData) {
|
|
||||||
if (this.openInventoryMenus.containsKey(user.uuid)) {
|
|
||||||
this.openInventoryMenus.get(user.uuid).closeInventory(itemData);
|
|
||||||
}
|
|
||||||
this.openInventoryMenus.remove(user.uuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether edits to the inventory or ender chest menu are allowed
|
|
||||||
*
|
|
||||||
* @param user The online user with an inventory open to check
|
|
||||||
* @return {@code true} if edits to the inventory or ender chest menu are allowed; {@code false} otherwise, including if they don't have an inventory open
|
|
||||||
*/
|
|
||||||
public boolean cancelMenuEdit(@NotNull OnlineUser user) {
|
|
||||||
if (this.openInventoryMenus.containsKey(user.uuid)) {
|
|
||||||
return !this.openInventoryMenus.get(user.uuid).canEdit;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a chat menu detailing information about {@link UserDataSnapshot}
|
|
||||||
*
|
|
||||||
* @param user The online user to display the message to
|
|
||||||
* @param userData The {@link UserDataSnapshot} to display information about
|
|
||||||
* @param dataOwner The {@link User} who owns the {@link UserDataSnapshot}
|
|
||||||
*/
|
|
||||||
public void displayDataOverview(@NotNull OnlineUser user, @NotNull UserDataSnapshot userData,
|
|
||||||
@NotNull User dataOwner) {
|
|
||||||
locales.getLocale("data_manager_title",
|
|
||||||
userData.versionUUID().toString().split("-")[0],
|
|
||||||
userData.versionUUID().toString(),
|
|
||||||
dataOwner.username,
|
|
||||||
dataOwner.uuid.toString())
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
locales.getLocale("data_manager_timestamp",
|
|
||||||
new SimpleDateFormat("MMM dd yyyy, HH:mm:ss.sss").format(userData.versionTimestamp()))
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
if (userData.pinned()) {
|
|
||||||
locales.getLocale("data_manager_pinned").ifPresent(user::sendMessage);
|
|
||||||
}
|
|
||||||
locales.getLocale("data_manager_cause",
|
|
||||||
userData.cause().name().toLowerCase().replaceAll("_", " "))
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
locales.getLocale("data_manager_status",
|
|
||||||
Integer.toString((int) userData.userData().getStatusData().health),
|
|
||||||
Integer.toString((int) userData.userData().getStatusData().maxHealth),
|
|
||||||
Integer.toString(userData.userData().getStatusData().hunger),
|
|
||||||
Integer.toString(userData.userData().getStatusData().expLevel),
|
|
||||||
userData.userData().getStatusData().gameMode.toLowerCase())
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
locales.getLocale("data_manager_advancements_statistics",
|
|
||||||
Integer.toString(userData.userData().getAdvancementData().size()),
|
|
||||||
generateAdvancementPreview(userData.userData().getAdvancementData()),
|
|
||||||
String.format("%.2f", (((userData.userData().getStatisticsData().untypedStatistics.getOrDefault(
|
|
||||||
"PLAY_ONE_MINUTE", 0)) / 20d) / 60d) / 60d))
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
if (user.hasPermission(Permission.COMMAND_INVENTORY.node)
|
|
||||||
&& user.hasPermission(Permission.COMMAND_ENDER_CHEST.node)) {
|
|
||||||
locales.getLocale("data_manager_item_buttons",
|
|
||||||
dataOwner.username, userData.versionUUID().toString())
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
}
|
|
||||||
if (user.hasPermission(Permission.COMMAND_USER_DATA_MANAGE.node)) {
|
|
||||||
locales.getLocale("data_manager_management_buttons",
|
|
||||||
dataOwner.username, userData.versionUUID().toString())
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
private String generateAdvancementPreview(@NotNull List<AdvancementData> advancementData) {
|
|
||||||
final StringJoiner joiner = new StringJoiner("\n");
|
|
||||||
final List<AdvancementData> advancementsToPreview = advancementData.stream().filter(dataItem ->
|
|
||||||
!dataItem.key.startsWith("minecraft:recipes/")).toList();
|
|
||||||
final int PREVIEW_SIZE = 8;
|
|
||||||
for (int i = 0; i < advancementsToPreview.size(); i++) {
|
|
||||||
joiner.add(advancementsToPreview.get(i).key);
|
|
||||||
if (i >= PREVIEW_SIZE) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final int remainingAdvancements = advancementsToPreview.size() - PREVIEW_SIZE;
|
|
||||||
if (remainingAdvancements > 0) {
|
|
||||||
joiner.add(locales.getRawLocale("data_manager_advancements_preview_remaining",
|
|
||||||
Integer.toString(remainingAdvancements)).orElse("+" + remainingAdvancements + "…"));
|
|
||||||
}
|
|
||||||
return joiner.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Display a chat list detailing a player's saved list of {@link UserDataSnapshot}
|
|
||||||
*
|
|
||||||
* @param user The online user to display the message to
|
|
||||||
* @param userDataList The list of {@link UserDataSnapshot} to display
|
|
||||||
* @param dataOwner The {@link User} who owns the {@link UserDataSnapshot}
|
|
||||||
*/
|
|
||||||
public void displayDataList(@NotNull OnlineUser user, @NotNull List<UserDataSnapshot> userDataList,
|
|
||||||
@NotNull User dataOwner) {
|
|
||||||
locales.getLocale("data_list_title",
|
|
||||||
dataOwner.username, dataOwner.uuid.toString())
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
|
|
||||||
final String[] numberedIcons = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳".split("");
|
|
||||||
for (int i = 0; i < Math.min(20, userDataList.size()); i++) {
|
|
||||||
final UserDataSnapshot userData = userDataList.get(i);
|
|
||||||
locales.getLocale("data_list_item",
|
|
||||||
numberedIcons[i],
|
|
||||||
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
|
|
||||||
.format(userData.versionTimestamp()),
|
|
||||||
userData.versionUUID().toString().split("-")[0],
|
|
||||||
userData.versionUUID().toString(),
|
|
||||||
userData.cause().name().toLowerCase().replaceAll("_", " "),
|
|
||||||
dataOwner.username,
|
|
||||||
userData.pinned() ? "※" : " ")
|
|
||||||
.ifPresent(user::sendMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether the user has an inventory editor menu open
|
|
||||||
*
|
|
||||||
* @param user {@link OnlineUser} to check
|
|
||||||
* @return {@code true} if the user has an inventory editor open; {@code false} otherwise
|
|
||||||
*/
|
|
||||||
public Optional<ItemEditorMenu> getEditingInventoryData(@NotNull OnlineUser user) {
|
|
||||||
return this.openInventoryMenus.containsKey(user.uuid) ? Optional.of(this.openInventoryMenus.get(user.uuid))
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
package net.william278.husksync.editor;
|
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
|
||||||
import net.william278.husksync.command.Permission;
|
|
||||||
import net.william278.husksync.config.Locales;
|
|
||||||
import net.william278.husksync.data.ItemData;
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
|
||||||
import net.william278.husksync.player.User;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
public class ItemEditorMenu {
|
|
||||||
|
|
||||||
public final ItemData itemData;
|
|
||||||
public final ItemEditorMenuType itemEditorMenuType;
|
|
||||||
public final MineDown menuTitle;
|
|
||||||
public final boolean canEdit;
|
|
||||||
|
|
||||||
private CompletableFuture<ItemData> inventoryDataCompletableFuture;
|
|
||||||
|
|
||||||
private ItemEditorMenu(@NotNull ItemData itemData, ItemEditorMenuType itemEditorMenuType,
|
|
||||||
@NotNull MineDown menuTitle, boolean canEdit) {
|
|
||||||
this.itemData = itemData;
|
|
||||||
this.menuTitle = menuTitle;
|
|
||||||
this.itemEditorMenuType = itemEditorMenuType;
|
|
||||||
this.canEdit = canEdit;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<ItemData> showInventory(@NotNull OnlineUser user) {
|
|
||||||
inventoryDataCompletableFuture = new CompletableFuture<>();
|
|
||||||
user.showMenu(this);
|
|
||||||
return inventoryDataCompletableFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void closeInventory(@NotNull ItemData itemData) {
|
|
||||||
inventoryDataCompletableFuture.complete(itemData);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ItemEditorMenu createInventoryMenu(@NotNull ItemData itemData, @NotNull User dataOwner,
|
|
||||||
@NotNull OnlineUser viewer, @NotNull Locales locales,
|
|
||||||
boolean canEdit) {
|
|
||||||
return new ItemEditorMenu(itemData, ItemEditorMenuType.INVENTORY_VIEWER,
|
|
||||||
locales.getLocale(ItemEditorMenuType.INVENTORY_VIEWER.localeKey, dataOwner.username).orElse(new MineDown("")),
|
|
||||||
viewer.hasPermission(Permission.COMMAND_INVENTORY_EDIT.node) && canEdit);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ItemEditorMenu createEnderChestMenu(@NotNull ItemData itemData, @NotNull User dataOwner,
|
|
||||||
@NotNull OnlineUser viewer, @NotNull Locales locales,
|
|
||||||
boolean canEdit) {
|
|
||||||
return new ItemEditorMenu(itemData, ItemEditorMenuType.ENDER_CHEST_VIEWER,
|
|
||||||
locales.getLocale(ItemEditorMenuType.ENDER_CHEST_VIEWER.localeKey, dataOwner.username).orElse(new MineDown("")),
|
|
||||||
viewer.hasPermission(Permission.COMMAND_ENDER_CHEST_EDIT.node) && canEdit);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package net.william278.husksync.editor;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
public enum ItemEditorMenuType {
|
|
||||||
INVENTORY_VIEWER(45, "inventory_viewer_menu_title"),
|
|
||||||
ENDER_CHEST_VIEWER(27, "ender_chest_viewer_menu_title");
|
|
||||||
|
|
||||||
public final int slotCount;
|
|
||||||
final String localeKey;
|
|
||||||
|
|
||||||
ItemEditorMenuType(int slotCount, @NotNull String localeKey) {
|
|
||||||
this.slotCount = slotCount;
|
|
||||||
this.localeKey = localeKey;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
public interface CancellableEvent extends Event {
|
public interface CancellableEvent extends Event {
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.event;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
public interface SyncCompleteEvent extends PlayerEvent {
|
public interface SyncCompleteEvent extends PlayerEvent {
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.hook;
|
package net.william278.husksync.hook;
|
||||||
|
|
||||||
import com.djrapitops.plan.extension.CallEvents;
|
import com.djrapitops.plan.extension.CallEvents;
|
||||||
@@ -10,13 +23,13 @@ import com.djrapitops.plan.extension.icon.Family;
|
|||||||
import com.djrapitops.plan.extension.icon.Icon;
|
import com.djrapitops.plan.extension.icon.Icon;
|
||||||
import com.djrapitops.plan.extension.table.Table;
|
import com.djrapitops.plan.extension.table.Table;
|
||||||
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
import com.djrapitops.plan.extension.table.TableColumnFormat;
|
||||||
import net.william278.husksync.data.StatusData;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.UserDataSnapshot;
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
import net.william278.husksync.database.Database;
|
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
@@ -44,14 +57,14 @@ import java.util.regex.Pattern;
|
|||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public class PlanDataExtension implements DataExtension {
|
public class PlanDataExtension implements DataExtension {
|
||||||
|
|
||||||
private Database database;
|
private HuskSync plugin;
|
||||||
|
|
||||||
private static final String UNKNOWN_STRING = "N/A";
|
private static final String UNKNOWN_STRING = "N/A";
|
||||||
|
|
||||||
private static final String PINNED_HTML_STRING = "📍 ";
|
private static final String PINNED_HTML_STRING = "📍 ";
|
||||||
|
|
||||||
protected PlanDataExtension(@NotNull Database database) {
|
protected PlanDataExtension(@NotNull HuskSync plugin) {
|
||||||
this.database = database;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PlanDataExtension() {
|
protected PlanDataExtension() {
|
||||||
@@ -67,9 +80,9 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
|
|
||||||
private CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull UUID uuid) {
|
private CompletableFuture<Optional<UserDataSnapshot>> getCurrentUserData(@NotNull UUID uuid) {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
final Optional<User> optionalUser = database.getUser(uuid).join();
|
final Optional<User> optionalUser = plugin.getDatabase().getUser(uuid).join();
|
||||||
if (optionalUser.isPresent()) {
|
if (optionalUser.isPresent()) {
|
||||||
return database.getCurrentUserData(optionalUser.get()).join();
|
return plugin.getDatabase().getCurrentUserData(optionalUser.get()).join();
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
});
|
});
|
||||||
@@ -114,8 +127,8 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
)
|
)
|
||||||
@Tab("Current Status")
|
@Tab("Current Status")
|
||||||
public String getCurrentDataId(@NotNull UUID uuid) {
|
public String getCurrentDataId(@NotNull UUID uuid) {
|
||||||
return getCurrentUserData(uuid).join().map(
|
return getCurrentUserData(uuid).join()
|
||||||
versionedUserData -> versionedUserData.versionUUID().toString()
|
.map(versionedUserData -> versionedUserData.versionUUID().toString()
|
||||||
.split(Pattern.quote("-"))[0])
|
.split(Pattern.quote("-"))[0])
|
||||||
.orElse(UNKNOWN_STRING);
|
.orElse(UNKNOWN_STRING);
|
||||||
}
|
}
|
||||||
@@ -130,11 +143,9 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
)
|
)
|
||||||
@Tab("Current Status")
|
@Tab("Current Status")
|
||||||
public String getHealth(@NotNull UUID uuid) {
|
public String getHealth(@NotNull UUID uuid) {
|
||||||
return getCurrentUserData(uuid).join().map(
|
return getCurrentUserData(uuid).join()
|
||||||
versionedUserData -> {
|
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
|
||||||
final StatusData statusData = versionedUserData.userData().getStatusData();
|
.map(statusData -> (int) statusData.health + "/" + (int) statusData.maxHealth)
|
||||||
return (int) statusData.health + "/" + (int) statusData.maxHealth;
|
|
||||||
})
|
|
||||||
.orElse(UNKNOWN_STRING);
|
.orElse(UNKNOWN_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,8 +159,9 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
)
|
)
|
||||||
@Tab("Current Status")
|
@Tab("Current Status")
|
||||||
public long getHunger(@NotNull UUID uuid) {
|
public long getHunger(@NotNull UUID uuid) {
|
||||||
return getCurrentUserData(uuid).join().map(
|
return getCurrentUserData(uuid).join()
|
||||||
versionedUserData -> (long) versionedUserData.userData().getStatusData().hunger)
|
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
|
||||||
|
.map(statusData -> (long) statusData.hunger)
|
||||||
.orElse(0L);
|
.orElse(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,8 +175,9 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
)
|
)
|
||||||
@Tab("Current Status")
|
@Tab("Current Status")
|
||||||
public long getExperienceLevel(@NotNull UUID uuid) {
|
public long getExperienceLevel(@NotNull UUID uuid) {
|
||||||
return getCurrentUserData(uuid).join().map(
|
return getCurrentUserData(uuid).join()
|
||||||
versionedUserData -> (long) versionedUserData.userData().getStatusData().expLevel)
|
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
|
||||||
|
.map(statusData -> (long) statusData.expLevel)
|
||||||
.orElse(0L);
|
.orElse(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,8 +191,9 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
)
|
)
|
||||||
@Tab("Current Status")
|
@Tab("Current Status")
|
||||||
public String getGameMode(@NotNull UUID uuid) {
|
public String getGameMode(@NotNull UUID uuid) {
|
||||||
return getCurrentUserData(uuid).join().map(
|
return getCurrentUserData(uuid).join()
|
||||||
versionedUserData -> versionedUserData.userData().getStatusData().gameMode.toLowerCase())
|
.flatMap(versionedUserData -> versionedUserData.userData().getStatus())
|
||||||
|
.map(status -> status.gameMode)
|
||||||
.orElse(UNKNOWN_STRING);
|
.orElse(UNKNOWN_STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,8 +206,9 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
)
|
)
|
||||||
@Tab("Current Status")
|
@Tab("Current Status")
|
||||||
public long getAdvancementsCompleted(@NotNull UUID playerUUID) {
|
public long getAdvancementsCompleted(@NotNull UUID playerUUID) {
|
||||||
return getCurrentUserData(playerUUID).join().map(
|
return getCurrentUserData(playerUUID).join()
|
||||||
versionedUserData -> (long) versionedUserData.userData().getAdvancementData().size())
|
.flatMap(versionedUserData -> versionedUserData.userData().getAdvancements())
|
||||||
|
.map(advancementsData -> (long) advancementsData.size())
|
||||||
.orElse(0L);
|
.orElse(0L);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,17 +216,17 @@ public class PlanDataExtension implements DataExtension {
|
|||||||
@TableProvider(tableColor = Color.LIGHT_BLUE)
|
@TableProvider(tableColor = Color.LIGHT_BLUE)
|
||||||
@Tab("Data Snapshots")
|
@Tab("Data Snapshots")
|
||||||
public Table getDataSnapshots(@NotNull UUID playerUUID) {
|
public Table getDataSnapshots(@NotNull UUID playerUUID) {
|
||||||
Table.Factory dataSnapshotsTable = Table.builder()
|
final Table.Factory dataSnapshotsTable = Table.builder()
|
||||||
.columnOne("Time", new Icon(Family.SOLID, "clock", Color.NONE))
|
.columnOne("Time", new Icon(Family.SOLID, "clock", Color.NONE))
|
||||||
.columnOneFormat(TableColumnFormat.DATE_SECOND)
|
.columnOneFormat(TableColumnFormat.DATE_SECOND)
|
||||||
.columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE))
|
.columnTwo("ID", new Icon(Family.SOLID, "bolt", Color.NONE))
|
||||||
.columnThree("Cause", new Icon(Family.SOLID, "flag", Color.NONE))
|
.columnThree("Cause", new Icon(Family.SOLID, "flag", Color.NONE))
|
||||||
.columnFour("Pinned", new Icon(Family.SOLID, "thumbtack", Color.NONE));
|
.columnFour("Pinned", new Icon(Family.SOLID, "thumbtack", Color.NONE));
|
||||||
database.getUser(playerUUID).join().ifPresent(user ->
|
plugin.getDatabase().getUser(playerUUID).join().ifPresent(user ->
|
||||||
database.getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow(
|
plugin.getDatabase().getUserData(user).join().forEach(versionedUserData -> dataSnapshotsTable.addRow(
|
||||||
versionedUserData.versionTimestamp().getTime(),
|
versionedUserData.versionTimestamp().getTime(),
|
||||||
versionedUserData.versionUUID().toString().split("-")[0],
|
versionedUserData.versionUUID().toString().split("-")[0],
|
||||||
versionedUserData.cause().name().toLowerCase().replaceAll("_", " "),
|
versionedUserData.cause().name().toLowerCase(Locale.ENGLISH).replaceAll("_", " "),
|
||||||
versionedUserData.pinned() ? PINNED_HTML_STRING + "Pinned" : "Unpinned"
|
versionedUserData.pinned() ? PINNED_HTML_STRING + "Pinned" : "Unpinned"
|
||||||
)));
|
)));
|
||||||
return dataSnapshotsTable.build();
|
return dataSnapshotsTable.build();
|
||||||
|
|||||||
@@ -1,21 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.hook;
|
package net.william278.husksync.hook;
|
||||||
|
|
||||||
import com.djrapitops.plan.capability.CapabilityService;
|
import com.djrapitops.plan.capability.CapabilityService;
|
||||||
import com.djrapitops.plan.extension.ExtensionService;
|
import com.djrapitops.plan.extension.ExtensionService;
|
||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class PlanHook {
|
public class PlanHook {
|
||||||
|
|
||||||
private final Database database;
|
private final HuskSync plugin;
|
||||||
private final Logger logger;
|
|
||||||
|
|
||||||
public PlanHook(@NotNull Database database, @NotNull Logger logger) {
|
public PlanHook(@NotNull HuskSync plugin) {
|
||||||
this.database = database;
|
this.plugin = plugin;
|
||||||
this.logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void hookIntoPlan() {
|
public void hookIntoPlan() {
|
||||||
@@ -33,13 +43,9 @@ public class PlanHook {
|
|||||||
|
|
||||||
private void registerDataExtension() {
|
private void registerDataExtension() {
|
||||||
try {
|
try {
|
||||||
ExtensionService.getInstance().register(new PlanDataExtension(database));
|
ExtensionService.getInstance().register(new PlanDataExtension(plugin));
|
||||||
} catch (IllegalStateException planIsNotEnabled) {
|
} catch (IllegalStateException | IllegalArgumentException e) {
|
||||||
logger.log(Level.SEVERE, "Plan extension hook failed to register. Plan is not enabled.", planIsNotEnabled);
|
plugin.log(Level.WARNING, "Failed to register Plan data extension: " + e.getMessage(), e);
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.listener;
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
|
||||||
import net.william278.husksync.data.ItemData;
|
|
||||||
import net.william278.husksync.data.DataSaveCause;
|
import net.william278.husksync.data.DataSaveCause;
|
||||||
|
import net.william278.husksync.data.ItemData;
|
||||||
import net.william278.husksync.player.OnlineUser;
|
import net.william278.husksync.player.OnlineUser;
|
||||||
import net.william278.husksync.editor.ItemEditorMenuType;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -53,13 +65,17 @@ public abstract class EventListener {
|
|||||||
* @param user The {@link OnlineUser} to handle
|
* @param user The {@link OnlineUser} to handle
|
||||||
*/
|
*/
|
||||||
protected final void handlePlayerJoin(@NotNull OnlineUser user) {
|
protected final void handlePlayerJoin(@NotNull OnlineUser user) {
|
||||||
|
if (user.isNpc()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lockedPlayers.add(user.uuid);
|
lockedPlayers.add(user.uuid);
|
||||||
CompletableFuture.runAsync(() -> {
|
CompletableFuture.runAsync(() -> {
|
||||||
try {
|
try {
|
||||||
// Hold reading data for the network latency threshold, to ensure the source server has set the redis key
|
// 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().getIntegerValue(Settings.ConfigOption.SYNCHRONIZATION_NETWORK_LATENCY_MILLISECONDS)));
|
Thread.sleep(Math.max(0, plugin.getSettings().getNetworkLatencyMilliseconds()));
|
||||||
} catch (InterruptedException e) {
|
} 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 {
|
} finally {
|
||||||
plugin.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> {
|
plugin.getRedisManager().getUserServerSwitch(user).thenAccept(changingServers -> {
|
||||||
if (!changingServers) {
|
if (!changingServers) {
|
||||||
@@ -85,8 +101,7 @@ public abstract class EventListener {
|
|||||||
}
|
}
|
||||||
plugin.getRedisManager().getUserData(user).thenAccept(redisUserData ->
|
plugin.getRedisManager().getUserData(user).thenAccept(redisUserData ->
|
||||||
redisUserData.ifPresent(redisData -> {
|
redisUserData.ifPresent(redisData -> {
|
||||||
user.setData(redisData, plugin.getSettings(), plugin.getEventCannon(),
|
user.setData(redisData, plugin)
|
||||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion())
|
|
||||||
.thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)).join();
|
.thenAccept(succeeded -> handleSynchronisationCompletion(user, succeeded)).join();
|
||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
})).join();
|
})).join();
|
||||||
@@ -108,8 +123,7 @@ public abstract class EventListener {
|
|||||||
private CompletableFuture<Boolean> setUserFromDatabase(@NotNull OnlineUser user) {
|
private CompletableFuture<Boolean> setUserFromDatabase(@NotNull OnlineUser user) {
|
||||||
return plugin.getDatabase().getCurrentUserData(user).thenApply(databaseUserData -> {
|
return plugin.getDatabase().getCurrentUserData(user).thenApply(databaseUserData -> {
|
||||||
if (databaseUserData.isPresent()) {
|
if (databaseUserData.isPresent()) {
|
||||||
return user.setData(databaseUserData.get().userData(), plugin.getSettings(), plugin.getEventCannon(),
|
return user.setData(databaseUserData.get().userData(), plugin).join();
|
||||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).join();
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
@@ -123,9 +137,17 @@ public abstract class EventListener {
|
|||||||
*/
|
*/
|
||||||
private void handleSynchronisationCompletion(@NotNull OnlineUser user, boolean succeeded) {
|
private void handleSynchronisationCompletion(@NotNull OnlineUser user, boolean succeeded) {
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
plugin.getLocales().getLocale("synchronisation_complete").ifPresent(user::sendActionBar);
|
switch (plugin.getSettings().getNotificationDisplaySlot()) {
|
||||||
lockedPlayers.remove(user.uuid);
|
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();
|
plugin.getDatabase().ensureUser(user).join();
|
||||||
|
lockedPlayers.remove(user.uuid);
|
||||||
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||||
} else {
|
} else {
|
||||||
plugin.getLocales().getLocale("synchronisation_failed")
|
plugin.getLocales().getLocale("synchronisation_failed")
|
||||||
@@ -145,19 +167,19 @@ public abstract class EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Don't sync players awaiting synchronization
|
// Don't sync players awaiting synchronization
|
||||||
if (lockedPlayers.contains(user.uuid)) {
|
if (lockedPlayers.contains(user.uuid) || user.isNpc()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle asynchronous disconnection
|
// Handle asynchronous disconnection
|
||||||
lockedPlayers.add(user.uuid);
|
lockedPlayers.add(user.uuid);
|
||||||
CompletableFuture.runAsync(() -> plugin.getRedisManager().setUserServerSwitch(user)
|
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()
|
optionalUserData -> optionalUserData.ifPresent(userData -> plugin.getRedisManager()
|
||||||
.setUserData(user, userData).thenRun(() -> plugin.getDatabase()
|
.setUserData(user, userData).thenRun(() -> plugin.getDatabase()
|
||||||
.setUserData(user, userData, DataSaveCause.DISCONNECT)))))
|
.setUserData(user, userData, DataSaveCause.DISCONNECT)))))
|
||||||
.thenRun(() -> lockedPlayers.remove(user.uuid)).exceptionally(throwable -> {
|
.exceptionally(throwable -> {
|
||||||
plugin.getLoggingAdapter().log(Level.SEVERE,
|
plugin.log(Level.SEVERE,
|
||||||
"An exception occurred handling a player disconnection");
|
"An exception occurred handling a player disconnection");
|
||||||
throwable.printStackTrace();
|
throwable.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
@@ -165,50 +187,48 @@ public abstract class EventListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Asynchronously handles a world save event
|
* Handles the saving of data when the world save event is fired
|
||||||
*
|
*
|
||||||
* @param usersInWorld a list of users in the world that is being saved
|
* @param usersInWorld a list of users in the world that is being saved
|
||||||
*/
|
*/
|
||||||
protected final void handleAsyncWorldSave(@NotNull List<OnlineUser> usersInWorld) {
|
protected final void saveOnWorldSave(@NotNull List<OnlineUser> usersInWorld) {
|
||||||
if (disabling || !plugin.getSettings().getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_ON_WORLD_SAVE)) {
|
if (disabling || !plugin.getSettings().doSaveOnWorldSave()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
usersInWorld.forEach(user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
usersInWorld.stream()
|
||||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.WORLD_SAVE).join()));
|
.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))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an inventory menu closing
|
* Handles the saving of data when a player dies
|
||||||
*
|
*
|
||||||
* @param user The user who closed the menu
|
* @param user The user who died
|
||||||
* @param menuInventory Serialized {@link ItemData} containing the inventory contents
|
* @param drops The items that this user would have dropped
|
||||||
* @implNote The size of the serialized {@link ItemData} array is determined by the {@link ItemEditorMenuType} of the closed inventory
|
|
||||||
*/
|
*/
|
||||||
protected final void handleMenuClose(@NotNull OnlineUser user, @NotNull ItemData menuInventory) {
|
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull ItemData drops) {
|
||||||
if (disabling) {
|
if (disabling || !plugin.getSettings().doSaveOnDeath() || lockedPlayers.contains(user.uuid) || user.isNpc()
|
||||||
|
|| (!plugin.getSettings().doSaveEmptyDropsOnDeath() && drops.isEmpty())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.getDataEditor().closeInventoryMenu(user, menuInventory);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
user.getUserData(plugin)
|
||||||
* Determine whether an inventory click should be cancelled
|
.thenAccept(data -> data.ifPresent(userData -> {
|
||||||
*
|
userData.getInventory().orElse(ItemData.empty()).serializedItems = drops.serializedItems;
|
||||||
* @param user {@link OnlineUser} performing the event
|
plugin.getDatabase().setUserData(user, userData, DataSaveCause.DEATH);
|
||||||
* @return Whether the event should be cancelled
|
}));
|
||||||
*/
|
|
||||||
protected final boolean cancelInventoryClick(@NotNull OnlineUser user) {
|
|
||||||
return plugin.getDataEditor().cancelMenuEdit(user) || cancelPlayerEvent(user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine whether a player event should be cancelled
|
* 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
|
* @return Whether the event should be cancelled
|
||||||
*/
|
*/
|
||||||
protected final boolean cancelPlayerEvent(@NotNull OnlineUser user) {
|
protected final boolean cancelPlayerEvent(@NotNull UUID userUuid) {
|
||||||
return disabling || lockedPlayers.contains(user.uuid);
|
return disabling || lockedPlayers.contains(userUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -217,12 +237,23 @@ public abstract class EventListener {
|
|||||||
public final void handlePluginDisable() {
|
public final void handlePluginDisable() {
|
||||||
disabling = true;
|
disabling = true;
|
||||||
|
|
||||||
plugin.getOnlineUsers().stream().filter(user -> !lockedPlayers.contains(user.uuid)).forEach(
|
// Save data for all online users
|
||||||
user -> user.getUserData(plugin.getLoggingAdapter(), plugin.getSettings()).join().ifPresent(
|
plugin.getOnlineUsers().stream()
|
||||||
userData -> plugin.getDatabase().setUserData(user, userData, DataSaveCause.SERVER_SHUTDOWN).join()));
|
.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.getDatabase().close();
|
||||||
plugin.getRedisManager().close();
|
plugin.getRedisManager().close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public final Set<UUID> getLockedPlayers() {
|
||||||
|
return this.lockedPlayers;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.migrator;
|
package net.william278.husksync.migrator;
|
||||||
|
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
|||||||
@@ -1,19 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.player;
|
package net.william278.husksync.player;
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
|
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.config.Settings;
|
||||||
import net.william278.husksync.data.*;
|
import net.william278.husksync.data.*;
|
||||||
import net.william278.husksync.editor.ItemEditorMenu;
|
|
||||||
import net.william278.husksync.event.EventCannon;
|
|
||||||
import net.william278.husksync.event.PreSyncEvent;
|
import net.william278.husksync.event.PreSyncEvent;
|
||||||
import net.william278.husksync.util.Logger;
|
|
||||||
import net.william278.husksync.util.Version;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
@@ -39,9 +50,29 @@ public abstract class OnlineUser extends User {
|
|||||||
* @param statusData the player's {@link StatusData}
|
* @param statusData the player's {@link StatusData}
|
||||||
* @param statusDataFlags the flags to use for setting the status data
|
* @param statusDataFlags the flags to use for setting the status data
|
||||||
* @return a future returning void when complete
|
* @return a future returning void when complete
|
||||||
|
* @deprecated Use {@link #setStatus(StatusData, Settings)} instead
|
||||||
*/
|
*/
|
||||||
public abstract CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
|
@Deprecated(since = "2.1")
|
||||||
@NotNull List<StatusDataFlag> statusDataFlags);
|
public final CompletableFuture<Void> setStatus(@NotNull StatusData statusData,
|
||||||
|
@NotNull List<StatusDataFlag> statusDataFlags) {
|
||||||
|
final Settings settings = new Settings();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the player's {@link StatusData}
|
||||||
|
*
|
||||||
|
* @param statusData the player's {@link StatusData}
|
||||||
|
* @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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the player's inventory {@link ItemData} contents
|
* Get the player's inventory {@link ItemData} contents
|
||||||
@@ -165,70 +196,20 @@ public abstract class OnlineUser extends User {
|
|||||||
public abstract Version getMinecraftVersion();
|
public abstract Version getMinecraftVersion();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set {@link UserData} to a player
|
* Get the player's adventure {@link Audience}
|
||||||
*
|
*
|
||||||
* @param data The data to set
|
* @return the player's {@link Audience}
|
||||||
* @param settings Plugin settings, for determining what needs setting
|
|
||||||
* @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
|
||||||
@NotNull EventCannon eventCannon, @NotNull Logger logger,
|
public abstract Audience getAudience();
|
||||||
@NotNull Version serverMinecraftVersion) {
|
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
|
||||||
// Prevent synchronising user data from newer versions of Minecraft
|
|
||||||
if (Version.minecraftVersion(data.getMinecraftVersion()).compareTo(serverMinecraftVersion) > 0) {
|
|
||||||
logger.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 + ").");
|
|
||||||
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 +
|
|
||||||
" 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 UserData finalData = preSyncEvent.getUserData();
|
|
||||||
final List<CompletableFuture<Void>> dataSetOperations = new ArrayList<>() {{
|
|
||||||
if (!isOffline() && !preSyncEvent.isCancelled()) {
|
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_INVENTORIES)) {
|
|
||||||
add(setInventory(finalData.getInventoryData()));
|
|
||||||
}
|
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ENDER_CHESTS)) {
|
|
||||||
add(setEnderChest(finalData.getEnderChestData()));
|
|
||||||
}
|
|
||||||
add(setStatus(finalData.getStatusData(), StatusDataFlag.getFromSettings(settings)));
|
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_POTION_EFFECTS)) {
|
|
||||||
add(setPotionEffects(finalData.getPotionEffectsData()));
|
|
||||||
}
|
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_ADVANCEMENTS)) {
|
|
||||||
add(setAdvancements(finalData.getAdvancementData()));
|
|
||||||
}
|
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_STATISTICS)) {
|
|
||||||
add(setStatistics(finalData.getStatisticsData()));
|
|
||||||
}
|
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_LOCATION)) {
|
|
||||||
add(setLocation(finalData.getLocationData()));
|
|
||||||
}
|
|
||||||
if (settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SYNC_PERSISTENT_DATA_CONTAINER)) {
|
|
||||||
add(setPersistentDataContainer(finalData.getPersistentDataContainerData()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
// Apply operations in parallel, join when complete
|
|
||||||
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() + ")");
|
|
||||||
exception.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}).join();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message to this player
|
||||||
|
*
|
||||||
|
* @param component the {@link Component} message to send
|
||||||
|
*/
|
||||||
|
public void sendMessage(@NotNull Component component) {
|
||||||
|
getAudience().sendMessage(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -236,14 +217,33 @@ public abstract class OnlineUser extends User {
|
|||||||
*
|
*
|
||||||
* @param mineDown the parsed {@link MineDown} to send
|
* @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
|
* Dispatch a MineDown-formatted action bar message to this player
|
||||||
*
|
*
|
||||||
* @param mineDown the parsed {@link MineDown} to send
|
* @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
|
* Returns if the player has the permission node
|
||||||
@@ -254,11 +254,17 @@ public abstract class OnlineUser extends User {
|
|||||||
public abstract boolean hasPermission(@NotNull String node);
|
public abstract boolean hasPermission(@NotNull String node);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the player a {@link ItemEditorMenu} GUI
|
* Show a GUI chest menu to the player, containing the given {@link ItemData}
|
||||||
*
|
*
|
||||||
* @param menu The {@link ItemEditorMenu} interface to show
|
* @param itemData Item data to be shown in the GUI
|
||||||
|
* @param editable If the player should be able to remove, replace and move around the items
|
||||||
|
* @param minimumRows The minimum number of rows to show in the chest menu
|
||||||
|
* @param title The title of the chest menu, as a {@link MineDown} locale
|
||||||
|
* @return A future returning the {@link ItemData} in the chest menu when the player closes it
|
||||||
|
* @since 2.1
|
||||||
*/
|
*/
|
||||||
public abstract void showMenu(@NotNull ItemEditorMenu menu);
|
public abstract CompletableFuture<Optional<ItemData>> showMenu(@NotNull ItemData itemData, boolean editable,
|
||||||
|
int minimumRows, @NotNull MineDown title);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the player is dead
|
* Returns true if the player is dead
|
||||||
@@ -268,28 +274,144 @@ public abstract class OnlineUser extends User {
|
|||||||
public abstract boolean isDead();
|
public abstract boolean isDead();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the player's current {@link UserData} in an {@link Optional}
|
* Apply {@link UserData} to a player, updating their inventory, status, statistics, etc. as per the config.
|
||||||
* <p>
|
* <p>
|
||||||
* If the {@code SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES} ConfigOption has been set,
|
* This will only set data that is enabled as per the enabled settings in the config file.
|
||||||
* the user's inventory will only be returned if they are alive
|
* Data present in the {@link UserData} object, but not enabled to be set in the config, will be ignored.
|
||||||
|
*
|
||||||
|
* @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 HuskSync plugin) {
|
||||||
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
|
// Prevent synchronising user data from newer versions of Minecraft
|
||||||
|
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 (" + plugin.getMinecraftVersion() + ").");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Prevent synchronising user data from newer versions of the plugin
|
||||||
|
if (data.getFormatVersion() > UserData.CURRENT_FORMAT_VERSION) {
|
||||||
|
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) 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)));
|
||||||
|
}
|
||||||
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.ENDER_CHESTS)) {
|
||||||
|
finalData.getEnderChest().ifPresent(itemData -> add(setEnderChest(itemData)));
|
||||||
|
}
|
||||||
|
finalData.getStatus().ifPresent(statusData -> add(setStatus(statusData, settings)));
|
||||||
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.POTION_EFFECTS)) {
|
||||||
|
finalData.getPotionEffects().ifPresent(potionEffectData -> add(setPotionEffects(potionEffectData)));
|
||||||
|
}
|
||||||
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.ADVANCEMENTS)) {
|
||||||
|
finalData.getAdvancements().ifPresent(advancementData -> add(setAdvancements(advancementData)));
|
||||||
|
}
|
||||||
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.STATISTICS)) {
|
||||||
|
finalData.getStatistics().ifPresent(statisticData -> add(setStatistics(statisticData)));
|
||||||
|
}
|
||||||
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.LOCATION)) {
|
||||||
|
finalData.getLocation().ifPresent(locationData -> add(setLocation(locationData)));
|
||||||
|
}
|
||||||
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.PERSISTENT_DATA_CONTAINER)) {
|
||||||
|
finalData.getPersistentDataContainer().ifPresent(persistentDataContainerData ->
|
||||||
|
add(setPersistentDataContainer(persistentDataContainerData)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
// Apply operations in parallel, join when complete
|
||||||
|
return CompletableFuture.allOf(dataSetOperations.toArray(new CompletableFuture[0])).thenApply(unused -> true)
|
||||||
|
.exceptionally(exception -> {
|
||||||
|
// Handle synchronisation exceptions
|
||||||
|
plugin.log(Level.SEVERE, "Failed to set data for player " + username + " (" + exception.getMessage() + ")");
|
||||||
|
exception.printStackTrace();
|
||||||
|
return false;
|
||||||
|
}).join();
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the player's current {@link UserData} in an {@link Optional}.
|
||||||
|
* <p>
|
||||||
|
* Since v2.1, this method will respect the data synchronisation settings; user data will only be as big as the
|
||||||
|
* enabled synchronisation values set in the config file
|
||||||
|
* <p>
|
||||||
|
* Also note that if the {@code SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES} ConfigOption has been set,
|
||||||
|
* the user's inventory will only be returned if the player is alive.
|
||||||
* <p>
|
* <p>
|
||||||
* If the user data could not be returned due to an exception, the optional will return empty
|
* 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
|
* @param plugin The plugin instance
|
||||||
* @return the player's current {@link UserData} in an optional; empty if an exception occurs
|
|
||||||
*/
|
*/
|
||||||
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull Logger logger, @NotNull Settings settings) {
|
public final CompletableFuture<Optional<UserData>> getUserData(@NotNull HuskSync plugin) {
|
||||||
return CompletableFuture.supplyAsync(() -> Optional.of(new UserData(getStatus().join(),
|
return CompletableFuture.supplyAsync(() -> {
|
||||||
(settings.getBooleanValue(Settings.ConfigOption.SYNCHRONIZATION_SAVE_DEAD_PLAYER_INVENTORIES)
|
final UserDataBuilder builder = UserData.builder(getMinecraftVersion());
|
||||||
? getInventory().join() : (isDead() ? new ItemData("") : getInventory().join())),
|
final List<CompletableFuture<Void>> dataGetOperations = new ArrayList<>() {{
|
||||||
getEnderChest().join(), getPotionEffects().join(), getAdvancements().join(),
|
if (!isOffline()) {
|
||||||
getStatistics().join(), getLocation().join(), getPersistentDataContainer().join(),
|
final Settings settings = plugin.getSettings();
|
||||||
getMinecraftVersion().toString())))
|
if (settings.getSynchronizationFeature(Settings.SynchronizationFeature.INVENTORIES)) {
|
||||||
.exceptionally(exception -> {
|
if (isDead() && settings.isSynchroniseDeadPlayersChangingServer()) {
|
||||||
logger.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
|
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 -> {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to get user data from online player " + username + " (" + exception.getMessage() + ")");
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
return Optional.empty();
|
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,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.player;
|
package net.william278.husksync.player;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|||||||
@@ -1,7 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.redis;
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public enum RedisKeyType {
|
public enum RedisKeyType {
|
||||||
CACHE(60 * 60 * 24),
|
CACHE(60 * 60 * 24),
|
||||||
DATA_UPDATE(10),
|
DATA_UPDATE(10),
|
||||||
@@ -15,6 +30,6 @@ public enum RedisKeyType {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String getKeyPrefix() {
|
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,7 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.redis;
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings;
|
|
||||||
import net.william278.husksync.data.UserData;
|
import net.william278.husksync.data.UserData;
|
||||||
import net.william278.husksync.player.User;
|
import net.william278.husksync.player.User;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -19,7 +32,7 @@ import java.util.concurrent.CompletableFuture;
|
|||||||
/**
|
/**
|
||||||
* Manages the connection to the Redis server, handling the caching of user data
|
* 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 final String KEY_NAMESPACE = "husksync:";
|
||||||
protected static String clusterId = "";
|
protected static String clusterId = "";
|
||||||
@@ -33,13 +46,13 @@ public class RedisManager {
|
|||||||
|
|
||||||
public RedisManager(@NotNull HuskSync plugin) {
|
public RedisManager(@NotNull HuskSync plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
clusterId = plugin.getSettings().getStringValue(Settings.ConfigOption.CLUSTER_ID);
|
clusterId = plugin.getSettings().getClusterId();
|
||||||
|
|
||||||
// Set redis credentials
|
// Set redis credentials
|
||||||
this.redisHost = plugin.getSettings().getStringValue(Settings.ConfigOption.REDIS_HOST);
|
this.redisHost = plugin.getSettings().getRedisHost();
|
||||||
this.redisPort = plugin.getSettings().getIntegerValue(Settings.ConfigOption.REDIS_PORT);
|
this.redisPort = plugin.getSettings().getRedisPort();
|
||||||
this.redisPassword = plugin.getSettings().getStringValue(Settings.ConfigOption.REDIS_PASSWORD);
|
this.redisPassword = plugin.getSettings().getRedisPassword();
|
||||||
this.redisUseSsl = plugin.getSettings().getBooleanValue(Settings.ConfigOption.REDIS_USE_SSL);
|
this.redisUseSsl = plugin.getSettings().isRedisUseSsl();
|
||||||
|
|
||||||
// Configure the jedis pool
|
// Configure the jedis pool
|
||||||
this.jedisPoolConfig = new JedisPoolConfig();
|
this.jedisPoolConfig = new JedisPoolConfig();
|
||||||
@@ -53,8 +66,7 @@ public class RedisManager {
|
|||||||
*
|
*
|
||||||
* @return a future returning void when complete
|
* @return a future returning void when complete
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Boolean> initialize() {
|
public boolean initialize() {
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
|
||||||
if (redisPassword.isBlank()) {
|
if (redisPassword.isBlank()) {
|
||||||
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisUseSsl);
|
jedisPool = new JedisPool(jedisPoolConfig, redisHost, redisPort, 0, redisUseSsl);
|
||||||
} else {
|
} else {
|
||||||
@@ -67,7 +79,6 @@ public class RedisManager {
|
|||||||
}
|
}
|
||||||
CompletableFuture.runAsync(this::subscribe);
|
CompletableFuture.runAsync(this::subscribe);
|
||||||
return true;
|
return true;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void subscribe() {
|
private void subscribe() {
|
||||||
@@ -75,19 +86,33 @@ public class RedisManager {
|
|||||||
new Jedis(redisHost, redisPort, DefaultJedisClientConfig.builder()
|
new Jedis(redisHost, redisPort, DefaultJedisClientConfig.builder()
|
||||||
.password(redisPassword).timeoutMillis(0).ssl(redisUseSsl).build())) {
|
.password(redisPassword).timeoutMillis(0).ssl(redisUseSsl).build())) {
|
||||||
subscriber.connect();
|
subscriber.connect();
|
||||||
subscriber.subscribe(new JedisPubSub() {
|
subscriber.subscribe(this, Arrays.stream(RedisMessageType.values())
|
||||||
|
.map(RedisMessageType::getMessageChannel)
|
||||||
|
.toArray(String[]::new));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(@NotNull String channel, @NotNull String message) {
|
public void onMessage(@NotNull String channel, @NotNull String message) {
|
||||||
RedisMessageType.getTypeFromChannel(channel).ifPresent(messageType -> {
|
final RedisMessageType messageType = RedisMessageType.getTypeFromChannel(channel).orElse(null);
|
||||||
if (messageType == RedisMessageType.UPDATE_USER_DATA) {
|
if (messageType != RedisMessageType.UPDATE_USER_DATA) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final RedisMessage redisMessage = RedisMessage.fromJson(message);
|
final RedisMessage redisMessage = RedisMessage.fromJson(message);
|
||||||
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
|
plugin.getOnlineUser(redisMessage.targetUserUuid).ifPresent(user -> {
|
||||||
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
|
final UserData userData = plugin.getDataAdapter().fromBytes(redisMessage.data);
|
||||||
user.setData(userData, plugin.getSettings(), plugin.getEventCannon(),
|
user.setData(userData, plugin).thenAccept(succeeded -> {
|
||||||
plugin.getLoggingAdapter(), plugin.getMinecraftVersion()).thenAccept(succeeded -> {
|
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
plugin.getLocales().getLocale("data_update_complete")
|
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);
|
.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);
|
plugin.getEventCannon().fireSyncCompleteEvent(user);
|
||||||
} else {
|
} else {
|
||||||
plugin.getLocales().getLocale("data_update_failed")
|
plugin.getLocales().getLocale("data_update_failed")
|
||||||
@@ -96,11 +121,6 @@ public class RedisManager {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}, Arrays.stream(RedisMessageType.values()).map(RedisMessageType::getMessageChannel).toArray(String[]::new));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void sendMessage(@NotNull String channel, @NotNull String message) {
|
protected void sendMessage(@NotNull String channel, @NotNull String message) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
@@ -130,7 +150,9 @@ public class RedisManager {
|
|||||||
jedis.setex(getKey(RedisKeyType.DATA_UPDATE, user.uuid),
|
jedis.setex(getKey(RedisKeyType.DATA_UPDATE, user.uuid),
|
||||||
RedisKeyType.DATA_UPDATE.timeToLive,
|
RedisKeyType.DATA_UPDATE.timeToLive,
|
||||||
plugin.getDataAdapter().toBytes(userData));
|
plugin.getDataAdapter().toBytes(userData));
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
|
|
||||||
|
// Debug logging
|
||||||
|
plugin.debug("[" + user.username + "] Set " + RedisKeyType.DATA_UPDATE.name()
|
||||||
+ " key to redis at: " +
|
+ " key to redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
}
|
}
|
||||||
@@ -146,7 +168,7 @@ public class RedisManager {
|
|||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
|
jedis.setex(getKey(RedisKeyType.SERVER_SWITCH, user.uuid),
|
||||||
RedisKeyType.SERVER_SWITCH.timeToLive, new byte[0]);
|
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: " +
|
+ " key to redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -167,12 +189,12 @@ public class RedisManager {
|
|||||||
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.uuid);
|
||||||
final byte[] dataByteArray = jedis.get(key);
|
final byte[] dataByteArray = jedis.get(key);
|
||||||
if (dataByteArray == null) {
|
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: " +
|
RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
|
plugin.debug("[" + user.username + "] Successfully read "
|
||||||
+ RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
+ RedisKeyType.DATA_UPDATE.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
|
|
||||||
@@ -194,12 +216,12 @@ public class RedisManager {
|
|||||||
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
|
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.uuid);
|
||||||
final byte[] readData = jedis.get(key);
|
final byte[] readData = jedis.get(key);
|
||||||
if (readData == null) {
|
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: " +
|
RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
plugin.getLoggingAdapter().debug("[" + user.username + "] Successfully read "
|
plugin.debug("[" + user.username + "] Successfully read "
|
||||||
+ RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
+ RedisKeyType.SERVER_SWITCH.name() + " key from redis at: " +
|
||||||
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,16 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.redis;
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
|||||||
@@ -1,8 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.redis;
|
package net.william278.husksync.redis;
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public enum RedisMessageType {
|
public enum RedisMessageType {
|
||||||
@@ -11,8 +25,8 @@ public enum RedisMessageType {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public String getMessageChannel() {
|
public String getMessageChannel() {
|
||||||
return RedisManager.KEY_NAMESPACE.toLowerCase() + ":" + RedisManager.clusterId.toLowerCase()
|
return RedisManager.KEY_NAMESPACE.toLowerCase(Locale.ENGLISH) + ":" + RedisManager.clusterId.toLowerCase(Locale.ENGLISH)
|
||||||
+ ":" + name().toLowerCase();
|
+ ":" + name().toLowerCase(Locale.ENGLISH);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<RedisMessageType> getTypeFromChannel(@NotNull String messageChannel) {
|
public static Optional<RedisMessageType> getTypeFromChannel(@NotNull String messageChannel) {
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.util;
|
||||||
|
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for dumping {@link UserDataSnapshot}s to a file or as a paste on the web
|
||||||
|
*/
|
||||||
|
public class DataDumper {
|
||||||
|
|
||||||
|
private static final String LOGS_SITE_ENDPOINT = "https://api.mclo.gs/1/log";
|
||||||
|
|
||||||
|
private final HuskSync plugin;
|
||||||
|
private final UserDataSnapshot dataSnapshot;
|
||||||
|
private final User user;
|
||||||
|
|
||||||
|
private DataDumper(@NotNull UserDataSnapshot dataSnapshot,
|
||||||
|
@NotNull User user, @NotNull HuskSync implementor) {
|
||||||
|
this.dataSnapshot = dataSnapshot;
|
||||||
|
this.user = user;
|
||||||
|
this.plugin = implementor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a {@link DataDumper} of the given {@link UserDataSnapshot}
|
||||||
|
*
|
||||||
|
* @param dataSnapshot The {@link UserDataSnapshot} to dump
|
||||||
|
* @param user The {@link User} whose data is being dumped
|
||||||
|
* @param plugin The implementing {@link HuskSync} plugin
|
||||||
|
* @return A {@link DataDumper} for the given {@link UserDataSnapshot}
|
||||||
|
*/
|
||||||
|
public static DataDumper create(@NotNull UserDataSnapshot dataSnapshot,
|
||||||
|
@NotNull User user, @NotNull HuskSync plugin) {
|
||||||
|
return new DataDumper(dataSnapshot, user, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dumps the data snapshot to a string
|
||||||
|
*
|
||||||
|
* @return the data snapshot as a string
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public String toString() {
|
||||||
|
return plugin.getDataAdapter().toJson(dataSnapshot.userData(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String toWeb() {
|
||||||
|
try {
|
||||||
|
final URL url = new URL(LOGS_SITE_ENDPOINT);
|
||||||
|
final HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||||
|
connection.setRequestMethod("POST");
|
||||||
|
connection.setDoOutput(true);
|
||||||
|
|
||||||
|
// Dispatch the request
|
||||||
|
final byte[] messageBody = getWebContentField().getBytes(StandardCharsets.UTF_8);
|
||||||
|
final int messageLength = messageBody.length;
|
||||||
|
connection.setFixedLengthStreamingMode(messageLength);
|
||||||
|
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||||
|
connection.connect();
|
||||||
|
try (OutputStream messageOutputStream = connection.getOutputStream()) {
|
||||||
|
messageOutputStream.write(messageBody);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the response
|
||||||
|
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||||
|
// Get the body as a json
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||||
|
final StringBuilder response = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
response.append(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the response as json
|
||||||
|
final JsonObject responseJson = JsonParser.parseString(response.toString()).getAsJsonObject();
|
||||||
|
if (responseJson.has("url")) {
|
||||||
|
return responseJson.get("url").getAsString();
|
||||||
|
}
|
||||||
|
return "(Failed to get URL from response)";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return "(Failed to upload to logs site, got: " + connection.getResponseCode() + ")";
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.log(Level.SEVERE, "Failed to upload data to logs site", e);
|
||||||
|
}
|
||||||
|
return "(Failed to upload to logs site)";
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private String getWebContentField() {
|
||||||
|
return "content=" + URLEncoder.encode(toString(), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump the {@link UserDataSnapshot} to a file and return the file name
|
||||||
|
*
|
||||||
|
* @return the relative path of the file the data was dumped to
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public String toFile() throws IOException {
|
||||||
|
final File filePath = getFilePath();
|
||||||
|
|
||||||
|
// Write the data from #getString to the file using a writer
|
||||||
|
try (final FileWriter writer = new FileWriter(filePath, StandardCharsets.UTF_8, false)) {
|
||||||
|
writer.write(toString());
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IOException("Failed to write data to file", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return "~/plugins/HuskSync/dumps/" + filePath.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the file path to dump the data to
|
||||||
|
*
|
||||||
|
* @return the file path
|
||||||
|
* @throws IOException if the prerequisite dumps parent folder could not be created
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private File getFilePath() throws IOException {
|
||||||
|
return new File(getDumpsFolder(), getFileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the folder to dump the data to and create it if it does not exist
|
||||||
|
*
|
||||||
|
* @return the dumps folder
|
||||||
|
* @throws IOException if the folder could not be created
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private File getDumpsFolder() throws IOException {
|
||||||
|
final File dumpsFolder = new File(plugin.getDataFolder(), "dumps");
|
||||||
|
if (!dumpsFolder.exists()) {
|
||||||
|
if (!dumpsFolder.mkdirs()) {
|
||||||
|
throw new IOException("Failed to create user data dumps folder");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dumpsFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the name of the file to dump the data snapshot to
|
||||||
|
*
|
||||||
|
* @return the file name
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private String getFileName() {
|
||||||
|
return new StringJoiner("_")
|
||||||
|
.add(user.username)
|
||||||
|
.add(new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss").format(dataSnapshot.versionTimestamp()))
|
||||||
|
.add(dataSnapshot.cause().name().toLowerCase(Locale.ENGLISH))
|
||||||
|
.add(dataSnapshot.versionUUID().toString().split("-")[0])
|
||||||
|
+ ".json";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync by William278. Do not redistribute!
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.util;
|
||||||
|
|
||||||
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.data.UserDataSnapshot;
|
||||||
|
import net.william278.husksync.player.OnlineUser;
|
||||||
|
import net.william278.husksync.player.User;
|
||||||
|
import net.william278.paginedown.PaginatedList;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a chat-viewable paginated list of {@link UserDataSnapshot}s
|
||||||
|
*/
|
||||||
|
public class DataSnapshotList {
|
||||||
|
|
||||||
|
// Used for displaying number ordering next to snapshots in the list
|
||||||
|
private static final String[] CIRCLED_NUMBER_ICONS = "①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳".split("");
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final PaginatedList paginatedList;
|
||||||
|
|
||||||
|
private DataSnapshotList(@NotNull List<UserDataSnapshot> snapshots, @NotNull User dataOwner,
|
||||||
|
@NotNull Locales locales) {
|
||||||
|
final AtomicInteger snapshotNumber = new AtomicInteger(1);
|
||||||
|
this.paginatedList = PaginatedList.of(snapshots.stream()
|
||||||
|
.map(snapshot -> locales.getRawLocale("data_list_item",
|
||||||
|
getNumberIcon(snapshotNumber.getAndIncrement()),
|
||||||
|
new SimpleDateFormat("MMM dd yyyy, HH:mm:ss.sss")
|
||||||
|
.format(snapshot.versionTimestamp()),
|
||||||
|
snapshot.versionUUID().toString().split("-")[0],
|
||||||
|
snapshot.versionUUID().toString(),
|
||||||
|
snapshot.cause().getDisplayName(),
|
||||||
|
dataOwner.username,
|
||||||
|
snapshot.pinned() ? "※" : " ")
|
||||||
|
.orElse("• " + snapshot.versionUUID())).toList(),
|
||||||
|
locales.getBaseChatList(6)
|
||||||
|
.setHeaderFormat(locales.getRawLocale("data_list_title", dataOwner.username,
|
||||||
|
"%first_item_on_page_index%", "%last_item_on_page_index%", "%total_items%")
|
||||||
|
.orElse(""))
|
||||||
|
.setCommand("/husksync:userdata list " + dataOwner.username)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new {@link DataSnapshotList} from a list of {@link UserDataSnapshot}s
|
||||||
|
*
|
||||||
|
* @param snapshots The list of {@link UserDataSnapshot}s to display
|
||||||
|
* @param user The {@link User} who owns the {@link UserDataSnapshot}s
|
||||||
|
* @param locales The {@link Locales} instance
|
||||||
|
* @return A new {@link DataSnapshotList}, to be viewed with {@link #displayPage(OnlineUser, int)}
|
||||||
|
*/
|
||||||
|
public static DataSnapshotList create(@NotNull List<UserDataSnapshot> snapshots, @NotNull User user,
|
||||||
|
@NotNull Locales locales) {
|
||||||
|
return new DataSnapshotList(snapshots, user, locales);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an icon for the given snapshot number, via {@link #CIRCLED_NUMBER_ICONS}
|
||||||
|
*
|
||||||
|
* @param number the snapshot number
|
||||||
|
* @return the icon for the given snapshot number
|
||||||
|
*/
|
||||||
|
private static String getNumberIcon(int number) {
|
||||||
|
if (number < 1 || number > 20) {
|
||||||
|
return String.valueOf(number);
|
||||||
|
}
|
||||||
|
return CIRCLED_NUMBER_ICONS[number - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a page of the list of {@link UserDataSnapshot} to the user
|
||||||
|
*
|
||||||
|
* @param onlineUser The online user to display the message to
|
||||||
|
* @param page The page number to display
|
||||||
|
*/
|
||||||
|
public void displayPage(@NotNull OnlineUser onlineUser, int page) {
|
||||||
|
onlineUser.sendMessage(paginatedList.getNearestValidPage(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package net.william278.husksync.util;
|
|
||||||
|
|
||||||
import de.themoep.minedown.MineDown;
|
|
||||||
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 log(@NotNull Level level, @NotNull MineDown mineDown);
|
|
||||||
|
|
||||||
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,57 +0,0 @@
|
|||||||
package net.william278.husksync.util;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
public class UpdateChecker {
|
|
||||||
|
|
||||||
private final static int SPIGOT_PROJECT_ID = 97144;
|
|
||||||
private final Logger logger;
|
|
||||||
private final Version currentVersion;
|
|
||||||
|
|
||||||
public UpdateChecker(@NotNull Version currentVersion, @NotNull Logger logger) {
|
|
||||||
this.currentVersion = currentVersion;
|
|
||||||
this.logger = logger;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<Version> fetchLatestVersion() {
|
|
||||||
return CompletableFuture.supplyAsync(() -> {
|
|
||||||
try {
|
|
||||||
final URL url = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + SPIGOT_PROJECT_ID);
|
|
||||||
URLConnection urlConnection = url.openConnection();
|
|
||||||
return Version.pluginVersion(new BufferedReader(new InputStreamReader(urlConnection.getInputStream())).readLine());
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, "Failed to fetch the latest plugin version", e);
|
|
||||||
}
|
|
||||||
return new Version();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUpdateAvailable(@NotNull Version latestVersion) {
|
|
||||||
return latestVersion.compareTo(currentVersion) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Version getCurrentVersion() {
|
|
||||||
return currentVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<Boolean> isUpToDate() {
|
|
||||||
return fetchLatestVersion().thenApply(this::isUpdateAvailable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void logToConsole() {
|
|
||||||
fetchLatestVersion().thenAccept(latestVersion -> {
|
|
||||||
if (isUpdateAvailable(latestVersion)) {
|
|
||||||
logger.log(Level.WARNING, "A new version of HuskSync is available: v" + latestVersion);
|
|
||||||
} else {
|
|
||||||
logger.log(Level.INFO, "HuskSync is up-to-date! (Running: v" + getCurrentVersion().toString() + ")");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user