Skip to content

Add client config to swap fluid port bucket pour/store mouse actions, fix tooltips, and added four processing table to denylist 增加流体端口桶操作左右键翻转功能,修正tooltip换行,把四个加工台放入精确拾取黑名单 - #4940

Merged
TBPig merged 6 commits into
Anvil-Dev:dev/1.21/1.6from
PigeonNian:fluidre/1.21/1.6
Sep 19, 2026

Conversation

@PigeonNian

@PigeonNian PigeonNian commented Sep 19, 2026

Copy link
Copy Markdown
Contributor
  • 新增客户端配置项 invertFluidPortBucketAction,控制流体端口桶操作左右键功能互换
  • 修正了 tooltip 的换行问题
  • 给四个加工台添加精确拾取黑名单

- 新增客户端配置项 invertFluidPortBucketAction,控制流体端口桶操作左右键功能互换
- 客户端事件监听同步该配置至服务端,确保服务端知道当前玩家的翻转状态
- 修改存储界面逻辑,根据配置动态判断左键或右键执行倒液或存桶操作
- 服务端根据客户端翻转状态及按键决定执行倒液或存桶行为,保证逻辑一致性
- 重构多处 RPC 调用,新增按键参数传递,实现客户端按键行为正确同步服务端
- 玩家退出时清理其翻转状态,避免内存泄漏
- 更新多语言资源文件,添加流体端口桶操作翻转相关提示文字
@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

curl -s "https://api.github.com/repos/Anvil-Dev/AnvilCraft/pulls/4940" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('title'));print('state',d.get('state'),'merged',d.get('merged'),...

Reason: Security scan — [HIGH] Pipe to interpreter: curl | python3: Command pipes output from 'curl' directly to interpreter 'python3'. Downloaded content will be executed without inspection.
Safer: tirith run https://api.github.com/repos/Anvil-Dev/AnvilCraft/pulls/4940 — or: vet https://api.github.com/repos/Anvil-Dev/AnvilCraft/pulls/4940 (https://getvet.sh); script execution via -e/-c flag

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4940

操作: opened
范围: 8 个文件(6 Java + 2 生成 lang)/ 496 行 diff / 0 新增 / 0 删除
校验方式: 除 diff 外,已用本地克隆交叉核对 head 真实源码(git fetch origin pull/4940/head:refs/remotes/rvhead4940),逐点比对 base→head 的「倒液/存桶」判定语义。


🔴 关键

无(未发现编译错误、调用点遗漏或未翻转路径的行为回归;非翻转路径已逐点验证等价,见下)。

⚠️ 警告

  1. StorageServerStub.INVERTED_BUCKET_ACTION 可被伪造StorageServerStub.java:173
    新增的 updateInvertedBucketAction(UUID playerId, boolean inverted)唯一没有 validator 的存储 RPC。同文件其它 callable 一律带 @RemoteCallable(validator = StorageAccessValidator.class),而该 validator 的第一件事就是 player.getGameProfile().getId().equals(playerId)StorageServerStub.java:4520 附近)——即 args[0] 由客户端提供、框架不代填。因此改造过的客户端可以替任意玩家写入翻转状态,后果是受害者的点击被反着解释(想「存桶」却把整桶流体倒进端口)。建议补一行身份校验,或干脆不传 UUID(改为从 payload 上下文取玩家)。

    附注:BundleLikeServerStub.updateInverted 有同样形状的旧问题,可一并修。

  2. 断线重连(或换服)后翻转状态失效,且客户端不会再上报InvertedActionEventListener.java:16,35 + PlayerEventListener.java:72
    本 PR 在服务端退出时清表是对的,但客户端 lastBucketInverted 是静态字段、从不在断开时复位,而 ClientEventListener#onClientPlayerDisconnectClientEventListener.java:99)也没清它;上报逻辑又只在「值发生变化」时触发。于是:配置开 ON 的玩家断线→重连(同一客户端会话内第二次进服)后,服务端表被清空、客户端却认为已同步,双方永久错配:UI 认为左键=存桶,服务端按未翻转处理左键=倒液 —— 一次左键会把整桶流体倒进端口,而不是把桶当物品存入;Shift+左键拖拽同理。修法(任一即可):在 LoggingOut 里把 lastBucketInverted/lastInverted 复位为 false;或在进入世界/打开存储界面时强制上报一次。

💡 建议

  1. quickMoveToStorage 新增的 button 参数恒为 0StorageScreen.java:1989 + StorageServerStub.java:498
    拖拽起始分支只有在 button == 1 已被上一分支拦掉后才可达,故 quickMoveButton 只能是 0,StorageClientStub.quickMoveToStorage(..., button) 永远传 0。这个新参数目前不带来任何自由度,建议回退为 boolean/常量,或在 javadoc 里明确写出该不变式。
  2. 注释残留StorageServerStub.java:4978:「右键不走这里,而走原有物品行为」在开启翻转后不再成立(此时右键正是流体行为键),建议改掉。
  3. amount 计算位置StorageServerStub.java:360-361int amount = button == 0 ? carried.getCount() : 1;pour 分支已完全不用(倒液一律整叠),放进 else 分支里会更不容易被误读为「倒液数量受按键影响」。
  4. 设计可选:本 PR 把「倒液/存桶」从「调用点随请求传参」改成「服务端查同步状态」。但 deposit/moveSameToStorage/quickMoveToStorage 的调用点本来就掌握按键,仍可直接传 pour(旧方案),从而免掉这份状态与上面第 1、2 条隐患;若坚持服务端权威,可考虑把生效值随 InteractionResult 回给客户端,避免两份逻辑漂移。

🟢 看起来不错

  • 非翻转路径逐点等价:6 处 pour 判定点全部核对与 base 一致 —— interact 的 QUICK_MOVE_TO_STORAGE / !carried.isEmpty()moveInventoryStackToStoragequickMoveToStoragemoveSameToStoragedeposittakeFluidBucket(未翻转时 shouldPourFluidbutton == 0)。
  • slot < FLUID_SLOT_BASE 守卫方向正确(流体伪槽位自 1 << 24 起,客户端 StorageScreen.FLUID_SLOT_BASE 与之同源),阻断了「翻转后把流体格上的存桶又变回倒液」。
  • takeFluidBucket 去掉 button 后恒倒整叠:顺手修掉了翻转场景下「整叠被误减为 1 个」的潜在缺陷。
  • 空手时流体格键位固定为左键取液,客户端与服务端判定一致(客户端 carried.isEmpty() ? 0 : bucketActionButton() ↔ 服务端 FLUID_BUCKET 分支不看按键)。
  • 退出清理(PlayerLoggedOutEventclearInvertedBucketAction)避免静态表永久残留 ✅。
  • 生成资源与源码一致ConfigData.readConfigClass 按字段名 + @Comment 自动生成 name/tooltip,en_us/en_ud 均新增对应两条 key(JSON 均合法,2530 条,排序位置正确),en_ud 为翻转文本、逐词核对无误,无需手工干预。
  • 无遗漏调用点:deposit/moveSameToStorage/quickMoveToStorage 的调用者只有 StorageScreen,参数 boolean → int 改动全链路闭合。

📋 声称验证表

声称 状态 对应文件
新增客户端配置 invertFluidPortBucketAction AnvilCraftClientConfig.java:44 + 生成 lang 两 key
客户端事件监听同步配置至服务端 InvertedActionEventListener.java:33-38StorageClientStub.updateInvertedBucketAction
存储界面按配置动态判断左右键倒液/存桶 StorageScreen.bucketActionButton()mouseClicked:1902-1912
服务端按翻转状态及按键决定行为 StorageServerStub.shouldPourFluid:191 + 6 处调用
重构多处 RPC 新增按键参数 quickMoveToStorage/moveSameToStorage/depositboolean pour → int button
玩家退出时清理翻转状态 PlayerEventListener.java:72
客户端/服务端键位裁决一致性 ⚠️ 逻辑一致,但存在同步漏发窗口(见警告 2)

结论: REQUEST_CHANGES — 功能与非翻转路径的实现质量都不错,但警告 1(身份校验缺失)与警告 2(重连后静默失效、且表现为「做相反的事」)建议修掉后再合并。

标题建议(本次 gh 未认证 —— gh auth status 显示 token invalid,故未自动修改):
Add client option to invert fluid port bucket left/right-click actions 新增客户端配置项以翻转流体端口桶操作左右键

🧪 测试建议

被测目标 推荐测试场景 优先级
StorageServerStub.shouldPourFluid / interact 翻转 × 左/右键 × 背包格/物品格/流体格组合矩阵,断言倒液/存桶分支与数量语义 🔴
InvertedActionEventListener.onClientTick 断线重连后仍能重新上报(回归本次发现的漏发窗口) 🔴
updateInvertedBucketAction 伪造他人 UUID 的调用应被拒绝 🟡
quickMoveToStorage(button) 断言拖拽路径 button 恒为 0(若保留该参数) 🟢

由 Hermes Agent 审查

- 引入TooltipUtil.addTranslatedLines方法,支持含换行的翻译文本拆行显示
- 将EquipmentArmorItem中tooltip文本替换为调用TooltipUtil的统一多行添加方法
- 修改model_selection_denylist.json,新增多种工作台模型禁止清单
- 优化翻译文本在tooltip中的颜色和格式处理,保证换行效果正常显示
@Gugle2308

Copy link
Copy Markdown
Collaborator

审查完成。以下是完整结论(webhook 会自动投递到 PR 评论区)。


代码审查摘要 — PR #4940

操作: synchronize(分支 fluidre/1.21/1.6dev/1.21/1.6
范围: 11 个文件(9 Java / 2 生成 lang + 1 资源 JSON),+179 / −52,2 个 commit
合并状态: mergeable_state = unstable(无冲突,CI 待跑)

📋 提交构成(重要)

commit 内容 与 PR 主题关系
feat(storage): 增加流体端口桶操作左右键翻转功能 本 PR 声称的功能
refactor(tooltip): 优化装备tooltip的多行翻译处理 TooltipUtil.addTranslatedLines + EquipmentArmorItem 重写 + model_selection_denylist.json 改 ID 与主题无关

第 2 个 commit 完全不在 PR 标题/描述范围内,而下面最严重的问题恰好出在它里面


🔴 关键(必须修复)

1. model_selection_denylist.json:29 用了不存在的方块 ID,导致整个 denylist 文件加载失败、全部名单失效

"anvilcraft:pink_cement_cauldron",
"anvilcraft:stamping_table",     // ← 该方块不存在
  • 全仓库搜索 stamping_table 只有这一处出现;该工作台的注册名是 stamping_platformModBlocks.java:402REGISTRUM.block("stamping_platform", StampingPlatformBlock::new)ModBlockEntities.java:588 同理)。旁边三个 crushing_table / sifting_table / unpacking_table 都是真实 ID,显然这里是 stamping_platform 的笔误。
  • 后果不是"这一条无效"这么轻。ModelSelectionDenylist.readBlocks()
blocks.add(BuiltInRegistries.BLOCK.getOptional(id)
    .orElseThrow(() -> new IllegalArgumentException("Unknown block in model selection denylist: " + id)));

reload()loaded = new Rules(readBlocks(...), ...),而 IllegalArgumentExceptioncatch (IOException | RuntimeException) 吞掉并只打一条 warn → loaded 保持 EMPTY,三个名单(precise_picking / model_outline / ber_selection)一起清空。该文件只有 src/main/resources 一份资源,getResourceStack 没有更低优先级包可回退 → rules == EMPTY

  • 实际表现:所有原本被排除的方块(23 个 cauldron、large_cauldronfish_tank、各类 pipe、trading_stationheavy_iron_column 等)重新走精确拾取/模型描边/BER 拾取 —— 正是这份名单存在要避免的显示问题,只在日志里留一行 warn,很容易被忽略。
  • 修复"anvilcraft:stamping_table""anvilcraft:stamping_platform"(若确实想排除主工作台就保留,若只想排除三个变体则直接删掉这一行)。

