版本更新 1.2.2 -> 1.2.3

修复拴绳配置同步问题
This commit is contained in:
叁玖领域 2026-03-30 20:21:59 +08:00
parent 0b6412548f
commit 18d1d06d39
10 changed files with 172 additions and 33 deletions

View File

@ -123,6 +123,11 @@ legacyForge {
server { server {
server() server()
} }
testServer {
server()
//
gameDirectory = file('run/server')
}
data { data {
data() data()
programArguments.addAll '--mod', mod_id, '--all', programArguments.addAll '--mod', mod_id, '--all',

View File

@ -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. # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=GPLv3 mod_license=GPLv3
# The mod version. See https://semver.org/ # 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. # 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. # This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html # See https://maven.apache.org/guides/mini/guide-naming-conventions.html

View File

@ -23,6 +23,7 @@ import net.minecraftforge.fml.config.ModConfig;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import top.r3944realms.superleadrope.compat.CurtainCompat;
import top.r3944realms.superleadrope.compat.WayStoneCompat; import top.r3944realms.superleadrope.compat.WayStoneCompat;
import top.r3944realms.superleadrope.config.LeashCommonConfig; import top.r3944realms.superleadrope.config.LeashCommonConfig;
import top.r3944realms.superleadrope.core.register.*; import top.r3944realms.superleadrope.core.register.*;
@ -72,6 +73,7 @@ public class SuperLeadRope {
ModLoadingContext modLoadingContext = ModLoadingContext.get(); ModLoadingContext modLoadingContext = ModLoadingContext.get();
ConfigUtil.registerConfig(modLoadingContext, ModConfig.Type.COMMON, LeashCommonConfig.SPEC, c, "leash"); ConfigUtil.registerConfig(modLoadingContext, ModConfig.Type.COMMON, LeashCommonConfig.SPEC, c, "leash");
WayStoneCompat.init(); WayStoneCompat.init();
CurtainCompat.init();
} }
/** /**

View File

@ -104,4 +104,11 @@ public interface IWorkSpaceHelper {
* @return the leash state * @return the leash state
*/ */
Optional<ILeashState> getLeashState(@NotNull Entity pEntity); Optional<ILeashState> getLeashState(@NotNull Entity pEntity);
/**
* Register fake player.
*
* @param classes the classes
*/
void registerFakePlayer(@NotNull Class<?>... classes);
} }

View File

@ -29,15 +29,11 @@ public class CurtainCompat{
public final static boolean isModLoaded = ModList.get().isLoaded("curtain"); public final static boolean isModLoaded = ModList.get().isLoaded("curtain");
/** /**
* Is not fake player boolean. * Init.
*
* @param player the player
* @return the boolean
*/ */
public static boolean isNotFakePlayer(Player player) { public static void init() {
if (isModLoaded) { if (isModLoaded) {
return !(player instanceof EntityPlayerMPFake); FakePlayerJudge.registersFakePlayer(EntityPlayerMPFake.class);
} }
return true;
} }
} }

View File

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Class<?>> 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());
}
}

View File

