diff --git a/build.gradle b/build.gradle index d55764d..3076729 100644 --- a/build.gradle +++ b/build.gradle @@ -123,6 +123,11 @@ legacyForge { server { server() } + testServer { + server() + // 设置服务器运行目录 + gameDirectory = file('run/server') + } data { data() programArguments.addAll '--mod', mod_id, '--all', diff --git a/gradle.properties b/gradle.properties index fa98729..25e40bf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -59,7 +59,7 @@ mod_name=Super Lead Rope # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.2.2 +mod_version=1.2.3 # 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 diff --git a/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java b/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java index d0f8570..3b258af 100644 --- a/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java +++ b/src/main/java/top/r3944realms/superleadrope/SuperLeadRope.java @@ -23,6 +23,7 @@ import net.minecraftforge.fml.config.ModConfig; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import top.r3944realms.superleadrope.compat.CurtainCompat; import top.r3944realms.superleadrope.compat.WayStoneCompat; import top.r3944realms.superleadrope.config.LeashCommonConfig; import top.r3944realms.superleadrope.core.register.*; @@ -72,6 +73,7 @@ public class SuperLeadRope { ModLoadingContext modLoadingContext = ModLoadingContext.get(); ConfigUtil.registerConfig(modLoadingContext, ModConfig.Type.COMMON, LeashCommonConfig.SPEC, c, "leash"); WayStoneCompat.init(); + CurtainCompat.init(); } /** diff --git a/src/main/java/top/r3944realms/superleadrope/api/workspace/IWorkSpaceHelper.java b/src/main/java/top/r3944realms/superleadrope/api/workspace/IWorkSpaceHelper.java index e405f07..5be2bbc 100644 --- a/src/main/java/top/r3944realms/superleadrope/api/workspace/IWorkSpaceHelper.java +++ b/src/main/java/top/r3944realms/superleadrope/api/workspace/IWorkSpaceHelper.java @@ -104,4 +104,11 @@ public interface IWorkSpaceHelper { * @return the leash state */ Optional getLeashState(@NotNull Entity pEntity); + + /** + * Register fake player. + * + * @param classes the classes + */ + void registerFakePlayer(@NotNull Class... classes); } diff --git a/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java b/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java index 154cfe6..c025a20 100644 --- a/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java +++ b/src/main/java/top/r3944realms/superleadrope/compat/CurtainCompat.java @@ -29,15 +29,11 @@ public class CurtainCompat{ public final static boolean isModLoaded = ModList.get().isLoaded("curtain"); /** - * Is not fake player boolean. - * - * @param player the player - * @return the boolean + * Init. */ - public static boolean isNotFakePlayer(Player player) { + public static void init() { if (isModLoaded) { - return !(player instanceof EntityPlayerMPFake); + FakePlayerJudge.registersFakePlayer(EntityPlayerMPFake.class); } - return true; } } diff --git a/src/main/java/top/r3944realms/superleadrope/compat/FakePlayerJudge.java b/src/main/java/top/r3944realms/superleadrope/compat/FakePlayerJudge.java new file mode 100644 index 0000000..a090238 --- /dev/null +++ b/src/main/java/top/r3944realms/superleadrope/compat/FakePlayerJudge.java @@ -0,0 +1,60 @@ +/* + * Super Lead rope mod + * Copyright (C) 2026 R3944Realms + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package top.r3944realms.superleadrope.compat; + +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.NotNull; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * The type Fake player judge. + */ +public class FakePlayerJudge { + private final static Set> fakePlayerClasses = new HashSet<>(); + + private static void check(final @NotNull Class fakePlayerClass) throws IllegalArgumentException { + if(fakePlayerClass.equals(Player.class) || fakePlayerClass.equals(ServerPlayer.class)) + throw new IllegalArgumentException("Player or ServerPlayer class cannot be used as FakePlayer"); + } + + /** + * Registers fake player. + * + * @param fakePlayerClass the fake player class + */ + public static void registersFakePlayer(final Class @NotNull ...fakePlayerClass) { + for (Class playerClass : fakePlayerClass) { + check(playerClass); + } + fakePlayerClasses.addAll(List.of(fakePlayerClass)); + } + + /** + * Is fake player boolean. + * + * @param entity the entity + * @return the boolean + */ + public static boolean isNotFakePlayer(@NotNull Entity entity) { + if (!(entity instanceof Player)) return true; + return !fakePlayerClasses.contains(entity.getClass()); + } +} diff --git a/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java b/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java index 4e29b2b..d5371ab 100644 --- a/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java +++ b/src/main/java/top/r3944realms/superleadrope/config/LeashConfigManager.java @@ -228,7 +228,7 @@ public class LeashConfigManager { * @return the teleport whitelist */ // ================== 白名单 ================== - public List getTeleportWhitelist() { return Collections.unmodifiableList(teleportWhitelistCache); } + public List getTeleportWhitelist() { return new ArrayList<>(teleportWhitelistCache); } /** * Is entity teleport allowed boolean. @@ -358,7 +358,7 @@ public class LeashConfigManager { * * @return the axis elasticity */ - public List getAxisElasticity() { return Collections.unmodifiableList(axisElasticity); } + public List getAxisElasticity() { return new ArrayList<>(axisElasticity); } /** * Gets x elasticity. @@ -423,7 +423,7 @@ public class LeashConfigManager { maxForce = LeashCommonConfig.COMMON.maxForce.get(); playerSpringFactor = LeashCommonConfig.COMMON.playerSpringFactor.get(); mobSpringFactor = LeashCommonConfig.COMMON.mobSpringFactor.get(); - cacheHash = calculateConfigHash(); + cacheHash = -1; cacheTag = serializeToNBT(); SuperLeadRope.logger.debug("Configs reloaded: {}", getStats()); } catch (Exception e) { @@ -476,7 +476,11 @@ public class LeashConfigManager { * @return the compound tag */ public synchronized CompoundTag serializeToNBT() { - if (cacheHash == calculateConfigHash() && cacheTag != null) return cacheTag; + int currentHash = calculateConfigHash(); + + if (cacheTag != null && cacheHash == currentHash) { + return cacheTag; + } CompoundTag tag = new CompoundTag(); // 序列化偏移映射 @@ -518,7 +522,7 @@ public class LeashConfigManager { tag.put("axis_elasticity", elasticityTag); tag.putInt("max_leashes_per_entity", maxLeashesPerEntity); - cacheHash = calculateConfigHash(); + cacheHash = currentHash; cacheTag = tag; return tag; @@ -554,7 +558,6 @@ public class LeashConfigManager { LeashCommonConfig.COMMON.maxForce.set(maxForce); LeashCommonConfig.COMMON.playerSpringFactor.set(playerSpringFactor); LeashCommonConfig.COMMON.mobSpringFactor.set(mobSpringFactor); - } /** @@ -617,6 +620,9 @@ public class LeashConfigManager { public void deserializeFromNBT(CompoundTag tag) { if (tag == null || tag.isEmpty()) return; + cacheHash = -1; + cacheTag = null; + // 反序列化偏移映射 if (tag.contains("offsets", Tag.TAG_COMPOUND)) { CompoundTag offsets = tag.getCompound("offsets"); @@ -635,7 +641,7 @@ public class LeashConfigManager { for (int i = 0; i < whitelistTag.size(); i++) { whitelist.add(whitelistTag.getString(i)); } - teleportWhitelistCache = Collections.unmodifiableList(whitelist); + teleportWhitelistCache = new ArrayList<>(whitelist); } if (tag.contains("command_prefix", Tag.TAG_STRING)) { @@ -681,7 +687,7 @@ public class LeashConfigManager { for (int i = 0; i < elasticityTag.size(); i++) { elasticity.add(elasticityTag.getDouble(i)); } - axisElasticity = Collections.unmodifiableList(elasticity); + axisElasticity = new ArrayList<>(elasticity); } if (tag.contains("max_leashes_per_entity", Tag.TAG_INT)) { @@ -707,10 +713,6 @@ public class LeashConfigManager { hash = fnv1aHashMap(hash, tagLeashMap); hash = fnv1aHashMap(hash, modLeashMap); - // 哈希白名单 - for (String entry : teleportWhitelistCache) { - hash = fnv1aHashString(hash, entry); - } // 哈希字符串参数 hash = fnv1aHashString(hash, commandPrefixCache); @@ -727,8 +729,18 @@ public class LeashConfigManager { hash = fnv1aHashLong(hash, Double.doubleToLongBits(extremeSnapFactor)); hash = fnv1aHashLong(hash, Double.doubleToLongBits(springDampening)); - // 哈希轴弹性列表 - for (double value : axisElasticity) { + + // 白名单排序后再哈希 + List sortedWhitelist = new ArrayList<>(teleportWhitelistCache); + Collections.sort(sortedWhitelist); + for (String entry : sortedWhitelist) { + hash = fnv1aHashString(hash, entry); + } + + // 轴弹性列表排序(或者保持原序但确保两端一致) + List sortedElasticity = new ArrayList<>(axisElasticity); + Collections.sort(sortedElasticity); + for (double value : sortedElasticity) { hash = fnv1aHashLong(hash, Double.doubleToLongBits(value)); } @@ -743,7 +755,7 @@ public class LeashConfigManager { private void serializeOffsetMap(CompoundTag parent, String key, @NotNull Map map) { CompoundTag mapTag = new CompoundTag(); for (Map.Entry entry : map.entrySet()) { - String entryKey = entry.getKey().replace(':', '_'); // 避免NBT键中的冒号问题 + String entryKey = entry.getKey(); ListTag offsetList = new ListTag(); for (double value : entry.getValue()) { offsetList.add(DoubleTag.valueOf(value)); @@ -764,12 +776,13 @@ public class LeashConfigManager { for (int i = 0; i < offsetList.size(); i++) { offset[i] = offsetList.getDouble(i); } - map.put(entryKey.replace('_', ':'), offset); // 恢复原始键名 + map.put(entryKey, offset); } } } } + // FNV-1a哈希辅助方法 private int fnv1aHashInt(int hash, int value) { hash ^= (value & 0xFF); @@ -797,7 +810,8 @@ public class LeashConfigManager { } private int fnv1aHashMap(int hash, @NotNull Map map) { - for (Map.Entry entry : map.entrySet()) { + Map sortedMap = map instanceof TreeMap ? map : new TreeMap<>(map); + for (Map.Entry entry : sortedMap.entrySet()) { hash = fnv1aHashString(hash, entry.getKey()); for (double value : entry.getValue()) { hash = fnv1aHashLong(hash, Double.doubleToLongBits(value)); diff --git a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java index 11cadeb..3966652 100644 --- a/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java +++ b/src/main/java/top/r3944realms/superleadrope/content/capability/impi/LeashDataImpl.java @@ -44,7 +44,7 @@ import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.api.event.SuperLeadRopeEvent; import top.r3944realms.superleadrope.api.type.capabilty.ILeashData; import top.r3944realms.superleadrope.api.type.capabilty.LeashInfo; -import top.r3944realms.superleadrope.compat.CurtainCompat; +import top.r3944realms.superleadrope.compat.FakePlayerJudge; import top.r3944realms.superleadrope.compat.LuckPermsCompat; import top.r3944realms.superleadrope.config.LeashConfigManager; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; @@ -785,7 +785,7 @@ public class LeashDataImpl implements ILeashData { } @Override public void applyLeashForcesClientPlayer() { - if (entity instanceof ServerPlayer player && CurtainCompat.isNotFakePlayer(player)) return; + if (entity instanceof ServerPlayer player && FakePlayerJudge.isNotFakePlayer(player)) return; Vec3 combinedForce = Vec3.ZERO; Vec3 combinedDirection = Vec3.ZERO; Map result = leashHolders.entrySet().stream() @@ -881,7 +881,7 @@ public class LeashDataImpl implements ILeashData { if (MinecraftForge.EVENT_BUS.post(hasFocus)) return; combinedForce = hasFocus.getCombinedForce(); // 玩家与普通实体统一力应用 - if (targetEntity instanceof ServerPlayer player && CurtainCompat.isNotFakePlayer(player) ) { + if (targetEntity instanceof ServerPlayer player && FakePlayerJudge.isNotFakePlayer(player)) { // 是真实玩家则交给客户端自行处理拴绳逻辑 // DO NOTHING if(targetEntity == entity) { diff --git a/src/main/java/top/r3944realms/superleadrope/network/toClient/SyncCommonConfigPacket.java b/src/main/java/top/r3944realms/superleadrope/network/toClient/SyncCommonConfigPacket.java index 2015753..4eaa9b9 100644 --- a/src/main/java/top/r3944realms/superleadrope/network/toClient/SyncCommonConfigPacket.java +++ b/src/main/java/top/r3944realms/superleadrope/network/toClient/SyncCommonConfigPacket.java @@ -18,9 +18,12 @@ package top.r3944realms.superleadrope.network.toClient; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.NotNull; import top.r3944realms.superleadrope.CommonEventHandler; import top.r3944realms.superleadrope.SuperLeadRope; +import java.util.Objects; +import java.util.Set; import java.util.function.Supplier; /** @@ -54,15 +57,61 @@ public record SyncCommonConfigPacket(CompoundTag config, int hash) { * @param msg the msg * @param ctx the ctx */ - public static void handle(SyncCommonConfigPacket msg, Supplier ctx) { + public static void handle(SyncCommonConfigPacket msg, @NotNull Supplier ctx) { ctx.get().enqueueWork(() -> { - CompoundTag old = CommonEventHandler.leashConfigManager.serializeToNBT(); + // 1. 保存当前配置(强制重新序列化,不使用缓存) + CompoundTag currentConfig = CommonEventHandler.leashConfigManager.serializeToNBT(); + int currentHash = CommonEventHandler.leashConfigManager.calculateConfigHash(); + + // 2. 应用新配置 CommonEventHandler.leashConfigManager.deserializeFromNBT(msg.config); - if (CommonEventHandler.leashConfigManager.calculateConfigHash() != msg.hash) { //BACK - SuperLeadRope.logger.error("Hash mismatch! Except:{}, Actual:{}", msg.hash, CommonEventHandler.leashConfigManager.calculateConfigHash()); - CommonEventHandler.leashConfigManager.deserializeFromNBT(old); + + // 3. 验证哈希 + int newHash = CommonEventHandler.leashConfigManager.calculateConfigHash(); + if (newHash != msg.hash) { + SuperLeadRope.logger.error("Hash mismatch! Expected: {}, Actual: {}", msg.hash, newHash); + SuperLeadRope.logger.error("Current hash before deserialization: {}", currentHash); + + // 可选:打印差异详情 + if (currentConfig != null && msg.config != null) { + compareConfigs(currentConfig, msg.config); + } + + // 4. 恢复旧配置 + CommonEventHandler.leashConfigManager.deserializeFromNBT(currentConfig); + } else { + SuperLeadRope.logger.debug("Config sync successful, hash: {}", msg.hash); } }); ctx.get().setPacketHandled(true); } + + // 辅助方法:比较配置差异 + private static void compareConfigs(CompoundTag oldConfig, CompoundTag newConfig) { + Set oldKeys = oldConfig.getAllKeys(); + Set newKeys = newConfig.getAllKeys(); + + // 找出只存在于旧配置的键 + for (String key : oldKeys) { + if (!newConfig.contains(key)) { + SuperLeadRope.logger.warn("Key only in old config: {}", key); + } + } + + // 找出只存在于新配置的键 + for (String key : newKeys) { + if (!oldConfig.contains(key)) { + SuperLeadRope.logger.warn("Key only in new config: {}", key); + } + } + + // 比较共同键的值 + for (String key : oldKeys) { + if (newConfig.contains(key) && !Objects.equals(oldConfig.get(key), newConfig.get(key))) { + SuperLeadRope.logger.warn("Value mismatch for key: {}", key); + SuperLeadRope.logger.warn(" Old: {}", oldConfig.get(key)); + SuperLeadRope.logger.warn(" New: {}", newConfig.get(key)); + } + } + } } diff --git a/src/main/java/top/r3944realms/superleadrope/workspace/WorkSpaceHelper.java b/src/main/java/top/r3944realms/superleadrope/workspace/WorkSpaceHelper.java index 6d2a4a6..aefe1b0 100644 --- a/src/main/java/top/r3944realms/superleadrope/workspace/WorkSpaceHelper.java +++ b/src/main/java/top/r3944realms/superleadrope/workspace/WorkSpaceHelper.java @@ -26,6 +26,7 @@ import top.r3944realms.superleadrope.api.type.capabilty.ILeashData; import top.r3944realms.superleadrope.api.type.capabilty.ILeashState; import top.r3944realms.superleadrope.api.type.util.ILeashHelper; import top.r3944realms.superleadrope.api.workspace.IWorkSpaceHelper; +import top.r3944realms.superleadrope.compat.FakePlayerJudge; import top.r3944realms.superleadrope.content.capability.impi.LeashDataImpl; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI; @@ -84,4 +85,9 @@ public class WorkSpaceHelper implements IWorkSpaceHelper { return LeashStateInnerAPI.getLeashState(pEntity); } + @Override + public void registerFakePlayer(@NotNull Class... classes) { + FakePlayerJudge.registersFakePlayer(classes); + } + }