2. 建议拆分 PR:把 commit 2(tooltip 重构 + denylist)拆成独立 PR。它属于另一主题(且与已合并的 #4937「优化提示文本格式和内容」同一片区域,容易产生混淆/冲突),现在被塞进一个"流体端口桶翻转"PR,评审与回滚粒度都被破坏了。


⚠️ 警告

3. 翻转状态只在"配置值变化"时上报,跨世界/跨服务器会静默失效

InvertedActionEventListener.java:16,34-38private static boolean lastBucketInverted = false; 只在 bucketInverted != lastBucketInverted 时才 updateInvertedBucketAction

复现:设置里开启该选项(客户端上报 true,静态位=true)→ 退出存档 / 换服务器 / 服务器重启(服务端 INVERTED_BUCKET_ACTIONclearInvertedBucketAction 或进程重启清空)→ 重新进入世界:客户端 bucketInverted(true) == lastBucketInverted(true)不再上报,服务端查不到该玩家 → shouldPourFluid 按未翻转处理 → 用户在存储界面里"左键存桶、右键倒液"的设置完全不生效,直到手动关一次再开。

服务端行为的判断只在服务端(端口/物品槽走 shouldPourFluid(playerId, button)),客户端不会与之"错配"(流体格的 takeFluidBucket 已不看按键),所以症状是"选项没反应"而非"行为反了"。

修复建议(任一):lastBucketInverted 改成 Boolean(null 为初值,进世界后首个 tick 必发一次);或在 ClientPlayerNetworkEvent.LoggingOut / LevelEvent.Unload 里复位静态位;或在 LoggingIn 时主动推送一次。顺带一提,相邻的 lastInverted(BundleLike)有同一潜在问题,可一并处理。

4. updateInvertedBucketAction(UUID, boolean) 无 validator,且 UUID 由客户端提供

@RemoteCallable                                  // ← 无 validator
public static void updateInvertedBucketAction(UUID playerId, boolean inverted)

StorageClientStub.updateInvertedBucketAction 传的是本地玩家的 UUID,服务端不做任何校验就写表 → 改造过的客户端可以把任意玩家的翻转状态改掉(受害者左键本想倒液却变成存桶)。RPC 框架本身支持发送方校验(IRemoteCallableValidator#validate(IPayloadContext ctx, ...)ctx.player() 即发起方),建议加 validator 校验 playerId.equals(ctx.player().getUUID())BundleLikeServerStub.updateInverted 是同样的形态(既有问题),但这次新增的是新表,顺手收紧更好。

5. 缺少 zh_cn 翻译

src/main/resources/assets/anvilcraft/lang/zh_cn.json 是唯一维护配置项翻译的语言文件(含 273 个 anvilcraft.configuration.*,包括 invert_override_action),本 PR 只更新了生成的 en_us.json / en_ud.json,没有加:

"anvilcraft.configuration.invert_fluid_port_bucket_action": "",
"anvilcraft.configuration.invert_fluid_port_bucket_action.tooltip": "",

→ 中文客户端会显示英文。其余语言文件(de/ja/ru…)不含配置键,无需处理。


💡 建议

  • StorageScreen.quickMoveButton 实际恒为 0button == 1mouseClicked 更早的分支(if (button == 1)QUICK_MOVE_TO_STORAGE)就 return 了,拖拽只可能由左键发起。字段+参数可以简化,或至少补一行注释说明"拖拽只有左键一种来源,翻转后左键=存桶"。
  • 同一映射被写在两处StorageScreen.bucketActionButton()(客户端)与 StorageServerStub.shouldPourFluid()(服务端)各自实现 inverted ? 1 : 0。当前一致(已核对 6 个调用点全部走 shouldPourFluid,无 button == 0 残留),但这是"必须同步漂移"的耦合,建议在两处加互相指向的注释或抽一个共享常量。
  • 流体格的键位不对称:空手时"装一桶"固定左键,持桶时才随配置翻转(fluidButton = carried.isEmpty() ? 0 : bucketActionButton())。代码注释已解释,属于有意设计;建议在配置 tooltip 里也点明,否则用户会认为翻转"没翻干净"。
  • StorageClientStub.playerId() 在 player 为 null 时抛 IllegalStateException,而 onClientTick 只挡了 getConnection() != null。登录/加载地形阶段两者可能不一致(NeoForge 的 getConnection() 会回落到 pendingConnection),一旦抛异常就是在 tick 事件处理器里。相邻的 BundleLikeClientStub 用的是 Minecraft.getInstance().getGameProfile().getId(),建议对齐。
  • interact()int amount = button == 0 ? carried.getCount() : 1; 现在只服务物品存入分支,倒液改用 carried.getCount()。逻辑正确(注释也写了),但建议把变量名/注释再收紧一点,避免后人误以为它也参与倒液。

🟢 看起来不错

  • 默认(未翻转)行为与目标分支逐点等价(已对照 rvbase1216 源码逐处核对):
    • interactQUICK_MOVE_TO_STORAGE 分支:pour 条件与旧 button == 0 完全一致;
    • !carried.isEmpty() 分支:旧 amountbutton==0 时本就等于 carried.getCount(),换成显式整叠无行为差异;
    • takeFluidBucket 删掉的 button==1 → amount 1 分支在旧客户端里不可达(旧代码 button == 1 提前走 PICKUP),属死分支清理。
  • slot < StoragePortManager.FLUID_SLOT_BASE 的守卫是对的:流体伪槽位编号远大于真实槽位(FLUID_SLOT_BASE + index),与客户端"物品行为键在流体格发 PICKUP"的分支语义一致,翻转后不会把"存桶"又变回"倒液"。
  • RPC 参数顺序两端一致interact/quickMoveToStorage/moveSameToStorage/deposit 四个方法客户端 stub 与服务端签名逐个对齐,@CallableParam(... "ORDER_STREAM_CODEC") 保留完好(这是最容易出错的地方,本次没问题)。
  • 登出清理对称PlayerEventListener.loggedOut 增加 StorageClientStub.clearInvertedBucketAction,与 BundleLikeServerStub.clear 并列;RPC 处理在 ctx.enqueueWork 主线程执行,静态 HashMap 无并发问题。
  • en_ud 生成正确:我复刻了 RegistrumLangProvider#toUpsideDown(含占位符保留 + 分段反转)对 head 的两份 lang 全量校验:2530 个键全部匹配、0 缺失,新增的两条 en_ud 是标准的 datagen 产物,不是手写。
  • 配置项命名 invertFluidPortBucketActioninvert_fluid_port_bucket_action@Comment 文本与生成文件完全一致,键序(字母序)也正确。

📋 声称验证表

PR 描述声称 状态 对应实现
新增客户端配置项 invertFluidPortBucketAction AnvilCraftClientConfig.java:42-44 + en_us/en_ud
客户端事件监听同步配置至服务端 ⚠️ InvertedActionEventListener.java:33-38(仅变化时上报,见警告 3)
存储界面按配置动态判断左/右键倒液或存桶 StorageScreen.java:1896-1918, 2456-2464
服务端按翻转状态+按键决定行为 StorageServerStub.shouldPourFluid:191 + 6 处调用点
重构 RPC 新增按键参数传递 StorageClientStub 4 个方法 ↔ 服务端签名一致
玩家退出时清理翻转状态 PlayerEventListener.java:72StorageServerStub.java:177-179
更新多语言资源与提示文字 ⚠️ en_us/en_ud ✅;zh_cn 缺失(见警告 5)
(未声称)tooltip 重构 + denylist 改 ID commit 2,含关键 bug 1,且超出 PR 范围

🧪 测试建议

被测目标 推荐场景 优先级
ModelSelectionDenylist.reload() 名单含未知 ID 时应保留其余条目(或让 CI/单测校验所有 ID 可解析),当前是整文件失效 🔴
shouldPourFluid × 4 入口(interact/quickMoveToStorage/moveSameToStorage/deposit 翻转 on/off × 左/右键 2×2 全矩阵,断言"倒液 vs 存物品"结果 🔴
客户端同步链路 开配置 → 退世界 → 重进,断言服务端仍按翻转处理(当前失败) 🟡
配置项 lang 断言每个 AnvilCraftClientConfig 字段在 en_us 与 zh_cn 都有键(防止再漏) 🟡

结论: REQUEST_CHANGES — 主体功能(翻转逻辑、RPC 按键传递、默认行为等价性、en_ud 生成)实现质量很好,但同 PR 内 commit 2 引入的 stamping_table 无效 ID 会让整份 model_selection_denylist 失效(关键,必须修);另建议修掉跨世界同步失效与无 validator 两点,并补齐 zh_cn、把 commit 2 拆出去。


关于 PR 标题gh auth status 显示 token 无效(The token in /opt/data/home/.config/gh/hosts.yml is invalid),按流程不导出凭据、未自动修改标题。当前标题格式合规但英文表述略含糊,建议改为:

Add client config to swap fluid port bucket pour/store mouse actions 增加流体端口桶操作左右键翻转功能

(若采纳 commit 2 拆分,则本 PR 标题即可按上面这条定稿。)

由 Hermes Agent 审查

@PigeonNian PigeonNian changed the title Add functionality to the fluid port bucket to allow for left and right key toggling. 增加流体端口桶操作左右键翻转功能 Add client config to swap fluid port bucket pour/store mouse actions 增加流体端口桶操作左右键翻转功能 Sep 19, 2026
- 将拒绝列表中的 "stamping_table" 更正为 "stamping_platform"
- 修复模型匹配错误,避免加载错误模型
- 保持模型资源的一致性和正确性
@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'cube-selection-pr-review' (1 replacement). · Patched SKILL.md in skill 'minecraft-lang-pipeline-review' (1 replacement). · Patched SKILL.md in skill 'network-packet-pr-review' (1 replacement).

@PigeonNian PigeonNian changed the title Add client config to swap fluid port bucket pour/store mouse actions 增加流体端口桶操作左右键翻转功能 Add client config to swap fluid port bucket pour/store mouse actions, fix tooltips, and added four processing table to denylist 增加流体端口桶操作左右键翻转功能,修正tooltip换行,把四个加工台放入精确拾取黑名单 Sep 19, 2026
@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4940

操作: edited(head 刚更新到 6b95997a58
范围: 11 个文件(8 Java,2 个生成 lang,1 个资源 JSON)/ 610 行 diff(+179 / −52,新增 0,删除 0)
对账: 下载的 patch-diff 与本地 git diff base tip..head(merge-base = base tip = 68d5f625内容零差异,文件列表一致,无 ghost 文件、无截断。

PR 实际包含两个 commit:

  • 2a55581925 feat(storage): 流体端口桶操作左右键翻转(本次主题)
  • 6b95997a58 refactor(tooltip): 装备 tooltip 多行翻译 + model_selection_denylist.json 新增 4 项(与标题无关

✅ 默认配置下的行为等价性(逐路径核对,全部通过)

路径 旧行为 新行为(invertFluidPortBucketAction=false 等价
流体格手持流体桶 左=倒液 / 右=存桶 bucketActionButton()=0 同上
流体格空手 左=装出一桶 carried.isEmpty()→fluidButton=0 同上
Alt+点击 button==0 倒液 shouldPourFluid(id,button) 未翻转时 = button==0
Shift 拖拽 硬编码 pour=true quickMoveButton=0 → true
Shift+双击左键 moveSameToStorage(target,true) 0 → true
PUT 按钮 第一处理器 true / 第二 false deposit(...,0) / (...,1)TexturedButton 第一参数=左键、rightPress=右键)
QUICK_MOVE_TO_STORAGE button == 0 shouldPourFluid 未翻转时相同

takeFluidBucket 去掉 button 并固定倒整叠也是对的:该分支原本只有左键能到达,翻转后若仍按 button == 0 取值会把整叠误减为 1 —— 这个坑避得正确。


🔴 关键

1. 重新登录后客户端/服务端翻转状态失步(InvertedActionEventListener.java:16,33-38 + PlayerEventListener.java:72

客户端只在「配置值发生变化」时上报(bucketInverted != lastBucketInverted),而服务端在玩家登出时主动清空该玩家的条目,且客户端任何地方都没有重置 lastBucketInverted(已全仓 grep 确认:只有 InvertedActionEventListener 自身读写它,ClientEventListener:99 onClientPlayerDisconnect 未清理,也没有 LoggingIn 无条件补发)。

复现:

  1. 开启 invert_fluid_port_bucket_action=true,进世界 —— 首个 tick 上报 true,正常;
  2. 退回标题界面(服务端 loggedOut → clearInvertedBucketAction 删除条目;客户端静态值仍为 true);
  3. 再次进入世界/换服务器 —— 客户端认为「值没变」不上报,服务端表为空,shouldPourFluid 退化为默认的「左键倒液」
  4. 此时界面(直接读本地配置)把倒液键画在右键,但 Alt+左键、Shift 拖拽、PUT 按钮左键在服务端仍按「左键倒液」处理 → 用户本想把桶当物品存入,结果桶里的流体被倒进了端口。

这正是 StorageScreen.java:2450-2453 javadoc 里自己警告的「界面走了存桶分支但服务端按倒液处理」错配。流体格本身侥幸不受影响(takeFluidBucket 已与按键解耦),受损的是普通槽位/Alt/拖拽/PUT 按钮。

建议修(任一):

  • ClientEventListener.onClientPlayerDisconnect 中重置 lastInverted / lastBucketInverted,让重连后的首个 tick 重新上报;
  • 或监听 ClientPlayerNetworkEvent.LoggingIn,每次建立连接无条件上报一次;
  • 注意既有的 invertOverrideActionlastInverted)有完全相同的缺陷,一并修更划算。

⚠️ 警告

2. 新增 RPC 未加 validator,且静态表以客户端 UUID 为键(StorageServerStub.java:167,172-179

private static final Map<UUID, Boolean> INVERTED_BUCKET_ACTION = new HashMap<>();
@RemoteCallable                                  // ← 同类其它 RPC 均为 @RemoteCallable(validator = StorageAccessValidator.class)
public static void updateInvertedBucketAction(UUID playerId, boolean inverted)

清理只在该 UUID 的玩家登出时发生,改装客户端可用任意 UUID 反复调用把这张静态表撑大(内存增长),也能改写他人的翻转状态。BundleLikeServerStub.INVERTED 是同样写法(无 validator 的 HashMap),算既有模式,但新代码至少应补 validator。另:HashMap 由 RPC 处理器写入,若 RPC 不保证在主线程执行应改 ConcurrentHashMap

3. 范围外改动(与标题/描述无关)

  • model_selection_denylist.json:29-32 新增 stamping_platform / crushing_table / sifting_table / unpacking_table(四个 ID 均已注册,JSON 结构合法),但描述里一字未提,评审者无法判断依据;
  • EquipmentArmorItem.java + TooltipUtil.java 的 tooltip 重构同样不在描述内。
  • 建议把 6b95997a58 拆成单独 PR,或至少在描述里补上理由。内容本身没问题:@Getterprivate final boolean weatherproof 生成 isWeatherproof()IonocraftBackpackItem:71 的调用不受影响;addTranslatedLines 主动拆 \n、缺键时 getString() 回退为键名,与旧行为一致。

4. zh_cn.json 缺两个新键
对账结果:anvilcraft.configuration.* 在 zh_cn 中对 en_us 是全覆盖的(273 vs 229,zh_cn 还多),唯一缺的就是本次新增的两个键invert_fluid_port_bucket_action.tooltip 未加入 src/main/resources/assets/anvilcraft/lang/zh_cn.json,中文环境会显示英文。


💡 建议

  • StorageScreen.java:1981this.moveSameToStorage(target, 0) 用魔法数字代替了本作用域可见的 button(该分支只可能 button==0,语义等价)。直接传 button,避免后人误读为「固定左键」。
  • quickMoveButton 生命周期 — 只在拖拽起点赋值(:1990),而 flushQuickMoves 每 tick 都会走(containerTick:992)。当前两条 queueQuickMove 路径都在拖拽中,安全;但字段在拖拽结束/取消时未复位,将来新增一条「不入拖拽直接排队」的路径就会用旧按键决定倒液/存桶。建议收尾处复位,或把按键沿 queueQuickMove 传递。
  • 数量语义随物理键走interactamount = button == 0 ? count : 1:翻转开启后「存入」从右键变左键,「存入整叠」也随之换到左键。配置 tooltip 只承诺换「倒液/存桶」,建议在 tooltip 或注释中点明这一点。
  • 更彻底的方案 — 客户端本来就把实际按键发过来了,把「倒液/存桶」解析成语义参数(pour)随各请求下发即可:服务端不再保存任何玩家配置副本,第 1、2 条问题(失步 + 无校验静态表 + 登出清理)一次性消失。风险面不变(仍受 StorageAccessValidator 保护)。

🟢 看起来不错

  • 默认配置下逐路径等价,向后兼容无回归(见上表)。
  • interact!carried.isEmpty() 分支显式加 slot < StoragePortManager.FLUID_SLOT_BASE,防止翻转配置把「流体格存桶」再翻回倒液 —— 这是本次最容易踩的反向 bug,处理正确(FLUID_SLOT_BASE = 1<<24 已核实)。
  • 流体格键位对「空手装桶」与「倒液/存桶」做了区分,逻辑自洽;FLUID_BUCKETvalidButtons 限制,按键 1 不会触发 isValid 抛异常。
  • 改名后的 RPC 参数(boolean pourint button)调用点已全部同步,无遗漏调用方。
  • 玩家登出清理、注释与 javadoc 质量都很高;en_ud 生成项与 en_us 同步、JSON 结构正确。

📋 声称验证表

PR 描述声称 状态 对应实现
新增客户端配置 invertFluidPortBucketAction AnvilCraftClientConfig.java:44 + en_us/en_ud
客户端事件监听同步配置至服务端 ⚠️ InvertedActionEventListener.java:33-38(重登录失步,见 🔴1)
界面按配置动态判断左右键 StorageScreen.java:1900-1910, 2456-2458
服务端按翻转状态+按键决定倒液/存桶 shouldPourFluid 用于 interact:325,359quickMoveToStorage:515moveSameToStorage:554deposit
重构多处 RPC 新增按键参数 StorageClientStub 4 个方法 + 服务端签名同步
玩家退出时清理翻转状态 PlayerEventListener.java:72
(未声称)装备 tooltip 重构 + denylist 新增 ⚠️ 6b95997a58,超出范围且描述未提,见 ⚠️3
(未声称)zh_cn 翻译 两个新键缺失,见 ⚠️4

🧪 测试建议

被测目标 推荐场景 优先级
StorageServerStub.shouldPourFluid {button 0,1} × {inverted true,false} 四组矩阵断言 🔴
interact / deposit / moveSameToStorage / quickMoveToStorage 默认配置下与改动前逐一等价(用 pour 参数化断言,锁住向后兼容) 🔴
状态同步(集成) 开启配置 → 进世界 → 登出 → 重进 → 断言服务端翻转状态仍为 true(本 PR 的回归点) 🔴
TooltipUtil.addTranslatedLines \n 的键拆成多行、缺键回退为键名、带 %s 参数替换 🟡
EquipmentArmorItem.appendHoverText 四类装备 × weatherproof 的 tooltip 行数与颜色 🟡

结论: REQUEST_CHANGES — 核心设计(把倒液/存桶的判定下沉到服务端、默认配置零回归)是站得住的,takeFluidBucket 与流体格守卫处理得很细;但 🔴1 的重登录失步会造成「想存桶却被倒液」的实际错配,建议连同 ⚠️2 的 validator 一起修掉;⚠️3 的范围外改动建议拆 PR;⚠️4 补上 zh_cn 后即可合并。


gh 未认证(The token ... is invalid),故未修改 PR 标题;标题本身准确描述了功能,无需改动 —— 需要处理的是「把无关 commit 拆出去」而非改标题。

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/client-config-state-sync-review.md in skill 'network-packet-pr-review' (1 replacement). · Patched references/pr-class-routing.md in skill 'github-webhook-pr-review' (1 replacement). · Patched SKILL.md in skill 'network-packet-pr-review' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

Diff re-verified (md5 stable, IDs cross-checked by grep) — all findings confirmed against the target branch source. Here is the review.


代码审查摘要 — PR #4940

操作: edited(标题/描述变更,按 synchronize 级完整审查)
范围: 11 个文件(9 Java,2 生成 lang,1 资源 JSON)/ 610 行 diff,无新增/删除文件,无 EOF 缺失

核心改动理解: 流体端口桶操作(倒液 ↔ 存桶)原本硬编码在左键(button == 0)。本 PR 把「哪一键倒液」交给客户端配置 invertFluidPortBucketAction,由客户端在配置变化时上报(与既有 BundleLikeServerStub.updateInverted 同思路),服务端 shouldPourFluid(playerId, button) 统一判定;顺带把装备 tooltip 的 \n 显式拆行,并给加工台加精确拾取黑名单。

⚠️ 建议修复

1. InvertedActionEventListener.java — 重连/切换世界后配置不再上报,服务端标记永久丢失(客户端与服务端判定正好对调)

private static boolean lastBucketInverted = false;
...
if (bucketInverted != InvertedActionEventListener.lastBucketInverted) {   // 只在“变化”时上报
    StorageClientStub.updateInvertedBucketAction(bucketInverted);
    InvertedActionEventListener.lastBucketInverted = bucketInverted;
}

lastBucketInverted 是客户端静态字段,跨「退出世界/断线」存活;而服务端在 PlayerEventListener.loggedOutclearInvertedBucketAction(uuid) 已清空。于是:

  1. 开翻转 → 进世界(首次进:true != false → 正常上报 ✅)
  2. 退出世界 / 断线(服务端 map 清空)
  3. 再次进入世界 → 客户端认为「没变化」→ 不上报 → 服务端 map 无记录 → shouldPourFluid 退回 button == 0

结果:客户端仍按「右键倒液」渲染/派发,服务端按「右键存桶」处理,两个键的角色恰好对调——正是本 PR 注释里警告的「界面走了存桶分支但服务端按倒液处理」的错配。单机退出到标题界面再进另一个存档同样触发。

建议修法(三行):在 ClientEventListener.onClientPlayerDisconnect(或 LevelEvent.Unload)里把 lastBucketInverted 重置为 false,强制下次进入世界重发;或进入世界时无条件重发一次。顺带一提,既有 BundleLike 路径的 lastInverted 有完全相同的缺陷,可以一并修。

2. StorageServerStub.interact() — 翻转后「存桶」的数量语义也随之改变,与配置描述“swap them”不一致

boolean pour = slot < StoragePortManager.FLUID_SLOT_BASE && shouldPourFluid(playerId, button);
int amount = button == 0 ? carried.getCount() : 1;   // 仍按物理键取值
int poured = pour ? pourIntoFluidPort(player, view, carried, carried.getCount()) : 0;

倒液固定整叠没问题(旧代码只有 0 键能走到 takeFluidBucket,等价)。但 amount 仍绑在物理键上,导致:

场景 默认 开翻转
存桶键(流体格上把满桶当物品存入) 右键,存 1 个 左键,存 整叠
倒液键倒不进去时的回退存入 左键,存 整叠 右键,存 1 个

也就是说「翻转」不只是换键,还把两键的整叠/单个分配一起换掉了(对同一动作而言并非纯交换)。注释“也不改变各键的数量语义”按物理键读是对的,但用户看到的配置描述是 swap them。请确认是否有意为之;若只想换键,amount 应改为按 pour(动作)取值,或在注释/描述里写明数量也随键对调。

💡 建议

3. StorageScreen.mouseClicked() — 守卫条件与注释不符
注释写「手持流体桶时才可能涉及倒液还是存桶」,代码却是 this.carried.isEmpty() ? 0 : bucketActionButton()——手持空桶(或任意物品)也会切到翻转键。开翻转后,手持空桶左键点流体格会走进物品分支,把空桶当物品存进仓储(默认不翻转时左键是「装一桶」)。建议守卫改为「手持能容纳该流体的容器」判定,或明确写入手册。

4. 新增的 button 管线实际恒为 0
quickMoveButton 只在 else if (this.carried.isEmpty()) 分支赋值,而 if (button == 1) 在前短路(Shift+右键走 interact(..., QUICK_MOVE_TO_STORAGE)),外层又限定 button == 0 || button == 1,所以 button 恒为 0。quickMoveToStorage 的第三个参数、以及 deposit(..., 0|1)moveSameToStorage(target, 0) 里的魔数都属于多余的显式传参,建议改用具名常量(如 MOUSE_LEFT/MOUSE_RIGHT)以对齐语义;若本意是将来支持右键拖拽,建议在注释里写明当前不可达。

5. TooltipUtil.addTranslatedLines() — 请说明与语言文件现状的关系
装备 tooltip 确实含 \nbreathing/buffer_boots/pockets/recharge),"修正换行" 的说法有依据。但两点想确认:

  • 新实现用 Component.literal(line) 取代了可翻译组件,丢掉了翻译组件身份与参数样式(本例参数是 int,暂时无影响),后续若有带样式/事件的参数会静默降级;
  • 语言文件里仍有约 70 条含 \n 的 tooltip 键走原路径(如 tooltip.anvilcraft.item.stamping_platform.shifttooltip.anvilcraft.item.amulet_box.shift)。若「换行会失效」的判断成立,这些同样受影响,本 PR 只是局部修复;若原版本就会处理 \n,则此处属等价重构,建议在描述里说明原因以省去后续疑惑。

6. updateInvertedBucketAction 缺少 validator,静态表以客户端上报的 UUID 为键
BundleLikeServerStub.updateInverted 同样如此(属既有先例),但该表无界、只在退出时清理,未校验的客户端可写入任意 UUID 使静态表增长。建议加一个轻量 validator,或直接以连接对应的玩家为准而非信任报文里的 UUID。

🟢 看起来不错

  • 默认(不翻转)路径行为等价shouldPourFluid(id, 0) == (button == 0) 与旧逻辑逐点核对一致;deposit / moveSameToStorage / quickMoveToStorage / interact 的默认分支均与 base 一致;takeFluidBucket 去掉 button 参数、改传 carried.getCount() 也等价(旧代码只有 0 键能进该方法,amount 恒为整叠)。
  • StorageInput.FLUID_BUCKETvalidButtons == null → 接受任意键,翻转后的右键不会撞 isValid(button) 校验 ✅
  • 状态清理与登录期安全PlayerEventListener.loggedOut 清理 ✅;新 RPC 只写静态表、不做玩家实体查找,登录早期到达也不会 NPE ✅
  • 四个加工台的精确拾取黑名单 ID 全部存在stamping_platform(不是 stamping_table)、crushing_tablesifting_tableunpacking_table,与 block.anvilcraft.* 语言键一致。⚠️ 这点尤其重要:ModelSelectionDenylist.readBlocks() 对未知 ID 是 BuiltInRegistries.BLOCK.getOptional(id).orElseThrow(...),异常被 reload()catch (IOException | RuntimeException) 吞掉后 rules 会退回 EMPTY——即一个错 ID 会让整份 denylist(含所有炼药锅、管道、红石线等)静默失效。本 PR 的 ID 是对的,但建议给该加载器加一个单测/启动断言,避免以后手滑。
  • lang 一致性en_us/en_ud 各新增 2 个键、字符数完全相等(1:1 翻转+反转变换)、无其它键被修改;@Comment 拼接结果与生成的 .tooltip 值逐字一致 ✅
  • EquipmentArmorItem@Getter 取代手写 isWeatherproof() 签名不变,调用方无需改动 ✅
  • 没有其它调用方受影响:StorageClientStub.deposit/moveSameToStorage/quickMoveToStorage 全仓库仅 StorageScreen 调用(StoragePortHammerGestureListenerTerminalKeyMappingEventListenerStorageMenuStorageTerminalClientStub 均无调用),RPC 签名变更范围可控 ✅

📋 声称验证表

声称 状态 证据
新增客户端配置 invertFluidPortBucketAction AnvilCraftClientConfig + 两个生成 lang(键名/注释一致)
流体端口桶操作左右键互换 ✅ / ⚠️ shouldPourFluid + 客户端上报;默认路径等价,但重连后不再上报(问题 1)
修正 tooltip 换行问题 4 条装备 tooltip 确含 \naddTranslatedLines 逐行拆分(见建议 5)
给四个加工台添加精确拾取黑名单 4 个 ID 均存在于 ModBlocks,且写入 disable_precise_picking 数组

结论: COMMENT — 主逻辑(默认路径等价、翻转语义、lang/黑名单数据)核对无误,可合并;但建议合并前修掉问题 1(重连丢配置会导致客户端与服务端键位对调),问题 2/3 请确认是否有意为之。PR 描述只提了配置项,建议补一句「顺带修正装备 tooltip 换行 + 加工台精确拾取黑名单」,方便回查。

关于 PR 标题: gh auth status 显示 token 失效(The token in .../hosts.yml is invalid),按约定不导出凭据、未自动改标题。现标题「Add client config to swap fluid port bucket pour/store mouse actions 增加流体端口桶操作左右键翻转功能」中英双语且准确,可保留。

🧪 测试建议

被测目标 推荐测试场景 优先级
StorageServerStub.shouldPourFluid 未上报(map 无记录)/ 上报 true / 上报 false × button 0、1、非法值(如 2) 🔴
重连一致性 开翻转 → 进世界 → 退出 → 再进世界,验证键位仍与配置一致(当前会失败) 🔴
interact × deposit × moveSameToStorage × quickMoveToStorage button×配置 2×2 组合矩阵:满桶/空桶/空手 × 流体格/物品格/背包格 🟡
ModelSelectionDenylist.reload 未知 ID / 非 anvilcraft 命名空间 / 缺字段时是否整份丢弃(建议断言而非仅 warn) 🟡
TooltipUtil.addTranslatedLines 单行 / 多行 / 带 %s 参数 / 翻译键缺失 🟢

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

rm -f /opt/data/workspace/check_pr4940.py /opt/data/workspace/check_pr4940b.py /tmp/h.txt /tmp/headers.txt 2>/dev/null; echo cleaned

Reason: Security scan — [CRITICAL] Mass file deletion in a short window: 5 non-build files were deleted within 20s. A burst of deletions can be destructive (ransomware-like or an accidental recursive wipe).

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4940

操作: edited
范围: 11 个文件(9 Java + 2 生成 lang + 1 资源 JSON)/ +179 −52
复核基线: HEAD 46dcafd6640359991cc53922c63f5100a4c2ad38git ls-remote refs/pull/4940/head 一致;base 68d5f625)。CI build ✅ / checkstyle ✅。

⚠️ 审查提示(非 PR 问题):patch-diff.githubusercontent.com 首次返回的是旧版 diff(denylist 里写的是不存在的 anvilcraft:stamping_table),复核时已发现并改按 head 内容审查;当前 head 已是 stamping_platform(见下)。

📋 声称验证表

声称 状态 依据
新增客户端配置 invertFluidPortBucketAction 控制左右键互换 AnvilCraftClientConfig(新字段)→ InvertedActionEventListener(每 tick 上报变化)→ StorageServerStub.INVERTED_BUCKET_ACTION / shouldPourFluid;客户端 StorageScreen#bucketActionButton 两端判定一致
修正 tooltip 换行 TooltipUtil#addTranslatedLines(按 \n 拆行);被改的 6 个护甲键中 breathing / pockets / buffer_boots 确实含 \n(en_us 与 zh_cn 均有),其余为等价重写
四个加工台加入精确拾取黑名单 model_selection_denylist.json 新增 stamping_platform / crushing_table / sifting_table / unpacking_tableen_us 中名称以 "Processing Table -" 开头的方块恰好这四个,无遗漏

🔴 关键

  • 未发现阻塞性问题。两处最容易踩的坑作者都处理了(见 🟢)。

⚠️ 警告

  1. 翻转并非「纯动作互换」:存桶的入库数量会随物理键变化StorageServerStub.java:361
    int amount = button == 0 ? carried.getCount() : 1; 决定「物品存入」的个数:默认(右键存桶)一次存 1 个;开启翻转后存桶键变成左键 → 一次存 整叠。流体格上的物品分支同理(StorageScreen.java:1908 传 button,interact 用同一 amount)。
    客户端 javadoc 明确写了「不改变各键的数量语义」(有意为之),但配置 tooltip 只说 "swap them",用户会预期是纯互换。建议二选一:把 amount 改为跟随动作(存桶恒 1 个),或在 tooltip 里补一句「左/右键的数量语义(整叠/单个)保持不变」。

  2. 翻转后失去「拖拽批量倒液」StorageScreen.java:2142-2147 / 1989-1990
    拖拽只能由 Shift+左键启动(if (button == 1) ... else if (carried.isEmpty()),故 quickMoveButton 恒为 0),且 mouseDragged 只在 button == 0 && hasShiftDown() 时收集槽位 → 服务端 quickMoveToStoragepour 在翻转后恒为 false。结果是:开启翻转后批量倒液只能逐格 Shift+右键(Shift+右键确实会走 QUICK_MOVE_TO_STORAGE → 服务端倒液 ✅),拖拽批量倒液不再可用。若这不是有意取舍,需要让拖拽支持右键启动/收集,或在文档注明。

  3. updateInvertedBucketAction 缺少身份校验StorageServerStub.java:171-175
    本仓库其余 65 处 @RemoteCallable 都带 validator;只有它与既有的 BundleLikeServerStub#updateInverted 是无 validator 的。而 StorageAccessValidator.isValid 明确在做身份校验(player.getGameProfile().getId().equals(args[0])),说明框架不会自动校验第一个 UUID 参数 —— 即任意客户端可上报 (他人UUID, true) 翻转别人的倒液键(影响面仅该布尔值,属轻微骚扰)。
    注意:StorageAccessValidator 无法直接复用(它要求 args[1]long sourcePos),建议加一个只校验「args[0] == ctx.player」的轻量 validator,顺带修掉同款既有问题。

💡 建议

  • quickMoveButton 是恒为 0 的死参数StorageScreen.java:338/1990/2389。要么直接传 0 并注明「拖拽仅左键可启动」,要么补齐右键拖拽(与 ⚠️2 同一处)。
  • 与既有实现重复ItemTooltipManager#addTranslatedTooltip(≈L1107,注释「添加翻译后的tooltip,自动将 \n 拆分为多行」)已是同款「\n 拆行 + literal + GRAY」。建议把新工具作为唯一实现,让 ItemTooltipManager 改为调用它(它用 I18n.get,新的用 Component.translatable(...).getString(),二者继续并存会漂移)。
  • addTranslatedLines 的副作用请在 javadoc 写明TooltipUtil.java:41-57getString() + literal 会把组件扁平化(丢 translatable 身份与嵌套样式/富文本参数),并在调用侧就按当前语言解析完成。当前 6 个键均为单一样式纯文本(参数只有 1 个 int),行为等价 ✅;但将来这些键若引入带样式参数,样式会被静默丢弃。建议加一句「仅适用于纯文本 + 单一样式」的限制说明。
  • zh_cn 缺新增配置项翻译anvilcraft.configuration.invert_fluid_port_bucket_action(.tooltip) 只进了生成文件 en_us/en_udzh_cn.json 里同族的 invert_override_actionamulet_box_invert_override_actionlocal_terminal_invert_override_action 等都有中文,中文玩家会看到英文回退(weblate 后续补齐,非阻塞)。
  • denylist 依旧是「一错全废」ModelSelectionDenylist#readBlocks 对未知 ID 抛 IllegalArgumentExceptionreload catch 后保留的是低优先级列表 —— 只有一个内置资源时等于整份规则被清空(三个列表全失效,仅剩 WARN 日志)。本次 head 已把 stamping_table 修正为真实存在的 anvilcraft:stamping_platformModBlocks:402)✅,建议顺手加一条 ID 存在性校验/单测(或让单个坏条目只跳过自身),避免以后再出现「改了 JSON 却静默失效」。
  • 是否也需要 disable_model_outline 四个加工台只进了 disable_precise_picking。看 ModelBlockSelection#highlight:未列入 outline 名单时,originalPicking == true 不会提前 return,方块仍会走本 mod 的模型轮廓绘制(大釜等则同时出现在两个列表里)。如果加工台只是「精确拾取目标」需要退回原版、模型轮廓保留没问题,那当前写法正确;若轮廓本身也画得不对,需要一并加入 outline 名单。请确认。
  • 终端界面行为与配置不一致StorageServerStub#terminalInsert(终端「存入」)无条件先 pourIntoFluidPort 再按物品存入,不读 shouldPourFluid。tooltip 已限定 "In the storage screen",符合现状;若希望终端也给用户选择权,需要另开一处。
  • EquipmentArmorItem@Getter weatherproof 重构(等价,Lombok 生成同名 isWeatherproof())与本 PR 主题无关,可考虑拆出,减少审查面。

🟢 看起来不错

  • takeFluidBucket 的修正是必要的:翻转后该分支会被 button == 1 进入,若仍按鼠标键取数量会把整叠误减为 1 个,改为恒 carried.getCount() 正确(StorageServerStub.java:4988-5000)。
  • 流体格物品行为分支加了 slot < StoragePortManager.FLUID_SLOT_BASE 守卫(:356-359),阻止翻转配置把「存桶」又变回「倒液」;而客户端 interactWithStorage 对流体伪槽位是原样上报(StorageScreen.java:2532),守卫判定成立,这条链路是对的。
  • StorageInput.FLUID_BUCKETvalidButtons == null(任意 button 有效),所以翻转后 button == 1 不会触发 isValid 抛异常 —— 这个隐藏地雷避开了。
  • INVERTED_BUCKET_ACTIONPlayerEventListener#loggedOut 中随 BundleLikeServerStub.clear 一并清理,静态表无 UUID 泄漏 ✅;单线程服务端 + 每次点击读取,无并发问题。
  • lang 生成文件:新增键与 @Comment 两行拼接结果逐字符一致en_ud 的反转(含 等字形)与既有条目风格一致 ✅。
  • 所有变更签名(deposit/moveSameToStorage/quickMoveToStorageboolean pourint button)的调用方已全部同步,无残留 true/false 调用点。

🧪 测试建议(仓库暂无 src/test,可放 gametest/单测)

被测目标 推荐场景 优先级
shouldPourFluid 未上报 / 上报 false / 上报 true × button 0/1 四种组合 🔴
interact(流体格, 存桶键) 翻转开启时流体格 PICKUP 分支不得倒液(FLUID_SLOT_BASE 守卫回归测试) 🔴
takeFluidBucket 翻转后(button=1)倒液仍为整叠,不退回 1 个 🟡
TooltipUtil.addTranslatedLines \n、无 \n、带 %s 参数、含空行 🟡
ModelSelectionDenylist.readBlocks 未知方块 ID 时应整体失败(记录当前语义)而非静默半残 🟡
StorageServerStub#clearInvertedBucketAction 玩家退出后表内条目被移除 🟢

结论: COMMENT — 功能实现正确、两端判定自洽,无阻塞问题。建议至少处理 ⚠️1(补 tooltip 说明或改数量语义)与 ⚠️3(补身份校验),其余为可选改进。

标题建议(未自动修改)

gh auth status 显示 /opt/data/home/.config/gh/hosts.yml 中 token 已失效(未按约定导出凭据),因此自动执行 gh pr edit。当前标题语法略有瑕疵("added four processing table to denylist"),建议改为:

Add fluid port bucket action inversion config, fix armor tooltip line breaks, and add four processing tables to the denylist 新增流体端口桶操作翻转配置,修正护甲 tooltip 换行,并将四个加工台加入精确拾取黑名单

由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'client-server-side-separation-review' (1 replacement). · Patched SKILL.md in skill 'pr-fix-claim-verification' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'minecraft-lang-pipeline-review' (1 replacement). · Patched SKILL.md in skill 'pr-diff-scope-reconciliation' (1 replacement).

@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4940

操作: synchronize
范围: 11 个文件(9 Java + en_us/en_ud 生成语言文件 + 精确拾取黑名单 JSON)/ +171 −51 / 无新增、无删除、无 EOF 缺换行
分支: fluidre/1.21/1.6dev/1.21/1.6(1.21.6 常规 feature PR,非 26.1 迁移,跳过 API 迁移健康度检查)


✅ 关键结论(已逐项核实)

1. 默认行为完全向后兼容 —— 三处"硬编码键位"改写均等价

位置 等价性
takeFluidBucket amount = button == 0 ? count : 1 固定 carried.getCount() 基线中 button == 1 在客户端早返回走 PICKUPFLUID_BUCKET 只可能带 button 0 → 旧值与 getCount() 恒等 ✅
shouldPourFluid button == 0 TRUE.equals(map.get(id)) ? button == 1 : button == 0 表内无记录(默认/未翻转)→ button == 0
deposit(0) / deposit(1) true / false 0 / 1 TexturedButton.onClick:button 1 → rightPress、其余 → onPress,字面量与物理键一一对应 ✅

2. 最容易踩的坑已排除 —— StorageInput.isValid(button)
FLUID_BUCKETQUICK_MOVE_TO_STORAGE 使用默认构造器 → validButtons == nullisValid() 对任意键返回 true。翻转后客户端用右键FLUID_BUCKETinteract() 开头会校验 action.isValid(button) 并抛 IllegalArgumentException不会触发异常 ✅。若这两个枚举带 new int[]{0} 白名单,本 PR 会直接炸服,已确认无此问题。

3. 键位矩阵客户端/服务端自洽

场景(手持流体桶) 客户端发送 服务端判定 结果
流体格 · 倒液键 FLUID_BUCKET 该分支不读配置,固定整叠倒液 倒入 ✅
流体格 · 存桶键 PICKUP slot < FLUID_SLOT_BASE 为 false → 永不倒液 存桶 ✅
流体格 · 空手 固定 button 0 取桶分支 键位不随配置变 ✅
物品格 ALT / Shift / 存入按钮 透传实际键 shouldPourFluid(playerId, button) 与客户端 bucketActionButton() 同一判据 ✅

StoragePortManager.FLUID_SLOT_BASE = 1 << 24slot < FLUID_SLOT_BASE 判真实槽位成立 ✅。

4. 黑名单(重点核对)
ModelSelectionDenylist.readBlocks 对未知 id 抛 IllegalArgumentException,而 reload 会 catch 异常并保留低优先级名单 —— 一个错 id 会让整份名单静默失效。四个 id 已与 ModBlocks 逐个核对:STAMPING_PLATFORM(402)、CRUSHING_TABLE(417)、SIFTING_TABLE(438)、UNPACKING_TABLE(459) 全部存在 ✅。只加入 disable_precise_pickingdisable_model_outline 未动:与既有 picking-only 条目(trading_station / heavy_iron_column / overflow_chute)一致 —— usesOriginalOutline 只看 outline 名单,故这四张加工台保留模型轮廓、仅拾取回退原版,符合预期 ✅。

5. 生成资源一致性

  • .tooltip 值与 AnvilCraftClientConfig#@Comment 拼接结果逐字符相同(203 字符)→ 手改 generated 文件与 runData 产物一致,无 datagen 漂移 ✅
  • en_ud 用「既有条目反推字符映射」逐位校验:invert_fluid_port_bucket_action 31 字符、.tooltip 203 字符,长度一致、0 处不匹配
  • 标签 "Invert Fluid Port Bucket Action" 与同类无 @SerializedName 字段的自动标签规则(heliostats_render_distance → "Heliostats Render Distance" 等)一致 ✅
  • 新增行无 >140 字符的 Java 行、无 tab/行尾空格;CustomImportOrder 组内 ASCII 升序正确(…client.AnvilCraftClient < …client.gui…dev.dubhe… < lombok.Getter < net.minecraft…)——满足 style_check.yml 的 checkstyle + reviewdog -fail-level=any
  • @Getter 替换手写 isWeatherproof():仓库无 lombok.config,默认 accessor 即 isWeatherproof()IonocraftBackpackItem:71 调用不受影响 ✅

⚠️ 警告

W1. 配置只在"值变化时"上报 → 重新进入世界/服务器后静默错配(真实功能缺陷)

InvertedActionEventListener.lastBucketInverted客户端静态字段,跨存档/跨服务器不复位;服务端 INVERTED_BUCKET_ACTIONloggedOut 会清空。复现路径:

  1. 开启 invertFluidPortBucketAction → 客户端上报 true → 服务端表内有记录;
  2. 退出世界/服务器 → 服务端 clearInvertedBucketAction 清空,客户端静态 lastBucketInverted 仍为 true
  3. 重新进入 → bucketInverted == lastBucketInverted不重发 → 服务端无记录 → shouldPourFluid 退化为 button == 0,而客户端 UI 仍按 bucketActionButton() == 1 把右键当倒液键;
  4. 结果:右键倒液静默变成"把桶当物品存入",直到玩家手动关开一次该配置才恢复。

建议:登录/进入时补发一次(PlayerLoggedInEvent,或打开存储界面时无条件上报),或让 RPC 幂等并在 ClientPlayerNetworkEvent.LoggingIn 无条件发送。(既有 invertOverrideAction/BundleLike 是同一模式、同一缺陷,可一并修。)

W2. 翻转后失去「Shift+左键拖拽批量倾倒」路径

quickMoveButton 实际恒为 0(拖拽只在 Screen.hasShiftDown() && carried.isEmpty() && button != 1 分支启动,mouseDragged 也只在 button == 0 时累积 quickMoveDrag),故翻转时 shouldPourFluid(0) == false → 拖拽一律存桶;而右键不触发拖拽 → 批量倾倒仅剩「右键存入按钮」(deposit button 1)。若确为设计(PR 注释确实这么写),建议在配置 tooltip 中说明"翻转后拖拽不再倾倒,请用右键存入按钮批量倾倒";否则需为拖拽单独保留倾倒语义。


💡 建议

  1. 与既有实现重复ItemTooltipManager.addTranslatedTooltip(同文件 1109 行附近)已在做"\n 拆行",用 I18n.get(key)tooltip.add(1, …) 让物品名保持在首行。两套拆行实现日后必然分叉,建议 addTranslatedLines 做成通用版(增加 color / 插入位置参数),ItemTooltipManager 改为委托调用。
  2. 公共工具方法的样式损失Component.translatable(key, args).getString() 会拍平翻译中的样式与嵌套 component —— 带 § 颜色码或嵌套参数的翻译经 Component.literal(line) 后会丢色。(本次 6 个 equipment 键均不含 §,已核对,当前无实际影响;但方法公开可复用,建议注释该限制。)
  3. zh_cn 未补新配置翻译zh_cn.jsonanvilcraft.configuration.* 共 273 条,en_us 的 229 个配置键目前只有本次新增的两个缺失(其余全部有中文)→ 中文客户端会看到英文标签 + 英文长 tooltip。若走 Crowdin(crowdin.yml 仅在 releases/1.21 推送 en_us)可等同步,否则建议补:
    • anvilcraft.configuration.invert_fluid_port_bucket_action: "流体端口桶操作翻转"
    • …invert_fluid_port_bucket_action.tooltip: "在存储界面中交换流体端口的桶操作:默认左键把手中的流体桶倒进流体端口、右键把桶作为普通物品存入;开启后两者互换"
  4. 过期 javadocStorageServerStub#takeFluidBucket(约 4975 行)仍写"右键不走这里,而走原有物品行为",翻转后右键正是走这里,与上方新增的 <p> 说明自相矛盾,建议删掉这半句。
  5. 覆盖完整性(正向):本次已把 tooltip.anvilcraft.equipment.* 全部 4 个含 \n 的键纳入拆行;en_us 中其余含 \n 的 tooltip 只有 tooltip.anvilcraft.item.*(67 个,早已由 ItemTooltipManager 拆行)→ 物品 tooltip 侧已无遗漏。剩余 screen.anvilcraft.cfa.*(14) / building_rod.*(4) / button.*(1) 属界面文本(如 AdvancedComparatorScreen:103screen.anvilcraft.button.redstone_control),是否同样丢换行取决于各界面绘制方式,可另开 PR 核查。
  6. 手工验证矩阵(仓库无 src/test/gametest 源集,CI 仅 ./gradlew build + dedicated-server 启动测试,行为矩阵无法自动覆盖):
场景 期望
默认 · 流体格 手持桶 左键 / 右键 倒液整叠 / 桶作为物品入库
默认 · 流体格 空手 左键 装出一桶
翻转 · 流体格 手持桶 右键 / 左键 倒液整叠 / 桶作为物品入库
翻转 · 流体格 空手 键位不变(左键装桶,右键无动作)
翻转 · ALT 点击 / Shift+右键 / 存入按钮左右键 倒液键=右键,另一键存桶
翻转 · 退出重进世界 ⚠️ 目前会错配(见 W1)

🟢 看起来不错

  • "倒液还是存桶"的判定统一收敛到 shouldPourFluid,客户端注释与服务端 javadoc 互相引用,从设计上封住了"界面走存桶、服务端按倒液处理"的错配 —— 方向正确。
  • 流体格分支显式用 slot < FLUID_SLOT_BASE 守卫,避免翻转把"存入"又变回倒液。
  • INVERTED_BUCKET_ACTION 静态表在 PlayerEventListener.loggedOut 有清理,未引入静态 UUID 表泄漏。
  • 生成语言文件与 datagen 产物一致,en_ud 反向映射零误差。
  • 无调试残留、无 TODO/FIXME、无硬编码凭据。

📋 声称验证表

声称 状态 证据
新增客户端配置 invertFluidPortBucketAction 控制流体端口桶操作左右键互换 AnvilCraftClientConfig + 字段;en_us/en_ud + 2 键(与 @Comment 逐字符一致);StorageScreen.bucketActionButton()StorageServerStub.shouldPourFluid() 同一判据;默认值 false 与旧行为等价
修正了 tooltip 换行问题 ⚠️ 已修但范围限于装备护甲,且与既有实现重复 TooltipUtil.addTranslatedLines 新增,EquipmentArmorItem 6 处改用;tooltip.anvilcraft.equipment.* 4 个含 \n 键全覆盖;其余含 \n 的 item tooltip 原本已由 ItemTooltipManager 拆行。建议合并两套实现(建议 1);screen.* 多行文本未处理(建议 5)
把四个加工台放入精确拾取黑名单 disable_precise_picking +4 条;4 个 id 与 ModBlocks 注册逐一核对存在(否则整份名单会被 readBlocks 抛异常丢弃);disable_model_outline/disable_ber_selection 未被误动

结论: COMMENT —— 主体实现正确、默认行为向后兼容、生成资源与黑名单均已交叉核对无误;建议先处理 W1(重连重发,改动很小)或至少注释为已知限制,并按需采纳 W2 / 建议 1、3、4。

PR 标题建议(未自动修改)gh auth status 显示 token 失效(HTTP 401: Bad credentials),故未执行 gh pr edit。当前标题 "…and added four processing table to denylist" 语法有误(应为 tables / add),建议改为:
"Add client config to invert fluid port bucket actions, fix tooltip line wrapping, and denylist four processing tables 新增流体端口桶操作左右键翻转配置、修正 tooltip 换行、四个加工台加入精确拾取黑名单"


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'cube-selection-pr-review' (1 replacement). · Patched SKILL.md in skill 'minecraft-lang-pipeline-review' (1 replacement). · Patched SKILL.md in skill 'menu-widget-interaction-pr-review' (1 replacement).

- 在模型选择拒绝列表中添加了 fish_tank 模型
- 确保 unpacking_table 项目后有正确的逗号分隔符
- 维护 JSON 格式的正确性与一致性
@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4940

操作: synchronize(Reopen/新提交,分支 PigeonNian:fluidre/1.21/1.6dev/1.21/1.6,head cd65210f,base 68d5f625
范围: 11 个文件(8 个 Java,0 新增 / 0 删除)/ +172 −51 行,无 EOF 缺行、无调试残留

✅ 已核对通过的点(先说结论)

核对项 结果
客户端/服务端键位映射一致性 StorageScreen.bucketActionButton()invert ? 1 : 0)与 StorageServerStub.shouldPourFluid()inverted ? button==1 : button==0)语义完全同构
流体格守卫 slot < FLUID_SLOT_BASE FLUID_SLOT_BASE = 1 << 24(StoragePortManager:50),伪槽位恒大于真实槽位,守卫方向正确,避免了「翻转把存桶又变回倒液」
整叠倒液修复 ✅ 原按 button 取 amount,翻转后会把整叠误减为 1 个;现在 pourIntoFluidPort(..., carried.getCount()),并保留左键整叠/右键 1 个只用于物品存入
旧接口迁移完整性 boolean pourint button 的三个 @RemoteCallablemoveSameToStorage / deposit / quickMoveToStorage)与全部调用点均已同步,无残留布尔调用;takeFluidBucket 去掉无用 button 参数后仅剩 1 个调用点
StorageInput.isValid(button) FLUID_BUCKETvalidButtons == null(任意键合法),翻转后客户端上报 button=1 不会触发 interact 开头的 IllegalArgumentException
静态表清理 INVERTED_BUCKET_ACTIONPlayerEventListener.loggedOutclearInvertedBucketAction(:72),无泄漏
denylist 4 个加工台 ID ✅ 与注册名逐字核对一致:stamping_platform(ModBlocks:402) / crushing_table(:417) / sifting_table(:438) / unpacking_table(:459);放入 disable_precise_pickingtrading_stationoverflow_chuteheavy_iron_column 这类「机器类仅入 picking 名单」的既有先例一致
en_ud 镜像行 ✅ 新增两行与 en_us 逐词长度序列反转完全匹配(词数 5/5、36/36),非手写错位;@Comment 拼接结果与 en_us tooltip 逐字一致
tooltip 换行修复覆盖面 ✅ 全库 91 个含 \n 的 lang 值中,只有 equipment 那几个是直接 tooltip.add(Component.translatable(...)),其余(tooltip.anvilcraft.item.*.shift)都走 ItemTooltipManager.addTranslatedTooltip——这次修复恰好补上了唯一的漏洞点
@Getter 替换手写 getter ✅ Lombok 对 boolean weatherproof 生成 isWeatherproof(),唯一调用点 IonocraftBackpackItem:71 不受影响(无 lombok.config 改写 is 前缀)

🔴 关键

1. client/event/InvertedActionEventListener.java + rpc/StorageServerStub.java — 重连后翻转配置不再上报,界面与服务端判定相反

变更检测用的是客户端静态字段,服务端状态却在退出时被清掉,两者生命周期不一致:

  • 客户端:lastBucketInverted 是静态字段(:16),只在 onClientTick 里「值变了才发」(:35-38)。静态字段在同一游戏进程内跨世界重连不会复位
  • 服务端:clearInvertedBucketAction(player.getUUID())PlayerLoggedOutEvent 里清空表项(PlayerEventListener:72)。

复现路径:开启 invertFluidPortBucketAction → 客户端上报 true(服务端表项=true)→ 退出存档/服务器(服务端表项被删除,客户端 lastBucketInverted 仍为 true)→ 重新进入同一服务器 → 客户端认为「无变化」永不重发 → 服务端 shouldPourFluid 恒为 button == 0,而界面按 bucketActionButton() == 1 走按键分流。后果正是方法注释里自己警告的那种错配:流体格上「存桶」键被服务端当「倒液」处理、Alt+点击/存入按钮的键位语义整体反了,且无法自愈(除非再手动来回切一次配置)。

建议修法(改动很小):在客户端 ClientPlayerNetworkEvent.LoggingOut 里把两个静态哨兵复位,使下一次进入世界时重新上报:

@SubscribeEvent
public static void onLogout(ClientPlayerNetworkEvent.LoggingOut event) {
    // 配置为 true 时,下一 tick 会因不等而重新上报;为 false 时服务端默认值本就一致
    InvertedActionEventListener.lastInverted = false;
    InvertedActionEventListener.lastBucketInverted = false;
}

注:既有的 BundleLikeClientStub.updateInverted / lastInverted 是同一缺陷(BundleLikeServerStub.clear + PlayerEventListener.loggedOut),本 PR 只是照抄了这个模式;顺手一并修掉更划算。

⚠️ 警告

2. model_selection_denylist.jsonanvilcraft:fish_tank 重复添加(PR 描述说「四个加工台」,实际多加了一条已存在的项)

disable_precise_picking 第 4 项本来就有 anvilcraft:fish_tank(base 分支同此),PR 在数组末尾又追加了一次,该数组出现 31 项、其中 1 项重复。运行时无害(ModelSelectionDenylist.readBlocks 收敛进 Set<Block>),但属于必删的冗余行——很可能作者把它误当作第 5 个「加工台」。真正的 4 个加工台(stamping/crushing/sifting/unpacking)ID 均正确。

3. src/main/resources/assets/anvilcraft/lang/zh_cn.json — 新增配置项缺中文翻译(en_us 的 configuration 键中仅此 2 个在 zh_cn 缺失)

zh_cnanvilcraft.configuration.* 的覆盖率本来就是 100%(另外 271 个键全部有译文),本 PR 引入了仅有的两个缺口,中文玩家会在配置界面看到英文名和英文 tooltip。按仓库既有风格建议补:

"anvilcraft.configuration.invert_fluid_port_bucket_action": "流体端口桶操作翻转",
"anvilcraft.configuration.invert_fluid_port_bucket_action.tooltip": "在存储界面交换流体端口的桶操作:默认左键将手持流体桶倒入流体端口、右键把桶作为普通物品存入;开启后两者互换"

💡 建议

4. util/TooltipUtil.addTranslatedLines 与既有实现重复。 ItemTooltipManager.addTranslatedTooltip(:1111-1117)已经是「I18n.get + split("\n") 逐行 add」的同一逻辑。建议把后者改为委托 TooltipUtil,避免两套换行实现各自漂移。注意二者插入位置不同:既有实现是 tooltip.add(1, ...) 插到开头,新的 helper 是追加到末尾——复用时需确认组装顺序(armor tooltip 这里追加到 super.appendHoverText 之后是符合预期的)。

5. 过时注释。 StorageServerStub:4978 的「右键不走这里,而走原有物品行为」在翻转后已不成立(新增段落已说明键位随配置变,但旧句未删,前后矛盾);StorageInput.FLUID_BUCKET 的枚举注释「用背包中的空桶装一桶取出」也不再完整——现在它同时承担「倒入指针上的容器」,只有空手时才是取桶。建议同步更新。

6. StorageScreen.quickMoveButton 实际恒为 0。 Shift 拖拽只能从 button == 0 的那个分支进入(button == 1 在前面被 interactWithStorage(..., QUICK_MOVE_TO_STORAGE) 分流,见 :1985-1996),所以拖拽路径上的新参数永远传 0,翻转开启时拖拽一律「存桶」。这与注释里的声明一致,但既然参数值恒定,要么简化为常量并注明,要么确认是否希望拖拽也受某键驱动。另外该字段在拖拽结束后不复位,未来若有别的入口复用 quickMoveSlots 会读到上一次的脏值,建议在 mouseReleased 的拖拽收尾处一并清零。

🟢 看起来不错

  • 键位翻转的「单一职责」切得很干净:配置只管「倒液 vs 存桶」这一个取舍,明确不动流体格空手取桶的键位、也不动各键数量语义,且客户端与服务端用同一套表达式,注释里主动写明了错配风险。
  • pour 收敛成服务端单一入口 shouldPourFluid(playerId, button),四个调用点(Shift 单击 / Alt 点击 / 拖拽 / 存入按钮)语义统一,比原先散落的 button == 0 判断好维护得多。
  • 顺手清理了 takeFluidBucket 的无用 button 参数,并解释了「走到这里一定是流体行为键」的不变量。
  • tooltip 修复用了正确的最小侵入方式(先 getString() 再拆行),并配了说明为何原版 Font 会吞掉 \n,注释质量高。

📋 声称验证表

声称 状态 对应文件
新增客户端配置项 invertFluidPortBucketAction,控制流体端口桶操作左右键互换 AnvilCraftClientConfig:42AnvilCraftClientInvertedActionEventListenerStorageScreenStorageClientStubStorageServerStub(缺 zh_cn 译文,见 ⚠️3)
修正了 tooltip 的换行问题 TooltipUtil.addTranslatedLinesEquipmentArmorItem(另含移除手写 getter 改 @Getter 的小重构)
给四个加工台添加精确拾取黑名单 ⚠️ model_selection_denylist.json:4 个加工台 ✅,但多出一条重复的 fish_tank(见 ⚠️2)
(标题未提及的伴随改动) PlayerEventListener 退出清理、StorageServerStub 倒液整叠修复、denylist 语义守卫

结论: REQUEST_CHANGES — 主体设计正确、迁移完整、注释质量高,是一份干净的 PR;但 🔴1 会让新功能在「重连后」静默失效并与界面相反,属功能性缺陷,建议按上面的 LoggingOut 复位方案修掉;⚠️2 删掉重复行、⚠️3 补两条 zh_cn 即可合并。

🧪 测试建议

被测目标 推荐测试场景 优先级
InvertedActionEventListener.onClientTick + StorageServerStub.shouldPourFluid 开启配置 → 上报 → 模拟 LoggingOut(服务端清表)→ 重新进入,断言客户端会重发且服务端 shouldPourFluid(0) 仍为 false 🔴
TooltipUtil.addTranslatedLines \n / 不含 \n / 多条连续 \n / 带 %s 参数的键,断言行数与顺序、且不丢参数渲染 🟡
ModelSelectionDenylist.reload model_selection_denylist.json 全量断言「无重复项」且每个 ID 可解析为已注册方块(现有实现会在未知 ID 时抛异常,重复项则静默) 🟡
StorageServerStub.interact(FLUID_BUCKET) 翻转开启下 button=1 时仍应 isValid 通过并整叠倒入(回归 account 由 button 取值的旧逻辑) 🟡

由 Hermes Agent 审查

📌 PR 标题未自动修改gh auth status 显示 The token in /opt/data/home/.config/gh/hosts.yml is invalid(Active account: Gugle2308),按流程不导出 GITHUB_TOKEN,故跳过自动改标题。建议标题(当前标题语法有误 four processing table,并且缺中文):

Add client config to swap fluid port bucket pour/store mouse actions, fix tooltips, and add four processing tables to denylist 增加流体端口桶操作左右键翻转配置,修正 tooltip 换行,把四个加工台加入精确拾取黑名单

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/client-config-state-sync-review.md in skill 'network-packet-pr-review' (1 replacement). · Patched references/pr-class-routing.md in skill 'github-webhook-pr-review' (1 replacement). · Patched SKILL.md in skill 'network-packet-pr-review' (1 replacement).

- 移除模型选择黑名单中多余的 "fish_tank" 条目
- 保持 "unpacking_table" 条目在黑名单中不变
@Gugle2308

Copy link
Copy Markdown
Collaborator

代码审查摘要 — PR #4940

操作: synchronize
范围: 11 个文件(9 Java / 1 资源 JSON / 2 生成 lang),610 行 diff(+179 / −52)
主题: ① 客户端配置 invertFluidPortBucketAction(流体端口桶操作左右键互换)② tooltip 换行修正 ③ 四个加工台加入精确拾取黑名单

📋 声称验证表

声称 状态 证据
新增客户端配置控制桶操作左右键互换 AnvilCraftClientConfig:44 + lang(en_us/en_ud) + InvertedActionEventListener:33-38 上报 + StorageServerStub.shouldPourFluid:190 + StorageScreen.bucketActionButton:2456
修正 tooltip 换行 TooltipUtil.addTranslatedLines:53 + EquipmentArmorItem 的 4 个 equipment 键;机制与既有 ItemTooltipManager.addTranslatedTooltip:1111(注释「自动将 \n 拆分为多行」)一致
四个加工台加入精确拾取黑名单 model_selection_denylist.jsondisable_precise_picking 新增 4 条,id 全部为真名ModBlocks.java 402/417/438/459),ProcessingTableBlock 家族恰好这 4 个变体,无遗漏

🔴 关键问题

1. 翻转状态的服务端快照在「重登后」失效 → 同一功能两条路径行为不一致

InvertedActionEventListener.lastBucketInverted:16客户端进程级静态,只在「配置值 ≠ 上次上报值」时上报(InvertedActionEventListener:35);而服务端在玩家登出时清表PlayerEventListener:72StorageServerStub.clearInvertedBucketAction:179)。二者不对称:

  • 开启配置 → 进世界(上报 true)→ 退回标题/退出存档(服务端清表)→ 重新进入bucketInverted(true) == lastBucketInverted(true) ⇒ 不再上报,服务端 shouldPourFluidnull ⇒ 判定为「未翻转」。
  • 结果:由服务端判定倒液的路径(deposit 存入按钮、Alt+点击批量、Shift+点击、Shift+拖拽、moveSameToStorage)全部回到「左键倒液」,而流体格自身键位由客户端直接读配置(bucketActionButton() 始终最新)⇒ 玩家看到「流体格右键倒液、背包槽左键倒液」。

修复建议(任选):① 在登录/进入世界时复位快照并重发(lastBucketInvertedBooleannull,或在 ClientPlayerNetworkEvent.LoggingIn 中复位;顺手把 lastInverted(BundleLike) 一起处理,形态相同);② 更彻底:不维护服务端静态表,让客户端把「本次意图(pour 与否)」随请求一起发(interact/deposit/moveSameToStorage/quickMoveToStorage 都已传 button,加一个参数即可),可一次性消除登出/重登、配置热改、多客户端带来的所有时序问题。

2. updateInvertedBucketAction@RemoteCallable validator → playerId 可伪造 + 静态表无界增长

StorageServerStub 内 50 个 @RemoteCallable 有 49 个带 validator,新增的这个没有:

@RemoteCallable                                     // ← 其余 49 处均为 @RemoteCallable(validator = …)
public static void updateInvertedBucketAction(UUID playerId, boolean inverted) {
    StorageServerStub.INVERTED_BUCKET_ACTION.put(playerId, inverted);
}

StorageAccessValidator.isValid:4518 明确校验 ctx.player().getGameProfile().getId().equals(args[0]),此处缺失 ⇒ 客户端可写入任意 UUID 的翻转状态(改别人的桶键位),且 INVERTED_BUCKET_ACTION 只在该 UUID 玩家登出时清理(:179),伪造条目永不释放。建议加一个只校验「args[0] == 发送者」的轻量 validator(不需要仓储访问检查)。


⚠️ 警告

3. 新配置缺 zh_cn 翻译。 en_us 的 87 个 anvilcraft.configuration.* 键里,唯一在 zh_cn 缺失的就是本次新增的 invert_fluid_port_bucket_action(及其 .tooltip)——既有同类项(invert_override_action 等)都有中文。中文客户端会看到英文文案(若 zh_cn 走 Weblate 后补,请忽略本条)。

4. TooltipUtil.addTranslatedLines 与既有实现重复。 ItemTooltipManager.addTranslatedTooltip:1111-1117 已完成同一件事(split("\n") + literal + GRAY,仅插入位置/顺序不同)。建议把 TooltipUtil 作为唯一实现、让 manager 委托,避免两处行为漂移(例如将来过滤空行、支持自定义颜色只改一处)。另外 getString() 会把参数拍平为纯文本:当前调用点参数是 int,功能等价;但若将来传入带样式的 Component 参数会丢样式,建议在 Javadoc 里注明「参数不要传带样式的 Component」。


💡 建议

5. 拖拽路径的新参数恒为 0。 mouseClickedif (button == 1) {…} else if (this.carried.isEmpty()) { this.quickMoveButton = button; … }StorageScreen:1978-1992)——走到赋值分支时 button 必为 0;且 mouseDragged:2143 只在 button == 0 && hasShiftDown() 时扩选。所以 quickMoveToStorage(pos, slots, this.quickMoveButton) 的第三参数在拖拽路径上是恒定值,翻转开启后 Shift+左键拖拽会把流体桶当物品存入(符合「左键=存」语义),但无法用拖拽批量倒液。要么删掉该参数(服务端直接查翻转表),要么有意放开 mouseDragged 的按钮判断,让拖拽也遵循翻转后的倒液键。

