- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
8.6 KiB
8.6 KiB
combat.affect_status —— 受击、眩晕、击倒、状态旗标和状态图标
状态:PARTIAL
本合同对照 40250 Windows 客户端的两条相关但不能合并的链路:
- Actor 的
AffectFlagContainer变化如何驱动附着效果、眩晕/睡眠显示和石头烟雾。 - 服务端
GC_DAMAGE_INFO、GC_STUN、GC_AFFECT_ADD/REMOVE和角色更新如何进入实体、动作、状态图标与清理流程。
注意:40250 Windows 客户端不在本地决定普攻中毒率、眩晕率、减速率或持续伤害;这些结果由服务端计算,客户端消费状态包和伤害包。当前端的 combat_status_effect_system.gd 是本地/server-like 模拟器,因此即使其单元测试通过,也不能作为客户端等价实现的证据。
40250 参考调用链
Actor affect flags
UserInterface/AffectFlagContainer.cppClear、CopyInstance、CopyData按位复制固定字节数据。Set/IsSet对每个 affect bit 做边界检查。
UserInterface/InstanceBaseEffect.cppSetAffectFlagContainer:建筑直接忽略;石头走__SetStoneSmokeFlagContainer;普通 Actor 走__SetNormalAffectFlagContainer。- 普通 Actor 逐 bit 比较旧/新值,只对变化位调用
__SetAffect、GraphicThingInstance.__OnSetAffect或__OnResetAffect,最后复制新容器。 __ClearAffects在删除、重建和重置时销毁附着效果并清空容器;石头按烟雾等级重建烟雾。AFFECT_INVISIBILITY会清附着特效和文字尾;AFFECT_STUN调用SetSleep。
UserInterface/NetworkActorManager.cpp/InstanceBase.cpp- 角色创建和更新顺序包含
SetAffectFlagContainer,随后才刷新速度、攻击速度、阵营、PK 和 state flags。 SetStateFlags仅负责 killer/party 等展示与目标相关状态,不等同于 affect bitset。
- 角色创建和更新顺序包含
受击、眩晕和伤害
UserInterface/PythonNetworkStreamPhaseGame.cppRecvDamageInfoPacket找到 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.cppCharacterAdd2/CharacterUpdate搬运两个 32-bit affect words 和 state flag;更新进入 Actor manager 后再刷新 Actor 表现。
当前端实现映射
extension/src/net/classic/classic_parser.cpp、extension/src/net/entity_store.cpp- 已解析角色创建/更新的两个 affect words、
GC_STUN、GC_AFFECT_ADD/REMOVE和GC_DAMAGE_INFO。 Entity.affect_flags使用 64-bit 保存 bitset,Affect/AffectChange保存本地玩家的状态图标数据。m2_client.cpp已把 affect change 转为affect_added/affect_removed信号。
- 已解析角色创建/更新的两个 affect words、
project/net_world.gd、project/ui/player_view.gd、project/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_updates → set_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_state 和 NetPlay._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.gd:PASS;覆盖伤害飘字分类和显示门。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.gd:PASS;覆盖死亡/动作锁相关入口。build/extension/net_entity_test:PASS;覆盖 native stun/affect add/remove 等状态存储。build/extension/net_classic_session_test:PASS;覆盖 classic session 状态包入口。project/hit_view_test.gd:FAIL,1 项:fScalar < 0的前向受击期望damage/damage_1.msa,实际仍为onehand_sword/combo_01.msa。
已确认差异和未验证项
combat_status_effect_system.gd把服务端战斗状态结算复制到客户端,包含随机 roll、持续时间、毒/火跳伤害和直接改 HP;必须与协议消费链分离,否则会造成双重结算或离线假状态。- native
GC_STUN目前只置stunned=true,没有专门的解除包/清除路径;当前依赖后续实体更新/重建恢复,未证明与 40250 Actor affect 清除顺序一致。 NetWorld._apply_field_updates没有 40250__SetNormalAffectFlagContainer的逐 bit 差分回调,也没有 stone smoke/building/no-op 的显式分支。- 远端
stunned/knock_down没有统一进入SetSleep、击倒动作和网络状态拒收链;本地NetPlay与远端NetWorld的门控职责不一致。 hit_view_test.gd的失败说明受击动作资源/动作锁时序仍有真实差异,当前不能将受击合同标记为通过。- 需要补齐 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 资源选择后再提升合同状态。