Meteor Client自动告示牌放置模块修复及扩展需求问询
Meteor Client 自定义告示牌自动放置模块修复及后续功能扩展方案
一、现有模块修复方案
1. 核心问题排查
常见故障原因包括:模块未正确注册、物品检测逻辑错误、放置位置判断失效、无冷却控制导致触发频率异常、未适配Meteor Client的事件监听机制。
2. 修复后代码实现
(1) Addon.java(模块注册类)
import meteordevelopment.meteorclient.addons.MeteorAddon; import meteordevelopment.meteorclient.systems.modules.Category; import meteordevelopment.meteorclient.systems.modules.Modules; public class SignPlacerAddon extends MeteorAddon { public static final Category CATEGORY = new Category("Sign Placer"); @Override public void onInitialize() { Modules.get().add(new ModuleSignPlacer()); } @Override public String getPackage() { return "com.yourname.signplacer"; // 替换为你的实际包名 } }
(2) ModuleSignPlacer.java(核心功能类)
import meteordevelopment.meteorclient.events.world.TickEvent; import meteordevelopment.meteorclient.settings.BoolSetting; import meteordevelopment.meteorclient.settings.IntSetting; import meteordevelopment.meteorclient.settings.SettingGroup; import meteordevelopment.meteorclient.systems.modules.Module; import meteordevelopment.orbit.EventHandler; import net.minecraft.item.Items; import net.minecraft.util.Hand; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.math.Vec3d; public class ModuleSignPlacer extends Module { private final SettingGroup sgGeneral = settings.getDefaultGroup(); // 放置范围设置 private final IntSetting range = sgGeneral.add(new IntSetting.Builder() .name("range") .description("放置告示牌的最大范围") .defaultValue(3) .min(1) .max(5) .build() ); // 潜行触发开关 private final BoolSetting onlyWhenSneaking = sgGeneral.add(new BoolSetting.Builder() .name("only-when-sneaking") .description("仅在潜行时执行放置") .defaultValue(false) .build() ); // 放置延迟控制 private final IntSetting delay = sgGeneral.add(new IntSetting.Builder() .name("delay") .description("两次放置的间隔(游戏刻)") .defaultValue(2) .min(1) .max(10) .build() ); private int tickCounter = 0; public ModuleSignPlacer() { super(SignPlacerAddon.CATEGORY, "sign-placer", "自动在玩家周围放置告示牌"); } @EventHandler private void onTick(TickEvent.Post event) { // 潜行条件检查 if (onlyWhenSneaking.get() && !mc.player.isSneaking()) return; // 延迟控制 if (tickCounter < delay.get()) { tickCounter++; return; } tickCounter = 0; // 手持物品检查 Hand hand = mc.player.getMainHandStack().isOf(Items.OAK_SIGN) ? Hand.MAIN_HAND : Hand.OFF_HAND; if (!mc.player.getStackInHand(hand).isOf(Items.OAK_SIGN)) return; // 遍历周围可放置位置 BlockPos playerPos = mc.player.getBlockPos(); int maxRange = range.get(); for (int x = -maxRange; x <= maxRange; x++) { for (int y = -maxRange; y <= maxRange; y++) { for (int z = -maxRange; z <= maxRange; z++) { BlockPos basePos = playerPos.add(x, y, z); // 检查基底是否为固体方块 if (!mc.world.getBlockState(basePos).isSolid()) continue; // 尝试在方块六个面放置 for (Direction dir : Direction.values()) { BlockPos placePos = basePos.offset(dir); if (!mc.world.getBlockState(placePos).isAir()) continue; // 构造命中结果并执行放置 Vec3d hitPos = Vec3d.ofCenter(basePos).add(Vec3d.of(dir.getVector()).multiply(0.5)); BlockHitResult hitResult = new BlockHitResult(hitPos, dir.getOpposite(), basePos, false); mc.interactionManager.interactBlock(mc.player, hand, hitResult); return; // 每tick只放置一个,避免触发反作弊 } } } } } }
(3) build.gradle 依赖检查
确保依赖版本与你的Minecraft/Meteor Client版本匹配:
plugins { id 'java' id 'com.github.johnrengelman.shadow' version '7.1.2' } repositories { mavenCentral() maven { url 'https://maven.meteordev.org/releases' } maven { url 'https://maven.fabricmc.net/' } } dependencies { implementation 'meteordevelopment:meteor-client:0.5.4' // 替换为当前最新版本 implementation 'net.fabricmc:fabric-loader:0.14.21' implementation 'net.minecraft:minecraft:1.20.1' // 替换为你的MC版本 } shadowJar { archiveBaseName.set('SignPlacerAddon') archiveVersion.set('1.0.0') archiveClassifier.set('') relocate 'com.yourname.signplacer', 'meteordevelopment.addons.signplacer' // 替换为你的包名 } tasks.withType(JavaCompile) { options.encoding = 'UTF-8' options.release = 17 // MC 1.18+ 需Java 17 }
(4) 项目目录结构验证
SignPlacerAddon/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── yourname/ │ │ └── signplacer/ │ │ ├── SignPlacerAddon.java │ │ └── ModuleSignPlacer.java │ └── resources/ │ └── fabric.mod.json └── build.gradle
二、后续功能扩展:画框与地图放置
1. 功能设计思路
- 新增枚举类型区分放置目标:告示牌、画框、带地图的画框
- 复用现有位置遍历逻辑,针对不同物品调整检测与放置逻辑
- 适配2B2T下界环境,保留延迟控制避免反作弊触发
2. 核心扩展代码
(1) 添加类型选择设置
在ModuleSignPlacer.java中新增枚举与设置项:
import meteordevelopment.meteorclient.settings.EnumSetting; public enum PlaceTarget { Sign, PaintingFrame, MapFrame } // 在sgGeneral设置组中添加 private final EnumSetting<PlaceTarget> placeTarget = sgGeneral.add(new EnumSetting.Builder<PlaceTarget>() .name("place-target") .description("选择要放置的对象类型") .defaultValue(PlaceTarget.Sign) .build() );
(2) 修改物品检测与放置逻辑
替换原有onTick方法中的物品检查与放置代码:
// 手持物品检查 Hand hand = Hand.MAIN_HAND; boolean hasValidItem = false; switch (placeTarget.get()) { case Sign: hasValidItem = mc.player.getMainHandStack().isOf(Items.OAK_SIGN) || mc.player.getOffHandStack().isOf(Items.OAK_SIGN); hand = mc.player.getMainHandStack().isOf(Items.OAK_SIGN) ? Hand.MAIN_HAND : Hand.OFF_HAND; break; case PaintingFrame: hasValidItem = mc.player.getMainHandStack().isOf(Items.PAINTING) || mc.player.getOffHandStack().isOf(Items.PAINTING); hand = mc.player.getMainHandStack().isOf(Items.PAINTING) ? Hand.MAIN_HAND : Hand.OFF_HAND; break; case MapFrame: hasValidItem = mc.player.getMainHandStack().isOf(Items.FILLED_MAP) || mc.player.getOffHandStack().isOf(Items.FILLED_MAP); hand = mc.player.getMainHandStack().isOf(Items.FILLED_MAP) ? Hand.MAIN_HAND : Hand.OFF_HAND; break; } if (!hasValidItem) return; // 放置逻辑分支 switch (placeTarget.get()) { case Sign: case PaintingFrame: // 复用原有告示牌的放置遍历逻辑 BlockPos playerPos = mc.player.getBlockPos(); int maxRange = range.get(); for (int x = -maxRange; x <= maxRange; x++) { for (int y = -maxRange; y <= maxRange; y++) { for (int z = -maxRange; z <= maxRange; z++) { BlockPos basePos = playerPos.add(x, y, z); if (!mc.world.getBlockState(basePos).isSolid()) continue; for (Direction dir : Direction.values()) { BlockPos placePos = basePos.offset(dir); if (!mc.world.getBlockState(placePos).isAir()) continue; Vec3d hitPos = Vec3d.ofCenter(basePos).add(Vec3d.of(dir.getVector()).multiply(0.5)); BlockHitResult hitResult = new BlockHitResult(hitPos, dir.getOpposite(), basePos, false); mc.interactionManager.interactBlock(mc.player, hand, hitResult); return; } } } } break; case MapFrame: // 寻找周围已放置的画框并放置地图 BlockPos playerPosMap = mc.player.getBlockPos(); int maxRangeMap = range.get(); for (int x = -maxRangeMap; x <= maxRangeMap; x++) { for (int y = -maxRangeMap; y <= maxRangeMap; y++) { for (int z = -maxRangeMap; z <= maxRangeMap; z++) { BlockPos framePos = playerPosMap.add(x, y, z); if (!mc.world.getBlockState(framePos).isOf(Blocks.PAINTING)) continue; Vec3d hitPos = Vec3d.ofCenter(framePos); BlockHitResult hitResult = new BlockHitResult(hitPos, Direction.UP, framePos, false); mc.interactionManager.interactBlock(mc.player, hand, hitResult); return; } } } break; }
内容的提问来源于stack exchange,提问作者Andrex Youtube
相关产品推荐
相关产品推荐

