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>
126 lines
9.9 KiB
Markdown
126 lines
9.9 KiB
Markdown
# combat.target_selection_rules —— 目标选择、可攻击判定、目标框和安全区
|
||
|
||
状态:`PARTIAL`
|
||
|
||
本合同对照 40250 Windows 客户端的三套不能合并的判据:
|
||
|
||
1. `CInstanceBase::IsAttackableInstance`:是否允许战斗逻辑攻击目标。
|
||
2. `CInstanceBase::CanPickInstance` / `IsTargetableInstance`:是否允许鼠标/脚本设置目标。
|
||
3. `CPythonNetworkStream::RecvTargetPacket` + `CanViewTargetHP`:服务端目标包到达后,目标框是否关闭、显示血量或保持。
|
||
|
||
## 40250 参考调用链
|
||
|
||
### 战斗判定
|
||
|
||
- `UserInterface/InstanceBase.cpp:2147` `CInstanceBase::IsAttackableInstance`
|
||
- 主角观战模式先拒绝;自身 VID 先拒绝。
|
||
- 石头只能被 PC 攻击。
|
||
- PC 目标按决斗、同公会、killer、PK mode、队伍、同/异帝国、PVP/GVG、善恶冲突逐分支判断。
|
||
- PC 可攻击敌怪、木门;敌怪可攻击 PC/建筑;变身实例按 `IsPoly` 分支判断。
|
||
- `UserInterface/PythonPlayer.cpp:207` `CPythonPlayer::__Update_AutoAttack`
|
||
- 自动攻击目标必须存在、未死亡、骑乘等级允许,并通过 `IsAttackableInstance`。
|
||
- `UserInterface/PythonPlayerInput.cpp:324-370` `__OnClickActor`
|
||
- 点击实体后先清预约;攻击目标再检查 `__CanAttack`、距离和 NPC 交互分支。
|
||
- `UserInterface/PythonPlayerInput.cpp:336-380` `__OnPressActor`
|
||
- 目标切换、自动攻击、弓箭射程、预约靠近和最终 `IsAttackableInstance` 的顺序固定。
|
||
|
||
### 目标可选中判定
|
||
|
||
- `UserInterface/PythonPlayerInput.cpp:112` `CPythonPlayer::SetTarget`
|
||
- 先检查主角 `CanChangeTarget()`。
|
||
- 已选中目标有强制/非强制切换时间窗。
|
||
- 新目标必须通过主角 `IsTargetableInstance()`。
|
||
- 目标成功时设置 actor targeted 状态、飞行目标和网络 `SendTargetPacket`;失败时清空本地目标。
|
||
- `UserInterface/InstanceBase.cpp:2251` `IsTargetableInstance`
|
||
- 直接委托目标的 `CanPickInstance()`。
|
||
- `UserInterface/InstanceBase.cpp:2263` `CanPickInstance`
|
||
- 视锥外拒绝。
|
||
- 死亡门;死亡门对门对象单独处理。
|
||
- PC 的隐身、复活隐身、普通隐身按 affect 和主角可见性拒绝。
|
||
|
||
### 目标框和安全区
|
||
|
||
- `UserInterface/PythonNetworkStreamPhaseGame.cpp:2424` `RecvTargetPacket`
|
||
- 目标或主角实例缺失:关闭目标框。
|
||
- 目标已死亡:保持当前目标框状态,不写入新的实例目标。
|
||
- PC/建筑:`CloseTargetBoardIfDifferent`。
|
||
- 石头/木门/敌怪:`SetHPTargetBoard`。
|
||
- 其它实体:关闭目标框。
|
||
- `UserInterface/InstanceBase.cpp:2293` `CanViewTargetHP`
|
||
- 只有石头、木门和敌怪能显示 HP 目标框。
|
||
- `UserInterface/InstanceBase.cpp:560` `IsInSafe`
|
||
- 通过地图 `ATTRIBUTE_BANPK` 判定安全区;它与“能否选目标”和“目标框能否显示”是独立判据。
|
||
- `UserInterface/PythonPlayer.cpp:496-520` `NotifyCharacterDead/Update`
|
||
- 当前目标死亡时清目标;目标更新后不再 `CanPickInstance` 时清目标并关闭目标框。
|
||
|
||
## 当前端实现映射
|
||
|
||
- `project/entity_rules.gd`
|
||
- `is_attackable_instance` 已覆盖观战、自身、PC/怪/石头/木门、PK mode、party、killer、duel、PVP/GVG 和 alignment 分支;`can_pick_instance` 统一镜像视锥、死亡、门死亡和三类隐身 affect 门。
|
||
- `project/net_play.gd`
|
||
- `_is_attackable` 拼装会话上下文并调用 `EntityRules`。
|
||
- `_on_pick` 负责 NPC/warp 交互、战斗目标选择、预约靠近和自动攻击。
|
||
- `_reserve_process_click_actor` 处理距离、攻击资格、死亡、安全区、弓箭和攻击顺序。
|
||
- `_on_target_info` 镜像 `RecvTargetPacket` 的目标框分派。
|
||
- `_change_target_to_picked_instance` 是当前统一脚本目标入口,先走 `_can_change_target`,再走重复目标时间窗和 `_is_targetable_instance`。
|
||
- `_motion_blocks_target_change` 读取当前 `.msa` 的 WARP/FLY/EFFECT_TO_TARGET 事件,缺少动作数据时按 40250 的保守门处理。
|
||
- `project/target_board.gd`
|
||
- `classify`、`can_view_target_hp`、异阵营过滤、Ctrl 悄悄话和 HP 百分比边界已独立镜像。
|
||
- `project/player_controller.gd`
|
||
- 使用射线和可点选节点进行实体命中;射线与悬停两条路径均消费 NetWorld 写入的 `can_pick` 门。
|
||
- `project/net_world.gd`
|
||
- 负责目标光圈、目标/悬停名字强显和实体节点生命周期,并在实体创建/更新时同步 `can_pick`。
|
||
- `project/alignment_pk_system.gd`
|
||
- 提供善恶值、PK 头衔和死亡掉落辅助逻辑;这些不是 40250 客户端 `SetTarget` 的替代实现。
|
||
|
||
## 分支等价矩阵
|
||
|
||
| 参考判据/副作用 | 当前实现 | 结论 |
|
||
|---|---|---|
|
||
| `IsAttackableInstance` 的 PC/怪/石头/木门/PK/决斗/PVP/GVG 分支 | `EntityRules.is_attackable_instance` | 基本等价;已有 `entity_rules_test.gd` 覆盖边界 |
|
||
| 自动攻击先检查存在、死亡、坐骑等级,再检查攻击资格 | `_on_pick`、`_reserve_process_click_actor`、自动攻击链 | 部分等价;多个调用点重复拼接门控,未由统一目标状态对象约束 |
|
||
| `SetTarget` 先执行 `CanChangeTarget` | `_can_change_target` → `_motion_blocks_target_change`,按当前 `.msa` 的 WARP/FLY/EFFECT_TO_TARGET 事件门控 | 已补齐静态/可读取动作事件路径;真实完整动作资源仍待 live 验证 |
|
||
| `SetTarget` 必须通过 `IsTargetableInstance`/`CanPickInstance` | `EntityRules.can_pick_instance` + NetWorld `can_pick` 元数据,脚本目标、射线点选和悬停共用 | 已补齐视锥字段、死亡、门死亡、EUNHYEONG/复活隐身/普通隐身门;真实视锥仍待 live 验证 |
|
||
| NPC/warp 不可攻击但可在距离内交互 | `_on_pick` 分流到 `_send_click_actor_packet` | 基本等价;真实包拒绝/冷却仍缺 live 证据 |
|
||
| `RecvTargetPacket` 的 PC/building/HP/CLOSE/NONE 分支 | `TargetBoard.classify` + `_on_target_info` | 静态分支基本等价;缺少目标死亡/更新/删除乱序时序测试 |
|
||
| `NotifyCharacterDead/Update` 清目标并关闭目标框 | `_on_entity_dead` / `_on_entity_gone` 统一 `CG_TARGET(0)` 清理;NetWorld 更新同步 `can_pick` | 死亡/删除已补齐;隐身、出视野和 stale 包时序仍待完整生命周期测试 |
|
||
| `IsInSafe` 地图属性判定 | 使用实体快照 `in_safe` | 数据来源不等价;当前端未证明由地图 `ATTRIBUTE_BANPK` 实时计算并在移动/换图时更新 |
|
||
| PC 异阵营目标框开关和 Ctrl 悄悄话 | `TargetBoard` + `net_play` | helper 分支通过,真实 UI/输入时序仍需 live 验证 |
|
||
|
||
## 已执行测试
|
||
|
||
- `project/entity_rules_test.gd`:PASS,覆盖攻击资格判定矩阵。
|
||
- `project/target_board_test.gd`:PASS,覆盖目标框分派、HP 可见性、异阵营过滤和 Ctrl 悄悄话。
|
||
- `project/test_alignment_pk_parity.gd`:35/35 PASS,覆盖善恶值和 PK 辅助逻辑。
|
||
- `project/test_click_target_effect_parity.gd`:0 failures,覆盖点击、目标光圈和飞行物地形碰撞;存在 ObjectDB/RID 清理警告。
|
||
- `project/test_auto_combat_bot_parity.gd`:22/22 PASS,覆盖自动选目标、移动、技能/普攻和拾取;主要为 fake client/helper 路径。
|
||
- `project/playable_combat_test.gd`:0 failures,覆盖可玩战斗主链。
|
||
|
||
## 本轮实现修复(2026-09-20T21:05Z)
|
||
|
||
- 按 `CPythonPlayer::SetTarget` 的顺序增加 `_can_change_target`、重复目标 1/2 秒时间窗和失败时的 `CG_TARGET(0)`;自动命中改走 `SetTarget(victim, FALSE)` 语义。
|
||
- 按 `CInstanceBase::CanPickInstance` 增加统一 `EntityRules.can_pick_instance`:视锥字段、死亡/木门死亡、EUNHYEONG、复活隐身和普通隐身均在脚本目标入口检查。
|
||
- NetWorld 在实体创建/更新时把同一结果写入 `can_pick`,PlayerController 的点击和悬停射线均消费该门,避免 UI/脚本两条路径出现不同目标。
|
||
- `netplay_test.gd` 新增技能动作锁、四类不可选目标、非强制命中换目标窗口测试;`netplay_test`、`entity_rules_test`、`playable_adapter_test`、点击目标/NetWorld/自动战斗/可玩战斗回归均通过。
|
||
|
||
本合同仍为 `PARTIAL`:完整动作资源事件、隐身/裁剪/迟到更新生命周期、地图 `ATTRIBUTE_BANPK` 权威和真实服务端目标包顺序尚未闭合。
|
||
|
||
## 已确认差异和未验证项
|
||
|
||
1. 真实完整 `.msa` 的 `get_events()`、动作结束和缺失动作资源下的 `CanChangeTarget` live 时序仍需验证。
|
||
2. 网络实体更新没有完全对应 40250 `NotifyCharacterUpdate` 的统一目标失效回调;隐身、出视野/淡出和迟到更新仍可能在不同入口留下目标框或光圈状态。
|
||
3. 当前安全区来自实体字段 `in_safe`,尚未证明与地图 `ATTRIBUTE_BANPK` 在移动、跨图和服务端状态更新时保持同一数据权威。
|
||
4. 当前端的 `_is_attackable` 在 helper 层提前检查 `dead`,而 40250 的 `IsAttackableInstance` 本身不检查死亡;虽然主要调用点另有死亡门,但该职责边界尚未统一。
|
||
5. `OnTargeted/OnUntargeted`、飞行目标和 `SendTargetPacket` 的完整副作用顺序,以及真实服务端目标包拒绝/乱序,仍无 live fixture。
|
||
|
||
## 下一轮验证入口
|
||
|
||
- 增加目标生命周期测试:存活 → 隐身 → 普通死亡 → 删除/裁剪 → 迟到更新,逐步检查目标 VID、目标框、光圈、名字强显和 `CG_TARGET(0)`。
|
||
- 增加完整 `.msa` 事件源的 `CanChangeTarget`/动作锁/重复点击时间窗测试,并对照 `_change_target_to_picked_instance` 的实际调用链。
|
||
- 增加地图属性 `ATTRIBUTE_BANPK` 与 `in_safe` 的移动、换图、目标切换矩阵。
|
||
- 只有以上差异修复并通过 native/classic 与场景回归后,才能将本合同从 `PARTIAL` 提升。
|
||
|
||
### 2026-09-22 死代码删除
|
||
|
||
以下文件只被自身测试引用、运行时从不加载,已删除;本文中以它们为实现或证据的结论作废,相关行为需按 40250 源码单元重新移植:`alignment_pk_system.gd`, `test_alignment_pk_parity.gd`, `test_auto_combat_bot_parity.gd`。
|