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

13 KiB
Raw Blame History

skill.player_ui_cooldown —— 技能栏、快捷键、等级、冷却和禁止条件

状态:PARTIAL

本合同覆盖 40250 Windows 客户端的技能数据解析、技能页加点、快捷栏持久化、技能施放前检查、目标/射程预约、冷却和服务端解锁事件。判断标准是调用顺序、禁止条件、数据来源、冷却公式和网络副作用一致,而不是只看当前端是否存在同名 use_skill 函数。

40250 参考调用链

技能数据和等级

  • UserInterface/PythonSkill.cpp
    • RegisterSkillDesc/RegisterSkillTable 读取技能属性、武器限制、动作、等级、目标范围、目标数量、SP/冷却公式和 .msk 数据。
    • SSkillData::IsCanUseSkill 只允许非被动主动施放;GetSkillCoolTimeGetTargetCountGetNeedSP 都以 fcurEfficientPercentage 代入公式。
  • UserInterface/PythonPlayer.cpp
    • SetSkillLevel_ 将服务端等级/grade 转为当前 slot 的 grade、level 和 LocaleService_GetSkillPower 效率百分比。
    • 快捷栏是服务端 QUICKSLOT_MAX_NUM=36,本地显示 4×8;分页、slot/ref 映射、add/del/move 请求均以全局槽位为权威。
    • affect 的 toggle 技能会调用 ActivateSkillSlot/DeactivateSkillSlot,而不是由 UI 本地猜测。

施放前检查和副作用顺序

  • UserInterface/PythonPlayerSkill.cpp::ClickSkillSlot
    • 槽位/技能数据 → 公会技能路由 → 被动静默返回 → standing/toggle-off → __UseSkill
  • __CanUseSkill
    • 主角存在、非观战、坐骑术等级满足、CInstanceBase::CanUseSkill 通过。
  • CInstanceBase::CanUseSkill
    • 拒绝变身 IsPoly、穿礼服 IsWearingDress、持镐 IsHoldingPickAxe、坐骑不可用技能和当前图形动作不可施法。
  • __CheckSkillUsable
    • 骑乘/骑乘技、攻击技安全区、被动、空瓶/毒瓶、钓鱼、等级/未学习、武器类型、箭矢、冲刺 affect、冷却、HP/SP 按固定顺序检查。
  • __UseSkill
    • 私人商店静默返回 → __CanUseSkill → 特殊技能 → toggle-off → __CheckSkillUsable → 施法中检查 → 目标/尸体/结盟/安全区/可攻击/射程预约 → 改朝向 → 扇形/圆形追加目标 → 清预约 → NEW_UseSkill 动作 → SendUseSkillPacket__RunCoolTime
  • __RunCoolTime
    • 以 skill .msk 冷却公式计算基础时间,再按 POINT_CASTING_SPEED 修正,最后触发 UI RunUseSkillEvent

网络回包

  • PythonNetworkStreamPhaseGame.cpp
    • RecvSkillLevel/RecvSkillLevelNew 刷新技能和效率百分比。
    • RecvSkillCoolTimeEnd 调用 EndSkillCoolTime,按技能 ID 清除本地冷却。
    • RecvChangeSkillGroupPacket 清空旧 skill data 并刷新角色窗口。
    • GC_QUICKSLOT_ADD/DEL/MOVE 更新本地 36 槽数据。

当前端实现映射

  • project/ui/skill_table.gd
    • 已解析 skilldesc.txt、属性/武器 bit、.msk 的冷却/SP/目标数量/范围公式,并实现技能分类和等级加点谓词。
  • project/player_skill.gd
    • 已按 ClickSkillSlot → can_use_skill → check_skill_usable → use_skill 分层校验,包含 CInstanceBase::CanUseSkill 的变身、礼服、矿镐、坐骑动作和图形动作五类门,以及安全区、骑乘、被动、瓶子、钓鱼、等级、武器、箭矢、冷却、HP/SP、尸体、结盟、目标和射程预约。
    • 公会技能按 GUILD_ROUTED 转入独立 use_guild_skill,补齐公会战状态门和独立冷却入口。
  • project/ui/quickbar.gd
    • 维护 36 个服务端槽位、4×8 可见页、server quickslot add/del/swap、技能图标/等级/冷却遮罩、移动端快捷栏和 GC_SKILL_COOLTIME_END 解锁。
      • 通过 M2Client.use_skilluse_guild_skillcast_skillNetPlay 发起技能;冷却按 .msk 基础值和 POINT_CASTING_SPEED 的 40250 分段公式本地预测。
  • project/ui/skill_ui.gd
    • 有主动/辅助/坐骑页、技能等级/grade、加点按钮、技能拖入快捷栏和服务端 skill/group 刷新。
  • extension/src/net/entity_store.cppclassic_parser.cppm2_client.cpp
    • 已存储/转发 skill level、master、group、quickslot 和 cooldown-end 包,并暴露相应信号。