6. 数量语义仍绑定物理键。 int amount = button == 0 ? carried.getCount() : 1;StorageServerStub:362)——翻转后「存桶」键变成左键 ⇒ 永远整叠存入,「只存 1 个」在翻转下不可达(注释已说明是有意取舍,若确实如此可忽略;否则请把 amount 绑定到「存桶动作」而非物理键)。

7. tooltip 文案可再补一句。 空手时取液键固定为左键(fluidButton = carried.isEmpty() ? 0 : bucketActionButton())与文案一致 ✅,但建议在 tooltip 里点明「空手取液仍为左键」,避免玩家期待完全对称。

8. 黑名单健壮性(非本 PR 引入,顺手提醒)。 ModelSelectionDenylist.readBlocksgetOptional(id).orElseThrow(...),异常被 reload()catch (RuntimeException) 吞掉 ⇒ 任一条目为未知 id 会让整份名单静默退化为 EMPTY(历史上本 PR 的前一 revision 就曾因 stamping_table 踩过,本 revision 已修正)。建议给这份名单加一条单测/CI 校验(逐 id 查 BuiltInRegistries.BLOCK),避免同类回归只能靠肉眼发现。

9. 名单档位确认。 四个加工台只进了 disable_precise_picking(拾取/交互回到方块 VoxelShape),未进 disable_model_outline ⇒ 仍会绘制自定义精确轮廓(与既有 fish_tank/trading_station 同类)。若本意是「完全原版框」需同时进 disable_model_outline;若有意保留精确轮廓则无需改动——请确认。


