Files
mtgodot-poc/audit/contracts/combat.affect-status.md
T
shenandshen c93894313a fix: 装备属性面板避让逻辑 + 多项功能更新
- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧
- inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域
- 包含其他累积的功能开发和测试文件
2026-09-21 16:38:59 -07:00

8.6 KiB
Raw Blame History

combat.affect_status —— 受击、眩晕、击倒、状态旗标和状态图标

状态:PARTIAL

本合同对照 40250 Windows 客户端的两条相关但不能合并的链路:

  1. Actor 的 AffectFlagContainer 变化如何驱动附着效果、眩晕/睡眠显示和石头烟雾。
  2. 服务端 GC_DAMAGE_INFOGC_STUNGC_AFFECT_ADD/REMOVE 和角色更新如何进入实体、动作、状态图标与清理流程。

注意:40250 Windows 客户端不在本地决定普攻中毒率、眩晕率、减速率或持续伤害;这些结果由服务端计算,客户端消费状态包和伤害包。当前端的 combat_status_effect_system.gd 是本地/server-like 模拟器,因此即使其单元测试通过,也不能作为客户端等价实现的证据。

40250 参考调用链

Actor affect flags

  • UserInterface/AffectFlagContainer.cpp
    • ClearCopyInstanceCopyData 按位复制固定字节数据。
    • Set/IsSet 对每个 affect bit 做边界检查。
  • UserInterface/InstanceBaseEffect.cpp
    • SetAffectFlagContainer:建筑直接忽略;石头走 __SetStoneSmokeFlagContainer;普通 Actor 走 __SetNormalAffectFlagContainer
    • 普通 Actor 逐 bit 比较旧/新值,只对变化位调用 __SetAffectGraphicThingInstance.__OnSetAffect__OnResetAffect,最后复制新容器。
    • __ClearAffects 在删除、重建和重置时销毁附着效果并清空容器;石头按烟雾等级重建烟雾。
    • AFFECT_INVISIBILITY 会清附着特效和文字尾;AFFECT_STUN 调用 SetSleep
  • UserInterface/NetworkActorManager.cpp / InstanceBase.cpp
    • 角色创建和更新顺序包含 SetAffectFlagContainer,随后才刷新速度、攻击速度、阵营、PK 和 state flags。
    • SetStateFlags 仅负责 killer/party 等展示与目标相关状态,不等同于 affect bitset。

受击、眩晕和伤害

  • UserInterface/PythonNetworkStreamPhaseGame.cpp
    • RecvDamageInfoPacket 找到 VID 后调用 AddDamageEffect(damage, flag, bSelf, bTarget);负伤害只记录错误,不由客户端扣血。
    • RecvAffectAddPacket 处理能量状态时间,并调用 BINARY_NEW_AddAffect(type, point, value, duration)
    • RecvAffectRemovePacket 调用 BINARY_NEW_RemoveAffect(type, point)
  • InstanceBase/ActorInstanceBattle
    • __HitGood__HitGreate__HitStone 只负责表现层受击动作、震屏、击倒/起身队列;服务器伤害和状态仍是权威来源。
    • __CanProcessNetworkStatePacket 在死亡、击倒或不能取消的技能动作中拒绝网络状态输入。
  • PythonNetworkStreamPhaseGameActor.cpp
    • CharacterAdd2/CharacterUpdate 搬运两个 32-bit affect words 和 state flag;更新进入 Actor manager 后再刷新 Actor 表现。

当前端实现映射

  • extension/src/net/classic/classic_parser.cppextension/src/net/entity_store.cpp
    • 已解析角色创建/更新的两个 affect words、GC_STUNGC_AFFECT_ADD/REMOVEGC_DAMAGE_INFO
    • Entity.affect_flags 使用 64-bit 保存 bitsetAffect/AffectChange 保存本地玩家的状态图标数据。
    • m2_client.cpp 已把 affect change 转为 affect_added/affect_removed 信号。
  • project/net_world.gdproject/ui/player_view.gdproject/ui/mob_view.gd
    • 远端节点会收到 set_affect_flags,并有附着特效映射;hit_reaction.gd 实现 damage/knockdown/standup 队列和 shake/InsertDelay 近似。
    • game_scene.gd/hud.gd/quickbar.gd 已消费一般 affect 数据,但图标目前是通用色块,未逐 affect 对照 40250 的 icon/附着资源。
  • project/net_play.gd
    • 本地主角消费 damage、stunned、knock_down 门控,并在攻击成功时驱动目标受击表现。
  • project/combat_status_effect_system.gd
    • 另有客户端本地中毒、眩晕、减速、灼烧/流血施加、计时和直接改 HP 的模拟链;它不是 40250 Windows 客户端的协议消费链。

分支等价矩阵

