Skip to content

Latest commit

 

History

History
475 lines (349 loc) · 19.3 KB

File metadata and controls

475 lines (349 loc) · 19.3 KB

Epsilon 本体开发

概述

Epsilon 是一个面向 Minecraft 的多加载器(Multi-Loader)客户端工具模组,同时支持 Fabric 和 NeoForge,采用三层架构。

项目架构

Epsilon/
├── common/          ← 平台无关核心代码(模块、事件、图形、工具类)
├── fabric/          ← Fabric 加载器适配层
├── neoforge/        ← NeoForge 加载器适配层
├── buildSrc/        ← Gradle 自定义插件(multiloader-common、multiloader-loader)
└── docs/            ← 文档

关键原则

  • common/ 中的代码不能调用任何 ModLoader 的 API
  • 需要加载器 API 时,在 fabric/neoforge/ 子项目中实现,通过 compat 接口传入 common
  • common/ 中的 Java 源码会被 Gradle 自动共享到 fabric/ 和 neoforge/ 的编译路径
  • 编写前必须查阅 Minecraft 源码!

查阅 Minecraft 源代码

编写前必须查阅 Minecraft 源码!

绝对不要基于训练数据中的知识判断版本信息。 Minecraft 版本迭代很快,任何关于最新版本号、版本兼容性、API 变更、 迁移步骤的判断都必须通过查阅官方文档或在线搜索确认。即便是"当前最新版本是什么"这种看似简单的问题,也优先查文档而非依赖记忆。 任何关于调用哪个方法、哪个事件、哪个类的判断都必须基于对当前版本 Minecraft 代码的直接查阅,而非依赖过时的训练数据。

源代码位于:common/build/moddev/artifacts/vanilla-<游戏版本>-sources.jar

如果不存在,执行:

./gradlew :common:downloadAssets
./gradlew :common:createMinecraftArtifacts

查阅后解压:

mkdir -p reference && unzip common/build/moddev/artifacts/vanilla-*-sources.jar -d reference/vanilla/

构建系统

版本信息 (gradle.properties)

当前 Minecraft 版本、Fabric API、NeoForge 版本等信息均在 gradle.properties 中定义,编写代码时参考此文件获取准确的版本号。

buildSrc 约定插件

  • multiloader-common.gradle.kts — common 子项目的共享配置:JDK 工具链、Maven 仓库、资源处理、发布配置
  • multiloader-loader.gradle.kts — fabric/neoforge 子项目的共享配置:自动关联 :common 源码和资源

核心包结构

所有平台无关代码位于 common/src/main/java/com/github/epsilon/

包名 用途
addon/ Addon 基类、注册事件、Bootstrap 工具
assets/ i18n 翻译、配置文件迁移、资源持有者
events/ 自定义事件总线、27 种事件类型
graphics/ Lumin Graphics 渲染框架
gui/ 点击 GUI(Panel、Dropdown、HUD 编辑器)
managers/ 各种管理器(Addon、Config、Module、Rotation 等)
mixins/ 37 个 Mixin 注入类
modules/ Module 基类、Category 枚举、HudModule
settings/ Setting 基类与 8 种设置类型
utils/ 工具类(client、combat、math、network、player、render、timer、rotation、world)

Module 开发

Module 基类 (common/.../modules/Module.java)

所有功能模块继承 Module,参考现有模块(如 common/.../modules/impl/combat/KillAura.java)。

public class MyModule extends Module {

    public static final MyModule INSTANCE = new MyModule();

    private MyModule() {
        super("My Module", Category.COMBAT); // 名称 + 分类
    }

    // -- Setting DSL --

    // Boolean 设置
    private final BoolSetting enabled = boolSetting("Enabled", true);

    // Boolean 设置 + 可见性依赖
    private final BoolSetting advanced = boolSetting("Advanced", false, () -> enabled.getValue());

    // Integer 设置 (name, default, min, max, step)
    private final IntSetting speed = intSetting("Speed", 5, 1, 10, 1);