🟢 看起来不错

  • shouldPourFluid 单点判定 + 客户端/服务端共用同一套按键语义,并把「必须与服务端一致,否则界面走存桶分支而服务端按倒液处理」写进 javadoc,注释质量高。
  • pour = slot < FLUID_SLOT_BASE && … 让流体格一律不倒液,避免翻转配置把「存桶」又变回倒液,思路正确(流体格物品键走 PICKUP、流体键走 FLUID_BUCKET,服务端行为与 button 无关,因此该路径不存在配置错配)。
  • takeFluidBucket 去掉 button 参数、改为一律整叠倒出:与「FLUID_BUCKET 只由流体行为键触发」一致,消除了翻转后按 button 取值把整叠误减为 1 个的隐患 ✅。
  • 登出清理(clearInvertedBucketAction)防静态表残留 ✅;@Getter 替换手写 isWeatherproof()(唯一使用者 IonocraftBackpackItem:71 是子类,Lombok 生成的 isWeatherproof() 名称一致)✅。
  • 客户端 tick 的 getConnection() == null 守卫等价于「本地玩家为空」(已按 1.21.1 官方 mappings + 混淆客户端字节码核对:Minecraft.getConnection() 实际读 LocalPlayer player 再取 LocalPlayer.connection),因此 StorageClientStub.playerId()IllegalStateException 在此路径不会触发,无需额外判空 ✅。
  • 黑名单 4 条 id 逐条核实为真名,en_us/en_ud 两条新增键位置对称、en_ud 为镜像文本 ✅。

