From cf43f35ee9efa87151fc89f2bfdf4f0227852e29 Mon Sep 17 00:00:00 2001 From: mlus <1319237806@qq.com> Date: Thu, 15 May 2025 18:03:40 +0800 Subject: [PATCH 01/24] language file fix --- src/main/resources/assets/playersync/lang/en_us.json | 3 ++- src/main/resources/assets/playersync/lang/zh_cn.json | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/resources/assets/playersync/lang/en_us.json b/src/main/resources/assets/playersync/lang/en_us.json index fceecfd..8e04540 100644 --- a/src/main/resources/assets/playersync/lang/en_us.json +++ b/src/main/resources/assets/playersync/lang/en_us.json @@ -1,5 +1,6 @@ { - "playersync.item_placeholder_description": "Item is unknown on this server. This can either\nbe a modded item, an added, or a removed vanilla\nitem.\nThis voucher will automatically be replaced with\nthe corresponding item when joining a server\nwhere the item is known.","playersync.placeholder_titel_override": "Item Voucher", + "playersync.item_placeholder_description": "Item is unknown on this server. This can either\nbe a modded item, an added, or a removed vanilla\nitem.\nThis voucher will automatically be replaced with\nthe corresponding item when joining a server\nwhere the item is known.", + "playersync.placeholder_title_override": "Item Voucher", "playersync.item_placeholder_title": "Item Voucher", "playersync.already_online": "You can't join more than one synchronization server at the same time." } diff --git a/src/main/resources/assets/playersync/lang/zh_cn.json b/src/main/resources/assets/playersync/lang/zh_cn.json index a19647d..92bbe6a 100644 --- a/src/main/resources/assets/playersync/lang/zh_cn.json +++ b/src/main/resources/assets/playersync/lang/zh_cn.json @@ -1,3 +1,6 @@ { + "playersync.item_placeholder_description": "物品在此服务器未知。这可能是一个模组物品,或是不同版本的原版物品。\n这张券将会在加入可识别此物品的服务器后自动替换为对应物品。", + "playersync.placeholder_title_override": "物品券", + "playersync.item_placeholder_title": "物品券", "playersync.already_online": "你不能同时加入多个同步的服务器。" } \ No newline at end of file From 26421096ac732fbbaf3fe7cf0ff53066e18134b4 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 17 May 2025 19:08:48 +0000 Subject: [PATCH 02/24] remove unused override in the language files --- src/main/resources/assets/playersync/lang/en_us.json | 1 - src/main/resources/assets/playersync/lang/zh_cn.json | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/resources/assets/playersync/lang/en_us.json b/src/main/resources/assets/playersync/lang/en_us.json index 8e04540..4b03f8e 100644 --- a/src/main/resources/assets/playersync/lang/en_us.json +++ b/src/main/resources/assets/playersync/lang/en_us.json @@ -1,6 +1,5 @@ { "playersync.item_placeholder_description": "Item is unknown on this server. This can either\nbe a modded item, an added, or a removed vanilla\nitem.\nThis voucher will automatically be replaced with\nthe corresponding item when joining a server\nwhere the item is known.", - "playersync.placeholder_title_override": "Item Voucher", "playersync.item_placeholder_title": "Item Voucher", "playersync.already_online": "You can't join more than one synchronization server at the same time." } diff --git a/src/main/resources/assets/playersync/lang/zh_cn.json b/src/main/resources/assets/playersync/lang/zh_cn.json index 92bbe6a..f206090 100644 --- a/src/main/resources/assets/playersync/lang/zh_cn.json +++ b/src/main/resources/assets/playersync/lang/zh_cn.json @@ -1,6 +1,5 @@ { "playersync.item_placeholder_description": "物品在此服务器未知。这可能是一个模组物品,或是不同版本的原版物品。\n这张券将会在加入可识别此物品的服务器后自动替换为对应物品。", - "playersync.placeholder_title_override": "物品券", "playersync.item_placeholder_title": "物品券", "playersync.already_online": "你不能同时加入多个同步的服务器。" -} \ No newline at end of file +} From 2b835996c0ac5e4904eef88dbcfa9c58f04d7fbb Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 17 May 2025 18:54:12 +0000 Subject: [PATCH 03/24] add action to automatically backport PRs (cherry picked from commit 5ba7cc29724428e2c5f5d3b07e7b5f4ceeff592d) --- .github/workflows/backport-prs.yml | 38 ++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/backport-prs.yml diff --git a/.github/workflows/backport-prs.yml b/.github/workflows/backport-prs.yml new file mode 100644 index 0000000..3c7b90c --- /dev/null +++ b/.github/workflows/backport-prs.yml @@ -0,0 +1,38 @@ +name: Backport merged pull request +on: + pull_request_target: + types: [closed] + issue_comment: + types: [created] +permissions: + contents: write # so it can comment + pull-requests: write # so it can create pull requests +jobs: + backport: + name: Backport pull request + runs-on: ubuntu-latest + + # Only run when pull request is merged + # or when a comment starting with `/backport` is created by someone other than the + # https://github.com/backport-action bot user (user id: 97796249). Note that if you use your + # own PAT as `github_token`, that you should replace this id with yours. + if: > + ( + github.event_name == 'pull_request_target' && + github.event.pull_request.merged + ) || ( + github.event_name == 'issue_comment' && + github.event.issue.pull_request && + github.event.comment.user.id != 97796249 && + startsWith(github.event.comment.body, '/backport') + ) + steps: + - uses: actions/checkout@v4 + - name: Create backport pull requests + uses: korthout/backport-action@v3 + with: + pull_description: | + Backport of #${pull_number} to `${target_branch}`. + + ### Description + ${pull_description} From 0bcdc86d32b6eb10dcd5642dc34ce36c3a7055e3 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 17 May 2025 18:45:03 +0000 Subject: [PATCH 04/24] fix XP being lost or duplicated The current calculation did not work for larger amounts of levels and either removed or added levels unintentionally. (cherry picked from commit d83bad5a3321adb4ff44e7717f72ad0167eabdd4) --- .../fubuki/playersync/sync/VanillaSync.java | 56 +++++++++++++++++-- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index 86e4816..0b293e2 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -184,10 +184,8 @@ public class VanillaSync { // Restore basic attributes serverPlayer.setHealth(rs2.getInt("health")); serverPlayer.getFoodData().setFoodLevel(rs2.getInt("food_level")); - serverPlayer.totalExperience = 0; - serverPlayer.experienceLevel = 0; - serverPlayer.experienceProgress = 0; - serverPlayer.giveExperiencePoints(rs2.getInt("xp")); + + setXpForPlayer(serverPlayer, rs2.getInt("xp")); serverPlayer.setScore(rs2.getInt("score")); // Restore left-hand item @@ -472,7 +470,7 @@ public class VanillaSync { PlayerSync.LOGGER.info("Storing data for player " + player_uuid + " (init=" + init + ")"); // Basic Attributes - int XP = player.totalExperience; + int XP = getTotalExperience(player); int score = player.getScore(); int food_level = player.getFoodData().getFoodLevel(); int health = (int) player.getHealth(); @@ -635,4 +633,52 @@ public class VanillaSync { } } } + + private static void setXpForPlayer(ServerPlayer serverPlayer, int databaseXp) { + // Don't use giveExperience() as it has several side-effects: + // triggers an event, sends network packets, increases the score, ... + serverPlayer.totalExperience = databaseXp; + serverPlayer.experienceLevel = 0; + serverPlayer.experienceProgress = 0; + + int xpForLevel; + + while (databaseXp >= (xpForLevel = serverPlayer.getXpNeededForNextLevel())) { + databaseXp -= xpForLevel; + serverPlayer.experienceLevel++; + } + + serverPlayer.experienceProgress = serverPlayer.experienceLevel > 0 + ? (float) databaseXp / serverPlayer.getXpNeededForNextLevel() + : 0f; + + PlayerSync.LOGGER.debug("Giving player " + + serverPlayer.experienceLevel + " levels and " + + serverPlayer.experienceProgress * 100 + "% experience progress, calculated from " + + serverPlayer.totalExperience + " XP."); + } + + private static int getTotalExperience(final Player player) { + int level = player.experienceLevel; + int totalXp = 0; + + // Calculate total XP for completed levels + if (level > 30) { + totalXp = (int) (4.5 * Math.pow(level, 2) - 162.5 * level + 2220); + } else if (level > 15) { + totalXp = (int) (2.5 * Math.pow(level, 2) - 40.5 * level + 360); + } else { + totalXp = level * level + 6 * level; + } + + // Add partial level progress + totalXp += Math.round(player.getXpNeededForNextLevel() * player.experienceProgress); + + PlayerSync.LOGGER.debug("Experience calcuation for " + + player.experienceLevel + " levels and " + + player.experienceProgress * 100 + "% experience progress yields " + + totalXp + " XP."); + + return totalXp; + } } From 02036478189718d489c77d676c8bf35dc745bc01 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sun, 18 May 2025 17:18:23 +0000 Subject: [PATCH 05/24] use PAT in backport action (cherry picked from commit e0f0b518519f9514d0c34e3d09eef69531bf2dc7) --- .github/workflows/backport-prs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/backport-prs.yml b/.github/workflows/backport-prs.yml index 3c7b90c..0e9d05a 100644 --- a/.github/workflows/backport-prs.yml +++ b/.github/workflows/backport-prs.yml @@ -31,6 +31,7 @@ jobs: - name: Create backport pull requests uses: korthout/backport-action@v3 with: + github_token: ${{ secrets.TOKEN }} pull_description: | Backport of #${pull_number} to `${target_branch}`. From 357579abbbb76c877ce7c39e001aaff09109bbd6 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sun, 18 May 2025 22:02:56 +0000 Subject: [PATCH 06/24] bump mod version to 2.1.1 (cherry picked from commit e2b90ccf988cf0d450685f850c1cfd527f8e836c) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1d78eeb..bd8f3a0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -34,7 +34,7 @@ mod_name=PlayerSync # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPL-3.0 license # The mod version. See https://semver.org/ -mod_version=2.1.0 +mod_version=2.1.1 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 205d74e52238910023bdd1f2d7d75a3890b6d94a Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:52:04 +0000 Subject: [PATCH 07/24] optimize all imports --- .../vip/fubuki/playersync/PlayerSync.java | 20 +++++++++---------- .../fubuki/playersync/config/JdbcConfig.java | 3 ++- .../fubuki/playersync/sync/ModsSupport.java | 9 ++++----- .../vip/fubuki/playersync/util/JDBCsetUp.java | 6 ++---- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index ddfecf3..8098123 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -1,6 +1,15 @@ package vip.fubuki.playersync; import com.mojang.logging.LogUtils; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.ModList; +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.common.Mod; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.server.ServerStartingEvent; import org.slf4j.Logger; import vip.fubuki.playersync.config.JdbcConfig; import vip.fubuki.playersync.sync.ChatSync; @@ -11,17 +20,6 @@ import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import net.neoforged.bus.api.IEventBus; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.ModList; -import net.neoforged.fml.ModLoadingContext; -import net.neoforged.fml.common.Mod; -import net.neoforged.fml.config.ModConfig; -import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; -import net.neoforged.fml.javafmlmod.FMLJavaModLoadingContext; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.server.ServerStartingEvent; -import net.neoforged.bus.api.IEventBus; @Mod(PlayerSync.MODID) public class PlayerSync { diff --git a/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java b/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java index fd99bbe..cc6c7da 100644 --- a/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java +++ b/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java @@ -1,10 +1,11 @@ package vip.fubuki.playersync.config; +import net.neoforged.neoforge.common.ModConfigSpec; + import java.util.ArrayList; import java.util.List; import java.util.Random; -import net.neoforged.neoforge.common.ModConfigSpec; public class JdbcConfig { diff --git a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java index 039167d..3af5f3b 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java @@ -6,6 +6,10 @@ import net.minecraft.nbt.NbtUtils; import net.minecraft.world.entity.player.Player; import net.minecraft.world.item.ItemStack; import net.neoforged.fml.ModList; +import top.theillusivec4.curios.api.CuriosApi; +import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; +import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; +import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler; import vip.fubuki.playersync.PlayerSync; import vip.fubuki.playersync.util.JDBCsetUp; import vip.fubuki.playersync.util.LocalJsonUtil; @@ -14,11 +18,6 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; - -import top.theillusivec4.curios.api.CuriosApi; -import top.theillusivec4.curios.api.type.capability.ICuriosItemHandler; -import top.theillusivec4.curios.api.type.inventory.ICurioStacksHandler; -import top.theillusivec4.curios.api.type.inventory.IDynamicStackHandler; import java.util.Optional; import java.util.UUID; diff --git a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java index fea7fd7..cc0b70f 100644 --- a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java +++ b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java @@ -1,13 +1,11 @@ package vip.fubuki.playersync.util; +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; import vip.fubuki.playersync.config.JdbcConfig; import java.sql.*; -import org.slf4j.Logger; - -import com.mojang.logging.LogUtils; - public class JDBCsetUp { private static final Logger LOGGER = LogUtils.getLogger(); From d69df0fa49fdeefa43964fa1a73ab8b2fb3148e7 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Tue, 17 Jun 2025 23:52:11 +0000 Subject: [PATCH 08/24] add vscode setting for IntelliJ-like imports --- .vscode/settings.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ad06a8e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "java.compile.nullAnalysis.mode": "automatic", + "java.completion.importOrder": [ + "", + "javax", + "java", + "#" + ], + "java.sources.organizeImports.starThreshold": 5 +} From 0e37cdc0912c57b1e2b94a180ecd6490c152e611 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Tue, 17 Jun 2025 20:01:14 +0000 Subject: [PATCH 09/24] properly upgrade items with newer MC versions --- .../fubuki/playersync/sync/ModsSupport.java | 2 +- .../fubuki/playersync/sync/VanillaSync.java | 25 ++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java index 3af5f3b..c33d3d3 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java @@ -73,7 +73,7 @@ public class ModsSupport { String serialized = entry.getValue(); try { String nbtString = VanillaSync.deserializeString(serialized); - CompoundTag tag = NbtUtils.snbtToStructure(nbtString); + CompoundTag tag = VanillaSync.snbtToFixedCompoundTag(nbtString); ItemStack stack = ItemStack.of(tag); if (handler.getCurios().containsKey(slotType)) { ICurioStacksHandler stacksHandler = handler.getCurios().get(slotType); diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index aa4d3cf..de47a05 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -1,7 +1,9 @@ package vip.fubuki.playersync.sync; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.serialization.Dynamic; import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.*; import net.minecraft.network.chat.Component; @@ -10,6 +12,8 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; import net.minecraft.server.PlayerAdvancements; import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; import net.minecraft.world.InteractionHand; import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffectInstance; @@ -253,7 +257,7 @@ public class VanillaSync { } String nbtString = deserializeString(serializedNbt); - CompoundTag compoundTag = NbtUtils.snbtToStructure(nbtString); + CompoundTag compoundTag = snbtToFixedCompoundTag(nbtString); if (compoundTag == null || compoundTag.isEmpty() || !compoundTag.contains("id", Tag.TAG_STRING)) { return ItemStack.EMPTY; // Invalid or empty tag @@ -346,6 +350,23 @@ public class VanillaSync { return placeholder; } + public static CompoundTag snbtToFixedCompoundTag(String nbtString) throws CommandSyntaxException { + CompoundTag parsedTag = TagParser.parseTag(nbtString); + + int currentDataVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); + int snbtDataVersion = NbtUtils.getDataVersion(parsedTag, 500); + + Dynamic dynamicTagInput = new Dynamic<>(NbtOps.INSTANCE, parsedTag); + + Dynamic updatedDynamicTag = DataFixers.getDataFixer().update( + References.ITEM_STACK, + dynamicTagInput, + snbtDataVersion, + currentDataVersion); + CompoundTag compoundTag = (CompoundTag) updatedDynamicTag.getValue(); + return compoundTag; + } + /** * Deserializes a string from the database back into an NBT string. * Handles both the new Base64 format (prefixed with "B64:") and the old custom format. @@ -453,6 +474,8 @@ public class VanillaSync { // Serialize the ItemStack to NBT CompoundTag compoundTag = new CompoundTag(); itemStack.save(compoundTag); + // Adding data version to allow newer version of Minecraft to properly update the itemstack from the db + NbtUtils.addCurrentDataVersion(compoundTag); return compoundTag; } From a0f71ac6e5161afa74813e1e1493551262baee57 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:15:35 +0000 Subject: [PATCH 10/24] remove unnecessary deserializeString import --- src/main/java/vip/fubuki/playersync/sync/ModsSupport.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java index c33d3d3..1c0a3ad 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java @@ -21,8 +21,6 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; -import static vip.fubuki.playersync.sync.VanillaSync.deserializeString; - public class ModsSupport { @@ -111,7 +109,7 @@ public class ModsSupport { ResultSet rsBackpack = qrBackpack.resultSet(); if (rsBackpack.next()) { String serialized = rsBackpack.getString("backpack_nbt"); - String nbtString = deserializeString(serialized); + String nbtString = VanillaSync.deserializeString(serialized); CompoundTag backpackNbt = NbtUtils.snbtToStructure(nbtString); // Update BackpackStorage with the retrieved NBT net.p3pp3rf1y.sophisticatedbackpacks.backpack.BackpackStorage.get().setBackpackContents(contentsUuid, backpackNbt); From 6adb8c2622535d82e18ac659e5971658ce3bb2a7 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:14:52 +0000 Subject: [PATCH 11/24] unify item creation in curios and normal inventory this also allows creation of placeholders within curios containers (cherry picked from commit a70605a8b68da89a241162ac867c53f46e96d9e5) --- src/main/java/vip/fubuki/playersync/sync/ModsSupport.java | 4 +--- src/main/java/vip/fubuki/playersync/sync/VanillaSync.java | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java index c33d3d3..e08d5ec 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java @@ -72,9 +72,7 @@ public class ModsSupport { } String serialized = entry.getValue(); try { - String nbtString = VanillaSync.deserializeString(serialized); - CompoundTag tag = VanillaSync.snbtToFixedCompoundTag(nbtString); - ItemStack stack = ItemStack.of(tag); + ItemStack stack = VanillaSync.deserializeAndCreatePlaceholderIfNeeded(serialized); if (handler.getCurios().containsKey(slotType)) { ICurioStacksHandler stacksHandler = handler.getCurios().get(slotType); IDynamicStackHandler dynStacks = stacksHandler.getStacks(); diff --git a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java index de47a05..fe00ae6 100644 --- a/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java +++ b/src/main/java/vip/fubuki/playersync/sync/VanillaSync.java @@ -249,7 +249,7 @@ public class VanillaSync { } // deserialize item and potentially create placeholders - private static ItemStack deserializeAndCreatePlaceholderIfNeeded(String serializedNbt) + public static ItemStack deserializeAndCreatePlaceholderIfNeeded(String serializedNbt) throws CommandSyntaxException { if (serializedNbt == null || serializedNbt.isEmpty() || serializedNbt.equals("B64:e30=")) { // Check for empty NBT (Base64 encoded '{}') From db4af7121586f5dbe41d3f5375c141108503248b Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:14:50 +0000 Subject: [PATCH 12/24] fix string to map generation with base64 encoding The new base64 encoding uses "=" characters as part of its encoding. The previous code split those and trimmed them afterwards, making the base64 not consistent with the rest of the code that assumed "=" are still present, like "B64:e30=". (cherry picked from commit de324a23be1e1dd06595763a295d2577daea0cb1) --- .../fubuki/playersync/util/LocalJsonUtil.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java b/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java index f82748d..e3423e4 100644 --- a/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java +++ b/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java @@ -11,8 +11,15 @@ public class LocalJsonUtil { String[] split = s2.split(","); for (int i = split.length - 1; i >= 0; i--) { String trim = split[i].trim(); - String[] split1 = trim.split("="); - map.put(split1[0],split1[1]); + + // only check for the first "=" as the values also contain additional "=" + int equalIndex = trim.indexOf('='); + if (equalIndex < 0) + continue; + + String key = trim.substring(0, equalIndex); + String value = trim.substring(equalIndex + 1); + map.put(key, value); } return map; } @@ -24,8 +31,15 @@ public class LocalJsonUtil { String[] split = s2.split(","); for (int i = split.length - 1; i >= 0; i--) { String trim = split[i].trim(); - String[] split1 = trim.split("="); - map.put(Integer.parseInt(split1[0]),split1[1]); + + // only check for the first "=" as the values also contain additional "=" + int equalIndex = trim.indexOf('='); + if (equalIndex < 0) + continue; + + String key = trim.substring(0, equalIndex); + String value = trim.substring(equalIndex + 1); + map.put(Integer.parseInt(key), value); } return map; } From 85f953a2200585d3206c2c571d8abf149f259598 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Thu, 10 Jul 2025 21:46:34 +0000 Subject: [PATCH 13/24] unify both stringToMap functions (cherry picked from commit 228b835c2aa6812bcdb486168d630bf4ffe19454) --- .../fubuki/playersync/util/LocalJsonUtil.java | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java b/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java index e3423e4..f583270 100644 --- a/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java +++ b/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java @@ -2,10 +2,11 @@ package vip.fubuki.playersync.util; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; public class LocalJsonUtil { - public static Map StringToMap(String param) { - Map map = new HashMap<>(); + private static Map stringToGenericMap(String param, Function keyParser) { + Map map = new HashMap<>(); String s1 = param.substring(1,param.length()-1); String s2 = s1.trim(); String[] split = s2.split(","); @@ -19,28 +20,16 @@ public class LocalJsonUtil { String key = trim.substring(0, equalIndex); String value = trim.substring(equalIndex + 1); - map.put(key, value); + map.put(keyParser.apply(key), value); } return map; } - public static Map StringToEntryMap(String param) { - Map map = new HashMap<>(); - String s1 = param.substring(1,param.length()-1); - String s2 = s1.trim(); - String[] split = s2.split(","); - for (int i = split.length - 1; i >= 0; i--) { - String trim = split[i].trim(); + public static Map StringToMap(String param) { + return stringToGenericMap(param, Function.identity()); + } - // only check for the first "=" as the values also contain additional "=" - int equalIndex = trim.indexOf('='); - if (equalIndex < 0) - continue; - - String key = trim.substring(0, equalIndex); - String value = trim.substring(equalIndex + 1); - map.put(Integer.parseInt(key), value); - } - return map; + public static Map StringToEntryMap(String param) { + return stringToGenericMap(param, Integer::parseInt); } } From 9b7ebebf98ae0a7d906608b3cf665bc7191a3b33 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Thu, 10 Jul 2025 22:04:05 +0000 Subject: [PATCH 14/24] simplify and exit early in stringToGenericMap (cherry picked from commit 53bdfe23097fc9b34ff4c43ea9b11100d90bbd09) --- .../fubuki/playersync/util/LocalJsonUtil.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java b/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java index f583270..44c2249 100644 --- a/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java +++ b/src/main/java/vip/fubuki/playersync/util/LocalJsonUtil.java @@ -7,11 +7,21 @@ import java.util.function.Function; public class LocalJsonUtil { private static Map stringToGenericMap(String param, Function keyParser) { Map map = new HashMap<>(); - String s1 = param.substring(1,param.length()-1); - String s2 = s1.trim(); - String[] split = s2.split(","); - for (int i = split.length - 1; i >= 0; i--) { - String trim = split[i].trim(); + + // check if string is at least minimal json + if (param == null || param.length() < 2 || param.equals("{}")) { + return map; + } + + // extract string within outermost json brackets {} + String s1 = param.substring(param.indexOf('{')+1, param.lastIndexOf('}')).trim(); + if (s1.isEmpty()) { + return map; + } + + // split all json elements + for (String split : s1.split(",")) { + String trim = split.trim(); // only check for the first "=" as the values also contain additional "=" int equalIndex = trim.indexOf('='); From 0b4a6aa78a180eb5f78a949676abbcee92f21e01 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 19 Jul 2025 18:06:42 +0000 Subject: [PATCH 15/24] log the advancement's data type before altering it seems that sometimes this is triggered on an existing database. This should help identifying what is happening in these cases. (cherry picked from commit 54cbb9c9a81a7af090aef59b62f21fb76eeab639) --- src/main/java/vip/fubuki/playersync/PlayerSync.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index 8098123..453b6e8 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -169,7 +169,7 @@ public class PlayerSync { if (rsAdvCol.next()) { String dataType = rsAdvCol.getString("DATA_TYPE"); if (!"mediumblob".equalsIgnoreCase(dataType)) { - LOGGER.info("Altering player_data table to modify 'advancements' column to MEDIUMBLOB."); + LOGGER.info("Altering player_data table to modify 'advancements' column from {} to MEDIUMBLOB.", dataType); JDBCsetUp.executeUpdate("ALTER TABLE " + dbName + ".player_data MODIFY COLUMN advancements MEDIUMBLOB", 1); } } From 3d111125a9989ccdddd23e3e63a0c1a3254470ff Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 19 Jul 2025 18:12:55 +0000 Subject: [PATCH 16/24] remove duplicate UPDATE server_info the UPDATE is already happening in in the INSERT statement above (cherry picked from commit 0e961074166b4fab29e4fcc0a8b2738ad8683b15) --- src/main/java/vip/fubuki/playersync/PlayerSync.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index 8098123..27d4e28 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -118,10 +118,6 @@ public class PlayerSync { "ON DUPLICATE KEY UPDATE id= " + JdbcConfig.SERVER_ID.get() + ",enable = 1," + "last_update=" + current + ";" ); - JDBCsetUp.executeUpdate( - "UPDATE " + dbName + ".server_info SET last_update=" + System.currentTimeMillis() + - " WHERE id='" + JdbcConfig.SERVER_ID.get() + "'" - ); // Create curios table if the Curios mod is loaded if (ModList.get().isLoaded("curios")) { From 2c3512da8a7839b7aac3fdae886437611d6c489e Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:32:24 +0000 Subject: [PATCH 17/24] make QueryResult AutoClosable This allows QueryResults to be used within a try() block without explicitely closing them. (cherry picked from commit ad76e0e311143cb3b3873ba61512ae36818c895f) --- .../vip/fubuki/playersync/util/JDBCsetUp.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java index cc0b70f..c2a14b0 100644 --- a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java +++ b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java @@ -90,6 +90,24 @@ public class JDBCsetUp { } } - public record QueryResult(Connection connection, ResultSet resultSet) { + public record QueryResult(Connection connection, ResultSet resultSet) implements AutoCloseable { + @Override + public void close() { + if (resultSet != null) { + try { + resultSet.close(); + } catch (SQLException e) { + LOGGER.error("Error closing ResultSet", e); + } + } + + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + LOGGER.error("Error closing Connection", e); + } + } + } } } From 6ddd77646833bac1cdad7d36e4238a0da0aabf73 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:38:38 +0000 Subject: [PATCH 18/24] clarify executeUpdate variants with and without db (cherry picked from commit 8f77a96544cc26a89ec94bd6709a916e374dbc63) --- .../vip/fubuki/playersync/PlayerSync.java | 12 +++++----- .../vip/fubuki/playersync/util/JDBCsetUp.java | 22 ++++++++++--------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index 5e0701a..405fb47 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -49,7 +49,7 @@ public class PlayerSync { String dbName = JdbcConfig.DATABASE_NAME.get(); // Step 1: Create the database using a connection that does not select a database. - JDBCsetUp.executeUpdate("CREATE DATABASE IF NOT EXISTS " + dbName, 1); + JDBCsetUp.executeUpdateWithoutDatabase("CREATE DATABASE IF NOT EXISTS " + dbName); // Step 2: Explicitly select the database on a connection obtained without default database. try (Connection conn = JDBCsetUp.getConnection(false); @@ -130,10 +130,10 @@ public class PlayerSync { // Create backpack_data table if (ModList.get().isLoaded("sophisticatedbackpacks")) { - JDBCsetUp.executeUpdate( + JDBCsetUp.executeUpdateWithoutDatabase( "CREATE TABLE IF NOT EXISTS " + dbName + ".backpack_data (" + "uuid CHAR(36) NOT NULL, backpack_nbt MEDIUMBLOB, PRIMARY KEY (uuid)" + - ");", 1 + ");" ); // Check if backpack_data table has the 'uuid' column @@ -147,8 +147,8 @@ public class PlayerSync { if (rsBackpackCol.next() && rsBackpackCol.getInt("colCount") == 0) { LOGGER.info("Altering backpack_data table to add missing 'uuid' column."); // Add the missing column and set it as primary key. - JDBCsetUp.executeUpdate("ALTER TABLE " + dbName + ".backpack_data ADD COLUMN uuid CHAR(36) NOT NULL", 1); - JDBCsetUp.executeUpdate("ALTER TABLE " + dbName + ".backpack_data ADD PRIMARY KEY (uuid)", 1); + JDBCsetUp.executeUpdateWithoutDatabase("ALTER TABLE " + dbName + ".backpack_data ADD COLUMN uuid CHAR(36) NOT NULL"); + JDBCsetUp.executeUpdateWithoutDatabase("ALTER TABLE " + dbName + ".backpack_data ADD PRIMARY KEY (uuid)"); } rsBackpackCol.close(); backpackColCheck.connection().close(); @@ -166,7 +166,7 @@ public class PlayerSync { String dataType = rsAdvCol.getString("DATA_TYPE"); if (!"mediumblob".equalsIgnoreCase(dataType)) { LOGGER.info("Altering player_data table to modify 'advancements' column from {} to MEDIUMBLOB.", dataType); - JDBCsetUp.executeUpdate("ALTER TABLE " + dbName + ".player_data MODIFY COLUMN advancements MEDIUMBLOB", 1); + JDBCsetUp.executeUpdateWithoutDatabase("ALTER TABLE " + dbName + ".player_data MODIFY COLUMN advancements MEDIUMBLOB"); } } rsAdvCol.close(); diff --git a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java index c2a14b0..19493a2 100644 --- a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java +++ b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java @@ -52,28 +52,30 @@ public class JDBCsetUp { } /** - * Executes an update using a connection that includes the database. + * Executes an update using a connection with or without the database within the JDBC URL */ - public static void executeUpdate(String sql) throws SQLException { + private static void executeUpdate(boolean selectDatabase, String sql) throws SQLException { LOGGER.trace(sql); - try (Connection connection = getConnection()) { // With database selected + try (Connection connection = getConnection(selectDatabase)) { try (PreparedStatement updateStatement = connection.prepareStatement(sql)) { updateStatement.executeUpdate(); } } } + /** + * Executes an update using a connection that includes the database in the JDBC URL + */ + public static void executeUpdate(String sql) throws SQLException { + executeUpdate(true, sql); + } + /** * Executes an update using a connection that does NOT include a default database. * This method is used for commands like "CREATE DATABASE IF NOT EXISTS ..." */ - public static void executeUpdate(String sql, int dummy) throws SQLException { - LOGGER.trace(sql); - try (Connection connection = getConnection(false)) { // Without default database - try (PreparedStatement updateStatement = connection.prepareStatement(sql)) { - updateStatement.executeUpdate(); - } - } + public static void executeUpdateWithoutDatabase(String sql) throws SQLException { + executeUpdate(false, sql); } /** From c434f3c6e4d7cd5f9a5d76a725b53938f5ac9056 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:40:11 +0000 Subject: [PATCH 19/24] allow format strings within SQL queries This makes SQL queries more readable in some cases (cherry picked from commit e1ac7adb1186babc38144bf803aabb7e11ccb01e) --- .../java/vip/fubuki/playersync/util/JDBCsetUp.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java index 19493a2..033c034 100644 --- a/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java +++ b/src/main/java/vip/fubuki/playersync/util/JDBCsetUp.java @@ -43,7 +43,8 @@ public class JDBCsetUp { /** * Executes a query using a connection that includes the database. */ - public static QueryResult executeQuery(String sql) throws SQLException { + public static QueryResult executeQuery(String sqlFormatString, Object... args) throws SQLException { + String sql = String.format(sqlFormatString, args); LOGGER.trace(sql); Connection connection = getConnection(); // With database selected (and "USE" already run) PreparedStatement queryStatement = connection.prepareStatement(sql); @@ -54,7 +55,8 @@ public class JDBCsetUp { /** * Executes an update using a connection with or without the database within the JDBC URL */ - private static void executeUpdate(boolean selectDatabase, String sql) throws SQLException { + private static void executeUpdate(boolean selectDatabase, String sqlFormatString, Object... args) throws SQLException { + String sql = String.format(sqlFormatString, args); LOGGER.trace(sql); try (Connection connection = getConnection(selectDatabase)) { try (PreparedStatement updateStatement = connection.prepareStatement(sql)) { @@ -66,16 +68,16 @@ public class JDBCsetUp { /** * Executes an update using a connection that includes the database in the JDBC URL */ - public static void executeUpdate(String sql) throws SQLException { - executeUpdate(true, sql); + public static void executeUpdate(String sqlFormatString, Object... args) throws SQLException { + executeUpdate(true, sqlFormatString, args); } /** * Executes an update using a connection that does NOT include a default database. * This method is used for commands like "CREATE DATABASE IF NOT EXISTS ..." */ - public static void executeUpdateWithoutDatabase(String sql) throws SQLException { - executeUpdate(false, sql); + public static void executeUpdateWithoutDatabase(String sqlFormatString, Object... args) throws SQLException { + executeUpdate(false, sqlFormatString, args); } /** From 41732cbcd1a67ef16beb9780440de6f46e1db7f0 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 19 Jul 2025 19:41:02 +0000 Subject: [PATCH 20/24] reformat insert into server_info for readability Use the new format capabilities on SQL queries to make the insert more readable. (cherry picked from commit 7f06aa7511c0122794c0011965efc3ddd059b0d6) --- .../vip/fubuki/playersync/PlayerSync.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index 405fb47..bb106d1 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -112,12 +112,28 @@ public class PlayerSync { ");" ); long current = System.currentTimeMillis(); - JDBCsetUp.executeUpdate( - "INSERT INTO " + dbName + ".server_info(id,enable,last_update) " + - "VALUES(" + JdbcConfig.SERVER_ID.get() + ",true," + current + ") " + - "ON DUPLICATE KEY UPDATE id= " + JdbcConfig.SERVER_ID.get() + ",enable = 1," + - "last_update=" + current + ";" - ); + JDBCsetUp.executeUpdate(""" + INSERT INTO %s.server_info + ( + id, + enable, + last_update + ) + VALUES ( + %d, + true, + %d + ) + ON DUPLICATE KEY UPDATE + id = %d, + enable = true, + last_update = %d; + """, + dbName, + JdbcConfig.SERVER_ID.get(), + current, + JdbcConfig.SERVER_ID.get(), + current); // Create curios table if the Curios mod is loaded if (ModList.get().isLoaded("curios")) { From 872168c88e98469ab95b316a5830e27eff56d7cf Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Mon, 28 Jul 2025 21:17:54 +0000 Subject: [PATCH 21/24] extract addColumnIfNotExists into separate method (cherry picked from commit 4fe13bd24d3dbabd625b2e102532c4a6754951fb) --- .../vip/fubuki/playersync/PlayerSync.java | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index bb106d1..e660226 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -151,23 +151,8 @@ public class PlayerSync { "uuid CHAR(36) NOT NULL, backpack_nbt MEDIUMBLOB, PRIMARY KEY (uuid)" + ");" ); - // Check if backpack_data table has the 'uuid' column - JDBCsetUp.QueryResult backpackColCheck = JDBCsetUp.executeQuery( - "SELECT COUNT(*) AS colCount FROM INFORMATION_SCHEMA.COLUMNS " + - "WHERE TABLE_SCHEMA = '" + dbName + "' " + - "AND TABLE_NAME = 'backpack_data' " + - "AND COLUMN_NAME = 'uuid';" - ); - ResultSet rsBackpackCol = backpackColCheck.resultSet(); - if (rsBackpackCol.next() && rsBackpackCol.getInt("colCount") == 0) { - LOGGER.info("Altering backpack_data table to add missing 'uuid' column."); - // Add the missing column and set it as primary key. - JDBCsetUp.executeUpdateWithoutDatabase("ALTER TABLE " + dbName + ".backpack_data ADD COLUMN uuid CHAR(36) NOT NULL"); - JDBCsetUp.executeUpdateWithoutDatabase("ALTER TABLE " + dbName + ".backpack_data ADD PRIMARY KEY (uuid)"); - } - rsBackpackCol.close(); - backpackColCheck.connection().close(); + addColumnIfNotExists("backpack_data", "uuid", "CHAR(36) NOT NULL", true); } // Check and alter the 'advancements' column in player_data if necessary @@ -191,4 +176,44 @@ public class PlayerSync { LOGGER.info("PlayerSync is ready!"); } + private static void addColumnIfNotExists(String tableName, String columnName, String dataTypeDefaultNullness, + boolean makePrimaryKey) throws SQLException { + + // Making use of the AutoCloseable QueryResult here + try (JDBCsetUp.QueryResult backpackColCheck = JDBCsetUp.executeQuery( + "SELECT COUNT(*) AS colCount FROM INFORMATION_SCHEMA.COLUMNS " + + "WHERE TABLE_SCHEMA = DATABASE()" + + "AND TABLE_NAME = '" + tableName + "' " + + "AND COLUMN_NAME = '" + columnName + "';")) { + ResultSet rsBackpackCol = backpackColCheck.resultSet(); + + if (!rsBackpackCol.next()) { + LOGGER.warn("Warning: Unable to check existence of colum {} in table {}.", columnName, tableName); + return; + } + + if (rsBackpackCol.getInt("colCount") > 0) { + LOGGER.debug("Column {} already exists. Skipping creation.", columnName); + return; + } + } + + LOGGER.info("ALTER {} table to add missing {} column.", tableName, columnName); + // Add the missing column and set it as primary key. + JDBCsetUp.executeUpdate( + "ALTER TABLE %s ADD COLUMN %s %s", + tableName, columnName, dataTypeDefaultNullness); + + if (makePrimaryKey) { + LOGGER.info("Altering {} table to add primary key on {}.", tableName, columnName); + JDBCsetUp.executeUpdate( + "ALTER TABLE %s ADD PRIMARY KEY (%s)", + tableName, columnName); + } + } + + private static void addColumnIfNotExists(String tableName, String columnName, + String dataTypeDefaultNullness) throws SQLException { + addColumnIfNotExists(tableName, columnName, dataTypeDefaultNullness, false); + } } From b2f3aa5497d1e277239f6fa62bcfbc2359f69018 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:31:40 +0000 Subject: [PATCH 22/24] add new data_version column to server_info table --- src/main/java/vip/fubuki/playersync/PlayerSync.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/vip/fubuki/playersync/PlayerSync.java b/src/main/java/vip/fubuki/playersync/PlayerSync.java index e660226..f7690e0 100644 --- a/src/main/java/vip/fubuki/playersync/PlayerSync.java +++ b/src/main/java/vip/fubuki/playersync/PlayerSync.java @@ -1,6 +1,7 @@ package vip.fubuki.playersync; import com.mojang.logging.LogUtils; +import net.minecraft.SharedConstants; import net.neoforged.bus.api.IEventBus; import net.neoforged.bus.api.SubscribeEvent; import net.neoforged.fml.ModList; @@ -111,28 +112,37 @@ public class PlayerSync { "PRIMARY KEY (`id`)" + ");" ); + // do not modify the create table statement to make sure this code is compatible with older database versions + addColumnIfNotExists("server_info", "data_version", "INT NOT NULL DEFAULT 0"); + long current = System.currentTimeMillis(); + int data_version = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); JDBCsetUp.executeUpdate(""" INSERT INTO %s.server_info ( id, enable, + data_version, last_update ) VALUES ( %d, true, + %d, %d ) ON DUPLICATE KEY UPDATE id = %d, enable = true, + data_version = %d, last_update = %d; """, dbName, JdbcConfig.SERVER_ID.get(), + data_version, current, JdbcConfig.SERVER_ID.get(), + data_version, current); // Create curios table if the Curios mod is loaded From 02de6b50d27e6d40286576bb4cea2390abc2e9b8 Mon Sep 17 00:00:00 2001 From: EoD <293499+EoD@users.noreply.github.com> Date: Sat, 19 Jul 2025 17:51:38 +0000 Subject: [PATCH 23/24] make all config variables final the classes never change, even if the config is modified. Only the values we read with get() inside the classes change. --- .../fubuki/playersync/config/JdbcConfig.java | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java b/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java index cc6c7da..f1b6bb8 100644 --- a/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java +++ b/src/main/java/vip/fubuki/playersync/config/JdbcConfig.java @@ -9,24 +9,24 @@ import java.util.Random; public class JdbcConfig { - public static ModConfigSpec COMMON_CONFIG; - public static ModConfigSpec.ConfigValue HOST; - public static ModConfigSpec.IntValue PORT; - public static ModConfigSpec.ConfigValue USERNAME; - public static ModConfigSpec.ConfigValue PASSWORD; - public static ModConfigSpec.ConfigValue DATABASE_NAME; - public static ModConfigSpec.ConfigValue> SYNC_WORLD; - public static ModConfigSpec.BooleanValue SYNC_ADVANCEMENTS; - public static ModConfigSpec.BooleanValue USE_SSL; - public static ModConfigSpec.BooleanValue SYNC_CHAT; - public static ModConfigSpec.BooleanValue IS_CHAT_SERVER; + public static final ModConfigSpec COMMON_CONFIG; + public static final ModConfigSpec.ConfigValue HOST; + public static final ModConfigSpec.IntValue PORT; + public static final ModConfigSpec.ConfigValue USERNAME; + public static final ModConfigSpec.ConfigValue PASSWORD; + public static final ModConfigSpec.ConfigValue DATABASE_NAME; + public static final ModConfigSpec.ConfigValue> SYNC_WORLD; + public static final ModConfigSpec.BooleanValue SYNC_ADVANCEMENTS; + public static final ModConfigSpec.BooleanValue USE_SSL; + public static final ModConfigSpec.BooleanValue SYNC_CHAT; + public static final ModConfigSpec.BooleanValue IS_CHAT_SERVER; public static final ModConfigSpec.ConfigValue ITEM_PLACEHOLDER_TITLE_OVERRIDE; public static final ModConfigSpec.ConfigValue ITEM_PLACEHOLDER_DESCRIPTION_OVERRIDE; - public static ModConfigSpec.ConfigValue CHAT_SERVER_IP; - public static ModConfigSpec.IntValue CHAT_SERVER_PORT; - public static ModConfigSpec.BooleanValue USE_LEGACY_SERIALIZATION; + public static final ModConfigSpec.ConfigValue CHAT_SERVER_IP; + public static final ModConfigSpec.IntValue CHAT_SERVER_PORT; + public static final ModConfigSpec.BooleanValue USE_LEGACY_SERIALIZATION; - public static ModConfigSpec.ConfigValue SERVER_ID; + public static final ModConfigSpec.ConfigValue SERVER_ID; static { From cbcdc0e749653462d557afdaa690e88f045f7dcb Mon Sep 17 00:00:00 2001 From: mlus <1319237806@qq.com> Date: Tue, 16 Sep 2025 22:11:25 +0800 Subject: [PATCH 24/24] may fix #111 (cherry picked from commit befa36a30374977c6643824ebc0740e18bc9a0f7) --- .../java/vip/fubuki/playersync/sync/ModsSupport.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java index b07db91..3a86cb9 100644 --- a/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java +++ b/src/main/java/vip/fubuki/playersync/sync/ModsSupport.java @@ -36,14 +36,8 @@ public class ModsSupport { ResultSet rs = qr.resultSet(); if (rs.next()) { String curiosData = rs.getString("curios_item"); - if (curiosData.length() <= 2) { - rs.close(); - qr.connection().close(); - return; - } // Parse the stored data (assumes a simple Map.toString() format: "{key=value, key2=value2, ...}") Map storedMap = LocalJsonUtil.StringToMap(curiosData); - // Clear current Curios slots to avoid conflicts. handlerOpt.ifPresent(handler -> handler.getCurios().forEach((slotType, stacksHandler) -> { // Use the dynamic stack handler to clear slots. @@ -53,6 +47,12 @@ public class ModsSupport { } })); + if (curiosData.length() <= 2) { + rs.close(); + qr.connection().close(); + return; + } + // Restore each saved item. handlerOpt.ifPresent(handler -> { for (Map.Entry entry : storedMap.entrySet()) {