@ -228,7 +228,7 @@ public class LeashConfigManager {
* @return the teleport whitelist * @return the teleport whitelist
*/ */
// ================== 白名单 ================== // ================== 白名单 ==================
public List<String> getTeleportWhitelist() { return Collections.unmodifiableList(teleportWhitelistCache); } public List<String> getTeleportWhitelist() { return new ArrayList<>(teleportWhitelistCache); }
/** /**
* Is entity teleport allowed boolean. * Is entity teleport allowed boolean.
@ -358,7 +358,7 @@ public class LeashConfigManager {
* *
* @return the axis elasticity * @return the axis elasticity
*/ */
public List<Double> getAxisElasticity() { return Collections.unmodifiableList(axisElasticity); } public List<Double> getAxisElasticity() { return new ArrayList<>(axisElasticity); }
/** /**
* Gets x elasticity. * Gets x elasticity.
@ -423,7 +423,7 @@ public class LeashConfigManager {
maxForce = LeashCommonConfig.COMMON.maxForce.get(); maxForce = LeashCommonConfig.COMMON.maxForce.get();
playerSpringFactor = LeashCommonConfig.COMMON.playerSpringFactor.get(); playerSpringFactor = LeashCommonConfig.COMMON.playerSpringFactor.get();
mobSpringFactor = LeashCommonConfig.COMMON.mobSpringFactor.get(); mobSpringFactor = LeashCommonConfig.COMMON.mobSpringFactor.get();
cacheHash = calculateConfigHash(); cacheHash = -1;
cacheTag = serializeToNBT(); cacheTag = serializeToNBT();
SuperLeadRope.logger.debug("Configs reloaded: {}", getStats()); SuperLeadRope.logger.debug("Configs reloaded: {}", getStats());
} catch (Exception e) { } catch (Exception e) {
@ -476,7 +476,11 @@ public class LeashConfigManager {
* @return the compound tag * @return the compound tag
*/ */
public synchronized CompoundTag serializeToNBT() { 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(); CompoundTag tag = new CompoundTag();
// 序列化偏移映射 // 序列化偏移映射
@ -518,7 +522,7 @@ public class LeashConfigManager {
tag.put("axis_elasticity", elasticityTag); tag.put("axis_elasticity", elasticityTag);
tag.putInt("max_leashes_per_entity", maxLeashesPerEntity); tag.putInt("max_leashes_per_entity", maxLeashesPerEntity);
cacheHash = calculateConfigHash(); cacheHash = currentHash;
cacheTag = tag; cacheTag = tag;
return tag; return tag;
@ -554,7 +558,6 @@ public class LeashConfigManager {
LeashCommonConfig.COMMON.maxForce.set(maxForce); LeashCommonConfig.COMMON.maxForce.set(maxForce);
LeashCommonConfig.COMMON.playerSpringFactor.set(playerSpringFactor); LeashCommonConfig.COMMON.playerSpringFactor.set(playerSpringFactor);
LeashCommonConfig.COMMON.mobSpringFactor.set(mobSpringFactor); LeashCommonConfig.COMMON.mobSpringFactor.set(mobSpringFactor);
} }
/** /**
@ -617,6 +620,9 @@ public class LeashConfigManager {
public void deserializeFromNBT(CompoundTag tag) { public void deserializeFromNBT(CompoundTag tag) {
if (tag == null || tag.isEmpty()) return; if (tag == null || tag.isEmpty()) return;
cacheHash = -1;
cacheTag = null;
// 反序列化偏移映射 // 反序列化偏移映射
if (tag.contains("offsets", Tag.TAG_COMPOUND)) { if (tag.contains("offsets", Tag.TAG_COMPOUND)) {
CompoundTag offsets = tag.getCompound("offsets"); CompoundTag offsets = tag.getCompound("offsets");
@ -635,7 +641,7 @@ public class LeashConfigManager {
for (int i = 0; i < whitelistTag.size(); i++) { for (int i = 0; i < whitelistTag.size(); i++) {
whitelist.add(whitelistTag.getString(i)); whitelist.add(whitelistTag.getString(i));
} }
teleportWhitelistCache = Collections.unmodifiableList(whitelist); teleportWhitelistCache = new ArrayList<>(whitelist);
} }
if (tag.contains("command_prefix", Tag.TAG_STRING)) { if (tag.contains("command_prefix", Tag.TAG_STRING)) {
@ -681,7 +687,7 @@ public class LeashConfigManager {
for (int i = 0; i < elasticityTag.size(); i++) { for (int i = 0; i < elasticityTag.size(); i++) {
elasticity.add(elasticityTag.getDouble(i)); elasticity.add(elasticityTag.getDouble(i));
} }
axisElasticity = Collections.unmodifiableList(elasticity); axisElasticity = new ArrayList<>(elasticity);
} }
if (tag.contains("max_leashes_per_entity", Tag.TAG_INT)) { if (tag.contains("max_leashes_per_entity", Tag.TAG_INT)) {
@ -707,10 +713,6 @@ public class LeashConfigManager {
hash = fnv1aHashMap(hash, tagLeashMap); hash = fnv1aHashMap(hash, tagLeashMap);
hash = fnv1aHashMap(hash, modLeashMap); hash = fnv1aHashMap(hash, modLeashMap);
// 哈希白名单
for (String entry : teleportWhitelistCache) {
hash = fnv1aHashString(hash, entry);
}
// 哈希字符串参数 // 哈希字符串参数
hash = fnv1aHashString(hash, commandPrefixCache); hash = fnv1aHashString(hash, commandPrefixCache);
@ -727,8 +729,18 @@ public class LeashConfigManager {
hash = fnv1aHashLong(hash, Double.doubleToLongBits(extremeSnapFactor)); hash = fnv1aHashLong(hash, Double.doubleToLongBits(extremeSnapFactor));
hash = fnv1aHashLong(hash, Double.doubleToLongBits(springDampening)); hash = fnv1aHashLong(hash, Double.doubleToLongBits(springDampening));
// 哈希轴弹性列表
for (double value : axisElasticity) { // 白名单排序后再哈希
List<String> sortedWhitelist = new ArrayList<>(teleportWhitelistCache);
Collections.sort(sortedWhitelist);
for (String entry : sortedWhitelist) {
hash = fnv1aHashString(hash, entry);
}
// 轴弹性列表排序或者保持原序但确保两端一致
List<Double> sortedElasticity = new ArrayList<>(axisElasticity);
Collections.sort(sortedElasticity);
for (double value : sortedElasticity) {
hash = fnv1aHashLong(hash, Double.doubleToLongBits(value)); hash = fnv1aHashLong(hash, Double.doubleToLongBits(value));
} }
@ -743,7 +755,7 @@ public class LeashConfigManager {
private void serializeOffsetMap(CompoundTag parent, String key, @NotNull Map<String, double[]> map) { private void serializeOffsetMap(CompoundTag parent, String key, @NotNull Map<String, double[]> map) {
CompoundTag mapTag = new CompoundTag(); CompoundTag mapTag = new CompoundTag();
for (Map.Entry<String, double[]> entry : map.entrySet()) { for (Map.Entry<String, double[]> entry : map.entrySet()) {
String entryKey = entry.getKey().replace(':', '_'); // 避免NBT键中的冒号问题 String entryKey = entry.getKey();
ListTag offsetList = new ListTag(); ListTag offsetList = new ListTag();
for (double value : entry.getValue()) { for (double value : entry.getValue()) {
offsetList.add(DoubleTag.valueOf(value)); offsetList.add(DoubleTag.valueOf(value));
@ -764,12 +776,13 @@ public class LeashConfigManager {
for (int i = 0; i < offsetList.size(); i++) { for (int i = 0; i < offsetList.size(); i++) {
offset[i] = offsetList.getDouble(i); offset[i] = offsetList.getDouble(i);
} }
map.put(entryKey.replace('_', ':'), offset); // 恢复原始键名 map.put(entryKey, offset);
} }
} }
} }
} }
// FNV-1a哈希辅助方法 // FNV-1a哈希辅助方法
private int fnv1aHashInt(int hash, int value) { private int fnv1aHashInt(int hash, int value) {
hash ^= (value & 0xFF); hash ^= (value & 0xFF);
@ -797,7 +810,8 @@ public class LeashConfigManager {
} }
private int fnv1aHashMap(int hash, @NotNull Map<String, double[]> map) { private int fnv1aHashMap(int hash, @NotNull Map<String, double[]> map) {
for (Map.Entry<String, double[]> entry : map.entrySet()) { Map<String, double[]> sortedMap = map instanceof TreeMap ? map : new TreeMap<>(map);
for (Map.Entry<String, double[]> entry : sortedMap.entrySet()) {
hash = fnv1aHashString(hash, entry.getKey()); hash = fnv1aHashString(hash, entry.getKey());
for (double value : entry.getValue()) { for (double value : entry.getValue()) {
hash = fnv1aHashLong(hash, Double.doubleToLongBits(value)); hash = fnv1aHashLong(hash, Double.doubleToLongBits(value));

View File

@ -44,7 +44,7 @@ import top.r3944realms.superleadrope.SuperLeadRope;
import top.r3944realms.superleadrope.api.event.SuperLeadRopeEvent; import top.r3944realms.superleadrope.api.event.SuperLeadRopeEvent;
import top.r3944realms.superleadrope.api.type.capabilty.ILeashData; import top.r3944realms.superleadrope.api.type.capabilty.ILeashData;
import top.r3944realms.superleadrope.api.type.capabilty.LeashInfo; 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.compat.LuckPermsCompat;
import top.r3944realms.superleadrope.config.LeashConfigManager; import top.r3944realms.superleadrope.config.LeashConfigManager;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
@ -785,7 +785,7 @@ public class LeashDataImpl implements ILeashData {
} }
@Override @Override
public void applyLeashForcesClientPlayer() { 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 combinedForce = Vec3.ZERO;
Vec3 combinedDirection = Vec3.ZERO; Vec3 combinedDirection = Vec3.ZERO;
Map<Integer, LeashInfo> result = leashHolders.entrySet().stream() Map<Integer, LeashInfo> result = leashHolders.entrySet().stream()
@ -881,7 +881,7 @@ public class LeashDataImpl implements ILeashData {
if (MinecraftForge.EVENT_BUS.post(hasFocus)) return; if (MinecraftForge.EVENT_BUS.post(hasFocus)) return;
combinedForce = hasFocus.getCombinedForce(); combinedForce = hasFocus.getCombinedForce();
// 玩家与普通实体统一力应用 // 玩家与普通实体统一力应用
if (targetEntity instanceof ServerPlayer player && CurtainCompat.isNotFakePlayer(player) ) { if (targetEntity instanceof ServerPlayer player && FakePlayerJudge.isNotFakePlayer(player)) {
// 是真实玩家则交给客户端自行处理拴绳逻辑 // 是真实玩家则交给客户端自行处理拴绳逻辑
// DO NOTHING // DO NOTHING
if(targetEntity == entity) { if(targetEntity == entity) {

View File

@ -18,9 +18,12 @@ package top.r3944realms.superleadrope.network.toClient;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.NotNull;
import top.r3944realms.superleadrope.CommonEventHandler; import top.r3944realms.superleadrope.CommonEventHandler;
import top.r3944realms.superleadrope.SuperLeadRope; import top.r3944realms.superleadrope.SuperLeadRope;
import java.util.Objects;
import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@ -54,15 +57,61 @@ public record SyncCommonConfigPacket(CompoundTag config, int hash) {
* @param msg the msg * @param msg the msg
* @param ctx the ctx * @param ctx the ctx
*/ */
public static void handle(SyncCommonConfigPacket msg, Supplier<NetworkEvent.Context> ctx) { public static void handle(SyncCommonConfigPacket msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { 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); CommonEventHandler.leashConfigManager.deserializeFromNBT(msg.config);
if (CommonEventHandler.leashConfigManager.calculateConfigHash() != msg.hash) { //BACK
SuperLeadRope.logger.error("Hash mismatch! Except:{}, Actual:{}", msg.hash, CommonEventHandler.leashConfigManager.calculateConfigHash()); // 3. 验证哈希
CommonEventHandler.leashConfigManager.deserializeFromNBT(old); 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); ctx.get().setPacketHandled(true);
} }
// 辅助方法比较配置差异
private static void compareConfigs(CompoundTag oldConfig, CompoundTag newConfig) {
Set<String> oldKeys = oldConfig.getAllKeys();
Set<String> 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));
}
}
}
} }

View File

@ -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.capabilty.ILeashState;
import top.r3944realms.superleadrope.api.type.util.ILeashHelper; import top.r3944realms.superleadrope.api.type.util.ILeashHelper;
import top.r3944realms.superleadrope.api.workspace.IWorkSpaceHelper; 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.capability.impi.LeashDataImpl;
import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity; import top.r3944realms.superleadrope.content.entity.SuperLeashKnotEntity;
import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI; import top.r3944realms.superleadrope.util.capability.LeashDataInnerAPI;
@ -84,4 +85,9 @@ public class WorkSpaceHelper implements IWorkSpaceHelper {
return LeashStateInnerAPI.getLeashState(pEntity); return LeashStateInnerAPI.getLeashState(pEntity);
} }
@Override
public void registerFakePlayer(@NotNull Class<?>... classes) {
FakePlayerJudge.registersFakePlayer(classes);
}
} }