These files were loaded only by their own test_*_parity.gd and never by the runtime (transitively checked, incl. client_phase_integration_system). Removes 76 manifest implementation/evidence references across 18 contracts; contract docs get a note that conclusions based on them are void. Live systems kept: chest, consumable, item_attr, metin_socket, whisper_chat. Full headless suite: no new failures (7 failing + 5 headless-hanging render tests are identical before the deletion). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
155 lines
13 KiB
Markdown
155 lines
13 KiB
Markdown
# skill.player_ui_cooldown —— 技能栏、快捷键、等级、冷却和禁止条件
|
||
|
||
状态:`PARTIAL`
|
||
|
||
本合同覆盖 40250 Windows 客户端的技能数据解析、技能页加点、快捷栏持久化、技能施放前检查、目标/射程预约、冷却和服务端解锁事件。判断标准是调用顺序、禁止条件、数据来源、冷却公式和网络副作用一致,而不是只看当前端是否存在同名 `use_skill` 函数。
|
||
|
||
## 40250 参考调用链
|
||
|
||
### 技能数据和等级
|
||
|
||
- `UserInterface/PythonSkill.cpp`
|
||
- `RegisterSkillDesc`/`RegisterSkillTable` 读取技能属性、武器限制、动作、等级、目标范围、目标数量、SP/冷却公式和 `.msk` 数据。
|
||
- `SSkillData::IsCanUseSkill` 只允许非被动主动施放;`GetSkillCoolTime`、`GetTargetCount`、`GetNeedSP` 都以 `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_skill`、`use_guild_skill`、`cast_skill` 和 `NetPlay` 发起技能;冷却按 `.msk` 基础值和
|
||
`POINT_CASTING_SPEED` 的 40250 分段公式本地预测。
|
||
- `project/ui/skill_ui.gd`
|
||
- 有主动/辅助/坐骑页、技能等级/grade、加点按钮、技能拖入快捷栏和服务端 skill/group 刷新。
|
||
- `extension/src/net/entity_store.cpp`、`classic_parser.cpp`、`m2_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_SKILL`,toggle 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` | 公式主体基本等价 |
|
||
| `__RunCoolTime` 的 `POINT_CASTING_SPEED` 修正 | `M2Client.get_points()` 暴露点 21,`Quickbar._skill_cooldown` 按 40250 分段公式换算 | 基本等价;真实服务端点变更与冷却回包乱序仍待 fixture |
|
||
| 冷却开始时机:动作成功后 `SendUseSkillPacket` → `__RunCoolTime` | 当前先成功发送 `cast_skill`(`FUNC_SKILL`),再发送 `use_skill`(`CG_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.gd`:PASS;覆盖三层技能合法性校验和 `CanUseSkill` 五类动作门。
|
||
- `project/skill_test.gd`:PASS;覆盖技能表、技能页和快捷栏基础链。
|
||
- `project/netplay_test.gd`:PASS;覆盖 `GUILD_WAR_NONE/ON_WAR/WAIT_START` 到公会技状态门的映射。
|
||
- `project/test_skill_inventory_parity.gd`:45 项 PASS;覆盖技能门控、库存/装备、角色状态和 quickslot 同步。
|
||
- `project/test_taskbar_quickslot_parity.gd`:34/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_test`、`build/extension/net_classic_session_test`:PASS;覆盖 native skill/group/quickslot/cooldown-end 协议存储和会话入口。
|
||
- `project/test_full_skill_flow.gd`:PASS;共享 `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_SKILL`、`CG_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.h` 的 `TSkillData`/技能描述字段包括 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()` 新增并按参考顺序执行 `POLYMORPHED`、`WEARING_DRESS`、
|
||
`HOLDING_PICKAXE`、`HORSE_CANNOT_USE`、`GRAPHIC_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_test`、`player_skill_test`、`test_skill_inventory_parity`、`playable_combat_test`、
|
||
`netplay_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_test`、`skill_test`、`mobile_gesture_test`、`player_skill_test` 和
|
||
`test_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_SKILL`(loop=1),再调用
|
||
`M2Client.use_guild_skill(skill, 0)`,并复用技能冷却/动作信号;普通技能仍走 `CG_USE_SKILL`。
|
||
- `player_skill_test`、`skill_test`、`guild_war_skill_test` 均通过,覆盖非公会战拒绝、公会战独立包和 UI 公会技能页。
|
||
|
||
仍保持 `PARTIAL`:真实 `GUILD_WAR_ON_WAR` 回包、服务端拒绝/冷却回滚、跨生命周期乱序和完整动作/damage 证据尚未闭合。
|
||
|
||
### 2026-09-22 死代码删除
|
||
|
||
以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:`test_status_skill_reset_parity.gd`。
|