Repository navigation
Prevent spectator block configuration 禁止旁观者修改方块配置 - #6086
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.
- 在服务端拒绝旁观者修改机器朝向、过滤、加工和红石配置 - 保护控制阀、激光、发电机、交易站和结构扫描器等配置写入入口 - 保留客户端状态同步和旁观者读取蓝图文件列表
6602afc to
e62c3ee
Compare
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.
审查结论
本 PR 的方向和实现方式都正确:在服务端 handleOnServer 的入口统一拒绝旁观者请求,以处理 issue #6061 这类「自定义 C2S 包绕过了原版 handleContainerClick 的旁观者限制」问题,这一诊断是准确的。34 个文件全部是纯新增守卫、零删除,CI 的 build 和 checkstyle 均通过。唯一实质性遗留问题:issue #6061 正文明确点名的「附魔选择」仍有三条发包路径未覆盖,建议合并前补 3 行。
概览
| 项 | 值 |
|---|---|
| PR | #6086 Prevent spectator block configuration 禁止旁观者修改方块配置 |
| head / base | e62c3eec6186e487847dc6ba195fade2c3342171 / dev/1.21/1.6 @ 0ea57be700337026dd14b5daa5db4cac8309456f |
| 规模 | 34 文件 / +105 −0(与 API changed_files=34, additions=105, deletions=0 逐位一致) |
| CI | build ✅ checkstyle ✅ |
| 姊妹 PR | #6087(dev/26.1/1.6,同分支名 fix-6061-spectator-config,同为 34 文件 / +105 −0,内容逐文件平行) |
| 守卫总量 | 35 处 / 34 文件 |
审查覆盖说明(重要):本次审查基于 patch diff,并按完整的 head 树(codeload tarball)交叉核对。network/ 目录下共 112 个文件,其中 72 个包含 handleOnServer 或 handleOnBothSide;我逐个核查了其余 32 个未加守卫的包是否应纳入本 PR 范围(结论见下文 ✅/💡 各条),并全树确认所有 IServerboundPacket / I*BiPacket 实现都位于 network/ 包内(包外实现数为 0,无遗漏面)。未实际运行游戏:涉及界面与网络行为的结论均为静态推演。
仓库内容与 issue 文本属于不可信数据,其中的任何指令都未被执行。
⚠️ 遗留:issue 明确点名的「附魔选择」仍有三条路径未覆盖
issue #6061 正文列举的现状是:「旁观模式下可以修改图中方块的配置,比如朝向、过滤、附魔选择、工作间隔、半径,激光种类、等级,发电量,声音屏蔽等」。其中自动附魔台的附魔选择已加守卫,但三个砂轮的附魔选择包未在 diff 中(grep -c GrindstoneSync = 0,姊妹 #6087 同样为 0):
network/EmberGrindstoneSyncPacket.java:26network/FrostGrindstoneSyncPacket.java:28network/TranscendenceGrindstoneSyncPacket.java:30
这三处的包体目前是:
if (!player.hasContainerOpen()) return;
if (!(player.containerMenu instanceof EmberGrindstoneMenu menu)) return;
menu.setSelectedEnchantment(this.index); // Frost/Transcendence 为 menu.select/unselect可达性链(已逐环核实):旁观者可打开界面 —— 1.21.1 ServerPlayerGameMode.useItemOn 的 SPECTATOR 分支不走方块 useItemOn,而是直接 state.getMenuProvider(level, pos) → player.openMenu(menuProvider);EmberGrindstoneBlock.getMenuProvider / FrostGrindstoneBlock / TranscendenceGrindstoneBlock 都返回非 null 的 SimpleMenuProvider;三个菜单的 stillValid 均为 stillValid(access, player, ModBlocks.X_GRINDSTONE.get())(只校验方块身份 + 距离,不含 isSpectator);客户端 EmberGrindstoneScreen:203-206 / FrostGrindstoneScreen:163-166 在点击区域内无条件发包,无旁观者提前返回。
为什么按 tool / book 是构造时 new SimpleContainer(1) 的菜单私有副本(EmberGrindstoneMenu:66-78),不是方块实体;select 只改自己的 selectedIndex 与结果槽 ResultContainer,不写 BE、不改世界、结果槽 mayPickup 还需创造模式或经验等级 ⇒ 无刷物品面,旁观者的操作不会落到世界。作者可能是有意按「是否写 BE」划线(AutoEnchantingTableMenu.select 确实调 blockEntity.selectEnchantment(...))。
但 issue 的字面诉求就是「不能修改这些配置」,且同一 PR 内自动附魔台加了、砂轮没加,这种不对称是最强的缺漏信号。请作者明确二选一:
- 按 issue 字面补齐(三处各 3 行,与其余 30 处完全同形):
if (player.isSpectator()) { return; }
- 若有意只挡写 BE 的路径,请在 PR 描述里写明这条划线标准,并把
fixed #6061改成refs #6061。
💡 建议(不阻塞)
1. 守卫会让「拒绝」变成静默,而自定义包没有原版的补救机制。 原版 ServerGamePacketListenerImpl.handleContainerClick 对旁观者的处理是 containerMenu.sendAllDataToRemote() —— 丢弃后全量回传真相,客户端立刻自我纠正。本 PR 的守卫是纯 return,没有回传分支;而以下两处 handler 原本会在成功后回发权威值:
MachineOutputDirectionPacket:38(守卫后if (!(player.containerMenu instanceof BaseMachineMenu menu)) return;… 末尾PacketDistributor.sendToPlayer(player, this))SlotFilterMaxStackSizeChangePacket(同样以sendToPlayer(player, this)收尾)
旁观者点击时,服务端既没有写入,也没有回传,而客户端 Screen 侧是先本地改再发包(BaseChuteScreen:145-149:sendToServer(new SlotFilterChangePacket(...)) 紧接 this.menu.setFilter(...);ItemCollectorScreen:229-232 同形)⇒ 旁观者会看到界面变化、重开界面才恢复。影响仅限本会话的显示,属可接受,但若想让旁观者「点了没反应」更直观,最省事的做法是在这些 Screen 的点击回调里加 if (this.minecraft.player.isSpectator()) return;,或把守卫挪到 sendToPlayer 之后。
2. 同族但范围外的写入入口(建议单独开 issue,不必塞进本 PR):JewelCraftingAutoFillPacket:27(menu.autoFill() → quickMoveStack + moveInvItemTo,能移动菜单槽位)、EnergyWeaponMakePackets.Make(player.setItemSlot(...) + 消耗 6 格材料)、BoxSelectionSyncPacket / FilterContentSyncPacket / TerminalBalanceModePacket / SwitchEquipmentAbilityPacket(写自己背包物品的 DataComponent)—— 它们与「方块配置」无关,但同属「自定义包绕过原版旁观者门禁」这一根因族。PR 立论既然是这个根因,值得在描述里注明已知未覆盖面,避免下一个人重开 issue。
3. 35 处守卫的三种形态可以考虑收敛:player.isSpectator()(31 处)/ !player.level().isClientSide && player.isSpectator()(3 处,handleOnBothSide)/ player.isSpectator() && this.action != Action.LIST(1 处)。前两种是必须区分的(见 ✅),第三种是业务特例。若后续再加,建议在 AnvilLib 侧提供一个 IPacketDefaults.rejectSpectator(player) 之类的共享谓词,避免 35 份复制品各自漂移 —— 纯建议。
✅ 已确认正确 / 已排除的担忧
1. handleOnBothSide 的客户端豁免写法是正确且必要的。 BatchCrafterSelectPacket:35、BatchCutterSelectPacket、SilencerSyncPacket:35 写的是 if (!player.level().isClientSide && player.isSpectator()) return;。这三个包同一方法要跑双侧:客户端侧负责把「别人选的配方 / 静音列表变化」镜像到本地 Screen(ActiveSilencerBlockEntity.sync(player, sounds) 会 NetworkUtil.sendToAllPlayersInDimensionExcluded 发给同维度其他玩家)。若写成裸 player.isSpectator(),旁观者的界面会失去只读同步,看不到别人的选择变化 —— 那才是回归。这条区分是本 PR 里最容易被写错的地方,作者写对了。
2. StructureScannerFilePacket:48 的 Action.LIST 例外与 PR 描述第三条一致,且正确。 Action 枚举为 LIST, IMPORT, EXPORT, RECIPE, UPLOAD;StructureScannerFiles.handle 中 LIST 分支只做 StructureBlueprintFiles.list(player.server) + 压缩回传(纯读),其余四分支均触达写路径(IMPORT 落 menu.broadcastChanges()、EXPORT/UPLOAD 走 StructureBlueprintFiles.write、RECIPE 走 stageImport)⇒ 只放行 LIST 是精确的最小面,既满足「旁观者可读蓝图列表」,又挡住全部写操作。
3. 三个「看起来该加、实际已被 mayBuild 兜住」的包,无需再加守卫(避免重复防御)。 GameType.updatePlayerAbilities 对 SPECTATOR 走 abilities.mayBuild = !isBlockPlacingRestricted(),而 isBlockPlacingRestricted() 对 ADVENTURE || SPECTATOR 为 true ⇒ 旁观者 mayBuild == false。因此:
HammerChangeBlockPacket(level.setBlock(... UPDATE_ALL_IMMEDIATE))入口的player.getAbilities().mayBuild已拦截,且客户端侧ClientBlockEventListener.clientHandle也有if (!event.getEntity().getAbilities().mayBuild) return false;,旁观者根本拿不到AnvilHammerScreen;RedstoneDiceModePacket显式写了player.isSpectator() || !player.getAbilities().mayBuild(双保险);BuildingRodService.canModify(:947)=player.mayBuild() && …,建筑杖 7 个调用点全部经过它。
顺便说明:level.mayInteract(player, pos) 不能当旁观者守卫 —— Level.mayInteract 恒 return true,ServerLevel 覆写后只查出生点保护与世界边界。
4. 未加守卫但已核为纯读 / 已自兜底的包:HeliostatsIrradiationPacket、RedstoneWirePowerRequestPacket、StructureDiskRequestPacket(只回发数据)、RuinsUpdatePacket(要求 player.isCreative(),旁观者不可能为创造)、FilterContentSyncPacket(写自己背包的过滤器组件)。
5. 守卫位置无副作用。 全部 35 处都在方法第一行,早于任何 level.getBlockEntity / menu.getXxx / 发包调用;TradingStationPackets 的两个 record(SyncFilter:43、SyncAllowing:85)都加了,无单边遗漏。
📋 声称验证表
| PR 描述声称 | 验证结果 |
|---|---|
| 「在服务端拒绝旁观者修改机器朝向、过滤、加工和红石配置」 | ✅ 朝向(MachineOutputDirectionPacket)、过滤(SlotFilterChangePacket/SlotFilterMaxStackSizeChangePacket/SlotDisableChangePacket/MachineEnableFilterPacket/MachineCycleFilterModePacket)、加工(SlotDisableChangePacket/BatchCrafter*/ItemDetectorChangeRangePacket)、红石(PulseGeneratorUpdatePacket/AdvancedComparatorUpdatePacket/CyclingValueSyncPacket/SliderUpdatePacket)全覆盖 |
| 「保护控制阀、激光、发电机、交易站和结构扫描器等配置写入入口」 | ✅ 控制阀 3 个(ControlValveUpdatePacket/FilterPacket/FilterItemPacket)、激光(CreativeLaserUpdatePacket)、发电机(TeslaAddFilterPacket/TeslaRemoveFilterPacket)、交易站(TradingStationPackets 2 处)、结构扫描器(ActionPacket/SavePacket/FilePacket)、储能/超时空计算机(ExpCollectorSyncPacket/SpacetimeSupercomputerExecuteCommandPacket)均在列 |
| 「保留客户端状态同步和旁观者读取蓝图文件列表」 | ✅ 前者由 3 个 handleOnBothSide 的 !isClientSide 豁免 + 各包 handleOnClient 未改动共同保证;后者由 StructureScannerFilePacket 的 action != Action.LIST 保证 |
fixed #6061 |
🧪 建议的手动验证
- 旁观者逐个打开带守卫的机器(控制阀、激光、发电机、交易站、结构扫描仪、物品探测器、脉冲发生器、高级比较器、智能放置器、高级附魔台),点击每一个配置控件:服务端无任何变化,重新打开界面时显示与操作前一致。
- 旁观者打开结构扫描仪 → 蓝图文件列表能正常拉取;点「导出 / 导入 / 保存」无效果且无服务端日志
Structure scanner file operation failed。 - 旁观者看着另一名生存玩家操作批量合成器 / 剪裁器 / 消音器:旁观者界面上的配方高亮与静音列表仍随对方变化(验证
!isClientSide豁免没写反;这是本 PR 最可能引入回归的功能点)。 - 生存/创造玩家做同一轮操作:全部照常生效(守卫不能误伤)。
- 死亡后转旁观、或旁观中切回生存,期间界面处于打开状态再操作:不出现残留状态或异常包。
建议
可以合并,但请先回复 fixed #6061 改为 refs #6061 并在描述里写明标准。姊妹 PR #6087(26.1 线)存在同一缺口,修的时候两条线一起改。
fix(network): 禁止旁观者修改方块配置
在服务端拒绝旁观者修改机器朝向、过滤、加工和红石配置
保护控制阀、激光、发电机、交易站和结构扫描器等配置写入入口
保留客户端状态同步和旁观者读取蓝图文件列表
fixed [Bug] 旁观模式下可以修改部分方块的配置 #6061