mirror of
https://github.com/WiIIiam278/HuskSync.git
synced 2025-12-26 18:19:10 +00:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ada0497ec | ||
|
|
e9f2856040 | ||
|
|
6050c584c0 | ||
|
|
7ebf91bfae | ||
|
|
2d7799628a | ||
|
|
1627de732b | ||
|
|
fea882c642 | ||
|
|
8b749357f7 | ||
|
|
e4ff7e6d6c | ||
|
|
396630821f | ||
|
|
70f65d126b | ||
|
|
e8925a0d79 | ||
|
|
2a3cf9be7d | ||
|
|
971d3f5167 | ||
|
|
99da65a4d8 | ||
|
|
25744b4ef7 | ||
|
|
8f2d1c7298 | ||
|
|
a9aa93a682 | ||
|
|
ef340840ab | ||
|
|
cf08015961 | ||
|
|
cb09e0cfb2 | ||
|
|
554fac89c0 | ||
|
|
215bed9908 |
10
.github/workflows/ci.yml
vendored
10
.github/workflows/ci.yml
vendored
@@ -55,15 +55,19 @@ jobs:
|
|||||||
paper-1.21.1
|
paper-1.21.1
|
||||||
paper-1.21.4
|
paper-1.21.4
|
||||||
paper-1.21.5
|
paper-1.21.5
|
||||||
|
paper-1.21.6
|
||||||
fabric-1.20.1
|
fabric-1.20.1
|
||||||
fabric-1.21.1
|
fabric-1.21.1
|
||||||
fabric-1.21.4
|
fabric-1.21.4
|
||||||
fabric-1.21.5
|
fabric-1.21.5
|
||||||
|
fabric-1.21.6
|
||||||
distro-groups: |
|
distro-groups: |
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
fabric
|
fabric
|
||||||
fabric
|
fabric
|
||||||
fabric
|
fabric
|
||||||
@@ -73,16 +77,20 @@ jobs:
|
|||||||
Paper 1.21.1
|
Paper 1.21.1
|
||||||
Paper 1.21.4
|
Paper 1.21.4
|
||||||
Paper 1.21.5
|
Paper 1.21.5
|
||||||
|
Paper 1.21.6
|
||||||
Fabric 1.20.1
|
Fabric 1.20.1
|
||||||
Fabric 1.21.1
|
Fabric 1.21.1
|
||||||
Fabric 1.21.4
|
Fabric 1.21.4
|
||||||
Fabric 1.21.5
|
Fabric 1.21.5
|
||||||
|
Fabric 1.21.6
|
||||||
files: |
|
files: |
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.20.1.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.1.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.1.jar
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.4.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.5.jar
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.5.jar
|
||||||
|
target/HuskSync-Bukkit-${{ env.version_name }}+mc.1.21.6.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.1.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.1.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.4.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.5.jar
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.5.jar
|
||||||
|
target/HuskSync-Fabric-${{ env.version_name }}+mc.1.21.6.jar
|
||||||
10
.github/workflows/release.yml
vendored
10
.github/workflows/release.yml
vendored
@@ -44,15 +44,19 @@ jobs:
|
|||||||
paper-1.21.1
|
paper-1.21.1
|
||||||
paper-1.21.4
|
paper-1.21.4
|
||||||
paper-1.21.5
|
paper-1.21.5
|
||||||
|
paper-1.21.6
|
||||||
fabric-1.20.1
|
fabric-1.20.1
|
||||||
fabric-1.21.1
|
fabric-1.21.1
|
||||||
fabric-1.21.4
|
fabric-1.21.4
|
||||||
fabric-1.21.5
|
fabric-1.21.5
|
||||||
|
fabric-1.21.6
|
||||||
distro-groups: |
|
distro-groups: |
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
paper
|
paper
|
||||||
|
paper
|
||||||
|
fabric
|
||||||
fabric
|
fabric
|
||||||
fabric
|
fabric
|
||||||
fabric
|
fabric
|
||||||
@@ -62,16 +66,20 @@ jobs:
|
|||||||
Paper 1.21.1
|
Paper 1.21.1
|
||||||
Paper 1.21.4
|
Paper 1.21.4
|
||||||
Paper 1.21.5
|
Paper 1.21.5
|
||||||
|
Paper 1.21.6
|
||||||
Fabric 1.20.1
|
Fabric 1.20.1
|
||||||
Fabric 1.21.1
|
Fabric 1.21.1
|
||||||
Fabric 1.21.4
|
Fabric 1.21.4
|
||||||
Fabric 1.21.5
|
Fabric 1.21.5
|
||||||
|
Fabric 1.21.6
|
||||||
files: |
|
files: |
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
||||||
|
target/HuskSync-Bukkit-${{ github.event.release.tag_name }}+mc.1.21.6.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.20.1.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.1.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.4.jar
|
||||||
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.5.jar
|
||||||
|
target/HuskSync-Fabric-${{ github.event.release.tag_name }}+mc.1.21.6.jar
|
||||||
@@ -48,7 +48,8 @@ HuskSync supports the following [compatible versions](https://william278.net/doc
|
|||||||
|
|
||||||
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||||
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
||||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
| 1.21.6 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||||
|
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
||||||
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
||||||
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
||||||
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ plugins {
|
|||||||
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
id 'org.cadixdev.licenser' version '0.6.1' apply false
|
||||||
id 'dev.architectury.loom' version '1.9-SNAPSHOT' apply false
|
id 'dev.architectury.loom' version '1.9-SNAPSHOT' apply false
|
||||||
id 'gg.essential.multi-version.root' apply false
|
id 'gg.essential.multi-version.root' apply false
|
||||||
id 'org.ajoberstar.grgit' version '5.3.0'
|
id 'org.ajoberstar.grgit' version '5.3.2'
|
||||||
id 'maven-publish'
|
id 'maven-publish'
|
||||||
id 'java'
|
id 'java'
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ allprojects {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation(platform("org.junit:junit-bom:5.12.2"))
|
testImplementation(platform("org.junit:junit-bom:5.13.1"))
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter'
|
testImplementation 'org.junit.jupiter:junit-jupiter'
|
||||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||||
testCompileOnly 'org.jetbrains:annotations:26.0.2'
|
testCompileOnly 'org.jetbrains:annotations:26.0.2'
|
||||||
|
|||||||
3
bukkit/1.21.6/gradle.properties
Normal file
3
bukkit/1.21.6/gradle.properties
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
minecraft_version_numeric=12106
|
||||||
|
minecraft_api_version=1.21
|
||||||
|
paper_api_version=1.21.6-R0.1-SNAPSHOT
|
||||||
@@ -8,9 +8,9 @@ plugins {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation project(path: ':common')
|
implementation project(path: ':common')
|
||||||
|
|
||||||
implementation 'net.william278.uniform:uniform-bukkit:1.3.3'
|
implementation 'net.william278.uniform:uniform-bukkit:1.3.5'
|
||||||
implementation 'net.william278.uniform:uniform-paper:1.3.4'
|
implementation 'net.william278.uniform:uniform-paper:1.3.5'
|
||||||
implementation 'net.william278.toilet:toilet-bukkit:1.0.13'
|
implementation 'net.william278.toilet:toilet-bukkit:1.0.14'
|
||||||
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:2.0'
|
implementation 'net.william278:mapdataapi:2.0'
|
||||||
@@ -18,14 +18,14 @@ dependencies {
|
|||||||
implementation 'net.kyori:adventure-platform-bukkit:4.4.0'
|
implementation 'net.kyori:adventure-platform-bukkit:4.4.0'
|
||||||
implementation 'dev.triumphteam:triumph-gui:3.1.12'
|
implementation 'dev.triumphteam:triumph-gui:3.1.12'
|
||||||
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
implementation 'space.arim.morepaperlib:morepaperlib:0.4.4'
|
||||||
implementation 'de.tr7zw:item-nbt-api:2.15.0'
|
implementation 'de.tr7zw:item-nbt-api:2.15.1-SNAPSHOT'
|
||||||
|
|
||||||
compileOnly "io.papermc.paper:paper-api:${paper_api_version}"
|
compileOnly "io.papermc.paper:paper-api:${paper_api_version}"
|
||||||
compileOnly 'com.github.retrooper:packetevents-spigot:2.7.0'
|
compileOnly 'com.github.retrooper:packetevents-spigot:2.8.0'
|
||||||
compileOnly 'com.github.dmulloy2:ProtocolLib:5.3.0'
|
compileOnly 'com.github.dmulloy2:ProtocolLib:5.3.0'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.38'
|
compileOnly 'org.projectlombok:lombok:1.18.38'
|
||||||
compileOnly 'commons-io:commons-io:2.19.0'
|
compileOnly 'commons-io:commons-io:2.19.0'
|
||||||
compileOnly 'org.json:json:20250107'
|
compileOnly 'org.json:json:20250517'
|
||||||
compileOnly 'net.william278:minedown:1.8.2'
|
compileOnly 'net.william278:minedown:1.8.2'
|
||||||
compileOnly 'de.exlll:configlib-yaml:4.6.1'
|
compileOnly 'de.exlll:configlib-yaml:4.6.1'
|
||||||
compileOnly 'com.zaxxer:HikariCP:6.3.0'
|
compileOnly 'com.zaxxer:HikariCP:6.3.0'
|
||||||
|
|||||||
@@ -354,9 +354,11 @@ public class BukkitHuskSync extends JavaPlugin implements HuskSync, BukkitTask.S
|
|||||||
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", "1.21.1" -> DataFixerUtil.VERSION1_21;
|
case "1.21", "1.21.1" -> DataFixerUtil.VERSION1_21;
|
||||||
case "1.21.2", "1.21.3" -> DataFixerUtil.VERSION1_21_2;
|
case "1.21.2" -> DataFixerUtil.VERSION1_21_2;
|
||||||
case "1.21.4" -> 4189;
|
case "1.21.3" -> DataFixerUtil.VERSION1_21_3;
|
||||||
|
case "1.21.4" -> DataFixerUtil.VERSION1_21_4;
|
||||||
case "1.21.5" -> DataFixerUtil.VERSION1_21_5;
|
case "1.21.5" -> DataFixerUtil.VERSION1_21_5;
|
||||||
|
case "1.21.6" -> 4435;
|
||||||
default -> DataFixerUtil.getCurrentVersion();
|
default -> DataFixerUtil.getCurrentVersion();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@SuppressWarnings("UnstableApiUsage")
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
@@ -46,13 +47,20 @@ public class PaperHuskSyncLoader implements PluginLoader {
|
|||||||
resolveLibraries(classpathBuilder).stream()
|
resolveLibraries(classpathBuilder).stream()
|
||||||
.map(DefaultArtifact::new)
|
.map(DefaultArtifact::new)
|
||||||
.forEach(artifact -> resolver.addDependency(new Dependency(artifact, null)));
|
.forEach(artifact -> resolver.addDependency(new Dependency(artifact, null)));
|
||||||
resolver.addRepository(new RemoteRepository.Builder(
|
resolver.addRepository(new RemoteRepository.Builder("maven", "default", getMavenUrl()).build());
|
||||||
"maven", "default", "https://repo.maven.apache.org/maven2/"
|
|
||||||
).build());
|
|
||||||
|
|
||||||
classpathBuilder.addLibrary(resolver);
|
classpathBuilder.addLibrary(resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private static String getMavenUrl() {
|
||||||
|
return Stream.of(
|
||||||
|
System.getenv("PAPER_DEFAULT_CENTRAL_REPOSITORY"),
|
||||||
|
System.getProperty("org.bukkit.plugin.java.LibraryLoader.centralURL"),
|
||||||
|
"https://maven-central.storage-download.googleapis.com/maven2"
|
||||||
|
).filter(Objects::nonNull).findFirst().orElseThrow(IllegalStateException::new);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private static List<String> resolveLibraries(@NotNull PluginClasspathBuilder classpathBuilder) {
|
private static List<String> resolveLibraries(@NotNull PluginClasspathBuilder classpathBuilder) {
|
||||||
try (InputStream input = getLibraryListFile()) {
|
try (InputStream input = getLibraryListFile()) {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ public class BukkitEventListener extends EventListener implements BukkitJoinEven
|
|||||||
@EventHandler(ignoreCancelled = true)
|
@EventHandler(ignoreCancelled = true)
|
||||||
public void onMapInitialize(@NotNull MapInitializeEvent event) {
|
public void onMapInitialize(@NotNull MapInitializeEvent event) {
|
||||||
if (plugin.getSettings().getSynchronization().isPersistLockedMaps() && event.getMap().isLocked()) {
|
if (plugin.getSettings().getSynchronization().isPersistLockedMaps() && event.getMap().isLocked()) {
|
||||||
getPlugin().runAsync(() -> ((BukkitHuskSync) plugin).renderPersistedMap(event.getMap()));
|
getPlugin().runAsync(() -> ((BukkitHuskSync) plugin).renderInitializingLockedMap(event.getMap()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -254,111 +254,139 @@ public interface BukkitMapHandler {
|
|||||||
if (!nbt.hasTag(MAP_DATA_KEY)) {
|
if (!nbt.hasTag(MAP_DATA_KEY)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ReadableNBT mapData = nbt.getCompound(MAP_DATA_KEY);
|
final ReadableNBT mapData = nbt.getCompound(MAP_DATA_KEY);
|
||||||
if (mapData == null) {
|
if (mapData == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine map ID
|
// Server the map was originally created on, and the current server. If they match, isOrigin is true.
|
||||||
final String originServerName = mapData.getString(MAP_ORIGIN_KEY);
|
final String originServer = mapData.getString(MAP_ORIGIN_KEY);
|
||||||
final String currentServerName = getPlugin().getServerName();
|
final String currentServer = getPlugin().getServerName();
|
||||||
final int originalMapId = mapData.getInteger(MAP_ID_KEY);
|
final boolean isOrigin = currentServer.equals(originServer);
|
||||||
int newId = currentServerName.equals(originServerName)
|
|
||||||
? originalMapId : getBoundMapId(originServerName, originalMapId, currentServerName);
|
// Determine the map's ID on its origin server, and the new ID it should be bound to here.
|
||||||
|
// Then, update the map item / data accordingly (re-rendering and caching the map if needed)
|
||||||
|
final int originalId = mapData.getInteger(MAP_ID_KEY);
|
||||||
|
int newId = isOrigin ? originalId : getBoundMapId(originServer, originalId, currentServer);
|
||||||
if (newId != -1) {
|
if (newId != -1) {
|
||||||
final MapView view = Bukkit.getMap(newId);
|
handleBoundMap(meta, nbt, originServer, originalId, newId, isOrigin);
|
||||||
if (view != null) {
|
} else {
|
||||||
meta.setMapView(view);
|
handleUnboundMap(meta, nbt, originServer, originalId, currentServer);
|
||||||
meta.setMapId(newId);
|
|
||||||
map.setItemMeta(meta);
|
|
||||||
getPlugin().debug(String.format("Map ID set to #%s", newId));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
getPlugin().debug(String.format("Map ID #%s not saved on this server, creating...", newId));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the pixel data from the ItemStack and generate a map view otherwise
|
|
||||||
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
|
||||||
@Nullable Map.Entry<MapData, Boolean> readMapData = readMapData(originServerName, originalMapId);
|
|
||||||
if (readMapData == null && nbt.hasTag(MAP_LEGACY_PIXEL_DATA_KEY)) {
|
|
||||||
readMapData = readLegacyMapItemData(nbt);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If map data was found, add a renderer to the MapView
|
|
||||||
if (readMapData == null) {
|
|
||||||
getPlugin().debug("Read pixel data was not found in database, skipping...");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final MapData canvasData = Objects.requireNonNull(readMapData, "Pixel data null!").getKey();
|
|
||||||
final MapView view = generateRenderedMap(canvasData);
|
|
||||||
meta.setMapView(view);
|
|
||||||
map.setItemMeta(meta);
|
map.setItemMeta(meta);
|
||||||
|
|
||||||
// Bind in the database & Redis
|
|
||||||
final int id = view.getId();
|
|
||||||
getRedisManager().bindMapIds(originServerName, originalMapId, currentServerName, id);
|
|
||||||
getPlugin().getDatabase().setMapBinding(originServerName, originalMapId, currentServerName, id);
|
|
||||||
meta.setMapId(id);
|
|
||||||
|
|
||||||
getPlugin().debug(String.format("Bound map to view (#%s) on server %s", id, currentServerName));
|
|
||||||
});
|
});
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
default void renderPersistedMap(@NotNull MapView view) {
|
private void handleBoundMap(@NotNull MapMeta meta, @NotNull ReadableItemNBT nbt, @NotNull String originServer,
|
||||||
if (getMapView(view.getId()).isPresent()) {
|
int originalId, int newId, boolean isOrigin) {
|
||||||
|
MapView view = Bukkit.getMap(newId);
|
||||||
|
if (isOrigin && view != null) {
|
||||||
|
meta.setMapView(view);
|
||||||
|
getPlugin().debug("Map ID set to original ID #%s".formatted(newId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read map data, or
|
Optional<MapView> optionalView = getMapView(newId);
|
||||||
@Nullable Map.Entry<MapData, Boolean> data = readMapData(getPlugin().getServerName(), view.getId());
|
if (optionalView.isPresent()) {
|
||||||
|
meta.setMapView(optionalView.get());
|
||||||
|
getPlugin().debug("Map ID set to #%s".formatted(newId));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
||||||
|
Map.Entry<MapData, Boolean> mapData = readMapData(originServer, originalId);
|
||||||
|
if (mapData == null && nbt.hasTag(MAP_LEGACY_PIXEL_DATA_KEY)) {
|
||||||
|
mapData = readLegacyMapItemData(nbt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapData == null) {
|
||||||
|
getPlugin().debug("Read pixel data was not found in database, skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
MapView newView = view != null ? view : Bukkit.createMap(getDefaultMapWorld());
|
||||||
|
generateRenderedMap(Objects.requireNonNull(mapData).getKey(), newView);
|
||||||
|
meta.setMapView(newView);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleUnboundMap(@NotNull MapMeta meta, @NotNull ReadableItemNBT nbt, @NotNull String originServer,
|
||||||
|
int originalId, @NotNull String currentServer) {
|
||||||
|
getPlugin().debug("Deserializing map data from NBT and generating view...");
|
||||||
|
Map.Entry<MapData, Boolean> mapData = readMapData(originServer, originalId);
|
||||||
|
if (mapData == null && nbt.hasTag(MAP_LEGACY_PIXEL_DATA_KEY)) {
|
||||||
|
mapData = readLegacyMapItemData(nbt);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapData == null) {
|
||||||
|
getPlugin().debug("Read pixel data was not found in database, skipping...");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final MapView view = generateRenderedMap(Objects.requireNonNull(mapData, "Pixel data null!").getKey());
|
||||||
|
meta.setMapView(view);
|
||||||
|
|
||||||
|
final int id = view.getId();
|
||||||
|
getRedisManager().bindMapIds(originServer, originalId, currentServer, id);
|
||||||
|
getPlugin().getDatabase().setMapBinding(originServer, originalId, currentServer, id);
|
||||||
|
|
||||||
|
getPlugin().debug("Bound map to view (#%s) on server %s".formatted(id, currentServer));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render a persisted locked map that is initializing (i.e. in an item frame)
|
||||||
|
default void renderInitializingLockedMap(@NotNull MapView view) {
|
||||||
|
if (view.isVirtual()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Optional<MapView> optionalView = getMapView(view.getId());
|
||||||
|
if (optionalView.isPresent()) {
|
||||||
|
view.getRenderers().clear();
|
||||||
|
view.getRenderers().addAll(optionalView.get().getRenderers());
|
||||||
|
view.setLocked(true);
|
||||||
|
view.setScale(MapView.Scale.NORMAL);
|
||||||
|
view.setTrackingPosition(false);
|
||||||
|
view.setUnlimitedTracking(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map.Entry<MapData, Boolean> data = readMapData(getPlugin().getServerName(), view.getId());
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
data = readLegacyMapFileData(view.getId());
|
data = readLegacyMapFileData(view.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't render maps with no data
|
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
final World world = view.getWorld() == null ? getDefaultMapWorld() : view.getWorld();
|
World world = view.getWorld() == null ? getDefaultMapWorld() : view.getWorld();
|
||||||
getPlugin().debug("Not rendering map: no data in DB for world %s, map #%s."
|
getPlugin().debug("Not rendering map: no data in DB for world %s, map #%s."
|
||||||
.formatted(world.getName(), view.getId()));
|
.formatted(world.getName(), view.getId()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Don't render persisted maps on this server
|
|
||||||
if (data.getValue()) {
|
if (data.getValue()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
renderMapView(view, data.getKey());
|
||||||
final MapData canvasData = data.getKey();
|
|
||||||
|
|
||||||
// Create a new map view renderer with the map data color at each pixel
|
|
||||||
// use view.removeRenderer() to remove all this maps renderers
|
|
||||||
view.getRenderers().forEach(view::removeRenderer);
|
|
||||||
view.addRenderer(new PersistentMapRenderer(canvasData));
|
|
||||||
view.setLocked(true);
|
|
||||||
view.setScale(MapView.Scale.NORMAL);
|
|
||||||
view.setTrackingPosition(false);
|
|
||||||
view.setUnlimitedTracking(false);
|
|
||||||
|
|
||||||
// Set the view to the map
|
|
||||||
setMapView(view);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the renderer of a map, and returns the generated MapView
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private MapView generateRenderedMap(@NotNull MapData canvasData) {
|
private MapView generateRenderedMap(@NotNull MapData canvasData) {
|
||||||
final MapView view = Bukkit.createMap(getDefaultMapWorld());
|
return generateRenderedMap(canvasData, Bukkit.createMap(getDefaultMapWorld()));
|
||||||
view.getRenderers().clear();
|
}
|
||||||
|
|
||||||
// Create a new map view renderer with the map data color at each pixel
|
@NotNull
|
||||||
|
private MapView generateRenderedMap(@NotNull MapData canvasData, @NotNull MapView view) {
|
||||||
|
renderMapView(view, canvasData);
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void renderMapView(@NotNull MapView view, @NotNull MapData canvasData) {
|
||||||
|
view.getRenderers().clear();
|
||||||
view.addRenderer(new PersistentMapRenderer(canvasData));
|
view.addRenderer(new PersistentMapRenderer(canvasData));
|
||||||
view.setLocked(true);
|
view.setLocked(true);
|
||||||
view.setScale(MapView.Scale.NORMAL);
|
view.setScale(MapView.Scale.NORMAL);
|
||||||
view.setTrackingPosition(false);
|
view.setTrackingPosition(false);
|
||||||
view.setUnlimitedTracking(false);
|
view.setUnlimitedTracking(false);
|
||||||
|
|
||||||
// Set the view to the map and return it
|
|
||||||
setMapView(view);
|
setMapView(view);
|
||||||
return view;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ dependencies {
|
|||||||
api 'org.apache.commons:commons-text:1.13.1'
|
api 'org.apache.commons:commons-text:1.13.1'
|
||||||
api 'net.william278:minedown:1.8.2'
|
api 'net.william278:minedown:1.8.2'
|
||||||
api 'net.william278:mapdataapi:2.0'
|
api 'net.william278:mapdataapi:2.0'
|
||||||
api 'org.json:json:20250107'
|
api 'org.json:json:20250517'
|
||||||
api 'com.google.code.gson:gson:2.13.0'
|
api 'com.google.code.gson:gson:2.13.1'
|
||||||
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
api 'com.fatboyindustrial.gson-javatime-serialisers:gson-javatime-serialisers:1.1.2'
|
||||||
api 'de.exlll:configlib-yaml:4.6.1'
|
api 'de.exlll:configlib-yaml:4.6.1'
|
||||||
api 'net.william278:paginedown:1.1.2'
|
api 'net.william278:paginedown:1.1.2'
|
||||||
@@ -17,15 +17,15 @@ dependencies {
|
|||||||
exclude module: 'slf4j-api'
|
exclude module: 'slf4j-api'
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOnlyApi 'net.william278.toilet:toilet-common:1.0.13'
|
compileOnlyApi 'net.william278.toilet:toilet-common:1.0.14'
|
||||||
|
|
||||||
compileOnly 'net.william278.uniform:uniform-common:1.3.3'
|
compileOnly 'net.william278.uniform:uniform-common:1.3.5'
|
||||||
compileOnly 'com.mojang:brigadier:1.1.8'
|
compileOnly 'com.mojang:brigadier:1.1.8'
|
||||||
compileOnly 'org.projectlombok:lombok:1.18.38'
|
compileOnly 'org.projectlombok:lombok:1.18.38'
|
||||||
compileOnly 'org.jetbrains:annotations:26.0.2'
|
compileOnly 'org.jetbrains:annotations:26.0.2'
|
||||||
compileOnly 'net.kyori:adventure-api:4.20.0'
|
compileOnly 'net.kyori:adventure-api:4.23.0'
|
||||||
compileOnly 'net.kyori:adventure-platform-api:4.4.0'
|
compileOnly 'net.kyori:adventure-platform-api:4.4.0'
|
||||||
compileOnly "net.kyori:adventure-text-serializer-plain:4.21.0"
|
compileOnly "net.kyori:adventure-text-serializer-plain:4.23.0"
|
||||||
compileOnly 'com.google.guava:guava:33.4.8-jre'
|
compileOnly 'com.google.guava:guava:33.4.8-jre'
|
||||||
compileOnly 'com.github.plan-player-analytics:Plan:5.6.2965'
|
compileOnly 'com.github.plan-player-analytics:Plan:5.6.2965'
|
||||||
compileOnly "redis.clients:jedis:$jedis_version"
|
compileOnly "redis.clients:jedis:$jedis_version"
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ public class Settings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 𝓡𝓮𝓭𝓲𝓼 settings
|
// Redis settings
|
||||||
@Comment("Redis settings")
|
@Comment("Redis settings")
|
||||||
private RedisSettings redis = new RedisSettings();
|
private RedisSettings redis = new RedisSettings();
|
||||||
|
|
||||||
@@ -159,7 +159,9 @@ public class Settings {
|
|||||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
public static class RedisSettings {
|
public static class RedisSettings {
|
||||||
|
|
||||||
@Comment("Specify the credentials of your Redis server here. Set \"password\" to '' if you don't have one")
|
@Comment({"Specify the credentials of your Redis server here.",
|
||||||
|
"Set \"user\" to '' if you don't have one or would like to use the default user.",
|
||||||
|
"Set \"password\" to '' if you don't have one."})
|
||||||
private RedisCredentials credentials = new RedisCredentials();
|
private RedisCredentials credentials = new RedisCredentials();
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@@ -168,6 +170,9 @@ public class Settings {
|
|||||||
public static class RedisCredentials {
|
public static class RedisCredentials {
|
||||||
private String host = "localhost";
|
private String host = "localhost";
|
||||||
private int port = 6379;
|
private int port = 6379;
|
||||||
|
@Comment("Only change the database if you know what you are doing. The default is 0.")
|
||||||
|
private int database = 0;
|
||||||
|
private String user = "";
|
||||||
private String password = "";
|
private String password = "";
|
||||||
private boolean useSsl = false;
|
private boolean useSsl = false;
|
||||||
}
|
}
|
||||||
@@ -321,6 +326,9 @@ public class Settings {
|
|||||||
@Getter(AccessLevel.NONE)
|
@Getter(AccessLevel.NONE)
|
||||||
private Map<String, String> eventPriorities = EventListener.ListenerType.getDefaults();
|
private Map<String, String> eventPriorities = EventListener.ListenerType.getDefaults();
|
||||||
|
|
||||||
|
@Comment("Enable check-in petitions for data syncing (don't change this unless you know what you're doing)")
|
||||||
|
private boolean checkinPetitions = true;
|
||||||
|
|
||||||
public boolean doAutoPin(@NotNull DataSnapshot.SaveCause cause) {
|
public boolean doAutoPin(@NotNull DataSnapshot.SaveCause cause) {
|
||||||
return autoPinnedSaveCauses.contains(cause.name());
|
return autoPinnedSaveCauses.contains(cause.name());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,9 +65,11 @@ public class RedisManager extends JedisPubSub {
|
|||||||
@Blocking
|
@Blocking
|
||||||
public void initialize() throws IllegalStateException {
|
public void initialize() throws IllegalStateException {
|
||||||
final Settings.RedisSettings.RedisCredentials credentials = plugin.getSettings().getRedis().getCredentials();
|
final Settings.RedisSettings.RedisCredentials credentials = plugin.getSettings().getRedis().getCredentials();
|
||||||
|
final String user = credentials.getUser();
|
||||||
final String password = credentials.getPassword();
|
final String password = credentials.getPassword();
|
||||||
final String host = credentials.getHost();
|
final String host = credentials.getHost();
|
||||||
final int port = credentials.getPort();
|
final int port = credentials.getPort();
|
||||||
|
final int database = credentials.getDatabase();
|
||||||
final boolean useSSL = credentials.isUseSsl();
|
final boolean useSSL = credentials.isUseSsl();
|
||||||
|
|
||||||
// Create the jedis pool
|
// Create the jedis pool
|
||||||
@@ -79,9 +81,20 @@ public class RedisManager extends JedisPubSub {
|
|||||||
final Settings.RedisSettings.RedisSentinel sentinel = plugin.getSettings().getRedis().getSentinel();
|
final Settings.RedisSettings.RedisSentinel sentinel = plugin.getSettings().getRedis().getSentinel();
|
||||||
Set<String> redisSentinelNodes = new HashSet<>(sentinel.getNodes());
|
Set<String> redisSentinelNodes = new HashSet<>(sentinel.getNodes());
|
||||||
if (redisSentinelNodes.isEmpty()) {
|
if (redisSentinelNodes.isEmpty()) {
|
||||||
this.jedisPool = password.isEmpty()
|
DefaultJedisClientConfig.Builder clientConfigBuilder = DefaultJedisClientConfig.builder()
|
||||||
? new JedisPool(config, host, port, 0, useSSL)
|
.ssl(useSSL)
|
||||||
: new JedisPool(config, host, port, 0, password, useSSL);
|
.database(database)
|
||||||
|
.timeoutMillis(0);
|
||||||
|
|
||||||
|
if (!user.isEmpty()) {
|
||||||
|
clientConfigBuilder.user(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!password.isEmpty()) {
|
||||||
|
clientConfigBuilder.password(password);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.jedisPool = new JedisPool(config, new HostAndPort(host, port), clientConfigBuilder.build());
|
||||||
} else {
|
} else {
|
||||||
final String sentinelPassword = sentinel.getPassword();
|
final String sentinelPassword = sentinel.getPassword();
|
||||||
this.jedisPool = new JedisSentinelPool(sentinel.getMaster(), redisSentinelNodes, password.isEmpty()
|
this.jedisPool = new JedisSentinelPool(sentinel.getMaster(), redisSentinelNodes, password.isEmpty()
|
||||||
@@ -177,7 +190,8 @@ public class RedisManager extends JedisPubSub {
|
|||||||
).dispatch(plugin, RedisMessage.Type.RETURN_USER_DATA)
|
).dispatch(plugin, RedisMessage.Type.RETURN_USER_DATA)
|
||||||
);
|
);
|
||||||
case CHECK_IN_PETITION -> {
|
case CHECK_IN_PETITION -> {
|
||||||
if (!redisMessage.isTargetServer(plugin)) {
|
if (!redisMessage.isTargetServer(plugin)
|
||||||
|
|| !plugin.getSettings().getSynchronization().isCheckinPetitions()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final String payload = new String(redisMessage.getPayload(), StandardCharsets.UTF_8);
|
final String payload = new String(redisMessage.getPayload(), StandardCharsets.UTF_8);
|
||||||
|
|||||||
@@ -54,7 +54,9 @@ public class LockstepDataSyncer extends DataSyncer {
|
|||||||
// If they are checked out, ask the server to check them back in and return false
|
// If they are checked out, ask the server to check them back in and return false
|
||||||
final Optional<String> server = getRedis().getUserCheckedOut(user);
|
final Optional<String> server = getRedis().getUserCheckedOut(user);
|
||||||
if (server.isPresent() && !server.get().equals(plugin.getServerName())) {
|
if (server.isPresent() && !server.get().equals(plugin.getServerName())) {
|
||||||
getRedis().petitionServerCheckin(server.get(), user);
|
if (plugin.getSettings().getSynchronization().isCheckinPetitions()) {
|
||||||
|
getRedis().petitionServerCheckin(server.get(), user);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ public interface DumpProvider {
|
|||||||
Map.entry("Redis Version", StatusLine.REDIS_VERSION.getValue(getPlugin())),
|
Map.entry("Redis Version", StatusLine.REDIS_VERSION.getValue(getPlugin())),
|
||||||
Map.entry("Redis Latency", StatusLine.REDIS_LATENCY.getValue(getPlugin())),
|
Map.entry("Redis Latency", StatusLine.REDIS_LATENCY.getValue(getPlugin())),
|
||||||
Map.entry("Redis Sentinel", StatusLine.USING_REDIS_SENTINEL.getValue(getPlugin())),
|
Map.entry("Redis Sentinel", StatusLine.USING_REDIS_SENTINEL.getValue(getPlugin())),
|
||||||
|
Map.entry("Redis Database", StatusLine.REDIS_DATABASE.getValue(getPlugin())),
|
||||||
|
Map.entry("Redis User", StatusLine.USING_REDIS_USER.getValue(getPlugin())),
|
||||||
Map.entry("Redis Password", StatusLine.USING_REDIS_PASSWORD.getValue(getPlugin())),
|
Map.entry("Redis Password", StatusLine.USING_REDIS_PASSWORD.getValue(getPlugin())),
|
||||||
Map.entry("Redis SSL", StatusLine.REDIS_USING_SSL.getValue(getPlugin())),
|
Map.entry("Redis SSL", StatusLine.REDIS_USING_SSL.getValue(getPlugin())),
|
||||||
Map.entry("Redis Local", StatusLine.IS_REDIS_LOCAL.getValue(getPlugin()))
|
Map.entry("Redis Local", StatusLine.IS_REDIS_LOCAL.getValue(getPlugin()))
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ public enum StatusLine {
|
|||||||
USING_REDIS_SENTINEL(plugin -> getBoolean(
|
USING_REDIS_SENTINEL(plugin -> getBoolean(
|
||||||
!plugin.getSettings().getRedis().getSentinel().getMaster().isBlank()
|
!plugin.getSettings().getRedis().getSentinel().getMaster().isBlank()
|
||||||
)),
|
)),
|
||||||
|
REDIS_DATABASE(plugin -> Component.text(plugin.getSettings().getRedis().getCredentials().getDatabase())),
|
||||||
|
USING_REDIS_USER(plugin -> getBoolean(
|
||||||
|
!plugin.getSettings().getRedis().getCredentials().getUser().isBlank()
|
||||||
|
)),
|
||||||
USING_REDIS_PASSWORD(plugin -> getBoolean(
|
USING_REDIS_PASSWORD(plugin -> getBoolean(
|
||||||
!plugin.getSettings().getRedis().getCredentials().getPassword().isBlank()
|
!plugin.getSettings().getRedis().getCredentials().getPassword().isBlank()
|
||||||
)),
|
)),
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ locales:
|
|||||||
data_list_title: '[%1% 的玩家資料快照:](#00fb9a) [(%2%-%3% 共](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
data_list_title: '[%1% 的玩家資料快照:](#00fb9a) [(%2%-%3% 共](#00fb9a) [%4%](#00fb9a bold)[)](#00fb9a)\n'
|
||||||
data_list_item: '[%1%](gray show_text=&7玩家資料快照 %2%\n&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7已標記:\n&8標記的快照將不會自動輪換。 run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7版本時間戳:\n&8資料儲存時間\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7儲存原因:\n&8觸發儲存的原因 run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7快照大小:\n&8快照的預估檔案大小(KiB) run_command=/userdata view %2% %3%)'
|
data_list_item: '[%1%](gray show_text=&7玩家資料快照 %2%\n&8⚡ %4% run_command=/userdata view %2% %3%) [%5%](#d8ff2b show_text=&7已標記:\n&8標記的快照將不會自動輪換。 run_command=/userdata view %2% %3%) [%6%](color=#ffc43b-#f5c962 show_text=&7版本時間戳:\n&8資料儲存時間\n&8%7% run_command=/userdata view %2% %3%) [⚑ %8%](#23a825-#36f539 show_text=&7儲存原因:\n&8觸發儲存的原因 run_command=/userdata view %2% %3%) [⏏ %9%](color=#62a9f5-#7ab8fa show_text=&7快照大小:\n&8快照的預估檔案大小(KiB) run_command=/userdata view %2% %3%)'
|
||||||
data_list_item_invalid: '[%1%](dark_gray show_text=&7玩家資料快照 %2%\n&8⚡ %4% suggest_command=/userdata delete %2% %3%) [%5%](dark_gray show_text=&7已標記:\n&8標記的快照將不會自動輪換。 suggest_command=/userdata delete %2% %3%) [%6% ⚑ %8% ⏏ %9%](gray strikethrough show_text=&#ff3300&無效的資料快照\n&#ff7e5e&點擊刪除\n\n&7⚠ %10% suggest_command=/userdata delete %2% %3%)'
|
data_list_item_invalid: '[%1%](dark_gray show_text=&7玩家資料快照 %2%\n&8⚡ %4% suggest_command=/userdata delete %2% %3%) [%5%](dark_gray show_text=&7已標記:\n&8標記的快照將不會自動輪換。 suggest_command=/userdata delete %2% %3%) [%6% ⚑ %8% ⏏ %9%](gray strikethrough show_text=&#ff3300&無效的資料快照\n&#ff7e5e&點擊刪除\n\n&7⚠ %10% suggest_command=/userdata delete %2% %3%)'
|
||||||
data_saved: '[Successfully saved a snapshot of %1%''s current user data.](#00fb9a)'
|
data_saved: '[✅ 成功儲存 %1% 的目前使用者資料快照。](#00fb9a)'
|
||||||
data_deleted: '[❌ 成功刪除:](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
data_deleted: '[❌ 成功刪除:](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
||||||
data_restored: '[⏪ 成功將玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&8%2%)[的資料恢復為 快照:](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\n&8%4%)'
|
data_restored: '[⏪ 成功將玩家](#00fb9a) [%1%](#00fb9a show_text=&7玩家 UUID:\n&8%2%)[的資料恢復為 快照:](#00fb9a) [%3%.](#00fb9a show_text=&7Version UUID:\n&8%4%)'
|
||||||
data_pinned: '[※ 成功標記](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
data_pinned: '[※ 成功標記](#00fb9a) [%3%](#00fb9a show_text=&7玩家 UUID:\n&8%4%) [的快照:](#00fb9a) [%1%](#00fb9a show_text=&7Version UUID:\n&8%2%)'
|
||||||
@@ -41,8 +41,8 @@ locales:
|
|||||||
save_cause_world_save: '世界儲存'
|
save_cause_world_save: '世界儲存'
|
||||||
save_cause_death: '死亡'
|
save_cause_death: '死亡'
|
||||||
save_cause_server_shutdown: '伺服器關閉'
|
save_cause_server_shutdown: '伺服器關閉'
|
||||||
save_cause_save_command: 'save command'
|
save_cause_save_command: '儲存指令'
|
||||||
save_cause_dump_command: 'dump command'
|
save_cause_dump_command: '導出指令'
|
||||||
save_cause_inventory_command: '背包指令'
|
save_cause_inventory_command: '背包指令'
|
||||||
save_cause_enderchest_command: '終界箱指令'
|
save_cause_enderchest_command: '終界箱指令'
|
||||||
save_cause_backup_restore: '備份還原'
|
save_cause_backup_restore: '備份還原'
|
||||||
@@ -54,9 +54,9 @@ locales:
|
|||||||
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本: v%1% (running: v%2%).](#ff7e5e)'
|
update_available: '[HuskSync](#ff7e5e bold) [| 發現可用的新版本: v%1% (running: v%2%).](#ff7e5e)'
|
||||||
reload_complete: '[HuskSync](#00fb9a bold) [| 配置和語言文件已重新加載。](#00fb9a)\n[⚠ 確保所有伺服器上的配置文件都是最新的!](#00fb9a)\n[重啟後配置變更才會生效。](#00fb9a italic)'
|
reload_complete: '[HuskSync](#00fb9a bold) [| 配置和語言文件已重新加載。](#00fb9a)\n[⚠ 確保所有伺服器上的配置文件都是最新的!](#00fb9a)\n[重啟後配置變更才會生效。](#00fb9a italic)'
|
||||||
system_status_header: '[HuskSync](#00fb9a bold) [| 系統狀態報告:](#00fb9a)'
|
system_status_header: '[HuskSync](#00fb9a bold) [| 系統狀態報告:](#00fb9a)'
|
||||||
system_dump_confirm: '[HuskSync](#00fb9a bold) [| Prepare a system dump? This will include:](#00fb9a)\n[• Your latest server logs and HuskSync config files](gray)\n[• Current plugin system status information](gray)\n[• Information about your Java & Minecraft server environment](gray)\n[• A list of other currently installed plugins](gray)\n[To confirm, use:](#00fb9a) [/husksync dump confirm](#00fb9a italic show_text=&7Click to prepare dump run_command=/husksync dump confirm)'
|
system_dump_confirm: '[HuskSync](#00fb9a bold) [| 要產生系統狀態紀錄檔嗎?這將包含以下內容:](#00fb9a)\n[• 最近的伺服器日誌與 HuskSync 設定檔](gray)\n[• 插件目前的系統狀態資訊](gray)\n[• 有關您的 Java 與 Minecraft 伺服器環境的資訊](gray)\n[• 目前已安裝的其他插件清單](gray)\n[若要確認,請輸入:](#00fb9a) [/husksync dump confirm](#00fb9a italic show_text=&7點擊以產生紀錄檔 run_command=/husksync dump confirm)'
|
||||||
system_dump_started: '[HuskSync](#00fb9a bold) [| Preparing system status dump, please wait…](#00fb9a)'
|
system_dump_started: '[HuskSync](#00fb9a bold) [| 正在產生系統狀態紀錄檔,請稍候…](#00fb9a)'
|
||||||
system_dump_ready: '[HuskSync](#00fb9a bold) [| System status dump prepared! Click to view:](#00fb9a)'
|
system_dump_ready: '[HuskSync](#00fb9a bold) [| 系統狀態紀錄檔已完成!點擊以下連結以查看:](#00fb9a)'
|
||||||
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&點擊建議 suggest_command=%1%)'
|
error_invalid_syntax: '[錯誤:](#ff3300) [語法不正確,用法:](#ff7e5e) [%1%](#ff7e5e italic show_text=&#ff7e5e&點擊建議 suggest_command=%1%)'
|
||||||
error_invalid_player: '[錯誤:](#ff3300) [找不到這位玩家](#ff7e5e)'
|
error_invalid_player: '[錯誤:](#ff3300) [找不到這位玩家](#ff7e5e)'
|
||||||
error_invalid_data: '[錯誤:](#ff3300) [無法解壓使用者資料,因為快照無效或已損壞。](#ff7e5e) [(詳細資訊…)](gray show_text=&7⚠ %1%)'
|
error_invalid_data: '[錯誤:](#ff3300) [無法解壓使用者資料,因為快照無效或已損壞。](#ff7e5e) [(詳細資訊…)](gray show_text=&7⚠ %1%)'
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ HuskSync supports the following versions of Minecraft. Since v3.7, you must down
|
|||||||
|
|
||||||
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
| Minecraft | Latest HuskSync | Java Version | Platforms | Support Status |
|
||||||
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
|:---------------:|:---------------:|:------------:|:--------------|:------------------------------|
|
||||||
| 1.21.5 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
| 1.21.6 | _latest_ | 21 | Paper | ✅ **Active Release** |
|
||||||
|
| 1.21.5 | _latest_ | 21 | Paper | ✅ **January 2026** (Non-LTS) |
|
||||||
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
| 1.21.4 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (Non-LTS) |
|
||||||
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
| 1.21.3 | 3.7.1 | 21 | Paper, Fabric | 🗃️ Archived (December 2024) |
|
||||||
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
| 1.21.1 | _latest_ | 21 | Paper, Fabric | ✅ **November 2025** (LTS) |
|
||||||
@@ -32,5 +33,5 @@ This plugin does not support the following software-Minecraft version combinatio
|
|||||||
## Incompatible plugins / mods
|
## Incompatible plugins / mods
|
||||||
Please note the following plugins / mods can cause issues with HuskSync:
|
Please note the following plugins / mods can cause issues with HuskSync:
|
||||||
|
|
||||||
* Restart plugins / mods are not supported. These will cause [player data to not save correctly when your server restarts](troubleshooting#issues-with-player-data-going-out-of-sync-during-a-server-restart) due to the way these plugins utilise bash scripts. It's important to understand that restart plugins don't actually restart yur server, they just trigger some (often unstable) process-killing scripting logic to occur!
|
* Restart plugins / mods are not supported. These will cause [player data to not save correctly when your server restarts](troubleshooting#issues-with-player-data-going-out-of-sync-during-a-server-restart) due to the way these plugins utilise bash scripts. It's important to understand that restart plugins don't actually restart your server, they just trigger some (often unstable) process-killing scripting logic to occur!
|
||||||
* Combat logging plugins / mods are not supported. Some have built-in support for HuskSync and should work as expected, but for others you may wish to modify the [[Event Priorities]]
|
* Combat logging plugins / mods are not supported. Some have built-in support for HuskSync and should work as expected, but for others you may wish to modify the [[Event Priorities]]
|
||||||
@@ -65,10 +65,15 @@ database:
|
|||||||
user_data: husksync_user_data
|
user_data: husksync_user_data
|
||||||
# Redis settings
|
# Redis settings
|
||||||
redis:
|
redis:
|
||||||
# Specify the credentials of your Redis server here. Set "password" to '' if you don't have one
|
# Specify the credentials of your Redis server here.
|
||||||
|
# Set "user" to '' if you don't have one or would like to use the default user.
|
||||||
|
# Set "password" to '' if you don't have one.
|
||||||
credentials:
|
credentials:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
|
# Only change the database if you know what you are doing. The default is 0.
|
||||||
|
database: 0
|
||||||
|
user: ''
|
||||||
password: ''
|
password: ''
|
||||||
use_ssl: false
|
use_ssl: false
|
||||||
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
|
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
|
||||||
|
|||||||
@@ -16,10 +16,15 @@ To configure Redis, navigate to your [`config.yml`](Config-File) file and modify
|
|||||||
```yaml
|
```yaml
|
||||||
# Redis settings
|
# Redis settings
|
||||||
redis:
|
redis:
|
||||||
# Specify the credentials of your Redis server here. Set "password" to '' if you don't have one
|
# Specify the credentials of your Redis server here.
|
||||||
|
# Set "user" to '' if you don't have one or would like to use the default user.
|
||||||
|
# Set "password" to '' if you don't have one.
|
||||||
credentials:
|
credentials:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
|
# Only change the database if you know what you are doing. The default is 0.
|
||||||
|
database: 0
|
||||||
|
user: ''
|
||||||
password: ''
|
password: ''
|
||||||
use_ssl: false
|
use_ssl: false
|
||||||
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
|
# Options for if you're using Redis sentinel. Don't modify this unless you know what you're doing!
|
||||||
@@ -33,8 +38,9 @@ redis:
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Credentials
|
### Credentials
|
||||||
Enter the hostname, port, and default user password of your Redis server.
|
Enter the hostname, port, user, and password of your Redis server.
|
||||||
|
|
||||||
|
If you don't have a Redis user, just use the default user password and leave the user field empty (`user: ''`).
|
||||||
If your Redis default user doesn't have a password, leave the password field blank (`password: ''`') and the plugin will attempt to connect without a password.
|
If your Redis default user doesn't have a password, leave the password field blank (`password: ''`') and the plugin will attempt to connect without a password.
|
||||||
|
|
||||||
### Default user password
|
### Default user password
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.5+build.1:v2
|
|||||||
fabric_loader_version=0.16.14
|
fabric_loader_version=0.16.14
|
||||||
fabric_api_version=0.122.0+1.21.5
|
fabric_api_version=0.122.0+1.21.5
|
||||||
fabric_permissions_api_version=0.3.3
|
fabric_permissions_api_version=0.3.3
|
||||||
fabric_adventure_platform_version=6.4.0-SNAPSHOT
|
fabric_adventure_platform_version=6.4.0
|
||||||
fabric_sgui_version=1.9.0+1.21.5
|
fabric_sgui_version=1.9.0+1.21.5
|
||||||
7
fabric/1.21.6/gradle.properties
Normal file
7
fabric/1.21.6/gradle.properties
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
essential.defaults.loom.mappings=net.fabricmc:yarn:1.21.6+build.1:v2
|
||||||
|
|
||||||
|
fabric_loader_version=0.16.14
|
||||||
|
fabric_api_version=0.127.1+1.21.6
|
||||||
|
fabric_permissions_api_version=0.4.0
|
||||||
|
fabric_adventure_platform_version=6.5.0-SNAPSHOT
|
||||||
|
fabric_sgui_version=1.10.0+1.21.6
|
||||||
@@ -14,22 +14,23 @@ dependencies {
|
|||||||
modImplementation include("net.kyori:adventure-platform-fabric:${fabric_adventure_platform_version}")
|
modImplementation include("net.kyori:adventure-platform-fabric:${fabric_adventure_platform_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:${fabric_sgui_version}")
|
modImplementation include("eu.pb4:sgui:${fabric_sgui_version}")
|
||||||
modImplementation include("net.william278.uniform:uniform-fabric:1.3.3+${project.name}")
|
modImplementation include("net.william278.uniform:uniform-fabric:1.3.5+${project.name}")
|
||||||
modImplementation include("net.william278.toilet:toilet-fabric:1.0.13+${project.name}")
|
modImplementation include("net.william278.toilet:toilet-fabric:1.0.14+${project.name}")
|
||||||
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
modImplementation "net.fabricmc.fabric-api:fabric-api:${fabric_api_version}"
|
||||||
|
|
||||||
// Manually include config deps due to the way including api deps works
|
// Manually include config deps due to the way including api deps works
|
||||||
implementation include("de.exlll:configlib-core:4.6.1")
|
implementation include("de.exlll:configlib-core:4.6.1")
|
||||||
implementation include("org.snakeyaml:snakeyaml-engine:2.7")
|
implementation include("org.snakeyaml:snakeyaml-engine:2.9")
|
||||||
implementation include('org.apache.commons:commons-pool2:2.12.1')
|
implementation include('org.apache.commons:commons-pool2:2.12.1')
|
||||||
|
|
||||||
// Include driver deps due to no runtime dep loading support
|
// Include driver deps due to no runtime dep loading support
|
||||||
implementation include('org.apache.commons:commons-pool2:2.12.1')
|
|
||||||
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
implementation include("com.mysql:mysql-connector-j:$mysql_driver_version")
|
||||||
implementation include("org.postgresql:postgresql:$postgres_driver_version")
|
implementation include("org.postgresql:postgresql:$postgres_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("redis.clients:jedis:$jedis_version")
|
|
||||||
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
implementation include("org.xerial.snappy:snappy-java:$snappy_version")
|
||||||
|
implementation include("redis.clients:jedis:$jedis_version")
|
||||||
|
implementation include("redis.clients.authentication:redis-authx-core:0.1.1-beta2") // Redis dep
|
||||||
|
implementation include('org.apache.commons:commons-pool2:2.12.1') // Redis dep
|
||||||
|
|
||||||
compileOnly 'net.william278:DesertWell:2.0.4'
|
compileOnly 'net.william278:DesertWell:2.0.4'
|
||||||
compileOnly 'org.jetbrains:annotations:26.0.2'
|
compileOnly 'org.jetbrains:annotations:26.0.2'
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.21.5
|
1.21.6
|
||||||
@@ -3,13 +3,15 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
preprocess {
|
preprocess {
|
||||||
|
def fabric12106 = createNode("1.21.6", 12106, "yarn")
|
||||||
def fabric12105 = createNode("1.21.5", 12105, "yarn")
|
def fabric12105 = createNode("1.21.5", 12105, "yarn")
|
||||||
def fabric12104 = createNode("1.21.4", 12104, "yarn")
|
def fabric12104 = createNode("1.21.4", 12104, "yarn")
|
||||||
def fabric12101 = createNode("1.21.1", 12101, "yarn")
|
def fabric12101 = createNode("1.21.1", 12101, "yarn")
|
||||||
def fabric12001 = createNode("1.20.1", 12001, "yarn")
|
def fabric12001 = createNode("1.20.1", 12001, "yarn")
|
||||||
|
|
||||||
strictExtraMappings.set(true)
|
strictExtraMappings.set(true)
|
||||||
fabric12104.link(fabric12105, null)
|
fabric12105.link(fabric12106, null)
|
||||||
fabric12101.link(fabric12105, null)
|
fabric12104.link(fabric12106, null)
|
||||||
fabric12001.link(fabric12105, null)
|
fabric12101.link(fabric12106, null)
|
||||||
|
fabric12001.link(fabric12106, null)
|
||||||
}
|
}
|
||||||
@@ -99,8 +99,9 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
private static final int VERSION1_20_5 = 3837;
|
private static final int VERSION1_20_5 = 3837;
|
||||||
private static final int VERSION1_21_1 = 3955;
|
private static final int VERSION1_21_1 = 3955;
|
||||||
private static final int VERSION1_21_3 = 4082;
|
private static final int VERSION1_21_3 = 4082;
|
||||||
private static final int VERSION1_21_4 = 4189; // Current
|
private static final int VERSION1_21_4 = 4189;
|
||||||
private static final int VERSION1_21_5 = 4323;
|
private static final int VERSION1_21_5 = 4323;
|
||||||
|
private static final int VERSION1_21_6 = 4435;
|
||||||
|
|
||||||
private final HashMap<Identifier, Serializer<? extends Data>> serializers = Maps.newHashMap();
|
private final HashMap<Identifier, Serializer<? extends Data>> serializers = Maps.newHashMap();
|
||||||
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
private final Map<UUID, Map<Identifier, Data>> playerCustomDataStore = Maps.newConcurrentMap();
|
||||||
@@ -387,10 +388,13 @@ public class FabricHuskSync implements DedicatedServerModInitializer, HuskSync,
|
|||||||
case "1.21.2", "1.21.3" -> VERSION1_21_3;
|
case "1.21.2", "1.21.3" -> VERSION1_21_3;
|
||||||
case "1.21.4" -> VERSION1_21_4;
|
case "1.21.4" -> VERSION1_21_4;
|
||||||
case "1.21.5" -> VERSION1_21_5;
|
case "1.21.5" -> VERSION1_21_5;
|
||||||
//#if MC==12105
|
case "1.21.6" -> VERSION1_21_6;
|
||||||
|
//#if MC==12106
|
||||||
|
default -> VERSION1_21_6;
|
||||||
|
//#elseif MC==12105
|
||||||
//$$ default -> VERSION1_21_5;
|
//$$ default -> VERSION1_21_5;
|
||||||
//#elseif MC==12104
|
//#elseif MC==12104
|
||||||
default -> VERSION1_21_4;
|
//$$ default -> VERSION1_21_4;
|
||||||
//#elseif MC==12101
|
//#elseif MC==12101
|
||||||
//$$ default -> VERSION1_21_1;
|
//$$ default -> VERSION1_21_1;
|
||||||
//#elseif MC==12001
|
//#elseif MC==12001
|
||||||
|
|||||||
@@ -272,12 +272,14 @@ public abstract class FabricSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private NbtCompound encodeNbt(@NotNull ItemStack item, @NotNull DynamicRegistryManager registryManager) {
|
private NbtCompound encodeNbt(@NotNull ItemStack item, @NotNull DynamicRegistryManager reg) {
|
||||||
try {
|
try {
|
||||||
//#if MC>=12104
|
//#if MC>=12106
|
||||||
return (NbtCompound) item.toNbt(registryManager);
|
return (NbtCompound) ItemStack.CODEC.encodeStart(reg.getOps(NbtOps.INSTANCE), item).getOrThrow();
|
||||||
|
//#elseif MC>=12104
|
||||||
|
//$$ return (NbtCompound) item.toNbt(reg);
|
||||||
//#elseif MC==12101
|
//#elseif MC==12101
|
||||||
//$$ return (NbtCompound) item.encode(registryManager);
|
//$$ return (NbtCompound) item.encode(reg);
|
||||||
//#elseif MC==12001
|
//#elseif MC==12001
|
||||||
//$$ final NbtCompound compound = new NbtCompound();
|
//$$ final NbtCompound compound = new NbtCompound();
|
||||||
//$$ item.writeNbt(compound);
|
//$$ item.writeNbt(compound);
|
||||||
@@ -289,14 +291,16 @@ public abstract class FabricSerializer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
private ItemStack decodeNbt(@NotNull NbtElement item, @NotNull DynamicRegistryManager registryManager) {
|
private ItemStack decodeNbt(@NotNull NbtElement item, @NotNull DynamicRegistryManager reg) {
|
||||||
//#if MC==12001
|
//#if MC>=12106
|
||||||
|
final @Nullable ItemStack stack = ItemStack.CODEC.decode(reg.getOps(NbtOps.INSTANCE), item).getOrThrow().getFirst();
|
||||||
|
//#elseif MC>12001
|
||||||
|
//$$ final @Nullable ItemStack stack = ItemStack.fromNbt(reg, item).orElse(null);
|
||||||
|
//#elseif MC==12001
|
||||||
//$$ final @Nullable ItemStack stack = ItemStack.fromNbt((NbtCompound) item);
|
//$$ final @Nullable ItemStack stack = ItemStack.fromNbt((NbtCompound) item);
|
||||||
//#else
|
|
||||||
final @Nullable ItemStack stack = ItemStack.fromNbt(registryManager, item).orElse(null);
|
|
||||||
//#endif
|
//#endif
|
||||||
if (stack == null) {
|
if (stack == null) {
|
||||||
throw new IllegalStateException("Failed to decode item NBT (got null 'fromNbt'): (%s)".formatted(item));
|
throw new IllegalStateException("Failed to decode item NBT (decode got null): (%s)".formatted(item));
|
||||||
}
|
}
|
||||||
return stack;
|
return stack;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ org.gradle.daemon=true
|
|||||||
javaVersion=21
|
javaVersion=21
|
||||||
|
|
||||||
# Plugin metadata
|
# Plugin metadata
|
||||||
plugin_version=3.8.2
|
plugin_version=3.8.5
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,9 @@ redis:
|
|||||||
credentials:
|
credentials:
|
||||||
host: localhost
|
host: localhost
|
||||||
port: 6379
|
port: 6379
|
||||||
|
# Only change the database if you know what you are doing. The default is 0.
|
||||||
|
database: 0
|
||||||
|
user: ''
|
||||||
password: ''
|
password: ''
|
||||||
use_ssl: false
|
use_ssl: false
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,6 @@ 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.4
|
||||||
tqdm==4.66.3
|
tqdm==4.66.3
|
||||||
urllib3==2.2.2
|
urllib3==2.5.0
|
||||||
|
|||||||
Reference in New Issue
Block a user