mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-26 18:19:10 +00:00
Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52ec138273 | ||
|
|
0f7a866652 | ||
|
|
eeb52ac41e | ||
|
|
4c7ec9ec21 | ||
|
|
2f9064c4c6 | ||
|
|
5c234cdb1d | ||
|
|
7d8a74381b | ||
|
|
04a7793585 | ||
|
|
ea068529f6 | ||
|
|
fead3df0d8 | ||
|
|
0c5a42a344 | ||
|
|
75a2378ea8 | ||
|
|
662fc96ad5 | ||
|
|
07da1c04ce | ||
|
|
845abf370a | ||
|
|
83b5209a75 | ||
|
|
8e9850dd19 | ||
|
|
1d24209b68 | ||
|
|
da70a54d78 | ||
|
|
32ac57e2a4 | ||
|
|
c949c976d6 | ||
|
|
ab736829f2 | ||
|
|
4433926ce7 | ||
|
|
f819fd4d5e | ||
|
|
e7659255fe | ||
|
|
0dee2e8319 | ||
|
|
7b35c47315 | ||
|
|
5056a794d8 | ||
|
|
5e6068431a | ||
|
|
8d69508689 | ||
|
|
efb6d8a7de | ||
|
|
79d9778378 | ||
|
|
6a6695e447 | ||
|
|
8862e6cd70 | ||
|
|
0b29de9efc | ||
|
|
962cdfce0b | ||
|
|
0c527202e5 | ||
|
|
d4e33aa9d2 | ||
|
|
2fcd58fc18 | ||
|
|
3d10b2324f | ||
|
|
31419f3b97 | ||
|
|
8105ac27fc | ||
|
|
44f251a948 | ||
|
|
463e707d27 | ||
|
|
2d85910744 | ||
|
|
268b279fdf | ||
|
|
a8ca3314d8 | ||
|
|
2bdd3dae37 | ||
|
|
e29564c4ad | ||
|
|
6b8bb23af9 | ||
|
|
91bbe05851 | ||
|
|
8ed6869aad | ||
|
|
7efdf0d329 | ||
|
|
49c32e3f98 | ||
|
|
f0574527b9 | ||
|
|
ad510a8fca | ||
|
|
303b287705 | ||
|
|
549508b9c1 | ||
|
|
6c8a577701 | ||
|
|
862177bec7 | ||
|
|
dbed4d83a2 | ||
|
|
aa2090d97a | ||
|
|
b168ede7c5 | ||
|
|
0e706d36c4 | ||
|
|
69d68de5c0 | ||
|
|
3d5395e5ae | ||
|
|
332c71f041 | ||
|
|
b9fbcd72dd | ||
|
|
68897e6265 | ||
|
|
04606a7c9a | ||
|
|
6286bbe2ad |
20
.github/workflows/ci.yml
vendored
20
.github/workflows/ci.yml
vendored
@@ -42,3 +42,23 @@ jobs:
|
|||||||
- name: Get Version
|
- name: Get Version
|
||||||
run: |
|
run: |
|
||||||
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
echo "version_name=${{steps.fetch-version.outputs.VERSION_NAME}}" >> $GITHUB_ENV
|
||||||
|
- name: 'Publish to William278.net 🚀'
|
||||||
|
uses: WiIIiam278/bones-publish-action@v1
|
||||||
|
with:
|
||||||
|
api-key: ${{ secrets.BONES_API_KEY }}
|
||||||
|
project: 'husksync'
|
||||||
|
channel: 'alpha'
|
||||||
|
version: ${{ env.version_name }}
|
||||||
|
changelog: ${{ github.event.head_commit.message }}
|
||||||
|
distro-names: |
|
||||||
|
paper
|
||||||
|
fabric-1.20.1
|
||||||
|
distro-groups: |
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
|
distro-descriptions: |
|
||||||
|
Paper
|
||||||
|
Fabric 1.20.1
|
||||||
|
files: |
|
||||||
|
target/HuskSync-Paper-${{ env.version_name }}.jar
|
||||||
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
|
||||||
20
.github/workflows/release.yml
vendored
20
.github/workflows/release.yml
vendored
@@ -31,3 +31,23 @@ jobs:
|
|||||||
if: success() || failure() # Continue on failure
|
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: 'Publish to William278.net 🚀'
|
||||||
|
uses: WiIIiam278/bones-publish-action@v1
|
||||||
|
with:
|
||||||
|
api-key: ${{ secrets.BONES_API_KEY }}
|
||||||
|
project: 'husksync'
|
||||||
|
channel: 'release'
|
||||||
|
version: ${{ github.event.release.tag_name }}
|
||||||
|
changelog: ${{ github.event.release.body }}
|
||||||
|
distro-names: |
|
||||||
|
paper
|
||||||
|
fabric-1.20.1
|
||||||
|
distro-groups: |
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
|
distro-descriptions: |
|
||||||
|
Paper
|
||||||
|
Fabric 1.20.1
|
||||||
|
files: |
|
||||||
|
target/HuskSync-Paper-${{ github.event.release.tag_name }}.jar
|
||||||
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<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://repo.william278.net/#/releases/net/william278/husksync/">
|
<a href="https://repo.william278.net/#/releases/net/william278/husksync/">
|
||||||
<img src="https://repo.william278.net/api/badge/latest/releases/net/william278/husksync?color=00fb9a&name=Maven&prefix=v" />
|
<img src="https://repo.william278.net/api/badge/latest/releases/net/william278/husksync/husksync-common?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" />
|
||||||
|
|||||||
21
build.gradle
21
build.gradle
@@ -3,6 +3,7 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
|||||||
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 'fabric-loom' version '1.7-SNAPSHOT' apply false
|
||||||
id 'org.ajoberstar.grgit' version '5.2.2'
|
id 'org.ajoberstar.grgit' version '5.2.2'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'java'
|
id 'java'
|
||||||
@@ -69,6 +70,7 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url 'https://repo.william278.net/releases/' }
|
||||||
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
|
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
|
||||||
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
maven { url 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/' }
|
||||||
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
maven { url "https://repo.dmulloy2.net/repository/public/" }
|
||||||
@@ -78,13 +80,12 @@ allprojects {
|
|||||||
maven { url 'https://jitpack.io' }
|
maven { url 'https://jitpack.io' }
|
||||||
maven { url 'https://mvn-repo.arim.space/lesser-gpl3/' }
|
maven { url 'https://mvn-repo.arim.space/lesser-gpl3/' }
|
||||||
maven { url 'https://libraries.minecraft.net/' }
|
maven { url 'https://libraries.minecraft.net/' }
|
||||||
maven { url 'https://repo.william278.net/releases/' }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.10.3'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.10.3'
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.2'
|
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.10.3'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@@ -98,14 +99,20 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
processResources {
|
processResources {
|
||||||
|
def tokenMap = rootProject.ext.properties
|
||||||
|
tokenMap.merge("grgit",'',(s, s2) -> s)
|
||||||
filesMatching(['**/*.json', '**/*.yml']) {
|
filesMatching(['**/*.json', '**/*.yml']) {
|
||||||
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
filter ReplaceTokens as Class, beginToken: '${', endToken: '}',
|
||||||
tokens: rootProject.ext.properties
|
tokens: tokenMap
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
if (['fabric'].contains(project.name)) {
|
||||||
|
apply plugin: 'fabric-loom'
|
||||||
|
}
|
||||||
|
|
||||||
version rootProject.version
|
version rootProject.version
|
||||||
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
|
archivesBaseName = "${rootProject.name}-${project.name.capitalize()}"
|
||||||
|
|
||||||
@@ -169,8 +176,8 @@ subprojects {
|
|||||||
mavenJavaFabric(MavenPublication) {
|
mavenJavaFabric(MavenPublication) {
|
||||||
groupId = 'net.william278.husksync'
|
groupId = 'net.william278.husksync'
|
||||||
artifactId = 'husksync-fabric'
|
artifactId = 'husksync-fabric'
|
||||||
version = "$rootProject.version"
|
version = "$rootProject.version+${fabric_minecraft_version}"
|
||||||
artifact shadowJar
|
artifact remapJar
|
||||||
artifact sourcesJar
|
artifact sourcesJar
|
||||||
artifact javadocJar
|
artifact javadocJar
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':common')
|
implementation project(path: ':common')
|
||||||
|
|
||||||
implementation 'org.bstats:bstats-bukkit:3.0.2'
|
implementation 'net.william278.uniform:uniform-bukkit:1.2.1'
|
||||||
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
implementation 'net.william278:mpdbdataconverter:1.0.1'
|
||||||
implementation 'net.william278:hsldataconverter:1.0'
|
implementation 'net.william278:hsldataconverter:1.0'
|
||||||
implementation 'net.william278:mapdataapi:1.0.3'
|
implementation 'net.william278:mapdataapi:1.0.3'
|
||||||
implementation 'net.william278:andjam:1.0.2'
|
implementation 'org.bstats:bstats-bukkit:3.0.2'
|
||||||
implementation 'me.lucko:commodore:2.2'
|
|
||||||
implementation 'net.kyori:adventure-platform-bukkit:4.3.3'
|
implementation 'net.kyori:adventure-platform-bukkit:4.3.3'
|
||||||
implementation 'dev.triumphteam:triumph-gui:3.1.10'
|
implementation 'dev.triumphteam:triumph-gui:3.1.10'
|
||||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
||||||
implementation 'de.tr7zw:item-nbt-api:2.13.0'
|
implementation 'de.tr7zw:item-nbt-api:2.13.2'
|
||||||
|
|
||||||
compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT'
|
compileOnly 'org.spigotmc:spigot-api:1.17.1-R0.1-SNAPSHOT'
|
||||||
compileOnly 'com.github.retrooper.packetevents:spigot:2.3.0'
|
compileOnly 'com.github.retrooper.packetevents:spigot:2.3.0'
|
||||||
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
|
compileOnly 'com.comphenix.protocol:ProtocolLib:5.1.0'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
compileOnly 'commons-io:commons-io:2.16.1'
|
compileOnly 'commons-io:commons-io:2.16.1'
|
||||||
compileOnly 'org.json:json:20240303'
|
compileOnly 'org.json:json:20240303'
|
||||||
compileOnly 'net.william278:minedown:1.8.2'
|
compileOnly 'net.william278:minedown:1.8.2'
|
||||||
@@ -25,7 +24,7 @@ dependencies {
|
|||||||
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
compileOnly 'net.william278:AdvancementAPI:97a9583413'
|
||||||
compileOnly "redis.clients:jedis:$jedis_version"
|
compileOnly "redis.clients:jedis:$jedis_version"
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
@@ -42,16 +41,15 @@ shadowJar {
|
|||||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||||
relocate 'de.exlll', 'net.william278.husksync.libraries'
|
relocate 'de.exlll', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'net.william278.uniform', 'net.william278.husksync.libraries.uniform'
|
||||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||||
relocate 'net.william278.andjam', 'net.william278.husksync.libraries.andjam'
|
|
||||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||||
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
||||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||||
relocate 'net.querz', 'net.william278.husksync.libraries.nbtparser'
|
relocate 'net.querz', 'net.william278.husksync.libraries.nbtparser'
|
||||||
relocate 'net.roxeez', 'net.william278.husksync.libraries'
|
relocate 'net.roxeez', 'net.william278.husksync.libraries'
|
||||||
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
|
||||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||||
relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui'
|
relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui'
|
||||||
relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib'
|
relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib'
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import net.william278.husksync.adapter.DataAdapter;
|
|||||||
import net.william278.husksync.adapter.GsonAdapter;
|
import net.william278.husksync.adapter.GsonAdapter;
|
||||||
import net.william278.husksync.adapter.SnappyGsonAdapter;
|
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.PluginCommand;
|
||||||
import net.william278.husksync.config.Locales;
|
import net.william278.husksync.config.Locales;
|
||||||
import net.william278.husksync.config.Server;
|
import net.william278.husksync.config.Server;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
@@ -57,6 +57,8 @@ import net.william278.husksync.util.BukkitLegacyConverter;
|
|||||||
import net.william278.husksync.util.BukkitMapPersister;
|
import net.william278.husksync.util.BukkitMapPersister;
|
||||||
import net.william278.husksync.util.BukkitTask;
|
import net.william278.husksync.util.BukkitTask;
|
||||||
import net.william278.husksync.util.LegacyConverter;
|
import net.william278.husksync.util.LegacyConverter;
|
||||||
|
import net.william278.uniform.Uniform;
|
||||||
|
import net.william278.uniform.bukkit.BukkitUniform;
|
||||||
import org.bstats.bukkit.Metrics;
|
import org.bstats.bukkit.Metrics;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.map.MapView;
|
import org.bukkit.map.MapView;
|
||||||
@@ -64,7 +66,6 @@ import org.bukkit.plugin.Plugin;
|
|||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import space.arim.morepaperlib.MorePaperLib;
|
import space.arim.morepaperlib.MorePaperLib;
|
||||||
import space.arim.morepaperlib.commands.CommandRegistration;
|
|
||||||
import space.arim.morepaperlib.scheduling.AsynchronousScheduler;
|
import space.arim.morepaperlib.scheduling.AsynchronousScheduler;
|
||||||
import space.arim.morepaperlib.scheduling.AttachedScheduler;
|
import space.arim.morepaperlib.scheduling.AttachedScheduler;
|
||||||
import space.arim.morepaperlib.scheduling.GracefulScheduling;
|
import space.arim.morepaperlib.scheduling.GracefulScheduling;
|
||||||
@@ -135,6 +136,10 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
this.audiences = BukkitAudiences.create(this);
|
this.audiences = BukkitAudiences.create(this);
|
||||||
|
|
||||||
|
// Register commands
|
||||||
|
initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this)));
|
||||||
|
|
||||||
// Prepare data adapter
|
// Prepare data adapter
|
||||||
initialize("data adapter", (plugin) -> {
|
initialize("data adapter", (plugin) -> {
|
||||||
if (settings.getSynchronization().isCompressData()) {
|
if (settings.getSynchronization().isCompressData()) {
|
||||||
@@ -196,9 +201,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
// Register events
|
// Register events
|
||||||
initialize("events", (plugin) -> eventListener.onEnable());
|
initialize("events", (plugin) -> eventListener.onEnable());
|
||||||
|
|
||||||
// Register commands
|
|
||||||
initialize("commands", (plugin) -> BukkitCommand.Type.registerCommands(this));
|
|
||||||
|
|
||||||
// Register plugin hooks
|
// Register plugin hooks
|
||||||
initialize("hooks", (plugin) -> {
|
initialize("hooks", (plugin) -> {
|
||||||
if (isDependencyLoaded("Plan") && getSettings().isEnablePlanHook()) {
|
if (isDependencyLoaded("Plan") && getSettings().isEnablePlanHook()) {
|
||||||
@@ -264,6 +266,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
this.dataSyncer = dataSyncer;
|
this.dataSyncer = dataSyncer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Uniform getUniform() {
|
||||||
|
return BukkitUniform.getInstance(this);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public Map<Identifier, Data> getPlayerCustomDataStore(@NotNull OnlineUser user) {
|
public Map<Identifier, Data> getPlayerCustomDataStore(@NotNull OnlineUser user) {
|
||||||
@@ -282,7 +290,7 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
@Override
|
@Override
|
||||||
public boolean isDependencyLoaded(@NotNull String name) {
|
public boolean isDependencyLoaded(@NotNull String name) {
|
||||||
final Plugin plugin = getServer().getPluginManager().getPlugin(name);
|
final Plugin plugin = getServer().getPluginManager().getPlugin(name);
|
||||||
return plugin != null && plugin.isEnabled();
|
return plugin != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register bStats metrics
|
// Register bStats metrics
|
||||||
@@ -325,6 +333,12 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
return PLATFORM_TYPE_ID;
|
return PLATFORM_TYPE_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public String getServerVersion() {
|
||||||
|
return String.format("%s/%s", getServer().getName(), getServer().getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<LegacyConverter> getLegacyConverter() {
|
public Optional<LegacyConverter> getLegacyConverter() {
|
||||||
return Optional.of(legacyConverter);
|
return Optional.of(legacyConverter);
|
||||||
@@ -352,11 +366,6 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
return getScheduler().entitySpecificScheduler(((BukkitUser) user).getPlayer());
|
return getScheduler().entitySpecificScheduler(((BukkitUser) user).getPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public CommandRegistration getCommandRegistrar() {
|
|
||||||
return paperLib.commandRegistration();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Path getConfigDirectory() {
|
public Path getConfigDirectory() {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class BukkitHuskSyncAPI extends HuskSyncAPI {
|
|||||||
public static BukkitHuskSyncAPI getInstance() {
|
public static BukkitHuskSyncAPI getInstance() {
|
||||||
if (!JavaPlugin.getProvidingPlugin(BukkitHuskSyncAPI.class).getName().equals("HuskSync")) {
|
if (!JavaPlugin.getProvidingPlugin(BukkitHuskSyncAPI.class).getName().equals("HuskSync")) {
|
||||||
throw new NotRegisteredException("This is likely because you have shaded HuskSync into your plugin JAR " +
|
throw new NotRegisteredException("This is likely because you have shaded HuskSync into your plugin JAR " +
|
||||||
"and need to fix your maven/gradle/build script so that it *compiles against* HuskSync instead.");
|
"and need to fix your maven/gradle/build script so that it *compiles against* HuskSync instead.");
|
||||||
}
|
}
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
throw new NotRegisteredException();
|
throw new NotRegisteredException();
|
||||||
|
|||||||
@@ -1,60 +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.command;
|
|
||||||
|
|
||||||
import me.lucko.commodore.CommodoreProvider;
|
|
||||||
import me.lucko.commodore.file.CommodoreFileReader;
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
|
|
||||||
public class BrigadierUtil {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses commodore to register command completions.
|
|
||||||
*
|
|
||||||
* @param plugin instance of the registering Bukkit plugin
|
|
||||||
* @param bukkitCommand the Bukkit PluginCommand to register completions for
|
|
||||||
* @param command the {@link Command} to register completions for
|
|
||||||
*/
|
|
||||||
protected static void registerCommodore(@NotNull BukkitHuskSync plugin,
|
|
||||||
@NotNull org.bukkit.command.Command bukkitCommand,
|
|
||||||
@NotNull Command command) {
|
|
||||||
final InputStream commodoreFile = plugin.getResource(
|
|
||||||
"commodore/" + bukkitCommand.getName() + ".commodore"
|
|
||||||
);
|
|
||||||
if (commodoreFile == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
CommodoreProvider.getCommodore(plugin).register(bukkitCommand,
|
|
||||||
CommodoreFileReader.INSTANCE.parse(commodoreFile),
|
|
||||||
player -> player.hasPermission(command.getPermission()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
plugin.log(Level.SEVERE, String.format(
|
|
||||||
"Failed to read command commodore completions for %s", bukkitCommand.getName()), e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,164 +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.command;
|
|
||||||
|
|
||||||
|
|
||||||
import me.lucko.commodore.CommodoreProvider;
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
|
||||||
import net.william278.husksync.user.BukkitUser;
|
|
||||||
import net.william278.husksync.user.CommandUser;
|
|
||||||
import org.bukkit.command.CommandSender;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.permissions.Permission;
|
|
||||||
import org.bukkit.permissions.PermissionDefault;
|
|
||||||
import org.bukkit.plugin.PluginManager;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class BukkitCommand extends org.bukkit.command.Command {
|
|
||||||
|
|
||||||
private final BukkitHuskSync plugin;
|
|
||||||
private final Command command;
|
|
||||||
|
|
||||||
public BukkitCommand(@NotNull Command command, @NotNull BukkitHuskSync plugin) {
|
|
||||||
super(command.getName(), command.getDescription(), command.getUsage(), command.getAliases());
|
|
||||||
this.command = command;
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean execute(@NotNull CommandSender sender, @NotNull String commandLabel, @NotNull String[] args) {
|
|
||||||
this.command.onExecuted(sender instanceof Player p ? BukkitUser.adapt(p, plugin) : plugin.getConsole(), args);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
@Override
|
|
||||||
public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias,
|
|
||||||
@NotNull String[] args) throws IllegalArgumentException {
|
|
||||||
if (!(this.command instanceof TabProvider provider)) {
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
final CommandUser user = sender instanceof Player p ? BukkitUser.adapt(p, plugin) : plugin.getConsole();
|
|
||||||
if (getPermission() == null || user.hasPermission(getPermission())) {
|
|
||||||
return provider.getSuggestions(user, args);
|
|
||||||
}
|
|
||||||
return List.of();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void register() {
|
|
||||||
// Register with bukkit
|
|
||||||
plugin.getCommandRegistrar().getServerCommandMap().register("husksync", this);
|
|
||||||
|
|
||||||
// Register permissions
|
|
||||||
BukkitCommand.addPermission(
|
|
||||||
plugin,
|
|
||||||
command.getPermission(),
|
|
||||||
command.getUsage(),
|
|
||||||
BukkitCommand.getPermissionDefault(command.isOperatorCommand())
|
|
||||||
);
|
|
||||||
final List<Permission> childNodes = command.getAdditionalPermissions()
|
|
||||||
.entrySet().stream()
|
|
||||||
.map((entry) -> BukkitCommand.addPermission(
|
|
||||||
plugin,
|
|
||||||
entry.getKey(),
|
|
||||||
"",
|
|
||||||
BukkitCommand.getPermissionDefault(entry.getValue()))
|
|
||||||
)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.toList();
|
|
||||||
if (!childNodes.isEmpty()) {
|
|
||||||
BukkitCommand.addPermission(
|
|
||||||
plugin,
|
|
||||||
command.getPermission("*"),
|
|
||||||
command.getUsage(),
|
|
||||||
PermissionDefault.FALSE,
|
|
||||||
childNodes.toArray(new Permission[0])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register commodore TAB completion
|
|
||||||
if (CommodoreProvider.isSupported() && plugin.getSettings().isBrigadierTabCompletion()) {
|
|
||||||
BrigadierUtil.registerCommodore(plugin, this, command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
protected static Permission addPermission(@NotNull BukkitHuskSync plugin, @NotNull String node,
|
|
||||||
@NotNull String description, @NotNull PermissionDefault permissionDefault,
|
|
||||||
@NotNull Permission... children) {
|
|
||||||
final Map<String, Boolean> childNodes = Arrays.stream(children)
|
|
||||||
.map(Permission::getName)
|
|
||||||
.collect(HashMap::new, (map, child) -> map.put(child, true), HashMap::putAll);
|
|
||||||
|
|
||||||
final PluginManager manager = plugin.getServer().getPluginManager();
|
|
||||||
if (manager.getPermission(node) != null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Permission permission;
|
|
||||||
if (description.isEmpty()) {
|
|
||||||
permission = new Permission(node, permissionDefault, childNodes);
|
|
||||||
} else {
|
|
||||||
permission = new Permission(node, description, permissionDefault, childNodes);
|
|
||||||
}
|
|
||||||
manager.addPermission(permission);
|
|
||||||
|
|
||||||
return permission;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected static PermissionDefault getPermissionDefault(boolean isOperatorCommand) {
|
|
||||||
return isOperatorCommand ? PermissionDefault.OP : PermissionDefault.TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commands available on the Bukkit HuskSync implementation
|
|
||||||
*/
|
|
||||||
public enum Type {
|
|
||||||
|
|
||||||
HUSKSYNC_COMMAND(HuskSyncCommand::new),
|
|
||||||
USERDATA_COMMAND(UserDataCommand::new),
|
|
||||||
INVENTORY_COMMAND(InventoryCommand::new),
|
|
||||||
ENDER_CHEST_COMMAND(EnderChestCommand::new);
|
|
||||||
|
|
||||||
public final Function<BukkitHuskSync, Command> commandSupplier;
|
|
||||||
|
|
||||||
Type(@NotNull Function<BukkitHuskSync, Command> supplier) {
|
|
||||||
this.commandSupplier = supplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Command createCommand(@NotNull BukkitHuskSync plugin) {
|
|
||||||
return commandSupplier.apply(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void registerCommands(@NotNull BukkitHuskSync plugin) {
|
|
||||||
Arrays.stream(values())
|
|
||||||
.map((type) -> type.createCommand(plugin))
|
|
||||||
.forEach((command) -> new BukkitCommand(command, plugin).register());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -25,16 +25,15 @@ import com.google.gson.annotations.SerializedName;
|
|||||||
import de.tr7zw.changeme.nbtapi.NBTCompound;
|
import de.tr7zw.changeme.nbtapi.NBTCompound;
|
||||||
import de.tr7zw.changeme.nbtapi.NBTPersistentDataContainer;
|
import de.tr7zw.changeme.nbtapi.NBTPersistentDataContainer;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
import net.kyori.adventure.util.TriState;
|
||||||
import net.william278.desertwell.util.ThrowingConsumer;
|
import net.william278.desertwell.util.ThrowingConsumer;
|
||||||
import net.william278.desertwell.util.Version;
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
import net.william278.husksync.BukkitHuskSync;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.adapter.Adaptable;
|
import net.william278.husksync.adapter.Adaptable;
|
||||||
|
import net.william278.husksync.config.Settings.SynchronizationSettings.AttributeSettings;
|
||||||
import net.william278.husksync.user.BukkitUser;
|
import net.william278.husksync.user.BukkitUser;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.*;
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.Registry;
|
|
||||||
import org.bukkit.Statistic;
|
|
||||||
import org.bukkit.advancement.AdvancementProgress;
|
import org.bukkit.advancement.AdvancementProgress;
|
||||||
import org.bukkit.attribute.AttributeInstance;
|
import org.bukkit.attribute.AttributeInstance;
|
||||||
import org.bukkit.attribute.AttributeModifier;
|
import org.bukkit.attribute.AttributeModifier;
|
||||||
@@ -48,7 +47,9 @@ import org.bukkit.potion.PotionEffectType;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.Range;
|
import org.jetbrains.annotations.Range;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
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;
|
||||||
@@ -161,11 +162,15 @@ public abstract class BukkitData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void clearInventoryCraftingSlots(@NotNull Player player) {
|
private void clearInventoryCraftingSlots(@NotNull Player player) {
|
||||||
final org.bukkit.inventory.Inventory inventory = player.getOpenInventory().getTopInventory();
|
try {
|
||||||
if (inventory.getType() == InventoryType.CRAFTING) {
|
final org.bukkit.inventory.Inventory inventory = player.getOpenInventory().getTopInventory();
|
||||||
for (int slot = 0; slot < 5; slot++) {
|
if (inventory.getType() == InventoryType.CRAFTING) {
|
||||||
inventory.setItem(slot, null);
|
for (int slot = 0; slot < 5; slot++) {
|
||||||
|
inventory.setItem(slot, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Ignore any exceptions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -232,8 +237,9 @@ public abstract class BukkitData implements Data {
|
|||||||
private final Collection<PotionEffect> effects;
|
private final Collection<PotionEffect> effects;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static BukkitData.PotionEffects from(@NotNull Collection<PotionEffect> effects) {
|
public static BukkitData.PotionEffects from(@NotNull Collection<PotionEffect> sei) {
|
||||||
return new BukkitData.PotionEffects(effects);
|
return new BukkitData.PotionEffects(Lists.newArrayList(sei.stream().filter(e -> !e.isAmbient()).toList()));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -257,7 +263,7 @@ public abstract class BukkitData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static BukkitData.PotionEffects empty() {
|
public static BukkitData.PotionEffects empty() {
|
||||||
return new BukkitData.PotionEffects(List.of());
|
return new BukkitData.PotionEffects(Lists.newArrayList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -273,6 +279,7 @@ public abstract class BukkitData implements Data {
|
|||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
@Unmodifiable
|
||||||
public List<Effect> getActiveEffects() {
|
public List<Effect> getActiveEffects() {
|
||||||
return effects.stream()
|
return effects.stream()
|
||||||
.map(potionEffect -> new Effect(
|
.map(potionEffect -> new Effect(
|
||||||
@@ -360,7 +367,7 @@ public abstract class BukkitData implements Data {
|
|||||||
|
|
||||||
// Set player experience and level (prevent advancement awards applying twice), reset game rule
|
// Set player experience and level (prevent advancement awards applying twice), reset game rule
|
||||||
if (!toAward.isEmpty()
|
if (!toAward.isEmpty()
|
||||||
&& (player.getLevel() != expLevel || player.getExp() != expProgress)) {
|
&& (player.getLevel() != expLevel || player.getExp() != expProgress)) {
|
||||||
player.setLevel(expLevel);
|
player.setLevel(expLevel);
|
||||||
player.setExp(expProgress);
|
player.setExp(expProgress);
|
||||||
}
|
}
|
||||||
@@ -561,19 +568,24 @@ public abstract class BukkitData implements Data {
|
|||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public static class Attributes extends BukkitData implements Data.Attributes, Adaptable {
|
public static class Attributes extends BukkitData implements Data.Attributes, Adaptable {
|
||||||
|
|
||||||
|
private static final String EQUIPMENT_SLOT_GROUP = "org.bukkit.inventory.EquipmentSlotGroup";
|
||||||
|
private static final String EQUIPMENT_SLOT_GROUP$ANY = "ANY";
|
||||||
|
private static final String EQUIPMENT_SLOT$getGroup = "getGroup";
|
||||||
|
private static TriState USE_KEYED_MODIFIERS = TriState.NOT_SET;
|
||||||
|
|
||||||
private List<Attribute> attributes;
|
private List<Attribute> attributes;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static BukkitData.Attributes adapt(@NotNull Player player, @NotNull HuskSync plugin) {
|
public static BukkitData.Attributes adapt(@NotNull Player player, @NotNull HuskSync plugin) {
|
||||||
final List<Attribute> attributes = Lists.newArrayList();
|
final List<Attribute> attributes = Lists.newArrayList();
|
||||||
|
final AttributeSettings settings = plugin.getSettings().getSynchronization().getAttributes();
|
||||||
Registry.ATTRIBUTE.forEach(id -> {
|
Registry.ATTRIBUTE.forEach(id -> {
|
||||||
final AttributeInstance instance = player.getAttribute(id);
|
final AttributeInstance instance = player.getAttribute(id);
|
||||||
if (instance == null || instance.getValue() == instance.getDefaultValue() || plugin
|
if (instance == null || Double.compare(instance.getValue(), instance.getDefaultValue()) == 0
|
||||||
.getSettings().getSynchronization().isIgnoredAttribute(id.getKey().toString())) {
|
|| settings.isIgnoredAttribute(id.getKey().toString())) {
|
||||||
// We don't sync unmodified or disabled attributes
|
return; // We don't sync unmodified or disabled attributes
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
attributes.add(adapt(instance, plugin.getMinecraftVersion()));
|
attributes.add(adapt(instance, settings));
|
||||||
});
|
});
|
||||||
return new BukkitData.Attributes(attributes);
|
return new BukkitData.Attributes(attributes);
|
||||||
}
|
}
|
||||||
@@ -592,18 +604,20 @@ public abstract class BukkitData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static Attribute adapt(@NotNull AttributeInstance instance, @NotNull Version version) {
|
private static Attribute adapt(@NotNull AttributeInstance instance, @NotNull AttributeSettings settings) {
|
||||||
return new Attribute(
|
return new Attribute(
|
||||||
instance.getAttribute().getKey().toString(),
|
instance.getAttribute().getKey().toString(),
|
||||||
instance.getBaseValue(),
|
instance.getBaseValue(),
|
||||||
instance.getModifiers().stream().map(m -> adapt(m, version)).collect(Collectors.toSet())
|
instance.getModifiers().stream()
|
||||||
|
.filter(modifier -> !settings.isIgnoredModifier(modifier.getName()))
|
||||||
|
.map(BukkitData.Attributes::adapt).collect(Collectors.toSet())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static Modifier adapt(@NotNull AttributeModifier modifier, @NotNull Version version) {
|
private static Modifier adapt(@NotNull AttributeModifier modifier) {
|
||||||
return new Modifier(
|
return new Modifier(
|
||||||
version.compareTo(Version.fromString("1.21")) >= 0 ? null : modifier.getUniqueId(),
|
getModifierId(modifier),
|
||||||
modifier.getName(),
|
modifier.getName(),
|
||||||
modifier.getAmount(),
|
modifier.getAmount(),
|
||||||
modifier.getOperation().ordinal(),
|
modifier.getOperation().ordinal(),
|
||||||
@@ -611,28 +625,83 @@ public abstract class BukkitData implements Data {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Nullable
|
||||||
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
private static UUID getModifierId(@NotNull AttributeModifier modifier) {
|
||||||
Registry.ATTRIBUTE.forEach(id -> applyAttribute(user.getPlayer().getAttribute(id), getAttribute(id).orElse(null)));
|
try {
|
||||||
|
return modifier.getUniqueId();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute) {
|
private static boolean useKeyedModifiers(@NotNull HuskSync plugin) {
|
||||||
|
if (USE_KEYED_MODIFIERS == TriState.NOT_SET) {
|
||||||
|
boolean is1_21 = plugin.getMinecraftVersion().compareTo(Version.fromString("1.21")) >= 0;
|
||||||
|
USE_KEYED_MODIFIERS = TriState.byBoolean(is1_21);
|
||||||
|
return is1_21;
|
||||||
|
}
|
||||||
|
return Boolean.TRUE.equals(USE_KEYED_MODIFIERS.toBoolean());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void applyAttribute(@Nullable AttributeInstance instance, @Nullable Attribute attribute,
|
||||||
|
@NotNull HuskSync plugin) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
instance.setBaseValue(attribute == null ? instance.getDefaultValue() : attribute.baseValue());
|
instance.setBaseValue(attribute == null ? instance.getDefaultValue() : attribute.baseValue());
|
||||||
instance.getModifiers().forEach(instance::removeModifier);
|
instance.getModifiers().forEach(instance::removeModifier);
|
||||||
if (attribute != null) {
|
if (attribute != null) {
|
||||||
attribute.modifiers().forEach(modifier -> instance.addModifier(new AttributeModifier(
|
attribute.modifiers().stream()
|
||||||
modifier.uuid(),
|
.filter(mod -> instance.getModifiers().stream().map(AttributeModifier::getName)
|
||||||
modifier.name(),
|
.noneMatch(n -> n.equals(mod.name())))
|
||||||
modifier.amount(),
|
.distinct()
|
||||||
AttributeModifier.Operation.values()[modifier.operationType()],
|
.filter(mod -> useKeyedModifiers(plugin) == !mod.hasUuid())
|
||||||
modifier.equipmentSlot() != -1 ? EquipmentSlot.values()[modifier.equipmentSlot()] : null
|
.forEach(mod -> instance.addModifier(adapt(mod, plugin)));
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||||
|
@NotNull
|
||||||
|
private static AttributeModifier adapt(@NotNull Modifier modifier, @NotNull HuskSync plugin) {
|
||||||
|
final int slotId = modifier.equipmentSlot();
|
||||||
|
if (useKeyedModifiers(plugin)) {
|
||||||
|
try {
|
||||||
|
// Reflexively create a modern keyed attribute modifier instance. Remove in favor of API long-term.
|
||||||
|
final EquipmentSlot slot = slotId != -1 ? EquipmentSlot.values()[slotId] : null;
|
||||||
|
final Class<?> slotGroup = Class.forName(EQUIPMENT_SLOT_GROUP);
|
||||||
|
final String modifierName = modifier.name() == null ? modifier.uuid().toString() : modifier.name();
|
||||||
|
final NamespacedKey modifierKey = Objects.requireNonNull(NamespacedKey.fromString(modifierName),
|
||||||
|
"Modifier key returned null");
|
||||||
|
final Constructor<AttributeModifier> constructor = AttributeModifier.class.getDeclaredConstructor(
|
||||||
|
NamespacedKey.class, double.class, AttributeModifier.Operation.class, slotGroup);
|
||||||
|
return constructor.newInstance(
|
||||||
|
modifierKey,
|
||||||
|
modifier.amount(),
|
||||||
|
AttributeModifier.Operation.values()[modifier.operationType()],
|
||||||
|
slot == null ? slotGroup.getField(EQUIPMENT_SLOT_GROUP$ANY).get(null)
|
||||||
|
: EquipmentSlot.class.getDeclaredMethod(EQUIPMENT_SLOT$getGroup).invoke(slot)
|
||||||
|
);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
plugin.log(Level.WARNING, "Error reflectively creating keyed attribute modifier", e);
|
||||||
|
USE_KEYED_MODIFIERS = TriState.FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new AttributeModifier(
|
||||||
|
modifier.uuid(),
|
||||||
|
modifier.name(),
|
||||||
|
modifier.amount(),
|
||||||
|
AttributeModifier.Operation.values()[modifier.operationType()],
|
||||||
|
slotId != -1 ? EquipmentSlot.values()[slotId] : null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(@NotNull BukkitUser user, @NotNull BukkitHuskSync plugin) throws IllegalStateException {
|
||||||
|
Registry.ATTRIBUTE.forEach(id -> applyAttribute(
|
||||||
|
user.getPlayer().getAttribute(id), getAttribute(id).orElse(null), plugin
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -692,11 +761,12 @@ public abstract class BukkitData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set health scale
|
// Set health scale
|
||||||
|
double scale = healthScale <= 0 ? player.getMaxHealth() : healthScale;
|
||||||
try {
|
try {
|
||||||
player.setHealthScale(healthScale);
|
player.setHealthScale(scale);
|
||||||
player.setHealthScaled(isHealthScaled);
|
player.setHealthScaled(isHealthScaled);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.WARNING, "Error setting %s's health scale to %s".formatted(player.getName(), healthScale), e);
|
plugin.log(Level.WARNING, "Error setting %s's health scale to %s".formatted(player.getName(), scale), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -165,6 +165,7 @@ public class BukkitSerializer {
|
|||||||
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2;
|
case "1.20", "1.20.1", "1.20.2" -> DataFixerUtil.VERSION1_20_2;
|
||||||
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
|
case "1.20.3", "1.20.4" -> DataFixerUtil.VERSION1_20_4;
|
||||||
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
case "1.20.5", "1.20.6" -> DataFixerUtil.VERSION1_20_5;
|
||||||
|
case "1.21" -> DataFixerUtil.VERSION1_21;
|
||||||
default -> DataFixerUtil.getCurrentVersion();
|
default -> DataFixerUtil.getCurrentVersion();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import com.github.retrooper.packetevents.PacketEvents;
|
|||||||
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
import com.github.retrooper.packetevents.event.PacketListenerAbstract;
|
||||||
import com.github.retrooper.packetevents.event.PacketListenerPriority;
|
import com.github.retrooper.packetevents.event.PacketListenerPriority;
|
||||||
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
import com.github.retrooper.packetevents.event.PacketReceiveEvent;
|
||||||
|
import com.github.retrooper.packetevents.event.PacketSendEvent;
|
||||||
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
import com.github.retrooper.packetevents.protocol.packettype.PacketType;
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
|
import io.github.retrooper.packetevents.factory.spigot.SpigotPacketEventsBuilder;
|
||||||
@@ -78,7 +79,20 @@ public class BukkitPacketEventsLockedPacketListener extends BukkitLockedEventLis
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceive(PacketReceiveEvent event) {
|
public void onPacketReceive(PacketReceiveEvent event) {
|
||||||
if(!(event.getPacketType() instanceof PacketType.Play.Client client)) {
|
if (!(event.getPacketType() instanceof PacketType.Play.Client client)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!CANCEL_PACKETS.contains(client)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (listener.cancelPlayerEvent(event.getUser().getUUID())) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPacketSend(PacketSendEvent event) {
|
||||||
|
if (!(event.getPacketType() instanceof PacketType.Play.Client client)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!CANCEL_PACKETS.contains(client)) {
|
if (!CANCEL_PACKETS.contains(client)) {
|
||||||
|
|||||||
@@ -204,10 +204,10 @@ public class LegacyMigrator extends Migrator {
|
|||||||
}) {
|
}) {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
|
|||||||
@@ -201,10 +201,10 @@ public class MpdbMigrator extends Migrator {
|
|||||||
}) {
|
}) {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Successfully set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]));
|
obfuscateDataString(args[1]));
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
plugin.log(Level.INFO, "Invalid operation, could not set " + args[0] + " to " +
|
||||||
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
obfuscateDataString(args[1]) + " (is it a valid option?)");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.INFO, getHelpMenu());
|
plugin.log(Level.INFO, getHelpMenu());
|
||||||
@@ -255,7 +255,7 @@ public class MpdbMigrator extends Migrator {
|
|||||||
If any of these are not correct, please correct them
|
If any of these are not correct, please correct them
|
||||||
using the command:
|
using the command:
|
||||||
"husksync migrate mpdb set <parameter> <value>"
|
"husksync migrate mpdb set <parameter> <value>"
|
||||||
(e.g.: "husksync migrate mpdb set host 1.2.3.4")
|
(e.g.: "husksync migrate set mpdb host 1.2.3.4")
|
||||||
|
|
||||||
STEP 3] HuskSync will migrate data into the database
|
STEP 3] HuskSync will migrate data into the database
|
||||||
tables configures in the config.yml file of this
|
tables configures in the config.yml file of this
|
||||||
@@ -263,7 +263,7 @@ public class MpdbMigrator extends Migrator {
|
|||||||
before proceeding.
|
before proceeding.
|
||||||
|
|
||||||
STEP 4] To start the migration, please run:
|
STEP 4] To start the migration, please run:
|
||||||
"husksync migrate mpdb start"
|
"husksync migrate start mpdb"
|
||||||
|
|
||||||
NOTE: This migrator currently WORKS WITH MPDB version
|
NOTE: This migrator currently WORKS WITH MPDB version
|
||||||
v4.9.2 and below!
|
v4.9.2 and below!
|
||||||
|
|||||||
@@ -23,14 +23,10 @@ import de.themoep.minedown.adventure.MineDown;
|
|||||||
import dev.triumphteam.gui.builder.gui.StorageBuilder;
|
import dev.triumphteam.gui.builder.gui.StorageBuilder;
|
||||||
import dev.triumphteam.gui.guis.Gui;
|
import dev.triumphteam.gui.guis.Gui;
|
||||||
import dev.triumphteam.gui.guis.StorageGui;
|
import dev.triumphteam.gui.guis.StorageGui;
|
||||||
import net.roxeez.advancement.display.FrameType;
|
|
||||||
import net.william278.andjam.Toast;
|
|
||||||
import net.william278.husksync.BukkitHuskSync;
|
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.BukkitData;
|
import net.william278.husksync.data.BukkitData;
|
||||||
import net.william278.husksync.data.BukkitUserDataHolder;
|
import net.william278.husksync.data.BukkitUserDataHolder;
|
||||||
import net.william278.husksync.data.Data;
|
import net.william278.husksync.data.Data;
|
||||||
import org.bukkit.Material;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@@ -40,8 +36,6 @@ import java.util.Arrays;
|
|||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
import static net.william278.husksync.util.BukkitKeyedAdapter.matchMaterial;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bukkit platform implementation of an {@link OnlineUser}
|
* Bukkit platform implementation of an {@link OnlineUser}
|
||||||
*/
|
*/
|
||||||
@@ -68,20 +62,12 @@ public class BukkitUser extends OnlineUser implements BukkitUserDataHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
@NotNull String iconMaterial, @NotNull String backgroundType) {
|
||||||
try {
|
plugin.log(Level.WARNING, "Toast notifications are deprecated. " +
|
||||||
final Material material = matchMaterial(iconMaterial);
|
"Please change your notification display slot to CHAT, ACTION_BAR or NONE.");
|
||||||
Toast.builder((BukkitHuskSync) plugin)
|
this.sendActionBar(title);
|
||||||
.setTitle(title.toComponent())
|
|
||||||
.setDescription(description.toComponent())
|
|
||||||
.setIcon(material != null ? material : Material.BARRIER)
|
|
||||||
.setFrameType(FrameType.valueOf(backgroundType))
|
|
||||||
.build()
|
|
||||||
.show(player);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
plugin.log(Level.WARNING, "Failed to send toast to player " + player.getName(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -31,8 +31,10 @@ import net.william278.mapdataapi.MapData;
|
|||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Material;
|
import org.bukkit.Material;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.ShulkerBox;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.inventory.ItemStack;
|
import org.bukkit.inventory.ItemStack;
|
||||||
|
import org.bukkit.inventory.meta.BlockStateMeta;
|
||||||
import org.bukkit.inventory.meta.MapMeta;
|
import org.bukkit.inventory.meta.MapMeta;
|
||||||
import org.bukkit.map.*;
|
import org.bukkit.map.*;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
@@ -94,6 +96,9 @@ public interface BukkitMapPersister {
|
|||||||
}
|
}
|
||||||
if (item.getType() == Material.FILLED_MAP && item.hasItemMeta()) {
|
if (item.getType() == Material.FILLED_MAP && item.hasItemMeta()) {
|
||||||
items[i] = function.apply(item);
|
items[i] = function.apply(item);
|
||||||
|
} else if (item.getItemMeta() instanceof BlockStateMeta b && b.getBlockState() instanceof ShulkerBox box) {
|
||||||
|
forEachMap(box.getInventory().getContents(), function);
|
||||||
|
b.setBlockState(box);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
@@ -419,19 +424,23 @@ public interface BukkitMapPersister {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private MapData extractMapData() {
|
private MapData extractMapData() {
|
||||||
final List<MapBanner> banners = Lists.newArrayList();
|
final List<MapBanner> banners = Lists.newArrayList();
|
||||||
final String BANNER_PREFIX = "banner_";
|
try {
|
||||||
for (int i = 0; i < getCursors().size(); i++) {
|
final String BANNER_PREFIX = "banner_";
|
||||||
final MapCursor cursor = getCursors().getCursor(i);
|
for (int i = 0; i < getCursors().size(); i++) {
|
||||||
final String type = cursor.getType().name().toLowerCase(Locale.ENGLISH);
|
final MapCursor cursor = getCursors().getCursor(i);
|
||||||
if (type.startsWith(BANNER_PREFIX)) {
|
final String type = cursor.getType().name().toLowerCase(Locale.ENGLISH);
|
||||||
banners.add(new MapBanner(
|
if (type.startsWith(BANNER_PREFIX)) {
|
||||||
type.replaceAll(BANNER_PREFIX, ""),
|
banners.add(new MapBanner(
|
||||||
cursor.getCaption() == null ? "" : cursor.getCaption(),
|
type.replaceAll(BANNER_PREFIX, ""),
|
||||||
cursor.getX(),
|
cursor.getCaption() == null ? "" : cursor.getCaption(),
|
||||||
mapView.getWorld() != null ? mapView.getWorld().getSeaLevel() : 128,
|
cursor.getX(),
|
||||||
cursor.getY()
|
mapView.getWorld() != null ? mapView.getWorld().getSeaLevel() : 128,
|
||||||
));
|
cursor.getY()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
}
|
}
|
||||||
return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, List.of());
|
return MapData.fromPixels(pixels, getDimension(), (byte) 2, banners, List.of());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
inventory {
|
|
||||||
name brigadier:string single_word;
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
husksync {
|
|
||||||
update;
|
|
||||||
about;
|
|
||||||
status;
|
|
||||||
reload;
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
enderchest {
|
|
||||||
name brigadier:string single_word;
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
userdata {
|
|
||||||
view {
|
|
||||||
name brigadier:string single_word {
|
|
||||||
version brigadier:string single_word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
list {
|
|
||||||
name brigadier:string single_word {
|
|
||||||
page brigadier:integer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete {
|
|
||||||
name brigadier:string single_word {
|
|
||||||
version brigadier:string single_word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
restore {
|
|
||||||
name brigadier:string single_word {
|
|
||||||
version brigadier:string single_word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pin {
|
|
||||||
name brigadier:string single_word {
|
|
||||||
version brigadier:string single_word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dump {
|
|
||||||
name brigadier:string single_word {
|
|
||||||
version brigadier:string single_word {
|
|
||||||
web;
|
|
||||||
file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,9 @@ dependencies {
|
|||||||
exclude module: 'slf4j-api'
|
exclude module: 'slf4j-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'net.william278.uniform:uniform-common:1.2.1'
|
||||||
|
compileOnly 'com.mojang:brigadier:1.1.8'
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
compileOnly 'net.kyori:adventure-api:4.17.0'
|
compileOnly 'net.kyori:adventure-api:4.17.0'
|
||||||
compileOnly 'net.kyori:adventure-platform-api:4.3.3'
|
compileOnly 'net.kyori:adventure-platform-api:4.3.3'
|
||||||
@@ -36,5 +38,5 @@ dependencies {
|
|||||||
testCompileOnly 'de.exlll:configlib-yaml:4.5.0'
|
testCompileOnly 'de.exlll:configlib-yaml:4.5.0'
|
||||||
testCompileOnly 'org.jetbrains:annotations:24.1.0'
|
testCompileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
}
|
}
|
||||||
@@ -41,6 +41,7 @@ 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;
|
||||||
import net.william278.husksync.util.Task;
|
import net.william278.husksync.util.Task;
|
||||||
|
import net.william278.uniform.Uniform;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -111,6 +112,14 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
*/
|
*/
|
||||||
void setDataSyncer(@NotNull DataSyncer dataSyncer);
|
void setDataSyncer(@NotNull DataSyncer dataSyncer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the uniform command provider
|
||||||
|
*
|
||||||
|
* @return the command provider
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
Uniform getUniform();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a list of available data {@link Migrator}s
|
* Returns a list of available data {@link Migrator}s
|
||||||
*
|
*
|
||||||
@@ -246,6 +255,14 @@ public interface HuskSync extends Task.Supplier, EventDispatcher, ConfigProvider
|
|||||||
@NotNull
|
@NotNull
|
||||||
String getPlatformType();
|
String getPlatformType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the server software version
|
||||||
|
*
|
||||||
|
* @return the server software version string
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
String getServerVersion();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the legacy data converter if it exists
|
* Returns the legacy data converter if it exists
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,94 +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.command;
|
|
||||||
|
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import net.william278.husksync.HuskSync;
|
|
||||||
import net.william278.husksync.user.CommandUser;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public abstract class Command extends Node {
|
|
||||||
|
|
||||||
private final String usage;
|
|
||||||
private final Map<String, Boolean> additionalPermissions;
|
|
||||||
|
|
||||||
protected Command(@NotNull String name, @NotNull List<String> aliases, @NotNull String usage,
|
|
||||||
@NotNull HuskSync plugin) {
|
|
||||||
super(name, aliases, plugin);
|
|
||||||
this.usage = usage;
|
|
||||||
this.additionalPermissions = Maps.newHashMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void onExecuted(@NotNull CommandUser executor, @NotNull String[] args) {
|
|
||||||
if (!executor.hasPermission(getPermission())) {
|
|
||||||
plugin.getLocales().getLocale("error_no_permission")
|
|
||||||
.ifPresent(executor::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.runAsync(() -> this.execute(executor, args));
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract void execute(@NotNull CommandUser executor, @NotNull String[] args);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
protected String[] removeFirstArg(@NotNull String[] args) {
|
|
||||||
if (args.length <= 1) {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
String[] newArgs = new String[args.length - 1];
|
|
||||||
System.arraycopy(args, 1, newArgs, 0, args.length - 1);
|
|
||||||
return newArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public final String getRawUsage() {
|
|
||||||
return usage;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public final String getUsage() {
|
|
||||||
return "/" + getName() + " " + getRawUsage();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void addAdditionalPermissions(@NotNull Map<String, Boolean> permissions) {
|
|
||||||
permissions.forEach((permission, value) -> this.additionalPermissions.put(getPermission(permission), value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public final Map<String, Boolean> getAdditionalPermissions() {
|
|
||||||
return additionalPermissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getDescription() {
|
|
||||||
return plugin.getLocales().getRawLocale(getName() + "_command_description")
|
|
||||||
.orElse(getUsage());
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public final HuskSync getPlugin() {
|
|
||||||
return plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,7 @@ import java.util.Optional;
|
|||||||
public class EnderChestCommand extends ItemsCommand {
|
public class EnderChestCommand extends ItemsCommand {
|
||||||
|
|
||||||
public EnderChestCommand(@NotNull HuskSync plugin) {
|
public EnderChestCommand(@NotNull HuskSync plugin) {
|
||||||
super(plugin, List.of("enderchest", "echest", "openechest"));
|
super("enderchest", List.of("echest", "openechest"), plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import de.themoep.minedown.adventure.MineDown;
|
import de.themoep.minedown.adventure.MineDown;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.JoinConfiguration;
|
import net.kyori.adventure.text.JoinConfiguration;
|
||||||
@@ -31,33 +33,26 @@ import net.william278.husksync.HuskSync;
|
|||||||
import net.william278.husksync.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
import net.william278.husksync.migrator.Migrator;
|
import net.william278.husksync.migrator.Migrator;
|
||||||
import net.william278.husksync.user.CommandUser;
|
import net.william278.husksync.user.CommandUser;
|
||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.uniform.BaseCommand;
|
||||||
|
import net.william278.uniform.CommandProvider;
|
||||||
|
import net.william278.uniform.Permission;
|
||||||
|
import net.william278.uniform.element.ArgumentElement;
|
||||||
import org.apache.commons.text.WordUtils;
|
import org.apache.commons.text.WordUtils;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class HuskSyncCommand extends Command implements TabProvider {
|
public class HuskSyncCommand extends PluginCommand {
|
||||||
|
|
||||||
private static final Map<String, Boolean> SUB_COMMANDS = Map.of(
|
|
||||||
"about", false,
|
|
||||||
"status", true,
|
|
||||||
"reload", true,
|
|
||||||
"migrate", true,
|
|
||||||
"update", true
|
|
||||||
);
|
|
||||||
|
|
||||||
private final UpdateChecker updateChecker;
|
private final UpdateChecker updateChecker;
|
||||||
private final AboutMenu aboutMenu;
|
private final AboutMenu aboutMenu;
|
||||||
|
|
||||||
public HuskSyncCommand(@NotNull HuskSync plugin) {
|
public HuskSyncCommand(@NotNull HuskSync plugin) {
|
||||||
super("husksync", List.of(), "[" + String.join("|", SUB_COMMANDS.keySet()) + "]", plugin);
|
super("husksync", List.of(), Permission.Default.TRUE, ExecutionScope.ALL, plugin);
|
||||||
addAdditionalPermissions(SUB_COMMANDS);
|
|
||||||
|
|
||||||
this.updateChecker = plugin.getUpdateChecker();
|
this.updateChecker = plugin.getUpdateChecker();
|
||||||
this.aboutMenu = AboutMenu.builder()
|
this.aboutMenu = AboutMenu.builder()
|
||||||
.title(Component.text("HuskSync"))
|
.title(Component.text("HuskSync"))
|
||||||
@@ -95,136 +90,137 @@ public class HuskSyncCommand extends Command implements TabProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull CommandUser executor, @NotNull String[] args) {
|
public void provide(@NotNull BaseCommand<?> command) {
|
||||||
final String subCommand = parseStringArg(args, 0).orElse("about").toLowerCase(Locale.ENGLISH);
|
command.setDefaultExecutor((ctx) -> about(command, ctx));
|
||||||
if (SUB_COMMANDS.containsKey(subCommand) && !executor.hasPermission(getPermission(subCommand))) {
|
command.addSubCommand("about", (sub) -> sub.setDefaultExecutor((ctx) -> about(command, ctx)));
|
||||||
plugin.getLocales().getLocale("error_no_permission")
|
command.addSubCommand("status", needsOp("status"), status());
|
||||||
.ifPresent(executor::sendMessage);
|
command.addSubCommand("reload", needsOp("reload"), reload());
|
||||||
return;
|
command.addSubCommand("update", needsOp("update"), update());
|
||||||
}
|
command.addSubCommand("migrate", migrate());
|
||||||
|
|
||||||
switch (subCommand) {
|
|
||||||
case "about" -> executor.sendMessage(aboutMenu.toComponent());
|
|
||||||
case "status" -> {
|
|
||||||
getPlugin().getLocales().getLocale("system_status_header").ifPresent(executor::sendMessage);
|
|
||||||
executor.sendMessage(Component.join(
|
|
||||||
JoinConfiguration.newlines(),
|
|
||||||
Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
case "reload" -> {
|
|
||||||
try {
|
|
||||||
plugin.loadSettings();
|
|
||||||
plugin.loadLocales();
|
|
||||||
plugin.loadServer();
|
|
||||||
plugin.getLocales().getLocale("reload_complete").ifPresent(executor::sendMessage);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
executor.sendMessage(new MineDown(
|
|
||||||
"[Error:](#ff3300) [Failed to reload the plugin. Check console for errors.](#ff7e5e)"
|
|
||||||
));
|
|
||||||
plugin.log(Level.SEVERE, "Failed to reload the plugin", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "migrate" -> {
|
|
||||||
if (executor instanceof OnlineUser) {
|
|
||||||
plugin.getLocales().getLocale("error_console_command_only")
|
|
||||||
.ifPresent(executor::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.handleMigrationCommand(args);
|
|
||||||
}
|
|
||||||
case "update" -> updateChecker.check().thenAccept(checked -> {
|
|
||||||
if (checked.isUpToDate()) {
|
|
||||||
plugin.getLocales().getLocale("up_to_date", plugin.getPluginVersion().toString())
|
|
||||||
.ifPresent(executor::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugin.getLocales().getLocale("update_available", checked.getLatestVersion().toString(),
|
|
||||||
plugin.getPluginVersion().toString()).ifPresent(executor::sendMessage);
|
|
||||||
});
|
|
||||||
default -> plugin.getLocales().getLocale("error_invalid_syntax", getUsage())
|
|
||||||
.ifPresent(executor::sendMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle a migration console command input
|
private void about(@NotNull BaseCommand<?> c, @NotNull CommandContext<?> ctx) {
|
||||||
private void handleMigrationCommand(@NotNull String[] args) {
|
user(c, ctx).getAudience().sendMessage(aboutMenu.toComponent());
|
||||||
if (args.length < 2) {
|
}
|
||||||
plugin.log(Level.INFO,
|
|
||||||
"Please choose a migrator, then run \"husksync migrate <migrator>\"");
|
|
||||||
this.logMigratorList();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Optional<Migrator> selectedMigrator = plugin.getAvailableMigrators().stream()
|
@NotNull
|
||||||
.filter(available -> available.getIdentifier().equalsIgnoreCase(args[1]))
|
private CommandProvider status() {
|
||||||
.findFirst();
|
return (sub) -> sub.setDefaultExecutor((ctx) -> {
|
||||||
selectedMigrator.ifPresentOrElse(migrator -> {
|
final CommandUser user = user(sub, ctx);
|
||||||
if (args.length < 3) {
|
plugin.getLocales().getLocale("system_status_header").ifPresent(user::sendMessage);
|
||||||
plugin.log(Level.INFO, migrator.getHelpMenu());
|
user.sendMessage(Component.join(
|
||||||
|
JoinConfiguration.newlines(),
|
||||||
|
Arrays.stream(StatusLine.values()).map(s -> s.get(plugin)).toList()
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider reload() {
|
||||||
|
return (sub) -> sub.setDefaultExecutor((ctx) -> {
|
||||||
|
final CommandUser user = user(sub, ctx);
|
||||||
|
try {
|
||||||
|
plugin.loadSettings();
|
||||||
|
plugin.loadLocales();
|
||||||
|
plugin.loadServer();
|
||||||
|
plugin.getLocales().getLocale("reload_complete").ifPresent(user::sendMessage);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
user.sendMessage(new MineDown(
|
||||||
|
"[Error:](#ff3300) [Failed to reload the plugin. Check console for errors.](#ff7e5e)"
|
||||||
|
));
|
||||||
|
plugin.log(Level.SEVERE, "Failed to reload the plugin", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider update() {
|
||||||
|
return (sub) -> sub.setDefaultExecutor((ctx) -> updateChecker.check().thenAccept(checked -> {
|
||||||
|
final CommandUser user = user(sub, ctx);
|
||||||
|
if (checked.isUpToDate()) {
|
||||||
|
plugin.getLocales().getLocale("up_to_date", plugin.getPluginVersion().toString())
|
||||||
|
.ifPresent(user::sendMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch (args[2]) {
|
plugin.getLocales().getLocale("update_available", checked.getLatestVersion().toString(),
|
||||||
case "start" -> migrator.start().thenAccept(succeeded -> {
|
plugin.getPluginVersion().toString()).ifPresent(user::sendMessage);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider migrate() {
|
||||||
|
return (sub) -> {
|
||||||
|
sub.setCondition((ctx) -> sub.getUser(ctx).isConsole());
|
||||||
|
sub.setDefaultExecutor((ctx) -> {
|
||||||
|
plugin.log(Level.INFO, "Please choose a migrator, then run \"husksync migrate start <migrator>\"");
|
||||||
|
plugin.log(Level.INFO, String.format(
|
||||||
|
"List of available migrators:\nMigrator ID / Migrator Name:\n%s",
|
||||||
|
plugin.getAvailableMigrators().stream()
|
||||||
|
.map(migrator -> String.format("%s - %s", migrator.getIdentifier(), migrator.getName()))
|
||||||
|
.collect(Collectors.joining("\n"))
|
||||||
|
));
|
||||||
|
});
|
||||||
|
sub.addSubCommand("help", (help) -> help.addSyntax((cmd) -> {
|
||||||
|
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
||||||
|
plugin.log(Level.INFO, migrator.getHelpMenu());
|
||||||
|
}, migrator()));
|
||||||
|
sub.addSubCommand("start", (start) -> start.addSyntax((cmd) -> {
|
||||||
|
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
||||||
|
migrator.start().thenAccept(succeeded -> {
|
||||||
if (succeeded) {
|
if (succeeded) {
|
||||||
plugin.log(Level.INFO, "Migration completed successfully!");
|
plugin.log(Level.INFO, "Migration completed successfully!");
|
||||||
} else {
|
} else {
|
||||||
plugin.log(Level.WARNING, "Migration failed!");
|
plugin.log(Level.WARNING, "Migration failed!");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
case "set" -> migrator.handleConfigurationCommand(Arrays.copyOfRange(args, 3, args.length));
|
}, migrator()));
|
||||||
default -> plugin.log(Level.INFO, String.format(
|
sub.addSubCommand("set", (set) -> set.addSyntax((cmd) -> {
|
||||||
"Invalid syntax. Console usage: \"husksync migrate %s <start/set>", args[1]
|
final Migrator migrator = cmd.getArgument("migrator", Migrator.class);
|
||||||
));
|
final String[] args = cmd.getArgument("args", String.class).split(" ");
|
||||||
}
|
migrator.handleConfigurationCommand(args);
|
||||||
}, () -> {
|
}, migrator(), BaseCommand.greedyString("args")));
|
||||||
plugin.log(Level.INFO,
|
|
||||||
"Please specify a valid migrator.\n" +
|
|
||||||
"If a migrator is not available, please verify that you meet the prerequisites to use it.");
|
|
||||||
this.logMigratorList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log the list of available migrators
|
|
||||||
private void logMigratorList() {
|
|
||||||
plugin.log(Level.INFO, String.format(
|
|
||||||
"List of available migrators:\nMigrator ID / Migrator Name:\n%s",
|
|
||||||
plugin.getAvailableMigrators().stream()
|
|
||||||
.map(migrator -> String.format("%s - %s", migrator.getIdentifier(), migrator.getName()))
|
|
||||||
.collect(Collectors.joining("\n"))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public List<String> suggest(@NotNull CommandUser user, @NotNull String[] args) {
|
|
||||||
return switch (args.length) {
|
|
||||||
case 0, 1 -> SUB_COMMANDS.keySet().stream().sorted().toList();
|
|
||||||
default -> null;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private <S> ArgumentElement<S, Migrator> migrator() {
|
||||||
|
return new ArgumentElement<>("migrator", reader -> {
|
||||||
|
final String id = reader.readString();
|
||||||
|
final Migrator migrator = plugin.getAvailableMigrators().stream()
|
||||||
|
.filter(m -> m.getIdentifier().equalsIgnoreCase(id)).findFirst().orElse(null);
|
||||||
|
if (migrator == null) {
|
||||||
|
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader);
|
||||||
|
}
|
||||||
|
return migrator;
|
||||||
|
}, (context, builder) -> {
|
||||||
|
for (Migrator material : plugin.getAvailableMigrators()) {
|
||||||
|
builder.suggest(material.getIdentifier());
|
||||||
|
}
|
||||||
|
return builder.buildFuture();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private enum StatusLine {
|
private enum StatusLine {
|
||||||
PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata())
|
PLUGIN_VERSION(plugin -> Component.text("v" + plugin.getPluginVersion().toStringWithoutMetadata())
|
||||||
.appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty()
|
.appendSpace().append(plugin.getPluginVersion().getMetadata().isBlank() ? Component.empty()
|
||||||
: Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))),
|
: Component.text("(build " + plugin.getPluginVersion().getMetadata() + ")"))),
|
||||||
PLATFORM_TYPE(plugin -> Component.text(WordUtils.capitalizeFully(plugin.getPlatformType()))),
|
SERVER_VERSION(plugin -> Component.text(plugin.getServerVersion())),
|
||||||
LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())),
|
LANGUAGE(plugin -> Component.text(plugin.getSettings().getLanguage())),
|
||||||
MINECRAFT_VERSION(plugin -> Component.text(plugin.getMinecraftVersion().toString())),
|
MINECRAFT_VERSION(plugin -> Component.text(plugin.getMinecraftVersion().toString())),
|
||||||
JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))),
|
JAVA_VERSION(plugin -> Component.text(System.getProperty("java.version"))),
|
||||||
JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))),
|
JAVA_VENDOR(plugin -> Component.text(System.getProperty("java.vendor"))),
|
||||||
|
SERVER_NAME(plugin -> Component.text(plugin.getServerName())),
|
||||||
|
CLUSTER_ID(plugin -> Component.text(plugin.getSettings().getClusterId().isBlank() ? "None" : plugin.getSettings().getClusterId())),
|
||||||
SYNC_MODE(plugin -> Component.text(WordUtils.capitalizeFully(
|
SYNC_MODE(plugin -> Component.text(WordUtils.capitalizeFully(
|
||||||
plugin.getSettings().getSynchronization().getMode().toString()
|
plugin.getSettings().getSynchronization().getMode().toString()
|
||||||
))),
|
))),
|
||||||
DELAY_LATENCY(plugin -> Component.text(
|
DELAY_LATENCY(plugin -> Component.text(
|
||||||
plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds() + "ms"
|
plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds() + "ms"
|
||||||
)),
|
)),
|
||||||
SERVER_NAME(plugin -> Component.text(plugin.getServerName())),
|
|
||||||
CLUSTER_ID(plugin -> Component.text(plugin.getSettings().getClusterId().isBlank() ? "None" : plugin.getSettings().getClusterId())),
|
|
||||||
DATABASE_TYPE(plugin ->
|
DATABASE_TYPE(plugin ->
|
||||||
Component.text(plugin.getSettings().getDatabase().getType().getDisplayName() +
|
Component.text(plugin.getSettings().getDatabase().getType().getDisplayName() +
|
||||||
(plugin.getSettings().getDatabase().getType() == Database.Type.MONGO ?
|
(plugin.getSettings().getDatabase().getType() == Database.Type.MONGO ?
|
||||||
(plugin.getSettings().getDatabase().getMongoSettings().isUsingAtlas() ? " Atlas" : "") : ""))
|
(plugin.getSettings().getDatabase().getMongoSettings().isUsingAtlas() ? " Atlas" : "") : ""))
|
||||||
),
|
),
|
||||||
IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getDatabase().getCredentials().getHost())),
|
IS_DATABASE_LOCAL(plugin -> getLocalhostBoolean(plugin.getSettings().getDatabase().getCredentials().getHost())),
|
||||||
USING_REDIS_SENTINEL(plugin -> getBoolean(
|
USING_REDIS_SENTINEL(plugin -> getBoolean(
|
||||||
@@ -282,7 +278,7 @@ public class HuskSyncCommand extends Command implements TabProvider {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static Component getLocalhostBoolean(@NotNull String value) {
|
private static Component getLocalhostBoolean(@NotNull String value) {
|
||||||
return getBoolean(value.equals("127.0.0.1") || value.equals("0.0.0.0")
|
return getBoolean(value.equals("127.0.0.1") || value.equals("0.0.0.0")
|
||||||
|| value.equals("localhost") || value.equals("::1"));
|
|| value.equals("localhost") || value.equals("::1"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ import java.util.Optional;
|
|||||||
public class InventoryCommand extends ItemsCommand {
|
public class InventoryCommand extends ItemsCommand {
|
||||||
|
|
||||||
public InventoryCommand(@NotNull HuskSync plugin) {
|
public InventoryCommand(@NotNull HuskSync plugin) {
|
||||||
super(plugin, List.of("inventory", "invsee", "openinv"));
|
super("inventory", List.of("invsee", "openinv"), plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -24,47 +24,43 @@ import net.william278.husksync.data.DataSnapshot;
|
|||||||
import net.william278.husksync.user.CommandUser;
|
import net.william278.husksync.user.CommandUser;
|
||||||
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 net.william278.uniform.BaseCommand;
|
||||||
|
import net.william278.uniform.Permission;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public abstract class ItemsCommand extends Command implements TabProvider {
|
public abstract class ItemsCommand extends PluginCommand {
|
||||||
|
|
||||||
protected ItemsCommand(@NotNull HuskSync plugin, @NotNull List<String> aliases) {
|
protected ItemsCommand(@NotNull String name, @NotNull List<String> aliases, @NotNull HuskSync plugin) {
|
||||||
super(aliases.get(0), aliases.subList(1, aliases.size()), "<player> [version_uuid]", plugin);
|
super(name, aliases, Permission.Default.IF_OP, ExecutionScope.IN_GAME, plugin);
|
||||||
setOperatorCommand(true);
|
|
||||||
addAdditionalPermissions(Map.of("edit", true));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull CommandUser executor, @NotNull String[] args) {
|
public void provide(@NotNull BaseCommand<?> command) {
|
||||||
if (!(executor instanceof OnlineUser player)) {
|
command.addSyntax((ctx) -> {
|
||||||
plugin.getLocales().getLocale("error_in_game_command_only")
|
final User user = ctx.getArgument("username", User.class);
|
||||||
.ifPresent(executor::sendMessage);
|
final UUID version = ctx.getArgument("version", UUID.class);
|
||||||
return;
|
final CommandUser executor = user(command, ctx);
|
||||||
}
|
if (!(executor instanceof OnlineUser online)) {
|
||||||
|
plugin.getLocales().getLocale("error_in_game_command_only")
|
||||||
// Find the user to view the items for
|
.ifPresent(executor::sendMessage);
|
||||||
final Optional<User> optionalUser = parseStringArg(args, 0)
|
return;
|
||||||
.flatMap(name -> plugin.getDatabase().getUserByName(name));
|
}
|
||||||
if (optionalUser.isEmpty()) {
|
this.showSnapshotItems(online, user, version);
|
||||||
plugin.getLocales().getLocale(
|
}, user("username"), uuid("version"));
|
||||||
args.length >= 1 ? "error_invalid_player" : "error_invalid_syntax", getUsage()
|
command.addSyntax((ctx) -> {
|
||||||
).ifPresent(player::sendMessage);
|
final User user = ctx.getArgument("username", User.class);
|
||||||
return;
|
final CommandUser executor = user(command, ctx);
|
||||||
}
|
if (!(executor instanceof OnlineUser online)) {
|
||||||
|
plugin.getLocales().getLocale("error_in_game_command_only")
|
||||||
// Show the user data
|
.ifPresent(executor::sendMessage);
|
||||||
final User user = optionalUser.get();
|
return;
|
||||||
parseUUIDArg(args, 1).ifPresentOrElse(
|
}
|
||||||
version -> this.showSnapshotItems(player, user, version),
|
this.showLatestItems(online, user);
|
||||||
() -> this.showLatestItems(player, user)
|
}, user("username"));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// View (and edit) the latest user data
|
// View (and edit) the latest user data
|
||||||
@@ -114,12 +110,4 @@ public abstract class ItemsCommand extends Command implements TabProvider {
|
|||||||
protected abstract void showItems(@NotNull OnlineUser viewer, @NotNull DataSnapshot.Unpacked snapshot,
|
protected abstract void showItems(@NotNull OnlineUser viewer, @NotNull DataSnapshot.Unpacked snapshot,
|
||||||
@NotNull User user, boolean allowEdit);
|
@NotNull User user, boolean allowEdit);
|
||||||
|
|
||||||
@Nullable
|
|
||||||
@Override
|
|
||||||
public List<String> suggest(@NotNull CommandUser executor, @NotNull String[] args) {
|
|
||||||
return switch (args.length) {
|
|
||||||
case 0, 1 -> plugin.getOnlineUsers().stream().map(User::getUsername).toList();
|
|
||||||
default -> null;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,105 +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.command;
|
|
||||||
|
|
||||||
import net.william278.husksync.HuskSync;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.StringJoiner;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public abstract class Node implements Executable {
|
|
||||||
|
|
||||||
protected static final String PERMISSION_PREFIX = "husksync.command";
|
|
||||||
|
|
||||||
protected final HuskSync plugin;
|
|
||||||
private final String name;
|
|
||||||
private final List<String> aliases;
|
|
||||||
private boolean operatorCommand = false;
|
|
||||||
|
|
||||||
protected Node(@NotNull String name, @NotNull List<String> aliases, @NotNull HuskSync plugin) {
|
|
||||||
if (name.isBlank()) {
|
|
||||||
throw new IllegalArgumentException("Command name cannot be blank");
|
|
||||||
}
|
|
||||||
this.name = name;
|
|
||||||
this.aliases = aliases;
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public List<String> getAliases() {
|
|
||||||
return aliases;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public String getPermission(@NotNull String... child) {
|
|
||||||
final StringJoiner joiner = new StringJoiner(".")
|
|
||||||
.add(PERMISSION_PREFIX)
|
|
||||||
.add(getName());
|
|
||||||
for (final String node : child) {
|
|
||||||
joiner.add(node);
|
|
||||||
}
|
|
||||||
return joiner.toString().trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOperatorCommand() {
|
|
||||||
return operatorCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOperatorCommand(boolean operatorCommand) {
|
|
||||||
this.operatorCommand = operatorCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Optional<String> parseStringArg(@NotNull String[] args, int index) {
|
|
||||||
if (args.length > index) {
|
|
||||||
return Optional.of(args[index]);
|
|
||||||
}
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Optional<Integer> parseIntArg(@NotNull String[] args, int index) {
|
|
||||||
return parseStringArg(args, index).flatMap(arg -> {
|
|
||||||
try {
|
|
||||||
return Optional.of(Integer.parseInt(arg));
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Optional<UUID> parseUUIDArg(@NotNull String[] args, int index) {
|
|
||||||
return parseStringArg(args, index).flatMap(arg -> {
|
|
||||||
try {
|
|
||||||
return Optional.of(UUID.fromString(arg));
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* This file is part of HuskSync, licensed under the Apache License 2.0.
|
||||||
|
*
|
||||||
|
* Copyright (c) William278 <will27528@gmail.com>
|
||||||
|
* Copyright (c) contributors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.context.CommandContext;
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
|
import net.william278.husksync.HuskSync;
|
||||||
|
import net.william278.husksync.user.CommandUser;
|
||||||
|
import net.william278.husksync.user.User;
|
||||||
|
import net.william278.uniform.BaseCommand;
|
||||||
|
import net.william278.uniform.Command;
|
||||||
|
import net.william278.uniform.Permission;
|
||||||
|
import net.william278.uniform.element.ArgumentElement;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public abstract class PluginCommand extends Command {
|
||||||
|
|
||||||
|
protected final HuskSync plugin;
|
||||||
|
|
||||||
|
protected PluginCommand(@NotNull String name, @NotNull List<String> aliases, @NotNull Permission.Default defPerm,
|
||||||
|
@NotNull ExecutionScope scope, @NotNull HuskSync plugin) {
|
||||||
|
super(name, aliases, getDescription(plugin, name), new Permission(createPermission(name), defPerm), scope);
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getDescription(@NotNull HuskSync plugin, @NotNull String name) {
|
||||||
|
return plugin.getLocales().getRawLocale("%s_command_description".formatted(name)).orElse("");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static String createPermission(@NotNull String name, @NotNull String... sub) {
|
||||||
|
return "husksync.command." + name + (sub.length > 0 ? "." + String.join(".", sub) : "");
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected String getPermission(@NotNull String... sub) {
|
||||||
|
return createPermission(this.getName(), sub);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
protected CommandUser user(@NotNull BaseCommand base, @NotNull CommandContext context) {
|
||||||
|
return adapt(base.getUser(context.getSource()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected Permission needsOp(@NotNull String... nodes) {
|
||||||
|
return new Permission(getPermission(nodes), Permission.Default.IF_OP);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected CommandUser adapt(net.william278.uniform.CommandUser user) {
|
||||||
|
return user.getUuid() == null ? plugin.getConsole() : plugin.getOnlineUser(user.getUuid()).orElseThrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected <S> ArgumentElement<S, User> user(@NotNull String name) {
|
||||||
|
return new ArgumentElement<>(name, reader -> {
|
||||||
|
final String username = reader.readString();
|
||||||
|
return plugin.getDatabase().getUserByName(username).orElseThrow(
|
||||||
|
() -> CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader)
|
||||||
|
);
|
||||||
|
}, (context, builder) -> {
|
||||||
|
plugin.getOnlineUsers().forEach(u -> builder.suggest(u.getUsername()));
|
||||||
|
return builder.buildFuture();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
protected <S> ArgumentElement<S, UUID> uuid(@NotNull String name) {
|
||||||
|
return new ArgumentElement<>(name, reader -> {
|
||||||
|
try {
|
||||||
|
return UUID.fromString(reader.readString());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().createWithContext(reader);
|
||||||
|
}
|
||||||
|
}, (context, builder) -> builder.buildFuture());
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Type {
|
||||||
|
|
||||||
|
HUSKSYNC_COMMAND(HuskSyncCommand::new),
|
||||||
|
USERDATA_COMMAND(UserDataCommand::new),
|
||||||
|
INVENTORY_COMMAND(InventoryCommand::new),
|
||||||
|
ENDER_CHEST_COMMAND(EnderChestCommand::new);
|
||||||
|
|
||||||
|
public final Function<HuskSync, PluginCommand> commandSupplier;
|
||||||
|
|
||||||
|
Type(@NotNull Function<HuskSync, PluginCommand> supplier) {
|
||||||
|
this.commandSupplier = supplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public PluginCommand supply(@NotNull HuskSync plugin) {
|
||||||
|
return commandSupplier.apply(plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
public static PluginCommand[] create(@NotNull HuskSync plugin) {
|
||||||
|
return Arrays.stream(values()).map(type -> type.supply(plugin))
|
||||||
|
.filter(command -> !plugin.getSettings().isCommandDisabled(command))
|
||||||
|
.toArray(PluginCommand[]::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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.command;
|
|
||||||
|
|
||||||
import net.william278.husksync.user.CommandUser;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface TabProvider {
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
List<String> suggest(@NotNull CommandUser user, @NotNull String[] args);
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
default List<String> getSuggestions(@NotNull CommandUser user, @NotNull String[] args) {
|
|
||||||
List<String> suggestions = suggest(user, args);
|
|
||||||
if (suggestions == null) {
|
|
||||||
suggestions = List.of();
|
|
||||||
}
|
|
||||||
return filter(suggestions, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
default List<String> filter(@NotNull List<String> suggestions, @NotNull String[] args) {
|
|
||||||
return suggestions.stream()
|
|
||||||
.filter(suggestion -> args.length == 0 || suggestion.toLowerCase()
|
|
||||||
.startsWith(args[args.length - 1].toLowerCase().trim()))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.command;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.exceptions.CommandSyntaxException;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.DataSnapshot;
|
import net.william278.husksync.data.DataSnapshot;
|
||||||
import net.william278.husksync.redis.RedisKeyType;
|
import net.william278.husksync.redis.RedisKeyType;
|
||||||
@@ -28,83 +29,32 @@ import net.william278.husksync.user.User;
|
|||||||
import net.william278.husksync.util.DataDumper;
|
import net.william278.husksync.util.DataDumper;
|
||||||
import net.william278.husksync.util.DataSnapshotList;
|
import net.william278.husksync.util.DataSnapshotList;
|
||||||
import net.william278.husksync.util.DataSnapshotOverview;
|
import net.william278.husksync.util.DataSnapshotOverview;
|
||||||
|
import net.william278.uniform.BaseCommand;
|
||||||
|
import net.william278.uniform.CommandProvider;
|
||||||
|
import net.william278.uniform.Permission;
|
||||||
|
import net.william278.uniform.element.ArgumentElement;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class UserDataCommand extends Command implements TabProvider {
|
public class UserDataCommand extends PluginCommand {
|
||||||
|
|
||||||
private static final Map<String, Boolean> SUB_COMMANDS = Map.of(
|
|
||||||
"view", false,
|
|
||||||
"list", false,
|
|
||||||
"delete", true,
|
|
||||||
"restore", true,
|
|
||||||
"pin", true,
|
|
||||||
"dump", true
|
|
||||||
);
|
|
||||||
|
|
||||||
public UserDataCommand(@NotNull HuskSync plugin) {
|
public UserDataCommand(@NotNull HuskSync plugin) {
|
||||||
super("userdata", List.of("playerdata"), String.format(
|
super("userdata", List.of("playerdata"), Permission.Default.IF_OP, ExecutionScope.ALL, plugin);
|
||||||
"<%s> [username] [version_uuid]", String.join("/", SUB_COMMANDS.keySet())
|
|
||||||
), plugin);
|
|
||||||
setOperatorCommand(true);
|
|
||||||
addAdditionalPermissions(SUB_COMMANDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void execute(@NotNull CommandUser executor, @NotNull String[] args) {
|
public void provide(@NotNull BaseCommand<?> command) {
|
||||||
final String subCommand = parseStringArg(args, 0).orElse("view").toLowerCase(Locale.ENGLISH);
|
command.addSubCommand("view", needsOp("view"), view());
|
||||||
final Optional<User> optionalUser = parseStringArg(args, 1)
|
command.addSubCommand("list", needsOp("list"), list());
|
||||||
.flatMap(name -> plugin.getDatabase().getUserByName(name))
|
command.addSubCommand("delete", needsOp("delete"), delete());
|
||||||
.or(() -> parseStringArg(args, 0).flatMap(name -> plugin.getDatabase().getUserByName(name)))
|
command.addSubCommand("restore", needsOp("restore"), restore());
|
||||||
.or(() -> args.length < 2 && executor instanceof User userExecutor
|
command.addSubCommand("pin", needsOp("pin"), pin());
|
||||||
? Optional.of(userExecutor) : Optional.empty());
|
command.addSubCommand("dump", needsOp("dump"), dump());
|
||||||
final Optional<UUID> uuid = parseUUIDArg(args, 2).or(() -> parseUUIDArg(args, 1));
|
|
||||||
if (optionalUser.isEmpty()) {
|
|
||||||
plugin.getLocales().getLocale("error_invalid_player")
|
|
||||||
.ifPresent(executor::sendMessage);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final User user = optionalUser.get();
|
|
||||||
switch (subCommand) {
|
|
||||||
case "view" -> uuid.ifPresentOrElse(
|
|
||||||
version -> viewSnapshot(executor, user, version),
|
|
||||||
() -> viewLatestSnapshot(executor, user)
|
|
||||||
);
|
|
||||||
case "list" -> listSnapshots(
|
|
||||||
executor, user, parseIntArg(args, 2).or(() -> parseIntArg(args, 1)).orElse(1)
|
|
||||||
);
|
|
||||||
case "delete" -> uuid.ifPresentOrElse(
|
|
||||||
version -> deleteSnapshot(executor, user, version),
|
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_syntax",
|
|
||||||
"/userdata delete <username> <version_uuid>")
|
|
||||||
.ifPresent(executor::sendMessage)
|
|
||||||
);
|
|
||||||
case "restore" -> uuid.ifPresentOrElse(
|
|
||||||
version -> restoreSnapshot(executor, user, version),
|
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_syntax",
|
|
||||||
"/userdata restore <username> <version_uuid>")
|
|
||||||
.ifPresent(executor::sendMessage)
|
|
||||||
);
|
|
||||||
case "pin" -> uuid.ifPresentOrElse(
|
|
||||||
version -> pinSnapshot(executor, user, version),
|
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_syntax",
|
|
||||||
"/userdata pin <username> <version_uuid>")
|
|
||||||
.ifPresent(executor::sendMessage)
|
|
||||||
);
|
|
||||||
case "dump" -> uuid.ifPresentOrElse(
|
|
||||||
version -> dumpSnapshot(executor, user, version, parseStringArg(args, 3)
|
|
||||||
.map(arg -> arg.equalsIgnoreCase("web")).orElse(false)),
|
|
||||||
() -> plugin.getLocales().getLocale("error_invalid_syntax",
|
|
||||||
"/userdata dump <username> <version_uuid> <web/file>")
|
|
||||||
.ifPresent(executor::sendMessage)
|
|
||||||
);
|
|
||||||
default -> plugin.getLocales().getLocale("error_invalid_syntax", getUsage())
|
|
||||||
.ifPresent(executor::sendMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the latest snapshot
|
// Show the latest snapshot
|
||||||
@@ -224,7 +174,8 @@ public class UserDataCommand extends Command implements TabProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Dump a snapshot
|
// Dump a snapshot
|
||||||
private void dumpSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version, boolean webDump) {
|
private void dumpSnapshot(@NotNull CommandUser executor, @NotNull User user, @NotNull UUID version,
|
||||||
|
@NotNull DumpType type) {
|
||||||
final Optional<DataSnapshot.Packed> data = plugin.getDatabase().getSnapshot(user, version);
|
final Optional<DataSnapshot.Packed> data = plugin.getDatabase().getSnapshot(user, version);
|
||||||
if (data.isEmpty()) {
|
if (data.isEmpty()) {
|
||||||
plugin.getLocales().getLocale("error_invalid_version_uuid")
|
plugin.getLocales().getLocale("error_invalid_version_uuid")
|
||||||
@@ -237,22 +188,99 @@ public class UserDataCommand extends Command implements TabProvider {
|
|||||||
final DataDumper dumper = DataDumper.create(userData, user, plugin);
|
final DataDumper dumper = DataDumper.create(userData, user, plugin);
|
||||||
try {
|
try {
|
||||||
plugin.getLocales().getLocale("data_dumped", userData.getShortId(), user.getUsername(),
|
plugin.getLocales().getLocale("data_dumped", userData.getShortId(), user.getUsername(),
|
||||||
(webDump ? dumper.toWeb() : dumper.toFile())).ifPresent(executor::sendMessage);
|
(type == DumpType.WEB ? dumper.toWeb() : dumper.toFile()))
|
||||||
|
.ifPresent(executor::sendMessage);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "Failed to dump user data", e);
|
plugin.log(Level.SEVERE, "Failed to dump user data", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@NotNull
|
||||||
@Override
|
private CommandProvider view() {
|
||||||
public List<String> suggest(@NotNull CommandUser executor, @NotNull String[] args) {
|
return (sub) -> {
|
||||||
return switch (args.length) {
|
sub.addSyntax((ctx) -> {
|
||||||
case 0, 1 -> SUB_COMMANDS.keySet().stream().sorted().toList();
|
final User user = ctx.getArgument("username", User.class);
|
||||||
case 2 -> plugin.getOnlineUsers().stream().map(User::getUsername).toList();
|
viewLatestSnapshot(user(sub, ctx), user);
|
||||||
case 4 -> parseStringArg(args, 0)
|
}, user("username"));
|
||||||
.map(arg -> arg.equalsIgnoreCase("dump") ? List.of("web", "file") : null)
|
sub.addSyntax((ctx) -> {
|
||||||
.orElse(null);
|
final User user = ctx.getArgument("username", User.class);
|
||||||
default -> null;
|
final UUID version = ctx.getArgument("version", UUID.class);
|
||||||
|
viewSnapshot(user(sub, ctx), user, version);
|
||||||
|
}, user("username"), uuid("version"));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider list() {
|
||||||
|
return (sub) -> {
|
||||||
|
sub.addSyntax((ctx) -> {
|
||||||
|
final User user = ctx.getArgument("username", User.class);
|
||||||
|
listSnapshots(user(sub, ctx), user, 1);
|
||||||
|
}, user("username"));
|
||||||
|
sub.addSyntax((ctx) -> {
|
||||||
|
final User user = ctx.getArgument("username", User.class);
|
||||||
|
final int page = ctx.getArgument("page", Integer.class);
|
||||||
|
listSnapshots(user(sub, ctx), user, page);
|
||||||
|
}, user("username"), BaseCommand.intNum("page", 1));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider delete() {
|
||||||
|
return (sub) -> sub.addSyntax((ctx) -> {
|
||||||
|
final User user = ctx.getArgument("username", User.class);
|
||||||
|
final UUID version = ctx.getArgument("version", UUID.class);
|
||||||
|
deleteSnapshot(user(sub, ctx), user, version);
|
||||||
|
}, user("username"), uuid("version"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider restore() {
|
||||||
|
return (sub) -> sub.addSyntax((ctx) -> {
|
||||||
|
final User user = ctx.getArgument("username", User.class);
|
||||||
|
final UUID version = ctx.getArgument("version", UUID.class);
|
||||||
|
restoreSnapshot(user(sub, ctx), user, version);
|
||||||
|
}, user("username"), uuid("version"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider pin() {
|
||||||
|
return (sub) -> sub.addSyntax((ctx) -> {
|
||||||
|
final User user = ctx.getArgument("username", User.class);
|
||||||
|
final UUID version = ctx.getArgument("version", UUID.class);
|
||||||
|
pinSnapshot(user(sub, ctx), user, version);
|
||||||
|
}, user("username"), uuid("version"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private CommandProvider dump() {
|
||||||
|
return (sub) -> sub.addSyntax((ctx) -> {
|
||||||
|
final User user = ctx.getArgument("username", User.class);
|
||||||
|
final UUID version = ctx.getArgument("version", UUID.class);
|
||||||
|
final DumpType type = ctx.getArgument("type", DumpType.class);
|
||||||
|
dumpSnapshot(user(sub, ctx), user, version, type);
|
||||||
|
}, user("username"), uuid("version"), dumpType());
|
||||||
|
}
|
||||||
|
|
||||||
|
private <S> ArgumentElement<S, DumpType> dumpType() {
|
||||||
|
return new ArgumentElement<>("type", reader -> {
|
||||||
|
final String type = reader.readString();
|
||||||
|
return switch (type.toLowerCase(Locale.ENGLISH)) {
|
||||||
|
case "web" -> DumpType.WEB;
|
||||||
|
case "file" -> DumpType.FILE;
|
||||||
|
default -> throw CommandSyntaxException.BUILT_IN_EXCEPTIONS
|
||||||
|
.dispatcherUnknownArgument().createWithContext(reader);
|
||||||
|
};
|
||||||
|
}, (context, builder) -> {
|
||||||
|
builder.suggest("web");
|
||||||
|
builder.suggest("file");
|
||||||
|
return builder.buildFuture();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DumpType {
|
||||||
|
WEB,
|
||||||
|
FILE
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -193,14 +193,20 @@ public class Locales {
|
|||||||
* Displays the notification in the action bar
|
* Displays the notification in the action bar
|
||||||
*/
|
*/
|
||||||
ACTION_BAR,
|
ACTION_BAR,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the notification in the chat
|
* Displays the notification in the chat
|
||||||
*/
|
*/
|
||||||
CHAT,
|
CHAT,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Displays the notification in an Advancement Toast
|
* Displays the notification in an Advancement Toast
|
||||||
|
*
|
||||||
|
* @deprecated No longer supported
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
TOAST,
|
TOAST,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does not display the notification
|
* Does not display the notification
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import de.exlll.configlib.Configuration;
|
|||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import net.william278.husksync.command.PluginCommand;
|
||||||
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.database.Database;
|
import net.william278.husksync.database.Database;
|
||||||
@@ -63,21 +64,21 @@ public class Settings {
|
|||||||
private boolean checkForUpdates = true;
|
private boolean checkForUpdates = true;
|
||||||
|
|
||||||
@Comment("Specify a common ID for grouping servers running HuskSync. "
|
@Comment("Specify a common ID for grouping servers running HuskSync. "
|
||||||
+ "Don't modify this unless you know what you're doing!")
|
+ "Don't modify this unless you know what you're doing!")
|
||||||
private String clusterId = "";
|
private String clusterId = "";
|
||||||
|
|
||||||
@Comment("Enable development debug logging")
|
@Comment("Enable development debug logging")
|
||||||
private boolean debugLogging = false;
|
private boolean debugLogging = false;
|
||||||
|
|
||||||
@Comment("Whether to provide modern, rich TAB suggestions for commands (if available)")
|
|
||||||
private boolean brigadierTabCompletion = false;
|
|
||||||
|
|
||||||
@Comment({"Whether to enable the Player Analytics hook.", "Docs: https://william278.net/docs/husksync/plan-hook"})
|
@Comment({"Whether to enable the Player Analytics hook.", "Docs: https://william278.net/docs/husksync/plan-hook"})
|
||||||
private boolean enablePlanHook = true;
|
private boolean enablePlanHook = true;
|
||||||
|
|
||||||
@Comment("Whether to cancel game event packets directly when handling locked players if ProtocolLib or PacketEvents is installed")
|
@Comment("Whether to cancel game event packets directly when handling locked players if ProtocolLib or PacketEvents is installed")
|
||||||
private boolean cancelPackets = true;
|
private boolean cancelPackets = true;
|
||||||
|
|
||||||
|
@Comment("Add HuskSync commands to this list to prevent them from being registered (e.g. ['userdata'])")
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private List<String> disabledCommands = Lists.newArrayList();
|
||||||
|
|
||||||
// Database settings
|
// Database settings
|
||||||
@Comment("Database settings")
|
@Comment("Database settings")
|
||||||
@@ -185,7 +186,7 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Synchronization settings
|
// Synchronization settings
|
||||||
@Comment("Redis settings")
|
@Comment("Data syncing settings")
|
||||||
private SynchronizationSettings synchronization = new SynchronizationSettings();
|
private SynchronizationSettings synchronization = new SynchronizationSettings();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -228,7 +229,7 @@ public class Settings {
|
|||||||
private boolean enabled = false;
|
private boolean enabled = false;
|
||||||
|
|
||||||
@Comment("What items to save in death snapshots? (DROPS or ITEMS_TO_KEEP). "
|
@Comment("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.")
|
+ "Note that ITEMS_TO_KEEP (suggested for keepInventory servers) requires a Paper 1.19.4+ server.")
|
||||||
private DeathItemsMode itemsToSave = DeathItemsMode.DROPS;
|
private DeathItemsMode itemsToSave = DeathItemsMode.DROPS;
|
||||||
|
|
||||||
@Comment("Should a death snapshot still be created even if the items to save on the player's death are empty?")
|
@Comment("Should a death snapshot still be created even if the items to save on the player's death are empty?")
|
||||||
@@ -249,14 +250,14 @@ public class Settings {
|
|||||||
@Comment("Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing")
|
@Comment("Whether to use the snappy data compression algorithm. Keep on unless you know what you're doing")
|
||||||
private boolean compressData = true;
|
private boolean compressData = true;
|
||||||
|
|
||||||
@Comment("Where to display sync notifications (ACTION_BAR, CHAT, TOAST or NONE)")
|
@Comment("Where to display sync notifications (ACTION_BAR, CHAT or NONE)")
|
||||||
private Locales.NotificationSlot notificationDisplaySlot = Locales.NotificationSlot.ACTION_BAR;
|
private Locales.NotificationSlot notificationDisplaySlot = Locales.NotificationSlot.ACTION_BAR;
|
||||||
|
|
||||||
@Comment("Persist maps locked in a Cartography Table to let them be viewed on any server")
|
@Comment("Persist maps locked in a Cartography Table to let them be viewed on any server")
|
||||||
private boolean persistLockedMaps = true;
|
private boolean persistLockedMaps = true;
|
||||||
|
|
||||||
@Comment("If using the DELAY sync method, how long should this server listen for Redis key data updates before "
|
@Comment("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).")
|
+ "pulling data from the database instead (i.e., if the user did not change servers).")
|
||||||
private int networkLatencyMilliseconds = 500;
|
private int networkLatencyMilliseconds = 500;
|
||||||
|
|
||||||
@Comment({"Which data types to synchronize.", "Docs: https://william278.net/docs/husksync/sync-features"})
|
@Comment({"Which data types to synchronize.", "Docs: https://william278.net/docs/husksync/sync-features"})
|
||||||
@@ -266,10 +267,45 @@ public class Settings {
|
|||||||
@Comment("Commands which should be blocked before a player has finished syncing (Use * to block all commands)")
|
@Comment("Commands which should be blocked before a player has finished syncing (Use * to block all commands)")
|
||||||
private List<String> blacklistedCommandsWhileLocked = new ArrayList<>(List.of("*"));
|
private List<String> blacklistedCommandsWhileLocked = new ArrayList<>(List.of("*"));
|
||||||
|
|
||||||
@Comment({"For attribute syncing, which attributes should be ignored/skipped when syncing",
|
@Comment("Configuration for how to sync attributes")
|
||||||
"(e.g. ['minecraft:generic.max_health', 'minecraft:generic.attack_damage'])"})
|
private AttributeSettings attributes = new AttributeSettings();
|
||||||
@Getter(AccessLevel.NONE)
|
|
||||||
private List<String> ignoredAttributes = new ArrayList<>(List.of(""));
|
@Getter
|
||||||
|
@Configuration
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public static class AttributeSettings {
|
||||||
|
|
||||||
|
@Comment({"Which attributes should not be saved when syncing users. Supports wildcard matching.",
|
||||||
|
"(e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])"})
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private List<String> ignoredAttributes = new ArrayList<>(List.of(""));
|
||||||
|
|
||||||
|
@Comment({"Which modifiers should not be saved when syncing users. Supports wildcard matching.",
|
||||||
|
"(e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])"})
|
||||||
|
@Getter(AccessLevel.NONE)
|
||||||
|
private List<String> ignoredModifiers = new ArrayList<>(List.of(
|
||||||
|
"minecraft:effect.*", "minecraft:creative_mode_*"
|
||||||
|
));
|
||||||
|
|
||||||
|
private boolean matchesWildcard(@NotNull String pat, @NotNull String value) {
|
||||||
|
if (!pat.contains(":")) {
|
||||||
|
pat = "minecraft:%s".formatted(pat);
|
||||||
|
}
|
||||||
|
if (!value.contains(":")) {
|
||||||
|
value = "minecraft:%s".formatted(value);
|
||||||
|
}
|
||||||
|
return pat.contains("*") ? value.matches(pat.replace("*", ".*")) : pat.equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoredAttribute(@NotNull String attribute) {
|
||||||
|
return ignoredAttributes.stream().anyMatch(wildcard -> matchesWildcard(wildcard, attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIgnoredModifier(@NotNull String modifier) {
|
||||||
|
return ignoredModifiers.stream().anyMatch(wildcard -> matchesWildcard(wildcard, modifier));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Comment("Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts")
|
@Comment("Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts")
|
||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
@@ -283,10 +319,6 @@ public class Settings {
|
|||||||
return id.isCustom() || features.getOrDefault(id.getKeyValue(), id.isEnabledByDefault());
|
return id.isCustom() || features.getOrDefault(id.getKeyValue(), id.isEnabledByDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isIgnoredAttribute(@NotNull String attribute) {
|
|
||||||
return ignoredAttributes.contains(attribute);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public EventListener.Priority getEventPriority(@NotNull EventListener.ListenerType type) {
|
public EventListener.Priority getEventPriority(@NotNull EventListener.ListenerType type) {
|
||||||
try {
|
try {
|
||||||
@@ -297,4 +329,10 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isCommandDisabled(@NotNull PluginCommand command) {
|
||||||
|
return disabledCommands.stream().map(c -> c.startsWith("/") ? c.substring(1) : c)
|
||||||
|
.anyMatch(c -> c.equalsIgnoreCase(command.getName()) || command.getAliases().contains(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -155,14 +155,14 @@ public interface Data {
|
|||||||
*/
|
*/
|
||||||
interface Advancements extends Data {
|
interface Advancements extends Data {
|
||||||
|
|
||||||
|
String RECIPE_ADVANCEMENT = "minecraft:recipe";
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
List<Advancement> getCompleted();
|
List<Advancement> getCompleted();
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
default List<Advancement> getCompletedExcludingRecipes() {
|
default List<Advancement> getCompletedExcludingRecipes() {
|
||||||
return getCompleted().stream()
|
return getCompleted().stream().filter(adv -> !adv.getKey().startsWith(RECIPE_ADVANCEMENT)).toList();
|
||||||
.filter(advancement -> !advancement.getKey().startsWith("minecraft:recipe"))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setCompleted(@NotNull List<Advancement> completed);
|
void setCompleted(@NotNull List<Advancement> completed);
|
||||||
@@ -191,13 +191,13 @@ public interface Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private static Map<String, Long> adaptDateMap(@NotNull Map<String, Date> dateMap) {
|
private static Map<String, Long> adaptDateMap(@NotNull Map<String, Date> dateMap) {
|
||||||
return dateMap.entrySet().stream()
|
return dateMap.entrySet().stream()
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getTime()));
|
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getTime()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static Map<String, Date> adaptLongMap(@NotNull Map<String, Long> dateMap) {
|
private static Map<String, Date> adaptLongMap(@NotNull Map<String, Long> dateMap) {
|
||||||
return dateMap.entrySet().stream()
|
return dateMap.entrySet().stream()
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> new Date(e.getValue())));
|
.collect(Collectors.toMap(Map.Entry::getKey, e -> new Date(e.getValue())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -250,9 +250,9 @@ public interface Data {
|
|||||||
void setWorld(@NotNull World world);
|
void setWorld(@NotNull World world);
|
||||||
|
|
||||||
record World(
|
record World(
|
||||||
@SerializedName("name") @NotNull String name,
|
@SerializedName("name") @NotNull String name,
|
||||||
@SerializedName("uuid") @NotNull UUID uuid,
|
@SerializedName("uuid") @NotNull UUID uuid,
|
||||||
@SerializedName("environment") @NotNull String environment
|
@SerializedName("environment") @NotNull String environment
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,9 +324,9 @@ public interface Data {
|
|||||||
List<Attribute> getAttributes();
|
List<Attribute> getAttributes();
|
||||||
|
|
||||||
record Attribute(
|
record Attribute(
|
||||||
@NotNull String name,
|
@NotNull String name,
|
||||||
double baseValue,
|
double baseValue,
|
||||||
@NotNull Set<Modifier> modifiers
|
@NotNull Set<Modifier> modifiers
|
||||||
) {
|
) {
|
||||||
|
|
||||||
public double getValue() {
|
public double getValue() {
|
||||||
@@ -366,7 +366,13 @@ public interface Data {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object obj) {
|
||||||
return obj instanceof Modifier modifier && modifier.uuid().equals(uuid());
|
if (obj instanceof Modifier other) {
|
||||||
|
if (uuid == null || other.uuid == null) {
|
||||||
|
return name.equals(other.name);
|
||||||
|
}
|
||||||
|
return uuid.equals(other.uuid);
|
||||||
|
}
|
||||||
|
return super.equals(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double modify(double value) {
|
public double modify(double value) {
|
||||||
@@ -378,6 +384,10 @@ public interface Data {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasUuid() {
|
||||||
|
return uuid != null;
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public UUID uuid() {
|
public UUID uuid() {
|
||||||
return uuid != null ? uuid : UUID.nameUUIDFromBytes(name.getBytes());
|
return uuid != null ? uuid : UUID.nameUUIDFromBytes(name.getBytes());
|
||||||
@@ -387,8 +397,8 @@ public interface Data {
|
|||||||
|
|
||||||
default Optional<Attribute> getAttribute(@NotNull Key key) {
|
default Optional<Attribute> getAttribute(@NotNull Key key) {
|
||||||
return getAttributes().stream()
|
return getAttributes().stream()
|
||||||
.filter(attribute -> attribute.name().equals(key.asString()))
|
.filter(attribute -> attribute.name().equals(key.asString()))
|
||||||
.findFirst();
|
.findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
default void removeAttribute(@NotNull Key key) {
|
default void removeAttribute(@NotNull Key key) {
|
||||||
@@ -397,8 +407,8 @@ public interface Data {
|
|||||||
|
|
||||||
default double getMaxHealth() {
|
default double getMaxHealth() {
|
||||||
return getAttribute(MAX_HEALTH_KEY)
|
return getAttribute(MAX_HEALTH_KEY)
|
||||||
.map(Attribute::getValue)
|
.map(Attribute::getValue)
|
||||||
.orElse(20.0);
|
.orElse(20.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
default void setMaxHealth(double maxHealth) {
|
default void setMaxHealth(double maxHealth) {
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ public class DataException extends IllegalStateException {
|
|||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public enum Reason {
|
public enum Reason {
|
||||||
INVALID_MINECRAFT_VERSION((plugin, snapshot) -> String.format("The Minecraft version of the snapshot (%s) is " +
|
INVALID_MINECRAFT_VERSION((plugin, snapshot) -> String.format("The Minecraft version of the snapshot (%s) is " +
|
||||||
"newer than the server's version (%s). Ensure each server is on the same version of Minecraft.",
|
"newer than the server's version (%s). Ensure each server is on the same version of Minecraft.",
|
||||||
snapshot.getMinecraftVersion(), plugin.getMinecraftVersion())),
|
snapshot.getMinecraftVersion(), plugin.getMinecraftVersion())),
|
||||||
INVALID_FORMAT_VERSION((plugin, snapshot) -> String.format("The format version of the snapshot (%s) is newer " +
|
INVALID_FORMAT_VERSION((plugin, snapshot) -> String.format("The format version of the snapshot (%s) is newer " +
|
||||||
"than the server's version (%s). Ensure each server is running the same version of HuskSync.",
|
"than the server's version (%s). Ensure each server is running the same version of HuskSync.",
|
||||||
snapshot.getFormatVersion(), DataSnapshot.CURRENT_FORMAT_VERSION)),
|
snapshot.getFormatVersion(), DataSnapshot.CURRENT_FORMAT_VERSION)),
|
||||||
INVALID_PLATFORM_TYPE((plugin, snapshot) -> String.format("The platform type of the snapshot (%s) does " +
|
INVALID_PLATFORM_TYPE((plugin, snapshot) -> String.format("The platform type of the snapshot (%s) does " +
|
||||||
"not match the server's platform type (%s). Ensure each server has the same platform type.",
|
"not match the server's platform type (%s). Ensure each server has the same platform type.",
|
||||||
snapshot.getPlatformType(), plugin.getPlatformType())),
|
snapshot.getPlatformType(), plugin.getPlatformType())),
|
||||||
NO_LEGACY_CONVERTER((plugin, snapshot) -> String.format("No legacy converter to convert format version: %s",
|
NO_LEGACY_CONVERTER((plugin, snapshot) -> String.format("No legacy converter to convert format version: %s",
|
||||||
snapshot.getFormatVersion()));
|
snapshot.getFormatVersion()));
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ public class DataSnapshot {
|
|||||||
.map(entry -> Map.entry(plugin.getIdentifier(entry.getKey()).orElseThrow(), entry.getValue()))
|
.map(entry -> Map.entry(plugin.getIdentifier(entry.getKey()).orElseThrow(), entry.getValue()))
|
||||||
.collect(Collectors.toMap(
|
.collect(Collectors.toMap(
|
||||||
Map.Entry::getKey,
|
Map.Entry::getKey,
|
||||||
entry -> plugin.deserializeData(entry.getKey(), entry.getValue()),
|
entry -> plugin.deserializeData(entry.getKey(), entry.getValue(), getMinecraftVersion()),
|
||||||
(a, b) -> b, () -> Maps.newTreeMap(SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR)
|
(a, b) -> b, () -> Maps.newTreeMap(SerializerRegistry.DEPENDENCY_ORDER_COMPARATOR)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -535,9 +535,9 @@ public class DataSnapshot {
|
|||||||
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"
|
+ "Make sure your database server time matches the server time.\n"
|
||||||
+ "Current game server timestamp: " + OffsetDateTime.now() + " / "
|
+ "Current game server timestamp: " + OffsetDateTime.now() + " / "
|
||||||
+ "Snapshot timestamp: " + timestamp);
|
+ "Snapshot timestamp: " + timestamp);
|
||||||
}
|
}
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
return this;
|
return this;
|
||||||
|
|||||||
@@ -45,12 +45,13 @@ public class Identifier {
|
|||||||
public static final Identifier ADVANCEMENTS = huskSync("advancements", true);
|
public static final Identifier ADVANCEMENTS = huskSync("advancements", true);
|
||||||
public static final Identifier STATISTICS = huskSync("statistics", true);
|
public static final Identifier STATISTICS = huskSync("statistics", true);
|
||||||
public static final Identifier POTION_EFFECTS = huskSync("potion_effects", true);
|
public static final Identifier POTION_EFFECTS = huskSync("potion_effects", true);
|
||||||
public static final Identifier GAME_MODE = huskSync("game_mode", false);
|
public static final Identifier GAME_MODE = huskSync("game_mode", true);
|
||||||
public static final Identifier FLIGHT_STATUS = huskSync("flight_status", true,
|
public static final Identifier FLIGHT_STATUS = huskSync("flight_status", true,
|
||||||
Dependency.optional("game_mode")
|
Dependency.optional("game_mode")
|
||||||
);
|
);
|
||||||
public static final Identifier ATTRIBUTES = huskSync("attributes", true,
|
public static final Identifier ATTRIBUTES = huskSync("attributes", true,
|
||||||
Dependency.required("potion_effects")
|
Dependency.optional("inventory"),
|
||||||
|
Dependency.optional("potion_effects")
|
||||||
);
|
);
|
||||||
public static final Identifier HEALTH = huskSync("health", true,
|
public static final Identifier HEALTH = huskSync("health", true,
|
||||||
Dependency.optional("attributes")
|
Dependency.optional("attributes")
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
package net.william278.husksync.data;
|
package net.william278.husksync.data;
|
||||||
|
|
||||||
|
import net.william278.desertwell.util.Version;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -119,19 +120,36 @@ public interface SerializerRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize data for the given {@link Identifier}
|
* Deserialize data of a given {@link Version Minecraft version} for the given {@link Identifier data identifier}
|
||||||
|
*
|
||||||
|
* @param identifier the {@link Identifier} to deserialize data for
|
||||||
|
* @param data the data to deserialize
|
||||||
|
* @param dataMcVersion the Minecraft version of the data
|
||||||
|
* @return the deserialized data
|
||||||
|
* @throws IllegalStateException if no serializer is found for the given {@link Identifier}
|
||||||
|
* @since 3.6.4
|
||||||
|
*/
|
||||||
|
@NotNull
|
||||||
|
default Data deserializeData(@NotNull Identifier identifier, @NotNull String data,
|
||||||
|
@NotNull Version dataMcVersion) throws IllegalStateException {
|
||||||
|
return getSerializer(identifier).map(serializer -> serializer.deserialize(data, dataMcVersion)).orElseThrow(
|
||||||
|
() -> new IllegalStateException("No serializer found for %s".formatted(identifier))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deserialize data for the given {@link Identifier data identifier}
|
||||||
*
|
*
|
||||||
* @param identifier the {@link Identifier} to deserialize data for
|
* @param identifier the {@link Identifier} to deserialize data for
|
||||||
* @param data the data to deserialize
|
* @param data the data to deserialize
|
||||||
* @return the deserialized data
|
* @return the deserialized data
|
||||||
* @throws IllegalStateException if no serializer is found for the given {@link Identifier}
|
|
||||||
* @since 3.5.4
|
* @since 3.5.4
|
||||||
|
* @deprecated Use {@link #deserializeData(Identifier, String, Version)} instead
|
||||||
*/
|
*/
|
||||||
@NotNull
|
@NotNull
|
||||||
default Data deserializeData(@NotNull Identifier identifier, @NotNull String data) throws IllegalStateException {
|
@Deprecated(since = "3.6.5")
|
||||||
return getSerializer(identifier).map(serializer -> serializer.deserialize(data)).orElseThrow(
|
default Data deserializeData(@NotNull Identifier identifier, @NotNull String data) {
|
||||||
() -> new IllegalStateException("No serializer found for %s".formatted(identifier))
|
return deserializeData(identifier, data, getPlugin().getMinecraftVersion());
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
|
|
||||||
private final String usersTable;
|
private final String usersTable;
|
||||||
private final String userDataTable;
|
private final String userDataTable;
|
||||||
|
|
||||||
public MongoDbDatabase(@NotNull HuskSync plugin) {
|
public MongoDbDatabase(@NotNull HuskSync plugin) {
|
||||||
super(plugin);
|
super(plugin);
|
||||||
this.usersTable = plugin.getSettings().getDatabase().getTableName(TableName.USERS);
|
this.usersTable = plugin.getSettings().getDatabase().getTableName(TableName.USERS);
|
||||||
@@ -76,7 +77,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +108,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
if (!existingUser.getUsername().equals(user.getUsername())) {
|
if (!existingUser.getUsername().equals(user.getUsername())) {
|
||||||
// Update a user's name if it has changed in the database
|
// Update a user's name if it has changed in the database
|
||||||
try {
|
try {
|
||||||
Document filter = new Document("uuid", existingUser.getUuid().toString());
|
Document filter = new Document("uuid", existingUser.getUuid());
|
||||||
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
||||||
if (doc == null) {
|
if (doc == null) {
|
||||||
throw new MongoException("User document returned null!");
|
throw new MongoException("User document returned null!");
|
||||||
@@ -123,7 +124,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
() -> {
|
() -> {
|
||||||
// Insert new player data into the database
|
// Insert new player data into the database
|
||||||
try {
|
try {
|
||||||
Document doc = new Document("uuid", user.getUuid().toString()).append("username", user.getUsername());
|
Document doc = new Document("uuid", user.getUuid()).append("username", user.getUsername());
|
||||||
mongoCollectionHelper.insertDocument(usersTable, doc);
|
mongoCollectionHelper.insertDocument(usersTable, doc);
|
||||||
} catch (MongoException e) {
|
} catch (MongoException e) {
|
||||||
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
plugin.log(Level.SEVERE, "Failed to insert a user into the database", e);
|
||||||
@@ -148,8 +149,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
Document filter = new Document("uuid", uuid);
|
Document filter = new Document("uuid", uuid);
|
||||||
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
||||||
if (doc != null) {
|
if (doc != null) {
|
||||||
return Optional.of(new User(UUID.fromString(doc.getString("uuid")),
|
return Optional.of(new User(uuid, doc.getString("username")));
|
||||||
doc.getString("username")));
|
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
} catch (MongoException e) {
|
} catch (MongoException e) {
|
||||||
@@ -171,7 +171,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
Document filter = new Document("username", username);
|
Document filter = new Document("username", username);
|
||||||
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
Document doc = mongoCollectionHelper.getCollection(usersTable).find(filter).first();
|
||||||
if (doc != null) {
|
if (doc != null) {
|
||||||
return Optional.of(new User(UUID.fromString(doc.getString("uuid")),
|
return Optional.of(new User(doc.get("uuid", UUID.class),
|
||||||
doc.getString("username")));
|
doc.getString("username")));
|
||||||
}
|
}
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
@@ -191,12 +191,12 @@ public class MongoDbDatabase extends Database {
|
|||||||
@Override
|
@Override
|
||||||
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
public Optional<DataSnapshot.Packed> getLatestSnapshot(@NotNull User user) {
|
||||||
try {
|
try {
|
||||||
Document filter = new Document("player_uuid", user.getUuid().toString());
|
Document filter = new Document("player_uuid", user.getUuid());
|
||||||
Document sort = new Document("timestamp", -1); // -1 = Descending
|
Document sort = new Document("timestamp", -1); // -1 = Descending
|
||||||
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
||||||
Document doc = iterable.first();
|
Document doc = iterable.first();
|
||||||
if (doc != null) {
|
if (doc != null) {
|
||||||
final UUID versionUuid = UUID.fromString(doc.getString("version_uuid"));
|
final UUID versionUuid = doc.get("version_uuid", UUID.class);
|
||||||
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId());
|
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId());
|
||||||
final Binary bin = doc.get("data", Binary.class);
|
final Binary bin = doc.get("data", Binary.class);
|
||||||
final byte[] dataByteArray = bin.getData();
|
final byte[] dataByteArray = bin.getData();
|
||||||
@@ -221,11 +221,11 @@ public class MongoDbDatabase extends Database {
|
|||||||
public List<DataSnapshot.Packed> getAllSnapshots(@NotNull User user) {
|
public List<DataSnapshot.Packed> getAllSnapshots(@NotNull User user) {
|
||||||
try {
|
try {
|
||||||
final List<DataSnapshot.Packed> retrievedData = Lists.newArrayList();
|
final List<DataSnapshot.Packed> retrievedData = Lists.newArrayList();
|
||||||
Document filter = new Document("player_uuid", user.getUuid().toString());
|
Document filter = new Document("player_uuid", user.getUuid());
|
||||||
Document sort = new Document("timestamp", -1); // -1 = Descending
|
Document sort = new Document("timestamp", -1); // -1 = Descending
|
||||||
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
||||||
for (Document doc : iterable) {
|
for (Document doc : iterable) {
|
||||||
final UUID versionUuid = UUID.fromString(doc.getString("version_uuid"));
|
final UUID versionUuid = doc.get("version_uuid", UUID.class);
|
||||||
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId());
|
final OffsetDateTime timestamp = OffsetDateTime.ofInstant(Instant.ofEpochMilli((long) doc.get("timestamp")), TimeZone.getDefault().toZoneId());
|
||||||
final Binary bin = doc.get("data", Binary.class);
|
final Binary bin = doc.get("data", Binary.class);
|
||||||
final byte[] dataByteArray = bin.getData();
|
final byte[] dataByteArray = bin.getData();
|
||||||
@@ -249,7 +249,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
@Override
|
@Override
|
||||||
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
public Optional<DataSnapshot.Packed> getSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
try {
|
try {
|
||||||
Document filter = new Document("player_uuid", user.getUuid().toString()).append("version_uuid", versionUuid.toString());
|
Document filter = new Document("player_uuid", user.getUuid()).append("version_uuid", versionUuid);
|
||||||
Document sort = new Document("timestamp", -1); // -1 = Descending
|
Document sort = new Document("timestamp", -1); // -1 = Descending
|
||||||
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable).find(filter).sort(sort);
|
||||||
Document doc = iterable.first();
|
Document doc = iterable.first();
|
||||||
@@ -281,7 +281,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots();
|
final int maxSnapshots = plugin.getSettings().getSynchronization().getMaxUserDataSnapshots();
|
||||||
if (unpinnedUserData.size() > maxSnapshots) {
|
if (unpinnedUserData.size() > maxSnapshots) {
|
||||||
|
|
||||||
Document filter = new Document("player_uuid", user.getUuid().toString()).append("pinned", false);
|
Document filter = new Document("player_uuid", user.getUuid()).append("pinned", false);
|
||||||
Document sort = new Document("timestamp", 1); // 1 = Ascending
|
Document sort = new Document("timestamp", 1); // 1 = Ascending
|
||||||
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable)
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable)
|
||||||
.find(filter)
|
.find(filter)
|
||||||
@@ -307,7 +307,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
@Override
|
@Override
|
||||||
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
public boolean deleteSnapshot(@NotNull User user, @NotNull UUID versionUuid) {
|
||||||
try {
|
try {
|
||||||
Document filter = new Document("player_uuid", user.getUuid().toString()).append("version_uuid", versionUuid.toString());
|
Document filter = new Document("player_uuid", user.getUuid()).append("version_uuid", versionUuid);
|
||||||
Document doc = mongoCollectionHelper.getCollection(userDataTable).find(filter).first();
|
Document doc = mongoCollectionHelper.getCollection(userDataTable).find(filter).first();
|
||||||
if (doc == null) {
|
if (doc == null) {
|
||||||
return false;
|
return false;
|
||||||
@@ -332,7 +332,7 @@ public class MongoDbDatabase extends Database {
|
|||||||
@Override
|
@Override
|
||||||
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
|
protected void rotateLatestSnapshot(@NotNull User user, @NotNull OffsetDateTime within) {
|
||||||
try {
|
try {
|
||||||
Document filter = new Document("player_uuid", user.getUuid().toString()).append("pinned", false);
|
Document filter = new Document("player_uuid", user.getUuid()).append("pinned", false);
|
||||||
Document sort = new Document("timestamp", 1); // 1 = Ascending
|
Document sort = new Document("timestamp", 1); // 1 = Ascending
|
||||||
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable)
|
FindIterable<Document> iterable = mongoCollectionHelper.getCollection(userDataTable)
|
||||||
.find(filter)
|
.find(filter)
|
||||||
@@ -362,8 +362,8 @@ public class MongoDbDatabase extends Database {
|
|||||||
@Override
|
@Override
|
||||||
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
protected void createSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
try {
|
try {
|
||||||
Document doc = new Document("player_uuid", user.getUuid().toString())
|
Document doc = new Document("player_uuid", user.getUuid())
|
||||||
.append("version_uuid", data.getId().toString())
|
.append("version_uuid", data.getId())
|
||||||
.append("timestamp", data.getTimestamp().toInstant().toEpochMilli())
|
.append("timestamp", data.getTimestamp().toInstant().toEpochMilli())
|
||||||
.append("save_cause", data.getSaveCause().name())
|
.append("save_cause", data.getSaveCause().name())
|
||||||
.append("pinned", data.isPinned())
|
.append("pinned", data.isPinned())
|
||||||
@@ -377,14 +377,14 @@ public class MongoDbDatabase extends Database {
|
|||||||
/**
|
/**
|
||||||
* Update a saved {@link DataSnapshot} by given version UUID
|
* Update a saved {@link DataSnapshot} by given version UUID
|
||||||
*
|
*
|
||||||
* @param user The user whose data snapshot
|
* @param user The user whose data snapshot
|
||||||
* @param data The {@link DataSnapshot} to update
|
* @param data The {@link DataSnapshot} to update
|
||||||
*/
|
*/
|
||||||
@Blocking
|
@Blocking
|
||||||
@Override
|
@Override
|
||||||
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
public void updateSnapshot(@NotNull User user, @NotNull DataSnapshot.Packed data) {
|
||||||
try {
|
try {
|
||||||
Document doc = new Document("player_uuid", user.getUuid().toString()).append("version_uuid", data.getId().toString());
|
Document doc = new Document("player_uuid", user.getUuid()).append("version_uuid", data.getId());
|
||||||
Bson updates = Updates.combine(
|
Bson updates = Updates.combine(
|
||||||
Updates.set("save_cause", data.getSaveCause().name()),
|
Updates.set("save_cause", data.getSaveCause().name()),
|
||||||
Updates.set("pinned", data.isPinned()),
|
Updates.set("pinned", data.isPinned()),
|
||||||
|
|||||||
@@ -124,11 +124,11 @@ public class MySqlDatabase extends Database {
|
|||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new IllegalStateException("Failed to create database tables. Please ensure you are running MySQL v8.0+ " +
|
throw new IllegalStateException("Failed to create database tables. Please ensure you are running MySQL v8.0+ " +
|
||||||
"and that your connecting user account has privileges to create tables.", e);
|
"and that your connecting user account has privileges to create tables.", e);
|
||||||
}
|
}
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IOException e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the MySQL database. " +
|
throw new IllegalStateException("Failed to establish a connection to the MySQL database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -123,11 +123,11 @@ public class PostgresDatabase extends Database {
|
|||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new IllegalStateException("Failed to create database tables. Please ensure you are running PostgreSQL " +
|
throw new IllegalStateException("Failed to create database tables. Please ensure you are running PostgreSQL " +
|
||||||
"and that your connecting user account has privileges to create tables.", e);
|
"and that your connecting user account has privileges to create tables.", e);
|
||||||
}
|
}
|
||||||
} catch (SQLException | IOException e) {
|
} catch (SQLException | IOException e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the PostgreSQL database. " +
|
throw new IllegalStateException("Failed to establish a connection to the PostgreSQL database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the collection helper
|
* Initialize the collection helper
|
||||||
|
*
|
||||||
* @param database Instance of {@link MongoConnectionHandler}
|
* @param database Instance of {@link MongoConnectionHandler}
|
||||||
*/
|
*/
|
||||||
public MongoCollectionHelper(@NotNull MongoConnectionHandler database) {
|
public MongoCollectionHelper(@NotNull MongoConnectionHandler database) {
|
||||||
@@ -37,6 +38,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a collection
|
* Create a collection
|
||||||
|
*
|
||||||
* @param collectionName the collection name
|
* @param collectionName the collection name
|
||||||
*/
|
*/
|
||||||
public void createCollection(@NotNull String collectionName) {
|
public void createCollection(@NotNull String collectionName) {
|
||||||
@@ -45,6 +47,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a collection
|
* Delete a collection
|
||||||
|
*
|
||||||
* @param collectionName the collection name
|
* @param collectionName the collection name
|
||||||
*/
|
*/
|
||||||
public void deleteCollection(@NotNull String collectionName) {
|
public void deleteCollection(@NotNull String collectionName) {
|
||||||
@@ -53,6 +56,7 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a collection
|
* Get a collection
|
||||||
|
*
|
||||||
* @param collectionName the collection name
|
* @param collectionName the collection name
|
||||||
* @return MongoCollection<Document>
|
* @return MongoCollection<Document>
|
||||||
*/
|
*/
|
||||||
@@ -62,8 +66,9 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a document to a collection
|
* Add a document to a collection
|
||||||
|
*
|
||||||
* @param collectionName collection to add to
|
* @param collectionName collection to add to
|
||||||
* @param document Document to add
|
* @param document Document to add
|
||||||
*/
|
*/
|
||||||
public void insertDocument(@NotNull String collectionName, @NotNull Document document) {
|
public void insertDocument(@NotNull String collectionName, @NotNull Document document) {
|
||||||
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
@@ -72,9 +77,10 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a document
|
* Update a document
|
||||||
|
*
|
||||||
* @param collectionName collection the document is in
|
* @param collectionName collection the document is in
|
||||||
* @param document filter of document
|
* @param document filter of document
|
||||||
* @param updates Bson of updates
|
* @param updates Bson of updates
|
||||||
*/
|
*/
|
||||||
public void updateDocument(@NotNull String collectionName, @NotNull Document document, @NotNull Bson updates) {
|
public void updateDocument(@NotNull String collectionName, @NotNull Document document, @NotNull Bson updates) {
|
||||||
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
@@ -83,8 +89,9 @@ public class MongoCollectionHelper {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a document
|
* Delete a document
|
||||||
|
*
|
||||||
* @param collectionName collection the document is in
|
* @param collectionName collection the document is in
|
||||||
* @param document filter to remove
|
* @param document filter to remove
|
||||||
*/
|
*/
|
||||||
public void deleteDocument(@NotNull String collectionName, @NotNull Document document) {
|
public void deleteDocument(@NotNull String collectionName, @NotNull Document document) {
|
||||||
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
MongoCollection<Document> collection = database.getDatabase().getCollection(collectionName);
|
||||||
|
|||||||
@@ -35,9 +35,10 @@ public class MongoConnectionHandler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Initiate a connection to a Mongo Server
|
* Initiate a connection to a Mongo Server
|
||||||
|
*
|
||||||
* @param uri The connection string
|
* @param uri The connection string
|
||||||
*/
|
*/
|
||||||
public MongoConnectionHandler(@NotNull ConnectionString uri, @NotNull String databaseName) {
|
public MongoConnectionHandler(@NotNull ConnectionString uri, @NotNull String databaseName) {
|
||||||
try {
|
try {
|
||||||
final MongoClientSettings settings = MongoClientSettings.builder()
|
final MongoClientSettings settings = MongoClientSettings.builder()
|
||||||
.applyConnectionString(uri)
|
.applyConnectionString(uri)
|
||||||
@@ -48,7 +49,7 @@ public class MongoConnectionHandler {
|
|||||||
this.database = mongoClient.getDatabase(databaseName);
|
this.database = mongoClient.getDatabase(databaseName);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
throw new IllegalStateException("Failed to establish a connection to the MongoDB database. " +
|
||||||
"Please check the supplied database credentials in the config file", e);
|
"Please check the supplied database credentials in the config file", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public abstract class EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.lockPlayer(user.getUuid());
|
plugin.lockPlayer(user.getUuid());
|
||||||
plugin.getDataSyncer().setUserData(user);
|
plugin.getDataSyncer().syncApplyUserData(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,7 +66,7 @@ public abstract class EventListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
plugin.lockPlayer(user.getUuid());
|
plugin.lockPlayer(user.getUuid());
|
||||||
plugin.getDataSyncer().saveUserData(user);
|
plugin.getDataSyncer().syncSaveUserData(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,7 +94,7 @@ public abstract class EventListener {
|
|||||||
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull Data.Items items) {
|
protected void saveOnPlayerDeath(@NotNull OnlineUser user, @NotNull Data.Items items) {
|
||||||
final SaveOnDeathSettings settings = plugin.getSettings().getSynchronization().getSaveOnDeath();
|
final SaveOnDeathSettings settings = plugin.getSettings().getSynchronization().getSaveOnDeath();
|
||||||
if (plugin.isDisabling() || !settings.isEnabled() || plugin.isLocked(user.getUuid())
|
if (plugin.isDisabling() || !settings.isEnabled() || plugin.isLocked(user.getUuid())
|
||||||
|| user.isNpc() || (!settings.isSaveEmptyItems() && items.isEmpty())) {
|
|| user.isNpc() || (!settings.isSaveEmptyItems() && items.isEmpty())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ public class RedisManager extends JedisPubSub {
|
|||||||
jedisPool.getResource().ping();
|
jedisPool.getResource().ping();
|
||||||
} catch (JedisException e) {
|
} catch (JedisException e) {
|
||||||
throw new IllegalStateException("Failed to establish connection with Redis. "
|
throw new IllegalStateException("Failed to establish connection with Redis. "
|
||||||
+ "Please check the supplied credentials in the config file", e);
|
+ "Please check the supplied credentials in the config file", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe using a thread (rather than a task)
|
// Subscribe using a thread (rather than a task)
|
||||||
@@ -281,16 +281,21 @@ public class RedisManager extends JedisPubSub {
|
|||||||
@Blocking
|
@Blocking
|
||||||
public void setUserCheckedOut(@NotNull User user, boolean checkedOut) {
|
public void setUserCheckedOut(@NotNull User user, boolean checkedOut) {
|
||||||
try (Jedis jedis = jedisPool.getResource()) {
|
try (Jedis jedis = jedisPool.getResource()) {
|
||||||
|
final String key = getKeyString(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId);
|
||||||
if (checkedOut) {
|
if (checkedOut) {
|
||||||
jedis.set(
|
jedis.set(
|
||||||
getKey(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId),
|
key.getBytes(StandardCharsets.UTF_8),
|
||||||
plugin.getServerName().getBytes(StandardCharsets.UTF_8)
|
plugin.getServerName().getBytes(StandardCharsets.UTF_8)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
jedis.del(getKey(RedisKeyType.DATA_CHECKOUT, user.getUuid(), clusterId));
|
if (jedis.del(key.getBytes(StandardCharsets.UTF_8)) == 0) {
|
||||||
|
plugin.debug(String.format("[%s] %s key not set on Redis when attempting removal (%s)",
|
||||||
|
user.getUsername(), RedisKeyType.DATA_CHECKOUT, key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
plugin.debug(String.format("[%s] %s %s key to/from Redis", user.getUsername(),
|
plugin.debug(String.format("[%s] %s %s key %s Redis (%s)", user.getUsername(),
|
||||||
checkedOut ? "Set" : "Removed", RedisKeyType.DATA_CHECKOUT));
|
checkedOut ? "Set" : "Removed", RedisKeyType.DATA_CHECKOUT, checkedOut ? "to" : "from", key));
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
plugin.log(Level.SEVERE, "An exception occurred setting checkout to", e);
|
plugin.log(Level.SEVERE, "An exception occurred setting checkout to", e);
|
||||||
}
|
}
|
||||||
@@ -418,7 +423,12 @@ public class RedisManager extends JedisPubSub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] getKey(@NotNull RedisKeyType keyType, @NotNull UUID uuid, @NotNull String clusterId) {
|
private static byte[] getKey(@NotNull RedisKeyType keyType, @NotNull UUID uuid, @NotNull String clusterId) {
|
||||||
return String.format("%s:%s", keyType.getKeyPrefix(clusterId), uuid).getBytes(StandardCharsets.UTF_8);
|
return getKeyString(keyType, uuid, clusterId).getBytes(StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static String getKeyString(@NotNull RedisKeyType keyType, @NotNull UUID uuid, @NotNull String clusterId) {
|
||||||
|
return String.format("%s:%s", keyType.getKeyPrefix(clusterId), uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,18 +81,18 @@ public abstract class DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a user's data should be fetched and applied to them
|
* Called when a user's data should be fetched and applied to them as part of a synchronization process
|
||||||
*
|
*
|
||||||
* @param user the user to fetch data for
|
* @param user the user to fetch data for
|
||||||
*/
|
*/
|
||||||
public abstract void setUserData(@NotNull OnlineUser user);
|
public abstract void syncApplyUserData(@NotNull OnlineUser user);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a user's data should be serialized and saved
|
* Called when a user's data should be serialized and saved as part of a synchronization process
|
||||||
*
|
*
|
||||||
* @param user the user to save
|
* @param user the user to save
|
||||||
*/
|
*/
|
||||||
public abstract void saveUserData(@NotNull OnlineUser user);
|
public abstract void syncSaveUserData(@NotNull OnlineUser user);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save a {@link DataSnapshot.Packed user's data snapshot} to the database,
|
* Save a {@link DataSnapshot.Packed user's data snapshot} to the database,
|
||||||
@@ -150,7 +150,7 @@ public abstract class DataSyncer {
|
|||||||
private long getMaxListenAttempts() {
|
private long getMaxListenAttempts() {
|
||||||
return BASE_LISTEN_ATTEMPTS + (
|
return BASE_LISTEN_ATTEMPTS + (
|
||||||
(Math.max(100, plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds()) / 1000)
|
(Math.max(100, plugin.getSettings().getSynchronization().getNetworkLatencyMilliseconds()) / 1000)
|
||||||
* 20 / LISTEN_DELAY
|
* 20 / LISTEN_DELAY
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ public class DelayDataSyncer extends DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUserData(@NotNull OnlineUser user) {
|
public void syncApplyUserData(@NotNull OnlineUser user) {
|
||||||
plugin.runAsyncDelayed(
|
plugin.runAsyncDelayed(
|
||||||
() -> {
|
() -> {
|
||||||
// Fetch from the database if the user isn't changing servers
|
// Fetch from the database if the user isn't changing servers
|
||||||
@@ -58,7 +58,7 @@ public class DelayDataSyncer extends DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveUserData(@NotNull OnlineUser onlineUser) {
|
public void syncSaveUserData(@NotNull OnlineUser onlineUser) {
|
||||||
plugin.runAsync(() -> {
|
plugin.runAsync(() -> {
|
||||||
getRedis().setUserServerSwitch(onlineUser);
|
getRedis().setUserServerSwitch(onlineUser);
|
||||||
saveData(
|
saveData(
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ public class LockstepDataSyncer extends DataSyncer {
|
|||||||
|
|
||||||
// Consume their data when they are checked in
|
// Consume their data when they are checked in
|
||||||
@Override
|
@Override
|
||||||
public void setUserData(@NotNull OnlineUser user) {
|
public void syncApplyUserData(@NotNull OnlineUser user) {
|
||||||
this.listenForRedisData(user, () -> {
|
this.listenForRedisData(user, () -> {
|
||||||
if (getRedis().getUserCheckedOut(user).isPresent()) {
|
if (getRedis().getUserCheckedOut(user).isPresent()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -58,7 +58,7 @@ public class LockstepDataSyncer extends DataSyncer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void saveUserData(@NotNull OnlineUser onlineUser) {
|
public void syncSaveUserData(@NotNull OnlineUser onlineUser) {
|
||||||
plugin.runAsync(() -> saveData(
|
plugin.runAsync(() -> saveData(
|
||||||
onlineUser, onlineUser.createSnapshot(DataSnapshot.SaveCause.DISCONNECT),
|
onlineUser, onlineUser.createSnapshot(DataSnapshot.SaveCause.DISCONNECT),
|
||||||
(user, data) -> {
|
(user, data) -> {
|
||||||
|
|||||||
@@ -42,4 +42,6 @@ public final class ConsoleUser implements CommandUser {
|
|||||||
public boolean hasPermission(@NotNull String permission) {
|
public boolean hasPermission(@NotNull String permission) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -89,7 +89,9 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo
|
|||||||
* @param description the description of the toast
|
* @param description the description of the toast
|
||||||
* @param iconMaterial the namespace-keyed material to use as an hasIcon of the toast
|
* @param iconMaterial the namespace-keyed material to use as an hasIcon of the toast
|
||||||
* @param backgroundType the background ("ToastType") of the toast
|
* @param backgroundType the background ("ToastType") of the toast
|
||||||
|
* @deprecated No longer supported
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
public abstract void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
public abstract void sendToast(@NotNull MineDown title, @NotNull MineDown description,
|
||||||
@NotNull String iconMaterial, @NotNull String backgroundType);
|
@NotNull String iconMaterial, @NotNull String backgroundType);
|
||||||
|
|
||||||
@@ -145,12 +147,6 @@ public abstract class OnlineUser extends User implements CommandUser, UserDataHo
|
|||||||
switch (plugin.getSettings().getSynchronization().getNotificationDisplaySlot()) {
|
switch (plugin.getSettings().getSynchronization().getNotificationDisplaySlot()) {
|
||||||
case CHAT -> cause.getCompletedLocale(plugin).ifPresent(this::sendMessage);
|
case CHAT -> cause.getCompletedLocale(plugin).ifPresent(this::sendMessage);
|
||||||
case ACTION_BAR -> cause.getCompletedLocale(plugin).ifPresent(this::sendActionBar);
|
case ACTION_BAR -> cause.getCompletedLocale(plugin).ifPresent(this::sendActionBar);
|
||||||
case TOAST -> cause.getCompletedLocale(plugin)
|
|
||||||
.ifPresent(locale -> this.sendToast(
|
|
||||||
locale, new MineDown(""),
|
|
||||||
"minecraft:bell",
|
|
||||||
"TASK"
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
plugin.fireEvent(
|
plugin.fireEvent(
|
||||||
plugin.getSyncCompleteEvent(this),
|
plugin.getSyncCompleteEvent(this),
|
||||||
|
|||||||
@@ -178,11 +178,11 @@ public class DataDumper {
|
|||||||
@NotNull
|
@NotNull
|
||||||
private String getFileName() {
|
private String getFileName() {
|
||||||
return new StringJoiner("_")
|
return new StringJoiner("_")
|
||||||
.add(user.getUsername())
|
.add(user.getUsername())
|
||||||
.add(snapshot.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")))
|
.add(snapshot.getTimestamp().format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss")))
|
||||||
.add(snapshot.getSaveCause().name().toLowerCase(Locale.ENGLISH))
|
.add(snapshot.getSaveCause().name().toLowerCase(Locale.ENGLISH))
|
||||||
.add(snapshot.getShortId())
|
.add(snapshot.getShortId())
|
||||||
+ ".json";
|
+ ".json";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ public class DataSnapshotOverview {
|
|||||||
.ifPresent(user::sendMessage);
|
.ifPresent(user::sendMessage);
|
||||||
|
|
||||||
if (user.hasPermission("husksync.command.inventory.edit")
|
if (user.hasPermission("husksync.command.inventory.edit")
|
||||||
&& user.hasPermission("husksync.command.enderchest.edit")) {
|
&& user.hasPermission("husksync.command.enderchest.edit")) {
|
||||||
locales.getLocale("data_manager_item_buttons", dataOwner.getUsername(), snapshot.getId().toString())
|
locales.getLocale("data_manager_item_buttons", dataOwner.getUsername(), snapshot.getId().toString())
|
||||||
.ifPresent(user::sendMessage);
|
.ifPresent(user::sendMessage);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ Add the repository to your `pom.xml` as per below. You can alternatively specify
|
|||||||
</repository>
|
</repository>
|
||||||
</repositories>
|
</repositories>
|
||||||
```
|
```
|
||||||
Add the dependency to your `pom.xml` as per below. Replace `VERSION` with the latest version of HuskSync (without the v): 
|
Add the dependency to your `pom.xml` as per below. Replace `VERSION` with the latest version of HuskSync (without the v): . Note for Fabric you must append the target Minecraft version to the version number (e.g. `3.6.1+1.20.1`).
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.william278.husksync</groupId>
|
<groupId>net.william278.husksync</groupId>
|
||||||
@@ -76,7 +76,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Add the dependency as per below. Replace `VERSION` with the latest version of HuskSync (without the v): 
|
Add the dependency as per below. Replace `VERSION` with the latest version of HuskSync (without the v): . Note for Fabric you must append the target Minecraft version to the version number (e.g. `3.6.1+1.20.1`).
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
@@ -28,13 +28,13 @@ check_for_updates: true
|
|||||||
cluster_id: ''
|
cluster_id: ''
|
||||||
# Enable development debug logging
|
# Enable development debug logging
|
||||||
debug_logging: true
|
debug_logging: true
|
||||||
# Whether to provide modern, rich TAB suggestions for commands (if available)
|
|
||||||
brigadier_tab_completion: false
|
|
||||||
# Whether to enable the Player Analytics hook.
|
# Whether to enable the Player Analytics hook.
|
||||||
# Docs: https://william278.net/docs/husksync/plan-hook
|
# Docs: https://william278.net/docs/husksync/plan-hook
|
||||||
enable_plan_hook: true
|
enable_plan_hook: true
|
||||||
# Whether to cancel game event packets directly when handling locked players if ProtocolLib or PacketEvents is installed
|
# Whether to cancel game event packets directly when handling locked players if ProtocolLib or PacketEvents is installed
|
||||||
cancel_packets: true
|
cancel_packets: true
|
||||||
|
# Add HuskSync commands to this list to prevent them from being registered (e.g. ['userdata'])
|
||||||
|
disabled_commands: []
|
||||||
# Database settings
|
# Database settings
|
||||||
database:
|
database:
|
||||||
# Type of database to use (MYSQL, MARIADB, POSTGRES, MONGO)
|
# Type of database to use (MYSQL, MARIADB, POSTGRES, MONGO)
|
||||||
@@ -78,7 +78,7 @@ redis:
|
|||||||
# List of host:port pairs
|
# List of host:port pairs
|
||||||
nodes: []
|
nodes: []
|
||||||
password: ''
|
password: ''
|
||||||
# Redis settings
|
# Data syncing settings
|
||||||
synchronization:
|
synchronization:
|
||||||
# The data synchronization mode to use (LOCKSTEP or DELAY). LOCKSTEP is recommended for most networks.
|
# The data synchronization mode to use (LOCKSTEP or DELAY). LOCKSTEP is recommended for most networks.
|
||||||
# Docs: https://william278.net/docs/husksync/sync-modes
|
# Docs: https://william278.net/docs/husksync/sync-modes
|
||||||
@@ -109,7 +109,7 @@ synchronization:
|
|||||||
sync_dead_players_changing_server: true
|
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 or NONE)
|
||||||
notification_display_slot: ACTION_BAR
|
notification_display_slot: ACTION_BAR
|
||||||
# Persist maps locked in a Cartography Table to let them be viewed on any server
|
# Persist maps locked in a Cartography Table to let them be viewed on any server
|
||||||
persist_locked_maps: true
|
persist_locked_maps: true
|
||||||
@@ -134,9 +134,14 @@ synchronization:
|
|||||||
# Commands which should be blocked before a player has finished syncing (Use * to block all commands)
|
# Commands which should be blocked before a player has finished syncing (Use * to block all commands)
|
||||||
blacklisted_commands_while_locked:
|
blacklisted_commands_while_locked:
|
||||||
- '*'
|
- '*'
|
||||||
# For attribute syncing, which attributes should be ignored/skipped when syncing
|
# Configuration for how to sync attributes
|
||||||
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.attack_damage'])
|
attributes:
|
||||||
ignored_attributes: []
|
# Which attributes should not be saved when syncing users. Supports wildcard matching.
|
||||||
|
# (e.g. ['minecraft:generic.max_health', 'minecraft:generic.*'])
|
||||||
|
ignored_attributes: []
|
||||||
|
# Which modifiers should not be saved when syncing users. Supports wildcard matching.
|
||||||
|
# (e.g. ['minecraft:effect.speed', 'minecraft:effect.*'])
|
||||||
|
ignored_modifiers: ['minecraft:effect.*', 'minecraft:creative_mode_*']
|
||||||
# Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts
|
# Event priorities for listeners (HIGHEST, NORMAL, LOWEST). Change if you encounter plugin conflicts
|
||||||
event_priorities:
|
event_priorities:
|
||||||
quit_listener: LOWEST
|
quit_listener: LOWEST
|
||||||
|
|||||||
@@ -18,12 +18,12 @@ This guide will walk you through how to upgrade from HuskSync v1.4.x to HuskSync
|
|||||||
|
|
||||||
### 3. Configure the migrator
|
### 3. Configure the migrator
|
||||||
- With your servers back on and correctly configured to run HuskSync v3.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 help 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 set legacy <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.
|
||||||
|
|
||||||
### 4. Start the migrator
|
### 4. Start the migrator
|
||||||
- Run `husksync migrate legacy start` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
- Run `husksync migrate start legacy` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
||||||
|
|
||||||
### 5. Ensure the migration was successful
|
### 5. Ensure the migration was successful
|
||||||
- HuskSync will notify in console when migration is complete. Verify that the migration went OK by logging in and using the `/userdata list <username>` command to see if the data was imported with the `legacy migration` saveCause.
|
- HuskSync will notify in console when migration is complete. Verify that the migration went OK by logging in and using the `/userdata list <username>` command to see if the data was imported with the `legacy migration` saveCause.
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ This guide will walk you through how to migrate from MySQLPlayerDataBridge (MPDB
|
|||||||
|
|
||||||
### 2. Configure the migrator
|
### 2. Configure the migrator
|
||||||
- With your servers back on and correctly configured to run HuskSync v3.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 help 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 set mpdb <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`.
|
||||||
|
|
||||||
### 3. Start the migrator
|
### 3. Start the migrator
|
||||||
- Run `husksync migrate mpdb start` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
- Run `husksync migrate start mpdb` to begin the migration process. This may take some time, depending on the amount of data you're migrating.
|
||||||
|
|
||||||
### 4. Uninstall MySQLPlayerDataBridge
|
### 4. Uninstall MySQLPlayerDataBridge
|
||||||
- HuskSync will display a message in console when data migration is complete.
|
- HuskSync will display a message in console when data migration is complete.
|
||||||
|
|||||||
@@ -31,22 +31,29 @@ This will walk you through installing HuskSync on your network of Spigot or Fabr
|
|||||||
- 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`.
|
- 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`.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Important — MongoDB Users</summary>
|
<summary>MongoDB users — additional instructions</summary>
|
||||||
|
|
||||||
- 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`)
|
||||||
- Set `type` in the `database` section to `MONGO`
|
- Set `type` in the `database` section to `MONGO`
|
||||||
- Under `credentials` in the `database` section, enter the credentials of your MongoDB Database. You shouldn't touch the `connection_pool` properties.
|
- Under `credentials` in the `database` section, enter the credentials of your MongoDB Database. You shouldn't touch the `connection_pool` properties.
|
||||||
<details>
|
- Under `parameters` in the `mongo_settings` section, ensure the specified `&authSource=` matches the database you are using (default is `HuskSync`).
|
||||||
|
|
||||||
<summary>Additional configuration for MongoDB Atlas users</summary>
|
#### Additional setup for MongoDB Atlas
|
||||||
|
|
||||||
- Navigate to the HuskSync config file on each server (`~/plugins/HuskSync/config.yml`)
|
|
||||||
- Set `using_atlas` in the `mongo_settings` section to `true`.
|
- Set `using_atlas` in the `mongo_settings` section to `true`.
|
||||||
- Remove `&authSource=HuskSync` from `parameters` in the `mongo_settings`.
|
- Remove `&authSource=HuskSync` from `parameters` in the `mongo_settings`.
|
||||||
|
|
||||||
(The `port` setting in `credentials` is disregarded when using Atlas.)
|
(The `port` setting in `credentials` is disregarded when using Atlas.)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Pterodactyl self-hosts — Redis setup instructions</summary>
|
||||||
|
|
||||||
|
If you are hosting your Redis server on the same node as your servers, you need to use `172.18.0.1` as your host (or equivalent if you changed your network settings), and bind it in the Redis config `nano /etc/redis/redis.conf`.
|
||||||
|
|
||||||
|
You will also need to uncomment the `requirepass` directive and set a password to allow outside connections, or disable `protected-mode`. Once a password is set and Redis is restarted `systemctl restart redis`, you will also need to update the password in your pterodactyl `.env` (`nano /var/www/pterodactyl/.env`) and refresh the cache `cd /var/www/pterodactyl && php artisan config:clear`.
|
||||||
|
|
||||||
|
You may also need to allow connections from your firewall depending on your distribution.
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 4. Set server names in server.yml files
|
### 4. Set server names in server.yml files
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ Although it's a common request, HuskSync doesn't synchronize economy data for a
|
|||||||
I strongly recommend making use of economy plugins that have cross-server economy balance synchronization built-in, of which there are a multitude of options available. Please see our [[FAQs]] section for more details on this decision.
|
I strongly recommend making use of economy plugins that have cross-server economy balance synchronization built-in, of which there are a multitude of options available. Please see our [[FAQs]] section for more details on this decision.
|
||||||
|
|
||||||
## Toggling Sync Features
|
## Toggling Sync Features
|
||||||
All synchronization features, except location and locked map synchronising, are enabled by default. To toggle a feature, navigate to the `features:` section in the `synchronization:` part of your `config.yml` file, and change the option to `true`/`false` respectively.
|
All synchronization features, except location and locked map synchronizing, are enabled by default. To toggle a feature, navigate to the `features:` section in the `synchronization:` part of your `config.yml` file, and change the option to `true`/`false` respectively.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Example in config.yml</summary>
|
<summary>Example in config.yml</summary>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
plugins {
|
plugins {
|
||||||
id 'fabric-loom' version '1.6-SNAPSHOT'
|
id 'fabric-loom' version '1.7-SNAPSHOT'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'fabric-loom'
|
apply plugin: 'fabric-loom'
|
||||||
@@ -18,20 +18,21 @@ dependencies {
|
|||||||
modImplementation include("net.kyori:adventure-platform-fabric:${adventure_platform_fabric_version}")
|
modImplementation include("net.kyori:adventure-platform-fabric:${adventure_platform_fabric_version}")
|
||||||
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
modImplementation include("me.lucko:fabric-permissions-api:${fabric_permissions_api_version}")
|
||||||
modImplementation include("eu.pb4:sgui:${sgui_version}")
|
modImplementation include("eu.pb4:sgui:${sgui_version}")
|
||||||
|
modImplementation include('net.william278.uniform:uniform-fabric:1.2.1+1.20.1')
|
||||||
modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
modCompileOnly "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||||
|
|
||||||
// Runtime dependencies on Bukkit; "include" them on Fabric. (todo: minify JAR?)
|
|
||||||
implementation include('org.apache.commons:commons-pool2:2.12.0')
|
implementation include('org.apache.commons:commons-pool2:2.12.0')
|
||||||
implementation include("redis.clients:jedis:$jedis_version")
|
implementation include("redis.clients:jedis:$jedis_version")
|
||||||
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
||||||
implementation include("org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version")
|
implementation include("org.mariadb.jdbc:mariadb-java-client:$mariadb_driver_version")
|
||||||
|
implementation include("org.postgresql:postgresql:$postgres_driver_version")
|
||||||
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
||||||
|
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
|
|
||||||
shadow project(path: ":common")
|
shadow project(path: ":common")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import lombok.Getter;
|
|||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import net.fabricmc.api.DedicatedServerModInitializer;
|
import net.fabricmc.api.DedicatedServerModInitializer;
|
||||||
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
|
|
||||||
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
|
||||||
import net.fabricmc.loader.api.FabricLoader;
|
import net.fabricmc.loader.api.FabricLoader;
|
||||||
import net.fabricmc.loader.api.ModContainer;
|
import net.fabricmc.loader.api.ModContainer;
|
||||||
@@ -40,8 +39,7 @@ import net.william278.husksync.adapter.DataAdapter;
|
|||||||
import net.william278.husksync.adapter.GsonAdapter;
|
import net.william278.husksync.adapter.GsonAdapter;
|
||||||
import net.william278.husksync.adapter.SnappyGsonAdapter;
|
import net.william278.husksync.adapter.SnappyGsonAdapter;
|
||||||
import net.william278.husksync.api.FabricHuskSyncAPI;
|
import net.william278.husksync.api.FabricHuskSyncAPI;
|
||||||
import net.william278.husksync.command.Command;
|
import net.william278.husksync.command.PluginCommand;
|
||||||
import net.william278.husksync.command.FabricCommand;
|
|
||||||
import net.william278.husksync.config.Locales;
|
import net.william278.husksync.config.Locales;
|
||||||
import net.william278.husksync.config.Server;
|
import net.william278.husksync.config.Server;
|
||||||
import net.william278.husksync.config.Settings;
|
import net.william278.husksync.config.Settings;
|
||||||
@@ -51,6 +49,7 @@ import net.william278.husksync.database.MongoDbDatabase;
|
|||||||
import net.william278.husksync.database.MySqlDatabase;
|
import net.william278.husksync.database.MySqlDatabase;
|
||||||
import net.william278.husksync.database.PostgresDatabase;
|
import net.william278.husksync.database.PostgresDatabase;
|
||||||
import net.william278.husksync.event.FabricEventDispatcher;
|
import net.william278.husksync.event.FabricEventDispatcher;
|
||||||
|
import net.william278.husksync.event.ModLoadedCallback;
|
||||||
import net.william278.husksync.hook.PlanHook;
|
import net.william278.husksync.hook.PlanHook;
|
||||||
import net.william278.husksync.listener.EventListener;
|
import net.william278.husksync.listener.EventListener;
|
||||||
import net.william278.husksync.listener.FabricEventListener;
|
import net.william278.husksync.listener.FabricEventListener;
|
||||||
@@ -62,6 +61,8 @@ import net.william278.husksync.user.FabricUser;
|
|||||||
import net.william278.husksync.user.OnlineUser;
|
import net.william278.husksync.user.OnlineUser;
|
||||||
import net.william278.husksync.util.FabricTask;
|
import net.william278.husksync.util.FabricTask;
|
||||||
import net.william278.husksync.util.LegacyConverter;
|
import net.william278.husksync.util.LegacyConverter;
|
||||||
|
import net.william278.uniform.Uniform;
|
||||||
|
import net.william278.uniform.fabric.FabricUniform;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -74,7 +75,6 @@ import java.nio.file.Files;
|
|||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -90,6 +90,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
private final Map<String, Boolean> permissions = Maps.newHashMap();
|
||||||
private final List<Migrator> availableMigrators = Lists.newArrayList();
|
private final List<Migrator> availableMigrators = Lists.newArrayList();
|
||||||
private final Set<UUID> lockedPlayers = Sets.newConcurrentHashSet();
|
private final Set<UUID> lockedPlayers = Sets.newConcurrentHashSet();
|
||||||
|
private final Map<UUID, FabricUser> playerMap = Maps.newConcurrentMap();
|
||||||
|
|
||||||
private Logger logger;
|
private Logger logger;
|
||||||
private ModContainer mod;
|
private ModContainer mod;
|
||||||
@@ -127,7 +128,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
initialize("commands", (plugin) -> this.registerCommands());
|
initialize("commands", (plugin) -> getUniform().register(PluginCommand.Type.create(this)));
|
||||||
|
|
||||||
// Load HuskSync after server startup
|
// Load HuskSync after server startup
|
||||||
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
ServerLifecycleEvents.SERVER_STARTED.register(server -> {
|
||||||
@@ -157,7 +158,7 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
registerSerializer(Identifier.INVENTORY, new FabricSerializer.Inventory(this));
|
registerSerializer(Identifier.INVENTORY, new FabricSerializer.Inventory(this));
|
||||||
registerSerializer(Identifier.ENDER_CHEST, new FabricSerializer.EnderChest(this));
|
registerSerializer(Identifier.ENDER_CHEST, new FabricSerializer.EnderChest(this));
|
||||||
registerSerializer(Identifier.ADVANCEMENTS, new FabricSerializer.Advancements(this));
|
registerSerializer(Identifier.ADVANCEMENTS, new FabricSerializer.Advancements(this));
|
||||||
registerSerializer(Identifier.STATISTICS, new Serializer.Json<>(this, FabricData.Statistics.class)); // TODO APPLY
|
registerSerializer(Identifier.STATISTICS, new Serializer.Json<>(this, FabricData.Statistics.class));
|
||||||
registerSerializer(Identifier.POTION_EFFECTS, new FabricSerializer.PotionEffects(this));
|
registerSerializer(Identifier.POTION_EFFECTS, new FabricSerializer.PotionEffects(this));
|
||||||
registerSerializer(Identifier.GAME_MODE, new Serializer.Json<>(this, FabricData.GameMode.class));
|
registerSerializer(Identifier.GAME_MODE, new Serializer.Json<>(this, FabricData.GameMode.class));
|
||||||
registerSerializer(Identifier.FLIGHT_STATUS, new Serializer.Json<>(this, FabricData.FlightStatus.class));
|
registerSerializer(Identifier.FLIGHT_STATUS, new Serializer.Json<>(this, FabricData.FlightStatus.class));
|
||||||
@@ -202,12 +203,20 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Register API
|
// Register API
|
||||||
initialize("api", (plugin) -> {
|
initialize("api", (plugin) -> FabricHuskSyncAPI.register(this));
|
||||||
FabricHuskSyncAPI.register(this);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check for updates
|
// Check for updates
|
||||||
this.checkForUpdates();
|
this.checkForUpdates();
|
||||||
|
|
||||||
|
log(Level.WARNING, """
|
||||||
|
**************
|
||||||
|
WARNING:
|
||||||
|
|
||||||
|
HuskSync for Fabric is still in an alpha state and is
|
||||||
|
not considered production ready.
|
||||||
|
**************""");
|
||||||
|
|
||||||
|
ModLoadedCallback.EVENT.invoker().post(FabricHuskSyncAPI.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDisable() {
|
private void onDisable() {
|
||||||
@@ -232,12 +241,6 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
log(Level.INFO, "Successfully disabled HuskSync v" + getPluginVersion());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerCommands() {
|
|
||||||
final List<Command> commands = FabricCommand.Type.getCommands(this);
|
|
||||||
CommandRegistrationCallback.EVENT.register((dispatcher, registry, environment) ->
|
|
||||||
commands.forEach(command -> new FabricCommand(command, this).register(dispatcher))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
@@ -253,16 +256,19 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Set<OnlineUser> getOnlineUsers() {
|
public Set<OnlineUser> getOnlineUsers() {
|
||||||
return minecraftServer.getPlayerManager().getPlayerList()
|
return Sets.newHashSet(playerMap.values());
|
||||||
.stream().map(user -> (OnlineUser) FabricUser.adapt(user, this))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@NotNull
|
@NotNull
|
||||||
public Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid) {
|
public Optional<OnlineUser> getOnlineUser(@NotNull UUID uuid) {
|
||||||
return Optional.ofNullable(minecraftServer.getPlayerManager().getPlayer(uuid))
|
return Optional.ofNullable(playerMap.get(uuid));
|
||||||
.map(user -> FabricUser.adapt(user, this));
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Uniform getUniform() {
|
||||||
|
return FabricUniform.getInstance(mod.getMetadata().getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -333,6 +339,14 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
return PLATFORM_TYPE_ID;
|
return PLATFORM_TYPE_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public String getServerVersion() {
|
||||||
|
return String.format("%s %s/%s", getPlatformType(), FabricLoader.getInstance()
|
||||||
|
.getModContainer("fabricloader").map(l -> l.getMetadata().getVersion().getFriendlyString())
|
||||||
|
.orElse("unknown"), minecraftServer.getVersion());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<LegacyConverter> getLegacyConverter() {
|
public Optional<LegacyConverter> getLegacyConverter() {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
|
|||||||
@@ -1,153 +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.command;
|
|
||||||
|
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
|
||||||
import me.lucko.fabric.api.permissions.v0.PermissionCheckEvent;
|
|
||||||
import me.lucko.fabric.api.permissions.v0.Permissions;
|
|
||||||
import net.fabricmc.fabric.api.util.TriState;
|
|
||||||
import net.minecraft.server.command.ServerCommandSource;
|
|
||||||
import net.minecraft.server.network.ServerPlayerEntity;
|
|
||||||
import net.william278.husksync.FabricHuskSync;
|
|
||||||
import net.william278.husksync.HuskSync;
|
|
||||||
import net.william278.husksync.user.CommandUser;
|
|
||||||
import net.william278.husksync.user.FabricUser;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import static com.mojang.brigadier.arguments.StringArgumentType.greedyString;
|
|
||||||
import static net.minecraft.server.command.CommandManager.argument;
|
|
||||||
import static net.minecraft.server.command.CommandManager.literal;
|
|
||||||
|
|
||||||
public class FabricCommand {
|
|
||||||
|
|
||||||
private final FabricHuskSync plugin;
|
|
||||||
private final Command command;
|
|
||||||
|
|
||||||
public FabricCommand(@NotNull Command command, @NotNull FabricHuskSync plugin) {
|
|
||||||
this.command = command;
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void register(@NotNull CommandDispatcher<ServerCommandSource> dispatcher) {
|
|
||||||
// Register brigadier command
|
|
||||||
final Predicate<ServerCommandSource> predicate = Permissions
|
|
||||||
.require(command.getPermission(), command.isOperatorCommand() ? 3 : 0);
|
|
||||||
final LiteralArgumentBuilder<ServerCommandSource> builder = literal(command.getName())
|
|
||||||
.requires(predicate).executes(getBrigadierExecutor());
|
|
||||||
plugin.getPermissions().put(command.getPermission(), command.isOperatorCommand());
|
|
||||||
if (!command.getRawUsage().isBlank()) {
|
|
||||||
builder.then(argument(command.getRawUsage().replaceAll("[<>\\[\\]]", ""), greedyString())
|
|
||||||
.executes(getBrigadierExecutor())
|
|
||||||
.suggests(getBrigadierSuggester()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register additional permissions
|
|
||||||
final Map<String, Boolean> permissions = command.getAdditionalPermissions();
|
|
||||||
permissions.forEach((permission, isOp) -> plugin.getPermissions().put(permission, isOp));
|
|
||||||
PermissionCheckEvent.EVENT.register((player, node) -> {
|
|
||||||
if (permissions.containsKey(node) && permissions.get(node) && player.hasPermissionLevel(3)) {
|
|
||||||
return TriState.TRUE;
|
|
||||||
}
|
|
||||||
return TriState.DEFAULT;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Register aliases
|
|
||||||
final LiteralCommandNode<ServerCommandSource> node = dispatcher.register(builder);
|
|
||||||
dispatcher.register(literal("husksync:" + command.getName())
|
|
||||||
.requires(predicate).executes(getBrigadierExecutor()).redirect(node));
|
|
||||||
command.getAliases().forEach(alias -> dispatcher.register(literal(alias)
|
|
||||||
.requires(predicate).executes(getBrigadierExecutor()).redirect(node)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private com.mojang.brigadier.Command<ServerCommandSource> getBrigadierExecutor() {
|
|
||||||
return (context) -> {
|
|
||||||
command.onExecuted(
|
|
||||||
resolveExecutor(context.getSource()),
|
|
||||||
command.removeFirstArg(context.getInput().split(" "))
|
|
||||||
);
|
|
||||||
return 1;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private com.mojang.brigadier.suggestion.SuggestionProvider<ServerCommandSource> getBrigadierSuggester() {
|
|
||||||
if (!(command instanceof TabProvider provider)) {
|
|
||||||
return (context, builder) -> com.mojang.brigadier.suggestion.Suggestions.empty();
|
|
||||||
}
|
|
||||||
return (context, builder) -> {
|
|
||||||
final String[] args = command.removeFirstArg(context.getInput().split(" ", -1));
|
|
||||||
provider.getSuggestions(resolveExecutor(context.getSource()), args).stream()
|
|
||||||
.map(suggestion -> {
|
|
||||||
final String completedArgs = String.join(" ", args);
|
|
||||||
int lastIndex = completedArgs.lastIndexOf(" ");
|
|
||||||
if (lastIndex == -1) {
|
|
||||||
return suggestion;
|
|
||||||
}
|
|
||||||
return completedArgs.substring(0, lastIndex + 1) + suggestion;
|
|
||||||
})
|
|
||||||
.forEach(builder::suggest);
|
|
||||||
return builder.buildFuture();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private CommandUser resolveExecutor(@NotNull ServerCommandSource source) {
|
|
||||||
if (source.getEntity() instanceof ServerPlayerEntity player) {
|
|
||||||
return FabricUser.adapt(player, plugin);
|
|
||||||
}
|
|
||||||
return plugin.getConsole();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commands available on the Fabric HuskSync implementation.
|
|
||||||
*/
|
|
||||||
public enum Type {
|
|
||||||
|
|
||||||
HUSKSYNC_COMMAND(HuskSyncCommand::new),
|
|
||||||
USERDATA_COMMAND(UserDataCommand::new),
|
|
||||||
INVENTORY_COMMAND(InventoryCommand::new),
|
|
||||||
ENDER_CHEST_COMMAND(EnderChestCommand::new);
|
|
||||||
|
|
||||||
private final Function<HuskSync, Command> supplier;
|
|
||||||
|
|
||||||
Type(@NotNull Function<HuskSync, Command> supplier) {
|
|
||||||
this.supplier = supplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public Command createCommand(@NotNull HuskSync plugin) {
|
|
||||||
return supplier.apply(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public static List<Command> getCommands(@NotNull FabricHuskSync plugin) {
|
|
||||||
return Arrays.stream(values()).map(type -> type.createCommand(plugin)).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -53,6 +53,7 @@ import net.william278.husksync.user.FabricUser;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.jetbrains.annotations.Range;
|
import org.jetbrains.annotations.Range;
|
||||||
|
import org.jetbrains.annotations.Unmodifiable;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -153,29 +154,23 @@ public abstract class FabricData implements Data {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getSlotCount() {
|
public int getSlotCount() {
|
||||||
return INVENTORY_SLOT_COUNT;
|
return getContents().length;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
final ServerPlayerEntity player = user.getPlayer();
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
this.clearInventoryCraftingSlots(player);
|
player.playerScreenHandler.clearCraftingSlots();
|
||||||
player.currentScreenHandler.setCursorStack(ItemStack.EMPTY);
|
player.currentScreenHandler.setCursorStack(ItemStack.EMPTY);
|
||||||
final ItemStack[] items = getContents();
|
final ItemStack[] items = getContents();
|
||||||
for (int slot = 0; slot < player.getInventory().size(); slot++) {
|
for (int slot = 0; slot < player.getInventory().size(); slot++) {
|
||||||
player.getInventory().setStack(
|
player.getInventory().setStack(slot, items[slot] == null ? ItemStack.EMPTY : items[slot]);
|
||||||
slot, items[slot] == null ? ItemStack.EMPTY : items[slot]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
player.getInventory().selectedSlot = heldItemSlot;
|
player.getInventory().selectedSlot = heldItemSlot;
|
||||||
player.playerScreenHandler.sendContentUpdates();
|
player.playerScreenHandler.sendContentUpdates();
|
||||||
player.getInventory().updateItems();
|
player.getInventory().updateItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearInventoryCraftingSlots(@NotNull ServerPlayerEntity player) {
|
|
||||||
player.playerScreenHandler.clearCraftingSlots();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class EnderChest extends FabricData.Items implements Data.Items.EnderChest {
|
public static class EnderChest extends FabricData.Items implements Data.Items.EnderChest {
|
||||||
@@ -243,8 +238,8 @@ public abstract class FabricData implements Data {
|
|||||||
private final Collection<StatusEffectInstance> effects;
|
private final Collection<StatusEffectInstance> effects;
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static FabricData.PotionEffects from(@NotNull Collection<StatusEffectInstance> effects) {
|
public static FabricData.PotionEffects from(@NotNull Collection<StatusEffectInstance> sei) {
|
||||||
return new FabricData.PotionEffects(effects);
|
return new FabricData.PotionEffects(Lists.newArrayList(sei.stream().filter(e -> !e.isAmbient()).toList()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@@ -269,18 +264,21 @@ public abstract class FabricData implements Data {
|
|||||||
@NotNull
|
@NotNull
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
public static FabricData.PotionEffects empty() {
|
public static FabricData.PotionEffects empty() {
|
||||||
return new FabricData.PotionEffects(List.of());
|
return new FabricData.PotionEffects(Lists.newArrayList());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
final ServerPlayerEntity player = user.getPlayer();
|
final ServerPlayerEntity player = user.getPlayer();
|
||||||
player.getActiveStatusEffects().forEach((effect, instance) -> player.removeStatusEffect(effect));
|
final List<StatusEffect> effectsToRemove = player.getActiveStatusEffects().entrySet().stream()
|
||||||
|
.filter(e -> !e.getValue().isAmbient()).map(Map.Entry::getKey).toList();
|
||||||
|
effectsToRemove.forEach(player::removeStatusEffect);
|
||||||
getEffects().forEach(player::addStatusEffect);
|
getEffects().forEach(player::addStatusEffect);
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
|
@Unmodifiable
|
||||||
public List<Effect> getActiveEffects() {
|
public List<Effect> getActiveEffects() {
|
||||||
return effects.stream()
|
return effects.stream()
|
||||||
.map(potionEffect -> {
|
.map(potionEffect -> {
|
||||||
@@ -321,7 +319,7 @@ public abstract class FabricData implements Data {
|
|||||||
|
|
||||||
// Only save the advancement if criteria has been completed
|
// Only save the advancement if criteria has been completed
|
||||||
if (!awardedCriteria.isEmpty()) {
|
if (!awardedCriteria.isEmpty()) {
|
||||||
advancements.add(Advancement.adapt(advancement.getId().asString(), awardedCriteria));
|
advancements.add(Advancement.adapt(advancement.getId().toString(), awardedCriteria));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return new FabricData.Advancements(advancements);
|
return new FabricData.Advancements(advancements);
|
||||||
@@ -373,7 +371,7 @@ public abstract class FabricData implements Data {
|
|||||||
|
|
||||||
// Restore player exp level & progress
|
// Restore player exp level & progress
|
||||||
if (!toAward.isEmpty()
|
if (!toAward.isEmpty()
|
||||||
&& (player.experienceLevel != expLevel || player.experienceProgress != expProgress)) {
|
&& (player.experienceLevel != expLevel || player.experienceProgress != expProgress)) {
|
||||||
player.setExperienceLevel(expLevel);
|
player.setExperienceLevel(expLevel);
|
||||||
player.setExperiencePoints((int) (player.getNextLevelExperience() * expProgress));
|
player.setExperiencePoints((int) (player.getNextLevelExperience() * expProgress));
|
||||||
}
|
}
|
||||||
@@ -485,7 +483,7 @@ public abstract class FabricData implements Data {
|
|||||||
Registries.STAT_TYPE.getEntrySet().forEach(stat -> {
|
Registries.STAT_TYPE.getEntrySet().forEach(stat -> {
|
||||||
final Registry<?> registry = stat.getValue().getRegistry();
|
final Registry<?> registry = stat.getValue().getRegistry();
|
||||||
|
|
||||||
final String registryId = registry.getKey().getValue().value();
|
final String registryId = registry.getKey().getValue().getPath();
|
||||||
if (registryId.equals("custom_stat")) {
|
if (registryId.equals("custom_stat")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -494,13 +492,13 @@ public abstract class FabricData implements Data {
|
|||||||
case ITEM_STAT_TYPE -> items;
|
case ITEM_STAT_TYPE -> items;
|
||||||
case ENTITY_STAT_TYPE -> entities;
|
case ENTITY_STAT_TYPE -> entities;
|
||||||
default -> throw new IllegalStateException("Unexpected value: %s".formatted(registryId));
|
default -> throw new IllegalStateException("Unexpected value: %s".formatted(registryId));
|
||||||
}).compute(stat.getKey().getValue().asString(), (k, v) -> v == null ? Maps.newHashMap() : v);
|
}).compute(stat.getKey().getValue().toString(), (k, v) -> v == null ? Maps.newHashMap() : v);
|
||||||
|
|
||||||
registry.getEntrySet().forEach(entry -> {
|
registry.getEntrySet().forEach(entry -> {
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"}) final int value = player.getStatHandler()
|
@SuppressWarnings({"unchecked", "rawtypes"}) final int value = player.getStatHandler()
|
||||||
.getStat((StatType) stat.getValue(), entry.getValue());
|
.getStat((StatType) stat.getValue(), entry.getValue());
|
||||||
if (value != 0) {
|
if (value != 0) {
|
||||||
map.put(entry.getKey().getValue().asString(), value);
|
map.put(entry.getKey().getValue().toString(), value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -510,7 +508,7 @@ public abstract class FabricData implements Data {
|
|||||||
Registries.CUSTOM_STAT.getEntrySet().forEach(stat -> {
|
Registries.CUSTOM_STAT.getEntrySet().forEach(stat -> {
|
||||||
final int value = player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(stat.getValue()));
|
final int value = player.getStatHandler().getStat(Stats.CUSTOM.getOrCreateStat(stat.getValue()));
|
||||||
if (value != 0) {
|
if (value != 0) {
|
||||||
generic.put(stat.getKey().getValue().asString(), value);
|
generic.put(stat.getKey().getValue().toString(), value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -593,7 +591,7 @@ public abstract class FabricData implements Data {
|
|||||||
-1
|
-1
|
||||||
)));
|
)));
|
||||||
attributes.add(new Attribute(
|
attributes.add(new Attribute(
|
||||||
key.asString(),
|
key.toString(),
|
||||||
instance.getBaseValue(),
|
instance.getBaseValue(),
|
||||||
modifiers
|
modifiers
|
||||||
));
|
));
|
||||||
@@ -602,7 +600,7 @@ public abstract class FabricData implements Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Attribute> getAttribute(@NotNull EntityAttribute id) {
|
public Optional<Attribute> getAttribute(@NotNull EntityAttribute id) {
|
||||||
return Optional.ofNullable(Registries.ATTRIBUTE.getId(id)).map(Identifier::asString)
|
return Optional.ofNullable(Registries.ATTRIBUTE.getId(id)).map(Identifier::toString)
|
||||||
.flatMap(key -> attributes.stream().filter(attribute -> attribute.name().equals(key)).findFirst());
|
.flatMap(key -> attributes.stream().filter(attribute -> attribute.name().equals(key)).findFirst());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,7 +767,7 @@ public abstract class FabricData implements Data {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
public void apply(@NotNull FabricUser user, @NotNull FabricHuskSync plugin) throws IllegalStateException {
|
||||||
user.getPlayer().interactionManager.changeGameMode(net.minecraft.world.GameMode.byName(gameMode));
|
user.getPlayer().changeGameMode(net.minecraft.world.GameMode.byName(gameMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ public abstract class FabricSerializer {
|
|||||||
int VERSION1_20_2 = 3578; // Future
|
int VERSION1_20_2 = 3578; // Future
|
||||||
int VERSION1_20_4 = 3700; // Future
|
int VERSION1_20_4 = 3700; // Future
|
||||||
int VERSION1_20_5 = 3837; // Future
|
int VERSION1_20_5 = 3837; // Future
|
||||||
|
int VERSION1_21 = 3953; // Future
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
default ItemStack[] getItems(@NotNull NbtCompound tag, @NotNull Version mcVersion, @NotNull FabricHuskSync plugin) {
|
default ItemStack[] getItems(@NotNull NbtCompound tag, @NotNull Version mcVersion, @NotNull FabricHuskSync plugin) {
|
||||||
@@ -158,15 +159,14 @@ public abstract class FabricSerializer {
|
|||||||
return upgradeItemStacks(tag, mcVersion, plugin);
|
return upgradeItemStacks(tag, mcVersion, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
final int size = tag.getInt("size");
|
final ItemStack[] contents = new ItemStack[tag.getInt("size")];
|
||||||
final NbtList items = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
final NbtList itemList = tag.getList("items", NbtElement.COMPOUND_TYPE);
|
||||||
final ItemStack[] itemStacks = new ItemStack[size];
|
itemList.forEach(element -> {
|
||||||
for (int i = 0; i < size; i++) {
|
final NbtCompound compound = (NbtCompound) element;
|
||||||
final NbtCompound compound = items.getCompound(i);
|
contents[compound.getInt("Slot")] = ItemStack.fromNbt(compound);
|
||||||
final int slot = compound.getInt("Slot");
|
});
|
||||||
itemStacks[slot] = ItemStack.fromNbt(compound);
|
plugin.debug(Arrays.toString(contents));
|
||||||
}
|
return contents;
|
||||||
return itemStacks;
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw new Serializer.DeserializationException("Failed to read item NBT string (%s)".formatted(tag), e);
|
throw new Serializer.DeserializationException("Failed to read item NBT string (%s)".formatted(tag), e);
|
||||||
}
|
}
|
||||||
@@ -232,6 +232,7 @@ public abstract class FabricSerializer {
|
|||||||
case "1.20.2" -> VERSION1_20_2; // Future
|
case "1.20.2" -> VERSION1_20_2; // Future
|
||||||
case "1.20.4" -> VERSION1_20_4; // Future
|
case "1.20.4" -> VERSION1_20_4; // Future
|
||||||
case "1.20.5", "1.20.6" -> VERSION1_20_5; // Future
|
case "1.20.5", "1.20.6" -> VERSION1_20_5; // Future
|
||||||
|
case "1.21" -> VERSION1_21; // Future
|
||||||
default -> VERSION1_20_1; // Current supported ver
|
default -> VERSION1_20_1; // Current supported ver
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,23 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package net.william278.husksync.command;
|
package net.william278.husksync.event;
|
||||||
|
|
||||||
import net.william278.husksync.user.CommandUser;
|
import net.fabricmc.fabric.api.event.Event;
|
||||||
|
import net.fabricmc.fabric.api.event.EventFactory;
|
||||||
|
import net.william278.husksync.api.HuskSyncAPI;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public interface Executable {
|
import java.util.Arrays;
|
||||||
|
|
||||||
void onExecuted(@NotNull CommandUser executor, @NotNull String[] args);
|
public interface ModLoadedCallback {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
Event<ModLoadedCallback> EVENT = EventFactory.createArrayBacked(
|
||||||
|
ModLoadedCallback.class,
|
||||||
|
(listeners) -> (api) -> Arrays.stream(listeners).forEach(listener -> listener.post(api))
|
||||||
|
);
|
||||||
|
|
||||||
|
void post(@NotNull HuskSyncAPI api);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -44,6 +44,7 @@ import net.minecraft.util.hit.BlockHitResult;
|
|||||||
import net.minecraft.util.hit.EntityHitResult;
|
import net.minecraft.util.hit.EntityHitResult;
|
||||||
import net.minecraft.util.math.BlockPos;
|
import net.minecraft.util.math.BlockPos;
|
||||||
import net.minecraft.world.World;
|
import net.minecraft.world.World;
|
||||||
|
import net.william278.husksync.FabricHuskSync;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.config.Settings.SynchronizationSettings.SaveOnDeathSettings;
|
import net.william278.husksync.config.Settings.SynchronizationSettings.SaveOnDeathSettings;
|
||||||
import net.william278.husksync.data.FabricData;
|
import net.william278.husksync.data.FabricData;
|
||||||
@@ -53,8 +54,6 @@ import net.william278.husksync.user.OnlineUser;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class FabricEventListener extends EventListener implements LockedHandler {
|
public class FabricEventListener extends EventListener implements LockedHandler {
|
||||||
|
|
||||||
public FabricEventListener(@NotNull HuskSync plugin) {
|
public FabricEventListener(@NotNull HuskSync plugin) {
|
||||||
@@ -68,7 +67,7 @@ public class FabricEventListener extends EventListener implements LockedHandler
|
|||||||
WorldSaveCallback.EVENT.register(this::handleWorldSave);
|
WorldSaveCallback.EVENT.register(this::handleWorldSave);
|
||||||
PlayerDeathDropsCallback.EVENT.register(this::handlePlayerDeathDrops);
|
PlayerDeathDropsCallback.EVENT.register(this::handlePlayerDeathDrops);
|
||||||
|
|
||||||
// TODO: Events of extra things to cancel if the player has not been set yet
|
// Locked events handling
|
||||||
ItemPickupCallback.EVENT.register(this::handleItemPickup);
|
ItemPickupCallback.EVENT.register(this::handleItemPickup);
|
||||||
ItemDropCallback.EVENT.register(this::handleItemDrop);
|
ItemDropCallback.EVENT.register(this::handleItemDrop);
|
||||||
UseBlockCallback.EVENT.register(this::handleBlockInteract);
|
UseBlockCallback.EVENT.register(this::handleBlockInteract);
|
||||||
@@ -82,22 +81,26 @@ public class FabricEventListener extends EventListener implements LockedHandler
|
|||||||
|
|
||||||
private void handlePlayerJoin(@NotNull ServerPlayNetworkHandler handler, @NotNull PacketSender sender,
|
private void handlePlayerJoin(@NotNull ServerPlayNetworkHandler handler, @NotNull PacketSender sender,
|
||||||
@NotNull MinecraftServer server) {
|
@NotNull MinecraftServer server) {
|
||||||
handlePlayerJoin(FabricUser.adapt(handler.player, plugin));
|
final FabricUser user = FabricUser.adapt(handler.player, plugin);
|
||||||
|
((FabricHuskSync) plugin).getPlayerMap().put(handler.player.getUuid(), user);
|
||||||
|
handlePlayerJoin(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlayerQuit(@NotNull ServerPlayNetworkHandler handler, @NotNull MinecraftServer server) {
|
private void handlePlayerQuit(@NotNull ServerPlayNetworkHandler handler, @NotNull MinecraftServer server) {
|
||||||
|
((FabricHuskSync) plugin).getPlayerMap().remove(handler.player.getUuid());
|
||||||
handlePlayerQuit(FabricUser.adapt(handler.player, plugin));
|
handlePlayerQuit(FabricUser.adapt(handler.player, plugin));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleWorldSave(@NotNull ServerWorld world) {
|
private void handleWorldSave(@NotNull ServerWorld world) {
|
||||||
saveOnWorldSave(world.getPlayers().stream()
|
this.saveOnWorldSave(
|
||||||
.map(player -> (OnlineUser) FabricUser.adapt(player, plugin)).collect(Collectors.toList()));
|
world.getPlayers().stream().map(player -> (OnlineUser) FabricUser.adapt(player, plugin)).toList()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlayerDeathDrops(@NotNull ServerPlayerEntity player, @Nullable ItemStack @NotNull [] toKeep,
|
private void handlePlayerDeathDrops(@NotNull ServerPlayerEntity player, @Nullable ItemStack @NotNull [] toKeep,
|
||||||
@Nullable ItemStack @NotNull [] toDrop) {
|
@Nullable ItemStack @NotNull [] toDrop) {
|
||||||
final SaveOnDeathSettings settings = plugin.getSettings().getSynchronization().getSaveOnDeath();
|
final SaveOnDeathSettings settings = plugin.getSettings().getSynchronization().getSaveOnDeath();
|
||||||
saveOnPlayerDeath(
|
this.saveOnPlayerDeath(
|
||||||
FabricUser.adapt(player, plugin),
|
FabricUser.adapt(player, plugin),
|
||||||
FabricData.Items.ItemArray.adapt(
|
FabricData.Items.ItemArray.adapt(
|
||||||
settings.getItemsToSave() == SaveOnDeathSettings.DeathItemsMode.DROPS ? toDrop : toKeep
|
settings.getItemsToSave() == SaveOnDeathSettings.DeathItemsMode.DROPS ? toDrop : toKeep
|
||||||
|
|||||||
@@ -20,22 +20,26 @@
|
|||||||
package net.william278.husksync.mixins;
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
import net.minecraft.entity.ItemEntity;
|
import net.minecraft.entity.ItemEntity;
|
||||||
import net.minecraft.entity.player.PlayerInventory;
|
import net.minecraft.entity.player.PlayerEntity;
|
||||||
import net.minecraft.item.ItemStack;
|
import net.minecraft.item.ItemStack;
|
||||||
import net.minecraft.util.ActionResult;
|
import net.minecraft.util.ActionResult;
|
||||||
import net.william278.husksync.event.ItemPickupCallback;
|
import net.william278.husksync.event.ItemPickupCallback;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Redirect;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
|
|
||||||
@Mixin(ItemEntity.class)
|
@Mixin(ItemEntity.class)
|
||||||
public class ItemEntityMixin {
|
public class ItemEntityMixin {
|
||||||
|
|
||||||
@Redirect(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/player/PlayerInventory;insertStack(Lnet/minecraft/item/ItemStack;)Z"),
|
@Inject(method = "onPlayerCollision", at = @At("HEAD"), cancellable = true)
|
||||||
method = "onPlayerCollision")
|
private void onPlayerPickupItem(PlayerEntity player, CallbackInfo ci) {
|
||||||
public boolean onPlayerCollision(PlayerInventory inventory, ItemStack stack) {
|
final ItemStack stack = ((ItemEntity) (Object) this).getStack();
|
||||||
ActionResult result = ItemPickupCallback.EVENT.invoker().interact(inventory.player, stack);
|
final ActionResult result = ItemPickupCallback.EVENT.invoker().interact(player, stack);
|
||||||
return (result != ActionResult.FAIL && inventory.insertStack(stack));
|
|
||||||
|
if (result == ActionResult.FAIL) {
|
||||||
|
ci.cancel();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ public abstract class ServerPlayNetworkHandlerMixin {
|
|||||||
@Inject(method = "onPlayerAction", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "onPlayerAction", at = @At("HEAD"), cancellable = true)
|
||||||
public void onPlayerAction(PlayerActionC2SPacket packet, CallbackInfo ci) {
|
public void onPlayerAction(PlayerActionC2SPacket packet, CallbackInfo ci) {
|
||||||
if (packet.getAction() == PlayerActionC2SPacket.Action.DROP_ITEM
|
if (packet.getAction() == PlayerActionC2SPacket.Action.DROP_ITEM
|
||||||
|| packet.getAction() == PlayerActionC2SPacket.Action.DROP_ALL_ITEMS) {
|
|| packet.getAction() == PlayerActionC2SPacket.Action.DROP_ALL_ITEMS) {
|
||||||
ItemStack stack = player.getStackInHand(Hand.MAIN_HAND);
|
ItemStack stack = player.getStackInHand(Hand.MAIN_HAND);
|
||||||
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ public class ServerPlayerEntityMixin {
|
|||||||
@Inject(method = "dropItem", at = @At("HEAD"), cancellable = true)
|
@Inject(method = "dropItem", at = @At("HEAD"), cancellable = true)
|
||||||
private void onPlayerDropItem(ItemStack stack, boolean dropAtFeet, boolean saveThrower,
|
private void onPlayerDropItem(ItemStack stack, boolean dropAtFeet, boolean saveThrower,
|
||||||
final CallbackInfoReturnable<ItemEntity> ci) {
|
final CallbackInfoReturnable<ItemEntity> ci) {
|
||||||
ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
|
final ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
|
||||||
ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
final ActionResult result = ItemDropCallback.EVENT.invoker().interact(player, stack);
|
||||||
|
|
||||||
if (result == ActionResult.FAIL) {
|
if (result == ActionResult.FAIL) {
|
||||||
ci.cancel();
|
ci.cancel();
|
||||||
|
|||||||
@@ -19,9 +19,12 @@
|
|||||||
|
|
||||||
package net.william278.husksync.mixins;
|
package net.william278.husksync.mixins;
|
||||||
|
|
||||||
|
import net.minecraft.server.MinecraftServer;
|
||||||
import net.minecraft.server.world.ServerWorld;
|
import net.minecraft.server.world.ServerWorld;
|
||||||
import net.william278.husksync.event.WorldSaveCallback;
|
import net.william278.husksync.event.WorldSaveCallback;
|
||||||
|
import org.spongepowered.asm.mixin.Final;
|
||||||
import org.spongepowered.asm.mixin.Mixin;
|
import org.spongepowered.asm.mixin.Mixin;
|
||||||
|
import org.spongepowered.asm.mixin.Shadow;
|
||||||
import org.spongepowered.asm.mixin.injection.At;
|
import org.spongepowered.asm.mixin.injection.At;
|
||||||
import org.spongepowered.asm.mixin.injection.Inject;
|
import org.spongepowered.asm.mixin.injection.Inject;
|
||||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||||
@@ -29,8 +32,15 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
|||||||
@Mixin(ServerWorld.class)
|
@Mixin(ServerWorld.class)
|
||||||
public class ServerWorldMixin {
|
public class ServerWorldMixin {
|
||||||
|
|
||||||
|
@Final
|
||||||
|
@Shadow
|
||||||
|
private MinecraftServer server;
|
||||||
|
|
||||||
@Inject(method = "saveLevel", at = @At("HEAD"))
|
@Inject(method = "saveLevel", at = @At("HEAD"))
|
||||||
public void saveLevel(CallbackInfo ci) {
|
public void saveLevel(CallbackInfo ci) {
|
||||||
|
if (server.isStopping() || server.isStopped()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
WorldSaveCallback.EVENT.invoker().save((ServerWorld) (Object) this);
|
WorldSaveCallback.EVENT.invoker().save((ServerWorld) (Object) this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.jetbrains.annotations.ApiStatus;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
|
||||||
public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
||||||
|
|
||||||
@@ -70,9 +71,12 @@ public class FabricUser extends OnlineUser implements FabricUserDataHolder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Deprecated(since = "3.6.7")
|
||||||
public void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial,
|
public void sendToast(@NotNull MineDown title, @NotNull MineDown description, @NotNull String iconMaterial,
|
||||||
@NotNull String backgroundType) {
|
@NotNull String backgroundType) {
|
||||||
player.sendActionBar(title.toComponent()); // Toasts unimplemented for now
|
plugin.log(Level.WARNING, "Toast notifications are deprecated. " +
|
||||||
|
"Please change your notification display slot to CHAT, ACTION_BAR or NONE.");
|
||||||
|
this.sendActionBar(title);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -19,18 +19,21 @@
|
|||||||
|
|
||||||
package net.william278.husksync.util;
|
package net.william278.husksync.util;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
import net.william278.husksync.FabricHuskSync;
|
import net.william278.husksync.FabricHuskSync;
|
||||||
import net.william278.husksync.HuskSync;
|
import net.william278.husksync.HuskSync;
|
||||||
import net.william278.husksync.data.UserDataHolder;
|
import net.william278.husksync.data.UserDataHolder;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.*;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public interface FabricTask extends Task {
|
public interface FabricTask extends Task {
|
||||||
|
ScheduledExecutorService ASYNC_EXEC = Executors.newScheduledThreadPool(4,
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setDaemon(true)
|
||||||
|
.setNameFormat("HuskSync-ThreadPool")
|
||||||
|
.build());
|
||||||
|
|
||||||
class Sync extends Task.Sync implements FabricTask {
|
class Sync extends Task.Sync implements FabricTask {
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ public interface FabricTask extends Task {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
Executors.newSingleThreadScheduledExecutor().schedule(
|
ASYNC_EXEC.schedule(
|
||||||
() -> ((FabricHuskSync) getPlugin()).getMinecraftServer().executeSync(runnable),
|
() -> ((FabricHuskSync) getPlugin()).getMinecraftServer().executeSync(runnable),
|
||||||
delayTicks * 50,
|
delayTicks * 50,
|
||||||
TimeUnit.MILLISECONDS
|
TimeUnit.MILLISECONDS
|
||||||
@@ -73,7 +76,7 @@ public interface FabricTask extends Task {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
this.task = CompletableFuture.runAsync(runnable, ((FabricHuskSync) getPlugin()).getMinecraftServer());
|
this.task = CompletableFuture.runAsync(runnable, ASYNC_EXEC);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,7 +100,7 @@ public interface FabricTask extends Task {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
this.task = Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(
|
this.task = ASYNC_EXEC.scheduleAtFixedRate(
|
||||||
runnable,
|
runnable,
|
||||||
0,
|
0,
|
||||||
repeatingTicks * 50,
|
repeatingTicks * 50,
|
||||||
@@ -129,7 +132,7 @@ public interface FabricTask extends Task {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
default void cancelTasks() {
|
default void cancelTasks() {
|
||||||
// Do nothing
|
ASYNC_EXEC.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
|||||||
org.gradle.daemon=true
|
org.gradle.daemon=true
|
||||||
javaVersion=17
|
javaVersion=17
|
||||||
|
|
||||||
plugin_version=3.6
|
plugin_version=3.6.8
|
||||||
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.1.3
|
jedis_version=5.1.4
|
||||||
mysql_driver_version=8.4.0
|
mysql_driver_version=9.0.0
|
||||||
mariadb_driver_version=3.4.0
|
mariadb_driver_version=3.4.1
|
||||||
postgres_driver_version=42.7.3
|
postgres_driver_version=42.7.3
|
||||||
mongodb_driver_version=5.1.0
|
mongodb_driver_version=5.1.2
|
||||||
snappy_version=1.1.10.5
|
snappy_version=1.1.10.6
|
||||||
|
|
||||||
fabric_minecraft_version=1.20.1
|
fabric_minecraft_version=1.20.1
|
||||||
fabric_loader_version=0.15.11
|
fabric_loader_version=0.15.11
|
||||||
|
|||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
22
gradlew
vendored
22
gradlew
vendored
@@ -83,7 +83,8 @@ done
|
|||||||
# This is normally unused
|
# This is normally unused
|
||||||
# shellcheck disable=SC2034
|
# shellcheck disable=SC2034
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -130,10 +131,13 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
@@ -141,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC3045
|
# shellcheck disable=SC2039,SC3045
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
@@ -149,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
|||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
# shellcheck disable=SC3045
|
# shellcheck disable=SC2039,SC3045
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -198,11 +202,11 @@ fi
|
|||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command:
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# and any embedded shellness will be escaped.
|
||||||
# double quotes to make sure that they get re-expanded; and
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
# * put everything else in single quotes, so that it's not re-expanded.
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
|||||||
20
gradlew.bat
vendored
20
gradlew.bat
vendored
@@ -43,11 +43,11 @@ set JAVA_EXE=java.exe
|
|||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if %ERRORLEVEL% equ 0 goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
@@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
|||||||
|
|
||||||
if exist "%JAVA_EXE%" goto execute
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
echo.
|
echo. 1>&2
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
echo location of your Java installation.
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
goto fail
|
goto fail
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,18 @@
|
|||||||
|
plugins {
|
||||||
|
id 'xyz.jpenilla.run-paper' version '2.3.0'
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation project(':bukkit')
|
implementation project(':bukkit')
|
||||||
compileOnly project(':common')
|
compileOnly project(':common')
|
||||||
|
|
||||||
|
implementation 'net.william278.uniform:uniform-paper:1.2.1'
|
||||||
|
|
||||||
compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT'
|
compileOnly 'io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT'
|
||||||
compileOnly 'org.jetbrains:annotations:24.1.0'
|
compileOnly 'org.jetbrains:annotations:24.1.0'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.32'
|
compileOnly 'org.projectlombok:lombok:1.18.34'
|
||||||
|
|
||||||
annotationProcessor 'org.projectlombok:lombok:1.18.32'
|
annotationProcessor 'org.projectlombok:lombok:1.18.34'
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
@@ -24,16 +30,15 @@ shadowJar {
|
|||||||
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
relocate 'org.intellij', 'net.william278.husksync.libraries'
|
||||||
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
relocate 'com.zaxxer', 'net.william278.husksync.libraries'
|
||||||
relocate 'de.exlll', 'net.william278.husksync.libraries'
|
relocate 'de.exlll', 'net.william278.husksync.libraries'
|
||||||
|
relocate 'net.william278.uniform', 'net.william278.husksync.libraries.uniform'
|
||||||
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
relocate 'net.william278.desertwell', 'net.william278.husksync.libraries.desertwell'
|
||||||
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
relocate 'net.william278.paginedown', 'net.william278.husksync.libraries.paginedown'
|
||||||
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
relocate 'net.william278.mapdataapi', 'net.william278.husksync.libraries.mapdataapi'
|
||||||
relocate 'net.william278.andjam', 'net.william278.husksync.libraries.andjam'
|
|
||||||
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
relocate 'net.william278.mpdbconverter', 'net.william278.husksync.libraries.mpdbconverter'
|
||||||
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
relocate 'net.william278.hslmigrator', 'net.william278.husksync.libraries.hslconverter'
|
||||||
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
relocate 'org.json', 'net.william278.husksync.libraries.json'
|
||||||
relocate 'net.querz', 'net.william278.husksync.libraries.nbtparser'
|
relocate 'net.querz', 'net.william278.husksync.libraries.nbtparser'
|
||||||
relocate 'net.roxeez', 'net.william278.husksync.libraries'
|
relocate 'net.roxeez', 'net.william278.husksync.libraries'
|
||||||
relocate 'me.lucko.commodore', 'net.william278.husksync.libraries.commodore'
|
|
||||||
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
relocate 'org.bstats', 'net.william278.husksync.libraries.bstats'
|
||||||
relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui'
|
relocate 'dev.triumphteam.gui', 'net.william278.husksync.libraries.triumphgui'
|
||||||
relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib'
|
relocate 'space.arim.morepaperlib', 'net.william278.husksync.libraries.paperlib'
|
||||||
@@ -41,3 +46,9 @@ shadowJar {
|
|||||||
|
|
||||||
minimize()
|
minimize()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks {
|
||||||
|
runServer {
|
||||||
|
minecraftVersion('1.21.1')
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ package net.william278.husksync;
|
|||||||
import net.kyori.adventure.audience.Audience;
|
import net.kyori.adventure.audience.Audience;
|
||||||
import net.william278.husksync.listener.BukkitEventListener;
|
import net.william278.husksync.listener.BukkitEventListener;
|
||||||
import net.william278.husksync.listener.PaperEventListener;
|
import net.william278.husksync.listener.PaperEventListener;
|
||||||
|
import net.william278.uniform.Uniform;
|
||||||
|
import net.william278.uniform.paper.PaperUniform;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@@ -43,4 +45,9 @@ public class PaperHuskSync extends BukkitHuskSync {
|
|||||||
return player == null || !player.isOnline() ? Audience.empty() : player;
|
return player == null || !player.isOnline() ? Audience.empty() : player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public Uniform getUniform() {
|
||||||
|
return PaperUniform.getInstance(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
certifi==2023.7.22
|
certifi==2024.7.4
|
||||||
charset-normalizer==3.2.0
|
charset-normalizer==3.2.0
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
idna==3.7
|
idna==3.7
|
||||||
requests==2.32.0
|
requests==2.32.0
|
||||||
tqdm==4.66.3
|
tqdm==4.66.3
|
||||||
urllib3==2.0.7
|
urllib3==2.2.2
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from tqdm import tqdm
|
|||||||
class Parameters:
|
class Parameters:
|
||||||
root_dir = './servers/'
|
root_dir = './servers/'
|
||||||
proxy_version = "1.21"
|
proxy_version = "1.21"
|
||||||
minecraft_version = '1.21'
|
minecraft_version = '1.21.1'
|
||||||
eula_agreement = 'true'
|
eula_agreement = 'true'
|
||||||
|
|
||||||
backend_names = ['alpha', 'beta']
|
backend_names = ['alpha', 'beta']
|
||||||
@@ -33,7 +33,7 @@ class Parameters:
|
|||||||
proxy_plugins = []
|
proxy_plugins = []
|
||||||
proxy_plugin_folders = []
|
proxy_plugin_folders = []
|
||||||
|
|
||||||
just_update_plugins = True
|
just_update_plugins = False
|
||||||
|
|
||||||
|
|
||||||
def main(update=False):
|
def main(update=False):
|
||||||
|
|||||||
Reference in New Issue
Block a user