    // Integer 设置 + 依赖
    private final IntSetting range = intSetting("Range", 4, 1, 64, 1, () -> advanced.getValue());

    // Double 设置 (name, default, min, max, step)
    private final DoubleSetting reach = doubleSetting("Reach", 3.0, 1.0, 6.0, 0.1);

    // Enum 设置
    private enum Mode { Fast, Slow }
    private final EnumSetting<Mode> mode = enumSetting("Mode", Mode.Fast);

    // Enum 设置 + 依赖 + onChanged 回调
    private final EnumSetting<Mode> mode2 = enumSetting("Mode 2", Mode.Slow, () -> enabled.getValue(), newMode -> { /* ... */ });

    // Color 设置 (name, default, allowAlpha: boolean)
    private final ColorSetting color = colorSetting("Color", new Color(255, 0, 0), true);

    // Keybind 设置 (name, defaultKeyCode: int)
    private final KeybindSetting key = keybindSetting("Key", -1);

    // Button 设置 (name, action: Runnable)
    private final ButtonSetting reset = buttonSetting("Reset", () -> { /* ... */ });

    // String 设置
    private final StringSetting text = stringSetting("Text", "default");

    // -- 生命周期 --
    @Override
    protected void onEnable() {
        // 模块启用时调用
    }

    @Override
    protected void onDisable() {
        // 模块禁用时调用
    }

    // -- 事件监听 --
    @EventHandler
    private void onTick(TickEvent.Pre event) {
        if (nullCheck()) return; // 检查 mc.player == null || mc.level == null
        // 处理每 tick 逻辑
    }

    @EventHandler
    private void onRender3D(Render3DEvent event) {
        // 3D 渲染逻辑
        PoseStack stack = event.getPoseStack();
    }
}

Module 开发约定

  1. 单例模式:每个 Module 使用 public static final MyModule INSTANCE = new MyModule(),构造函数私有
  2. 私有构造函数:防止外部实例化
  3. nullCheck():事件处理方法中先调用 nullCheck() 确保世界中存在玩家
  4. Setting 字段:Setting 必须声明为类的实例字段,构造时初始化
  5. @EventHandler 注解:事件监听方法必须用 @EventHandler 标记,且必须是实例方法(非 static),访问修饰符任意
  6. 事件方法签名:必须 void 返回,只有一个参数(事件类型),参数类型不能是原始类型
  7. 注册:内部模块在 ModuleManager.initModules() 列表中注册;Addon 模块通过 EpsilonAddon.registerModule() 注册
  8. i18n 翻译:需要为模块名称和设置名称添加 i18n 翻译,详见后文 i18n 章节

Module.Setting 可见性依赖(Dependency)

Setting 的第四个参数(如果有)是一个 Setting.Dependency 函数式接口(() -> boolean),返回 false 时该设置在 GUI 中隐藏。

private final BoolSetting enableEsp = boolSetting("Enable ESP", true);
private final ColorSetting espColor = colorSetting("ESP Color", Color.WHITE, () -> enableEsp.getValue());
// 使用已有的 EnumSetting/BoolSetting::getValue 引用作为依赖

也可用 lambda 实现 cross-setting 引用(A 依赖 B,而 B 也在同一个类中):

private final BoolSetting advanced = boolSetting("Advanced", false);
// 使用 lambda 延迟引用,解决声明顺序问题
private final IntSetting threshold = intSetting("Threshold", 50, 0, 100, 1, () -> advanced.getValue());

注意:EpsilonAddon 的 Setting 不支持带 onChanged 参数的 DSL 变体(与 Module 不同)。

Setting 分组

private final SettingGroup sgCombat = settingGroup("Combat");
private final BoolSetting autoAttack = boolSetting("Auto Attack", true).group(sgCombat);

模块键位

  • getKeyBind() / setKeyBind(int) — 键位代码(GLFW key code)
  • BindMode.Toggle — 按下切换开关(默认)
  • BindMode.Hold — 按住启用,松开禁用

事件系统

EventBus (common/.../events/bus/EventBus.java)

