diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/FallingBlockEntityMixin.java b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/FallingBlockEntityMixin.java index 11f4e46..81b9c6c 100644 --- a/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/FallingBlockEntityMixin.java +++ b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/FallingBlockEntityMixin.java @@ -1,25 +1,23 @@ package com.github.zly2006.carpetslsaddition.mixin.legacy; import com.github.zly2006.carpetslsaddition.SLSCarpetSettings; +import com.github.zly2006.carpetslsaddition.util.LegacyBlockChangeRecorder; import net.minecraft.core.BlockPos; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.resources.Identifier; -import net.minecraft.world.level.Level; -import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.ModifyVariable; /** - * [旧版] 落沙替换的核心注入点。 + * [旧版] 落沙替换的核心注入点——重建 1.12 的"替换窗口"(记录+回放版)。 * - *

26.1.2 的 {@code FallingBlockEntity.fall(Level, BlockPos, BlockState)} 直接使用传入的 state, - * 不像 1.12 那样在构造实体时重新读取世界。本 mixin 用 {@link ModifyVariable} 拦截该 state 参数: - * 当 {@code legacyFallingBlockReplace} 开启且配置了目标方块时,把 state 替换为目标方块的默认状态。 + *

1.12:{@code BlockFalling} 创建实体时【重读世界】,异步线程在"检查→读取"间改块即可替换。 + * 26.1.2 单线程下该窗口物理不存在(tick 内同步 fall,无让出)。 * - *

等价于 1.12 在"检查可下落"与"读取方块状态"之间的窗口替换效果——最终得到一个 - * "装着目标方块"的下落实体,落地后即还原为该目标方块。 + *

本 mixin 改用 {@link LegacyBlockChangeRecorder}: + * 当 {@code legacyFallingBlockReplace} 开启时,若该位置在最近 {@code WINDOW_TICKS} 内被改动过, + * 就用记录的新方块作为下落实体的内容。这样玩家搭建的装置(改变世界方块)会决定下落实体的内容, + * 与 1.12 的"装置驱动"玩法一致。 */ @Mixin(net.minecraft.world.entity.item.FallingBlockEntity.class) public class FallingBlockEntityMixin { @@ -30,18 +28,14 @@ public class FallingBlockEntityMixin { argsOnly = true, index = 2 ) - private static BlockState legacyReplaceFallingState(BlockState original) { + private static BlockState legacyReplaceFallingState(BlockState original, net.minecraft.world.level.Level level, BlockPos pos, BlockState state) { if (!SLSCarpetSettings.legacyExploitMode || !SLSCarpetSettings.legacyFallingBlockReplace) { return original; } - String target = SLSCarpetSettings.legacyFallingBlockTarget; - if (target == null || target.isEmpty() || "#none".equals(target)) { - return original; + BlockState recent = LegacyBlockChangeRecorder.getRecentChange(pos); + if (recent != null && !recent.isAir()) { + return recent; } - Block block = BuiltInRegistries.BLOCK.getOptional(Identifier.withDefaultNamespace(target)).orElse(null); - if (block == null) { - return original; - } - return block.defaultBlockState(); + return original; } } \ No newline at end of file diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/LevelChunkMixin.java b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/LevelChunkMixin.java new file mode 100644 index 0000000..7143330 --- /dev/null +++ b/src/main/java/com/github/zly2006/carpetslsaddition/mixin/legacy/LevelChunkMixin.java @@ -0,0 +1,35 @@ +package com.github.zly2006.carpetslsaddition.mixin.legacy; + +import com.github.zly2006.carpetslsaddition.SLSCarpetSettings; +import com.github.zly2006.carpetslsaddition.util.LegacyBlockChangeRecorder; +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.chunk.LevelChunk; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +/** + * [旧版] 方块变更记录——为"替换窗口"提供数据源。 + * + *

hook {@link LevelChunk#setBlockState}:当规则开启时,记录每次方块变更(位置 + 新状态 + 当前 tick)。 + * 重力方块下落时({@code FallingBlockEntityMixin})会查询此记录, + * 若该位置"刚被改过",就用记录的新方块作为下落实体内容——重建 1.12 的窗口替换效果。 + * + *

仅在 {@code legacyExploitMode} + {@code legacyFallingBlockReplace} 同时开启时记录,避免性能损耗。 + */ +@Mixin(LevelChunk.class) +public abstract class LevelChunkMixin { + + @Inject(method = "setBlockState", at = @At("RETURN")) + private void legacyRecordBlockChange(BlockPos pos, BlockState newState, int flags, CallbackInfoReturnable cir) { + if (!SLSCarpetSettings.legacyExploitMode || !SLSCarpetSettings.legacyFallingBlockReplace) { + return; + } + if (newState == null) { + return; + } + LegacyBlockChangeRecorder.record(pos, newState); + } +} \ No newline at end of file diff --git a/src/main/java/com/github/zly2006/carpetslsaddition/util/LegacyBlockChangeRecorder.java b/src/main/java/com/github/zly2006/carpetslsaddition/util/LegacyBlockChangeRecorder.java new file mode 100644 index 0000000..a1a2f51 --- /dev/null +++ b/src/main/java/com/github/zly2006/carpetslsaddition/util/LegacyBlockChangeRecorder.java @@ -0,0 +1,70 @@ +package com.github.zly2006.carpetslsaddition.util; + +import com.github.zly2006.carpetslsaddition.ServerMain; +import net.minecraft.core.BlockPos; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.block.state.BlockState; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * [旧版] 方块变更记录器——用于重建 1.12 的"替换窗口"。 + * + *

1.12 的窗口依赖"异步线程在主线程 tick 中途改块",26.1.2 单线程模型下物理不存在。 + * 本类改用"记录+回放":把最近发生的方块变更记录在此, + * 当重力方块触发下落时,若该位置"刚被改过",就用记录的新方块作为下落实体的内容—— + * 等价于 1.12 的窗口替换效果(只是窗口由 mod 主动开启)。 + * + *

记录以"服务器 tick 数"为界:仅保留最近 {@link #WINDOW_TICKS} 个 tick 内的变更, + * 超过即失效,避免记录无限增长或误伤无关方块。 + */ +public final class LegacyBlockChangeRecorder { + + private LegacyBlockChangeRecorder() {} + + /** 窗口大小:变更与下落之间允许的最大 tick 间隔。 */ + public static final long WINDOW_TICKS = 2L; + + private record Change(BlockState state, long tick) {} + + private static final Map RECENT = new ConcurrentHashMap<>(); + + /** 获取当前服务器 tick(拿不到时回退为 0)。 */ + private static long now() { + MinecraftServer server = ServerMain.server; + return server != null ? server.getTickCount() : 0L; + } + + /** 记录一次方块变更。 */ + public static void record(BlockPos pos, BlockState newState) { + RECENT.put(pos.asLong(), new Change(newState, now())); + } + + /** + * 查询某位置是否在窗口期内有变更记录。 + * + * @return 若命中窗口,返回记录的新状态;否则返回 null。 + */ + public static BlockState getRecentChange(BlockPos pos) { + Change c = RECENT.get(pos.asLong()); + if (c == null) { + return null; + } + if (now() - c.tick() > WINDOW_TICKS) { + RECENT.remove(pos.asLong()); + return null; + } + return c.state(); + } + + /** 清理过期记录。 */ + public static void cleanup() { + long t = now(); + RECENT.entrySet().removeIf(e -> t - e.getValue().tick() > WINDOW_TICKS); + } + + public static void clear() { + RECENT.clear(); + } +} \ No newline at end of file diff --git a/src/main/resources/slsaddition.mixins.json b/src/main/resources/slsaddition.mixins.json index fc2ec15..ecb715a 100644 --- a/src/main/resources/slsaddition.mixins.json +++ b/src/main/resources/slsaddition.mixins.json @@ -20,6 +20,7 @@ "legacy.FallingBlockEntityMixin", "legacy.ScheduledTickMixin", "legacy.ChunkMapMixin", + "legacy.LevelChunkMixin", "entity.MixinArmadilloEntity", "entity.MixinArmorStandEntity", "entity.MixinBoatEntity",