🧪 手测清单(建议合并前自测)

场景 期望
开启配置 → 进世界 → 退存档 → 重进,Alt+点击/Shift+点击/存入按钮 倒液键仍为右键(当前会退回左键 → 本条会失败
开启配置,流体格空手左键 / 右键 左键装出,右键不动作(键位固定)
开启配置,流体格持桶左键 / 右键 左键存桶(整叠),右键倒液(整叠)
默认配置,流体格持桶左键 / 右键 左键倒液,右键存 1 个桶
多客户端同服,各自不同配置 互不影响

结论: REQUEST_CHANGES — 建议先修 #1(重登后配置失效且两条路径行为不一致,会影响所有开启该选项的玩家)与 #2(validator 缺失),#3 视 zh_cn 流程决定;其余为可选优化。

建议标题(本次 gh 未认证,未自动修改):
Add client config to swap fluid port bucket pour/store actions, fix multiline item tooltips, and add four processing tables to the precise-picking denylist 增加流体端口桶操作左右键翻转、修正 tooltip 换行、四个加工台加入精确拾取黑名单


由 Hermes Agent 审查

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched references/pr-class-routing.md in skill 'github-webhook-pr-review' (1 replacement). · Patched SKILL.md in skill 'network-packet-pr-review' (1 replacement). · Patched references/mc-bytecode-semantics-probe.md in skill 'github-webhook-pr-review' (1 replacement).

- 将 TooltipUtil.addTranslatedLines 替换为返回列表后批量添加,简化代码结构
- TooltipUtil.translatedLines 方法改为返回拆分并着色的文本列表
- ItemTooltipManager 内添加翻译文本方法改为用 TooltipUtil 返回的列表插入
- InvertedActionEventListener 监听客户端连接变化,确保状态更新准确
- 解决不同连接之间配置状态未正确重置的问题
- 移除不再使用的 I18n 导入,改用 TooltipUtil 处理国际化文本
@Gugle2308

Copy link
Copy Markdown
Collaborator

⚠️ Dangerous command requires approval:

cd /tmp && python3 -c "import json;d=json.load(open('/dev/stdin'))" < /dev/null 2>/dev/null; sed -n '665,683p' pr4940.diff

Reason: script execution via -e/-c flag

Reply /approve to execute, /approve session to approve this pattern for the session, /approve always to approve permanently, or /deny to cancel.

@Gugle2308

Copy link
Copy Markdown
Collaborator

审查完成。以下是直接输出到 PR 评论区的审查意见(未执行任何 gh pr review/comment,未导出任何凭据)。


代码审查摘要 — PR #4940

操作: synchronize | 审查基准: head 7f3b41210e(与 api.github.com/pulls/4940 的 head sha 一致,已按当前 revision 重新取源码复核,不继承历史结论)
范围: 12 个文件(8 Java / 4 资源)|+214 / −58 | 无新增、无删除文件 | 无 EOF 缺换行 | mergeable_state: clean

三个主题:① 客户端配置 invertFluidPortBucketAction + 客户端→服务端配置同步;② tooltip 显式拆行;③ 四个加工台进精确拾取黑名单。

🔴 关键

  • 无。

📋 声称验证表

声称 状态 证据
新增客户端配置项,控制桶操作左右键互换 AnvilCraftClientConfig:42InvertedActionEventListener:57-62StorageServerStub.shouldPourFluid:184-187PlayerEventListener:72 登出清理
修正 tooltip 换行问题 TooltipUtil.translatedLines 抽出并接入 ItemTooltipManager:1111EquipmentArmorItem(6 处);en_us/en_ud 2530 键完全对称
四个加工台加入精确拾取黑名单 4 个 id 全为真名ModBlocks 402/417/438/459);JSON 合法、无重复;ProcessingTableBlock 恰好只有这 4 个子类 → 家族覆盖完整

