Skip to content

feat: 新增饱和度显示、自定义迷雾、AutoText 快捷消息与 Windows SMTC 集成 - #173

Merged
gaoyu06 merged 23 commits into
FPSMasterTeam:mainfrom
Serendisand:main
Aug 2, 2026
Merged

feat: 新增饱和度显示、自定义迷雾、AutoText 快捷消息与 Windows SMTC 集成#173
gaoyu06 merged 23 commits into
FPSMasterTeam:mainfrom
Serendisand:main

Conversation

@Serendisand

Copy link
Copy Markdown
Contributor

本次 PR 一次性引入四个新功能,并为现有 BlockOverlay 修复了一个 NPE:

  1. 饱和度显示(Saturation HUD) — 用可自由摆放的饱和度 HUD 替代原版饥饿条
  2. 自定义迷雾(Custom Fog) — 自定义世界迷雾的颜色与距离
  3. AutoText 快捷消息 — 按快捷键自动发送预配置的聊天消息
  4. Windows SMTC — 让客户端的音乐功能接入 Windows 系统媒体传输控件
  5. Bug 修复 — BlockOverlay 瞄准实体时的空指针崩溃

详细变更

饱和度显示(SaturationDisplay)

  • 新增 InterfaceModule + SaturationDisplayComponent,默认定位在原版饥饿栏位置,支持 HUD
    编辑器拖动/缩放/换背景
  • 绘制原版风格的食物图标并按真实饥饿值填充,旁边附精确饱和度数值
  • 开启模块时通过 MixinGuiIngameForge 取消原版 renderFood,关闭即恢复原版

自定义迷雾(CustomFog,Render 分类)

  • 线性 / 指数两种雾模式,可调雾色、起始距离、结束距离
  • 在 MixinEntityRenderer.setupFog 处覆盖 GL 雾状态,默认不干预水下/岩浆迷雾

AutoText(AutoText,实用分类)

  • 默认 G → gg,最多 20 条,禁止重复快捷键
  • ClickGUI 编辑器:每行「快捷键绑定 + 消息输入框 + 删除 ×」,底部居中「+」新增;空列表时显示占位提示
  • 新增 AutoTextSetting(ArrayList)与完整的配置序列化(autoText
    类型),加载时去重、丢弃畸形条目
  • Setting.copyValue 对 AutoText 条目做深拷贝,reset 不泄漏编辑

Windows SMTC(音乐功能接入系统媒体控件)

  • Java 侧新增 modules/music/smtc/ 门面与工厂:非 Windows 自动降级 no-op,不影响音乐功能
  • WindowsSystemMediaTransportControls 通过 JNA 加载随包发布的原生
    DLL(native/smtc/smtc.cpp,C++/WinRT),实现完整 SMTC 会话
  • 发布标题 / 歌手 / 封面(PNG)/ 播放进度,并暴露 播放/暂停、上一首、下一首 控制
  • SmtcMusicBridge 后台轮询曲目状态并发布快照,封面离线下载;控制事件回主线程调度,关闭客户端时释放会话
  • build.gradle.kts 增加 JNA 依赖;原生 DLL 资源目录与构建脚本占位

修复

  • BlockOverlay:瞄准实体时 objectMouseOver.getBlockPos() 为 null 导致的 NPE(此前导致 onRender3D 事件报错)

验证情况

  • compileJava 已通过(osh-core/JNA 依赖解析正常)
  • SMTC 的 Windows DLL 需在具备 C++/WinRT 工具链的环境编译后放入
    src/main/resources/native/windows/{x64,x86}/,未放置时自动降级为 no-op
  • 建议在游戏内验证饱和度 HUD 与原版饥饿栏的对齐效果

