feat: 补全 /place structure 的结构注册,使其与自然生成一致

原版 PlaceCommand.placeStructure 只调 StructureStart.placeInChunk 摆放方块,从不把 StructureStart 注册进 StructureManager / 区块 StructureAccess,导致结构重启丢失、专属刷怪规则不激活。

新增 PlaceCommandMixin,在 placeStructure 返回前补做自然生成的三步:1) setStartForStructure 把 start 存进所在区块;2) createReferences 写 17x17 邻居引用;3) markUnsaved 强制存盘。全程 try-catch 兜底,注册失败不影响命令返回。

项目骨架参照 CarpetSLSAddition:Loom 1.18-SNAPSHOT + Gradle 9.7.1 + Java 25 + official 映射。
This commit is contained in:
AxTps committed 2026-10-03 15:40:16 +08:00
1 parent cdd30c0190
commit eba2bdd5e3
12 files changed
+611

No files matched your search

@@ -0,0 +1,88 @@
package com.github.axtps.placemod.mixin;
import com.github.axtps.placemod.PlaceMod;
import net.minecraft.core.SectionPos;
import net.minecraft.server.commands.PlaceCommand;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.StructureManager;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureStart;
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.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
/**
* 让 /place structure 生成的结构"跟自然生成一样"。
*
* 原版 PlaceCommand.placeStructure 只调了 StructureStart.placeInChunk 摆放方块,
* 从不把 StructureStart 注册进 StructureManager / 区块 StructureAccess。
* 结果:结构不进 StructureManager、不写区块 NBT 引用 → 重启丢失、专属刷怪规则不激活。
*
* 本 Mixin 在 placeStructure 返回前,补做自然生成的注册步骤:
* 1. 对结构所在区块 setStartForStructure,把 start 存进区块;
* 2. 调 ChunkGenerator.createReferences 写周围区块引用(跟自然生成同款逻辑);
* 3. markUnsaved 标记涉及的区块,强制存盘。
*/
@Mixin(PlaceCommand.class)
public class PlaceCommandMixin {
@Inject(method = "placeStructure", at = @At("RETURN"), locals = LocalCapture.CAPTURE_FAILHARD)
private static void placemod$registerStructure(net.minecraft.commands.CommandSourceStack source,
net.minecraft.core.Holder.Reference<Structure> structureHolder,
net.minecraft.core.BlockPos pos,
CallbackInfo ci,
ServerLevel level,
Structure structure,
ChunkGenerator generator,
StructureStart start) {
if (start == null || !start.isValid()) {
return;
}
try {
StructureManager structureManager = level.structureManager();
BoundingBox box = start.getBoundingBox();
// 结构包围盒覆盖的区块范围
ChunkPos minChunk = new ChunkPos(
SectionPos.blockToSectionCoord(box.minX()),
SectionPos.blockToSectionCoord(box.minZ()));
ChunkPos maxChunk = new ChunkPos(
SectionPos.blockToSectionCoord(box.maxX()),
SectionPos.blockToSectionCoord(box.maxZ()));
// 1) 把 StructureStart 存进它自己所在的区块
// (自然生成在 ChunkGenerator.createStructures 里做)
ChunkPos startChunk = start.getChunkPos();
ChunkAccess startChunkAccess = level.getChunk(startChunk.x(), startChunk.z());
structureManager.setStartForStructure(
SectionPos.bottomOf(startChunkAccess), structure, start, startChunkAccess);
startChunkAccess.markUnsaved();
// 2) 引用写入:对涉及的每个区块调 createReferences,
// 它内部会扫 17x17 邻居,把相交的 StructureStart 引用写进区块。
// 跟自然生成用的是同一个方法,保证"完全一样"。
ChunkPos.rangeClosed(minChunk, maxChunk).forEach(cp -> {
ChunkAccess chunk = level.getChunk(cp.x(), cp.z());
try {
generator.createReferences(level, structureManager, chunk);
chunk.markUnsaved();
} catch (Exception e) {
PlaceMod.LOGGER.error("[place-mod] createReferences 失败 @ {}: {}", cp, e.toString());
}
});
PlaceMod.LOGGER.info("[place-mod] 已为结构 {} 补注册 StructureStart @ {}",
structure, startChunk);
} catch (Throwable t) {
// 注册失败不能影响命令本身的成功返回
PlaceMod.LOGGER.error("[place-mod] 补注册结构时出错(已忽略): ", t);
}
}
}