⚠️ 待确认(不阻塞)

  • zh_cn 缺 2 个新键en_us.json 新增 anvilcraft.configuration.invert_fluid_port_bucket_action(.tooltip) 并同步到 en_ud.json,但 zh_cn.json 未加。实测 en_us 的 configuration 键中只有这 2 个zh_cn 缺失(其余全部有中文),中文客户端会显示原始键名。若 zh_cn 惯例交由 Weblate 后续补齐,请确认即可。
  • 手持空桶时键位同样翻转StorageScreen:1902this.carried.isEmpty() 决定键位,因此手持空桶(最常见的取液姿态)时,开启翻转后左键变成「把空桶当物品存入」、取液改到右键。代码注释与服务端 javadoc 只描述「空手时键位固定不动 / 手持流体桶时按键位翻转」,与「空桶」这一实际场景略有出入——请确认是否有意;若有请顺手在配置 tooltip 中点明「手持任意物品时该键位均翻转」。
  • 倒液键一律整叠takeFluidBucket 去掉 button 后固定 carried.getCount(),翻转开启时右键=整叠倒液,而同一界面物品格右键=存入 1 个。作者的注释已解释此取舍(原本 amount 的右键分支不可达),此处仅提请确认语义有意不对称。
  • 四个加工台只写了 disable_precise_picking:品牌效应上这是「只禁精确拾取、保留模型描边」,符合 PR 描述;但模型几何与碰撞体并不一致——crushing_table.json 的台面由 8 个 [2,9,…]-[14,15,…] 小块拼成,在 7.5/8.5 处留有 1px 十字断缝,而 ProcessingTableBlock.SHAPEy∈[10,12] 是整块底板(模型该处无几何)。因此恢复原版拾取后,描边(按模型几何,带断缝)与命中框(原版 SHAPE,无断缝)仍不一致。若目标是「描边与命中都回到原版形状」,只需写 disable_model_outlineusesOriginalPicking = picking ∪ outline,并集会自动覆盖 picking)。请确认当前选择是刻意的。