分支等价矩阵

参考判据/副作用 当前实现 结论
技能属性、武器限制、.msk 公式和等级效率 SkillTable 读取 skilldesc/.msk_skill_point 有 40250 等级表 基本等价;全部语言、所有 .msk 和公式异常仍未建立快照
ClickSkillSlot 分层顺序、被动/公会/特殊/开关技 PlayerSkill.click_skill_slot/use_skill/use_guild_skill + Quickbar._activate_guild_skill 基本等价;公会技已独立走 CG_GUILD_USE_SKILLtoggle active 状态主要由调用方传入,未证明与 affect slot 状态始终一致
CInstanceBase::CanUseSkill 变身、礼服、持镐、坐骑和图形动作门 can_use_skill 按相同顺序拒绝五类动作状态,NetPlay.skill_context 从实体快照注入 poly/礼服/矿镐/坐骑/图形门 基本等价;礼服和图形层字段依赖服务器快照是否提供
安全区、瓶子、钓鱼、等级、武器、箭矢、HP/SP、尸体和结盟 check_skill_usable/_resolve_target 基本等价;in_safe、持有物、HP/SP 和目标字段均依赖当前端快照,实时/乱序未证实
冷却公式 GetSkillCoolTime(fcurEfficientPercentage) SkillTable.cooldown_of 公式主体基本等价
__RunCoolTimePOINT_CASTING_SPEED 修正 M2Client.get_points() 暴露点 21Quickbar._skill_cooldown 按 40250 分段公式换算 基本等价;真实服务端点变更与冷却回包乱序仍待 fixture
冷却开始时机:动作成功后 SendUseSkillPacket__RunCoolTime 当前先成功发送 cast_skillFUNC_SKILL),再发送 use_skillCG_USE_SKILL),最后写本地 cd_end 基本等价;没有以服务端/动作成功回执确认本地预测是否应回滚
GC_SKILL_COOLTIME_END 按 skill ID 解锁 native parser/store → skill_cooldown_end → Quickbar 清理 基本等价;重复、过期、断线和槽位重排时序未验证
36 槽 server quickslot 与 4×8 可见页 Quickbar 36 状态 + 32 可见槽,native 36 槽 基本等价;最后 4 个不可见槽的恢复、键盘输入和 UI 可达性未验证
目标/射程预约、自动索敌、扇形/圆形追加目标 NetPlay/PlayerSkill/Quickbar 部分等价;SetFlyTargetInstance、动作朝向、资源目标数量和发送顺序尚无完整包序证据
skill/group/quickslot 回包刷新 UI native signals → Quickbar/SkillUI 基本等价;已有单元测试,但没有真实乱序、拒绝和重连场景

已执行测试

  • project/player_skill_test.gdPASS;覆盖三层技能合法性校验和 CanUseSkill 五类动作门。
  • project/skill_test.gd:PASS;覆盖技能表、技能页和快捷栏基础链。
  • project/netplay_test.gdPASS;覆盖 GUILD_WAR_NONE/ON_WAR/WAIT_START 到公会技状态门的映射。
  • project/test_skill_inventory_parity.gd:45 项 PASS;覆盖技能门控、库存/装备、角色状态和 quickslot 同步。
  • project/test_taskbar_quickslot_parity.gd34/34 PASS;覆盖任务栏、4×8 页面和鼠标技能模式;有 Godot ObjectDB/RID/resource cleanup 警告。
  • project/test_status_skill_reset_parity.gd:32/32 PASS;覆盖技能重置相关状态变化。
  • project/mobile_gesture_test.gd:PASS;覆盖移动端技能拖放、瞄准和快捷栏交换。
  • project/playable_combat_test.gd:0 failures;覆盖离线可玩技能请求、冷却、目标、资源和断线。
  • project/guild_war_skill_test.gd:PASS;覆盖公会技能页、公会战状态和 use_guild_skill UI 入口。
  • build/extension/net_entity_testbuild/extension/net_classic_session_testPASS;覆盖 native skill/group/quickslot/cooldown-end 协议存储和会话入口。
  • project/test_full_skill_flow.gdPASS;共享 net_client_fake.gd fixture 可启动 GameScene,并断言 FUNC_SKILL → CG_USE_SKILL 动作/意图包序。

已确认差异和未验证项

  1. Quickbar 在本地 use_skill 返回成功后立即预测冷却,缺少服务端拒绝/动作未起播/断线后的冷却回滚证据;40250 也做客户端预测,但其状态由 CPythonPlayer 的 slot 冷却对象统一管理。
  2. 当前 skill/group/quickslot parser 虽已存在,尚无 GC_SKILL_LEVEL_NEW → group → quickslot → cooldown-end 乱序、重复、跨角色和重连生命周期测试。
  3. 需要建立全部技能 ID/grade/语言/.msk 公式和动作资源索引,验证动作事件、目标追加、SendUseSkillPacket、冷却和服务端 damage 的顺序。

