feat: 落沙替换改用记录+回放(装置驱动)

26.1.2单线程下1.12的替换窗口物理不存在,改用LegacyBlockChangeRecorder记录最近方块变更,fall时若命中窗口则用记录的新方块,保留装置驱动玩法。新增LevelChunkMixin记录setBlockState。
This commit is contained in:
AxTps committed 2026-10-04 18:56:50 +08:00
1 parent c42fffad3c
commit 0649034969
4 files changed
+119 -19

No files matched your search

@@ -1,25 +1,23 @@
package com.github.zly2006.carpetslsaddition.mixin.legacy; package com.github.zly2006.carpetslsaddition.mixin.legacy;
import com.github.zly2006.carpetslsaddition.SLSCarpetSettings; import com.github.zly2006.carpetslsaddition.SLSCarpetSettings;
import com.github.zly2006.carpetslsaddition.util.LegacyBlockChangeRecorder;
import net.minecraft.core.BlockPos; 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 net.minecraft.world.level.block.state.BlockState;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.ModifyVariable;
/** /**
* [旧版] 落沙替换的核心注入点。 * [旧版] 落沙替换的核心注入点——重建 1.12 的"替换窗口"(记录+回放版)。
* *
* <p>26.1.2 的 {@code FallingBlockEntity.fall(Level, BlockPos, BlockState)} 直接使用传入的 state, * <p>1.12:{@code BlockFalling} 创建实体时【重读世界】,异步线程在"检查→读取"间改块即可替换。
* 不像 1.12 那样在构造实体时重新读取世界。本 mixin 用 {@link ModifyVariable} 拦截该 state 参数: * 26.1.2 单线程下该窗口物理不存在(tick 内同步 fall,无让出)。
* 当 {@code legacyFallingBlockReplace} 开启且配置了目标方块时,把 state 替换为目标方块的默认状态。
* *
* <p>等价于 1.12 在"检查可下落"与"读取方块状态"之间的窗口替换效果——最终得到一个 * <p>本 mixin 改用 {@link LegacyBlockChangeRecorder}:
* "装着目标方块"的下落实体,落地后即还原为该目标方块。 * 当 {@code legacyFallingBlockReplace} 开启时,若该位置在最近 {@code WINDOW_TICKS} 内被改动过,
* 就用记录的新方块作为下落实体的内容。这样玩家搭建的装置(改变世界方块)会决定下落实体的内容,
* 与 1.12 的"装置驱动"玩法一致。
*/ */
@Mixin(net.minecraft.world.entity.item.FallingBlockEntity.class) @Mixin(net.minecraft.world.entity.item.FallingBlockEntity.class)
public class FallingBlockEntityMixin { public class FallingBlockEntityMixin {
@@ -30,18 +28,14 @@ public class FallingBlockEntityMixin {
argsOnly = true, argsOnly = true,
index = 2 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) { if (!SLSCarpetSettings.legacyExploitMode || !SLSCarpetSettings.legacyFallingBlockReplace) {
return original; return original;
} }
String target = SLSCarpetSettings.legacyFallingBlockTarget; BlockState recent = LegacyBlockChangeRecorder.getRecentChange(pos);
if (target == null || target.isEmpty() || "#none".equals(target)) { if (recent != null && !recent.isAir()) {
return original; return recent;
} }
Block block = BuiltInRegistries.BLOCK.getOptional(Identifier.withDefaultNamespace(target)).orElse(null); return original;
if (block == null) {
return original;
}
return block.defaultBlockState();
} }
} }
@@ -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;
/**
* [旧版] 方块变更记录——为"替换窗口"提供数据源。
*
* <p>hook {@link LevelChunk#setBlockState}:当规则开启时,记录每次方块变更(位置 + 新状态 + 当前 tick)。
* 重力方块下落时({@code FallingBlockEntityMixin})会查询此记录,
* 若该位置"刚被改过",就用记录的新方块作为下落实体内容——重建 1.12 的窗口替换效果。
*
* <p>仅在 {@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<BlockState> cir) {
if (!SLSCarpetSettings.legacyExploitMode || !SLSCarpetSettings.legacyFallingBlockReplace) {
return;
}
if (newState == null) {
return;
}
LegacyBlockChangeRecorder.record(pos, newState);
}
}
@@ -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 的"替换窗口"。
*
* <p>1.12 的窗口依赖"异步线程在主线程 tick 中途改块",26.1.2 单线程模型下物理不存在。
* 本类改用"记录+回放":把最近发生的方块变更记录在此,
* 当重力方块触发下落时,若该位置"刚被改过",就用记录的新方块作为下落实体的内容——
* 等价于 1.12 的窗口替换效果(只是窗口由 mod 主动开启)。
*
* <p>记录以"服务器 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<Long, Change> 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();
}
}
@@ -20,6 +20,7 @@
"legacy.FallingBlockEntityMixin", "legacy.FallingBlockEntityMixin",
"legacy.ScheduledTickMixin", "legacy.ScheduledTickMixin",
"legacy.ChunkMapMixin", "legacy.ChunkMapMixin",
"legacy.LevelChunkMixin",
"entity.MixinArmadilloEntity", "entity.MixinArmadilloEntity",
"entity.MixinArmorStandEntity", "entity.MixinArmorStandEntity",
"entity.MixinBoatEntity", "entity.MixinBoatEntity",