新增 SaturationDisplay InterfaceModule + SaturationDisplayComponent:
- 默认定位在原版饥饿栏处,可拖动/缩放/换背景
- 绘制原版风格食物图标并按实际饥饿值填充,附带饱和度数值
- 开启模块时通过 MixinGuiIngameForge 取消原版 renderFood,关闭即恢复原版
- CustomFog Render 模块:线性/指数两种模式,颜色、起始距离、结束距离可调
- 在 MixinEntityRenderer.setupFog RETURN 处覆盖雾色与 GL 雾参数
- 默认不影响水下/岩浆迷雾,开启后仅在世界迷雾生效
- AutoText Utility 模块:默认 G→gg,最多 20 条,禁止重复快捷键
- AutoTextSetting(ArrayList<AutoTextEntry>)+ AutoTextEntry 不可变对象
- ClickGUI 编辑器:每行 快捷键绑定 + 消息输入框 + 删除×,底部居中 + 新增
- ConfigManager 增加 autoText 类型的序列化/反序列化,去重并丢弃畸形条目
- Setting.copyValue 对 AutoTextEntry 列表做深拷贝,reset 不会泄漏编辑
- Java 侧:SystemMediaTransportControls 门面 + 工厂(非 Windows 自动降级 no-op)
- WindowsSystemMediaTransportControls:JNA 加载 fpsmaster-smtc.dll(native/smtc),
  C++/WinRT 实现 SMTC 会话,发布标题/歌手/封面/进度,暴露 播放/暂停/上一首/下一首
- SmtcMusicBridge:轮询当前曲目状态并发布快照,封面后台下载为 PNG
- MusicManager 构造时创建桥接,shutdown 时释放;控制事件回主线程调度
- build.gradle.kts 增加 JNA 依赖;原生 DLL 资源目录与构建脚本占位
同时为饱和度显示/自定义迷雾/AutoText 新增中英文翻译
@Serendisand
Serendisand marked this pull request as draft August 1, 2026 15:09

@gaoyu06 gaoyu06 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

过了一遍。主要问题是两个编译错误:CustomFog 用了 1.8.9 没有的 getEye() / GlStateManager.FogMode,SaturationDisplayComponent 往 int 形参传了 float。本地 ./gradlew build 应该是过不去的,PR 里写 compileJava 已通过,是不是在改动前的分支上编的?

另外几个值得改的:AutoText 有两处会意外往公屏发消息(默认键位写错成 Z + 新增条目自动绑到快捷栏 1 键);CustomFog 的颜色设置目前是无效的,而且会盖掉失明药水的雾;SMTC 那边 GetForCurrentView() 在 Win32 JVM 里必失败,功能整个静默不工作。细节写在行内了。


// Check if in water or lava — only override if the user opted in
if (!CustomFog.affectWater.getValue()) {
net.minecraft.block.material.Material eye = mc.thePlayer.getEye().getMaterial();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.8.9 没有 getEye(),Entity 上只有 getEyeHeight() / getPositionEyes(float) / isInsideOfMaterial(Material)。下面的 GlStateManager.FogMode 也是 1.9 才加的,1.8.9 只有 setFog(int)。312/316/324/328 四行都是 cannot find symbol。

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我再查一下wiki

}

java.awt.Color fogColor = CustomFog.color.getColor();
GlStateManager.color(fogColor.getRed() / 255f, fogColor.getGreen() / 255f, fogColor.getBlue() / 255f);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GlStateManager.color() 改的是顶点色(glColor4f),不是雾色。雾色只能用 glFog(GL_FOG_COLOR, buffer) 设,所以 Color 这个设置现在完全没效果,雾还是原版天空/群系色。

而且这行留下的颜色会带到后面的 renderSky、云、实体渲染,依赖当前颜色的几何体会被染上雾色,三参重载还把 alpha 强制成 1.0。

}