下一轮验证入口

  • 验证 POINT_CASTING_SPEED 在真实 points 包、重复点变更和冷却结束回包中的时序,并补 native/GDScript 端到端证据。
  • CanUseSkill 五类禁止状态接到实体真实 shape/动作字段,并验证拒绝时不发 CG_USE_SKILLCG_MOVE(FUNC_SKILL) 或 fly-target 包。
  • 用真实 GUILD_WAR_ON_WAR 回包验证公会技 ONLY_FOR_GUILD_WAR 门、CG_GUILD_USE_SKILL 和冷却结束包序。
  • 增加 skill level/group/quickslot/cooldown-end 的乱序、重复、断线、重连和换图测试,再决定是否可提升合同状态。

Active UserInterface skill-data bridge review

  • PythonSkill.hTSkillData/技能描述字段包括 target range、attribute、need weapon、状态上下限、affect description、cooldown、SP、continuation SP、motion loop count、target count、duration、motion index、grade icon 和职业/等级限制;RegisterSkill/Table/Desc 是资源权威入口。
  • 当前端有 SkillTable/quickbar/cooldown 基础,已补 casting speed、动作门和 FUNC_SKILL → CG_USE_SKILL 包序;loop count、grade/motion、技能描述公式和服务端拒绝后的冷却回滚尚未由同一数据链驱动,合同继续 PARTIAL

Implementation round 2026-09-21T14:45Z

本轮按 40250 CInstanceBase::CanUseSkill()CPythonPlayer::__RunCoolTime() 修复两处实现差异:

  • PlayerSkill.can_use_skill() 新增并按参考顺序执行 POLYMORPHEDWEARING_DRESSHOLDING_PICKAXEHORSE_CANNOT_USEGRAPHIC_CANNOT_USE 五类动作门; NetPlay.skill_context() 从实体 poly/shape/wedding_dress/parts[weapon] 及服务器动作字段注入这些状态。
  • native POINT_CASTING_SPEED=21 加入协议点位定义,M2Client.get_points() 暴露 casting_speed Quickbar._skill_cooldown()iSpd = 100 - casting_speed 的两段公式计算本地冷却遮罩和阻止时长, 默认中性值为 100,与 40250 char.cpp 的初始化一致。
  • player_skill_test 新增五类 gate 断言,skill_test 新增 casting_speed=20 → 180% 数值断言; skill_testplayer_skill_testtest_skill_inventory_parityplayable_combat_testnetplay_test 和 native 目标构建通过。

仍保持 PARTIAL:服务端拒绝/动作未起播后的冷却回滚、skill/group/quickslot/cooldown-end 乱序与跨生命周期、 完整动作资源和 damage 包序尚未闭合。

Implementation round 2026-09-20T20:45Z

本轮按 40250 CPythonPlayer::__UseSkill 的真实副作用顺序修复技能发送链:

  • Quickbar._activate_slot()activate_skill_direct() 现在先调用 cast_skill() 上行 FUNC_SKILL|motion + arg,动作成功后再调用 use_skill() 上行 CG_USE_SKILL(skill, target) 本地 skill_cast_started 也在动作状态成功后触发,扇形/圆形追加目标仍在主技能意图之后发送。
  • 补充共享 project/net_client_fake.gd,替换 test_full_skill_flow.gd 的缺失夹具;该测试现在实际装配 GameScene、执行一次技能,并断言动作包先于技能意图包。
  • playable_combat_testskill_testmobile_gesture_testplayer_skill_testtest_full_skill_flow 均通过。

仍保持 PARTIAL:服务端拒绝/动作未起播后的冷却回滚、skill/group/quickslot/cooldown-end 乱序与跨生命周期、完整动作资源和服务端 damage 包序仍需真实网络证据。

Implementation round 2026-09-20T20:52Z

本轮按 40250 ClickSkillSlot → UseGuildSkill 分支修复公会技能:

  • PlayerSkill 保留 GUILD_ROUTED 的入口分流,并新增 use_guild_skill(),执行主角动作门、技能数据、冷却和 ONLY_FOR_GUILD_WAR 检查。
  • NetPlay.skill_context() 将 native GUILD_WAR_ON_WAR=6 映射为 guild_war_active;其它公会战通知状态不会误放行公会战限定技能。
  • Quickbar 新增 _activate_guild_skill():先上行 FUNC_SKILLloop=1),再调用 M2Client.use_guild_skill(skill, 0),并复用技能冷却/动作信号;普通技能仍走 CG_USE_SKILL
  • player_skill_testskill_testguild_war_skill_test 均通过,覆盖非公会战拒绝、公会战独立包和 UI 公会技能页。

仍保持 PARTIAL:真实 GUILD_WAR_ON_WAR 回包、服务端拒绝/冷却回滚、跨生命周期乱序和完整动作/damage 证据尚未闭合。