mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-21 15:49:20 +00:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7e6f9cfa7 | ||
|
|
5ec0f1b098 | ||
|
|
8fad075357 | ||
|
|
83e27cca83 | ||
|
|
729230a646 | ||
|
|
029407613f | ||
|
|
3d6ff7c30b | ||
|
|
5833ce955f | ||
|
|
b3a5091828 | ||
|
|
693209ff00 | ||
|
|
5d1bd7c3a9 | ||
|
|
7b8c75dbeb | ||
|
|
b7a30bd6e9 | ||
|
|
2daf5fedef | ||
|
|
5fd40915d0 | ||
|
|
c49700e9ec | ||
|
|
0f35331441 | ||
|
|
0153e14ce5 | ||
|
|
419434bdca | ||
|
|
f1be4d2d88 | ||
|
|
c973dc5f05 | ||
|
|
b530941687 | ||
|
|
c09fde4c36 | ||
|
|
8d3beab145 | ||
|
|
cdf666bde6 | ||
|
|
350528e394 | ||
|
|
a1d3e5fddc | ||
|
|
e096e58c45 | ||
|
|
75eafe57e2 | ||
|
|
0005392cd3 | ||
|
|
93913ca4ef | ||
|
|
aa09639e55 | ||
|
|
b205643fdd | ||
|
|
6fc827dedf | ||
|
|
b8aa1d9701 | ||
|
|
2db3bb313f | ||
|
|
4d23377a18 | ||
|
|
51116cbdfb | ||
|
|
6831ce094d | ||
|
|
289227e763 | ||
|
|
3b8a9e4ed1 | ||
|
|
7db3ed678f | ||
|
|
6d9e68a65b | ||
|
|
2c33f3b0b4 | ||
|
|
c002d86fc0 | ||
|
|
a384de8e42 | ||
|
|
cae17f6e68 | ||
|
|
03ca335293 | ||
|
|
c2b9e6c932 | ||
|
|
518853c921 | ||
|
|
fe9dda31bd | ||
|
|
0fd29bca57 | ||
|
|
37a671dae9 | ||
|
|
c406f40898 | ||
|
|
7561762c25 | ||
|
|
d245245083 | ||
|
|
2b55e129b3 | ||
|
|
0caec74436 |
21
.github/dependabot.yml
vendored
21
.github/dependabot.yml
vendored
@@ -1,7 +1,22 @@
|
|||||||
# Dependabot configuration file for GitHub
|
# Dependabot configuration file for GitHub
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "gradle" # See documentation for possible values
|
# CI workflow action updates
|
||||||
directory: "/" # Location of package manifests
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "weekly"
|
||||||
|
commit-message:
|
||||||
|
prefix: "ci"
|
||||||
|
|
||||||
|
# Gradle package updates
|
||||||
|
- package-ecosystem: "gradle"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "weekly"
|
||||||
|
commit-message:
|
||||||
|
prefix: "deps"
|
||||||
|
ignore:
|
||||||
|
- dependency-name: 'org.spigotmc:spigot-api'
|
||||||
|
- dependency-name: 'org.papermc:paper-api'
|
||||||
1
.github/funding.yml
vendored
1
.github/funding.yml
vendored
@@ -1,3 +1,4 @@
|
|||||||
# Funding metadata for GitHub
|
# Funding metadata for GitHub
|
||||||
|
|
||||||
github: WiIIiam278
|
github: WiIIiam278
|
||||||
custom: https://buymeacoff.ee/william278
|
custom: https://buymeacoff.ee/william278
|
||||||
27
.github/workflows/ci.yml
vendored
27
.github/workflows/ci.yml
vendored
@@ -1,4 +1,3 @@
|
|||||||
# Builds, tests the project with Gradle
|
|
||||||
name: CI Tests
|
name: CI Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -17,21 +16,29 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: 'Checkout for CI 🛎️'
|
||||||
- name: Set up JDK 17
|
uses: actions/checkout@v4
|
||||||
uses: actions/setup-java@v3
|
- name: 'Set up JDK 17 📦'
|
||||||
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: Build with Gradle
|
- name: 'Build with Gradle 🏗️'
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: build test publish
|
arguments: build test publish
|
||||||
env:
|
env:
|
||||||
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
SNAPSHOTS_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
SNAPSHOTS_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
- name: Publish Test Report
|
- name: 'Publish Test Report 📊'
|
||||||
uses: mikepenz/action-junit-report@v3
|
uses: mikepenz/action-junit-report@v4
|
||||||
if: success() || failure() # always run even if the previous step fails
|
if: success() || failure() # Continue on failure
|
||||||
with:
|
with:
|
||||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
|
- name: 'Fetch Version Name 📝'
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=VERSION_NAME::$(${{github.workspace}}/gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')"
|
||||||
|
id: fetch-version
|
||||||
|
- name: Get Version
|
||||||
|
run: |
|
||||||
|
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
||||||
16
.github/workflows/pr_tests.yml
vendored
16
.github/workflows/pr_tests.yml
vendored
@@ -1,4 +1,3 @@
|
|||||||
# Carry out tests on pull requests
|
|
||||||
name: PR Tests
|
name: PR Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -7,18 +6,25 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
checks: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: 'Checkout for CI 🛎'
|
||||||
- name: Set up JDK 17
|
uses: actions/checkout@v4
|
||||||
uses: actions/setup-java@v3
|
- name: 'Set up JDK 17 📦'
|
||||||
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: Test Pull Request
|
- name: 'Build with Gradle 🏗️'
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: test
|
arguments: test
|
||||||
|
- name: 'Publish Test Report 📊'
|
||||||
|
uses: mikepenz/action-junit-report@v4
|
||||||
|
if: success() || failure() # Continue on failure
|
||||||
|
with:
|
||||||
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
18
.github/workflows/release.yml
vendored
18
.github/workflows/release.yml
vendored
@@ -1,9 +1,8 @@
|
|||||||
# Builds, tests and publishes to maven when a release is published
|
|
||||||
name: Release Tests
|
name: Release Tests
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ published ]
|
types: [ 'published' ]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -13,21 +12,22 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- name: 'Checkout for CI 🛎️'
|
||||||
- name: Set up JDK 17
|
uses: actions/checkout@v4
|
||||||
uses: actions/setup-java@v3
|
- name: 'Set up JDK 17 📦'
|
||||||
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
java-version: '17'
|
java-version: '17'
|
||||||
distribution: 'temurin'
|
distribution: 'temurin'
|
||||||
- name: Build with Gradle
|
- name: 'Build with Gradle 🏗️'
|
||||||
uses: gradle/gradle-build-action@v2
|
uses: gradle/gradle-build-action@v2
|
||||||
with:
|
with:
|
||||||
arguments: build test publish
|
arguments: build test publish
|
||||||
env:
|
env:
|
||||||
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
RELEASES_MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
RELEASES_MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
|
||||||
- name: Publish Test Report
|
- name: 'Publish Test Report 📊'
|
||||||
uses: mikepenz/action-junit-report@v3
|
uses: mikepenz/action-junit-report@v4
|
||||||
if: success() || failure() # always run even if the previous step fails
|
if: success() || failure() # Continue on failure
|
||||||
with:
|
with:
|
||||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||||
9
.github/workflows/update_docs.yml
vendored
9
.github/workflows/update_docs.yml
vendored
@@ -1,4 +1,3 @@
|
|||||||
# Update the GitHub Wiki documentation when a push is made to docs/
|
|
||||||
name: Update Docs
|
name: Update Docs
|
||||||
|
|
||||||
on:
|
on:
|
||||||
@@ -17,10 +16,10 @@ jobs:
|
|||||||
deploy-wiki:
|
deploy-wiki:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: 'Checkout Code'
|
- name: 'Checkout for CI 🛎️'
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- name: 'Push Changes to Wiki'
|
- name: 'Push Docs to Github Wiki 📄️'
|
||||||
uses: Andrew-Chen-Wang/github-wiki-action@v3
|
uses: Andrew-Chen-Wang/github-wiki-action@v4
|
||||||
env:
|
env:
|
||||||
WIKI_DIR: 'docs/'
|
WIKI_DIR: 'docs/'
|
||||||
GH_TOKEN: ${{ github.token }}
|
GH_TOKEN: ${{ github.token }}
|
||||||
|
|||||||
12
README.md
12
README.md
@@ -4,8 +4,8 @@
|
|||||||
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci.yml">
|
<a href="https://github.com/WiIIiam278/HuskSync/actions/workflows/ci.yml">
|
||||||
<img src="https://img.shields.io/github/actions/workflow/status/WiIIiam278/HuskSync/ci.yml?branch=master&logo=github"/>
|
<img src="https://img.shields.io/github/actions/workflow/status/WiIIiam278/HuskSync/ci.yml?branch=master&logo=github"/>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://jitpack.io/#net.william278/HuskSync">
|
<a href="https://repo.william278.net/#/releases/net/william278/husksync/">
|
||||||
<img src="https://img.shields.io/jitpack/version/net.william278/HuskSync?color=%2300fb9a&label=api&logo=gradle" />
|
<img src="https://repo.william278.net/api/badge/latest/releases/net/william278/husksync?color=00fb9a&name=Maven&prefix=v" />
|
||||||
</a>
|
</a>
|
||||||
<a href="https://discord.gg/tVYhJfyDWG">
|
<a href="https://discord.gg/tVYhJfyDWG">
|
||||||
<img src="https://img.shields.io/discord/818135932103557162.svg?label=&logo=discord&logoColor=fff&color=7389D8&labelColor=6A7EC2" />
|
<img src="https://img.shields.io/discord/818135932103557162.svg?label=&logo=discord&logoColor=fff&color=7389D8&labelColor=6A7EC2" />
|
||||||
@@ -66,17 +66,17 @@ HuskSync is licensed under the Apache 2.0 license.
|
|||||||
Contributions to the project are welcome—feel free to open a pull request with new features, improvements and/or fixes!
|
Contributions to the project are welcome—feel free to open a pull request with new features, improvements and/or fixes!
|
||||||
|
|
||||||
### Support
|
### Support
|
||||||
Due to its complexity, official support for HuskSync is provided through a paid model. This means that support is only available to users who have purchased a license to the plugin from Spigot, Polymart, or Craftaro and have provided proof of purchase. Please join our Discord server if you have done so and need help!
|
Due to its complexity, official binaries and customer support for HuskSync is provided through a paid model. This means that support is only available to users who have purchased a license to the plugin from Spigot, Polymart, Craftaro, or BuiltByBit and have provided proof of purchase. Please join our Discord server if you have done so and need help!
|
||||||
|
|
||||||
### Translations
|
### 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/languages)
|
- [Locales Directory](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales)
|
||||||
- [English Locales](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/languages/en-gb.yml)
|
- [English Locales](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales/en-gb.yml)
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
- [Docs](https://william278.net/docs/husksync/) — Read the plugin documentation!
|
- [Docs](https://william278.net/docs/husksync/) — Read the plugin documentation!
|
||||||
- [Spigot](https://www.spigotmc.org/resources/husksync.97144/) — View the Spigot resource page (Also: [Polymart](https://polymart.org/resource/husksync.1634), [Craftaro](https://craftaro.com/marketplace/product/husksync.758))
|
- [Spigot](https://www.spigotmc.org/resources/husksync.97144/) — View the Spigot resource page (Also: [Polymart](https://polymart.org/resource/husksync.1634), [Craftaro](https://craftaro.com/marketplace/product/husksync.758), [BuiltByBit](https://builtbybit.com/resources/husksync.34956/))
|
||||||
- [Issues](https://github.com/WiIIiam278/HuskSync/issues) — File a bug report or feature request
|
- [Issues](https://github.com/WiIIiam278/HuskSync/issues) — File a bug report or feature request
|
||||||
- [Discord](https://discord.gg/tVYhJfyDWG) — Get help, ask questions (Purchase required)
|
- [Discord](https://discord.gg/tVYhJfyDWG) — Get help, ask questions (Purchase required)
|
||||||
- [bStats](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140) — View plugin metrics
|
- [bStats](https://bstats.org/plugin/bukkit/HuskSync%20-%20Bukkit/13140) — View plugin metrics
|
||||||
|
|||||||
14
build.gradle
14
build.gradle
@@ -1,7 +1,7 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||||
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||||
id 'org.ajoberstar.grgit' version '5.2.0'
|
id 'org.ajoberstar.grgit' version '5.2.1'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'java'
|
id 'java'
|
||||||
}
|
}
|
||||||
@@ -48,9 +48,9 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.0'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.1'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.0'
|
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.1'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.0'
|
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.1'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -77,7 +77,11 @@ subprojects {
|
|||||||
from '../LICENSE'
|
from '../LICENSE'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['bukkit', 'plugin'].contains(project.name)) {
|
if (['paper'].contains(project.name)) {
|
||||||
|
compileJava.options.release.set 17
|
||||||
|
}
|
||||||
|
|
||||||
|
if (['bukkit', 'paper', 'plugin'].contains(project.name)) {
|
||||||
shadowJar {
|
shadowJar {
|
||||||
destinationDirectory.set(file("$rootDir/target"))
|
destinationDirectory.set(file("$rootDir/target"))
|
||||||
archiveClassifier.set('')
|
archiveClassifier.set('')
|
||||||
|
|||||||
@@ -7,17 +7,17 @@ dependencies {
|
|||||||
implementation 'net.william278:mapdataapi:1.0.3'
|
implementation 'net.william278:mapdataapi:1.0.3'
|
||||||
implementation 'net.william278:andjam: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 'net.kyori:adventure-platform-bukkit:4.3.1'
|
||||||
implementation 'dev.triumphteam:triumph-gui:3.1.5'
|
implementation 'dev.triumphteam:triumph-gui:3.1.7'
|
||||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.3'
|
implementation 'space.arim.morepaperlib:morepaperlib:0.4.3'
|
||||||
implementation 'de.tr7zw:item-nbt-api:2.12.0'
|
implementation 'de.tr7zw:item-nbt-api:2.12.2'
|
||||||
|
|
||||||
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
compileOnly 'org.spigotmc:spigot-api:1.16.5-R0.1-SNAPSHOT'
|
||||||
compileOnly 'commons-io:commons-io:2.13.0'
|
compileOnly 'commons-io:commons-io:2.15.1'
|
||||||
compileOnly 'org.json:json:20230618'
|
compileOnly 'org.json:json:20231013'
|
||||||
compileOnly 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
compileOnly 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||||
compileOnly 'dev.dejvokep:boosted-yaml:1.3.1'
|
compileOnly 'dev.dejvokep:boosted-yaml:1.3.1'
|
||||||
compileOnly 'com.zaxxer:HikariCP:5.0.1'
|
compileOnly 'com.zaxxer:HikariCP:5.1.0'
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'net.william278:annotaml:2.0.7'
|
compileOnly 'net.william278:annotaml:2.0.7'
|
||||||
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||||
@@ -34,7 +34,7 @@ shadowJar {
|
|||||||
relocate 'org.apache.commons.lang3', 'net.william278.husksync.libraries.commons.lang3'
|
relocate 'org.apache.commons.lang3', 'net.william278.husksync.libraries.commons.lang3'
|
||||||
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
||||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||||
relocate 'com.fatboyindustrial', 'net.william278.husktowns.libraries'
|
relocate 'com.fatboyindustrial', 'net.william278.husksync.libraries'
|
||||||
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
relocate 'de.themoep', 'net.william278.husksync.libraries'
|
||||||
relocate 'net.kyori', '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'
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import net.william278.husksync.adapter.SnappyGsonAdapter;
|
|||||||
import net.william278.husksync.api.BukkitHuskSyncAPI;
|
import net.william278.husksync.api.BukkitHuskSyncAPI;
|
||||||
import net.william278.husksync.command.BukkitCommand;
|
import net.william278.husksync.command.BukkitCommand;
|
||||||
import net.william278.husksync.config.Locales;
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.config.Server;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.BukkitSerializer;
|
import net.william278.husksync.data.BukkitSerializer;
|
||||||
import net.william278.husksync.data.Data;
|
import net.william278.husksync.data.Data;
|
||||||
@@ -43,6 +44,7 @@ import net.william278.husksync.migrator.LegacyMigrator;
|
|||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.migrator.MpdbMigrator;
|
import net.william278.husksync.migrator.MpdbMigrator;
|
||||||
import net.william278.husksync.redis.RedisManager;
|
import net.william278.husksync.redis.RedisManager;
|
||||||
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
import net.william278.husksync.user.BukkitUser;
|
import net.william278.husksync.user.BukkitUser;
|
||||||
import net.william278.husksync.user.ConsoleUser;
|
import net.william278.husksync.user.ConsoleUser;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
@@ -64,6 +66,7 @@ import space.arim.morepaperlib.scheduling.RegionalScheduler;
|
|||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentSkipListSet;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -82,8 +85,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
private DataAdapter dataAdapter;
|
private DataAdapter dataAdapter;
|
||||||
private Map<Identifier, Serializer<? extends Data>> serializers;
|
private Map<Identifier, Serializer<? extends Data>> serializers;
|
||||||
private Map<UUID, Map<Identifier, Data>> playerCustomDataStore;
|
private Map<UUID, Map<Identifier, Data>> playerCustomDataStore;
|
||||||
|
private Set<UUID> lockedPlayers;
|
||||||
|
private DataSyncer dataSyncer;
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
private Locales locales;
|
private Locales locales;
|
||||||
|
private Server server;
|
||||||
private List<Migrator> availableMigrators;
|
private List<Migrator> availableMigrators;
|
||||||
private LegacyConverter legacyConverter;
|
private LegacyConverter legacyConverter;
|
||||||
private Map<Integer, MapView> mapViews;
|
private Map<Integer, MapView> mapViews;
|
||||||
@@ -92,15 +98,18 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
private AsynchronousScheduler asyncScheduler;
|
private AsynchronousScheduler asyncScheduler;
|
||||||
private RegionalScheduler regionalScheduler;
|
private RegionalScheduler regionalScheduler;
|
||||||
private Gson gson;
|
private Gson gson;
|
||||||
|
private boolean disabling;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// Initial plugin setup
|
// Initial plugin setup
|
||||||
|
this.disabling = false;
|
||||||
this.gson = createGson();
|
this.gson = createGson();
|
||||||
this.audiences = BukkitAudiences.create(this);
|
this.audiences = BukkitAudiences.create(this);
|
||||||
this.paperLib = new MorePaperLib(this);
|
this.paperLib = new MorePaperLib(this);
|
||||||
this.availableMigrators = new ArrayList<>();
|
this.availableMigrators = new ArrayList<>();
|
||||||
this.serializers = new LinkedHashMap<>();
|
this.serializers = new LinkedHashMap<>();
|
||||||
|
this.lockedPlayers = new ConcurrentSkipListSet<>();
|
||||||
this.playerCustomDataStore = new ConcurrentHashMap<>();
|
this.playerCustomDataStore = new ConcurrentHashMap<>();
|
||||||
this.mapViews = new ConcurrentHashMap<>();
|
this.mapViews = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@@ -152,8 +161,14 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
this.redisManager.initialize();
|
this.redisManager.initialize();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Prepare data syncer
|
||||||
|
initialize("data syncer", (plugin) -> {
|
||||||
|
dataSyncer = getSettings().getSyncMode().create(this);
|
||||||
|
dataSyncer.initialize();
|
||||||
|
});
|
||||||
|
|
||||||
// Register events
|
// Register events
|
||||||
initialize("events", (plugin) -> this.eventListener = new BukkitEventListener(this));
|
initialize("events", (plugin) -> this.eventListener = createEventListener());
|
||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
initialize("commands", (plugin) -> BukkitCommand.Type.registerCommands(this));
|
initialize("commands", (plugin) -> BukkitCommand.Type.registerCommands(this));
|
||||||
@@ -176,6 +191,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
@Override
|
@Override
|
||||||
public void onDisable() {
|
public void onDisable() {
|
||||||
// Handle shutdown
|
// Handle shutdown
|
||||||
|
this.disabling = true;
|
||||||
|
|
||||||
|
// Close the event listener / data syncer
|
||||||
|
if (this.dataSyncer != null) {
|
||||||
|
this.dataSyncer.terminate();
|
||||||
|
}
|
||||||
if (this.eventListener != null) {
|
if (this.eventListener != null) {
|
||||||
this.eventListener.handlePluginDisable();
|
this.eventListener.handlePluginDisable();
|
||||||
}
|
}
|
||||||
@@ -188,6 +209,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected BukkitEventListener createEventListener() {
|
||||||
|
return new BukkitEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Set<OnlineUser> getOnlineUsers() {
|
public Set<OnlineUser> getOnlineUsers() {
|
||||||
@@ -226,6 +252,19 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
public DataSyncer getDataSyncer() {
|
||||||
|
return dataSyncer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setDataSyncer(@NotNull DataSyncer dataSyncer) {
|
||||||
|
log(Level.INFO, String.format("Switching data syncer to %s", dataSyncer.getClass().getSimpleName()));
|
||||||
|
this.dataSyncer = dataSyncer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public Map<Identifier, Serializer<? extends Data>> getSerializers() {
|
public Map<Identifier, Serializer<? extends Data>> getSerializers() {
|
||||||
return serializers;
|
return serializers;
|
||||||
}
|
}
|
||||||
@@ -258,6 +297,17 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String getServerName() {
|
||||||
|
return server.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setServer(@NotNull Server server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Locales getLocales() {
|
public Locales getLocales() {
|
||||||
@@ -328,7 +378,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Set<UUID> getLockedPlayers() {
|
public Set<UUID> getLockedPlayers() {
|
||||||
return this.eventListener.getLockedPlayers();
|
return lockedPlayers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -337,6 +387,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
return gson;
|
return gson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isDisabling() {
|
||||||
|
return disabling;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public Map<Integer, MapView> getMapViews() {
|
public Map<Integer, MapView> getMapViews() {
|
||||||
return mapViews;
|
return mapViews;
|
||||||
|
|||||||
@@ -28,10 +28,7 @@ import net.william278.husksync.HuskSync;
|
|||||||
import net.william278.husksync.adapter.Adaptable;
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
import net.william278.husksync.user.BukkitUser;
|
import net.william278.husksync.user.BukkitUser;
|
||||||
import org.apache.commons.lang.NotImplementedException;
|
import org.apache.commons.lang.NotImplementedException;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.*;
|
||||||
import org.bukkit.GameRule;
|
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Statistic;
|
|
||||||
import org.bukkit.advancement.AdvancementProgress;
|
import org.bukkit.advancement.AdvancementProgress;
|
||||||
import org.bukkit.attribute.Attribute;
|
import org.bukkit.attribute.Attribute;
|
||||||
import org.bukkit.attribute.AttributeInstance;
|
import org.bukkit.attribute.AttributeInstance;
|
||||||
@@ -49,6 +46,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public abstract class BukkitData implements Data {
|
public abstract class BukkitData implements Data {
|
||||||
|
|
||||||
@@ -98,15 +96,14 @@ public abstract class BukkitData implements Data {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setContents(@NotNull Data.Items contents) {
|
public void setContents(@NotNull Data.Items contents) {
|
||||||
System.arraycopy(
|
this.setContents(((BukkitData.Items) contents).getContents());
|
||||||
((BukkitData.Items) contents).getContents(),
|
|
||||||
0, this.contents,
|
|
||||||
0, this.contents.length
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void setContents(@NotNull ItemStack[] contents) {
|
public void setContents(@NotNull ItemStack[] contents) {
|
||||||
|
// Ensure the array is the correct length for the inventory
|
||||||
|
if (contents.length != this.contents.length) {
|
||||||
|
contents = Arrays.copyOf(contents, this.contents.length);
|
||||||
|
}
|
||||||
System.arraycopy(contents, 0, this.contents, 0, this.contents.length);
|
System.arraycopy(contents, 0, this.contents, 0, this.contents.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,7 +526,7 @@ public abstract class BukkitData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static class Statistics extends BukkitData implements Data.Statistics {
|
public static class Statistics extends BukkitData implements Data.Statistics {
|
||||||
private Map<Statistic, Integer> untypedStatistics;
|
private Map<Statistic, Integer> genericStatistics;
|
||||||
private Map<Statistic, Map<Material, Integer>> blockStatistics;
|
private Map<Statistic, Map<Material, Integer>> blockStatistics;
|
||||||
private Map<Statistic, Map<Material, Integer>> itemStatistics;
|
private Map<Statistic, Map<Material, Integer>> itemStatistics;
|
||||||
private Map<Statistic, Map<EntityType, Integer>> entityStatistics;
|
private Map<Statistic, Map<EntityType, Integer>> entityStatistics;
|
||||||
@@ -538,7 +535,7 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull Map<Statistic, Map<Material, Integer>> blockStatistics,
|
@NotNull Map<Statistic, Map<Material, Integer>> blockStatistics,
|
||||||
@NotNull Map<Statistic, Map<Material, Integer>> itemStatistics,
|
@NotNull Map<Statistic, Map<Material, Integer>> itemStatistics,
|
||||||
@NotNull Map<Statistic, Map<EntityType, Integer>> entityStatistics) {
|
@NotNull Map<Statistic, Map<EntityType, Integer>> entityStatistics) {
|
||||||
this.untypedStatistics = genericStatistics;
|
this.genericStatistics = genericStatistics;
|
||||||
this.blockStatistics = blockStatistics;
|
this.blockStatistics = blockStatistics;
|
||||||
this.itemStatistics = itemStatistics;
|
this.itemStatistics = itemStatistics;
|
||||||
this.entityStatistics = entityStatistics;
|
this.entityStatistics = entityStatistics;
|
||||||
@@ -596,30 +593,53 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public static BukkitData.Statistics from(@NotNull StatisticsMap stats) {
|
public static BukkitData.Statistics from(@NotNull StatisticsMap stats) {
|
||||||
return new BukkitData.Statistics(
|
return new BukkitData.Statistics(
|
||||||
stats.genericStats().entrySet().stream().collect(Collectors.toMap(
|
stats.genericStats().entrySet().stream()
|
||||||
entry -> matchStatistic(entry.getKey()),
|
.flatMap(entry -> {
|
||||||
Map.Entry::getValue
|
Statistic statistic = matchStatistic(entry.getKey());
|
||||||
|
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
|
||||||
|
})
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)),
|
||||||
|
stats.blockStats().entrySet().stream()
|
||||||
|
.flatMap(entry -> {
|
||||||
|
Statistic statistic = matchStatistic(entry.getKey());
|
||||||
|
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
|
||||||
|
})
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
Map.Entry::getKey,
|
||||||
|
entry -> entry.getValue().entrySet().stream()
|
||||||
|
.flatMap(blockEntry -> {
|
||||||
|
Material material = Material.matchMaterial(blockEntry.getKey());
|
||||||
|
return material != null ? Stream.of(new AbstractMap.SimpleEntry<>(material, blockEntry.getValue())) : Stream.empty();
|
||||||
|
})
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||||
)),
|
)),
|
||||||
stats.blockStats().entrySet().stream().collect(Collectors.toMap(
|
stats.itemStats().entrySet().stream()
|
||||||
entry -> matchStatistic(entry.getKey()),
|
.flatMap(entry -> {
|
||||||
entry -> entry.getValue().entrySet().stream().collect(Collectors.toMap(
|
Statistic statistic = matchStatistic(entry.getKey());
|
||||||
blockEntry -> Material.matchMaterial(blockEntry.getKey()),
|
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
|
||||||
Map.Entry::getValue
|
})
|
||||||
))
|
.collect(Collectors.toMap(
|
||||||
|
Map.Entry::getKey,
|
||||||
|
entry -> entry.getValue().entrySet().stream()
|
||||||
|
.flatMap(itemEntry -> {
|
||||||
|
Material material = Material.matchMaterial(itemEntry.getKey());
|
||||||
|
return material != null ? Stream.of(new AbstractMap.SimpleEntry<>(material, itemEntry.getValue())) : Stream.empty();
|
||||||
|
})
|
||||||
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||||
)),
|
)),
|
||||||
stats.itemStats().entrySet().stream().collect(Collectors.toMap(
|
stats.entityStats().entrySet().stream()
|
||||||
entry -> matchStatistic(entry.getKey()),
|
.flatMap(entry -> {
|
||||||
entry -> entry.getValue().entrySet().stream().collect(Collectors.toMap(
|
Statistic statistic = matchStatistic(entry.getKey());
|
||||||
itemEntry -> Material.matchMaterial(itemEntry.getKey()),
|
return statistic != null ? Stream.of(new AbstractMap.SimpleEntry<>(statistic, entry.getValue())) : Stream.empty();
|
||||||
Map.Entry::getValue
|
})
|
||||||
))
|
.collect(Collectors.toMap(
|
||||||
)),
|
Map.Entry::getKey,
|
||||||
stats.entityStats().entrySet().stream().collect(Collectors.toMap(
|
entry -> entry.getValue().entrySet().stream()
|
||||||
entry -> matchStatistic(entry.getKey()),
|
.flatMap(itemEntry -> {
|
||||||
entry -> entry.getValue().entrySet().stream().collect(Collectors.toMap(
|
EntityType entityType = matchEntityType(itemEntry.getKey());
|
||||||
entityEntry -> matchEntityType(entityEntry.getKey()),
|
return entityType != null ? Stream.of(new AbstractMap.SimpleEntry<>(entityType, itemEntry.getValue())) : Stream.empty();
|
||||||
Map.Entry::getValue
|
})
|
||||||
))
|
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -641,25 +661,23 @@ public abstract class BukkitData implements Data {
|
|||||||
return new StatisticsMap(genericStats, blockStats, itemStats, entityStats);
|
return new StatisticsMap(genericStats, blockStats, itemStats, entityStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@Nullable
|
||||||
private static Statistic matchStatistic(@NotNull String key) {
|
private static Statistic matchStatistic(@NotNull String key) {
|
||||||
return Arrays.stream(Statistic.values())
|
return Arrays.stream(Statistic.values())
|
||||||
.filter(stat -> stat.getKey().toString().equals(key))
|
.filter(stat -> stat.getKey().toString().equals(key))
|
||||||
.findFirst()
|
.findFirst().orElse(null);
|
||||||
.orElseThrow(() -> new IllegalArgumentException(String.format("Invalid statistic key: %s", key)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@Nullable
|
||||||
private static EntityType matchEntityType(@NotNull String key) {
|
private static EntityType matchEntityType(@NotNull String key) {
|
||||||
return Arrays.stream(EntityType.values())
|
return Arrays.stream(EntityType.values())
|
||||||
.filter(entityType -> entityType.getKey().toString().equals(key))
|
.filter(entityType -> entityType.getKey().toString().equals(key))
|
||||||
.findFirst()
|
.findFirst().orElse(null);
|
||||||
.orElseThrow(() -> new IllegalArgumentException(String.format("Invalid entity type key: %s", key)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
||||||
untypedStatistics.forEach((stat, value) -> applyStat(user, stat, null, value));
|
genericStatistics.forEach((stat, value) -> applyStat(user, stat, null, value));
|
||||||
blockStatistics.forEach((stat, m) -> m.forEach((block, value) -> applyStat(user, stat, block, value)));
|
blockStatistics.forEach((stat, m) -> m.forEach((block, value) -> applyStat(user, stat, block, value)));
|
||||||
itemStatistics.forEach((stat, m) -> m.forEach((item, value) -> applyStat(user, stat, item, value)));
|
itemStatistics.forEach((stat, m) -> m.forEach((item, value) -> applyStat(user, stat, item, value)));
|
||||||
entityStatistics.forEach((stat, m) -> m.forEach((entity, value) -> applyStat(user, stat, entity, value)));
|
entityStatistics.forEach((stat, m) -> m.forEach((entity, value) -> applyStat(user, stat, entity, value)));
|
||||||
@@ -682,45 +700,47 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Integer> getGenericStatistics() {
|
public Map<String, Integer> getGenericStatistics() {
|
||||||
return untypedStatistics.entrySet().stream().collect(
|
return convertStatistics(genericStatistics);
|
||||||
TreeMap::new,
|
|
||||||
(m, e) -> m.put(e.getKey().getKey().toString(), e.getValue()), TreeMap::putAll
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Map<String, Integer>> getBlockStatistics() {
|
public Map<String, Map<String, Integer>> getBlockStatistics() {
|
||||||
return blockStatistics.entrySet().stream().collect(
|
return blockStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||||
TreeMap::new,
|
TreeMap::new,
|
||||||
(m, e) -> m.put(e.getKey().getKey().toString(), e.getValue().entrySet().stream().collect(
|
(m, e) -> m.put(e.getKey().getKey().toString(), convertStatistics(e.getValue())), TreeMap::putAll
|
||||||
TreeMap::new,
|
|
||||||
(m2, e2) -> m2.put(e2.getKey().getKey().toString(), e2.getValue()), TreeMap::putAll
|
|
||||||
)), TreeMap::putAll
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Map<String, Integer>> getItemStatistics() {
|
public Map<String, Map<String, Integer>> getItemStatistics() {
|
||||||
return itemStatistics.entrySet().stream().collect(
|
return itemStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||||
TreeMap::new,
|
TreeMap::new,
|
||||||
(m, e) -> m.put(e.getKey().getKey().toString(), e.getValue().entrySet().stream().collect(
|
(m, e) -> m.put(e.getKey().getKey().toString(), convertStatistics(e.getValue())), TreeMap::putAll
|
||||||
TreeMap::new,
|
|
||||||
(m2, e2) -> m2.put(e2.getKey().getKey().toString(), e2.getValue()), TreeMap::putAll
|
|
||||||
)), TreeMap::putAll
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Map<String, Map<String, Integer>> getEntityStatistics() {
|
public Map<String, Map<String, Integer>> getEntityStatistics() {
|
||||||
return entityStatistics.entrySet().stream().collect(
|
return entityStatistics.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||||
TreeMap::new,
|
TreeMap::new,
|
||||||
(m, e) -> m.put(e.getKey().getKey().toString(), e.getValue().entrySet().stream().collect(
|
(m, e) -> m.put(e.getKey().getKey().toString(), convertStatistics(e.getValue())), TreeMap::putAll
|
||||||
TreeMap::new,
|
);
|
||||||
(m2, e2) -> m2.put(e2.getKey().getKey().toString(), e2.getValue()), TreeMap::putAll
|
}
|
||||||
)), TreeMap::putAll
|
|
||||||
|
@NotNull
|
||||||
|
private <T extends Keyed> Map<String, Integer> convertStatistics(@NotNull Map<T, Integer> stats) {
|
||||||
|
return stats.entrySet().stream().filter(entry -> entry.getKey() != null).collect(
|
||||||
|
TreeMap::new,
|
||||||
|
(m, e) -> {
|
||||||
|
try {
|
||||||
|
m.put(e.getKey().getKey().toString(), e.getValue());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
// Ignore; skip elements with invalid keys (e.g., legacy materials)
|
||||||
|
}
|
||||||
|
}, TreeMap::putAll
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ 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.PlayerInteractEntityEvent;
|
||||||
import org.bukkit.event.player.PlayerInteractEvent;
|
import org.bukkit.event.player.PlayerInteractEvent;
|
||||||
|
import org.bukkit.event.server.MapInitializeEvent;
|
||||||
import org.bukkit.event.world.WorldSaveEvent;
|
import org.bukkit.event.world.WorldSaveEvent;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -115,6 +116,13 @@ public class BukkitEventListener extends EventListener implements BukkitJoinEven
|
|||||||
.collect(Collectors.toList())));
|
.collect(Collectors.toList())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@EventHandler(ignoreCancelled = true)
|
||||||
|
public void onMapInitialize(@NotNull MapInitializeEvent event) {
|
||||||
|
if (plugin.getSettings().doPersistLockedMaps() && event.getMap().isLocked()) {
|
||||||
|
getPlugin().runAsync(() -> ((BukkitHuskSync) plugin).renderMapFromFile(event.getMap()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Events to cancel if the player has not been set yet
|
* Events to cancel if the player has not been set yet
|
||||||
|
|||||||
@@ -217,16 +217,16 @@ public class LegacyMigrator extends Migrator {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return "HuskSync v1.x --> v2.x Migrator";
|
return "HuskSync v1.x --> v3.x Migrator";
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String getHelpMenu() {
|
public String getHelpMenu() {
|
||||||
return """
|
return """
|
||||||
=== HuskSync v1.x --> v2.x Migration Wizard =========
|
=== HuskSync v1.x --> v3.x Migration Wizard =========
|
||||||
This will migrate all user data from HuskSync v1.x to
|
This will migrate all user data from HuskSync v1.x to
|
||||||
HuskSync v2.x's new format. To perform the migration,
|
HuskSync v3.x's new format. To perform the migration,
|
||||||
please follow the steps below carefully.
|
please follow the steps below carefully.
|
||||||
|
|
||||||
[!] Existing data in the database will be wiped. [!]
|
[!] Existing data in the database will be wiped. [!]
|
||||||
|
|||||||
@@ -221,6 +221,9 @@ public class MpdbMigrator extends Migrator {
|
|||||||
public String getHelpMenu() {
|
public String getHelpMenu() {
|
||||||
return """
|
return """
|
||||||
=== MySQLPlayerDataBridge Migration Wizard ==========
|
=== MySQLPlayerDataBridge Migration Wizard ==========
|
||||||
|
NOTE: This migrator currently WORKS WITH MPDB version
|
||||||
|
v4.9.2 and below!
|
||||||
|
|
||||||
This will migrate inventories, ender chests and XP
|
This will migrate inventories, ender chests and XP
|
||||||
from the MySQLPlayerDataBridge plugin to HuskSync.
|
from the MySQLPlayerDataBridge plugin to HuskSync.
|
||||||
|
|
||||||
@@ -254,6 +257,9 @@ public class MpdbMigrator extends Migrator {
|
|||||||
|
|
||||||
STEP 4] To start the migration, please run:
|
STEP 4] To start the migration, please run:
|
||||||
"husksync migrate mpdb start"
|
"husksync migrate mpdb start"
|
||||||
|
|
||||||
|
NOTE: This migrator currently WORKS WITH MPDB version
|
||||||
|
v4.9.2 and below!
|
||||||
""".replaceAll(Pattern.quote("%source_host%"), obfuscateDataString(sourceHost))
|
""".replaceAll(Pattern.quote("%source_host%"), obfuscateDataString(sourceHost))
|
||||||
.replaceAll(Pattern.quote("%source_port%"), Integer.toString(sourcePort))
|
.replaceAll(Pattern.quote("%source_port%"), Integer.toString(sourcePort))
|
||||||
.replaceAll(Pattern.quote("%source_username%"), obfuscateDataString(sourceUsername))
|
.replaceAll(Pattern.quote("%source_username%"), obfuscateDataString(sourceUsername))
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ package net.william278.husksync.util;
|
|||||||
import de.tr7zw.changeme.nbtapi.NBT;
|
import de.tr7zw.changeme.nbtapi.NBT;
|
||||||
import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBT;
|
import de.tr7zw.changeme.nbtapi.iface.ReadWriteNBT;
|
||||||
import de.tr7zw.changeme.nbtapi.iface.ReadableNBT;
|
import de.tr7zw.changeme.nbtapi.iface.ReadableNBT;
|
||||||
|
import net.querz.nbt.io.NBTUtil;
|
||||||
|
import net.querz.nbt.tag.CompoundTag;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.mapdataapi.MapBanner;
|
import net.william278.mapdataapi.MapBanner;
|
||||||
import net.william278.mapdataapi.MapData;
|
import net.william278.mapdataapi.MapData;
|
||||||
@@ -36,6 +38,7 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@@ -133,12 +136,15 @@ public interface BukkitMapPersister {
|
|||||||
final MapMeta meta = Objects.requireNonNull((MapMeta) map.getItemMeta());
|
final MapMeta meta = Objects.requireNonNull((MapMeta) map.getItemMeta());
|
||||||
NBT.get(map, nbt -> {
|
NBT.get(map, nbt -> {
|
||||||
if (!nbt.hasTag(MAP_DATA_KEY)) {
|
if (!nbt.hasTag(MAP_DATA_KEY)) {
|
||||||
return nbt;
|
return;
|
||||||
}
|
}
|
||||||
final ReadableNBT mapData = nbt.getCompound(MAP_DATA_KEY);
|
final ReadableNBT mapData = nbt.getCompound(MAP_DATA_KEY);
|
||||||
|
final ReadableNBT mapIds = nbt.getCompound(MAP_VIEW_ID_MAPPINGS_KEY);
|
||||||
|
if (mapData == null || mapIds == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Search for an existing map view
|
// Search for an existing map view
|
||||||
final ReadableNBT mapIds = nbt.getCompound(MAP_VIEW_ID_MAPPINGS_KEY);
|
|
||||||
Optional<String> world = Optional.empty();
|
Optional<String> world = Optional.empty();
|
||||||
for (String worldUid : mapIds.getKeys()) {
|
for (String worldUid : mapIds.getKeys()) {
|
||||||
world = Bukkit.getWorlds().stream()
|
world = Bukkit.getWorlds().stream()
|
||||||
@@ -157,7 +163,7 @@ public interface BukkitMapPersister {
|
|||||||
meta.setMapView(view);
|
meta.setMapView(view);
|
||||||
map.setItemMeta(meta);
|
map.setItemMeta(meta);
|
||||||
getPlugin().debug(String.format("View exists (#%s); updated map (UID: %s)", view.getId(), uid));
|
getPlugin().debug(String.format("View exists (#%s); updated map (UID: %s)", view.getId(), uid));
|
||||||
return nbt;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,28 +171,84 @@ public interface BukkitMapPersister {
|
|||||||
final MapData canvasData;
|
final MapData canvasData;
|
||||||
try {
|
try {
|
||||||
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
||||||
canvasData = MapData.fromByteArray(mapData.getByteArray(MAP_PIXEL_DATA_KEY));
|
canvasData = MapData.fromByteArray(Objects.requireNonNull(mapData.getByteArray(MAP_PIXEL_DATA_KEY),
|
||||||
|
"Map pixel data is null"));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
getPlugin().log(Level.WARNING, "Failed to deserialize map data from NBT", e);
|
getPlugin().log(Level.WARNING, "Failed to deserialize map data from NBT", e);
|
||||||
return nbt;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a renderer to the map with the data
|
// Add a renderer to the map with the data and save to file
|
||||||
final MapView view = generateRenderedMap(canvasData);
|
final MapView view = generateRenderedMap(canvasData);
|
||||||
final String worldUid = getDefaultMapWorld().getUID().toString();
|
final String worldUid = getDefaultMapWorld().getUID().toString();
|
||||||
meta.setMapView(view);
|
meta.setMapView(view);
|
||||||
map.setItemMeta(meta);
|
map.setItemMeta(meta);
|
||||||
|
saveMapToFile(canvasData, view.getId());
|
||||||
|
|
||||||
// Set the map view ID in NBT
|
// Set the map view ID in NBT
|
||||||
NBT.modify(map, editable -> {
|
NBT.modify(map, editable -> {
|
||||||
editable.getCompound(MAP_VIEW_ID_MAPPINGS_KEY).setInteger(worldUid, view.getId());
|
Objects.requireNonNull(editable.getCompound(MAP_VIEW_ID_MAPPINGS_KEY),
|
||||||
|
"Map view ID mappings compound is null")
|
||||||
|
.setInteger(worldUid, view.getId());
|
||||||
});
|
});
|
||||||
getPlugin().debug(String.format("Generated view (#%s) and updated map (UID: %s)", view.getId(), worldUid));
|
getPlugin().debug(String.format("Generated view (#%s) and updated map (UID: %s)", view.getId(), worldUid));
|
||||||
return nbt;
|
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default void renderMapFromFile(@NotNull MapView view) {
|
||||||
|
final File mapFile = new File(getMapCacheFolder(), view.getId() + ".dat");
|
||||||
|
if (!mapFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MapData canvasData;
|
||||||
|
try {
|
||||||
|
canvasData = MapData.fromNbt(mapFile);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
getPlugin().log(Level.WARNING, "Failed to deserialize map data from file", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new map view renderer with the map data color at each pixel
|
||||||
|
// use view.removeRenderer() to remove all this maps renderers
|
||||||
|
view.getRenderers().forEach(view::removeRenderer);
|
||||||
|
view.addRenderer(new PersistentMapRenderer(canvasData));
|
||||||
|
view.setLocked(true);
|
||||||
|
view.setScale(MapView.Scale.NORMAL);
|
||||||
|
view.setTrackingPosition(false);
|
||||||
|
view.setUnlimitedTracking(false);
|
||||||
|
|
||||||
|
// Set the view to the map
|
||||||
|
setMapView(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void saveMapToFile(@NotNull MapData data, int id) {
|
||||||
|
getPlugin().runAsync(() -> {
|
||||||
|
final File mapFile = new File(getMapCacheFolder(), id + ".dat");
|
||||||
|
if (mapFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final CompoundTag rootTag = new CompoundTag();
|
||||||
|
rootTag.put("data", data.toNBT().getTag());
|
||||||
|
NBTUtil.write(rootTag, mapFile);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
getPlugin().log(Level.WARNING, "Failed to serialize map data to file", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private File getMapCacheFolder() {
|
||||||
|
final File mapCache = new File(getPlugin().getDataFolder(), "maps");
|
||||||
|
if (!mapCache.exists() && !mapCache.mkdirs()) {
|
||||||
|
getPlugin().log(Level.WARNING, "Failed to create maps folder");
|
||||||
|
}
|
||||||
|
return mapCache;
|
||||||
|
}
|
||||||
|
|
||||||
// Sets the renderer of a map, and returns the generated MapView
|
// Sets the renderer of a map, and returns the generated MapView
|
||||||
@NotNull
|
@NotNull
|
||||||
private MapView generateRenderedMap(@NotNull MapData canvasData) {
|
private MapView generateRenderedMap(@NotNull MapData canvasData) {
|
||||||
@@ -245,6 +307,10 @@ public interface BukkitMapPersister {
|
|||||||
|
|
||||||
// Set the map banners and markers
|
// Set the map banners and markers
|
||||||
final MapCursorCollection cursors = canvas.getCursors();
|
final MapCursorCollection cursors = canvas.getCursors();
|
||||||
|
while (cursors.size() > 0) {
|
||||||
|
cursors.removeCursor(cursors.getCursor(0));
|
||||||
|
}
|
||||||
|
|
||||||
canvasData.getBanners().forEach(banner -> cursors.addCursor(createBannerCursor(banner)));
|
canvasData.getBanners().forEach(banner -> cursors.addCursor(createBannerCursor(banner)));
|
||||||
canvas.setCursors(cursors);
|
canvas.setCursors(cursors);
|
||||||
}
|
}
|
||||||
@@ -255,7 +321,7 @@ public interface BukkitMapPersister {
|
|||||||
return new MapCursor(
|
return new MapCursor(
|
||||||
(byte) banner.getPosition().getX(),
|
(byte) banner.getPosition().getX(),
|
||||||
(byte) banner.getPosition().getZ(),
|
(byte) banner.getPosition().getZ(),
|
||||||
(byte) 0,
|
(byte) 8, // Always rotate banners upright
|
||||||
switch (banner.getColor().toLowerCase(Locale.ENGLISH)) {
|
switch (banner.getColor().toLowerCase(Locale.ENGLISH)) {
|
||||||
case "white" -> MapCursor.Type.BANNER_WHITE;
|
case "white" -> MapCursor.Type.BANNER_WHITE;
|
||||||
case "orange" -> MapCursor.Type.BANNER_ORANGE;
|
case "orange" -> MapCursor.Type.BANNER_ORANGE;
|
||||||
@@ -351,12 +417,13 @@ public interface BukkitMapPersister {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private MapData extractMapData() {
|
private MapData extractMapData() {
|
||||||
final List<MapBanner> banners = new ArrayList<>();
|
final List<MapBanner> banners = new ArrayList<>();
|
||||||
|
final String BANNER_PREFIX = "banner_";
|
||||||
for (int i = 0; i < getCursors().size(); i++) {
|
for (int i = 0; i < getCursors().size(); i++) {
|
||||||
final MapCursor cursor = getCursors().getCursor(i);
|
final MapCursor cursor = getCursors().getCursor(i);
|
||||||
final String type = cursor.getType().name().toLowerCase(Locale.ENGLISH);
|
final String type = cursor.getType().name().toLowerCase(Locale.ENGLISH);
|
||||||
if (type.startsWith("banner_")) {
|
if (type.startsWith(BANNER_PREFIX)) {
|
||||||
banners.add(new MapBanner(
|
banners.add(new MapBanner(
|
||||||
type.replaceAll("banner_", ""),
|
type.replaceAll(BANNER_PREFIX, ""),
|
||||||
cursor.getCaption() == null ? "" : cursor.getCaption(),
|
cursor.getCaption() == null ? "" : cursor.getCaption(),
|
||||||
cursor.getX(),
|
cursor.getX(),
|
||||||
mapView.getWorld() != null ? mapView.getWorld().getSeaLevel() : 128,
|
mapView.getWorld() != null ? mapView.getWorld().getSeaLevel() : 128,
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'commons-io:commons-io:2.13.0'
|
api 'commons-io:commons-io:2.15.1'
|
||||||
api 'org.apache.commons:commons-text:1.10.0'
|
api 'org.apache.commons:commons-text:1.11.0'
|
||||||
api 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
api 'de.themoep:minedown-adventure:1.7.2-SNAPSHOT'
|
||||||
api 'net.kyori:adventure-api:4.14.0'
|
api 'net.kyori:adventure-api:4.14.0'
|
||||||
api 'org.json:json:20230618'
|
api 'org.json:json:20231013'
|
||||||
api 'com.google.code.gson:gson:2.10.1'
|
api 'com.google.code.gson:gson:2.10.1'
|
||||||
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
||||||
api 'dev.dejvokep:boosted-yaml:1.3.1'
|
api 'dev.dejvokep:boosted-yaml:1.3.1'
|
||||||
api 'net.william278:annotaml:2.0.7'
|
api 'net.william278:annotaml:2.0.7'
|
||||||
api 'net.william278:DesertWell:2.0.4'
|
api 'net.william278:DesertWell:2.0.4'
|
||||||
api 'net.william278:PagineDown:1.1'
|
api 'net.william278:PagineDown:1.1'
|
||||||
api('com.zaxxer:HikariCP:5.0.1') {
|
api('com.zaxxer:HikariCP:5.1.0') {
|
||||||
exclude module: 'slf4j-api'
|
exclude module: 'slf4j-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOnly 'org.jetbrains:annotations:24.0.1'
|
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
compileOnly 'com.github.plan-player-analytics:Plan:5.5.2272'
|
||||||
compileOnly "redis.clients:jedis:$jedis_version"
|
compileOnly "redis.clients:jedis:$jedis_version"
|
||||||
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
|
compileOnly "com.mysql:mysql-connector-j:$mysql_driver_version"
|
||||||
@@ -29,5 +29,5 @@ dependencies {
|
|||||||
testImplementation "redis.clients:jedis:$jedis_version"
|
testImplementation "redis.clients:jedis:$jedis_version"
|
||||||
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
|
testImplementation "org.xerial.snappy:snappy-java:$snappy_version"
|
||||||
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3.1'
|
testCompileOnly 'dev.dejvokep:boosted-yaml:1.3.1'
|
||||||
testCompileOnly 'org.jetbrains:annotations:24.0.1'
|
testCompileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
}
|
}
|
||||||
@@ -28,6 +28,7 @@ import net.william278.desertwell.util.UpdateChecker;
|
|||||||
import net.william278.desertwell.util.Version;
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.adapter.DataAdapter;
|
import net.william278.husksync.adapter.DataAdapter;
|
||||||
import net.william278.husksync.config.Locales;
|
import net.william278.husksync.config.Locales;
|
||||||
|
import net.william278.husksync.config.Server;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
import net.william278.husksync.data.Data;
|
import net.william278.husksync.data.Data;
|
||||||
import net.william278.husksync.data.Identifier;
|
import net.william278.husksync.data.Identifier;
|
||||||
@@ -36,6 +37,7 @@ import net.william278.husksync.database.Database;
|
|||||||
import net.william278.husksync.event.EventDispatcher;
|
import net.william278.husksync.event.EventDispatcher;
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.redis.RedisManager;
|
import net.william278.husksync.redis.RedisManager;
|
||||||
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
import net.william278.husksync.user.ConsoleUser;
|
import net.william278.husksync.user.ConsoleUser;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
import net.william278.husksync.util.LegacyConverter;
|
import net.william278.husksync.util.LegacyConverter;
|
||||||
@@ -90,6 +92,11 @@ public interface HuskSync extends Task.Supplier, EventDispatcher {
|
|||||||
@NotNull
|
@NotNull
|
||||||
RedisManager getRedisManager();
|
RedisManager getRedisManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the implementing adapter for serializing data
|
||||||
|
*
|
||||||
|
* @return the {@link DataAdapter}
|
||||||
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
DataAdapter getDataAdapter();
|
DataAdapter getDataAdapter();
|
||||||
|
|
||||||
@@ -130,6 +137,21 @@ public interface HuskSync extends Task.Supplier, EventDispatcher {
|
|||||||
return getSerializers().keySet();
|
return getSerializers().keySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data syncer implementation
|
||||||
|
*
|
||||||
|
* @return the {@link DataSyncer} implementation
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
DataSyncer getDataSyncer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the data syncer implementation
|
||||||
|
*
|
||||||
|
* @param dataSyncer the {@link DataSyncer} implementation
|
||||||
|
*/
|
||||||
|
void setDataSyncer(@NotNull DataSyncer dataSyncer);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of available data {@link Migrator}s
|
* Returns a list of available data {@link Migrator}s
|
||||||
*
|
*
|
||||||
@@ -167,6 +189,11 @@ public interface HuskSync extends Task.Supplier, EventDispatcher {
|
|||||||
|
|
||||||
void setSettings(@NotNull Settings settings);
|
void setSettings(@NotNull Settings settings);
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
String getServerName();
|
||||||
|
|
||||||
|
void setServer(@NotNull Server server);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the plugin {@link Locales}
|
* Returns the plugin {@link Locales}
|
||||||
*
|
*
|
||||||
@@ -255,7 +282,7 @@ public interface HuskSync extends Task.Supplier, EventDispatcher {
|
|||||||
String getPlatformType();
|
String getPlatformType();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the legacy data converter, if it exists
|
* Returns the legacy data converter if it exists
|
||||||
*
|
*
|
||||||
* @return the {@link LegacyConverter}
|
* @return the {@link LegacyConverter}
|
||||||
*/
|
*/
|
||||||
@@ -267,7 +294,16 @@ public interface HuskSync extends Task.Supplier, EventDispatcher {
|
|||||||
default void loadConfigs() {
|
default void loadConfigs() {
|
||||||
try {
|
try {
|
||||||
// Load settings
|
// Load settings
|
||||||
setSettings(Annotaml.create(new File(getDataFolder(), "config.yml"), Settings.class).get());
|
setSettings(Annotaml.create(
|
||||||
|
new File(getDataFolder(), "config.yml"),
|
||||||
|
Settings.class
|
||||||
|
).get());
|
||||||
|
|
||||||
|
// Load server name
|
||||||
|
setServer(Annotaml.create(
|
||||||
|
new File(getDataFolder(), "server.yml"),
|
||||||
|
Server.getDefault(this)
|
||||||
|
).get());
|
||||||
|
|
||||||
// Load locales from language preset default
|
// Load locales from language preset default
|
||||||
final Locales languagePresets = Annotaml.create(
|
final Locales languagePresets = Annotaml.create(
|
||||||
@@ -305,12 +341,31 @@ public interface HuskSync extends Task.Supplier, EventDispatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set of UUIDs of "locked players", for which events will be canceled.
|
||||||
|
* </p>
|
||||||
|
* Players are locked while their items are being set (on join) or saved (on quit)
|
||||||
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
Set<UUID> getLockedPlayers();
|
Set<UUID> getLockedPlayers();
|
||||||
|
|
||||||
|
default boolean isLocked(@NotNull UUID uuid) {
|
||||||
|
return getLockedPlayers().contains(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void lockPlayer(@NotNull UUID uuid) {
|
||||||
|
getLockedPlayers().add(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
default void unlockPlayer(@NotNull UUID uuid) {
|
||||||
|
getLockedPlayers().remove(uuid);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
Gson getGson();
|
Gson getGson();
|
||||||
|
|
||||||
|
boolean isDisabling();
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
default Gson createGson() {
|
default Gson createGson() {
|
||||||
return Converters.registerOffsetDateTime(new GsonBuilder()).create();
|
return Converters.registerOffsetDateTime(new GsonBuilder()).create();
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import net.william278.husksync.data.Data;
|
|||||||
import net.william278.husksync.data.DataSnapshot;
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
import net.william278.husksync.data.Identifier;
|
import net.william278.husksync.data.Identifier;
|
||||||
import net.william278.husksync.data.Serializer;
|
import net.william278.husksync.data.Serializer;
|
||||||
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
import net.william278.husksync.user.User;
|
import net.william278.husksync.user.User;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@@ -404,6 +405,7 @@ public abstract class HuskSyncAPI {
|
|||||||
* @param <T> The type of the element
|
* @param <T> The type of the element
|
||||||
* @return The deserialized element
|
* @return The deserialized element
|
||||||
* @throws Serializer.DeserializationException If the element could not be deserialized
|
* @throws Serializer.DeserializationException If the element could not be deserialized
|
||||||
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public <T extends Adaptable> T deserializeData(@NotNull String serialized, Class<T> type)
|
public <T extends Adaptable> T deserializeData(@NotNull String serialized, Class<T> type)
|
||||||
@@ -418,6 +420,7 @@ public abstract class HuskSyncAPI {
|
|||||||
* @param <T> The type of the element
|
* @param <T> The type of the element
|
||||||
* @return The serialized JSON string
|
* @return The serialized JSON string
|
||||||
* @throws Serializer.SerializationException If the element could not be serialized
|
* @throws Serializer.SerializationException If the element could not be serialized
|
||||||
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
public <T extends Adaptable> String serializeData(@NotNull T element)
|
public <T extends Adaptable> String serializeData(@NotNull T element)
|
||||||
@@ -425,6 +428,16 @@ public abstract class HuskSyncAPI {
|
|||||||
return plugin.getDataAdapter().toJson(element);
|
return plugin.getDataAdapter().toJson(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the {@link DataSyncer} to be used to sync data
|
||||||
|
*
|
||||||
|
* @param syncer The data syncer to use for synchronizing user data
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public void setDataSyncer(@NotNull DataSyncer syncer) {
|
||||||
|
plugin.setDataSyncer(syncer);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <b>(Internal use only)</b> - Get the plugin instance
|
* <b>(Internal use only)</b> - Get the plugin instance
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -72,7 +72,8 @@ public class HuskSyncCommand extends Command implements TabProvider {
|
|||||||
AboutMenu.Credit.of("Ghost-chu").description("Simplified Chinese (zh-cn)"),
|
AboutMenu.Credit.of("Ghost-chu").description("Simplified Chinese (zh-cn)"),
|
||||||
AboutMenu.Credit.of("DJelly4K").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("Thourgard").description("Ukrainian (uk-ua)"),
|
||||||
AboutMenu.Credit.of("xF3d3").description("Italian (it-it)"))
|
AboutMenu.Credit.of("xF3d3").description("Italian (it-it)"),
|
||||||
|
AboutMenu.Credit.of("cada3141").description("Korean (ko-kr)"))
|
||||||
.buttons(
|
.buttons(
|
||||||
AboutMenu.Link.of("https://william278.net/docs/husksync").text("Documentation").icon("⛏"),
|
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://github.com/WiIIiam278/HuskSync/issues").text("Issues").icon("❌").color(TextColor.color(0xff9f0f)),
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import java.util.Optional;
|
|||||||
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
┣╸ See plugin about menu for international locale credits
|
┣╸ See plugin about menu for international locale credits
|
||||||
┣╸ Formatted in MineDown: https://github.com/Phoenix616/MineDown
|
┣╸ Formatted in MineDown: https://github.com/Phoenix616/MineDown
|
||||||
┗╸ Translate HuskSync: https://william278.net/docs/husksync/Translations""")
|
┗╸ Translate HuskSync: https://william278.net/docs/husksync/translations""")
|
||||||
public class Locales {
|
public class Locales {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.config;
|
||||||
|
|
||||||
|
import net.william278.annotaml.Annotaml;
|
||||||
|
import net.william278.annotaml.YamlFile;
|
||||||
|
import net.william278.annotaml.YamlKey;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a server on a proxied network.
|
||||||
|
*/
|
||||||
|
@YamlFile(header = """
|
||||||
|
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
┃ HuskSync Server ID config ┃
|
||||||
|
┃ Developed by William278 ┃
|
||||||
|
┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
|
┣╸ This file should contain the ID of this server as defined in your proxy config.
|
||||||
|
┗╸ If you join it using /server alpha, then set it to 'alpha' (case-sensitive)""")
|
||||||
|
public class Server {
|
||||||
|
|
||||||
|
@YamlKey("name")
|
||||||
|
private String serverName;
|
||||||
|
|
||||||
|
private Server(@NotNull String serverName) {
|
||||||
|
this.serverName = serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private Server() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static Server getDefault(@NotNull HuskSync plugin) {
|
||||||
|
return new Server(getDefaultServerName(plugin));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a sensible default name for the server name property
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
private static String getDefaultServerName(@NotNull HuskSync plugin) {
|
||||||
|
try {
|
||||||
|
// Fetch server default from supported plugins if present
|
||||||
|
for (String s : List.of("HuskHomes", "HuskTowns")) {
|
||||||
|
final File serverFile = Path.of(plugin.getDataFolder().getParent(), s, "server.yml").toFile();
|
||||||
|
if (serverFile.exists()) {
|
||||||
|
return Annotaml.create(serverFile, Server.class).get().getName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch server default from user dir name
|
||||||
|
final Path serverDirectory = Path.of(System.getProperty("user.dir"));
|
||||||
|
return serverDirectory.getFileName().toString().trim();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return "server";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@NotNull Object other) {
|
||||||
|
// If the name of this server matches another, the servers are the same.
|
||||||
|
if (other instanceof Server server) {
|
||||||
|
return server.getName().equalsIgnoreCase(this.getName());
|
||||||
|
}
|
||||||
|
return super.equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Proxy-defined name of this server.
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public String getName() {
|
||||||
|
return serverName;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -26,6 +26,7 @@ import net.william278.husksync.data.DataSnapshot;
|
|||||||
import net.william278.husksync.data.Identifier;
|
import net.william278.husksync.data.Identifier;
|
||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
import net.william278.husksync.listener.EventListener;
|
import net.william278.husksync.listener.EventListener;
|
||||||
|
import net.william278.husksync.sync.DataSyncer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -44,7 +45,7 @@ import java.util.*;
|
|||||||
public class Settings {
|
public class Settings {
|
||||||
|
|
||||||
// Top-level settings
|
// Top-level settings
|
||||||
@YamlComment("Locale of the default language file to use. Docs: https://william278.net/docs/huskhomes/translations")
|
@YamlComment("Locale of the default language file to use. Docs: https://william278.net/docs/husksync/translations")
|
||||||
@YamlKey("language")
|
@YamlKey("language")
|
||||||
private String language = "en-gb";
|
private String language = "en-gb";
|
||||||
|
|
||||||
@@ -135,6 +136,11 @@ public class Settings {
|
|||||||
|
|
||||||
|
|
||||||
// Synchronization settings
|
// Synchronization settings
|
||||||
|
@YamlComment("The mode of data synchronization to use (DELAY or LOCKSTEP). DELAY should be fine for most networks."
|
||||||
|
+ " Docs: https://william278.net/docs/husksync/sync-modes")
|
||||||
|
@YamlKey("synchronization.mode")
|
||||||
|
private DataSyncer.Mode syncMode = DataSyncer.Mode.DELAY;
|
||||||
|
|
||||||
@YamlComment("The number of data snapshot backups that should be kept at once per user")
|
@YamlComment("The number of data snapshot backups that should be kept at once per user")
|
||||||
@YamlKey("synchronization.max_user_data_snapshots")
|
@YamlKey("synchronization.max_user_data_snapshots")
|
||||||
private int maxUserDataSnapshots = 16;
|
private int maxUserDataSnapshots = 16;
|
||||||
@@ -150,7 +156,6 @@ public class Settings {
|
|||||||
DataSnapshot.SaveCause.INVENTORY_COMMAND.name(),
|
DataSnapshot.SaveCause.INVENTORY_COMMAND.name(),
|
||||||
DataSnapshot.SaveCause.ENDERCHEST_COMMAND.name(),
|
DataSnapshot.SaveCause.ENDERCHEST_COMMAND.name(),
|
||||||
DataSnapshot.SaveCause.BACKUP_RESTORE.name(),
|
DataSnapshot.SaveCause.BACKUP_RESTORE.name(),
|
||||||
DataSnapshot.SaveCause.CONVERTED_FROM_V2.name(),
|
|
||||||
DataSnapshot.SaveCause.LEGACY_MIGRATION.name(),
|
DataSnapshot.SaveCause.LEGACY_MIGRATION.name(),
|
||||||
DataSnapshot.SaveCause.MPDB_MIGRATION.name()
|
DataSnapshot.SaveCause.MPDB_MIGRATION.name()
|
||||||
);
|
);
|
||||||
@@ -160,12 +165,21 @@ public class Settings {
|
|||||||
private boolean saveOnWorldSave = true;
|
private boolean saveOnWorldSave = true;
|
||||||
|
|
||||||
@YamlComment("Whether to create a snapshot for users when they die (containing their death drops)")
|
@YamlComment("Whether to create a snapshot for users when they die (containing their death drops)")
|
||||||
@YamlKey("synchronization.save_on_death")
|
@YamlKey("synchronization.save_on_death.enabled")
|
||||||
private boolean saveOnDeath = false;
|
private boolean saveOnDeath = false;
|
||||||
|
|
||||||
@YamlComment("Whether to save empty death drops for users when they die")
|
@YamlComment("What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). "
|
||||||
@YamlKey("synchronization.save_empty_drops_on_death")
|
+ " Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server.")
|
||||||
private boolean saveEmptyDropsOnDeath = true;
|
@YamlKey("synchronization.save_on_death.items_to_save")
|
||||||
|
private DeathItemsMode deathItemsMode = DeathItemsMode.DROPS;
|
||||||
|
|
||||||
|
@YamlComment("Should a death snapshot still be created even if the items to save on the player's death are empty?")
|
||||||
|
@YamlKey("synchronization.save_on_death.save_empty_items")
|
||||||
|
private boolean saveEmptyDeathItems = true;
|
||||||
|
|
||||||
|
@YamlComment("Whether dead players who log out and log in to a different server should have their items saved.")
|
||||||
|
@YamlKey("synchronization.save_on_death.sync_dead_players_changing_server")
|
||||||
|
private boolean synchronizeDeadPlayersChangingServer = true;
|
||||||
|
|
||||||
@YamlComment("Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing")
|
@YamlComment("Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing")
|
||||||
@YamlKey("synchronization.compress_data")
|
@YamlKey("synchronization.compress_data")
|
||||||
@@ -183,12 +197,7 @@ public class Settings {
|
|||||||
@YamlKey("synchronization.synchronize_max_health")
|
@YamlKey("synchronization.synchronize_max_health")
|
||||||
private boolean synchronizeMaxHealth = true;
|
private boolean synchronizeMaxHealth = true;
|
||||||
|
|
||||||
@YamlComment("Whether dead players who log out and log in to a different server should have their items saved. "
|
@YamlComment("If using the DELAY sync method, how long should this server listen for Redis key data updates before "
|
||||||
+ "You may need to modify this if you're using the keepInventory gamerule.")
|
|
||||||
@YamlKey("synchronization.synchronize_dead_players_changing_server")
|
|
||||||
private boolean synchronizeDeadPlayersChangingServer = true;
|
|
||||||
|
|
||||||
@YamlComment("How long, in milliseconds, this server should wait for a response from the redis server before "
|
|
||||||
+ "pulling data from the database instead (i.e., if the user did not change servers).")
|
+ "pulling data from the database instead (i.e., if the user did not change servers).")
|
||||||
@YamlKey("synchronization.network_latency_milliseconds")
|
@YamlKey("synchronization.network_latency_milliseconds")
|
||||||
private int networkLatencyMilliseconds = 500;
|
private int networkLatencyMilliseconds = 500;
|
||||||
@@ -315,6 +324,11 @@ public class Settings {
|
|||||||
return redisUseSsl;
|
return redisUseSsl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public DataSyncer.Mode getSyncMode() {
|
||||||
|
return syncMode;
|
||||||
|
}
|
||||||
|
|
||||||
public int getMaxUserDataSnapshots() {
|
public int getMaxUserDataSnapshots() {
|
||||||
return maxUserDataSnapshots;
|
return maxUserDataSnapshots;
|
||||||
}
|
}
|
||||||
@@ -331,8 +345,13 @@ public class Settings {
|
|||||||
return saveOnDeath;
|
return saveOnDeath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doSaveEmptyDropsOnDeath() {
|
@NotNull
|
||||||
return saveEmptyDropsOnDeath;
|
public DeathItemsMode getDeathItemsMode() {
|
||||||
|
return deathItemsMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean doSaveEmptyDeathItems() {
|
||||||
|
return saveEmptyDeathItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean doCompressData() {
|
public boolean doCompressData() {
|
||||||
@@ -387,6 +406,14 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the mode of saving items on death
|
||||||
|
*/
|
||||||
|
public enum DeathItemsMode {
|
||||||
|
DROPS,
|
||||||
|
ITEMS_TO_KEEP
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the names of tables in the database
|
* Represents the names of tables in the database
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -45,9 +45,9 @@ public class DataSnapshot {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* Current version of the snapshot data format.
|
* Current version of the snapshot data format.
|
||||||
* HuskSync v3.0 uses v4; HuskSync v2.0 uses v1-v3
|
* HuskSync v3.1 uses v5, v3.0 uses v4; v2.0 uses v1-v3
|
||||||
*/
|
*/
|
||||||
protected static final int CURRENT_FORMAT_VERSION = 4;
|
protected static final int CURRENT_FORMAT_VERSION = 5;
|
||||||
|
|
||||||
@SerializedName("id")
|
@SerializedName("id")
|
||||||
protected UUID id;
|
protected UUID id;
|
||||||
@@ -61,6 +61,9 @@ public class DataSnapshot {
|
|||||||
@SerializedName("save_cause")
|
@SerializedName("save_cause")
|
||||||
protected SaveCause saveCause;
|
protected SaveCause saveCause;
|
||||||
|
|
||||||
|
@SerializedName("server_name")
|
||||||
|
protected String serverName;
|
||||||
|
|
||||||
@SerializedName("minecraft_version")
|
@SerializedName("minecraft_version")
|
||||||
protected String minecraftVersion;
|
protected String minecraftVersion;
|
||||||
|
|
||||||
@@ -74,12 +77,13 @@ public class DataSnapshot {
|
|||||||
protected Map<String, String> data;
|
protected Map<String, String> data;
|
||||||
|
|
||||||
private DataSnapshot(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
private DataSnapshot(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull Map<String, String> data,
|
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.pinned = pinned;
|
this.pinned = pinned;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
this.saveCause = saveCause;
|
this.saveCause = saveCause;
|
||||||
|
this.serverName = serverName;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.minecraftVersion = minecraftVersion.toStringWithoutMetadata();
|
this.minecraftVersion = minecraftVersion.toStringWithoutMetadata();
|
||||||
this.platformType = platformType;
|
this.platformType = platformType;
|
||||||
@@ -114,7 +118,7 @@ public class DataSnapshot {
|
|||||||
"Please ensure each server is running the latest version of HuskSync.",
|
"Please ensure each server is running the latest version of HuskSync.",
|
||||||
snapshot.getFormatVersion(), CURRENT_FORMAT_VERSION));
|
snapshot.getFormatVersion(), CURRENT_FORMAT_VERSION));
|
||||||
}
|
}
|
||||||
if (snapshot.getFormatVersion() < CURRENT_FORMAT_VERSION) {
|
if (snapshot.getFormatVersion() < 4) {
|
||||||
if (plugin.getLegacyConverter().isPresent()) {
|
if (plugin.getLegacyConverter().isPresent()) {
|
||||||
return plugin.getLegacyConverter().get().convert(
|
return plugin.getLegacyConverter().get().convert(
|
||||||
data,
|
data,
|
||||||
@@ -195,13 +199,26 @@ public class DataSnapshot {
|
|||||||
return saveCause;
|
return saveCause;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the server the snapshot was created on.
|
||||||
|
* <p>
|
||||||
|
* Note that snapshots generated before v3.1 will return {@code "N/A"}
|
||||||
|
*
|
||||||
|
* @return The server name
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public String getServerName() {
|
||||||
|
return Optional.ofNullable(serverName).orElse("N/A");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set why the snapshot was created
|
* Set why the snapshot was created
|
||||||
*
|
*
|
||||||
* @param saveCause The {@link SaveCause data save cause} of the snapshot
|
* @param saveCause The {@link SaveCause data save cause} of the snapshot
|
||||||
* @since 3.0
|
* @since 3.0
|
||||||
*/
|
*/
|
||||||
public void setSaveCause(SaveCause saveCause) {
|
public void setSaveCause(@NotNull SaveCause saveCause) {
|
||||||
this.saveCause = saveCause;
|
this.saveCause = saveCause;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,9 +273,9 @@ public class DataSnapshot {
|
|||||||
public static class Packed extends DataSnapshot implements Adaptable {
|
public static class Packed extends DataSnapshot implements Adaptable {
|
||||||
|
|
||||||
protected Packed(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
protected Packed(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull Map<String, String> data,
|
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
||||||
super(id, pinned, timestamp, saveCause, data, minecraftVersion, platformType, formatVersion);
|
super(id, pinned, timestamp, saveCause, serverName, data, minecraftVersion, platformType, formatVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
@@ -282,8 +299,8 @@ public class DataSnapshot {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public Packed copy() {
|
public Packed copy() {
|
||||||
return new Packed(
|
return new Packed(
|
||||||
UUID.randomUUID(), pinned, OffsetDateTime.now(), saveCause, data,
|
UUID.randomUUID(), pinned, OffsetDateTime.now(), saveCause, serverName,
|
||||||
getMinecraftVersion(), platformType, formatVersion
|
data, getMinecraftVersion(), platformType, formatVersion
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +324,7 @@ public class DataSnapshot {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public DataSnapshot.Unpacked unpack(@NotNull HuskSync plugin) {
|
public DataSnapshot.Unpacked unpack(@NotNull HuskSync plugin) {
|
||||||
return new Unpacked(
|
return new Unpacked(
|
||||||
id, pinned, timestamp, saveCause, data,
|
id, pinned, timestamp, saveCause, serverName, data,
|
||||||
getMinecraftVersion(), platformType, formatVersion, plugin
|
getMinecraftVersion(), platformType, formatVersion, plugin
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -325,17 +342,17 @@ public class DataSnapshot {
|
|||||||
private final Map<Identifier, Data> deserialized;
|
private final Map<Identifier, Data> deserialized;
|
||||||
|
|
||||||
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull Map<String, String> data,
|
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<String, String> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion,
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion,
|
||||||
@NotNull HuskSync plugin) {
|
@NotNull HuskSync plugin) {
|
||||||
super(id, pinned, timestamp, saveCause, data, minecraftVersion, platformType, formatVersion);
|
super(id, pinned, timestamp, saveCause, serverName, data, minecraftVersion, platformType, formatVersion);
|
||||||
this.deserialized = deserializeData(plugin);
|
this.deserialized = deserializeData(plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
private Unpacked(@NotNull UUID id, boolean pinned, @NotNull OffsetDateTime timestamp,
|
||||||
@NotNull SaveCause saveCause, @NotNull Map<Identifier, Data> data,
|
@NotNull SaveCause saveCause, @NotNull String serverName, @NotNull Map<Identifier, Data> data,
|
||||||
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
@NotNull Version minecraftVersion, @NotNull String platformType, int formatVersion) {
|
||||||
super(id, pinned, timestamp, saveCause, Map.of(), minecraftVersion, platformType, formatVersion);
|
super(id, pinned, timestamp, saveCause, serverName, Map.of(), minecraftVersion, platformType, formatVersion);
|
||||||
this.deserialized = data;
|
this.deserialized = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -384,7 +401,7 @@ public class DataSnapshot {
|
|||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
public DataSnapshot.Packed pack(@NotNull HuskSync plugin) {
|
public DataSnapshot.Packed pack(@NotNull HuskSync plugin) {
|
||||||
return new DataSnapshot.Packed(
|
return new DataSnapshot.Packed(
|
||||||
id, pinned, timestamp, saveCause, serializeData(plugin),
|
id, pinned, timestamp, saveCause, serverName, serializeData(plugin),
|
||||||
getMinecraftVersion(), platformType, formatVersion
|
getMinecraftVersion(), platformType, formatVersion
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -402,6 +419,7 @@ public class DataSnapshot {
|
|||||||
private final HuskSync plugin;
|
private final HuskSync plugin;
|
||||||
private UUID id;
|
private UUID id;
|
||||||
private SaveCause saveCause;
|
private SaveCause saveCause;
|
||||||
|
private String serverName;
|
||||||
private boolean pinned;
|
private boolean pinned;
|
||||||
private OffsetDateTime timestamp;
|
private OffsetDateTime timestamp;
|
||||||
private final Map<Identifier, Data> data;
|
private final Map<Identifier, Data> data;
|
||||||
@@ -412,6 +430,7 @@ public class DataSnapshot {
|
|||||||
this.data = new HashMap<>();
|
this.data = new HashMap<>();
|
||||||
this.timestamp = OffsetDateTime.now();
|
this.timestamp = OffsetDateTime.now();
|
||||||
this.id = UUID.randomUUID();
|
this.id = UUID.randomUUID();
|
||||||
|
this.serverName = plugin.getServerName();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -441,6 +460,19 @@ public class DataSnapshot {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the name of the server where this snapshot was created
|
||||||
|
*
|
||||||
|
* @param serverName The server name
|
||||||
|
* @return The builder
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
public Builder serverName(@NotNull String serverName) {
|
||||||
|
this.serverName = serverName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set whether the data should be pinned
|
* Set whether the data should be pinned
|
||||||
*
|
*
|
||||||
@@ -471,7 +503,10 @@ public class DataSnapshot {
|
|||||||
@NotNull
|
@NotNull
|
||||||
public Builder timestamp(@NotNull OffsetDateTime timestamp) {
|
public Builder timestamp(@NotNull OffsetDateTime timestamp) {
|
||||||
if (timestamp.isAfter(OffsetDateTime.now())) {
|
if (timestamp.isAfter(OffsetDateTime.now())) {
|
||||||
throw new IllegalArgumentException("Data snapshots cannot have a timestamp set in the future");
|
throw new IllegalArgumentException("Data snapshots cannot have a timestamp set in the future! "
|
||||||
|
+ "Make sure your database server time matches the server time.\n"
|
||||||
|
+ "Current game server timestamp: " + OffsetDateTime.now() + " / "
|
||||||
|
+ "Snapshot timestamp: " + timestamp);
|
||||||
}
|
}
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
return this;
|
return this;
|
||||||
@@ -686,6 +721,7 @@ public class DataSnapshot {
|
|||||||
pinned || plugin.getSettings().doAutoPin(saveCause),
|
pinned || plugin.getSettings().doAutoPin(saveCause),
|
||||||
timestamp,
|
timestamp,
|
||||||
saveCause,
|
saveCause,
|
||||||
|
serverName,
|
||||||
data,
|
data,
|
||||||
plugin.getMinecraftVersion(),
|
plugin.getMinecraftVersion(),
|
||||||
plugin.getPlatformType(),
|
plugin.getPlatformType(),
|
||||||
@@ -789,6 +825,11 @@ public class DataSnapshot {
|
|||||||
.replaceAll("_", " "), 18);
|
.replaceAll("_", " "), 18);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getLocale(@NotNull HuskSync plugin) {
|
||||||
|
return plugin.getLocales().getRawLocale("save_cause_" + name().toLowerCase())
|
||||||
|
.orElse(getDisplayName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,13 +23,12 @@ import net.william278.husksync.HuskSync;
|
|||||||
import net.william278.husksync.data.Data;
|
import net.william278.husksync.data.Data;
|
||||||
import net.william278.husksync.data.DataSnapshot;
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
import net.william278.husksync.util.Task;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
import java.util.Map;
|
||||||
import java.util.logging.Level;
|
import java.util.UUID;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles what should happen when events are fired
|
* Handles what should happen when events are fired
|
||||||
@@ -39,22 +38,8 @@ public abstract class EventListener {
|
|||||||
// The plugin instance
|
// The plugin instance
|
||||||
protected final HuskSync plugin;
|
protected final HuskSync plugin;
|
||||||
|
|
||||||
/**
|
|
||||||
* Set of UUIDs of "locked players", for which events will be canceled.
|
|
||||||
* </p>
|
|
||||||
* Players are locked while their items are being set (on join) or saved (on quit)
|
|
||||||
*/
|
|
||||||
private final Set<UUID> lockedPlayers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the plugin is currently being disabled
|
|
||||||
*/
|
|
||||||
private boolean disabling;
|
|
||||||
|
|
||||||
protected EventListener(@NotNull HuskSync plugin) {
|
protected EventListener(@NotNull HuskSync plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.lockedPlayers = new HashSet<>();
|
|
||||||
this.disabling = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,51 +51,8 @@ public abstract class EventListener {
|
|||||||
if (user.isNpc()) {
|
if (user.isNpc()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lockedPlayers.add(user.getUuid());
|
plugin.lockPlayer(user.getUuid());
|
||||||
|
plugin.getDataSyncer().setUserData(user);
|
||||||
plugin.runAsyncDelayed(() -> {
|
|
||||||
// Fetch from the database if the user isn't changing servers
|
|
||||||
if (!plugin.getRedisManager().getUserServerSwitch(user)) {
|
|
||||||
this.setUserFromDatabase(user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the user as soon as the source server has set the data to redis
|
|
||||||
final long MAX_ATTEMPTS = 16L;
|
|
||||||
final AtomicLong timesRun = new AtomicLong(0L);
|
|
||||||
final AtomicReference<Task.Repeating> task = new AtomicReference<>();
|
|
||||||
final Runnable runnable = () -> {
|
|
||||||
if (user.isOffline()) {
|
|
||||||
task.get().cancel();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (disabling || timesRun.getAndIncrement() > MAX_ATTEMPTS) {
|
|
||||||
task.get().cancel();
|
|
||||||
this.setUserFromDatabase(user);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
plugin.getRedisManager().getUserData(user).ifPresent(redisData -> {
|
|
||||||
task.get().cancel();
|
|
||||||
user.applySnapshot(redisData, DataSnapshot.UpdateCause.SYNCHRONIZED);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
task.set(plugin.getRepeatingTask(runnable, 10));
|
|
||||||
task.get().run();
|
|
||||||
|
|
||||||
}, Math.max(0, plugin.getSettings().getNetworkLatencyMilliseconds() / 50L));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a user's data from the database
|
|
||||||
*
|
|
||||||
* @param user The user to set the data for
|
|
||||||
*/
|
|
||||||
private void setUserFromDatabase(@NotNull OnlineUser user) {
|
|
||||||
plugin.getDatabase().getLatestSnapshot(user).ifPresentOrElse(
|
|
||||||
snapshot -> user.applySnapshot(snapshot, DataSnapshot.UpdateCause.SYNCHRONIZED),
|
|
||||||
() -> user.completeSync(true, DataSnapshot.UpdateCause.NEW_USER, plugin)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,27 +61,11 @@ public abstract class EventListener {
|
|||||||
* @param user The {@link OnlineUser} to handle
|
* @param user The {@link OnlineUser} to handle
|
||||||
*/
|
*/
|
||||||
protected final void handlePlayerQuit(@NotNull OnlineUser user) {
|
protected final void handlePlayerQuit(@NotNull OnlineUser user) {
|
||||||
// Players quitting have their data manually saved when the plugin is disabled
|
if (user.isNpc() || plugin.isDisabling() || plugin.isLocked(user.getUuid())) {
|
||||||
if (disabling) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
plugin.lockPlayer(user.getUuid());
|
||||||
// Don't sync players awaiting synchronization
|
plugin.runAsync(() -> plugin.getDataSyncer().saveUserData(user));
|
||||||
if (lockedPlayers.contains(user.getUuid()) || user.isNpc()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle disconnection
|
|
||||||
try {
|
|
||||||
lockedPlayers.add(user.getUuid());
|
|
||||||
plugin.getRedisManager().setUserServerSwitch(user).thenRun(() -> {
|
|
||||||
final DataSnapshot.Packed data = user.createSnapshot(DataSnapshot.SaveCause.DISCONNECT);
|
|
||||||
plugin.getRedisManager().setUserData(user, data);
|
|
||||||
plugin.getDatabase().addSnapshot(user, data);
|
|
||||||
});
|
|
||||||
} catch (Throwable e) {
|
|
||||||
plugin.log(Level.SEVERE, "An exception occurred handling a player disconnection", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -148,11 +74,11 @@ public abstract class EventListener {
|
|||||||
* @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 saveOnWorldSave(@NotNull List<OnlineUser> usersInWorld) {
|
protected final void saveOnWorldSave(@NotNull List<OnlineUser> usersInWorld) {
|
||||||
if (disabling || !plugin.getSettings().doSaveOnWorldSave()) {
|
if (plugin.isDisabling() || !plugin.getSettings().doSaveOnWorldSave()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
usersInWorld.stream()
|
usersInWorld.stream()
|
||||||
.filter(user -> !lockedPlayers.contains(user.getUuid()) && !user.isNpc())
|
.filter(user -> !plugin.isLocked(user.getUuid()) && !user.isNpc())
|
||||||
.forEach(user -> plugin.getDatabase().addSnapshot(
|
.forEach(user -> plugin.getDatabase().addSnapshot(
|
||||||
user, user.createSnapshot(DataSnapshot.SaveCause.WORLD_SAVE)
|
user, user.createSnapshot(DataSnapshot.SaveCause.WORLD_SAVE)
|
||||||
));
|
));
|
||||||
@@ -162,16 +88,16 @@ public abstract class EventListener {
|
|||||||
* Handles the saving of data when a player dies
|
* Handles the saving of data when a player dies
|
||||||
*
|
*
|
||||||
* @param user The user who died
|
* @param user The user who died
|
||||||
* @param drops The items that this user would have dropped
|
* @param items The items that should be saved for this user on their death
|
||||||
*/
|
*/
|
||||||
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull Data.Items drops) {
|
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull Data.Items items) {
|
||||||
if (disabling || !plugin.getSettings().doSaveOnDeath() || lockedPlayers.contains(user.getUuid()) || user.isNpc()
|
if (plugin.isDisabling() || !plugin.getSettings().doSaveOnDeath() || plugin.isLocked(user.getUuid())
|
||||||
|| (!plugin.getSettings().doSaveEmptyDropsOnDeath() && drops.isEmpty())) {
|
|| user.isNpc() || (!plugin.getSettings().doSaveEmptyDeathItems() && items.isEmpty())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final DataSnapshot.Packed snapshot = user.createSnapshot(DataSnapshot.SaveCause.DEATH);
|
final DataSnapshot.Packed snapshot = user.createSnapshot(DataSnapshot.SaveCause.DEATH);
|
||||||
snapshot.edit(plugin, (data -> data.getInventory().ifPresent(inventory -> inventory.setContents(drops))));
|
snapshot.edit(plugin, (data -> data.getInventory().ifPresent(inventory -> inventory.setContents(items))));
|
||||||
plugin.getDatabase().addSnapshot(user, snapshot);
|
plugin.getDatabase().addSnapshot(user, snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,20 +108,18 @@ public abstract class EventListener {
|
|||||||
* @return Whether the event should be canceled
|
* @return Whether the event should be canceled
|
||||||
*/
|
*/
|
||||||
protected final boolean cancelPlayerEvent(@NotNull UUID userUuid) {
|
protected final boolean cancelPlayerEvent(@NotNull UUID userUuid) {
|
||||||
return disabling || lockedPlayers.contains(userUuid);
|
return plugin.isDisabling() || plugin.isLocked(userUuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle the plugin disabling
|
* Handle the plugin disabling
|
||||||
*/
|
*/
|
||||||
public final void handlePluginDisable() {
|
public final void handlePluginDisable() {
|
||||||
disabling = true;
|
// Save for all online players
|
||||||
|
|
||||||
// Save data for all online users
|
|
||||||
plugin.getOnlineUsers().stream()
|
plugin.getOnlineUsers().stream()
|
||||||
.filter(user -> !lockedPlayers.contains(user.getUuid()) && !user.isNpc())
|
.filter(user -> !plugin.isLocked(user.getUuid()) && !user.isNpc())
|
||||||
.forEach(user -> {
|
.forEach(user -> {
|
||||||
lockedPlayers.add(user.getUuid());
|
plugin.lockPlayer(user.getUuid());
|
||||||
plugin.getDatabase().addSnapshot(user, user.createSnapshot(DataSnapshot.SaveCause.SERVER_SHUTDOWN));
|
plugin.getDatabase().addSnapshot(user, user.createSnapshot(DataSnapshot.SaveCause.SERVER_SHUTDOWN));
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -204,10 +128,6 @@ public abstract class EventListener {
|
|||||||
plugin.getRedisManager().terminate();
|
plugin.getRedisManager().terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Set<UUID> getLockedPlayers() {
|
|
||||||
return this.lockedPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents priorities for events that HuskSync listens to
|
* Represents priorities for events that HuskSync listens to
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public enum RedisKeyType {
|
public enum RedisKeyType {
|
||||||
CACHE(60 * 60 * 24),
|
|
||||||
DATA_UPDATE(10),
|
DATA_UPDATE(10),
|
||||||
SERVER_SWITCH(10);
|
SERVER_SWITCH(10),
|
||||||
|
DATA_CHECKOUT(60 * 60 * 24 * 7 * 52);
|
||||||
|
|
||||||
private final int timeToLive;
|
private final int timeToLive;
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
jedis.subscribe(
|
jedis.subscribe(
|
||||||
this,
|
this,
|
||||||
Arrays.stream(RedisMessageType.values())
|
Arrays.stream(RedisMessage.Type.values())
|
||||||
.map(type -> type.getMessageChannel(clusterId))
|
.map(type -> type.getMessageChannel(clusterId))
|
||||||
.toArray(String[]::new)
|
.toArray(String[]::new)
|
||||||
);
|
);
|
||||||
@@ -101,7 +101,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessage(@NotNull String channel, @NotNull String message) {
|
public void onMessage(@NotNull String channel, @NotNull String message) {
|
||||||
final RedisMessageType messageType = RedisMessageType.getTypeFromChannel(channel, clusterId).orElse(null);
|
final RedisMessage.Type messageType = RedisMessage.Type.getTypeFromChannel(channel, clusterId).orElse(null);
|
||||||
if (messageType == null) {
|
if (messageType == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
user -> RedisMessage.create(
|
user -> RedisMessage.create(
|
||||||
UUID.fromString(new String(redisMessage.getPayload(), StandardCharsets.UTF_8)),
|
UUID.fromString(new String(redisMessage.getPayload(), StandardCharsets.UTF_8)),
|
||||||
user.createSnapshot(DataSnapshot.SaveCause.INVENTORY_COMMAND).asBytes(plugin)
|
user.createSnapshot(DataSnapshot.SaveCause.INVENTORY_COMMAND).asBytes(plugin)
|
||||||
).dispatch(plugin, RedisMessageType.RETURN_USER_DATA)
|
).dispatch(plugin, RedisMessage.Type.RETURN_USER_DATA)
|
||||||
);
|
);
|
||||||
case RETURN_USER_DATA -> {
|
case RETURN_USER_DATA -> {
|
||||||
final CompletableFuture<Optional<DataSnapshot.Packed>> future = pendingRequests.get(
|
final CompletableFuture<Optional<DataSnapshot.Packed>> future = pendingRequests.get(
|
||||||
@@ -142,7 +142,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
public void sendUserDataUpdate(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
public void sendUserDataUpdate(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
plugin.runAsync(() -> {
|
plugin.runAsync(() -> {
|
||||||
final RedisMessage redisMessage = RedisMessage.create(user.getUuid(), data.asBytes(plugin));
|
final RedisMessage redisMessage = RedisMessage.create(user.getUuid(), data.asBytes(plugin));
|
||||||
redisMessage.dispatch(plugin, RedisMessageType.UPDATE_USER_DATA);
|
redisMessage.dispatch(plugin, RedisMessage.Type.UPDATE_USER_DATA);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
user.getUuid(),
|
user.getUuid(),
|
||||||
requestId.toString().getBytes(StandardCharsets.UTF_8)
|
requestId.toString().getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
redisMessage.dispatch(plugin, RedisMessageType.REQUEST_USER_DATA);
|
redisMessage.dispatch(plugin, RedisMessage.Type.REQUEST_USER_DATA);
|
||||||
});
|
});
|
||||||
return future.orTimeout(
|
return future.orTimeout(
|
||||||
plugin.getSettings().getNetworkLatencyMilliseconds(),
|
plugin.getSettings().getNetworkLatencyMilliseconds(),
|
||||||
@@ -180,44 +180,96 @@ public class RedisManager extends JedisPubSub {
|
|||||||
* @param user the user to set data for
|
* @param user the user to set data for
|
||||||
* @param data the user's data to set
|
* @param data the user's data to set
|
||||||
*/
|
*/
|
||||||
|
@Blocking
|
||||||
public void setUserData(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
public void setUserData(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
plugin.runAsync(() -> {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
jedis.setex(
|
||||||
jedis.setex(
|
getKey(RedisKeyType.DATA_UPDATE, user.getUuid(), clusterId),
|
||||||
getKey(RedisKeyType.DATA_UPDATE, user.getUuid(), clusterId),
|
RedisKeyType.DATA_UPDATE.getTimeToLive(),
|
||||||
RedisKeyType.DATA_UPDATE.getTimeToLive(),
|
data.asBytes(plugin)
|
||||||
data.asBytes(plugin)
|
);
|
||||||
|
plugin.debug(String.format("[%s] Set %s key to redis at: %s", user.getUsername(),
|
||||||
|
RedisKeyType.DATA_UPDATE.name(), new SimpleDateFormat("mm:ss.SSS").format(new Date())));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
plugin.log(Level.SEVERE, "An exception occurred setting a user's server switch", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Blocking
|
||||||
|
public void setUserCheckedOut(@NotNull User user, boolean checkedOut) {
|
||||||
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
if (checkedOut) {
|
||||||
|
jedis.set(
|
||||||
|
getKey(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId),
|
||||||
|
plugin.getServerName().getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
plugin.debug(String.format("[%s] Set %s key to redis at: %s", user.getUsername(),
|
} else {
|
||||||
RedisKeyType.DATA_UPDATE.name(), new SimpleDateFormat("mm:ss.SSS").format(new Date())));
|
jedis.del(getKey(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId));
|
||||||
} catch (Throwable e) {
|
|
||||||
plugin.log(Level.SEVERE, "An exception occurred setting a user's server switch", e);
|
|
||||||
}
|
}
|
||||||
});
|
plugin.debug(String.format("[%s] %s %s key to redis at: %s",
|
||||||
|
checkedOut ? "set" : "removed", user.getUsername(), RedisKeyType.DATA_CHECKOUT.name(),
|
||||||
|
new SimpleDateFormat("mm:ss.SSS").format(new Date())));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
plugin.log(Level.SEVERE, "An exception occurred setting a user's server switch", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Blocking
|
||||||
|
public Optional<String> getUserCheckedOut(@NotNull User user) {
|
||||||
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
final byte[] key = getKey(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId);
|
||||||
|
final byte[] readData = jedis.get(key);
|
||||||
|
if (readData != null) {
|
||||||
|
plugin.debug("[" + user.getUsername() + "] Successfully read "
|
||||||
|
+ RedisKeyType.DATA_CHECKOUT.name() + " key from redis at: " +
|
||||||
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
|
return Optional.of(new String(readData, StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
plugin.log(Level.SEVERE, "An exception occurred fetching a user's checkout key from redis", e);
|
||||||
|
}
|
||||||
|
plugin.debug("[" + user.getUsername() + "] Could not read " +
|
||||||
|
RedisKeyType.DATA_CHECKOUT.name() + " key from redis at: " +
|
||||||
|
new SimpleDateFormat("mm:ss.SSS").format(new Date()));
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Blocking
|
||||||
|
public void clearUsersCheckedOutOnServer() {
|
||||||
|
final String keyFormat = String.format("%s*", RedisKeyType.DATA_CHECKOUT.getKeyPrefix(clusterId));
|
||||||
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
final Set<String> keys = jedis.keys(keyFormat);
|
||||||
|
if (keys == null) {
|
||||||
|
plugin.log(Level.WARNING, "Checkout key set returned null from jedis during clearing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (String key : keys) {
|
||||||
|
if (jedis.get(key).equals(plugin.getServerName())) {
|
||||||
|
jedis.del(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
plugin.log(Level.SEVERE, "An exception occurred clearing users checked out on this server", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a user's server switch to the Redis server
|
* Set a user's server switch to the Redis server
|
||||||
*
|
*
|
||||||
* @param user the user to set the server switch for
|
* @param user the user to set the server switch for
|
||||||
* @return a future returning void when complete
|
|
||||||
*/
|
*/
|
||||||
public CompletableFuture<Void> setUserServerSwitch(@NotNull User user) {
|
@Blocking
|
||||||
final CompletableFuture<Void> future = new CompletableFuture<>();
|
public void setUserServerSwitch(@NotNull User user) {
|
||||||
plugin.runAsync(() -> {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
jedis.setex(
|
||||||
jedis.setex(
|
getKey(RedisKeyType.SERVER_SWITCH, user.getUuid(), clusterId),
|
||||||
getKey(RedisKeyType.SERVER_SWITCH, user.getUuid(), clusterId),
|
RedisKeyType.SERVER_SWITCH.getTimeToLive(), new byte[0]
|
||||||
RedisKeyType.SERVER_SWITCH.getTimeToLive(), new byte[0]
|
);
|
||||||
);
|
plugin.debug(String.format("[%s] Set %s key to redis at: %s", user.getUsername(),
|
||||||
future.complete(null);
|
RedisKeyType.SERVER_SWITCH.name(), new SimpleDateFormat("mm:ss.SSS").format(new Date())));
|
||||||
plugin.debug(String.format("[%s] Set %s key to redis at: %s", user.getUsername(),
|
} catch (Throwable e) {
|
||||||
RedisKeyType.SERVER_SWITCH.name(), new SimpleDateFormat("mm:ss.SSS").format(new Date())));
|
plugin.log(Level.SEVERE, "An exception occurred setting a user's server switch", e);
|
||||||
} catch (Throwable e) {
|
}
|
||||||
plugin.log(Level.SEVERE, "An exception occurred setting a user's server switch", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -226,6 +278,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
* @param user The user to fetch data for
|
* @param user The user to fetch data for
|
||||||
* @return The user's data, if it's present on the database. Otherwise, an empty optional.
|
* @return The user's data, if it's present on the database. Otherwise, an empty optional.
|
||||||
*/
|
*/
|
||||||
|
@Blocking
|
||||||
public Optional<DataSnapshot.Packed> getUserData(@NotNull User user) {
|
public Optional<DataSnapshot.Packed> getUserData(@NotNull User user) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.getUuid(), clusterId);
|
final byte[] key = getKey(RedisKeyType.DATA_UPDATE, user.getUuid(), clusterId);
|
||||||
@@ -251,6 +304,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Blocking
|
||||||
public boolean getUserServerSwitch(@NotNull User user) {
|
public boolean getUserServerSwitch(@NotNull User user) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.getUuid(), clusterId);
|
final byte[] key = getKey(RedisKeyType.SERVER_SWITCH, user.getUuid(), clusterId);
|
||||||
@@ -274,6 +328,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Blocking
|
||||||
public void terminate() {
|
public void terminate() {
|
||||||
if (jedisPool != null) {
|
if (jedisPool != null) {
|
||||||
if (!jedisPool.isClosed()) {
|
if (!jedisPool.isClosed()) {
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ import net.william278.husksync.HuskSync;
|
|||||||
import net.william278.husksync.adapter.Adaptable;
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class RedisMessage implements Adaptable {
|
public class RedisMessage implements Adaptable {
|
||||||
@@ -53,7 +56,7 @@ public class RedisMessage implements Adaptable {
|
|||||||
return plugin.getGson().fromJson(json, RedisMessage.class);
|
return plugin.getGson().fromJson(json, RedisMessage.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void dispatch(@NotNull HuskSync plugin, @NotNull RedisMessageType type) {
|
public void dispatch(@NotNull HuskSync plugin, @NotNull Type type) {
|
||||||
plugin.runAsync(() -> plugin.getRedisManager().sendMessage(
|
plugin.runAsync(() -> plugin.getRedisManager().sendMessage(
|
||||||
type.getMessageChannel(plugin.getSettings().getClusterId()),
|
type.getMessageChannel(plugin.getSettings().getClusterId()),
|
||||||
plugin.getGson().toJson(this)
|
plugin.getGson().toJson(this)
|
||||||
@@ -77,4 +80,27 @@ public class RedisMessage implements Adaptable {
|
|||||||
this.payload = payload;
|
this.payload = payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
|
||||||
|
UPDATE_USER_DATA,
|
||||||
|
REQUEST_USER_DATA,
|
||||||
|
RETURN_USER_DATA;
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public String getMessageChannel(@NotNull String clusterId) {
|
||||||
|
return String.format(
|
||||||
|
"%s:%s:%s",
|
||||||
|
RedisManager.KEY_NAMESPACE.toLowerCase(Locale.ENGLISH),
|
||||||
|
clusterId.toLowerCase(Locale.ENGLISH),
|
||||||
|
name().toLowerCase(Locale.ENGLISH)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<Type> getTypeFromChannel(@NotNull String channel, @NotNull String clusterId) {
|
||||||
|
return Arrays.stream(values())
|
||||||
|
.filter(messageType -> messageType.getMessageChannel(clusterId).equalsIgnoreCase(channel))
|
||||||
|
.findFirst();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
|
||||||
*
|
|
||||||
* Copyright (c) William278 <will27528@gmail.com>
|
|
||||||
* Copyright (c) contributors
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.william278.husksync.redis;
|
|
||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public enum RedisMessageType {
|
|
||||||
|
|
||||||
UPDATE_USER_DATA,
|
|
||||||
REQUEST_USER_DATA,
|
|
||||||
RETURN_USER_DATA;
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getMessageChannel(@NotNull String clusterId) {
|
|
||||||
return String.format(
|
|
||||||
"%s:%s:%s",
|
|
||||||
RedisManager.KEY_NAMESPACE.toLowerCase(Locale.ENGLISH),
|
|
||||||
clusterId.toLowerCase(Locale.ENGLISH),
|
|
||||||
name().toLowerCase(Locale.ENGLISH)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Optional<RedisMessageType> getTypeFromChannel(@NotNull String channel, @NotNull String clusterId) {
|
|
||||||
return Arrays.stream(values())
|
|
||||||
.filter(messageType -> messageType.getMessageChannel(clusterId).equalsIgnoreCase(channel))
|
|
||||||
.findFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.sync;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.api.HuskSyncAPI;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import net.william278.husksync.util.Task;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the synchronization of data when a player changes servers or logs in
|
||||||
|
*
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public abstract class DataSyncer {
|
||||||
|
private static final long BASE_LISTEN_ATTEMPTS = 16;
|
||||||
|
private static final long LISTEN_DELAY = 10;
|
||||||
|
|
||||||
|
protected final HuskSync plugin;
|
||||||
|
private final long maxListenAttempts;
|
||||||
|
|
||||||
|
@ApiStatus.Internal
|
||||||
|
protected DataSyncer(@NotNull HuskSync plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.maxListenAttempts = getMaxListenAttempts();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API-exposed constructor for a {@link DataSyncer}
|
||||||
|
*
|
||||||
|
* @param api instance of the {@link HuskSyncAPI}
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public DataSyncer(@NotNull HuskSyncAPI api) {
|
||||||
|
this(api.getPlugin());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is enabled
|
||||||
|
*/
|
||||||
|
public void initialize() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the plugin is disabled
|
||||||
|
*/
|
||||||
|
public void terminate() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a user's data should be fetched and applied to them
|
||||||
|
*
|
||||||
|
* @param user the user to fetch data for
|
||||||
|
*/
|
||||||
|
public abstract void setUserData(@NotNull OnlineUser user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a user's data should be serialized and saved
|
||||||
|
*
|
||||||
|
* @param user the user to save
|
||||||
|
*/
|
||||||
|
public abstract void saveUserData(@NotNull OnlineUser user);
|
||||||
|
|
||||||
|
// Calculates the max attempts the system should listen for user data for based on the latency value
|
||||||
|
private long getMaxListenAttempts() {
|
||||||
|
return BASE_LISTEN_ATTEMPTS + (
|
||||||
|
(Math.max(100, plugin.getSettings().getNetworkLatencyMilliseconds()) / 1000) * 20 / LISTEN_DELAY
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set a user's data from the database, or set them as a new user
|
||||||
|
@ApiStatus.Internal
|
||||||
|
protected void setUserFromDatabase(@NotNull OnlineUser user) {
|
||||||
|
plugin.getDatabase().getLatestSnapshot(user).ifPresentOrElse(
|
||||||
|
snapshot -> user.applySnapshot(snapshot, DataSnapshot.UpdateCause.SYNCHRONIZED),
|
||||||
|
() -> user.completeSync(true, DataSnapshot.UpdateCause.NEW_USER, plugin)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continuously listen for data from Redis
|
||||||
|
@ApiStatus.Internal
|
||||||
|
protected void listenForRedisData(@NotNull OnlineUser user, @NotNull Supplier<Boolean> completionSupplier) {
|
||||||
|
final AtomicLong timesRun = new AtomicLong(0L);
|
||||||
|
final AtomicReference<Task.Repeating> task = new AtomicReference<>();
|
||||||
|
final Runnable runnable = () -> {
|
||||||
|
if (user.isOffline()) {
|
||||||
|
task.get().cancel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (plugin.isDisabling() || timesRun.getAndIncrement() > maxListenAttempts) {
|
||||||
|
task.get().cancel();
|
||||||
|
setUserFromDatabase(user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionSupplier.get()) {
|
||||||
|
task.get().cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
task.set(plugin.getRepeatingTask(runnable, LISTEN_DELAY));
|
||||||
|
task.get().run();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the different available default modes of {@link DataSyncer}
|
||||||
|
*
|
||||||
|
* @since 3.1
|
||||||
|
*/
|
||||||
|
public enum Mode {
|
||||||
|
DELAY(DelayDataSyncer::new),
|
||||||
|
LOCKSTEP(LockstepDataSyncer::new);
|
||||||
|
|
||||||
|
private final Function<HuskSync, ? extends DataSyncer> supplier;
|
||||||
|
|
||||||
|
Mode(@NotNull Function<HuskSync, ? extends DataSyncer> supplier) {
|
||||||
|
this.supplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public DataSyncer create(@NotNull HuskSync plugin) {
|
||||||
|
return supplier.apply(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.sync;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A data syncer which applies a network delay before checking the presence of user data
|
||||||
|
*/
|
||||||
|
public class DelayDataSyncer extends DataSyncer {
|
||||||
|
|
||||||
|
public DelayDataSyncer(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setUserData(@NotNull OnlineUser user) {
|
||||||
|
plugin.runAsyncDelayed(
|
||||||
|
() -> {
|
||||||
|
// Fetch from the database if the user isn't changing servers
|
||||||
|
if (!plugin.getRedisManager().getUserServerSwitch(user)) {
|
||||||
|
this.setUserFromDatabase(user);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen for the data to be updated
|
||||||
|
this.listenForRedisData(
|
||||||
|
user,
|
||||||
|
() -> plugin.getRedisManager().getUserData(user).map(data -> {
|
||||||
|
user.applySnapshot(data, DataSnapshot.UpdateCause.SYNCHRONIZED);
|
||||||
|
return true;
|
||||||
|
}).orElse(false)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Math.max(0, plugin.getSettings().getNetworkLatencyMilliseconds() / 50L)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveUserData(@NotNull OnlineUser user) {
|
||||||
|
plugin.runAsync(() -> {
|
||||||
|
plugin.getRedisManager().setUserServerSwitch(user);
|
||||||
|
final DataSnapshot.Packed data = user.createSnapshot(DataSnapshot.SaveCause.DISCONNECT);
|
||||||
|
plugin.getRedisManager().setUserData(user, data);
|
||||||
|
plugin.getDatabase().addSnapshot(user, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.sync;
|
||||||
|
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class LockstepDataSyncer extends DataSyncer {
|
||||||
|
|
||||||
|
public LockstepDataSyncer(@NotNull HuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
plugin.getRedisManager().clearUsersCheckedOutOnServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void terminate() {
|
||||||
|
plugin.getRedisManager().clearUsersCheckedOutOnServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume their data when they are checked in
|
||||||
|
@Override
|
||||||
|
public void setUserData(@NotNull OnlineUser user) {
|
||||||
|
this.listenForRedisData(user, () -> {
|
||||||
|
if (plugin.getRedisManager().getUserCheckedOut(user).isEmpty()) {
|
||||||
|
plugin.getRedisManager().setUserCheckedOut(user, true);
|
||||||
|
plugin.getRedisManager().getUserData(user).ifPresentOrElse(
|
||||||
|
data -> user.applySnapshot(data, DataSnapshot.UpdateCause.SYNCHRONIZED),
|
||||||
|
() -> this.setUserFromDatabase(user)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveUserData(@NotNull OnlineUser user) {
|
||||||
|
plugin.runAsync(() -> {
|
||||||
|
final DataSnapshot.Packed data = user.createSnapshot(DataSnapshot.SaveCause.DISCONNECT);
|
||||||
|
plugin.getRedisManager().setUserData(user, data);
|
||||||
|
plugin.getRedisManager().setUserCheckedOut(user, false);
|
||||||
|
plugin.getDatabase().addSnapshot(user, data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -158,7 +158,7 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo
|
|||||||
}
|
}
|
||||||
plugin.fireEvent(
|
plugin.fireEvent(
|
||||||
plugin.getSyncCompleteEvent(this),
|
plugin.getSyncCompleteEvent(this),
|
||||||
(event) -> plugin.getLockedPlayers().remove(getUuid())
|
(event) -> plugin.unlockPlayer(getUuid())
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
cause.getFailedLocale(plugin).ifPresent(this::sendMessage);
|
cause.getFailedLocale(plugin).ifPresent(this::sendMessage);
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ public class DataSnapshotList {
|
|||||||
.ofPattern("dd/MM/yyyy, HH:mm")),
|
.ofPattern("dd/MM/yyyy, HH:mm")),
|
||||||
snapshot.getTimestamp().format(DateTimeFormatter
|
snapshot.getTimestamp().format(DateTimeFormatter
|
||||||
.ofPattern("MMM dd yyyy, HH:mm:ss.SSS")),
|
.ofPattern("MMM dd yyyy, HH:mm:ss.SSS")),
|
||||||
snapshot.getSaveCause().getDisplayName(),
|
snapshot.getSaveCause().getLocale(plugin),
|
||||||
String.format("%.2fKiB", snapshot.getFileSize(plugin) / 1024f))
|
String.format("%.2fKiB", snapshot.getFileSize(plugin) / 1024f))
|
||||||
.orElse("• " + snapshot.getId())).toList(),
|
.orElse("• " + snapshot.getId())).toList(),
|
||||||
plugin.getLocales().getBaseChatList(6)
|
plugin.getLocales().getBaseChatList(6)
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ public class DataSnapshotOverview {
|
|||||||
locales.getLocale("data_manager_pinned")
|
locales.getLocale("data_manager_pinned")
|
||||||
.ifPresent(user::sendMessage);
|
.ifPresent(user::sendMessage);
|
||||||
}
|
}
|
||||||
locales.getLocale("data_manager_cause", snapshot.getSaveCause().getDisplayName())
|
locales.getLocale("data_manager_cause", snapshot.getSaveCause().getLocale(plugin))
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
|
locales.getLocale("data_manager_server", snapshot.getServerName())
|
||||||
.ifPresent(user::sendMessage);
|
.ifPresent(user::sendMessage);
|
||||||
|
|
||||||
// User status data, if present in the snapshot
|
// User status data, if present in the snapshot
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ data_manager_title: '[Преглеждане потребителският сн
|
|||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Клеймо на Версията:\n&8Когато данните са били запазени)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Клеймо на Версията:\n&8Когато данните са били запазени)'
|
||||||
data_manager_pinned: '[※ Закачен снапшот](#d8ff2b show_text=&7Закачен:\n&8Снапшота на този потребител няма да бъде автоматично завъртан.)'
|
data_manager_pinned: '[※ Закачен снапшот](#d8ff2b show_text=&7Закачен:\n&8Снапшота на този потребител няма да бъде автоматично завъртан.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Причина на Запазване:\n&8Какво е накарало данните да бъдат запазени)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Причина на Запазване:\n&8Какво е накарало данните да бъдат запазени)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Точки кръв) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Точки глад) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Ниво опит) [🏹 %5%](dark_aqua show_text=&7Режим на игра)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Точки кръв) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Точки глад) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Ниво опит) [🏹 %5%](dark_aqua show_text=&7Режим на игра)'
|
||||||
data_manager_advancements_statistics: '[⭐ Напредъци: %1%](color=#ffc43b-#f5c962 show_text=&7Напредъци, в които имате прогрес:\n&8%2%) [⌛ Изиграно Време: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Изиграно време в играта\n&8⚠ Базирано на статистики от играта)\n'
|
data_manager_advancements_statistics: '[⭐ Напредъци: %1%](color=#ffc43b-#f5c962 show_text=&7Напредъци, в които имате прогрес:\n&8%2%) [⌛ Изиграно Време: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Изиграно време в играта\n&8⚠ Базирано на статистики от играта)\n'
|
||||||
@@ -33,6 +34,17 @@ list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Презаредихме конфигурацията и файловете със съобщения.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Презаредихме конфигурацията и файловете със съобщения.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ inventory_viewer_opened: '[Du siehst den Schnappschuss des Inventares von](#00fb
|
|||||||
ender_chest_viewer_opened: '[Du siehst den Schnappschuss der Endertruhe von](#00fb9a) [%1%](#00fb9a bold) [von ⌚ %2%](#00fb9a)'
|
ender_chest_viewer_opened: '[Du siehst den Schnappschuss der Endertruhe von](#00fb9a) [%1%](#00fb9a bold) [von ⌚ %2%](#00fb9a)'
|
||||||
data_update_complete: '[🔔 Deine Daten wurden aktualisiert!](#00fb9a)'
|
data_update_complete: '[🔔 Deine Daten wurden aktualisiert!](#00fb9a)'
|
||||||
data_update_failed: '[🔔 Ein Fehler ist beim Aktualisieren deiner Daten aufgetreten! Bitte kontaktiere einen Administrator.](#ff7e5e)'
|
data_update_failed: '[🔔 Ein Fehler ist beim Aktualisieren deiner Daten aufgetreten! Bitte kontaktiere einen Administrator.](#ff7e5e)'
|
||||||
user_registration_complete: '[⭐ User registration complete!](#00fb9a)'
|
user_registration_complete: '[⭐ Benutzer-Registrierung abgeschlossen!](#00fb9a)'
|
||||||
data_manager_title: '[Du siehst den Nutzerdaten-Schnappschuss](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für %3%](#00fb9a bold show_text=&7Spieler-UUID:\n&8%4%)[:](#00fb9a)'
|
data_manager_title: '[Du siehst den Nutzerdaten-Schnappschuss](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für %3%](#00fb9a bold show_text=&7Spieler-UUID:\n&8%4%)[:](#00fb9a)'
|
||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Versions-Zeitstempel:\n&8Zeitpunkt der Speicherung der Daten)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Versions-Zeitstempel:\n&8Zeitpunkt der Speicherung der Daten)'
|
||||||
data_manager_pinned: '[※ Schnappschuss angeheftet](#d8ff2b show_text=&7Angeheftet:\n&8Dieser Nutzerdaten-Schnappschuss wird nicht automatisch rotiert.)'
|
data_manager_pinned: '[※ Schnappschuss angeheftet](#d8ff2b show_text=&7Angeheftet:\n&8Dieser Nutzerdaten-Schnappschuss wird nicht automatisch rotiert.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Speicherungsgrund:\n&8Der Grund für das Speichern der Daten)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Speicherungsgrund:\n&8Der Grund für das Speichern der Daten)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name des Servers, auf dem die Daten gespeichert wurden)'
|
||||||
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Schnappschuss-Größe:\n&8Geschätzte Dateigröße des Schnappschusses (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Lebenspunkte) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hungerpunkte) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP-Level) [🏹 %5%](dark_aqua show_text=&7Spielmodus)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Lebenspunkte) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hungerpunkte) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP-Level) [🏹 %5%](dark_aqua show_text=&7Spielmodus)'
|
||||||
data_manager_advancements_statistics: '[⭐ Erfolge: %1%](color=#ffc43b-#f5c962 show_text=&7Erfolge in denen du Fortschritt gemacht hast:\n&8%2%) [⌛ Spielzeit: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Deine verbrachte Zeit im Spiel\n&8⚠ Basierend auf Spielstatistiken)\n'
|
data_manager_advancements_statistics: '[⭐ Erfolge: %1%](color=#ffc43b-#f5c962 show_text=&7Erfolge in denen du Fortschritt gemacht hast:\n&8%2%) [⌛ Spielzeit: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Deine verbrachte Zeit im Spiel\n&8⚠ Basierend auf Spielstatistiken)\n'
|
||||||
data_manager_item_buttons: '[Sehen:](gray) [[🪣 Inventar…]](color=#a17b5f-#f5b98c show_text=&7Klicke zum Ansehen run_command=/inventory %1% %2%) [[⌀ Endertruhe…]](#b649c4-#d254ff show_text=&7Klicke zum Ansehen run_command=/enderchest %1% %2%)'
|
data_manager_item_buttons: '[Sehen:](gray) [[🪣 Inventar…]](color=#a17b5f-#f5b98c show_text=&7Klicke zum Ansehen run_command=/inventory %1% %2%) [[⌀ Endertruhe…]](#b649c4-#d254ff show_text=&7Klicke zum Ansehen run_command=/enderchest %1% %2%)'
|
||||||
@@ -19,7 +20,7 @@ data_manager_management_buttons: '[Verwalten:](gray) [[❌ Löschen…]](#ff3300
|
|||||||
data_manager_system_buttons: '[System:](gray) [[⏷ Daten-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss in eine Datei zu speichern.\n&8Daten-Dumps können unter ~/plugins/HuskSync/dumps/ gefunden werden. run_command=/husksync:userdata dump %1% %2% file) [[☂ Web-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss auf den mc-logs Service hochzuladen.\n&8Du erhältst dann eine URL, die die Daten enthält. run_command=/husksync:userdata dump %1% %2% web)'
|
data_manager_system_buttons: '[System:](gray) [[⏷ Daten-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss in eine Datei zu speichern.\n&8Daten-Dumps können unter ~/plugins/HuskSync/dumps/ gefunden werden. run_command=/husksync:userdata dump %1% %2% file) [[☂ Web-Dump…]](dark_gray show_text=&7Klicke, um diesen rohen Nutzerdaten-Schnappschuss auf den mc-logs Service hochzuladen.\n&8Du erhältst dann eine URL, die die Daten enthält. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
data_manager_advancements_preview_remaining: 'und %1% weitere…'
|
data_manager_advancements_preview_remaining: 'und %1% weitere…'
|
||||||
data_list_title: '[Nutzerdaten-Schnappschüsse von %1%:](#00fb9a) [(%2%-%3% von](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
data_list_title: '[Nutzerdaten-Schnappschüsse von %1%:](#00fb9a) [(%2%-%3% von](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
data_list_item: '[%1%](gray show_text=&7User Data Snapshot for %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Pinned:\n&8Pinned snapshots won''t be automatically rotated. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Version timestamp:&7\n&8When the data was saved\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:&7\n&8Estimated file size of the snapshot (in KiB) run_command=/userdata view %2% %3%)'
|
data_list_item: '[%1%](gray show_text=&7Nutzerdaten-Schnappschuss für %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Angeheftet:\n&8Angeheftete Schnappschüsse werden nicht automatisch rotiert. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Versions-Zeitstempel:&7\n&8Zeitpunkt der Speicherung der Daten\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Speicherungsgrund:\n&8Grund für das Speichern der Daten run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Schnappschuss-Größe:&7\n&8Geschätzte Dateigröße des Schnappschusses (in KiB) run_command=/userdata view %2% %3%)'
|
||||||
data_deleted: '[❌ Nutzerdaten-Schnappschuss erfolgreich gelöscht](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_deleted: '[❌ Nutzerdaten-Schnappschuss erfolgreich gelöscht](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
||||||
data_restored: '[⏪ Erfgreich wiederhergestellt](#00fb9a) [Aktuelle Nutzerdaten des Schnappschusses von %1%](#00fb9a show_text=&7Spieler-UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versions-UUID:\n&8%4%)'
|
data_restored: '[⏪ Erfgreich wiederhergestellt](#00fb9a) [Aktuelle Nutzerdaten des Schnappschusses von %1%](#00fb9a show_text=&7Spieler-UUID:\n&8%2%) [%3%.](#00fb9a show_text=&7Versions-UUID:\n&8%4%)'
|
||||||
data_pinned: '[※ Nutzerdaten-Schnappschuss erfolgreich angepinnt](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
data_pinned: '[※ Nutzerdaten-Schnappschuss erfolgreich angepinnt](#00fb9a) [%1%](#00fb9a show_text=&7Versions-UUID:\n&8%2%) [für](#00fb9a) [%3%.](#00fb9a show_text=&7Spieler-UUID:\n&8%4%)'
|
||||||
@@ -33,9 +34,20 @@ list_page_jumper_button: '[%1%](show_text=&7Springe zu Seite %1% run_command=%2%
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Sprachdateien wurden neu geladen.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
save_cause_disconnect: 'Server verlassen'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
save_cause_world_save: 'Welt gespeichert'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
save_cause_death: 'Tod'
|
||||||
|
save_cause_server_shutdown: 'Server gestoppt'
|
||||||
|
save_cause_inventory_command: 'Inventar Befehl'
|
||||||
|
save_cause_enderchest_command: 'Enderchest Befehl'
|
||||||
|
save_cause_backup_restore: 'Backup wiederhergestellt'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB Migration'
|
||||||
|
save_cause_legacy_migration: 'Legacy Migration'
|
||||||
|
save_cause_converted_from_v2: 'Import von v2'
|
||||||
|
reload_complete: '[HuskSync](#00fb9a bold) [| Die Konfigurations- und Sprachdateien wurden neu geladen.](#00fb9a)\n[⚠ Stelle sicher, dass die Konfigurationsdateien auf allen Servern aktuell sind!](#00fb9a)\n[Ein Neustart wird benötigt, damit Konfigurations-Änderungen wirkbar werden.](#00fb9a italic)'
|
||||||
|
up_to_date: '[HuskSync](#00fb9a bold) [| Du verwendest die neuste Version von HuskSync (v%1%).](#00fb9a)'
|
||||||
|
update_available: '[HuskSync](#ff7e5e bold) [| Eine neue Version von HuskSync ist verfügbar: v%1% (Aktuelle Version: v%2%).](#ff7e5e)'
|
||||||
error_invalid_syntax: '[Fehler:](#ff3300) [Falsche Syntax. Nutze:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
error_invalid_syntax: '[Fehler:](#ff3300) [Falsche Syntax. Nutze:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||||
error_invalid_player: '[Fehler:](#ff3300) [Es konnte kein Spieler mit diesem Namen gefunden werden.](#ff7e5e)'
|
error_invalid_player: '[Fehler:](#ff3300) [Es konnte kein Spieler mit diesem Namen gefunden werden.](#ff7e5e)'
|
||||||
error_no_permission: '[Fehler:](#ff3300) [Du hast nicht die benötigten Berechtigungen um diesen Befehl auszuführen](#ff7e5e)'
|
error_no_permission: '[Fehler:](#ff3300) [Du hast nicht die benötigten Berechtigungen um diesen Befehl auszuführen](#ff7e5e)'
|
||||||
@@ -43,7 +55,7 @@ error_console_command_only: '[Fehler:](#ff3300) [Dieser Befehl kann nur über di
|
|||||||
error_in_game_command_only: 'Fehler: Dieser Befehl kann nur im Spiel genutzt werden.'
|
error_in_game_command_only: 'Fehler: Dieser Befehl kann nur im Spiel genutzt werden.'
|
||||||
error_no_data_to_display: '[Fehler:](#ff3300) [Es konnten keine Nutzerdaten zum Anzeigen gefunden werden.](#ff7e5e)'
|
error_no_data_to_display: '[Fehler:](#ff3300) [Es konnten keine Nutzerdaten zum Anzeigen gefunden werden.](#ff7e5e)'
|
||||||
error_invalid_version_uuid: '[Fehler:](#ff3300) [Es konnten keine Nutzerdaten für diese Versions-UUID gefunden werden.](#ff7e5e)'
|
error_invalid_version_uuid: '[Fehler:](#ff3300) [Es konnten keine Nutzerdaten für diese Versions-UUID gefunden werden.](#ff7e5e)'
|
||||||
husksync_command_description: 'Manage the HuskSync plugin'
|
husksync_command_description: 'Das HuskSync-Plugin verwalten'
|
||||||
userdata_command_description: 'View, manage & restore player userdata'
|
userdata_command_description: 'Nutzerdaten eines Spielers anzeigen, verwalten und wiederherstellen'
|
||||||
inventory_command_description: 'View & edit a player''s inventory'
|
inventory_command_description: 'Inventar eines Spielers ansehen und bearbeiten'
|
||||||
enderchest_command_description: 'View & edit a player''s Ender Chest'
|
enderchest_command_description: 'Endertruhe eines Spielers ansehen und bearbeiten'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ data_manager_title: '[Viewing user data snapshot](#00fb9a) [%1%](#00fb9a show_te
|
|||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version timestamp:\n&8When the data was saved)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version timestamp:\n&8When the data was saved)'
|
||||||
data_manager_pinned: '[※ Snapshot pinned](#d8ff2b show_text=&7Pinned:\n&8This user data snapshot won''t be automatically rotated.)'
|
data_manager_pinned: '[※ Snapshot pinned](#d8ff2b show_text=&7Pinned:\n&8This user data snapshot won''t be automatically rotated.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
||||||
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7Advancements you have progress in:\n&8%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7In-game play time\n&8⚠ Based on in-game statistics)\n'
|
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7Advancements you have progress in:\n&8%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7In-game play time\n&8⚠ Based on in-game statistics)\n'
|
||||||
@@ -33,6 +34,17 @@ list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Reloaded config and message files.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Reloaded config and message files.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ data_manager_title: '[Viendo una snapshot sobre la informacion del jugador](#00f
|
|||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version del registro:\n&8Cuando los datos se han guardado)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version del registro:\n&8Cuando los datos se han guardado)'
|
||||||
data_manager_pinned: '[※ Snapshot anclada](#d8ff2b show_text=&Anclado:\n&8La informacion de este jugador no se rotará automaticamente.)'
|
data_manager_pinned: '[※ Snapshot anclada](#d8ff2b show_text=&Anclado:\n&8La informacion de este jugador no se rotará automaticamente.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Motivo del guardado:\n&8Lo que ha causado que se guarde)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Motivo del guardado:\n&8Lo que ha causado que se guarde)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Puntos de vida) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Puntos de hambre) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Nivel de exp) [🏹 %5%](dark_aqua show_text=&7Gamemode)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Puntos de vida) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Puntos de hambre) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Nivel de exp) [🏹 %5%](dark_aqua show_text=&7Gamemode)'
|
||||||
data_manager_advancements_statistics: '[⭐ Logros: %1%](color=#ffc43b-#f5c962 show_text=&7Logros que has conseguido:\n&8%2%) [⌛ Tiempo de juego: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7In-game play time\n&8⚠ Based on in-game statistics)\n'
|
data_manager_advancements_statistics: '[⭐ Logros: %1%](color=#ffc43b-#f5c962 show_text=&7Logros que has conseguido:\n&8%2%) [⌛ Tiempo de juego: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7In-game play time\n&8⚠ Based on in-game statistics)\n'
|
||||||
@@ -33,6 +34,17 @@ list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Recargada la configuración y los archivos de lenguaje.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Recargada la configuración y los archivos de lenguaje.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ data_manager_title: '[Stai vedendo l''istantanea](#00fb9a) [%1%](#00fb9a show_te
|
|||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7:\n&8Quando i dati sono stati salvati)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7:\n&8Quando i dati sono stati salvati)'
|
||||||
data_manager_pinned: '[※ Istantanea fissata](#d8ff2b show_text=&7Pinned:\n&8Quest''istantanea non sarà cancellata automaticamente.)'
|
data_manager_pinned: '[※ Istantanea fissata](#d8ff2b show_text=&7Pinned:\n&8Quest''istantanea non sarà cancellata automaticamente.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Causa di salvataggio:\n&8Cosa ha causato il salvataggio dei dati)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Causa di salvataggio:\n&8Cosa ha causato il salvataggio dei dati)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Peso dell''istantanea:\n&8Peso stimato del file (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Peso dell''istantanea:\n&8Peso stimato del file (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Vita) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Fame) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Livello di XP) [🏹 %5%](dark_aqua show_text=&7Modalità di gioco)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Vita) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Fame) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Livello di XP) [🏹 %5%](dark_aqua show_text=&7Modalità di gioco)'
|
||||||
data_manager_advancements_statistics: '[⭐ Progressi: %1%](color=#ffc43b-#f5c962 show_text=&7Progressi compiuti in:\n&8%2%) [⌛ Tempo di gioco: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Tempo di gioco\n&8⚠ Basato sulle statistiche di gioco)\n'
|
data_manager_advancements_statistics: '[⭐ Progressi: %1%](color=#ffc43b-#f5c962 show_text=&7Progressi compiuti in:\n&8%2%) [⌛ Tempo di gioco: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Tempo di gioco\n&8⚠ Basato sulle statistiche di gioco)\n'
|
||||||
@@ -33,6 +34,17 @@ list_page_jumper_button: '[%1%](show_text=&7Vai alla pagina %1% run_command=%2%
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| Il plugin è all''ultima versione disponibile (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| Il plugin è all''ultima versione disponibile (v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| Disponibile una nuova versione: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| Disponibile una nuova versione: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Configurazione e messaggi ricaricati.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Configurazione e messaggi ricaricati.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||||
|
|||||||
@@ -1,49 +1,61 @@
|
|||||||
synchronization_complete: '[⏵データが同期されました!](#00fb9a)'
|
synchronization_complete: '[⏵データが同期されました!](#00fb9a)'
|
||||||
synchronization_failed: '[⏵ Failed to synchronize your data! Please contact an administrator.](#ff7e5e)'
|
synchronization_failed: '[⏵ データの同期に失敗しました!管理者に連絡してください。](#ff7e5e)'
|
||||||
inventory_viewer_menu_title: '&0%1%''s Inventory'
|
inventory_viewer_menu_title: '&0%1%のインベントリ'
|
||||||
ender_chest_viewer_menu_title: '&0%1%''s Ender Chest'
|
ender_chest_viewer_menu_title: '&0%1%のエンダーチェスト'
|
||||||
inventory_viewer_opened: '[Viewing snapshot of](#00fb9a) [%1%](#00fb9a bold)[''s inventory as of ⌚ %2%](#00fb9a)'
|
inventory_viewer_opened: '[⌚ %2%](#00fb9a) [%1%](#00fb9a bold) [のインベントリのスナップショットを閲覧する](#00fb9a)'
|
||||||
ender_chest_viewer_opened: '[Viewing snapshot of](#00fb9a) [%1%](#00fb9a bold)[''s Ender Chest as of ⌚ %2%](#00fb9a)'
|
ender_chest_viewer_opened: '[⌚ %2%](#00fb9a) [%1%](#00fb9a bold) [のエンダーチェストのスナップショットを閲覧する](#00fb9a)'
|
||||||
data_update_complete: '[🔔 Your data has been updated!](#00fb9a)'
|
data_update_complete: '[🔔 データが更新されました!](#00fb9a)'
|
||||||
data_update_failed: '[🔔 Failed to update your data! Please contact an administrator.](#ff7e5e)'
|
data_update_failed: '[🔔 データの更新に失敗しました!管理者に連絡してください。](#ff7e5e)'
|
||||||
user_registration_complete: '[⭐ User registration complete!](#00fb9a)'
|
user_registration_complete: '[⭐ ユーザー登録が完了しました!](#00fb9a)'
|
||||||
data_manager_title: '[Viewing user data snapshot](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [for](#00fb9a) [%3%](#00fb9a bold show_text=&7Player UUID:\n&8%4%)[:](#00fb9a)'
|
data_manager_title: '[%3%](#00fb9a bold show_text=&7プレイヤーUUID:\n&8%4%) [のユーザーデータスナップショット](#00fb9a)[%1%](#00fb9a show_text=&7バージョンUUID:\n&8%2%)[を表示:](#00fb9a)'
|
||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version timestamp:\n&8When the data was saved)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7バージョンタイムスタンプ:\n&8データの保存時期)'
|
||||||
data_manager_pinned: '[※ Snapshot pinned](#d8ff2b show_text=&7Pinned:\n&8This user data snapshot won''t be automatically rotated.)'
|
data_manager_pinned: '[※ ピン留めされたスナップショット](#d8ff2b show_text=&7ピン留め:\n&8このユーザーデータのスナップショットは自動的にローテーションされません。)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7保存理由:\n&8データが保存された理由)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7スナップショットサイズ:\n&8スナップショットの推定ファイルサイズ(単位:KiB))\n'
|
||||||
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7Advancements you have progress in:\n&8%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7In-game play time\n&8⚠ Based on in-game statistics)\n'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7体力) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7空腹度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7経験値レベル) [🏹 %5%](dark_aqua show_text=&7ゲームモード)'
|
||||||
data_manager_item_buttons: '[View:](gray) [[🪣 Inventory…]](color=#a17b5f-#f5b98c show_text=&7Click to view run_command=/inventory %1% %2%) [[⌀ Ender Chest…]](#b649c4-#d254ff show_text=&7Click to view run_command=/enderchest %1% %2%)'
|
data_manager_advancements_statistics: '[⭐ 進捗: %1%](color=#ffc43b-#f5c962 show_text=&7達成した進捗:\n&8%2%) [⌛ プレイ時間: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7ゲーム内のプレイ時間\n&8⚠ ゲーム内の統計に基づく)\n'
|
||||||
data_manager_management_buttons: '[Manage:](gray) [[❌ Delete…]](#ff3300 show_text=&7Click to delete this snapshot of user data.\n&8This will not affect the user''s current data.\n&#ff3300&⚠ This cannot be undone! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Restore…]](#00fb9a show_text=&7Click to restore this user data.\n&8This will set the user''s data to this snapshot.\n&#ff3300&⚠ %1%''s current data will be overwritten! suggest_command=/husksync:userdata restore %1% %2%) [[※ Pin/Unpin…]](#d8ff2b show_text=&7Click to pin or unpin this user data snapshot\n&8Pinned snapshots won''t be automatically rotated run_command=/userdata pin %1% %2%)'
|
data_manager_item_buttons: '[表示:](gray) [[🪣 インベントリ…]](color=#a17b5f-#f5b98c show_text=&7クリックで表示 run_command=/husksync:inventory %1% %2%) [[⌀ エンダーチェスト…]](#b649c4-#d254ff show_text=&7クリックで表示 run_command=/husksync:enderchest %1% %2%)'
|
||||||
data_manager_system_buttons: '[System:](gray) [[⏷ File Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to a file.\n&8Data dumps can be found in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to the mc-logs service\n&8You will be provided with a URL containing the data. run_command=/husksync:userdata dump %1% %2% web)'
|
data_manager_management_buttons: '[管理:](gray) [[❌ 消去…]](#ff3300 show_text=&7クリックでこのユーザーデータのスナップショットを消去します。\n&8これはユーザーの現在のデータには影響しません。\n&#ff3300&⚠ この操作は元に戻せません! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ 復元…]](#00fb9a show_text=&7クリックでこのユーザーデータを復元します。\n&8これにより、ユーザーデータはこのスナップショットに設定されます。\n&#ff3300&⚠ %1% の現在のデータは上書きされます! suggest_command=/husksync:userdata restore %1% %2%) [[※ ピン留め/ピン外し…]](#d8ff2b show_text=&7クリックでこのユーザーデータのスナップショットをピン留め、若しくはピンを外します。\n&8ピン留めされたスナップショットは自動的にローテーションしません。 run_command=/userdata pin %1% %2%)'
|
||||||
data_manager_advancements_preview_remaining: 'and %1% more…'
|
data_manager_system_buttons: '[システム:](gray) [[⏷ ファイルダンプ…]](dark_gray show_text=&7クリックで未加工のユーザーデータスナップショットをダンプファイルにします。\n&8データダンプの場所は ~/plugins/HuskSync/dumps/ です run_command=/husksync:userdata dump %1% %2% file) [[☂ Webダンプ…]](dark_gray show_text=&7クリックでユーザーデータスナップショットをmc-logsサービスにダンプします。\n&8データを含むURLが提供されます。 run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
data_list_title: '[%1%''s user data snapshots:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
data_manager_advancements_preview_remaining: 'さらに %1% 件…'
|
||||||
data_list_item: '[%1%](gray show_text=&7User Data Snapshot for %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Pinned:\n&8Pinned snapshots won''t be automatically rotated. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Version timestamp:&7\n&8When the data was saved\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:&7\n&8Estimated file size of the snapshot (in KiB) run_command=/userdata view %2% %3%)'
|
data_list_title: '[%1% のユーザーデータスナップショット:](#00fb9a) [(%4%件中](#00fb9a bold) [%2%-%3%件](#00fb9a)[)](#00fb9a)\n'
|
||||||
data_deleted: '[❌ Successfully deleted user data snapshot](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_list_item: '[%1%](gray show_text=&7%2% のユーザーデータスナップショット&8⚡ %4% run_command=/husksync:userdata view %2% %3%) [%5%](#d8ff2b show_text=&7ピン留め:\n&8ピン留めされたスナップショットは自動的にローテーションしません。 run_command=/husksync:userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7バージョンタイムスタンプ:&7\n&8データの保存時期\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7保存理由:\n&8データが保存された理由 run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7スナップショットサイズ:&7\n&8スナップショットの推定ファイルサイズ (単位:KiB) run_command=/userdata view %2% %3%)'
|
||||||
data_restored: '[⏪ Successfully restored](#00fb9a) [%1%](#00fb9a show_text=&7Player UUID:\n&8%2%)[''s current user data from snapshot](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\n&8%4%)'
|
data_deleted: '[❌](#00fb9a) [%3%](#00fb9a show_text=&7Player UUID:\n&8%4%) [のユーザーデータスナップショット](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [の消去に成功しました。](#00fb9a)'
|
||||||
data_pinned: '[※ Successfully pinned user data snapshot](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_restored: '[⏪](#00fb9a) [スナップショット](#00fb9a) [%3%](#00fb9a show_text=&7Version UUID:\n&8%4%) [から](#00fb9a) [%1%](#00fb9a show_text=&7Player UUID:\n&8%2%) [の現在のユーザーデータの復元に成功しました。](#00fb9a)'
|
||||||
data_unpinned: '[※ Successfully unpinned user data snapshot](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [for](#00fb9a) [%3%.](#00fb9a show_text=&7Player UUID:\n&8%4%)'
|
data_pinned: '[※](#00fb9a) [%3%](#00fb9a show_text=&7Player UUID:\n&8%4%) [のユーザーデータスナップショット](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [のピン留めに成功しました。](#00fb9a)'
|
||||||
data_dumped: '[☂ Successfully dumped the user data snapshot %1% for %2% to:](#00fb9a) &7%3%'
|
data_unpinned: '[※](#00fb9a) [%3%](#00fb9a show_text=&7Player UUID:\n&8%4%) [のユーザーデータスナップショット](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%) [のピン外しに成功しました。](#00fb9a)'
|
||||||
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
data_dumped: '[☂ %2% のユーザーデータスナップショット %1% のダンプに成功:](#00fb9a) &7%3%'
|
||||||
list_previous_page_button: '[◀](white show_text=&7View previous page run_command=%2% %1%) '
|
list_footer: '\n%1%[ページ](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
list_next_page_button: ' [▶](white show_text=&7View next page run_command=%2% %1%)'
|
list_previous_page_button: '[◀](white show_text=&7前のページへ run_command=%2% %1%) '
|
||||||
|
list_next_page_button: ' [▶](white show_text=&7次のページへ run_command=%2% %1%)'
|
||||||
list_page_jumpers: '(%1%)'
|
list_page_jumpers: '(%1%)'
|
||||||
list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%)'
|
list_page_jumper_button: '[%1%](show_text=&7%1% ページ目へ run_command=%2% %1%)'
|
||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| HuskSyncの最新バージョンを実行しています(v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| HuskSyncの最新バージョンを実行しています(v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| HuskSyncの最新バージョンが更新されています: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| HuskSyncの最新バージョンが更新されています: v%1% (実行中: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 設定ファイルとメッセージファイルを再読み込みしました。](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 設定ファイルとメッセージファイルを再読み込みしました。](#00fb9a)\n[⚠ すべてのサーバーで設定ファイルが最新であることを確認してください!](#00fb9a)\n[設定の変更を有効にするには再起動が必要です。](#00fb9a italic)'
|
||||||
error_invalid_syntax: '[Error:](#ff3300) [構文が正しくありません。使用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
error_invalid_syntax: '[Error:](#ff3300) [構文が正しくありません。使用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&クリックでサジェスト suggest_command=%1%)'
|
||||||
error_invalid_player: '[Error:](#ff3300) [そのプレイヤーは見つかりませんでした](#ff7e5e)'
|
error_invalid_player: '[Error:](#ff3300) [そのプレイヤーは見つかりませんでした](#ff7e5e)'
|
||||||
error_no_permission: '[Error:](#ff3300) [このコマンドを実行する権限がありません](#ff7e5e)'
|
error_no_permission: '[Error:](#ff3300) [このコマンドを実行する権限がありません](#ff7e5e)'
|
||||||
error_console_command_only: '[Error:](#ff3300) [そのコマンドは%1%コンソールからのみ実行できます](#ff7e5e)'
|
error_console_command_only: '[Error:](#ff3300) [そのコマンドは%1%コンソールからのみ実行できます](#ff7e5e)'
|
||||||
error_in_game_command_only: 'Error: That command can only be used in-game.'
|
error_in_game_command_only: 'Error: そのコマンドはゲーム内でしか使えません。'
|
||||||
error_no_data_to_display: '[Error:](#ff3300) [Could not find any user data to display.](#ff7e5e)'
|
error_no_data_to_display: '[Error:](#ff3300) [表示するユーザーデータが見つかりませんでした。](#ff7e5e)'
|
||||||
error_invalid_version_uuid: '[Error:](#ff3300) [Could not find any user data for that version UUID.](#ff7e5e)'
|
error_invalid_version_uuid: '[Error:](#ff3300) [そのバージョンUUIDのユーザーデータが見つかりませんでした。](#ff7e5e)'
|
||||||
husksync_command_description: 'Manage the HuskSync plugin'
|
husksync_command_description: 'HuskSyncプラグインを管理する'
|
||||||
userdata_command_description: 'View, manage & restore player userdata'
|
userdata_command_description: 'プレーヤーのユーザーデータを表示・管理・復元する'
|
||||||
inventory_command_description: 'View & edit a player''s inventory'
|
inventory_command_description: 'プレイヤーのインベントリを閲覧・編集する'
|
||||||
enderchest_command_description: 'View & edit a player''s Ender Chest'
|
enderchest_command_description: 'プレイヤーのエンダーチェストを閲覧・編集する'
|
||||||
|
|||||||
61
common/src/main/resources/locales/ko-kr.yml
Normal file
61
common/src/main/resources/locales/ko-kr.yml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
synchronization_complete: '[⏵ 데이터 연동됨!](#00fb9a)'
|
||||||
|
synchronization_failed: '[⏵ 데이터 연동에 실패하였습니다! 관리자에게 문의해 주세요.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '&0%1%님의 인벤토리'
|
||||||
|
ender_chest_viewer_menu_title: '&0%1%님의 엔더상자'
|
||||||
|
inventory_viewer_opened: '[%1%](#00fb9a bold)[님의 ⌚ %2%의 인벤토리를 엽니다](#00fb9a)'
|
||||||
|
ender_chest_viewer_opened: '[%1%](#00fb9a bold)[님의 ⌚ %2%의 엔더상자를 엽니다](#00fb9a)'
|
||||||
|
data_update_complete: '[🔔 당신의 데이터가 업데이트 되었습니다!](#00fb9a)'
|
||||||
|
data_update_failed: '[🔔 데이터 업데이트에 실패하였습니다! 관리자에게 문의해 주세요.](#ff7e5e)'
|
||||||
|
user_registration_complete: '[⭐ 유저 등록이 완료되었습니다!](#00fb9a)'
|
||||||
|
data_manager_title: '[%3%](#00fb9a bold show_text=&7플레이어 UUID:\n&8%4%)[님의 ](#00fb9a) [%1%](#00fb9a show_text=&7버전 UUID:\n&8%2%) [데이터 스냅샷을 표시합니다](#00fb9a)[:](#00fb9a)'
|
||||||
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7저장 시각:\n&8데이터가 저장된 시각)'
|
||||||
|
data_manager_pinned: '[※ 스냅샷 고정됨](#d8ff2b show_text=&7고정됨:\n&8이 유저의 데이터 스냅샷은 자동으로 갱신되지 않습니다.)'
|
||||||
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7저장 사유:\n&8데이터가 저장된 사유입니다.)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7서버:\n&8데이터 저장이 이루어진 서버입니다.)'
|
||||||
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7스냅샷 크기:\n&8스냅샷 파일의 대략적인 크기입니다. (단위 KiB))\n'
|
||||||
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7체력) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7허기) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7경험치 레벨) [🏹 %5%](dark_aqua show_text=&7게임 모드)'
|
||||||
|
data_manager_advancements_statistics: '[⭐ 도전 과제: %1%](color=#ffc43b-#f5c962 show_text=&7진행한 도전 과제:\n&8%2%) [⌛ 플레이 타임: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7인게임 플레이 시간\n&8⚠ 인게임 통계에 기반합니다.)\n'
|
||||||
|
data_manager_item_buttons: '[보기:](gray) [[🪣 인벤토리…]](color=#a17b5f-#f5b98c show_text=&7클릭하여 확인 run_command=/inventory %1% %2%) [[⌀ 엔더상자…]](#b649c4-#d254ff show_text=&7클릭하여 확인 run_command=/enderchest %1% %2%)'
|
||||||
|
data_manager_management_buttons: '[관리:](gray) [[❌ 삭제…]](#ff3300 show_text=&7클릭하여 이 유저 스냅샷 데이터를 삭제\n&8이 기능은 현재 유저의 인벤토리 데이터에는 영향을 미치지 않습니다.\n&#ff3300&⚠ 이 작업은 되돌릴 수 없습니다! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ 복구…]](#00fb9a show_text=&7클릭하여 유저 데이터 복구\n&8유저의 데이터가 이 스냅샷의 데이터로 변경됩니다.\n&#ff3300&⚠ %1%님의 현재 데이터에 덧씌워 집니다! suggest_command=/husksync:userdata restore %1% %2%) [[※ 고정/고정 해제…]](#d8ff2b show_text=&7클릭하여 유저 데이터를 고정 또는 고정 해제\n&8고정된 스냅샷은 자동적으로 갱신되지 않습니다. run_command=/userdata pin %1% %2%)'
|
||||||
|
data_manager_system_buttons: '[시스템:](gray) [[⏷ 파일 덤프…]](dark_gray show_text=&7클릭하여 이 유저 데이터 스냅샷을 덤프하기\n&8데이터 덤프 파일은 ~/plugins/HuskSync/dumps/ 에서 찾을 수 있습니다. run_command=/husksync:userdata dump %1% %2% file) [[☂ 웹 덤프…]](dark_gray show_text=&7클릭하여 유저 데이터 스냅샷을 mc-log 서비스에 덤프하기\n&8데이터를 포함한 URL이 제공됩니다. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
|
data_manager_advancements_preview_remaining: '외 %1%개...'
|
||||||
|
data_list_title: '[%1%님의 유저 데이터 스냅샷 목록:](#00fb9a) [(%2%-%3% 중](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
|
data_list_item: '[%1%](gray show_text=&7%2%&7님의 유저 데이터 스냅샷&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7고정됨:\n&8고정된 스냅샷은 자동적으로 갱신되지 않습니다. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7저장 시각:&7\n&8데이터가 저장된 시각입니다.\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7저장 사유:\n&8데이터가 저장된 사유입니다. run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7스냅샷 크기:&7\n&8스냅샷 파일의 대략적인 크기입니다. (단위 KiB) run_command=/userdata view %2% %3%)'
|
||||||
|
data_deleted: '[❌ 성공적으로](#00fb9a) [%3%](#00fb9a show_text=&7플레이어 UUID:\n&8%4%) [님의 유저 데이터 스냅샷](#00fb9a) [%1%](#00fb9a show_text=&7버전 UUID:\n&8%2%)[을 삭제하였습니다.](#00fb9a)'
|
||||||
|
data_restored: '[⏪ 성공적으로 복구되었습니다.](#00fb9a) [%1%](#00fb9a show_text=&7플레이어 UUID:\n&8%2%)[님의 현재 유저 데이터 스냅샷이](#00fb9a) [%3%](#00fb9a show_text=&7버전 UUID:\n&8%4%)[으로 변경되었습니다.](#00fb9a)'
|
||||||
|
data_pinned: '[※ 성공적으로](#00fb9a) [%3%](#00fb9a show_text=&7플레이어 UUID:\n&8%4%)[님의 유저 데이터 스냅샷](#00fb9a) [%1%](#00fb9a show_text=&7버전 UUID:\n&8%2%)[을 고정하였습니다.](#00fb9a)'
|
||||||
|
data_unpinned: '[※ 성공적으로](#00fb9a) [%3%](#00fb9a show_text=&7플레이어 UUID:\n&8%4%) [님의 유저 데이터 스냅샷](#00fb9a) [%1%](#00fb9a show_text=&7버전 UUID:\n&8%2%)[을 고정 해제하였습니다.](#00fb9a)'
|
||||||
|
data_dumped: '[☂ 성공적으로 %2%님의 유저 데이터 스냅샷 %1%를 다음으로 덤프하였습니다:](#00fb9a) &7%3%'
|
||||||
|
list_footer: '\n%1%[페이지](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
|
list_previous_page_button: '[◀](white show_text=&7이전 페이지 보기 run_command=%2% %1%) '
|
||||||
|
list_next_page_button: ' [▶](white show_text=&7다음 페이지 보기 run_command=%2% %1%)'
|
||||||
|
list_page_jumpers: '(%1%)'
|
||||||
|
list_page_jumper_button: '[%1%](show_text=&7%1% 페이지 보기 run_command=%2% %1%)'
|
||||||
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
|
list_page_jumper_separator: ' '
|
||||||
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
|
up_to_date: '[HuskSync](#00fb9a bold) [| 가장 최신 버전의 HuskSync를 실행 중입니다 (v%1%).](#00fb9a)'
|
||||||
|
update_available: '[HuskSync](#ff7e5e bold) [| 새로운 버전의 HuskSync가 존재합니다: v%1% (현재 버전: v%2%).](#ff7e5e)'
|
||||||
|
reload_complete: '[HuskSync](#00fb9a bold) [| 콘피그와 메시지 파일을 다시 불러왔습니다.](#00fb9a)\n[⚠ 모든 서버의 컨피그 파일을 변경하였는지 확인하세요!](#00fb9a)\n[몇몇 설정은 재시작 후에 적용됩니다.](#00fb9a italic)'
|
||||||
|
error_invalid_syntax: '[오류:](#ff3300) [잘못된 사용법. 사용법:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&클릭하여 입력할 수 있습니다. suggest_command=%1%)'
|
||||||
|
error_invalid_player: '[오류:](#ff3300) [해당 이름의 사용자를 찾을 수 없습니다.](#ff7e5e)'
|
||||||
|
error_no_permission: '[오류:](#ff3300) [해당 명령어를 사용할 권한이 없습니다.](#ff7e5e)'
|
||||||
|
error_console_command_only: '[오류:](#ff3300) [해당 명령어는 콘솔을 통해서만 사용할 수 있습니다.](#ff7e5e)'
|
||||||
|
error_in_game_command_only: '오류: 해당 명령어는 게임 내부에서만 사용할 수 있습니다.'
|
||||||
|
error_no_data_to_display: '[오류:](#ff3300) [표시할 유저 데이터를 찾을 수 없습니다.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[오류:](#ff3300) [해당 버전 UUID의 유저 데이터 스냅샷을 찾을 수 없습니다.](#ff7e5e)'
|
||||||
|
husksync_command_description: 'HuskSync 플러그인을 관리합니다.'
|
||||||
|
userdata_command_description: '확인, 관리 또는 복구합니다.'
|
||||||
|
inventory_command_description: '플레이어의 인벤토리를 열람 또는 편집합니다.'
|
||||||
|
enderchest_command_description: '플레이어의 엔더상자를 열람 또는 편집합니다.'
|
||||||
61
common/src/main/resources/locales/nl-nl.yml
Normal file
61
common/src/main/resources/locales/nl-nl.yml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
synchronization_complete: '[⏵ Data gesynchroniseerd!](#00fb9a)'
|
||||||
|
synchronization_failed: '[⏵ Synchroniseren van jouw gegevens is niet gelukt! Neem contact op met een beheerder.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '&0%1%''s Inventaris'
|
||||||
|
ender_chest_viewer_menu_title: '&0%1%''s Enderkist'
|
||||||
|
inventory_viewer_opened: '[Momentopname bekijken van](#00fb9a) [%1%](#00fb9a bold)[''s inventaris per ⌚ %2%](#00fb9a)'
|
||||||
|
ender_chest_viewer_opened: '[Momentopname bekijken van](#00fb9a) [%1%](#00fb9a bold)[''s Enderkist per ⌚ %2%](#00fb9a)'
|
||||||
|
data_update_complete: '[🔔 Jouw gegevens zijn bijgewerkt!](#00fb9a)'
|
||||||
|
data_update_failed: '[🔔 Het is niet gelukt om jouw gegevens bij te werken! Neem contact op met een beheerder.](#ff7e5e)'
|
||||||
|
user_registration_complete: '[⭐ Gebruikersregistratie voltooid!](#00fb9a)'
|
||||||
|
data_manager_title: '[Momentopname van gebruikersgegevens bekijken](#00fb9a) [%1%](#00fb9a show_text=&7Versie UUID:\n&8%2%) [voor](#00fb9a) [%3%](#00fb9a bold show_text=&7Speler UUID:\n&8%4%)[:](#00fb9a)'
|
||||||
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Versie tijdmarkering:\n&8Toen de gegevens werden opgeslagen)'
|
||||||
|
data_manager_pinned: '[※ Momentopname vastgezet](#d8ff2b show_text=&7Vastgezet:\n&8Deze momentopname van gebruikersgegevens wordt niet automatisch gerouleerd.)'
|
||||||
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Reden opslaan:\n&8Waarom de data is opgeslagen)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Grootte van momentopname:\n&8Geschatte bestandsgrootte van de momentopname (in KiB))\n'
|
||||||
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Gezondheids punten) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Honger punten) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Speltype)'
|
||||||
|
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7Advancements waarin je voortgang hebt:\n&8%2%) [⌛ Speeltijd: %3%uren](color=#62a9f5-#7ab8fa show_text=&7In-game speeltijd\n&8⚠ Gebaseerd op in-game statistieken)\n'
|
||||||
|
data_manager_item_buttons: '[View:](gray) [[🪣 Inventaris…]](color=#a17b5f-#f5b98c show_text=&7Klikken om te bekijken run_command=/inventory %1% %2%) [[⌀ Enderkist…]](#b649c4-#d254ff show_text=&7Klikken om te bekijken run_command=/enderchest %1% %2%)'
|
||||||
|
data_manager_management_buttons: '[Beheren:](gray) [[❌ Verwijderen…]](#ff3300 show_text=&7Klik om deze momentopname van gebruikersgegevens te verwijderen.\n&8Dit heeft geen invloed op de huidige gegevens van de gebruiker.\n&#ff3300&⚠ Dit kan niet ongedaan gemaakt worden! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Herstellen…]](#00fb9a show_text=&7Klik om deze gebruikersgegevens te herstellen.\n&8Hierdoor worden de gegevens van de gebruiker ingesteld op deze momentopname.\n&#ff3300&⚠ %1%''s huidige gegevens worden overschreven! suggest_command=/husksync:userdata restore %1% %2%) [[※ Vastzetten/losmaken…]](#d8ff2b show_text=&7Klik om deze momentopname van gebruikersgegevens vast te zetten of los te maken\n&8Vastgezette momentopnamen worden niet automatisch gerouleerd run_command=/userdata pin %1% %2%)'
|
||||||
|
data_manager_system_buttons: '[Systeem:](gray) [[⏷ Bestandsdump…]](dark_gray show_text=&7Klik om deze ruwe gebruikersgegevenssnapshot naar een bestand te dumperen.\n&8Gegevensdumps zijn te vinden in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Webdump…]](dark_gray show_text=&7Klik om deze ruwe gebruikersgegevenssnapshot naar de mc-logs-service te dumpen\n&8Je ontvangt een URL met de gegevens. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
|
data_manager_advancements_preview_remaining: 'en %1% meer…'
|
||||||
|
data_list_title: '[%1%''s momentopnamen van gebruikersgegevens:](#00fb9a) [(%2%-%3% van](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
|
data_list_item: '[%1%](gray show_text=&7Gebruikersgegevens momentopname voor %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Vastgezet:\n&8Vastgezette momentopnamen worden niet automatisch gerouleerd. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Versie tijdmarkering:&7\n&8Wanneer de data was opgeslagen\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Reden opslaan:\n&8Waarom de data is opgeslagen run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Grootte van momentopname:&7\n&8Geschatte bestandsgrootte van de momentopname (in KiB) run_command=/userdata view %2% %3%)'
|
||||||
|
data_deleted: '[❌ Momentopname van gebruikersgegevens is verwijderd](#00fb9a) [%1%](#00fb9a show_text=&7Versie UUID:\n&8%2%) [voor](#00fb9a) [%3%.](#00fb9a show_text=&7Speler UUID:\n&8%4%)'
|
||||||
|
data_restored: '[⏪ Succesvol hersteld](#00fb9a) [%1%](#00fb9a show_text=&7Speler UUID:\n&8%2%)[''s huidige gebruikersgegevens uit momentopname](#00fb9a) [%3%.](#00fb9a show_text=&7Versie UUID:\n&8%4%)'
|
||||||
|
data_pinned: '[※ Momentopname van gebruikersgegevens is vastgezet](#00fb9a) [%1%](#00fb9a show_text=&7Versie UUID:\n&8%2%) [voor](#00fb9a) [%3%.](#00fb9a show_text=&7Speler UUID:\n&8%4%)'
|
||||||
|
data_unpinned: '[※ Momentopname van gebruikersgegevens is losgemaakt](#00fb9a) [%1%](#00fb9a show_text=&7Versie UUID:\n&8%2%) [voor](#00fb9a) [%3%.](#00fb9a show_text=&7Speler UUID:\n&8%4%)'
|
||||||
|
data_dumped: '[☂ De momentopname van gebruikersgegevens %1% voor %2% is met succes gedumpt naar:](#00fb9a) &7%3%'
|
||||||
|
list_footer: '\n%1%[Pagina](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
|
list_previous_page_button: '[◀](white show_text=&7Bekijk vorige pagina run_command=%2% %1%) '
|
||||||
|
list_next_page_button: ' [▶](white show_text=&7Bekijk volgende pagina run_command=%2% %1%)'
|
||||||
|
list_page_jumpers: '(%1%)'
|
||||||
|
list_page_jumper_button: '[%1%](show_text=&7Ga naar pagina %1% run_command=%2% %1%)'
|
||||||
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
|
list_page_jumper_separator: ' '
|
||||||
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
|
up_to_date: '[HuskSync](#00fb9a bold) [| Je gebruikt de nieuwste versie van HuskSync (v%1%).](#00fb9a)'
|
||||||
|
update_available: '[HuskSync](#ff7e5e bold) [| Er is een nieuwe versie van HuskSync beschikbaar: v%1% (huidige versie: v%2%).](#ff7e5e)'
|
||||||
|
reload_complete: '[HuskSync](#00fb9a bold) [| Configuratie- en berichtbestanden opnieuw geladen.](#00fb9a)\n[⚠ Controleer of de configuratiebestanden up-to-date zijn op alle servers!](#00fb9a)\n[Een herstart is nodig voor de configuratiewijzigingen van kracht te laten worden.](#00fb9a italic)'
|
||||||
|
error_invalid_syntax: '[Error:](#ff3300) [Onjuiste syntaxis. Gebruik:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||||
|
error_invalid_player: '[Error:](#ff3300) [Kan geen speler met die naam vinden.](#ff7e5e)'
|
||||||
|
error_no_permission: '[Error:](#ff3300) [Je hebt geen toestemming om deze opdracht uit te voeren](#ff7e5e)'
|
||||||
|
error_console_command_only: '[Error:](#ff3300) [Dat command kan alleen via de console worden uitgevoerd](#ff7e5e)'
|
||||||
|
error_in_game_command_only: 'Error: Dat command kan alleen in-game worden gebruikt.'
|
||||||
|
error_no_data_to_display: '[Error:](#ff3300) [Kon geen gebruikersgegevens vinden om weer te geven.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Error:](#ff3300) [Kon geen gebruikersgegevens vinden voor dat versie-UUID.](#ff7e5e)'
|
||||||
|
husksync_command_description: 'Beheer de HuskSync plugin'
|
||||||
|
userdata_command_description: 'Bekijk, beheer en herstel de gebruikersgegevens van spelers'
|
||||||
|
inventory_command_description: 'Bekijk en bewerk de inventaris van een speler'
|
||||||
|
enderchest_command_description: 'Bekijk en bewerk de Enderkist van een speler'
|
||||||
@@ -11,6 +11,7 @@ data_manager_title: '[Visualizando snapshot dos dados do usuário](#00fb9a) [%1%
|
|||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version timestamp:\n&8Quando os dados foram salvos)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version timestamp:\n&8Quando os dados foram salvos)'
|
||||||
data_manager_pinned: '[※ Snapshot marcada](#d8ff2b show_text=&7Marcada:\n&8Essa snapshot de dados do usuário não será girada automaticamente.)'
|
data_manager_pinned: '[※ Snapshot marcada](#d8ff2b show_text=&7Marcada:\n&8Essa snapshot de dados do usuário não será girada automaticamente.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Causa do salvamento:\n&8O motivo para que os dados fossem salvos)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Causa do salvamento:\n&8O motivo para que os dados fossem salvos)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Pontos de Vida) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Pontos de vida) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Pontos de Vida) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Pontos de vida) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
||||||
data_manager_advancements_statistics: '[⭐ Progressos: %1%](color=#ffc43b-#f5c962 show_text=&7Progressos que você tem realizado em:\n&8%2%) [⌛ Tempo de jogo: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Tempo de jogo dentro do jogo\n&8⚠ Com base em estatísticas dentro do jogo)\n'
|
data_manager_advancements_statistics: '[⭐ Progressos: %1%](color=#ffc43b-#f5c962 show_text=&7Progressos que você tem realizado em:\n&8%2%) [⌛ Tempo de jogo: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Tempo de jogo dentro do jogo\n&8⚠ Com base em estatísticas dentro do jogo)\n'
|
||||||
@@ -33,6 +34,17 @@ list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Arquivos de configuração e mensagens recarregados.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Arquivos de configuração e mensagens recarregados.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||||
|
|||||||
61
common/src/main/resources/locales/ru-ru.yml
Normal file
61
common/src/main/resources/locales/ru-ru.yml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
synchronization_complete: '[⏵ Данные синхронизированы!](#00fb9a)'
|
||||||
|
synchronization_failed: '[⏵ Не удалось синхронизировать данные! Пожалуйста, обратитесь к администратору.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '&0Инвентарь %1%'
|
||||||
|
ender_chest_viewer_menu_title: '&0Эндер-сундук %1%'
|
||||||
|
inventory_viewer_opened: '[Просмотр снимка инвентаря](#00fb9a) [%1%](#00fb9a bold) [во время ⌚ %2%](#00fb9a)'
|
||||||
|
ender_chest_viewer_opened: '[Просмотр снимка эндер-сундука](#00fb9a) [%1%](#00fb9a bold) [во время ⌚ %2%](#00fb9a)'
|
||||||
|
data_update_complete: '[🔔 Ваши данные обновлены!](#00fb9a)'
|
||||||
|
data_update_failed: '[🔔 Не удалось обновить ваши данные! Пожалуйста, обратитесь к администратору.](#ff7e5e)'
|
||||||
|
user_registration_complete: '[⭐ Регистрация пользователя завершена!](#00fb9a)'
|
||||||
|
data_manager_title: '[Просмотр снимка данных](#00fb9a) [%1%](#00fb9a show_text=&7UUID снимка:\n&8%2%) [пользователя](#00fb9a) [%3%](#00fb9a bold show_text=&7UUID игрока:\n&8%4%)[:](#00fb9a)'
|
||||||
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Время:\n&8Когда данные были сохранены)'
|
||||||
|
data_manager_pinned: '[※ Снимок закреплен](#d8ff2b show_text=&7Закреплен:\n&8Этот снимок данных пользователя не будет автоматически применено.)'
|
||||||
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Причина сохранения:\n&8Что привело к сохранению данных)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Сервер:\n&8Название сервера где данные были сохранены)'
|
||||||
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Размер:\n&8Предполагаемый размер снимка (в килобайтах))\n'
|
||||||
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Здоровье) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Голод) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7Уровень опыта) [🏹 %5%](dark_aqua show_text=&7Игровой режим)'
|
||||||
|
data_manager_advancements_statistics: '[⭐ Достижения: %1%](color=#ffc43b-#f5c962 show_text=&7Полученные вами достижения:\n&8%2%) [⌛ Отыгранное время: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7Сколько времени вы отыграли\n&8⚠ Строится по внутреигровой статистике)\n'
|
||||||
|
data_manager_item_buttons: '[Просмотр:](gray) [[🪣 Инвентарь…]](color=#a17b5f-#f5b98c show_text=&7Нажмите для просмотра run_command=/inventory %1% %2%) [[⌀ Эндер-сундук…]](#b649c4-#d254ff show_text=&7Нажмите для просмотра run_command=/enderchest %1% %2%)'
|
||||||
|
data_manager_management_buttons: '[Управление:](gray) [[❌ Удалить…]](#ff3300 show_text=&7Нажмите для удаления этого снимка данных пользователя\n&8Не влияет на текущие данные пользователя.\n&#ff3300&⚠ Необратимое действие! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Восстановить…]](#00fb9a show_text=&7Нажмите для восстановления данных пользователя\n&8Это восстановит данные пользователя до этого снимка.\n&#ff3300&⚠ Текущие данные %1% будут перезаписаны! suggest_command=/husksync:userdata restore %1% %2%) [[※ Закрепить/Открепить…]](#d8ff2b show_text=&7Нажмите для закрепления или открепления снимка данных пользователя\n&8Закрепленные снимки данных не удаляются автоматически. run_command=/userdata pin %1% %2%)'
|
||||||
|
data_manager_system_buttons: '[Система:](gray) [[⏷ Дамп в файл…]](dark_gray show_text=&7Нажмите для дампа исходного снимка данных пользователя в файл\n&8Файлы дампов данных находятся в ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Выгрузить дамп…]](dark_gray show_text=&7Нажмите для дампа исходного снимка данных пользователя с помощью сервиса mc-logs\n&8Вы получите ссылку с дампом данных. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
|
data_manager_advancements_preview_remaining: 'и еще %1%…'
|
||||||
|
data_list_title: '[Снимки данных %1%:](#00fb9a) [(%2%-%3% из](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
|
data_list_item: '[%1%](gray show_text=&7Снимок данных %4% пользователя %2%&8⚡ run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Закреплен:\n&8Закрепленные снимки данных не удаляются автоматически run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Время:&7\n&8Когда данные были сохранены\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Причина сохранения:\n&8Что привело к сохранению данных run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Размер:&7\n&8Предполагаемый размер снимка (в килобайтах) run_command=/userdata view %2% %3%)'
|
||||||
|
data_deleted: '[❌ Снимок данных](#00fb9a) [%1%](#00fb9a show_text=&7UUID снимка:\n&8%2%) [пользователя](#00fb9a) [%3%](#00fb9a show_text=&7UUID игрока:\n&8%4%) [удален.](#00fb9a)'
|
||||||
|
data_restored: '[⏪ Данные пользователя](#00fb9a) [%1%](#00fb9a show_text=&7UUID игрока:\n&8%2%) [из снимка](#00fb9a) [%3%](#00fb9a show_text=&7UUID снимка:\n&8%4%) [успешно восстановлены.](#00fb9a)'
|
||||||
|
data_pinned: '[※ Снимок данных](#00fb9a) [%1%](#00fb9a show_text=&7UUID снимка:\n&8%2%) [пользователя](#00fb9a) [%3%](#00fb9a show_text=&7UUID игрока:\n&8%4%) [успешно закреплен.](#00fb9a)'
|
||||||
|
data_unpinned: '[※ Снимок данных](#00fb9a) [%1%](#00fb9a show_text=&7UUID снимка:\n&8%2%) [пользователя](#00fb9a) [%3%](#00fb9a show_text=&7UUID пользователя:\n&8%4%) [успешно откреплен.](#00fb9a)'
|
||||||
|
data_dumped: '[☂ Дамп снимка данных %1% пользователя %2% успешно выгружен:](#00fb9a) &7%3%'
|
||||||
|
list_footer: '\n%1%[Страница](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%\n'
|
||||||
|
list_previous_page_button: '[◀](white show_text=&7Просмотр предыдущей страницы run_command=%2% %1%) '
|
||||||
|
list_next_page_button: ' [▶](white show_text=&7Просмотр следующей страницы run_command=%2% %1%)'
|
||||||
|
list_page_jumpers: '(%1%)'
|
||||||
|
list_page_jumper_button: '[%1%](show_text=&7Перейти на страницу %1% run_command=%2% %1%)'
|
||||||
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
|
list_page_jumper_separator: ' '
|
||||||
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'отключение с сервера'
|
||||||
|
save_cause_world_save: 'сохранение мира'
|
||||||
|
save_cause_death: 'смерть'
|
||||||
|
save_cause_server_shutdown: 'отключение сервера'
|
||||||
|
save_cause_inventory_command: 'команда inventory'
|
||||||
|
save_cause_enderchest_command: 'команда enderchest'
|
||||||
|
save_cause_backup_restore: 'восстановление из снимка'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'миграция из MPDB'
|
||||||
|
save_cause_legacy_migration: 'миграция с legacy'
|
||||||
|
save_cause_converted_from_v2: 'конвертация с v2'
|
||||||
|
up_to_date: '[HuskSync](#00fb9a bold) [| Вы используете последнюю версию HuskSync (v%1%).](#00fb9a)'
|
||||||
|
update_available: '[HuskSync](#ff7e5e bold) [| Доступна новая версия HuskSync: v%1% (текущая: v%2%).](#ff7e5e)'
|
||||||
|
reload_complete: '[HuskSync](#00fb9a bold) [| Конфигурация и файлы локализации перезагружены.](#00fb9a)\n[⚠ Убедитесь, что файлы конфигурации обновлены на всех серверах!](#00fb9a)\n[Необходима перезагрузка для вступления изменений конфигурации в силу.](#00fb9a italic)'
|
||||||
|
error_invalid_syntax: '[Ошибка:](#ff3300) [Неправильный синтаксис. Используйте:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
||||||
|
error_invalid_player: '[Ошибка:](#ff3300) [Не удалось найти игрока с данным именем.](#ff7e5e)'
|
||||||
|
error_no_permission: '[Ошибка:](#ff3300) [У вас недостаточно прав для выполнения данной команды.](#ff7e5e)'
|
||||||
|
error_console_command_only: '[Ошибка:](#ff3300) [Данная команда может быть выполнена только из консоли.](#ff7e5e)'
|
||||||
|
error_in_game_command_only: 'Ошибка: Данная команда может быть выполнена только в игре.'
|
||||||
|
error_no_data_to_display: '[Ошибка:](#ff3300) [Не удалось найти никаких пользовательских данных для отображения.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Ошибка:](#ff3300) [Не удалось найти никаких пользовательских данных с этим UUID снимка.](#ff7e5e)'
|
||||||
|
husksync_command_description: 'Управление плагином HuskSync'
|
||||||
|
userdata_command_description: 'Просмотр, редактирование и восстановление пользовательских данных игрока'
|
||||||
|
inventory_command_description: 'Просмотр и редактирование инвентаря игрока'
|
||||||
|
enderchest_command_description: 'Просмотр и редактирование эндер-сундука игрока'
|
||||||
61
common/src/main/resources/locales/tr-tr.yml
Normal file
61
common/src/main/resources/locales/tr-tr.yml
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
synchronization_complete: '[⏵ Veri senkronize edildi!](#00fb9a)'
|
||||||
|
synchronization_failed: '[⏵ Veriler senkronize edilemedi! Lütfen bir yönetici ile iletişime geçin.](#ff7e5e)'
|
||||||
|
inventory_viewer_menu_title: '&0%1%''ın Envanteri'
|
||||||
|
ender_chest_viewer_menu_title: '&0%1%''ın Ender Sandığı'
|
||||||
|
inventory_viewer_opened: '[Görüntülenen anlık](#00fb9a) [%1%](#00fb9a bold)[''ın envanteri ⌚ %2% tarihine kadar](#00fb9a)'
|
||||||
|
ender_chest_viewer_opened: '[Görüntülenen anlık](#00fb9a) [%1%](#00fb9a bold)[''ın Ender Sandığı ⌚ %2% tarihine kadar](#00fb9a)'
|
||||||
|
data_update_complete: '[🔔 Verileriniz güncellendi!](#00fb9a)'
|
||||||
|
data_update_failed: '[🔔 Verileriniz güncellenirken bir hata oluştu! Lütfen bir yönetici ile iletişime geçin.](#ff7e5e)'
|
||||||
|
user_registration_complete: '[⭐ Kullanıcı kaydı tamamlandı!](#00fb9a)'
|
||||||
|
data_manager_title: '[Kullanıcı veri anlık görünümü](#00fb9a) [%1%](#00fb9a show_text=&7Versiyon UUID:\n&8%2%) [için](#00fb9a) [%3%](#00fb9a bold show_text=&7Oyuncu UUID:\n&8%4%)[:](#00fb9a)'
|
||||||
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Versiyon zaman damgası:\n&8Verinin ne zaman kaydedildiği)'
|
||||||
|
data_manager_pinned: '[※ Anlık sabitlendi](#d8ff2b show_text=&7Sabitlendi:\n&8Bu kullanıcı veri anlığı otomatik olarak döndürülmeyecek.)'
|
||||||
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Kaydetme sebebi:\n&8Verinin kaydedilme nedeni)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Sunucu:\n&8Verinin kaydedildiği sunucu adı)'
|
||||||
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Anlık boyutu:\n&8Anlının tahmini dosya boyutu (KiB cinsinden))\n'
|
||||||
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Can puanı) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Açlık puanı) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP seviyesi) [🏹 %5%](dark_aqua show_text=&7Oyun modu)'
|
||||||
|
data_manager_advancements_statistics: '[⭐ Gelişmeler: %1%](color=#ffc43b-#f5c962 show_text=&7Gelişmelerdeki ilerlemeniz:\n&8%2%) [⌛ Oynama Süresi: %3%s](color=#62a9f5-#7ab8fa show_text=&7Oyunda geçen süre\n&8⚠ Oyun içi istatistiklere dayanır)\n'
|
||||||
|
data_manager_item_buttons: '[Görünüm:](gray) [[🪣 Envanter…]](color=#a17b5f-#f5b98c show_text=&7Görüntülemek için tıklayın run_command=/inventory %1% %2%) [[⌀ Ender Sandığı…]](#b649c4-#d254ff show_text=&7Görüntülemek için tıklayın run_command=/enderchest %1% %2%)'
|
||||||
|
data_manager_management_buttons: '[Yönet:](gray) [[❌ Sil…]](#ff3300 show_text=&7Bu kullanıcı veri anlığını silmek için tıklayın.\n&8Bu, kullanıcının mevcut verilerini etkilemez.\n&#ff3300&⚠ Geri alınamaz! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ Geri Yükle…]](#00fb9a show_text=&7Bu kullanıcı verisini geri yüklemek için tıklayın.\n&8Bu, kullanıcının verisini bu anlığa ayarlar.\n&#ff3300&⚠ %1%''nın mevcut verisi üzerine yazılacak! suggest_command=/husksync:userdata restore %1% %2%) [[※ Sabitle/Çöz…]](#d8ff2b show_text=&7Bu kullanıcı veri anlığını sabitlemek veya çözmek için tıklayın\n&8Sabitlenmiş anlıklar otomatik olarak döndürülmez run_command=/userdata pin %1% %2%)'
|
||||||
|
data_manager_system_buttons: '[Sistem:](gray) [[⏷ Dosya Dök…]](dark_gray show_text=&7Bu ham kullanıcı veri anlığını bir dosyaya dökmek için tıklayın.\n&8Veri dökümleri ~/plugins/HuskSync/dumps/ klasöründe bulunabilir. run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dök…]](dark_gray show_text=&7Bu ham kullanıcı veri anlığını mc-logs servisine dökme için tıklayın\n&8Verileri içeren bir URL ile sağlanacaksınız. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
|
data_manager_advancements_preview_remaining: 've %1% daha fazla…'
|
||||||
|
data_list_title: '[%1%''ın kullanıcı veri anlıkları:](#00fb9a) [(%2%-%3% /](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
|
data_list_item: '[%1%](gray show_text=&7Oyuncu Veri Anlığı %2% için %3%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Sabitlendi:\n&8Sabitlenmiş anlıklar otomatik olarak döndürülmez. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Versiyon zaman damgası:&7\n&8Verinin ne zaman kaydedildiği\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Kaydetme sebebi:\n&8Verinin kaydedilme nedeni run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Anlık boyutu:&7\n&8Anlının tahmini dosya boyutu (KiB cinsinden) run_command=/userdata view %2% %3%)'
|
||||||
|
data_deleted: '[❌ Kullanıcı veri anlığı başarıyla silindi](#00fb9a) [%1%](#00fb9a show_text=&7Versiyon UUID:\n&8%2%) [için](#00fb9a) [%3%.](#00fb9a show_text=&7Oyuncu UUID:\n&8%4%)'
|
||||||
|
data_restored: '[⏪ Başarıyla geri yüklendi](#00fb9a) [%1%](#00fb9a show_text=&7Oyuncu UUID:\n&8%2%)[''ın mevcut kullanıcı verisi anlığından](#00fb9a) [%3%.](#00fb9a show_text=&7Versiyon UUID:\n&8%4%)'
|
||||||
|
data_pinned: '[※ Kullanıcı veri anlığı başarıyla sabitlendi](#00fb9a) [%1%](#00fb9a show_text=&7Versiyon UUID:\n&8%2%) [için](#00fb9a) [%3%.](#00fb9a show_text=&7Oyuncu UUID:\n&8%4%)'
|
||||||
|
data_unpinned: '[※ Kullanıcı veri anlığı başarıyla çözüldü](#00fb9a) [%1%](#00fb9a show_text=&7Versiyon UUID:\n&8%2%) [için](#00fb9a) [%3%.](#00fb9a show_text=&7Oyuncu UUID:\n&8%4%)'
|
||||||
|
data_dumped: '[☂ Kullanıcı veri anlığı başarıyla döküldü %1% için %2%:](#00fb9a) &7%3%'
|
||||||
|
list_footer: '\n%1%[Sayfa](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
|
list_previous_page_button: '[◀](white show_text=&7Önceki sayfayı görüntüle run_command=%2% %1%) '
|
||||||
|
list_next_page_button: ' [▶](white show_text=&7Sonraki sayfayı görüntüle run_command=%2% %1%)'
|
||||||
|
list_page_jumpers: '(%1%)'
|
||||||
|
list_page_jumper_button: '[%1%](show_text=&7Sayfaya git %1% run_command=%2% %1%)'
|
||||||
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
|
list_page_jumper_separator: ' '
|
||||||
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
|
up_to_date: '[HuskSync](#00fb9a bold) [| HuskSync\''in en son sürümünü kullanıyorsunuz (v%1%).](#00fb9a)'
|
||||||
|
update_available: '[HuskSync](#ff7e5e bold) [| HuskSync\''in yeni bir sürümü mevcut: v%1% (kullanılan sürüm: v%2%).](#ff7e5e)'
|
||||||
|
reload_complete: '[HuskSync](#00fb9a bold) [| Yapılandırma ve mesaj dosyaları yeniden yüklendi.](#00fb9a)\n[⚠ Lütfen yapılandırma dosyalarının tüm sunucularda güncel olduğundan emin olun!](#00fb9a)\n[Yapılandırma değişikliklerinin etkili olabilmesi için bir yeniden başlatma gereklidir.](#00fb9a italic)'
|
||||||
|
error_invalid_syntax: '[Hata:](#ff3300) [Yanlış sözdizimi. Kullanım:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Öneri için tıklayın Suggest_command=%1%)'
|
||||||
|
error_invalid_player: '[Hata:](#ff3300) [Bu isimde bir oyuncu bulunamadı.](#ff7e5e)'
|
||||||
|
error_no_permission: '[Hata:](#ff3300) [Bu komutu gerçekleştirmek için izniniz yok](#ff7e5e)'
|
||||||
|
error_console_command_only: '[Hata:](#ff3300) [Bu komut yalnızca konsoldan çalıştırılabilir](#ff7e5e)'
|
||||||
|
error_in_game_command_only: 'Hata: Bu komut yalnızca oyun içinde kullanılabilir.'
|
||||||
|
error_no_data_to_display: '[Hata:](#ff3300) [Görüntülenecek kullanıcı verisi bulunamadı.](#ff7e5e)'
|
||||||
|
error_invalid_version_uuid: '[Hata:](#ff3300) [Bu sürüm UUID''si için kullanıcı verisi bulunamadı.](#ff7e5e)'
|
||||||
|
husksync_command_description: 'HuskSync eklentisini yönet'
|
||||||
|
userdata_command_description: 'Oyuncu verilerini görüntüle, yönet ve geri yükle'
|
||||||
|
inventory_command_description: 'Oyuncunun envanterini görüntüle ve düzenle'
|
||||||
|
enderchest_command_description: 'Oyuncunun Ender Chest''ini görüntüle ve düzenle'
|
||||||
@@ -11,6 +11,7 @@ data_manager_title: '[Viewing user data snapshot](#00fb9a) [%1%](#00fb9a show_te
|
|||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version timestamp:\n&8When the data was saved)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7Version timestamp:\n&8When the data was saved)'
|
||||||
data_manager_pinned: '[※ Snapshot pinned](#d8ff2b show_text=&7Pinned:\n&8This user data snapshot won''t be automatically rotated.)'
|
data_manager_pinned: '[※ Snapshot pinned](#d8ff2b show_text=&7Pinned:\n&8This user data snapshot won''t be automatically rotated.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7Health points) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7Hunger points) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7XP level) [🏹 %5%](dark_aqua show_text=&7Game mode)'
|
||||||
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7Advancements you have progress in:\n&8%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7In-game play time\n&8⚠ Based on in-game statistics)\n'
|
data_manager_advancements_statistics: '[⭐ Advancements: %1%](color=#ffc43b-#f5c962 show_text=&7Advancements you have progress in:\n&8%2%) [⌛ Play Time: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7In-game play time\n&8⚠ Based on in-game statistics)\n'
|
||||||
@@ -33,6 +34,17 @@ list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| You are running the latest version of HuskSync (v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| A new version of HuskSync is available: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| Перезавантажено конфіґ та файли повідомлень.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| Перезавантажено конфіґ та файли повідомлень.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||||
|
|||||||
@@ -6,44 +6,56 @@ inventory_viewer_opened: '[正在查看玩家](#00fb9a) [%1%](#00fb9a bold) [于
|
|||||||
ender_chest_viewer_opened: '[正在查看玩家](#00fb9a) [%1%](#00fb9a bold) [于 ⌚ %2% 的末影箱备份](#00fb9a)'
|
ender_chest_viewer_opened: '[正在查看玩家](#00fb9a) [%1%](#00fb9a bold) [于 ⌚ %2% 的末影箱备份](#00fb9a)'
|
||||||
data_update_complete: '[🔔 你的用户数据已更新!](#00fb9a)'
|
data_update_complete: '[🔔 你的用户数据已更新!](#00fb9a)'
|
||||||
data_update_failed: '[🔔 无法更新你的用户数据! 请联系管理员.](#ff7e5e)'
|
data_update_failed: '[🔔 无法更新你的用户数据! 请联系管理员.](#ff7e5e)'
|
||||||
user_registration_complete: '[⭐ User registration complete!](#00fb9a)'
|
user_registration_complete: '[⭐ 用户注册完成!](#00fb9a)'
|
||||||
data_manager_title: '[正在查看玩家](#00fb9a) [%3%](#00fb9a bold show_text=&7玩家 UUID:\n&8%4%) [的数据备份](#00fb9a) [%1%](#00fb9a show_text=&7备份版本 UUID:\n&8%2%) [:](#00fb9a)'
|
data_manager_title: '[正在查看玩家](#00fb9a) [%3%](#00fb9a bold show_text=&7玩家 UUID:\n&8%4%) [的数据备份](#00fb9a) [%1%](#00fb9a show_text=&7备份版本 UUID:\n&8%2%) [:](#00fb9a)'
|
||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7备份时间:\n&7何时保存了此数据)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7备份时间:\n&7何时保存了此数据)'
|
||||||
data_manager_pinned: '[※ 置顶备份](#d8ff2b show_text=&7置顶:\n&8此数据备份不会按照备份时间自动排序.)'
|
data_manager_pinned: '[※ 置顶备份](#d8ff2b show_text=&7置顶:\n&8此数据备份不会按照备份时间自动排序.)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7备份原因:\n&7为何保存了此数据)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7备份原因:\n&7为何保存了此数据)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7服务器:\n&8保存数据的服务器的名称)'
|
||||||
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7快照大小:\n&8快照的估计文件大小(以KiB为单位))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7血量) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7饱食度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7经验等级) [🏹 %5%](dark_aqua show_text=&7游戏模式)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7血量) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7饱食度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7经验等级) [🏹 %5%](dark_aqua show_text=&7游戏模式)'
|
||||||
data_manager_advancements_statistics: '[⭐ 成就: %1%](color=#ffc43b-#f5c962 show_text=&7%2%) [⌛ 游玩时间: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7⚠ 基于游戏内的统计)\n'
|
data_manager_advancements_statistics: '[⭐ 成就: %1%](color=#ffc43b-#f5c962 show_text=&7%2%) [⌛ 游玩时间: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7⚠ 基于游戏内的统计)\n'
|
||||||
data_manager_item_buttons: '[View:](gray) [[🪣 背包…]](color=#a17b5f-#f5b98c show_text=&7点击查看 run_command=/inventory %1% %2%) [[⌀ 末影箱…]](#b649c4-#d254ff show_text=&7点击查看 run_command=/enderchest %1% %2%)'
|
data_manager_item_buttons: '[View:](gray) [[🪣 背包…]](color=#a17b5f-#f5b98c show_text=&7点击查看 run_command=/inventory %1% %2%) [[⌀ 末影箱…]](#b649c4-#d254ff show_text=&7点击查看 run_command=/enderchest %1% %2%)'
|
||||||
data_manager_management_buttons: '[Manage:](gray) [[❌ 删除…]](#ff3300 show_text=&7点击删除此数据备份.\n这不会影响玩家当前的数据.\n&#ff3300&⚠ 此操作不可撤销! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ 恢复…]](#00fb9a show_text=&7点击让玩家恢复到此数据备份.\n这将会使玩家的数据恢复到这个备份.\n&#ff3300&⚠ %1% 当前的用户数据会被备份数据所覆盖! suggest_command=/husksync:userdata restore %1% %2%) [[※ Pin/Unpin…]](#d8ff2b show_text=&7Click to pin or unpin this user data snapshot\n&8Pinned snapshots won''t be automatically rotated run_command=/userdata pin %1% %2%)'
|
data_manager_management_buttons: '[Manage:](gray) [[❌ 删除…]](#ff3300 show_text=&7点击删除此数据备份.\n这不会影响玩家当前的数据.\n&#ff3300&⚠ 此操作不可撤销! suggest_command=/husksync:userdata delete %1% %2%) [[⏪ 恢复…]](#00fb9a show_text=&7点击让玩家恢复到此数据备份.\n这将会使玩家的数据恢复到这个备份.\n&#ff3300&⚠ %1% 当前的用户数据会被备份数据所覆盖! suggest_command=/husksync:userdata restore %1% %2%) [[※ 固定/取消固定…]](#d8ff2b show_text=&7单击可固定或取消固定此用户数据快照\n&8固定的快照不会自动重载 run_command=/userdata pin %1% %2%)'
|
||||||
data_manager_system_buttons: '[System:](gray) [[⏷ File Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to a file.\n&8Data dumps can be found in ~/plugins/HuskSync/dumps/ run_command=/husksync:userdata dump %1% %2% file) [[☂ Web Dump…]](dark_gray show_text=&7Click to dump this raw user data snapshot to the mc-logs service\n&8You will be provided with a URL containing the data. run_command=/husksync:userdata dump %1% %2% web)'
|
data_manager_system_buttons: '[System:](gray) [[⏷ 文件储存…]](dark_gray show_text=&7单击将此原始用户数据快照储存到文件.\n&8数据储存可以在 ~/plugins/HuskSync/dumps/ 中找到 run_command=/husksync:userdata dump %1% %2% file) [[☂ Web 储存…]](dark_gray show_text=&7单击将此原始用户数据快照转储到mc日志服务\n&8您将获得包含数据的URL. run_command=/husksync:userdata dump %1% %2% web)'
|
||||||
data_manager_advancements_preview_remaining: '还有 %1% …'
|
data_manager_advancements_preview_remaining: '还有 %1% …'
|
||||||
data_list_title: '[%1%''s user data snapshots:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
data_list_title: '[%1%的用户数据快照:](#00fb9a) [(%2%-%3% of](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
data_list_item: '[%1%](gray show_text=&7User Data Snapshot for %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7Pinned:\n&8Pinned snapshots won''t be automatically rotated. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7Version timestamp:&7\n&8When the data was saved\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7Save cause:\n&8What caused the data to be saved run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:&7\n&8Estimated file size of the snapshot (in KiB) run_command=/userdata view %2% %3%)'
|
data_list_item: '[%1%](gray show_text=&7的用户数据快照 %2%&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7固定:\n&8固定的快照不会自动加载. run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7版本时间戳:&7\n&8保存数据时\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7保存原因:\n&8是什么导致数据被保存 run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7快照大小:&7\n&8快照的估计文件大小(以KiB为单位) run_command=/userdata view %2% %3%)'
|
||||||
data_deleted: '[❌ 成功删除玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&7%4%) [的数据备份](#00fb9a) [%1%.](#00fb9a show_text=&7备份版本 UUID:\n&7%2%)'
|
data_deleted: '[❌ 成功删除玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&7%4%) [的数据备份](#00fb9a) [%1%.](#00fb9a show_text=&7备份版本 UUID:\n&7%2%)'
|
||||||
data_restored: '[⏪ 成功恢复玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&7%2%)[的数据备份](#00fb9a) [%3%.](#00fb9a show_text=&7备份版本 UUID:\n&7%4%)'
|
data_restored: '[⏪ 成功恢复玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&7%2%)[的数据备份](#00fb9a) [%3%.](#00fb9a show_text=&7备份版本 UUID:\n&7%4%)'
|
||||||
data_pinned: '[※ 成功置顶玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的数据备份](#00fb9a) [%1%.](#00fb9a show_text=&7备份版本 UUID:\n&8%2%)'
|
data_pinned: '[※ 成功置顶玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的数据备份](#00fb9a) [%1%.](#00fb9a show_text=&7备份版本 UUID:\n&8%2%)'
|
||||||
data_unpinned: '[※ 成功取消置顶玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的数据备份](#00fb9a) [%1%.](#00fb9a show_text=&7备份版本 UUID:\n&8%2%)'
|
data_unpinned: '[※ 成功取消置顶玩家](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的数据备份](#00fb9a) [%1%.](#00fb9a show_text=&7备份版本 UUID:\n&8%2%)'
|
||||||
data_dumped: '[☂ Successfully dumped the user data snapshot %1% for %2% to:](#00fb9a) &7%3%'
|
data_dumped: '[☂ 已成功将 %1% 的用户数据快照 %2% 转储到:](#00fb9a) &7%3%'
|
||||||
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
list_footer: '\n%1%[Page](#00fb9a) [%2%](#00fb9a)/[%3%](#00fb9a)%4% %5%'
|
||||||
list_previous_page_button: '[◀](white show_text=&7View previous page run_command=%2% %1%) '
|
list_previous_page_button: '[◀](white show_text=&7查看上一页 run_command=%2% %1%) '
|
||||||
list_next_page_button: ' [▶](white show_text=&7View next page run_command=%2% %1%)'
|
list_next_page_button: ' [▶](white show_text=&7查看下一页 run_command=%2% %1%)'
|
||||||
list_page_jumpers: '(%1%)'
|
list_page_jumpers: '(%1%)'
|
||||||
list_page_jumper_button: '[%1%](show_text=&7Jump to page %1% run_command=%2% %1%)'
|
list_page_jumper_button: '[%1%](show_text=&7跳转到页面 %1% run_command=%2% %1%)'
|
||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| 你正在使用最新版本的HuskSync (v%1%)](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| 你正在使用最新版本的HuskSync (v%1%)](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| 一个新版本的HuskSync已经可以更新: v%1% (当前: v%2%)](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| 一个新版本的HuskSync已经可以更新: v%1% (当前: v%2%)](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 插件配置和语言文件已重载.](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 插件配置和语言文件已重载.](#00fb9a)\n[⚠ 确保所有服务器上的配置文件都是最新的!](#00fb9a)\n[需要重新启动配置更改才能生效.](#00fb9a italic)'
|
||||||
error_invalid_syntax: ':](#ff3300) [格式错误, 使用方法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&Click to suggest suggest_command=%1%)'
|
error_invalid_syntax: ':](#ff3300) [格式错误, 使用方法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&点击建议 suggest_command=%1%)'
|
||||||
error_invalid_player: '[错误:](#ff3300) [无法找到目标玩家.](#ff7e5e)'
|
error_invalid_player: '[错误:](#ff3300) [无法找到目标玩家.](#ff7e5e)'
|
||||||
error_no_permission: '[错误:](#ff3300) [你没有执行此指令的权限](#ff7e5e)'
|
error_no_permission: '[错误:](#ff3300) [你没有执行此指令的权限](#ff7e5e)'
|
||||||
error_console_command_only: '[错误:](#ff3300) [该指令只能在控制台运行](#ff7e5e)'
|
error_console_command_only: '[错误:](#ff3300) [该指令只能在控制台运行](#ff7e5e)'
|
||||||
error_in_game_command_only: 'Error: 该指令只能在游戏内运行.'
|
error_in_game_command_only: 'Error: 该指令只能在游戏内运行.'
|
||||||
error_no_data_to_display: '[错误:](#ff3300) [无法找到用户数据显示.](#ff7e5e)'
|
error_no_data_to_display: '[错误:](#ff3300) [无法找到用户数据显示.](#ff7e5e)'
|
||||||
error_invalid_version_uuid: '[错误:](#ff3300) [无法找到该备份的 UUID .](#ff7e5e)'
|
error_invalid_version_uuid: '[错误:](#ff3300) [无法找到该备份的 UUID .](#ff7e5e)'
|
||||||
husksync_command_description: 'Manage the HuskSync plugin'
|
husksync_command_description: '管理HuskSync插件'
|
||||||
userdata_command_description: 'View, manage & restore player userdata'
|
userdata_command_description: '查看、管理和恢复玩家用户数据'
|
||||||
inventory_command_description: 'View & edit a player''s inventory'
|
inventory_command_description: '查看和编辑玩家的背包'
|
||||||
enderchest_command_description: 'View & edit a player''s Ender Chest'
|
enderchest_command_description: '查看和编辑玩家的末影箱'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ data_manager_title: '[查看](#00fb9a) [%3%](#00fb9a bold show_text=&7玩家 UUI
|
|||||||
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7快照時間:\n&8何時保存的資料)'
|
data_manager_timestamp: '[⌚ %1%](#ffc43b-#f5c962 show_text=&7快照時間:\n&8何時保存的資料)'
|
||||||
data_manager_pinned: '[※ 被標記的快照](#d8ff2b show_text=&7標記:\n&8此快照資料不會自動輪換更新)'
|
data_manager_pinned: '[※ 被標記的快照](#d8ff2b show_text=&7標記:\n&8此快照資料不會自動輪換更新)'
|
||||||
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7保存原因:\n&8保存此快照的原因)'
|
data_manager_cause: '[⚑ %1%](#23a825-#36f539 show_text=&7保存原因:\n&8保存此快照的原因)'
|
||||||
|
data_manager_server: '[Ⓢ %1%](#ff87b3-#f5538e show_text=&7Server:\n&8Name of the server the data was saved on)'
|
||||||
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
data_manager_size: '[⏏ %1%](color=#62a9f5-#7ab8fa show_text=&7Snapshot size:\n&8Estimated file size of the snapshot (in KiB))\n'
|
||||||
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7血量) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7飽食度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7經驗等級) [🏹 %5%](dark_aqua show_text=&7遊戲模式)'
|
data_manger_status: '[%1%](red)[/](gray)[%2%](red)[×](gray)[❤](red show_text=&7血量) [%3%](yellow)[×](gray)[🍖](yellow show_text=&7飽食度) [ʟᴠ](green)[.](gray)[%4%](green show_text=&7經驗等級) [🏹 %5%](dark_aqua show_text=&7遊戲模式)'
|
||||||
data_manager_advancements_statistics: '[⭐ 成就: %1%](color=#ffc43b-#f5c962 show_text=&7已獲得的成就:\n&8%2%) [⌛ 遊戲時間: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7遊戲內的遊玩時間\n&8⚠ 根據遊戲內統計)\n'
|
data_manager_advancements_statistics: '[⭐ 成就: %1%](color=#ffc43b-#f5c962 show_text=&7已獲得的成就:\n&8%2%) [⌛ 遊戲時間: %3%ʜʀs](color=#62a9f5-#7ab8fa show_text=&7遊戲內的遊玩時間\n&8⚠ 根據遊戲內統計)\n'
|
||||||
@@ -33,6 +34,17 @@ list_page_jumper_button: '[%1%](show_text=&7跳至第 %1% 頁 run_command=%2% %1
|
|||||||
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
list_page_jumper_current_page: '[%1%](#00fb9a)'
|
||||||
list_page_jumper_separator: ' '
|
list_page_jumper_separator: ' '
|
||||||
list_page_jumper_group_separator: '…'
|
list_page_jumper_group_separator: '…'
|
||||||
|
save_cause_disconnect: 'disconnect'
|
||||||
|
save_cause_world_save: 'world save'
|
||||||
|
save_cause_death: 'death'
|
||||||
|
save_cause_server_shutdown: 'server shutdown'
|
||||||
|
save_cause_inventory_command: 'inventory command'
|
||||||
|
save_cause_enderchest_command: 'enderchest command'
|
||||||
|
save_cause_backup_restore: 'backup restore'
|
||||||
|
save_cause_api: 'API'
|
||||||
|
save_cause_mpdb_migration: 'MPDB migration'
|
||||||
|
save_cause_legacy_migration: 'legacy migration'
|
||||||
|
save_cause_converted_from_v2: 'converted from v2'
|
||||||
up_to_date: '[HuskSync](#00fb9a bold) [| 您運行的是最新版本的 HuskSync (v%1%).](#00fb9a)'
|
up_to_date: '[HuskSync](#00fb9a bold) [| 您運行的是最新版本的 HuskSync (v%1%).](#00fb9a)'
|
||||||
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 已重新載入配置和訊息文件](#00fb9a)\n[⚠ Ensure config files are up-to-date on all servers!](#00fb9a)\n[A restart is needed for config changes to take effect.](#00fb9a italic)'
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
This page contains the configuration file reference for HuskSync. The config file is located in `/plugins/HuskSync/config.yml`
|
This page contains the configuration structure for HuskSync.
|
||||||
|
|
||||||
## Example config
|
## Configuration structure
|
||||||
|
📁 `plugins/HuskSync/`
|
||||||
|
- 📄 `config.yml`: General plugin configuration
|
||||||
|
- 📄 `server.yml`: Server ID configuration
|
||||||
|
- 📄 `messages-xx-xx.yml`: Plugin locales, formatted in MineDown (see [[Translations]])
|
||||||
|
|
||||||
|
## Example files
|
||||||
<details>
|
<details>
|
||||||
<summary>config.yml</summary>
|
<summary>config.yml</summary>
|
||||||
|
|
||||||
@@ -12,7 +18,7 @@ This page contains the configuration file reference for HuskSync. The config fil
|
|||||||
# ┣╸ Information: https://william278.net/project/husksync
|
# ┣╸ Information: https://william278.net/project/husksync
|
||||||
# ┣╸ Config Help: https://william278.net/docs/husksync/config-file/
|
# ┣╸ Config Help: https://william278.net/docs/husksync/config-file/
|
||||||
# ┗╸ Documentation: https://william278.net/docs/husksync
|
# ┗╸ Documentation: https://william278.net/docs/husksync
|
||||||
# Locale of the default language file to use. Docs: https://william278.net/docs/huskhomes/translations
|
# Locale of the default language file to use. Docs: https://william278.net/docs/husksync/translations
|
||||||
language: en-gb
|
language: en-gb
|
||||||
# Whether to automatically check for plugin updates on startup
|
# Whether to automatically check for plugin updates on startup
|
||||||
check_for_updates: true
|
check_for_updates: true
|
||||||
@@ -54,6 +60,8 @@ redis:
|
|||||||
password: ''
|
password: ''
|
||||||
use_ssl: false
|
use_ssl: false
|
||||||
synchronization:
|
synchronization:
|
||||||
|
# The mode of data synchronization to use (DELAY or LOCKSTEP). DELAY should be fine for most networks. Docs: https://william278.net/docs/husksync/sync-modes
|
||||||
|
mode: DELAY
|
||||||
# The number of data snapshot backups that should be kept at once per user
|
# The number of data snapshot backups that should be kept at once per user
|
||||||
max_user_data_snapshots: 16
|
max_user_data_snapshots: 16
|
||||||
# Number of hours between new snapshots being saved as backups (Use "0" to backup all snapshots)
|
# Number of hours between new snapshots being saved as backups (Use "0" to backup all snapshots)
|
||||||
@@ -63,15 +71,19 @@ synchronization:
|
|||||||
- INVENTORY_COMMAND
|
- INVENTORY_COMMAND
|
||||||
- ENDERCHEST_COMMAND
|
- ENDERCHEST_COMMAND
|
||||||
- BACKUP_RESTORE
|
- BACKUP_RESTORE
|
||||||
- CONVERTED_FROM_V2
|
|
||||||
- LEGACY_MIGRATION
|
- LEGACY_MIGRATION
|
||||||
- MPDB_MIGRATION
|
- MPDB_MIGRATION
|
||||||
# Whether to create a snapshot for users on a world when the server saves that world
|
# Whether to create a snapshot for users on a world when the server saves that world
|
||||||
save_on_world_save: true
|
save_on_world_save: true
|
||||||
# Whether to create a snapshot for users when they die (containing their death drops)
|
save_on_death:
|
||||||
save_on_death: false
|
# Whether to create a snapshot for users when they die (containing their death drops)
|
||||||
# Whether to save empty death drops for users when they die
|
enabled: true
|
||||||
save_empty_drops_on_death: true
|
# What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server
|
||||||
|
items_to_save: DROPS
|
||||||
|
# Should a death snapshot still be created even if the items to save on the player's death are empty?
|
||||||
|
save_empty_items: false
|
||||||
|
# Whether dead players who log out and log in to a different server should have their items saved.
|
||||||
|
sync_dead_players_changing_server: true
|
||||||
# Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing
|
# Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing
|
||||||
compress_data: true
|
compress_data: true
|
||||||
# Where to display sync notifications (ACTION_BAR, CHAT, TOAST or NONE)
|
# Where to display sync notifications (ACTION_BAR, CHAT, TOAST or NONE)
|
||||||
@@ -80,9 +92,7 @@ synchronization:
|
|||||||
persist_locked_maps: true
|
persist_locked_maps: true
|
||||||
# Whether to synchronize player max health (requires health syncing to be enabled)
|
# Whether to synchronize player max health (requires health syncing to be enabled)
|
||||||
synchronize_max_health: true
|
synchronize_max_health: true
|
||||||
# Whether dead players who log out and log in to a different server should have their items saved. You may need to modify this if you're using the keepInventory gamerule.
|
# If using the DELAY sync method, how long should this server listen for Redis key data updates before pulling data from the database instead (i.e., if the user did not change servers).
|
||||||
synchronize_dead_players_changing_server: true
|
|
||||||
# How long, in milliseconds, this server should wait for a response from the redis server before pulling data from the database instead (i.e., if the user did not change servers).
|
|
||||||
network_latency_milliseconds: 500
|
network_latency_milliseconds: 500
|
||||||
# Which data types to synchronize (Docs: https://william278.net/docs/husksync/sync-features)
|
# Which data types to synchronize (Docs: https://william278.net/docs/husksync/sync-features)
|
||||||
features:
|
features:
|
||||||
@@ -109,5 +119,17 @@ synchronization:
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
## Messages files
|
<details>
|
||||||
You can customize the plugin locales, too, by editing your `messages-xx-xx.yml` file. This file is formatted using [MineDown syntax](https://github.com/Phoenix616/MineDown). For more information, see [[Translations]].
|
<summary>server.yml</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||||
|
# ┃ HuskSync Server ID config ┃
|
||||||
|
# ┃ Developed by William278 ┃
|
||||||
|
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||||
|
# ┣╸ This file should contain the ID of this server as defined in your proxy config.
|
||||||
|
# ┗╸ If you join it using /server alpha, then set it to 'alpha' (case-sensitive)
|
||||||
|
name: beta
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
@@ -4,12 +4,15 @@ To do this, you create and register an implementation of a platform `Data` class
|
|||||||
|
|
||||||
> **Note:** Before you begin, consider if this is what you'd like to do. For simpler/smaller data sync tasks you may wish to consider using the PersistentDataContainer API format instead, which is a bit more portable if you decide to exit the HuskSync ecosystem.
|
> **Note:** Before you begin, consider if this is what you'd like to do. For simpler/smaller data sync tasks you may wish to consider using the PersistentDataContainer API format instead, which is a bit more portable if you decide to exit the HuskSync ecosystem.
|
||||||
|
|
||||||
|
If you'd like to have a look at an example of a data extension for HuskSync that provides serializers for Pixelmon data when running the plugin on Arclight, check out [PokeSync by GsTio86](https://github.com/GsTio86/PokeSync)!
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
1. [Extending the BukkitData Class](#1-extending-the-bukkitdata-class)
|
1. [Extending the BukkitData Class](#1-extending-the-bukkitdata-class)
|
||||||
1. [Implementing Adaptable](#11-implementing-adaptable)
|
1. [Implementing Adaptable](#11-implementing-adaptable)
|
||||||
2. [Extending the BukkitSerializer Class](#2-extending-the-bukkitserializer-class)
|
2. [Extending the BukkitSerializer Class](#2-extending-the-bukkitserializer-class)
|
||||||
3. [Identifiers and registering our Serializer](#3-identifiers--registering-our-serializer)
|
3. [Identifiers and registering our Serializer](#3-identifiers--registering-our-serializer)
|
||||||
4. [Setting and Getting our Data to/from a User](#4-setting-and-getting-our-data-tofrom-a-user)
|
4. [Setting and Getting our Data to/from a User](#4-setting-and-getting-our-data-tofrom-a-user)
|
||||||
|
1. [Persisting custom data on the DataSaveEvent](#41-persisting-custom-data-on-the-datasaveevent)
|
||||||
|
|
||||||
## 1. Extending the BukkitData Class
|
## 1. Extending the BukkitData Class
|
||||||
* HuskSync provides a `Data` interface that you must implement that will represent your custom data.
|
* HuskSync provides a `Data` interface that you must implement that will represent your custom data.
|
||||||
@@ -129,3 +132,13 @@ huskSyncAPI.getUser(player).setData(LOGIN_PARTICLES_ID, loginParticleData);
|
|||||||
// Get our data from a player
|
// Get our data from a player
|
||||||
LoginParticleData loginParticleData = (LoginParticleData) huskSyncAPI.getUser(player).getData(LOGIN_PARTICLES_ID);
|
LoginParticleData loginParticleData = (LoginParticleData) huskSyncAPI.getUser(player).getData(LOGIN_PARTICLES_ID);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 4.1 Persisting custom data on the DataSaveEvent
|
||||||
|
Add an EventListener to the `DataSaveEvent` and use the `#editData` consumer method to apply custom data during standard DataSaves. This will persist data to users any time the data save routine executes (on user logout, server shutdownm, world save, etc).
|
||||||
|
|
||||||
|
```java
|
||||||
|
@EventHandler
|
||||||
|
public void onDataSave(BukkitDataSaveEvent event) {
|
||||||
|
event.editData((unpacked) -> unpacked.setData(LOGIN_PARTICLES_ID, new LoginParticleData("FIREWORKS_SPARK", 10)));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ huskSyncAPI.editCurrentInventory(user, inventory -> {
|
|||||||
// Get the player's inventory contents
|
// Get the player's inventory contents
|
||||||
ItemStack[] inventoryContents = ((BukkitData.Items.Inventory) inventory).getContents();
|
ItemStack[] inventoryContents = ((BukkitData.Items.Inventory) inventory).getContents();
|
||||||
|
|
||||||
// The array of ItemStacks is a copy of the player's inventory contents (in 1.20.1, this is an array of length 42)
|
// The array of ItemStacks is a copy of the player's inventory contents (Typically an array of length 42)
|
||||||
inventoryContents[0] = new ItemStack(Material.DIAMOND_SWORD);
|
inventoryContents[0] = new ItemStack(Material.DIAMOND_SWORD);
|
||||||
inventoryContents[1] = null; // null = an empty slot
|
inventoryContents[1] = null; // null = an empty slot
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,8 @@ The name of the generated .json file will match the following format: `<username
|
|||||||
"pinned": false,
|
"pinned": false,
|
||||||
"timestamp": "2023-09-15T17:27:08.6768038+01:00",
|
"timestamp": "2023-09-15T17:27:08.6768038+01:00",
|
||||||
"save_cause": "DISCONNECT",
|
"save_cause": "DISCONNECT",
|
||||||
"minecraft_version": "1.20.1",
|
"server": "alpha",
|
||||||
|
"minecraft_version": "1.20.2",
|
||||||
"platform_type": "bukkit",
|
"platform_type": "bukkit",
|
||||||
"format_version": 4,
|
"format_version": 4,
|
||||||
"data": {
|
"data": {
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ HuskSync requires Redis to operate (for reasons demonstrated below). Redis is an
|
|||||||
<details>
|
<details>
|
||||||
<summary> <b>How does the plugin synchronize data?</b></summary>
|
<summary> <b>How does the plugin synchronize data?</b></summary>
|
||||||
|
|
||||||

|
HuskSync makes use of both MySQL and Redis for optimal data synchronization. You have the option of using one of two [[Sync Modes]], which synchronize data between servers (`DELAY` or `LOCKSTEP`)
|
||||||
|
|
||||||
HuskSync makes use of both MySQL and Redis for optimal data synchronization.
|
|
||||||
|
|
||||||
When a user changes servers, in addition to data being saved to MySQL, it is also cached via the Redis server with a temporary expiry key. When changing servers, the receiving server detects the key and sets the user data from Redis. When a player rejoins the network, the system fetches the last-saved data snapshot from the MySQL Database.
|
When a user changes servers, in addition to data being saved to MySQL, it is also cached via the Redis server with a temporary expiry key. When changing servers, the receiving server detects the key and sets the user data from Redis. When a player rejoins the network, the system fetches the last-saved data snapshot from the MySQL Database.
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ Welcome! This is the plugin documentation for HuskSync v3.x+. Please click throu
|
|||||||
## Documentation
|
## Documentation
|
||||||
* 🖥️ [[Commands]]
|
* 🖥️ [[Commands]]
|
||||||
* ✅ [[Sync Features]]
|
* ✅ [[Sync Features]]
|
||||||
|
* ⚙️ [[Sync Modes]]
|
||||||
* 🟩 [[Plan Hook]]
|
* 🟩 [[Plan Hook]]
|
||||||
* ☂️ [[Dumping UserData]]
|
* ☂️ [[Dumping UserData]]
|
||||||
* 📋 [[Event Priorities]]
|
* 📋 [[Event Priorities]]
|
||||||
|
|||||||
@@ -8,23 +8,27 @@ HuskSync has some special handling when players die, to account for scenarios wh
|
|||||||
* **Snapshot creation on death**—HuskSync can create a special snapshot for backup purposes when a player dies, formed by taking their drops and setting this to their inventory. When `keepInventory` is enabled, the player drops are empty, so this creates an inaccurate snapshot. This option is disabled by default.
|
* **Snapshot creation on death**—HuskSync can create a special snapshot for backup purposes when a player dies, formed by taking their drops and setting this to their inventory. When `keepInventory` is enabled, the player drops are empty, so this creates an inaccurate snapshot. This option is disabled by default.
|
||||||
|
|
||||||
## How can this be fixed?
|
## How can this be fixed?
|
||||||
You will need to set the `synchronization.save_on_death` (which controls making snapshots on death), `save_empty_drops_on_death` (which controls whether snapshots of players who have no items to drop should be created), and `synchronization.synchronize_dead_players_changing_server` (which controls whether to sync dead players when they change servers) options to `false` in `config.yml`.
|
You should change the `items_to_save` mode to `ITEMS_TO_KEEP` instead of drops. Also, ensure `save_empty_items` and `sync_dead_players_changing_server` are enabled.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Example in config.yml</summary>
|
<summary>Example in config.yml</summary>
|
||||||
|
|
||||||
```yml
|
|
||||||
synchronization:
|
|
||||||
# ...
|
|
||||||
save_on_death: false # <-- Set this to false
|
|
||||||
save_empty_drops_on_death: false # <-- Set this to false
|
|
||||||
# ...
|
|
||||||
synchronize_dead_players_changing_server: false # <-- Set this to false
|
|
||||||
```
|
|
||||||
|
|
||||||
|
```yml
|
||||||
|
synchronization:
|
||||||
|
#...
|
||||||
|
save_on_death:
|
||||||
|
# Whether to create a snapshot for users when they die (containing their death drops)
|
||||||
|
enabled: true
|
||||||
|
# What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server
|
||||||
|
items_to_save: ITEMS_TO_KEEP
|
||||||
|
# Should a death snapshot still be created even if the items to save on the player's death are empty?
|
||||||
|
save_empty_items: true
|
||||||
|
# Whether dead players who log out and log in to a different server should have their items saved.
|
||||||
|
sync_dead_players_changing_server: true
|
||||||
|
#...
|
||||||
|
```
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
## Troubleshooting with custom keepInventory setups
|
## Troubleshooting with custom keepInventory setups
|
||||||
If the above doesn't work for you, you may need to do more things to get this to work properly.
|
If the above doesn't work for you, you may need to do more things to get this to work properly.
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
This guide will walk you through how to upgrade from HuskSync v1.4.x to HuskSync v2.x or v3.x. Data from HuskSync v2.x will automatically be imported into HuskSync v3.x.
|
This guide will walk you through how to upgrade from HuskSync v1.4.x to HuskSync v3.x. Data from HuskSync v2.x will automatically be imported into HuskSync v3.x.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- MySQL Database with HuskSync v1.4.x data
|
- MySQL Database with HuskSync v1.4.x data
|
||||||
@@ -9,15 +9,15 @@ This guide will walk you through how to upgrade from HuskSync v1.4.x to HuskSync
|
|||||||
### 1. Uninstall HuskSync v1.x from all servers
|
### 1. Uninstall HuskSync v1.x from all servers
|
||||||
- Switch off all servers and your proxy
|
- Switch off all servers and your proxy
|
||||||
- Delete the .jar file from your `~/plugins/` folders on your Spigot servers
|
- Delete the .jar file from your `~/plugins/` folders on your Spigot servers
|
||||||
- Also delete the .jar file from your `~/plugins/` folders on your Proxy. HuskSync v2.x no longer requires a proxy plugin.
|
- Also delete the .jar file from your `~/plugins/` folders on your Proxy. HuskSync v3.x no longer requires a proxy plugin.
|
||||||
- Delete (or make a copy and delete) all HuskSync config data folders (`~/plugins/HuskSync/`). HuskSync 2.x has a new config and messages file.
|
- Delete (or make a copy and delete) all HuskSync config data folders (`~/plugins/HuskSync/`). HuskSync v3.x has new `config.yml`, `messages-xx-xx.yml` and `server.yml` files.
|
||||||
|
|
||||||
### 2. Install HuskSync v2.x on all Spigot servers
|
### 2. Install HuskSync v3.x on all Spigot servers
|
||||||
- HuskSync v2.x must only be installed on your Spigot servers, not your proxy.
|
- HuskSync v3.x must only be installed on your Spigot servers, not your proxy.
|
||||||
- Follow the setup instructions [here](Setup).
|
- Follow the setup instructions [here](Setup).
|
||||||
|
|
||||||
### 3. Configure the migrator
|
### 3. Configure the migrator
|
||||||
- With your servers back on and correctly configured to run HuskSync v2.x, ensure nobody is online.
|
- With your servers back on and correctly configured to run HuskSync v3.x, ensure nobody is online.
|
||||||
- Use the console on one of your Spigot servers to enter: `husksync migrate legacy`
|
- Use the console on one of your Spigot servers to enter: `husksync migrate legacy`
|
||||||
- Carefully read the migration configuration instructions. In most cases, you won't have to change the settings, but if you do need to adjust them, use `husksync migrate legacy set <setting> <value>`.
|
- Carefully read the migration configuration instructions. In most cases, you won't have to change the settings, but if you do need to adjust them, use `husksync migrate legacy set <setting> <value>`.
|
||||||
- Migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`. If you're migrating from multiple clusters, ensure you run the migrator on the correct servers corresponding to the migrator.
|
- Migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`. If you're migrating from multiple clusters, ensure you run the migrator on the correct servers corresponding to the migrator.
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
This guide will walk you through how to migrate from MySQLPlayerDataBridge (MPDB) to HuskSync v2.x.
|
This guide will walk you through how to migrate from MySQLPlayerDataBridge (MPDB) to HuskSync v3.x.
|
||||||
|
|
||||||
|
> **Warning:** Please note that due to MPDB changes, HuskSync only supports migrating from MySQLPlayerDataBridge `<= v4.9.2`. Support for newer versions will be added in the future.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- Spigot servers with MySQLPlayerDataBridge *still installed*
|
- Spigot servers with MySQLPlayerDataBridge *still installed*
|
||||||
|
|
||||||
## Migration Instructions
|
## Migration Instructions
|
||||||
### 1. Install HuskSync v2.x on all Spigot servers
|
### 1. Install HuskSync v3.x on all Spigot servers
|
||||||
- Download, then install HuskSync on all your servers. Don't uninstall MySQLPlayerDataBridge yet.
|
- Download, then install HuskSync on all your servers. Don't uninstall MySQLPlayerDataBridge yet.
|
||||||
- Follow the setup instructions [here](Setup).
|
- Follow the setup instructions [here](setup).
|
||||||
- Start your servers again when done.
|
- Start your servers again when done.
|
||||||
|
|
||||||
### 2. Configure the migrator
|
### 2. Configure the migrator
|
||||||
- With your servers back on and correctly configured to run HuskSync v2.x, ensure nobody is online.
|
- With your servers back on and correctly configured to run HuskSync v3.x, ensure nobody is online.
|
||||||
- Use the console on one of your Spigot servers to enter: `husksync migrate mpdb`. If the MPDB migrator is not available, ensure MySQLPlayerDataBridge is still installed.
|
- Use the console on one of your Spigot servers to enter: `husksync migrate mpdb`. If the MPDB migrator is not available, ensure MySQLPlayerDataBridge is still installed.
|
||||||
- Adjust the migration setting as needed using the following command: `husksync migrate mpdb set <setting> <value>`.
|
- Adjust the migration setting as needed using the following command: `husksync migrate mpdb set <setting> <value>`.
|
||||||
- Note that migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`.
|
- Note that migration will be carried out *from* the database you specify with the settings in console *to* the database configured in `config.yml`.
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
This will walk you through installing HuskSync on your network of Spigot servers.
|
This will walk you through installing HuskSync on your network of Spigot servers.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
> **Note:** If the plugin fails to load, please check that you are not running an [incompatible version combination](Unsupported-Versions)
|
||||||
|
|
||||||
* A MySQL Database (v8.0+)
|
* A MySQL Database (v8.0+)
|
||||||
* A Redis Database (v5.0+) — see [[FAQs]] for more details.
|
* A Redis Database (v5.0+) — see [[FAQs]] for more details.
|
||||||
* Any number of Spigot servers, connected by a BungeeCord or Velocity-based proxy (Minecraft v1.16.5+, running Java 16+)
|
* Any number of Spigot servers, connected by a BungeeCord or Velocity-based proxy (Minecraft v1.16.5+, running Java 16+)
|
||||||
@@ -17,8 +19,11 @@ This will walk you through installing HuskSync on your network of Spigot servers
|
|||||||
- Navigate to the HuskSync config file on each server (`~/plugins/HuskSync/config.yml`)
|
- Navigate to the HuskSync config file on each server (`~/plugins/HuskSync/config.yml`)
|
||||||
- Under `credentials` in the `database` section, enter the credentials of your MySQL Database. You shouldn't touch the `connection_pool` properties.
|
- Under `credentials` in the `database` section, enter the credentials of your MySQL Database. You shouldn't touch the `connection_pool` properties.
|
||||||
- Under `credentials` in the `redis` section, enter the credentials of your Redis Database. If your Redis server doesn't have a password, leave the password blank as it is.
|
- Under `credentials` in the `redis` section, enter the credentials of your Redis Database. If your Redis server doesn't have a password, leave the password blank as it is.
|
||||||
- Unless you want to have multiple clusters of servers within your network, each with separate user data, do not change the value of `cluster_id`.
|
- Unless you want to have multiple clusters of servers within your network, each with separate user data, you should not change the value of `cluster_id`.
|
||||||
### 4. Start every server again
|
### 4. Set server names in server.yml files
|
||||||
|
- Navigate to the HuskSync server name file on each server (`~/plugins/HuskSync/server.yml`)
|
||||||
|
- Set the `name:` of the server in this file to the ID of this server as defined in the config of your proxy (e.g., if this is the "hub" server you access with `/server hub`, put `'hub'` here)
|
||||||
|
### 5. Start every server again
|
||||||
- Provided your MySQL and Redis credentials were correct, synchronization should begin as soon as you start your servers again.
|
- Provided your MySQL and Redis credentials were correct, synchronization should begin as soon as you start your servers again.
|
||||||
- If you need to import data from HuskSync v1.x or MySQLPlayerDataBridge, please see the guides below:
|
- If you need to import data from HuskSync v1.x or MySQLPlayerDataBridge, please see the guides below:
|
||||||
- [[Legacy Migration]]
|
- [[Legacy Migration]]
|
||||||
|
|||||||
45
docs/Sync-Modes.md
Normal file
45
docs/Sync-Modes.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
HuskSync offers two built-in **synchronization modes** that utilise Redis and MySQL to optimally sync data as users change servers (illustrated below). These sync modes change the way data is synced between servers, and can be changed in the `config.yml` file.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Available Modes
|
||||||
|
* The `DELAY` sync mode is the default sync mode, that use the `network_latency_miliseconds` value to apply a delay before listening to Redis data
|
||||||
|
* The `LOCKSTEP` sync mode uses a data checkout system to ensure that all servers are in sync regardless of network latency or tick rate fluctuations. This mode was introduced in HuskSync v3.1
|
||||||
|
|
||||||
|
You can change which sync mode you are using by editing the `sync_mode` setting under `synchronization` in `config.yml`.
|
||||||
|
|
||||||
|
> **Warning:** Please note that you must use the same sync mode on all servers (at least within a cluster).
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Changing the sync mode (config.yml)</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
synchronization:
|
||||||
|
# The mode of data synchronization to use (DELAY or LOCKSTEP). DELAY should be fine for most networks. Docs: https://william278.net/docs/husksync/sync-modes
|
||||||
|
mode: DELAY
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Delay
|
||||||
|
The `DELAY` sync mode works as described below:
|
||||||
|
* When a user disconnects from a server, a `SERVER_SWITCH` key is immediately set on Redis, followed by a `DATA_UPDATE` key which contains the user's packed and serialized Data Snapshot.
|
||||||
|
* When the user connects to a server, they are marked as locked (unable to break blocks, use containers, etc.)
|
||||||
|
* The server asynchronously waits for the `network_latency_miliseconds` value (default: 500ms) to allow the source server time to serialize & set their key.
|
||||||
|
* After waiting, the server checks for the `SERVER_SWITCH` key.
|
||||||
|
* If present, it will continuously attempt to read for a `DATA_UPDATE` key; when read, their data will be set from the snapshot deserialized from Redis.
|
||||||
|
* If not present, their data will be pulled from the database (as though they joined the network)
|
||||||
|
|
||||||
|
`DELAY` has been the default sync mode since HuskSync v2.0. In HuskSync v3.1, `LOCKSTEP` was introduced. Since the delay mode has been tested and deployed for the longest, it is still the default, though note this may change in the future.
|
||||||
|
|
||||||
|
However, if your network has a fluctuating tick rate or significant latency (especially if you have servers on different hardware/locations), you may wish to use `LOCKSTEP` instead for a more reliable sync system.
|
||||||
|
|
||||||
|
## Lockstep
|
||||||
|
The `LOCKSTEP` sync mode works as described below:
|
||||||
|
* When a user connects to a server, the server will continuously asynchronously check if a `DATA_CHECKOUT` key is present.
|
||||||
|
* If, or when, the key is not present, the plugin will set a new `DATA_CHECKOUT` key.
|
||||||
|
* After this, the plugin will check Redis for the presence of a `DATA_UPDATE` key.
|
||||||
|
* If a `DATA_UPDATE` key is present, the user's data will be set from the snapshot deserialized from Redis contained within that key.
|
||||||
|
* Otherwise, their data will be pulled from the database.
|
||||||
|
* When a user disconnects from a server, their data is serialized and set to Redis with a `DATA_UPDATE` key. After this key has been set, the user's current `DATA_CHECKOUT` key will be removed from Redis.
|
||||||
|
|
||||||
|
Additionally, note that `DATA_CHECKOUT` keys are set with the server ID of the server which "checked out" the data (taken from the `server.yml` config file). On both shutdown and startup, the plugin will clear all `DATA_CHECKOUT` keys for the current server ID (to prevent stale keys in the event of a server crash for instance)
|
||||||
@@ -3,10 +3,10 @@ HuskSync supports a number of community-sourced translations of the plugin local
|
|||||||
You can change which preset language option to use by changing the top-level `language` setting in the plugin config.yml file. You must change this to one of the supported language codes. You can [view a list of the supported languages](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales) by looking at the locales source folder.
|
You can change which preset language option to use by changing the top-level `language` setting in the plugin config.yml file. You must change this to one of the supported language codes. You can [view a list of the supported languages](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales) by looking at the locales source folder.
|
||||||
|
|
||||||
## Contributing Locales
|
## Contributing Locales
|
||||||
You can contribute locales by submitting a pull request with a yaml file containing translations of the [default locales](https://github.com/WiIIiam278/HuskSync/blob/master/common/src/main/resources/locales/en-gb.yml) into your language. Here's a few pointers for doing this:
|
You can contribute locales by submitting a pull request with a yaml file containing translations of the [default locales](https://github.com/WiIIiam278/HuskSync/blob/master/common/src/main/resources/locales/en-gb.yml) into your language. Here are a few pointers for doing this:
|
||||||
* Do not translate the locale keys themselves (e.g. `teleporting_offline_player`)
|
* Do not translate the locale keys themselves (e.g. `synchronization_complete`)
|
||||||
* Your pull request should be for a file in the [locales folder](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales)
|
* Your pull request should be for a file in the [locales folder](https://github.com/WiIIiam278/HuskSync/tree/master/common/src/main/resources/locales)
|
||||||
* Do not translate the [MineDown](https://github.com/Phoenix616/MineDown) markdown syntax itself or commands and their parameters; only the english interface text
|
* Do not translate the [MineDown](https://github.com/Phoenix616/MineDown) Markdown syntax itself or commands and their parameters; only the english interface text
|
||||||
* Each locale should be on one line, and the header should be removed.
|
* Each locale should be on one line, and the header should be removed.
|
||||||
* Use the correct ISO 639-1 [locale code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language and dialect
|
* Use the correct ISO 639-1 [locale code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) for your language and dialect
|
||||||
* If you are able to, you can add your name to the `AboutMenu` translators credit list yourself, otherwise this can be done for you
|
* If you are able to, you can add your name to the `AboutMenu` translators credit list yourself, otherwise this can be done for you
|
||||||
|
|||||||
10
docs/Unsupported-Versions.md
Normal file
10
docs/Unsupported-Versions.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
This plugin does not support the following software-Minecraft version combinations. The plugin will fail to load if you attempt to run it with these versions. Apologies for the inconvenience.
|
||||||
|
|
||||||
|
## Incompatibility table
|
||||||
|
| Minecraft Versions | Server Software | Notes |
|
||||||
|
|--------------------|-------------------------------------------|----------------------------------------|
|
||||||
|
| 1.19.4 | Only: `Purpur, Pufferfish`† | Older Paper builds also not supported. |
|
||||||
|
| 1.19.3 | Only: `Paper, Purpur, Pufferfish`† | Upgrade to 1.19.4 or use Spigot |
|
||||||
|
| below 1.16.5 | _All_ | Upgrade to 1.16.5 |
|
||||||
|
|
||||||
|
†Further downstream forks of this server software are also affected.
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
## Documentation
|
## Documentation
|
||||||
* 🖥️ [[Commands]]
|
* 🖥️ [[Commands]]
|
||||||
* ✅ [[Sync Features]]
|
* ✅ [[Sync Features]]
|
||||||
|
* ⚙️ [[Sync Modes]]
|
||||||
* 🟩 [[Plan Hook]]
|
* 🟩 [[Plan Hook]]
|
||||||
* ☂️ [[Dumping UserData]]
|
* ☂️ [[Dumping UserData]]
|
||||||
* 📋 [[Event Priorities]]
|
* 📋 [[Event Priorities]]
|
||||||
@@ -27,4 +28,5 @@
|
|||||||
* 🚰 [Spigot](https://www.spigotmc.org/resources/husksync.97144/)
|
* 🚰 [Spigot](https://www.spigotmc.org/resources/husksync.97144/)
|
||||||
* 🛒 [Polymart](https://polymart.org/resource/husksync.1634)
|
* 🛒 [Polymart](https://polymart.org/resource/husksync.1634)
|
||||||
* ⚒️ [Craftaro](https://craftaro.com/marketplace/product/husksync.758)
|
* ⚒️ [Craftaro](https://craftaro.com/marketplace/product/husksync.758)
|
||||||
|
* 🛒 [BuiltByBit](https://craftaro.com/marketplace/product/husksync.758)
|
||||||
* 💬 [Discord Support](https://discord.gg/tVYhJfyDWG)
|
* 💬 [Discord Support](https://discord.gg/tVYhJfyDWG)
|
||||||
@@ -3,11 +3,11 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
|||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
javaVersion=16
|
javaVersion=16
|
||||||
|
|
||||||
plugin_version=3.0.1
|
plugin_version=3.1.2
|
||||||
plugin_archive=husksync
|
plugin_archive=husksync
|
||||||
plugin_description=A modern, cross-server player data synchronization system
|
plugin_description=A modern, cross-server player data synchronization system
|
||||||
|
|
||||||
jedis_version=5.0.0
|
jedis_version=5.1.0
|
||||||
mysql_driver_version=8.1.0
|
mysql_driver_version=8.2.0
|
||||||
mariadb_driver_version=3.2.0
|
mariadb_driver_version=3.3.0
|
||||||
snappy_version=1.1.10.3
|
snappy_version=1.1.10.5
|
||||||
|
|||||||
41
paper/build.gradle
Normal file
41
paper/build.gradle
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
dependencies {
|
||||||
|
implementation project(':bukkit')
|
||||||
|
compileOnly project(':common')
|
||||||
|
|
||||||
|
compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT'
|
||||||
|
}
|
||||||
|
|
||||||
|
shadowJar {
|
||||||
|
dependencies {
|
||||||
|
exclude(dependency('com.mojang:brigadier'))
|
||||||
|
}
|
||||||
|
|
||||||
|
relocate 'org.apache.commons.io', 'net.william278.husksync.libraries.commons.io'
|
||||||
|
relocate 'org.apache.commons.text', 'net.william278.husksync.libraries.commons.text'
|
||||||
|
relocate 'org.apache.commons.lang3', 'net.william278.husksync.libraries.commons.lang3'
|
||||||
|
relocate 'com.google.gson', 'net.william278.husksync.libraries.gson'
|
||||||
|
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||||
|
relocate 'com.fatboyindustrial', '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.intellij', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'com.zaxxer', '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.nbtparser'
|
||||||
|
relocate 'net.roxeez', 'net.william278.husksync.libraries'
|
||||||
|
|
||||||
|
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
||||||
|
relocate 'net.byteflux.libby', 'net.william278.husksync.libraries.libby'
|
||||||
|
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.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
||||||
|
relocate 'net.william278.annotaml', 'net.william278.husksync.libraries.annotaml'
|
||||||
|
relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib'
|
||||||
|
relocate 'de.tr7zw.changeme.nbtapi', 'net.william278.husksync.libraries.nbtapi'
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync;
|
||||||
|
|
||||||
|
import net.william278.husksync.listener.BukkitEventListener;
|
||||||
|
import net.william278.husksync.listener.PaperEventListener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class PaperHuskSync extends BukkitHuskSync {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
protected BukkitEventListener createEventListener() {
|
||||||
|
return new PaperEventListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync;
|
||||||
|
|
||||||
|
import io.papermc.paper.plugin.loader.PluginClasspathBuilder;
|
||||||
|
import io.papermc.paper.plugin.loader.PluginLoader;
|
||||||
|
import io.papermc.paper.plugin.loader.library.impl.MavenLibraryResolver;
|
||||||
|
import net.william278.annotaml.Annotaml;
|
||||||
|
import net.william278.annotaml.YamlFile;
|
||||||
|
import net.william278.annotaml.YamlKey;
|
||||||
|
import org.eclipse.aether.artifact.DefaultArtifact;
|
||||||
|
import org.eclipse.aether.graph.Dependency;
|
||||||
|
import org.eclipse.aether.repository.RemoteRepository;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@SuppressWarnings({"UnstableApiUsage", "unused"})
|
||||||
|
public class PaperHuskSyncLoader implements PluginLoader {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void classloader(@NotNull PluginClasspathBuilder classpathBuilder) {
|
||||||
|
MavenLibraryResolver resolver = new MavenLibraryResolver();
|
||||||
|
|
||||||
|
resolveLibraries(classpathBuilder).stream()
|
||||||
|
.map(DefaultArtifact::new)
|
||||||
|
.forEach(artifact -> resolver.addDependency(new Dependency(artifact, null)));
|
||||||
|
resolver.addRepository(new RemoteRepository.Builder(
|
||||||
|
"maven", "default", "https://repo.maven.apache.org/maven2/"
|
||||||
|
).build());
|
||||||
|
|
||||||
|
classpathBuilder.addLibrary(resolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static List<String> resolveLibraries(@NotNull PluginClasspathBuilder classpathBuilder) {
|
||||||
|
try (InputStream input = getLibraryListFile()) {
|
||||||
|
return Annotaml.create(PaperLibraries.class, Objects.requireNonNull(input)).get().libraries;
|
||||||
|
} catch (Exception e) {
|
||||||
|
classpathBuilder.getContext().getLogger().error("Failed to resolve libraries", e);
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static InputStream getLibraryListFile() {
|
||||||
|
return PaperHuskSyncLoader.class.getClassLoader().getResourceAsStream("paper-libraries.yml");
|
||||||
|
}
|
||||||
|
|
||||||
|
@YamlFile(header = "Dependencies for HuskSync on Paper")
|
||||||
|
public static class PaperLibraries {
|
||||||
|
|
||||||
|
@YamlKey("libraries")
|
||||||
|
private List<String> libraries;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private PaperLibraries() {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.listener;
|
||||||
|
|
||||||
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
|
import net.william278.husksync.data.BukkitData;
|
||||||
|
import net.william278.husksync.user.BukkitUser;
|
||||||
|
import net.william278.husksync.user.OnlineUser;
|
||||||
|
import org.bukkit.event.entity.PlayerDeathEvent;
|
||||||
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class PaperEventListener extends BukkitEventListener {
|
||||||
|
|
||||||
|
public PaperEventListener(@NotNull BukkitHuskSync plugin) {
|
||||||
|
super(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlePlayerDeath(@NotNull PlayerDeathEvent event) {
|
||||||
|
// If the player is locked or the plugin disabling, clear their drops
|
||||||
|
final OnlineUser user = BukkitUser.adapt(event.getEntity(), plugin);
|
||||||
|
if (cancelPlayerEvent(user.getUuid())) {
|
||||||
|
event.getDrops().clear();
|
||||||
|
event.getItemsToKeep().clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle saving player data snapshots on death
|
||||||
|
if (!plugin.getSettings().doSaveOnDeath()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Paper - support saving the player's items to keep if enabled
|
||||||
|
final int maxInventorySize = BukkitData.Items.Inventory.INVENTORY_SLOT_COUNT;
|
||||||
|
final List<ItemStack> itemsToSave = switch (plugin.getSettings().getDeathItemsMode()) {
|
||||||
|
case DROPS -> event.getDrops();
|
||||||
|
case ITEMS_TO_KEEP -> event.getItemsToKeep();
|
||||||
|
};
|
||||||
|
if (itemsToSave.size() > maxInventorySize) {
|
||||||
|
itemsToSave.subList(maxInventorySize, itemsToSave.size()).clear();
|
||||||
|
}
|
||||||
|
super.saveOnPlayerDeath(user, BukkitData.Items.ItemArray.adapt(itemsToSave));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
6
paper/src/main/resources/paper-libraries.yml
Normal file
6
paper/src/main/resources/paper-libraries.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Dependencies for HuskSync on Paper
|
||||||
|
libraries:
|
||||||
|
- 'redis.clients:jedis:${jedis_version}'
|
||||||
|
- 'com.mysql:mysql-connector-j:${mysql_driver_version}'
|
||||||
|
- 'org.mariadb.jdbc:mariadb-java-client:${mariadb_driver_version}'
|
||||||
|
- 'org.xerial.snappy:snappy-java:${snappy_version}'
|
||||||
18
paper/src/main/resources/paper-plugin.yml
Normal file
18
paper/src/main/resources/paper-plugin.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: 'HuskSync'
|
||||||
|
description: '${description}'
|
||||||
|
author: 'William278'
|
||||||
|
website: 'https://william278.net/'
|
||||||
|
main: 'net.william278.husksync.PaperHuskSync'
|
||||||
|
loader: 'net.william278.husksync.PaperHuskSyncLoader'
|
||||||
|
version: '${version}'
|
||||||
|
api-version: '1.19'
|
||||||
|
dependencies:
|
||||||
|
server:
|
||||||
|
MysqlPlayerDataBridge:
|
||||||
|
required: false
|
||||||
|
load: BEFORE
|
||||||
|
join-classpath: true
|
||||||
|
Plan:
|
||||||
|
required: false
|
||||||
|
load: BEFORE
|
||||||
|
join-classpath: true
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':bukkit', configuration: 'shadow')
|
implementation project(path: ':bukkit', configuration: 'shadow')
|
||||||
|
runtimeOnly project(path: ':paper')
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,9 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = 'HuskSync'
|
rootProject.name = 'HuskSync'
|
||||||
|
include(
|
||||||
include 'common'
|
'common',
|
||||||
include 'bukkit'
|
'bukkit',
|
||||||
include 'plugin'
|
'paper',
|
||||||
|
'plugin'
|
||||||
|
)
|
||||||
@@ -4,4 +4,4 @@ colorama==0.4.6
|
|||||||
idna==3.4
|
idna==3.4
|
||||||
requests==2.31.0
|
requests==2.31.0
|
||||||
tqdm==4.66.1
|
tqdm==4.66.1
|
||||||
urllib3==2.0.4
|
urllib3==2.0.7
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from tqdm import tqdm
|
|||||||
class Parameters:
|
class Parameters:
|
||||||
root_dir = './servers/'
|
root_dir = './servers/'
|
||||||
proxy_version = "1.20"
|
proxy_version = "1.20"
|
||||||
minecraft_version = '1.20.1'
|
minecraft_version = '1.20.4'
|
||||||
eula_agreement = 'true'
|
eula_agreement = 'true'
|
||||||
|
|
||||||
backend_names = ['alpha', 'beta']
|
backend_names = ['alpha', 'beta']
|
||||||
|
|||||||
Reference in New Issue
Block a user