Repository navigation
Fix spectator menu previews 修复旁观者菜单预览 - #6081
PigeonNian merged 1 commit into
Conversation
- 补齐脉冲发生器、高级比较器和特斯拉塔的旁观者菜单入口 - 通过菜单初始化同步创造发电机功率与特斯拉塔过滤配置
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing. Review the Copilot code review billing settings.
Gugle2308
left a comment
There was a problem hiding this comment.
审查结论:REQUEST_CHANGES(1 处必须补、1 处建议本轮补齐)
方向正确,而且实现手段是唯一正确的那条:1.21.1 的旁观者右键不走 useWithoutItem,它在 ServerPlayerGameMode.useItemOn 里被提前拦下(字节码 34-73:gameModeForPlayer == SPECTATOR → BlockState.getMenuProvider(level,pos) → 非 null 则 ServerPlayer.openMenu(provider) 并返回 CONSUME)。所以「让旁观者能开预览」的落点只能是 getMenuProvider,而 BlockBehaviour.getMenuProvider 默认返回 null——高级比较器 / 脉冲发生器继承 HorizontalDirectionalBlock、特斯拉塔继承多方块基类,三者都拿不到默认实现,PR 补这三处覆写是必需的,不是可选优化。writeClientSideData 的字段顺序也与菜单构造器逐一对齐(BlockPos[+NBT] / 仅 VarInt),✅。
但「开放入口」这一步同时开放了写路径,而这一层没有配套门禁;另有两条同链路的 extraData 缺口仍在,其中一条在旁观者路径上会直接让客户端在构造菜单时抛异常。
fb3e98b 的完整源码树(codeload 拉取)静态核对。CI:build / checkstyle 均 success。
🔴(阻塞)旁观者现在能把「预览」变成「改机器」,三个 C2S 更新包没有任何 isSpectator 门
本 PR 之前,旁观者对这三台机器右键拿不到菜单(getMenuProvider 为 null → PASS),因此下列 handler 对旁观者不可达;本 PR 之后全部可达,而它们只校验「当前菜单类型」,不校验玩家是否处于旁观模式。原版对旁观者的保护恰恰只做在 handleContainerClick / handleContainerButtonClick(槽位点击、菜单按钮),自定义 payload 不在原版保护范围内——这正是需要自己补的地方。
| 新开放的菜单 | handler | 旁观者可造成的写入 |
|---|---|---|
| 高级比较器 | network/AdvancedComparatorUpdatePacket.java handleOnServer,仅有 hasContainerOpen() + instanceof AdvancedComparatorMenu |
setCompareMode / setOutputInvert / setRedstoneControl / setHighLimit / setLowLimit / setInputtingSignal 全部直写 BE |
| 脉冲发生器 | network/PulseGeneratorUpdatePacket.java |
setWaitingTime / setSignalDuration / setOutputMode / setStartMode + syncToClient() |
| 特斯拉塔 | network/TeslaAddFilterPacket.java / TeslaRemoveFilterPacket.java(menu.addFilter / menu.removeFilter 落到 BE 的 allowList) |
增删他人特斯拉塔的过滤条目 |
修法一行,加在各 handleOnServer 入口(与 createMenu 无关,不要试图回到 createMenu 里挡,那会把预览一起挡掉):
if (player.isSpectator()) return;注意 AdvancedComparatorUpdatePacket 已有的 if (!player.hasContainerOpen()) return; 不能替代这一条——旁观者此刻的 containerMenu 正是刚打开的那个菜单,hasContainerOpen() 为 true。同理 PulseGeneratorUpdatePacket 连 hasContainerOpen() 都没有。
顺带:CreativeGeneratorBlockEntity 的 SliderUpdatePacket(callback.onValueChange(this.value) → setPower)也是同类形态,但它在 1.21 线本来就可达(CreativeGeneratorBlock extends BetterBaseEntityBlock,BaseEntityBlock.getMenuProvider 默认返回实现了 MenuProvider 的 BE),属既有缺口,不记在本 PR 账上,可一并补。
⚠️ 两条 writeClientSideData 缺口仍在,其中一条在旁观者路径上必然抛异常
MenuProvider 实现者里,只有两个没有写客户端数据,而它们的菜单构造器无条件读该缓冲:
| BE | writeClientSideData |
对应菜单读取 |
|---|---|---|
block/entity/StructureScannerBlockEntity.java |
缺失 | StructureScannerMenu:Objects.requireNonNull(level.getBlockEntity(extraData.readBlockPos())) |
block/entity/fluid/ControlValveBlockEntity.java |
缺失 | ControlValveMenu:BlockPos pos = buf.readBlockPos(); |
失效链(逐环节可证):
- 旁观者右键 → 走 vanilla 分支 →
player.openMenu(provider)(1 参,不带BlockPosconsumer); ServerPlayer.openMenu用FriendlyByteBufUtil.writeCustomData(buf -> { provider.writeClientSideData(...); consumer?.accept(buf); })组装额外数据;本 BE 没有覆写 ⇒ 走IMenuProviderExtension默认空实现 ⇒ 长度 0;- 长度 0 ⇒ 不发
AdvancedOpenScreenPayload,改发普通ClientboundOpenScreenPacket; - 客户端
MenuScreens.ScreenConstructor.fromPacket调 2 参MenuType.create(containerId, inventory);MenuType的 NeoForge 补丁把 4 参create委托给IContainerFactory,而IContainerFactory.create(int, Inventory)的默认实现是create(id, inv, **null**)(javap 实证:aconst_null作第 3 实参); - 菜单构造器在
null缓冲上调readBlockPos()⇒ 抛异常,客户端在打开界面时崩/断线。
定位口径:这两台机器的普通(非旁观)打开路径都显式带了 pos(ControlValveBlock.java:184 serverPlayer.openMenu(be, pos)、StructureScannerBlock.java:158 ModMenuTypes.open(serverPlayer, menuProvider, pos)),所以平时不暴露;一旦改走不带 consumer 的 openMenu(provider) 就必炸。按 git log/base 树核对,这两处在 1.21 线属于既有缺口,不是本 PR 引入;但本 PR 的立论就是「旁观者也能开预览」且 issue 正文明确要求「建议排查所有带 UI 的方块」,这两处正好落在同一机制上,补齐成本各一行:
@Override
public void writeClientSideData(AbstractContainerMenu menu, RegistryFriendlyByteBuf buffer) {
buffer.writeBlockPos(this.getBlockPos());
}(若 26.1 线另有同源 PR,也请同步核对这两台机器——26.1 上结构扫描仪的 createMenu 旁观者守卫已被移除,写端不补就是新增回归,而不是既有缺口。)
💡 建议(不阻塞)
- 特斯拉塔过滤表走了三条通道:
TeslaTowerBlockEntity.writeClientSideData(本 PR 新增)、TeslaTowerBlock.useWithoutItem里仍然保留的PacketDistributor.sendToPlayer(sp, TeslaFilterSyncPacket.create(...))、以及TeslaTowerScreen.init()新增的handleSync(List.copyOf(menu.getBlockEntity().getAllowList()));而TeslaTowerScreen.handleSync末尾还会再调this.menu.handleSync(filters)→blockEntity.handleSync(...)第三次写同一份列表。三条都是幂等的,功能上不出错,但属「补丁叠补丁」信号:建议只保留writeClientSideData一条(它随开界面同包、时序最可靠),删掉useWithoutItem里的那次发包与init()里的兜底同步。若确实需要init()兜底(客户端 BE 尚未同步时getAllowList()为空),请写明原因,否则后人无法判断哪条是权威通道。 SliderInitPacket现在与writeClientSideData重复:功率已随菜单初始化下发并由SliderScreen.init()自播(slider.setValue+value.setValue),CreativeGeneratorBlock之后的PacketDistributor.sendToPlayer(serverPlayer, new SliderInitPacket(entity.getPower()))全树仅此一处发送,可删;保留则建议注释说明保留理由。ModMenuTypes.open(player, provider)这个重载现在是"完全依赖 BE 自己写BlockPos"的隐式契约:本 PR 把TeslaTowerBlock/CreativeGeneratorBlock都切到了这个 2 参版本,AdvancedComparatorBlock/PulseGeneratorBlock早已如此。任何后续给这些方块换 BE、或新增实现MenuProvider的机器只要漏写writeClientSideData,就会静默退化成上面那条 NPE 链路,且编译期、CI 全绿。建议给该重载的 javadoc 写明「provider 必须自行写BlockPos」,或保留open(player, provider, pos)统一带坐标(重复字节无害,缺失字节会崩),把安全性做成默认。
📋 声称验证表
| PR 描述声称 | 核对结果 |
|---|---|
| 「补齐脉冲发生器、高级比较器和特斯拉塔的旁观者菜单入口」 | ✅ 三处 getMenuProvider 齐全且语义正确;特斯拉塔用 getMainPartPos(pos, state) 解析主部件是必要的(多方块只有主部件挂 BE),比较器/脉冲为单方块直接查 getBlockEntity(pos) 亦正确 |
| 「通过菜单初始化同步创造发电机功率」 | ✅ writeClientSideData → writeVarInt 与 SliderMenu 4 参构造器 readVarInt 对齐;ModMenuTypes.SLIDER 已切到 4 参工厂(否则 IContainerFactory 不生效);SliderScreen.init 在 setResponder 之前赋值,不会触发多余的 SliderUpdatePacket ✅ |
| 「同步特斯拉塔过滤配置」 | ✅ 时序正确(writeBlockPos 后 STREAM_CODEC.encode,菜单侧先 readBlockPos 再 decode); |
fixed #6055 |
createMenu 里的 player.isSpectator() → null;本 PR 针对 1.21 线,而 1.21 线的这些守卫本就不存在(全树 block/entity/ 已无 createMenu 旁观者守卫),所以 1.21 上的溜槽/批量加工/收集器旁观者预览在本 PR 前后行为相同——真正被本 PR 改变的是那三台机器的可达性。另外 issue 要求「排查所有带 UI 的方块」,结构扫描仪与控制阀仍未补齐(见 getMenuProvider 的三台机器」,避免与 26.1 线同源 PR 的叙述混淆 |
🧪 建议实机验证
- 旁观者右键:高级比较器 / 脉冲发生器 / 特斯拉塔 → 界面正常打开,且比较器阈值、脉冲时长、过滤列表显示值与服务端一致;
- 旁观者尝试触发写入:改比较器阈值、改脉冲时长、增删特斯拉塔过滤项 → 服务端应拒绝(补门禁后重测);
- 旁观者右键结构扫描仪、控制阀 → 补
writeClientSideData前预期崩溃(用于确认失效链),补齐后应正常打开; - 生存玩家正常打开上述 5 台机器,确认
writeClientSideData改造未破坏原有参数同步(比较器的 NBT 配置、创造发电机功率、特斯拉塔过滤表); - 窗口缩放(触发
SliderScreen.init()重跑)后创造发电机功率输入框与滑块仍与打开时一致。
fix(menu): 修复旁观者菜单预览
补齐脉冲发生器、高级比较器和特斯拉塔的旁观者菜单入口
通过菜单初始化同步创造发电机功率与特斯拉塔过滤配置
fixed [Bug] 旁观模式下 右键溜槽显示“无法打开:战利品未生成” 而非打开ui #6055