💡 建议

  • InvertedActionEventListener:57-62 现在会在换局后的首个 tick立即调用 StorageClientStub.playerId(),而该方法在 Minecraft#player == null 时直接抛 IllegalStateExceptionStorageClientStub:503-509);同文件的既有上报走 BundleLikeClientStub(用 getGameProfile().getId(),不依赖 player)。虽然 getConnection() != null ⇒ player != null(本仓 TerminalKeyMappingEventListener:70 也这样假设),但 tick 事件里抛异常没有兜底,建议改用 getGameProfile().getId() 或补 player == null 守卫。
  • quickMoveButton 目前恒为 0:唯一赋值点 StorageScreen:1990 位于 if (button == 1) 之后的 else if 分支,而 mouseDragged:2143 也只在左键下推进拖拽。因此新增的字段 + RPC 参数实际不携带信息(翻转行为完全由配置决定)。可保留(便于将来支持右键拖拽与 flushQuickMoves 的其它调用点),但值得加一句注释说明当前恒为 0。
  • takeFluidBucket javadoc 仍写「右键不走这里,而走原有物品行为」,与同段新增的「手持流体桶时视配置决定哪个键走本方法」自相矛盾(翻转后右键确实会走这里),建议删掉那半句。
  • interactslot < FLUID_SLOT_BASE 守卫在当前按键映射下两态都不可达(未翻转时流体格物品键=右键→false;翻转后=左键→false),属防御性写法,保留无妨;同分支 amount 现在只服务物品插入路径,注释已说明。
  • 默认配置下逐路径等价性我已逐条核对:deposit / moveSameToStorage / quickMoveToStorage(button 恒 0 → shouldPourFluid(0)=true,与旧硬编码 true 一致)/ interact(QUICK_MOVE_TO_STORAGE) / takeFluidBucket(旧右键分支本就不可达,amountcount 覆盖)/ 流体格键位(未翻转时 fluidButton=0,与旧 button==1 → 物品行为 完全一致)——无默认行为回归
  • RPC 线程模型已核对(anvillib snapshot.530 rpc 源码):RpcRequestPayload.bidirectionalHandlerctx.enqueueWork(...),即在服务端主线程执行 → 新增静态 HashMapBundleLikeServerStub 同样只在主线程读写,无并发隐患
  • 精确拾取名单本身的失效链未受影响:ModelSelectionDenylist.readBlocksorElseThrow 仍被 reload()catch (RuntimeException) 吞掉(非法 id = 整份名单静默退化为 EMPTY,且只有 src/main/resources 一份资源无回退)。本次 4 条 id 全部为真名(上一版的 stamping_table 错名已在此 revision 修正),JSON 亦合法。

