矿石与刷怪笼高亮 ESP
基本信息
| 属性 | 值 |
|---|---|
| 所属维度 | 纯客户端渲染功能,专用服务器不加载 |
| 实现类 | com.myname.mymodid.client.ESPRenderer |
| 事件接线 | com.myname.mymodid.client.ClientEvents(ClientEvents.java:12-22) |
| 注册位置 | MinecraftForge.EVENT_BUS.register(new ClientEvents())(ClientProxy.java:32) |
| 触发事件 | RenderWorldLastEvent(ClientEvents.java:17) |
| 渲染形式 | 红色线框立方体(GL_LINES) |
| 关闭方式 | 无(见「已知问题」) |
功能
在每个世界渲染帧的最后(区块渲染完成之后),扫描玩家周围的方块,把命中目标的方块以红色线框描边,从而透视被地形遮挡的矿石与刷怪笼。
代码注释明确说明了为何选 RenderWorldLastEvent 而非 FML 总线:后者只承载 FML 生命周期事件,而该事件在区块渲染结束后触发,因而不会破坏 Embeddium / Celeritas 区块渲染器共享的 OpenGL 状态(ClientEvents.java:1-4)。
该功能仅在客户端注册:ClientProxy 经 @SidedProxy 的 clientSide 挂载(MyMod.java:19),ClientEvents 只在 ClientProxy.init 中挂到 Forge 事件总线(ClientProxy.java:32),服务端走 CommonProxy,其 init 是空方法(CommonProxy.java:20)。
目标方块判定
isTargetBlock(ESPRenderer.java:39-45)先排除 null 与 Blocks.air,再分两路:
- 自定义集合优先:若
ClientProxy.espTargetBlocks非空,则只高亮集合内的方块(ESPRenderer.java:42-43)。 - 默认判定
isDefaultTarget(ESPRenderer.java:47-63),满足任一条件即命中:- 该方块的任意物品字典名以
ore或crushedOre开头(前缀数组ORE_PREFIXES,ESPRenderer.java:31;遍历逻辑ESPRenderer.java:49-56) - 方块非本地化名转小写后包含
spawner或mob_spawner(ESPRenderer.java:57-61)
- 该方块的任意物品字典名以
扫描与缓存
rescanIfNeeded(ESPRenderer.java:65-91)以玩家坐标向下取整得到基准点(ESPRenderer.java:66-68),按 Config.espRange 做球形扫描(ESPRenderer.java:76-80),把命中方块按打包坐标存入静态缓存 SCAN_CACHE(ESPRenderer.java:36, 84)。扫描完成后向 stdout 打印一行 [ESP] scan complete: cached N blocks around (x,y,z) range=R(ESPRenderer.java:89-90)。
缓存仅在玩家跨入新整数格或配置范围变化时失效(ESPRenderer.java:71-73),命中结果与缓存坐标同时更新(ESPRenderer.java:88)。
绘制
render(float frame)(ESPRenderer.java:93-174):
- 开头检查
ClientProxy.espEnabled(ESPRenderer.java:94),再经FMLClientHandler取客户端玩家与世界,任一为null即返回(ESPRenderer.java:98-101) - 缓存为空则直接返回(
ESPRenderer.java:104) - 先备份
Tessellator.instance.drawMode,绘制结束后在finally中还原,避免影响区块渲染器(ESPRenderer.java:108, 172) - 用
lastTickPos + (pos - lastTickPos) * frame做帧间插值(ESPRenderer.java:110-115),frame即partialTicks,通过ObfuscationReflectionHelper反射取得(ClientEvents.java:19-20) glPushAttrib保存 GL 状态(ESPRenderer.java:121-123),finally中glPopMatrix+glPopAttrib还原(ESPRenderer.java:169-171)- 逐个方块画 12 条边(底面 4 条、顶面 4 条、竖直 4 条,
ESPRenderer.java:146-160),颜色为 RGBA(1.0, 0.2, 0.2, 0.8)(ESPRenderer.java:143),线宽2.0(ESPRenderer.java:134) - 绘制循环受
FRAME_BUDGET限制(ESPRenderer.java:32, 140),超出即break - 整段绘制包在
try/catch(Throwable)中,异常打印到 stderr 且不影响游戏继续(ESPRenderer.java:165-167)
数值
| 数值名 | 值 | 来源 |
|---|---|---|
| 线框颜色 RGBA | 1.0, 0.2, 0.2, 0.8 | ESPRenderer.java:143 |
| 线宽 | 2.0F | ESPRenderer.java:134 |
| 每帧绘制上限 | 500 个方块 | ESPRenderer.java:32 |
| 物品字典前缀 | ore、crushedOre |
ESPRenderer.java:31 |
| 刷怪笼匹配子串 | spawner、mob_spawner |
ESPRenderer.java:60 |
| 默认检测半径 | 16(1–256 可配) | Config.java:15, 33-34 |
| 混合模式 | GL_SRC_ALPHA / GL_ONE_MINUS_SRC_ALPHA |
ESPRenderer.java:132 |
| 深度测试 | GL_LEQUAL |
ESPRenderer.java:129 |
交互
| 触发 | 行为 |
|---|---|
| 每帧世界渲染结束 | 执行一次 render(partialTicks) |
| 玩家跨入新整数格 | 触发球形重扫并刷新缓存 |
| 玩家静止不动 | 复用缓存,不再重扫 |
| 按 L 键 | 无反应 —— 仓库内无任何快捷键注册代码 |
本功能无 GUI、无指令、无方块/物品形态,也不改变世界数据;它只读取方块状态并画线。
实现依赖
ESPRenderer 的字段头注释(ESPRenderer.java:1-8)说明:该文件直接使用 MCP 命名的字段与方法(如 Tessellator.instance、Entity.posX、Blocks.air、World.getBlock),既不经 ObfuscationReflectionHelper 也不缓存 Method.invoke,而是依赖 MCP 反混淆转换器 在类加载时把 MCP 引用改写为运行时 Searge 名——与 NEI 通过 CodeChickenCore 的同名转换器所用的做法一致。
与之配套,仓库提供了 Access Transformer 声明 META-INF/mymodid_at.cfg(由 gradle.properties:98 的 accessTransformersFile 指向),把 Entity 的 6 个坐标字段、Tessellator.field_78409_u(drawMode)、Blocks.field_150350_a(air)开放为 public 字段,并把 World.func_147439_a(III) 开放为 public 方法。
已知问题
- 无法关闭。
ClientProxy.espEnabled硬编码为true(ClientProxy.java:15)且全仓无任何赋值语句;配置项espEnabledByDefault只写不读;源码中不存在按键注册或按键轮询代码。详见 ESP 默认开关。 - 自定义目标集合是死代码。
ClientProxy.espTargetBlocks声明为空的HashSet(ClientProxy.java:16),全仓检索该字段只有 2 处命中——声明处与读取处(ESPRenderer.java:42)——没有任何add操作。因此espTargetBlocks恒为空集,ESPRenderer.java:43的targets.contains(block)分支在运行时永远不可达,所有目标都走默认判定。 - 每帧绘制上限与扫描范围不联动。 半径 256 时可缓存数亿方块,但每帧只画前 500 个,且遍历的是
HashMap.entrySet(),输出顺序不确定。扫描结果越多,被实际画出的反而越是随机子集。
相关条目
- ESP 检测范围 - 控制扫描半径,1–256
- ESP 默认开关 - 声明存在但未接线的开关
- MCP 反混淆转换器 - 本功能赖以运行的前提