Epsilon 使用自定义事件总线,与 Minecraft 原生事件系统独立。

  • 订阅EventBus.INSTANCE.subscribe(object) / EventBus.INSTANCE.subscribe(Class)
  • 取消订阅EventBus.INSTANCE.unsubscribe(object) / EventBus.INSTANCE.unsubscribe(Class)
  • 发布EventBus.INSTANCE.post(event)
  • Lambda 工厂注册EventBus.INSTANCE.registerLambdaFactory("com.github.epsilon", factory) — 已由 EpsilonCommon.init() 自动注册

Module 启用时自动 subscribe(this),禁用时自动 unsubscribe(this)

可用事件类型(common/.../events/impl/

事件类 描述
TickEvent.Pre / TickEvent.Post 每 tick 前后
Render2DEvent.Level / Render2DEvent.HUD 2D 渲染(含 GuiGraphics)
Render3DEvent 3D 渲染(含 PoseStack)
PacketEvent.Send / PacketEvent.Receive 网络包收发
KeyPressEvent 按键按下/释放
MousePressEvent 鼠标按键
AttackEntityEvent 攻击实体
ClickEvent 点击
JumpEvent 跳跃
CollisionEvent 碰撞检测
SlowdownEvent 减速(灵魂沙等)
StrafeEvent 侧移
TravelEvent 移动
VelocityEvent 击退
RotationAnimationEvent 旋转动画
AfterRotationEvent 旋转完成标记事件(通过 RotationManager.getYRot()/getXRot() 获取角度)
AttackYawEvent 攻击偏航角
SendPositionEvent 发送位置包
SwingHandEvent 挥手
RaytraceEvent 射线追踪
KeyboardInputEvent 键盘输入
... 等 27 个事件

事件优先级

@EventHandler(priority = EventPriority.HIGH) — 优先级:HIGHEST > HIGH > MEDIUM > LOW > LOWEST

支持直接传入 int 值(如 priority = -1000)。数值越大越先执行,数值越小越晚执行。EventPriority 常量映射:HIGHEST=200, HIGH=100, MEDIUM=0, LOW=-100, LOWEST=-200

可取消事件

实现 Cancellable 接口的事件可以通过 event.setCancelled(true) 取消。取消后,后续低优先级监听器不再执行。

Mixin 开发

Mixin 类位于 common/src/main/java/com/github/epsilon/mixins/

Mixin 配置

  • common/src/main/resources/epsilon.mixins.json — common 层 mixins
  • fabric/src/main/resources/epsilon.fabric.mixins.json — Fabric 专有 mixins
  • neoforge/src/main/resources/epsilon.neoforge.mixins.json — NeoForge 专有 mixins

Access Widener / Access Transformer

  • common/src/main/resources/epsilon.accesswidener — Fabric 访问拓宽
  • common/src/main/resources/META-INF/accesstransformer.cfg — NeoForge 访问转换

Mixin 编写规则

  1. 不要凭空猜测 API。优先查阅官方文档:
  2. Mixin 类命名:Mixin<目标类名>(如 MixinLocalPlayer
  3. 优先使用 @Inject + @WrapOperation 而非 @Overwrite
  4. 事件发布通常在 Mixin 的 @Inject 回调中进行:EventBus.INSTANCE.post(new SomeEvent(...))
  5. 如果遇到不熟悉的类/方法签名,必须查阅文档确认

管理器(Managers)

ModuleManager (common/.../managers/ModuleManager.java)

  • initModules() — 注册所有内置模块(在 EpsilonCommon.init() 中调用)
  • registerAddonModule(addonId, module, translateComponent) — 注册 Addon 的模块
  • getModules() — 获取所有已注册模块列表

AddonManager (common/.../managers/AddonManager.java)

  • registerAddon(addon) — 注册单个 addon(含重复 ID 校验)
  • registerAddons(Iterable) — 批量注册
  • setupAddons() — 依次初始化所有 addon 的 i18n 并调用 onSetup()(含异常隔离)
  • getAddons() — 获取已注册 addon 列表

其他管理器

管理器 职责
ConfigManager 配置序列化/反序列化、自动保存
RotationManager 旋转角度管理(优先级队列)
HealthManager 实体生命值缓存
TargetManager 目标选择(FOV、距离、实体类型过滤)
FriendManager 好友管理
SoundManager 音效播放
ServerboundPacketManager / ClientboundPacketManager 网络包管理

RotationManager (common/.../managers/RotationManager.java)

RotationManager 管理玩家视角旋转,支持优先级队列、平滑插值和射线追踪偏移。

核心概念

  • setRotations() 设置目标旋转角度后,RotationManager 每 tick 自动平滑旋转,并在 SendPositionEvent 中将旋转角度写入发包
  • 旋转完成后(与真实角度差值 < 1 度)自动 active = false
  • 多个模块可同时调用 setRotations(),高优先级覆盖低优先级
  • 旋转过程中每 tick 发布 AfterRotationEvent,可在其中做 raycast 检测确认旋转已对准目标后再执行操作

主要 API

// 获取当前旋转角度
float yaw = RotationManager.INSTANCE.getYRot();   // 水平角度
float pitch = RotationManager.INSTANCE.getXRot(); // 垂直角度
Vector2f current = RotationManager.INSTANCE.getRotation(); // Vector2f(yaw, pitch)

// 设置旋转(默认优先级 Medium)
RotationManager.INSTANCE.setRotations(new Vector2f(yaw, pitch), rotationSpeed);

// 设置旋转 + 优先级
RotationManager.INSTANCE.setRotations(new Vector2f(yaw, pitch), rotationSpeed, Priority.High);

// 设置旋转 + 射线追踪偏移(用于绕过反作弊)
RotationManager.INSTANCE.setRotations(rotations, rotationSpeed, rayTraceFunction, Priority.High);

// 设置旋转 + 回调(旋转每 tick 平滑后执行,用于 raycast 检测 + 实际操作)
RotationManager.INSTANCE.setRotations(rotations, rotationSpeed, null, Priority.High, () -> {
    if (RaytraceUtils.overBlock(new Vector2f(getYRot(), getXRot()), side, blockPos, true)) {
        // 执行放置/攻击操作
    }
});

// 检查旋转是否激活 / 已完成
boolean active = RotationManager.INSTANCE.isActive();
boolean done = RotationManager.INSTANCE.isDone();

优先级(Priority)

优先级 用途
Lowest -200 预旋转(Pre Rotation)
Low -100 低优先级旋转
Medium 0 默认优先级
High 100 攻击/放置等即时操作
Highest 200 最高优先级

回调使用模式

推荐通过 setRotations 的 callback 参数实现「旋转→瞄准→操作」的解耦流程,无需手动订阅 AfterRotationEvent

// 设置旋转 + callback,在旋转对准后执行操作
RotationManager.INSTANCE.setRotations(rotation, speed, null, Priority.High, () -> {
    // 使用 getYRot()/getXRot() 获取当前平滑后的旋转角度
    if (RaytraceUtils.overBlock(
            new Vector2f(RotationManager.INSTANCE.getYRot(), RotationManager.INSTANCE.getXRot()),
            side, blockPos, true)) {
        mc.gameMode.useItemOn(mc.player, hand, hitResult);
    }
});

关键设计要点

  • callback 在 RotationManager.onTick() (priority=-1000) 中每 tick 执行一次,直到被新的 setRotations() 覆盖或旋转完成(active=false
  • callback 内使用 RotationManager.INSTANCE.getYRot() / getXRot() 获取当前平滑后的旋转角度,而非使用 AfterRotationEvent 的字段(已移除)
  • callback 应防止重复执行(通过 boolean 标记),避免同一操作触发多次
  • AfterRotationEvent 现在是纯标记事件(无字段),仅供需要知道旋转已更新的模块订阅
  • raycast 函数用于在平滑旋转时加入随机偏移绕过反作弊,如果偏移后射线被遮挡则重新计算偏移方向
  • 模块禁用时应清理 pending 状态并 InvUtils.swapBack() 恢复物品栏

Lumin Graphics 渲染系统

Lumin Graphics 是 Epsilon 的轻量高性能渲染框架,位于 common/.../graphics/

核心 Renderer

Renderer 用途
RectRenderer 矩形渲染(纯色/渐变)
RoundRectRenderer SDF 圆角矩形(Shader 实现,支持独立四角半径)
RoundRectOutlineRenderer 圆角矩形边框
TextureRenderer 纹理批量渲染
ShadowRenderer 阴影渲染
TriangleRenderer 三角形渲染
TextRenderer 文本渲染(静态字体 + TTF)
TtfTextRenderer TTF TrueType 字体渲染(Atlas 批处理)

Renderer 生命周期

  1. 线程安全:Renderer 必须在渲染线程初始化
  2. 推荐初始化方式:使用 Suppliers.memoize 延迟初始化
private final Supplier<RectRenderer> rectRenderer = Suppliers.memoize(RectRenderer::create);
  1. 使用模式一:即时绘制并清理
rectRenderer.get().addRect(10, 10, 100, 100, Color.WHITE);
rectRenderer.get().drawAndClear(); // 等价于 draw() + clear()
  1. 使用模式二:缓冲区复用(内容不变时更高效)
// 初始化时添加顶点一次
rectRenderer.get().addRect(10, 10, 100, 100, Color.WHITE);
// 渲染循环中每帧只 draw 不清除
rectRenderer.get().draw(); // GPU 数据已存在,无需重新上传
  1. 关闭:关闭时释放 GPU 资源
rectRenderer.get().close();

重要约束

同一帧内,不要在 draw() 之后再 clear() 然后继续 draw() — 这会导致帧内多次缓冲区分配,破坏 In-Flight 优化。如需多次清空-绘制循环,请创建新的 Renderer 实例。

文本渲染

静态字体加载器:StaticFontLoader.OSAKA_CHIPSStaticFontLoader.MINECRAFTIA 等。

TextRenderer textRenderer = textRendererSupplier.get();
textRenderer.addText("Hello", x, y, scale, color, StaticFontLoader.OSAKA_CHIPS);
float width = textRenderer.getWidth("Hello", scale);
float height = textRenderer.getHeight(scale);
textRenderer.drawAndClear();

特效

  • BlurShader.INSTANCE.render(x, y, width, height, radius, strength) — 背景模糊
  • FXAAShader — FXAA 抗锯齿
  • FilterShader — 色彩滤镜

参考示例

完整 HUD 渲染示例:common/.../modules/impl/hud/WatermarkHUD.java

i18n 翻译系统

  • TranslateComponent — 翻译组件接口
  • EpsilonTranslateComponent.create("modules", "kill_aura") → key: epsilon.modules.kill_aura
  • DefaultTranslateComponent.create("example_addon.settings.enable_particles") → key: example_addon.settings.enable_particles
  • 翻译 key 生成在 I18NFileGenerator.generate("epsilon-empty-i18n.json") 输出空模板

Module 翻译 key 约定

  • 内置模块:epsilon.modules.{moduleNameLowerCase}(如 epsilon.modules.kill_aura
  • Addon 模块:{addonId}.modules.{moduleNameLowerCase}(如 example_addon.modules.my_module
  • Module.getTranslatedName() — 获取翻译后的显示名称
  • Setting 翻译:{addonId}.settings.{settingNameLowerCase}
  • SettingGroup 翻译:{addonId}.settings.{groupNameLowerCase}

代码规范

  1. 使用 Java 编写,遵循项目现有代码风格
  2. 私有构造函数 + 单例 INSTANCE 模式用于 Module 和 Addon
  3. 所有注释用中文
  4. Logger 通过 Constants.LOGGER 获取
  5. Minecraft 实例通过 Constants.mc 或 Module 中的 this.mc 获取
  6. 编译环境使用 JDK 25

查阅文档资源