- item_tooltip_view.gd: 新增 avoid_rect 属性,tooltip 与装备窗口重叠时自动推到左侧 - inventory_ui.gd: 悬停装备时传入窗口矩形作为避让区域 - 包含其他累积的功能开发和测试文件
9.6 KiB
9.6 KiB
combat.target_selection_rules —— 目标选择、可攻击判定、目标框和安全区
状态:PARTIAL
本合同对照 40250 Windows 客户端的三套不能合并的判据:
CInstanceBase::IsAttackableInstance:是否允许战斗逻辑攻击目标。CInstanceBase::CanPickInstance/IsTargetableInstance:是否允许鼠标/脚本设置目标。CPythonNetworkStream::RecvTargetPacket+CanViewTargetHP:服务端目标包到达后,目标框是否关闭、显示血量或保持。
40250 参考调用链
战斗判定
UserInterface/InstanceBase.cpp:2147CInstanceBase::IsAttackableInstance- 主角观战模式先拒绝;自身 VID 先拒绝。
- 石头只能被 PC 攻击。
- PC 目标按决斗、同公会、killer、PK mode、队伍、同/异帝国、PVP/GVG、善恶冲突逐分支判断。
- PC 可攻击敌怪、木门;敌怪可攻击 PC/建筑;变身实例按
IsPoly分支判断。
UserInterface/PythonPlayer.cpp:207CPythonPlayer::__Update_AutoAttack- 自动攻击目标必须存在、未死亡、骑乘等级允许,并通过
IsAttackableInstance。
- 自动攻击目标必须存在、未死亡、骑乘等级允许,并通过
UserInterface/PythonPlayerInput.cpp:324-370__OnClickActor- 点击实体后先清预约;攻击目标再检查
__CanAttack、距离和 NPC 交互分支。
- 点击实体后先清预约;攻击目标再检查
UserInterface/PythonPlayerInput.cpp:336-380__OnPressActor- 目标切换、自动攻击、弓箭射程、预约靠近和最终
IsAttackableInstance的顺序固定。
- 目标切换、自动攻击、弓箭射程、预约靠近和最终
目标可选中判定
UserInterface/PythonPlayerInput.cpp:112CPythonPlayer::SetTarget- 先检查主角
CanChangeTarget()。 - 已选中目标有强制/非强制切换时间窗。
- 新目标必须通过主角
IsTargetableInstance()。 - 目标成功时设置 actor targeted 状态、飞行目标和网络
SendTargetPacket;失败时清空本地目标。
- 先检查主角
UserInterface/InstanceBase.cpp:2251IsTargetableInstance- 直接委托目标的
CanPickInstance()。
- 直接委托目标的
UserInterface/InstanceBase.cpp:2263CanPickInstance- 视锥外拒绝。
- 死亡门;死亡门对门对象单独处理。
- PC 的隐身、复活隐身、普通隐身按 affect 和主角可见性拒绝。
目标框和安全区
UserInterface/PythonNetworkStreamPhaseGame.cpp:2424RecvTargetPacket- 目标或主角实例缺失:关闭目标框。
- 目标已死亡:保持当前目标框状态,不写入新的实例目标。
- PC/建筑:
CloseTargetBoardIfDifferent。 - 石头/木门/敌怪:
SetHPTargetBoard。 - 其它实体:关闭目标框。
UserInterface/InstanceBase.cpp:2293CanViewTargetHP- 只有石头、木门和敌怪能显示 HP 目标框。
UserInterface/InstanceBase.cpp:560IsInSafe- 通过地图
ATTRIBUTE_BANPK判定安全区;它与“能否选目标”和“目标框能否显示”是独立判据。
- 通过地图
UserInterface/PythonPlayer.cpp:496-520NotifyCharacterDead/Update- 当前目标死亡时清目标;目标更新后不再
CanPickInstance时清目标并关闭目标框。
- 当前目标死亡时清目标;目标更新后不再
当前端实现映射
project/entity_rules.gdis_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.gdclassify、can_view_target_hp、异阵营过滤、Ctrl 悄悄话和 HP 百分比边界已独立镜像。
project/player_controller.gd- 使用射线和可点选节点进行实体命中;射线与悬停两条路径均消费 NetWorld 写入的
can_pick门。
- 使用射线和可点选节点进行实体命中;射线与悬停两条路径均消费 NetWorld 写入的
project/net_world.gd- 负责目标光圈、目标/悬停名字强显和实体节点生命周期,并在实体创建/更新时同步
can_pick。
- 负责目标光圈、目标/悬停名字强显和实体节点生命周期,并在实体创建/更新时同步
project/alignment_pk_system.gd- 提供善恶值、PK 头衔和死亡掉落辅助逻辑;这些不是 40250 客户端
SetTarget的替代实现。
- 提供善恶值、PK 头衔和死亡掉落辅助逻辑;这些不是 40250 客户端
分支等价矩阵
| 参考判据/副作用 | 当前实现 | 结论 |
|---|---|---|
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 权威和真实服务端目标包顺序尚未闭合。
已确认差异和未验证项
- 真实完整
.msa的get_events()、动作结束和缺失动作资源下的CanChangeTargetlive 时序仍需验证。 - 网络实体更新没有完全对应 40250
NotifyCharacterUpdate的统一目标失效回调;隐身、出视野/淡出和迟到更新仍可能在不同入口留下目标框或光圈状态。 - 当前安全区来自实体字段
in_safe,尚未证明与地图ATTRIBUTE_BANPK在移动、跨图和服务端状态更新时保持同一数据权威。 - 当前端的
_is_attackable在 helper 层提前检查dead,而 40250 的IsAttackableInstance本身不检查死亡;虽然主要调用点另有死亡门,但该职责边界尚未统一。 OnTargeted/OnUntargeted、飞行目标和SendTargetPacket的完整副作用顺序,以及真实服务端目标包拒绝/乱序,仍无 live fixture。
下一轮验证入口
- 增加目标生命周期测试:存活 → 隐身 → 普通死亡 → 删除/裁剪 → 迟到更新,逐步检查目标 VID、目标框、光圈、名字强显和
CG_TARGET(0)。 - 增加完整
.msa事件源的CanChangeTarget/动作锁/重复点击时间窗测试,并对照_change_target_to_picked_instance的实际调用链。 - 增加地图属性
ATTRIBUTE_BANPK与in_safe的移动、换图、目标切换矩阵。 - 只有以上差异修复并通过 native/classic 与场景回归后,才能将本合同从
PARTIAL提升。