🟢 做得好的地方

  • lastConnection 哨兵方案正确且理由充分:ClientPlayerNetworkEvent.LoggingOut 早于 Minecraft#disconnect 之后的额外 runTick,player 尚未置空会立刻把哨兵重新写成「已上报」,改用连接实例身份判断确实能覆盖「配置为 true 时退出再进入同一服务器」的场景。
  • 服务端 INVERTED_BUCKET_ACTION 以 UUID 为键 + PlayerLoggedOutEvent 清理,无静态表泄漏。
  • tooltip 改用 Component.translatable(...).getString() 替代客户端专用的 I18nTooltipUtil 本身无客户端类依赖,公共代码可安全调用;pockets%s 参数保留;addAll 追加顺序与倒序插下标 1 的旧行为一致。
  • @Getter 替换手写 isWeatherproof() 后签名不变(IonocraftBackpackItem:71 仍可编译),保留了对附属模组的公开 API。

📋 建议标题

当前标题已为「英文 + 中文」且准确描述三项改动,无需修改(本次未执行 gh pr editgh auth status 显示 token 已失效,且标题本身已达标)。

结论: APPROVE(附上述 ⚠️/💡 确认项) — 三项声称均已落地且有源码/资源硬证据,默认配置行为逐路径等价、无回归,RPC 线程模型与登出清理完整,名单 id 与 JSON 均已复核。剩余项为翻译补齐与几处语义/注释确认,可在合并前顺手处理。

🧪 测试建议

被测目标 推荐场景 优先级
shouldPourFluid × deposit/moveSameToStorage/quickMoveToStorage/interact 配置 false/true × 左/右键 × 手持流体桶/空桶/空手,断言「倒液 ↔ 存桶」与默认配置逐条等价 🔴
InvertedActionEventListener#onClientTick 配置 true → 进世界 → 退出 → 重进同一世界,断言服务端仍收到 true(哨兵复位) 🔴
ModelSelectionDenylist.reload 注入 1 个非法 id,断言整份名单退化为 EMPTY(回归守卫,防止未来 id 错名静默失效) 🟡
TooltipUtil.translatedLines \n 的多行键、含 %s 参数键(pockets 12/6)、缺失键(回退为键名) 🟡
StorageScreen 流体格键位 翻转为 true 时:手持流体桶右键=倒液、左键=存桶;空手左键=装出一桶、右键无操作 🟡

由 Hermes Agent 审查 · 基准 head 7f3b41210e

@Gugle2308

Copy link
Copy Markdown
Collaborator

💾 Self-improvement review: Patched SKILL.md in skill 'cube-selection-pr-review' (1 replacement). · Patched SKILL.md in skill 'client-server-side-separation-review' (1 replacement). · Patched references/client-config-server-sync-review.md in skill 'client-server-side-separation-review' (1 replacement). · Patched scripts/check_model_selection_blacklist_coverage.py in skill 'cube-selection-pr-review' (1 replacement).

@TBPig
TBPig merged commit fe91281 into Anvil-Dev:dev/1.21/1.6 Sep 19, 2026
2 checks passed
@Gugle2308

Copy link
Copy Markdown
Collaborator

PR #4940 状态为 closed,按 webhook 审查规则(closed/labeled → 停止),本次不进行审查、不输出审查意见。

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.

3 participants