@Inject(method = "setupFog", at = @At("RETURN"))
private void overrideFog(int startCoords, float partialTicks, CallbackInfo ci) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里没挡失明药水。原版 setupFog 对 blindness 会强制成很短的线性雾(约 5 ~ renderDistance*0.25),现在被无条件盖掉了——开着 CustomFog 把距离拉到 200,中了失明还能看得一清二楚,服务器上算优势项而不是外观改动。建议开头加个 isPotionActive(Potion.blindness) 直接 return。

水/岩浆那两个开关打开时也有类似问题:密度换了但原版水下雾色没换,会出现颜色和密度对不上的效果。

}

private void drawIcon(float x, float y, int textureX, int textureY) {
Gui.drawModalRectWithCustomSizedTexture(x, y, textureX, textureY, ICON_SIZE * scale, ICON_SIZE * scale, 256f, 256f);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.8.9 的签名是 (int x, int y, float u, float v, int width, int height, float tw, float th),x/y/width/height 都是 int,这里传的是 float,编不过。

utils/render/draw/Images.java:169 有全 float 的版本,用那个就行——顺便也能保住非整数 HUD scale 下的亚像素对齐,不然图标会抖。

public class AutoText extends Module {
private static final ArrayList<AutoTextEntry> DEFAULT = new ArrayList<>();
static {
DEFAULT.add(new AutoTextEntry(0x2C /* G key LWJGL */, "gg"));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x2CKEY_ZKEY_G0x22。现在的效果是按 G 没反应,按 Z 往公屏发 gg。

if (addClick != null) {
// Find first unused key code
int newKey = 0;
for (int k = 2; k < 256; k++) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newKey 初始化成 0 之后马上被这个循环覆盖成最小可用扫描码,只有一条已有条目时第一次点 + 就是 2,也就是 KEY_1

用户输完消息关掉 GUI 不会注意到有绑定,之后战斗里每次切到快捷栏第 1 格都往公屏发一次,很容易被服务器反刷屏禁言。建议新条目保持 keyCode 0,onKey 里跳过未绑定的。

EventDispatcher.dispatchEvent(new EventRender2D(partialTicks));
}

@Inject(method = "renderFood", at = @At("HEAD"), cancellable = true)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forge 的 renderFood 末尾有 right_height += 10,在 HEAD cancel 会把它一起跳过。右侧后面的 renderHealthMount 和 renderAir 都按 height - right_height 定位,结果氧气泡和坐骑血条整体下移 10px 压到快捷栏上。cancel 之前手动补一下 right_height 就好。

Comment thread native/smtc/smtc.cpp Outdated
winrt::init_apartment(winrt::apartment_type::single_threaded);

// Get the system transport controls
g_smtc = SystemMediaTransportControls::GetForCurrentView();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetForCurrentView() 需要 CoreWindow,MC 的 JVM 是普通 Win32 进程,这里必抛,然后被下面的 catch (...) 吃掉,g_smtc 一直是 null。

smtc_start 返回 void 不报错,Java 侧照样把 available 设成 true(WindowsSystemMediaTransportControls.java:122),后面 publish / set_buttons 各自在 if (!g_smtc) return 静默返回——功能整个不工作,日志里也看不出任何东西。桌面进程要走 ISystemMediaTransportControlsInterop::GetForWindow(hwnd),估计 smtc_start 收的那个没用上的 hwnd 本来就是给它留的。

return;
}
// Only refresh when we don't already have art for the current track
if (artwork.get() != null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

只要缓存里有东西就 return,所以封面只会取到第一首歌的。切歌时走到这里 artwork.get() 还是上一首的,下载被跳过,之后整个会话 Windows 那边都显示第一首的封面。清缓存的条件(URL 为空)跟切歌需要的正好反着。

if (artwork.get() != null) {
return;
}
final Thread t = new Thread(() -> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里自己起线程跑 ImageIO,绕开了 MusicTextures 的单线程 IMG_EXEC。那边注释写得挺明确:macOS(尤其 Rosetta)下并发调 AWT/CoreGraphics 会崩 objc_release。bridge 是不分平台轮询的,所以 mac 上 HUD 封面和这里同时解码有几率直接崩 JVM。

另外 downloadPng 和 UA 常量基本是照抄 MusicTextures 的,复用一下就行。

@Serendisand

Copy link
Copy Markdown
Contributor Author

过了一遍。主要问题是两个编译错误:CustomFog 用了 1.8.9 没有的 getEye() / GlStateManager.FogMode,SaturationDisplayComponent 往 int 形参传了 float。本地 ./gradlew build 应该是过不去的,PR 里写 compileJava 已通过,是不是在改动前的分支上编的?

另外几个值得改的:AutoText 有两处会意外往公屏发消息(默认键位写错成 Z + 新增条目自动绑到快捷栏 1 键);CustomFog 的颜色设置目前是无效的,而且会盖掉失明药水的雾;SMTC 那边 GetForCurrentView() 在 Win32 JVM 里必失败,功能整个静默不工作。细节写在行内了。

编译通过确实是在第一个提交的时候,后面并没有完全验证,为错误的报告致歉

- smtc.cpp: 切换到 GetForWindow 桌面互操作路径,smtc_start 返回状态码
- 修复 __stdcall→__cdecl 后导出符号与 JNA 绑定不匹配的问题(DLL 重编译)
- 封面图下载改走 MusicTextures 单线程 AWT/ImageIO 队列,避免 macOS 并发崩溃
- 新增 smtc.def 导出定义
- MixinEntityRenderer: 雾色改用 glFog(GL_FOG_COLOR),复位顶点色防污染后续渲染;
  失明药水跳过覆盖;水/岩浆材质检查改用 isInsideOfMaterial
- MixinGuiIngameForge: renderFood 被 cancel 时手动补 right_height += 10,修复
  血量/氧气条整体下移 10px
- MixinSplashScreen: @overwrite 补 remap = false
- SaturationDisplayComponent: 图标绘制改走 Images 全 float 版本
- 默认快捷键从错误的 0x2C(Z) 修正为 0x22(G)
- 新增条目 keyCode 保持 0(None),由用户显式绑定,避免误占键位
@Serendisand
Serendisand marked this pull request as ready for review August 2, 2026 04:25
@Serendisand

Copy link
Copy Markdown
Contributor Author

自定义迷雾的、性能里的performance.reuselevel、切换潜行的fontshadow、、还有performancehud的大量的,还有modslist的round,fontshadow,语言文件缺失了,稍后补齐一下。
另外自定义迷雾的线性模式有点范围过大了,导致近地面也出现了迷雾渲染色

Serendisand and others added 6 commits August 2, 2026 12:52
- performance.reuselevel 及其模式值(保守/均衡/激进)
- customfog.affectwater/affectlava
- performancehud 共享外观设置(背景/圆角/字体阴影等)
- modslist.round/fontshadow、togglesneak.fontshadow、sprint.fontshadow
- betterchat.round、betterscreen、blockindicator、hideindicator、
  inventorydisplay、taboverlay、targetdisplay 的共享设置
- minimap.fastrender.disable.title(小地图与快速渲染不兼容提示)
线性雾默认 StartDistance 0 / EndDistance 20 让雾色覆盖 0~20 格
近景,近地面整体被染色。默认值推远为 32/64 格,StartDistance
上限从 20 扩到 200 便于拉开区间;mixin 中 end<=start 时自动
修正,避免线性雾公式反转导致近处更浓。
Buffer(capacity) 只设置 Capacity,Length 仍为 0,而 IOutputStream::WriteAsync
只传输 Length 个字节,导致缩略图流永远是 0 字节、媒体控件显示空白封面。

注意:src/main/resources/native/windows/ 下的预编译 DLL 需要重新编译才会包含此修复。

Co-Authored-By: Claude <noreply@anthropic.com>
requestArtwork 之前只更新 artworkUrl 不清 artwork,新歌的标题会配着上一首的封面
发布;若新封面下载失败(404/超时),回调里的 png != null 守卫直接跳过,旧图会
一直留到会话结束。

Co-Authored-By: Claude <noreply@anthropic.com>
setupFog 每帧被调用多次(renderWorldPass 两次 + renderCloudsCheck),每次新建
direct FloatBuffer 会持续走 Bits.reserveMemory 和 Cleaner 注册。原版自身也是缓存
fogColorBuffer 复用的。

Co-Authored-By: Claude <noreply@anthropic.com>
Images.drawModalRectWithCustomSizedTexture 用 width/height 同时决定四边形尺寸和
UV 跨度,HUD scale != 1 时会把 icons.png 里相邻的饥饿/护甲图标一起采样进来。新增
可单独指定 UV 跨度的重载,饱和度图标固定采样 9x9。

Co-Authored-By: Claude <noreply@anthropic.com>

@gaoyu06 gaoyu06 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

又过了一遍新的几个提交。先说好消息:上一轮的编译错误确实修好了,我在本地 worktree 上跑了 ./gradlew compileJava,通过,只剩两个跟这个 PR 无关的 mixin warning。失明药水 guard、AutoText 键位和新条目不绑键也都到位了。

有 4 个我直接推到这个分支上了(都单独一个 commit,不合适就 revert 掉):

  • fix(smtc): WriteAsync 前设置 Buffer.Length — 这个比较要命,见行内
  • fix(smtc): 切歌时清空封面缓存 — 上轮第 8 条只修了一半
  • fix(fog): 复用 fog color buffer — 改成 glFog 是对的,但每次都新建 direct buffer
  • fix(saturation): 图标采样跨度与缩放解耦 — 编译是修了,但 UV 跨度也跟着 scale 放大了

注意:Buffer.Length 那个改的是 .cppsrc/main/resources/native/windows/ 下那两个预编译 DLL 我没法在 macOS 上重编,得你在 Windows 上重新编一次才会生效,否则封面还是空白的。

剩下几个偏设计取舍的没动,写在行内了:Forge 事件语义、COM 套间、窗口句柄、IMG_EXEC 的线程模型。

另外那两个 DLL 没法做行内评论所以放这儿说:native/smtc/ 现在只有 .cpp 和 .def,没有 CMakeLists / build.bat / gradle task,AGENTS.md 和 docs 里也没写怎么构建。结果就是没人能确认发布的 DLL 是不是从当前 .cpp 编出来的(比如上面那个 Buffer.Length bug 到底在不在已提交的二进制里,我就没法判断),以后改 .cpp 也会静默发布陈旧的二进制。而且这两个文件会打进每个 jar,包括 Linux/macOS 的构建。建议加个构建脚本 + 校验和,或者改成版本化依赖拉取。

Comment thread native/smtc/smtc.cpp
if (artwork_data && artwork_len > 0) {
try {
auto stream = InMemoryRandomAccessStream();
auto buffer = Buffer(static_cast<uint32_t>(artwork_len));

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已在 fix(smtc): WriteAsync 前设置 Buffer.Length 里改了,这里说下原因:

以容量构造的 WinRT Buffer 只有 Capacity == artwork_lenLength 仍然是 0,而 IOutputStream::WriteAsync 只传输 buffer.Length() 个字节。所以从 array_view 换成 Buffer 之后,InMemoryRandomAccessStream 实际上是 0 字节,CreateFromStream 包了个空流,媒体浮出控件永远显示空白占位图——Java 侧刚修好的整条封面管线到这一步全丢了。

补一行 buffer.Length(static_cast<uint32_t>(artwork_len)); 就行。但 DLL 得重编,否则这个修复不生效。

Comment thread native/smtc/smtc.cpp Outdated
if (g_initialized) return 1;
try {
// Initialize WinRT apartment (MTA so callbacks can fire on any thread)
winrt::init_apartment(winrt::apartment_type::multi_threaded);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个没动,属于设计取舍。

MusicManager 是从 MusicScreen(主渲染线程)懒构造的,所以 smtc_start 跑在游戏线程上。如果那个线程上已经有人以 STA 调过 CoInitializeEx/OleInitialize(LWJGL 剪贴板/拖放、其他用 Desktop.browse 或 shell API 的 Forge 模组),CoInitializeEx(COINIT_MULTITHREADED) 会返回 RPC_E_CHANGED_MODEinit_apartment 抛异常被 catch (...) 吞掉返回 0。用户看不到媒体控件,日志只有一句 "failure status",没有 HRESULT。

反过来成功的时候,游戏线程被永久加入 MTA(smtc_close 从不调 uninit_apartment),后续需要 STA 的 shell 调用可能受影响。

比较稳的做法是把 SMTC 放到自己的专用线程上初始化并跑消息循环,别借用游戏线程。

Comment thread native/smtc/smtc.cpp

// Find a visible top-level window owned by this process. GetForWindow needs a
// real HWND; the Java side passes 0 when it has no window handle to hand over.
static HWND find_process_window() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java 侧永远传 0,所以 GetForWindow 拿到的是 EnumWindows 对这个 PID 恰好先返回的那个窗口。Forge 启动期间那可能是 SplashProgress 窗口或者某个 AWT/Swing 框架窗口——SMTC 会把会话绑到一个很快就被销毁的窗口上,控件从浮出面板静默消失,而 smtc_start 返回 1、Java 报 available == true,排查不出来。

另外 static HWND found 是进程级共享的(无状态 lambda 没法捕获),两个并发 smtc_start 会互相覆盖。

专门为此加的 HWND 参数其实是对的,缺的是让 Java 侧真的把 LWJGL 的窗口句柄传进来。

Comment thread native/smtc/smtc.cpp
}

/// Get the last error message (for debugging).
__declspec(dllexport) void __cdecl smtc_get_last_error(wchar_t* buf, int bufLen) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

smtc.defsmtc_get_last_error 导出了,但函数体只有 (void)buf; (void)bufLen;,而 SmtcNative 里根本没声明它。

所以同一个提交里新加的 status != 1 告警只能告诉用户 "failure status"——正好是这个导出本来要提供的信息拿不到。要么实现它并写进失败日志,要么连 .def 条目一起删掉。

* 供 SMTC 等需要原始字节(而非 GL 纹理)的调用方复用同一套下载/解码路径,
* 避免自起线程并发调用 AWT/ImageIO 在 macOS 上触发崩溃。
*/
public static void downloadPngAsync(final String url, final java.util.function.Consumer<byte[]> callback) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

改走 downloadPngAsync 方向是对的,但下载和调用方回调都跑在同一个 IMG_EXEC 上,这个单线程是 cover()base64Image()qr() 共用的。

切歌会额外排一次同一 URL 的重复下载(MusicTextures.cover() 已经为 UI 取过一次),连接 10s + 读 15s;完成回调又在同一线程里调 controls.publish() → JNA → smtc_publish,里面是同步的 stream.WriteAsync(buffer).get()。这期间队列上其他图片都解不了码——QQ/网易云扫码登录的二维码最多空白 25 秒,用户会以为登录坏了。

要么给下载单开一个执行器、只把解码放回 IMG_EXEC,要么让回调里的 publish 换个线程。

if (SaturationDisplay.using) {
// renderFood 末尾的 right_height += 10 会在 cancel 时被跳过,
// 手动补上以免 renderHealthMount/renderAir 整体下移 10px。
right_height += 10;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right_height 是对的,但有两个边角:

  1. Forge 的 renderFood 开头是 if (pre(FOOD)) return;right_height += 10 在后面。也就是说别的 HUD 模组(小地图/HUD 管理器)在 Pre(FOOD) 里取消食物元素时,原本 right_height 是不变的,而这里无条件加了 10,氧气条和坐骑血条会比那个模组预期高 10px。
  2. 因为是在 HEAD 取消,ForgeHooks 的 Pre/Post FOOD 事件根本不会触发,在 Post(FOOD) 里画自己食物条的模组会静默停渲染。

改成注入在 pre(FOOD) 之后(或者用 Pre(FOOD) 事件监听器 cancel)能同时解决这两点。不过如果只面向自家用户、不考虑和别的 HUD 模组共存,现在这样也够用。

gaoyu06 and others added 6 commits August 1, 2026 22:28
native/smtc/CMakeLists.txt 用 MSVC 静态 CRT 编 x64/Win32 两份 DLL;CI 新增
build-native 作业(windows-latest)构建后作为 artifact 传给打包作业,注入
src/main/resources/native/windows/ 再执行 gradle build,并校验两个 DLL 非空。

已提交的二进制随之删除并加入 .gitignore:此前无法确认发布的 DLL 是否与当前
smtc.cpp 一致,改 .cpp 也会静默发布陈旧二进制。

Co-Authored-By: Claude <noreply@anthropic.com>
套间状态是线程私有的:在游戏线程上 init_apartment 要么撞 RPC_E_CHANGED_MODE
(别的模组已把该线程初始化成 STA)静默失败,要么把游戏线程永久并入 MTA。改为
所有 native 调用都走单线程 FPSMaster-SMTC,顺带把阻塞的 smtc_publish 从调用方
线程上摘下来(发布用最新快照覆盖,不排队)。

同时:
- smtc_start 的 hwnd 改传 LWJGL 窗口句柄(反射 WindowsDisplay.hwnd),JNA 侧
  由 long 改为 Pointer,x86 下 HWND 是 32 位,long 会传错宽度
- native 兜底的窗口搜索加上可见/无 owner/有标题过滤,并去掉非重入的函数内
  static,避免绑到马上就被销毁的 Forge 启动画面窗口
- 实现 smtc_get_last_error 并在启动失败时打进日志,此前它是空实现且没绑进 JNA
- init_apartment 成功时才在 smtc_close 里 uninit_apartment

Co-Authored-By: Claude <noreply@anthropic.com>
renderFood 第一句就是 if (pre(FOOD)) return;,在 HEAD 取消会让 Pre(FOOD) 事件
根本不派发,别的 HUD 模组的钩子静默失效。改为重定向这次 pre 调用:事件照常触发,
别人已取消时把占位交给他们,只有我们真正接管时才补 right_height += 10。

Co-Authored-By: Claude <noreply@anthropic.com>
C++/WinRT 头文件会拉协程支持,/std:c++17 下解析到 <experimental/coroutine>,
当前 MSVC 直接报 STL1011 错误(CI 实测 x64/Win32 均失败)。C++20 走标准
<coroutine>。

Co-Authored-By: Claude <noreply@anthropic.com>
原实现取消原版 renderFood,然后用同一套 icons.png 贴图把饥饿条原样重画一遍,
右边挂一个饱和度数值。真正新增的信息只有那个数值,但为了重画饥饿条付出的代价是:
right_height 补偿、Pre/Post(FOOD) 事件失效、图标 UV 采样、与第三方 HUD 模组冲突。

改为只画饱和度本身(一根 0-20 的条加数值),原版饥饿条照常渲染。随之删除:
- MixinGuiIngameForge 对 renderFood 的接管(连同 right_height 影子字段)
- Images 里为缩放采样加的 UV 重载(恢复成原来的 private 方法)

新增 BarColor 设置,默认位置移到饥饿条上方。

实机验证:原版饥饿条恢复正常,饱和度条在 HUD scale=2 下不再糊。

Co-Authored-By: Claude <noreply@anthropic.com>
雾在 1.8.9 里是两套状态:setupFog 设 GL_FOG_COLOR 只影响几何体,而天空和清屏色
走的是 EntityRenderer.fogColorRed/Green/Blue(renderWorldPass 拿它 glClearColor,
renderSky 拿它画地平线雾带)。此前只改了前者,结果是方块被自定义雾吃掉、天空还是
原版蓝,实机看上去割裂。现在注入 updateFogColor 一并覆盖。

顺带:
- 覆盖条件抽成 fpsmaster$shouldOverrideFog(),两处注入共用同一套判断
- 雾色 buffer 改用原版 setFogColorBuffer(它写的是 EntityRenderer 自己缓存的
  direct buffer),删掉上一版自己加的静态缓存
- 删掉 GlStateManager.color(1,1,1,1) 复位:它是为配套已删除的顶点色写入而加的,
  现在这里不再动顶点色,留着反而会覆盖调用方的颜色状态

已知残留:地平线仍有一条原版天空色,那来自 WorldClient.getSkyColor,是独立于
fogColor 的第三处颜色,本次未动。

Co-Authored-By: Claude <noreply@anthropic.com>
@gaoyu06

gaoyu06 commented Aug 2, 2026

Copy link
Copy Markdown
Member

在 macOS 上把客户端真跑起来验证了一遍,补充几条实机结论。

已验证生效的:

  • 饱和度改成只画饱和度本身之后,原版饥饿条恢复正常渲染,HUD scale=2 下也不再糊
  • CustomFog 的雾色确实作用到几何体上了
  • 补上 updateFogColor 之后天空整体跟着变色(之前只有方块变、天空还是原版蓝)

一个还没定位的问题,留给后面处理:

开着 CustomFog 时,地平线会横着留一条原版天色的带子,把它关掉画面立刻恢复正常,所以是 CustomFog 自己造成的,不是既有问题。

我排掉的可能性:

  • 不是几何体的雾(GL_FOG_COLOR
  • 不是清屏色(fogColorRed/Green/Blue)—— 天空其余部分都已经跟着变色了
  • 不是天空盒(试过 redirect renderSky 里的 WorldClient.getSkyColor,mixin 确认注入,那条带纹丝不动)
  • 不是云(同样 redirect 过 getCloudColour,无变化)

所以它来自 renderSky 里别的某处颜色写入。下一步可以从那 8 处 GlStateManager.color 逐个查。这层我没提交,未经证实的代码不该合进来。

顺带提醒:改 mixins.fpsmaster.json 之后只跑 compileJava 是不够的,那是资源文件,得走 processResources,否则运行时加载的还是旧配置、新 mixin 静默不生效。我在这上面浪费了两轮验证。

IMG_EXEC 是单线程,约束来自 AWT——macOS(尤其 Rosetta) 下并发调 CoreGraphics 会崩。
但 HTTP 下载没有这个约束,却跟着一起排队:一次封面下载超时(连接 10s + 读 15s)会把
后面所有解码堵死,扫码登录的二维码明明不需要联网也要跟着等最多 25 秒。

拆成两个池:NET_EXEC 跑网络,拿到字节再回 IMG_EXEC 解码。串行解码这个约束没有变化,
qr()/base64Image() 仍然全程在 IMG_EXEC 上。

顺带把 downloadPngAsync 改成 downloadBytesAsync:SMTC 的 RandomAccessStreamReference
吃的就是图片流,Windows 自己会解码、JPEG 也认,原先「下载 → ImageIO.read →
ImageIO.write 编回 PNG」那一读一写是白做的,还把这条路径绑在了串行队列上。现在
SMTC 取封面完全不碰 AWT。相应地 MediaPlaybackSnapshot.artworkPng 改名 artworkBytes。

Co-Authored-By: Claude <noreply@anthropic.com>
@gaoyu06
gaoyu06 merged commit 487d252 into FPSMasterTeam:main Aug 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants