故事的起因是有帮twf写模组时,需要用到查找结构指令显示坐标给玩家,但是玩家不一定有权限或者ftbq发送并不会sendmessage给玩家,所以有了此代码。
我也将结构查找的功能写成了一个类,可以直接拿去用。
public class StructureLocator {
private static final ResourceKey<Registry<ConfiguredStructureFeature<?, ?>>> STRUCTURE_REGISTRY_KEY =
Registry.CONFIGURED_STRUCTURE_FEATURE_REGISTRY;
/**
* 根据单个结构 ResourceLocation(如 "minecraft:village" 或者自定义 mod:id)查找最近的那个点。
*
* @param level 当前维度
* @param center 中心搜索点
* @param id 结构的 ResourceLocation
* @param radius 搜索半径(方块数)
* @param skipKnown 是否跳过已探索过的结构
* @return 如果找到,返回 Pair(结构坐标, Holder<该结构>); 找不到则 empty()
*/
public static Optional<Pair<BlockPos, Holder<ConfiguredStructureFeature<?, ?>>>> findNearest(
ServerLevel level,
BlockPos center,
ResourceLocation id,
int radius,
boolean skipKnown
) {
Registry<ConfiguredStructureFeature<?, ?>> registry =
level.registryAccess().registryOrThrow(STRUCTURE_REGISTRY_KEY);
ResourceKey<ConfiguredStructureFeature<?, ?>> key =
ResourceKey.create(STRUCTURE_REGISTRY_KEY, id);
Holder<ConfiguredStructureFeature<?, ?>> holder =
registry.getHolder(key).orElse(null);
if (holder == null) return Optional.empty();
HolderSet<ConfiguredStructureFeature<?, ?>> holderSet = HolderSet.direct(holder);
Pair<BlockPos, Holder<ConfiguredStructureFeature<?, ?>>> result =
level.getChunkSource()
.getGenerator()
.findNearestMapFeature(level, holderSet, center, radius, skipKnown);
return Optional.ofNullable(result);
}
/**
* 根据 TagKey(像 "#minecraft:village")来查找最近的结构。
*
* @param level 当前维度
* @param center 中心搜索点
* @param tagKey 结构 TagKey(Registry.CONFIGURED_STRUCTURE_FEATURE_REGISTRY 下的 Tag)
* @param radius 搜索半径
* @param skipKnown 是否跳过已探索结构
* @return 如果找到,返回 Pair(结构坐标, Holder<该结构>); 找不到则 empty()
*/
public static Optional<Pair<BlockPos, Holder<ConfiguredStructureFeature<?, ?>>>> findNearestByTag(
ServerLevel level,
BlockPos center,
TagKey<ConfiguredStructureFeature<?, ?>> tagKey,
int radius,
boolean skipKnown
) {
Registry<ConfiguredStructureFeature<?, ?>> registry =
level.registryAccess().registryOrThrow(STRUCTURE_REGISTRY_KEY);
HolderSet<ConfiguredStructureFeature<?, ?>> holderSet =
registry.getOrCreateTag(tagKey);
Pair<BlockPos, Holder<ConfiguredStructureFeature<?, ?>>> result =
level.getChunkSource()
.getGenerator()
.findNearestMapFeature(level, holderSet, center, radius, skipKnown);
return Optional.ofNullable(result);
}
/**
* 把查到的结果格式化成一个聊天用的组件(绿色坐标 + 距离)。
*
* @param structureName 你想显示的结构名称(如 "minecraft:village" 或 "#minecraft:village")
* @param origin 搜索中心
* @param pair findNearest 返回的 Pair
* @param translateKey 翻译 key,通常用 "commands.locate.success"
*/
public static Component formatLocateResult(
String structureName,
BlockPos origin,
Pair<BlockPos, Holder<ConfiguredStructureFeature<?, ?>>> pair,
String translateKey
) {
BlockPos found = pair.getFirst();
int distance = Mth.floor(
dist(origin.getX(), origin.getZ(), found.getX(), found.getZ())
);
MutableComponent coords = ComponentUtils.wrapInSquareBrackets(
new TranslatableComponent("chat.coordinates", found.getX(), "~", found.getZ())
);
return new TranslatableComponent(
translateKey, structureName, coords, distance
);
}
private static float dist(int x1, int z1, int x2, int z2) {
int dx = x2 - x1, dz = z2 - z1;
return Mth.sqrt((float) (dx * dx + dz * dz));
}
}
这部分代码就是查找结构有关的类了,通过获取ConfiguredStructureFeature的注册表,然后再获取结构的Holder类,再给ChunkGenerator的findNearestMapFeature方法去获取到BlockPos。
@Mixin(ServerPlayer.class)
public class ServerPlayerMixin {
@ModifyArg(method = "sendMessage(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/ChatType;Ljava/util/UUID;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/protocol/game/ClientboundChatPacket;<init>(Lnet/minecraft/network/chat/Component;Lnet/minecraft/network/chat/ChatType;Ljava/util/UUID;)V"), index = 0)
public Component modifyMessage(Component component){
MutableComponent mutableComponent = component.copy();
String message = mutableComponent.getString();
ServerPlayer serverPlayer = (ServerPlayer) (Object) this;
if (message.contains("<structure>") && message.contains("</structure>")) {
String structure = message.replaceAll(".*<structure>(.*?)</structure>.*", "$1");
BlockPos center = serverPlayer.blockPosition();
Optional<Pair<BlockPos, Holder<ConfiguredStructureFeature<?, ?>>>> holderPair = StructureLocator.findNearest(
serverPlayer.getLevel(),
center,
new ResourceLocation(structure),
100,
false
);
AtomicReference<String> replace = new AtomicReference<>();
holderPair.ifPresentOrElse(pair -> {
Component component1 = StructureLocator.formatLocateResult(structure, center, pair, "commands.locate.success");
replace.set(component1.getString());
}, () -> {
replace.set(new TranslatableComponent("commands.locate.failed", structure).getString());
});
Style style = component.getStyle();
mutableComponent = new TextComponent(message.replaceAll("<structure>(.*?)</structure>", "§a%s§r".formatted(replace.get()))).withStyle(style);
}
return mutableComponent;
}
}
这段mixin则是注入ServerPlayer的sendMessage方法,修改new ClientboundChatPacket(message, type, sender)的message参数,||因为我偷懒,所以没用MixinExtra||,用正则表达式进行匹配到对应的结构格式文字,然后修改原文字中的内容。
这差不多都就是本次代码的全部内容了。