参考判据/副作用 当前实现 结论
AffectFlagContainer 按位复制、边界检查和旧/新差分 native 64-bit 字段 + mut_affect_flags;视图用 AFFECT_EFFECT_MAP 遍历 基本等价;未覆盖完整 bit 宽度和所有 effect resource
普通 Actor affect 变化触发设置/重置附着效果 NetWorld._apply_field_updatesset_affect_flags 部分等价;当前按映射表重扫,未证明逐 bit 的旧/新回调和失败清理顺序
石头烟雾按 0/1/2/3 等级销毁并重建 当前通用 affect 映射,没有对应 stone smoke 分支证据 缺失/不等价
建筑忽略 affect flag 当前 _apply_field_updates 依赖节点是否有 setter,没有显式建筑 no-op 规则 未证明等价
AFFECT_INVISIBILITY 清附着效果、文字尾和可见性 当前有 affect effect 映射,但目标选择/文字尾的隐身门控分散在其它脚本 不完整
AFFECT_STUN 驱动睡眠/眩晕表现 当前 mut_stun 仅置 Entity.stunned;本地门控可阻止行动,远端主要靠命中回调 不完整;没有完整远端 SetSleep/清除链
GC_DAMAGE_INFO 只产生 DamageEffect/飘字,不在客户端扣血 net_world._on_damage 产生飘字;基础 damage_effect_test 通过 基本等价;本地主角 _on_damage 额外使用通用 damage/击退锁,flag 细分未完整
GC_AFFECT_ADD/REMOVE → UI 状态图标和能量时间 native signal → game_scene/HUD/quickbar,基础协议测试通过 基本等价;图标资源、重复/乱序/过期 duration 未证明
客户端不本地结算 poison/stun/slow/fire 结果 combat_status_effect_system.gd 本地 roll、tick 并直接改 HP 明确不等价;数据权威错误
死亡、击倒、技能不可取消时拒收网络状态 EntityStore::can_process_network_stateNetPlay._can_process_network_state 有 dead/knockdown/stun 门 部分等价;native 门不含 stunned,远端动作消费也未统一
受击动作 HitGood/Great/Stone 的资源选择和队列 hit_reaction.gd/PlayerView/MobView 有近似队列 部分等价;hit_view_test.gd 当前有 1 项失败,前向 damage 仍停留在 combo 资源

已执行测试

  • project/test_combat_status_effect_parity.gd:24/24 PASS;只能证明当前本地模拟器自身行为,不证明客户端权威模型正确。
  • project/damage_effect_test.gdPASS;覆盖伤害飘字分类和显示门。
  • project/combat_fx_test.gd:PASS;覆盖命中、连击、shake、affect 和死亡主链。
  • project/test_mob_combat_reaction_parity.gd:17/17 PASS;覆盖怪物受击和击倒 helper。
  • project/test_combat_death_pickup_entry_parity.gdPASS;覆盖死亡/动作锁相关入口。
  • build/extension/net_entity_testPASS;覆盖 native stun/affect add/remove 等状态存储。
  • build/extension/net_classic_session_testPASS;覆盖 classic session 状态包入口。
  • project/hit_view_test.gdFAIL1 项:fScalar < 0 的前向受击期望 damage/damage_1.msa,实际仍为 onehand_sword/combo_01.msa

已确认差异和未验证项

  1. combat_status_effect_system.gd 把服务端战斗状态结算复制到客户端,包含随机 roll、持续时间、毒/火跳伤害和直接改 HP;必须与协议消费链分离,否则会造成双重结算或离线假状态。
  2. native GC_STUN 目前只置 stunned=true,没有专门的解除包/清除路径;当前依赖后续实体更新/重建恢复,未证明与 40250 Actor affect 清除顺序一致。
  3. NetWorld._apply_field_updates 没有 40250 __SetNormalAffectFlagContainer 的逐 bit 差分回调,也没有 stone smoke/building/no-op 的显式分支。
  4. 远端 stunned/knock_down 没有统一进入 SetSleep、击倒动作和网络状态拒收链;本地 NetPlay 与远端 NetWorld 的门控职责不一致。
  5. hit_view_test.gd 的失败说明受击动作资源/动作锁时序仍有真实差异,当前不能将受击合同标记为通过。
  6. 需要补齐 affect bit→effect/icon 资源索引、重复/乱序 add/remove、duration 过期、死亡/切图/重连清理、隐身文字尾、stone smoke 和建筑 no-op 测试。

下一轮验证入口

  • 先移除或隔离本地状态结算器的权威写入,只保留服务端 affect/damage 包驱动的视图与 UI。
  • 为每个 affect bit 建立 40250 资源、附着骨骼、开关、清理和石头烟雾矩阵。
  • 增加 GC_AFFECT_ADD → duration → GC_AFFECT_REMOVE、重复/乱序/重连/换图的 native→HUD 端到端测试。
  • 增加远端 Actor 的 stunned/knockdown/skill-cancel 状态机测试,并修复 hit_view_test.gd 的前向 damage 资源选择后再提升合同状态。