---
title: "Targeting"
description: "人物与载具目标扫描、中键锁定、六槽动作轮盘及宿主生命周期。"
---


> 用公共值类型扫描人物与载具，锁定目标后通过六槽轮盘提交动作；宿主负责配置与生命周期，游戏实体操作留在 XBase 后端。

## 模块边界与入口

入口为 `include/XBase/Targeting.h`，命名空间 `XBase::Targeting`，已由 `XBase.h` 聚合。扫描与交互实现在 `src/controllers/Targeting.cpp` 和对应三作的私有 Targeting 后端。

Targeting 不是 BulletAssist 的瞄准/子弹追踪配置，也不等于对玩家当前车辆调用 `Vehicle::`。它作用于扫描列表中的非玩家人物及非玩家当前载具。死亡人物和血量为零的车辆不进入正常候选列表。

**当前没有 `Core::Domain::Targeting`，也没有独立 `FeatureCapability::Targeting`。** Core 不分发该模块；宿主单独驱动它，用 `GetActions(kind)` 的 `supported` 判断具体动作。不要把后台菜单编辑、存档持久化或 i18n 词条管理放进后端。

## 公共接口

```cpp
std::vector<ActionInfo> GetActions(Kind kind);
void SetConfig(const Config& config);
Config GetConfig();
void SetLabels(const Labels& labels);
void Process();
void Draw();
void Shutdown();
void NotifyGameInit();
std::vector<Target> GetTargets();
bool Select(Kind kind, EntityId id);
void ClearSelection();
bool GetSelected(Target& target);
bool SetSelectedHealth(float health);
bool DeleteSelected();
```

`Kind` 为 `Ped` 或 `Vehicle`。`Target` 包含 `kind/id/modelId/position/distance/health/selected`；`id` 是不透明 `EntityId`，不要还原为游戏指针或跨存档保存。`GetTargets()` 返回扫描快照，`GetSelected()` 返回最近的选择快照，不保证对象在下一帧仍然存在。

`Select()` 只接受当前列表中存在的非零 ID，失败返回假。`SetConfig()` 会清除当前选择及交互输入状态，宿主只在配置变化时提交，不能每帧提交后又期待锁定保持。

## 配置与六槽绑定

| Config 字段 | 默认值 | 边界 |
|---|---|---|
| `enabled` | false | 总开关 |
| `drawLinks` | true | 绘制屏幕中心到目标的链路 |
| `includePeds` / `includeVehicles` | true | 扫描种类 |
| `mouseSelect` | true | 准星候选、中键锁定与轮盘交互 |
| `radius` | 80 | 世界扫描半径，约束到 5–250，非有限数退回 80 |
| `hitRadius` | 160 | 准星命中半径，约束到 40–400，后端按显示高度相对 1080 缩放 |
| `maxTargets` | 16 | 快照数量限制，约束到 1–64 |
| `pedMenu` / `vehicleMenu` | 六槽默认菜单 | `Menu = std::array<Binding, SlotCount>`，`SlotCount = 6` |

默认人物槽位顺序为 `Restore, Armour, Disarm, Kill, Bring, Teleport`；载具为 `Restore, Upright, Unlock, Ignite, Bring, Teleport`。第一槽位于轮盘顶部，其余顺时针排列。

```cpp
struct Binding {
    Action action = Action::None;
    float value = 0.0f;
    int secondary = 0;
    int tertiary = 0;
    int quaternary = 0;
    bool enabled = true;
};
```

| 参数类型 | Binding 编码 |
|---|---|
| `None` | 无参数；空槽使用 `Action::None` |
| `Number` | `value` 为数值，按 ActionInfo 的范围约束 |
| `Toggle` | `enabled` 为开关意图，不是槽位启用标志；false 可执行关闭防护/引擎等动作 |
| `Colors` | `value/secondary/tertiary/quaternary` 为四色编号，SA 使用四个，VC/III 只使用前两个 |
| `Weapon` | `value` 为武器类型，`secondary` 为弹药数，约束到 0–99999 |
| `Door` | `value` 为门索引，0–5 |
| `Seat` | `value` 为座位索引，0 是驾驶位，1 起是乘客位；实际可用座位还需实体校验 |

`ActionInfo` 提供 `action/labelKey/parameter/minimum/maximum/defaultValue/supported`。`GetActions()` 返回的条目不保证均可用，必须检查 `supported`。`SetConfig()` 会把不存在、类型不匹配或不支持的动作改为空槽；有限 `value` 按动作范围约束，非有限值改为动作默认值。`Binding{action}` 的值为零，不会自动套用 `defaultValue`，编辑器新建参数动作时应主动复制默认值。

## 动作与版本能力

| 目标/分组 | Action |
|---|---|
| 两种目标 | `None, Restore, Health, MaxHealth, Bring, Teleport, Stop, Freeze, Delete, Visible, Proofs, BulletProof, FireProof, ExplosionProof, CollisionProof, MeleeProof` |
| 人物 | `Kill, Armour, Disarm, Weapon` |
| 载具基础 | `Ignite, Upright, Unlock, Lock, Colors, Explode, Engine, Lights, Speed, WarpToSeat, EnterVehicle, Heavy, Watertight` |
| 载具车门 | `OpenDoor`，由 `VehicleDoors` 门控，当前 VC/III 不支持 |
| SA 专属标志/车门 | `PopDoor, SkidMarks, Particles, DriverTargetable, HeatSeekingTargetable, PetrolTankWeakPoint, Siren, TakeLessDamage` |
| 载具改装 | `Paintjob, AddUpgrade, RemoveUpgrade, RemoveAllUpgrades`，按 `VehiclePaintjob/VehicleUpgrades` 门控，当前仅 SA |

全部动作先受 `PedBasic` 或 `VehicleBasic` 门控；`Delete` 还查询对应删除能力，`Weapon` 查询 `WeaponGive`，`Colors` 查询 `VehicleColors`。当前三作均有目标基础后端，具体动作以运行时返回表为准，不因后端存在一个实现分支而绕过能力门控。

主要数值范围：Health 为 0–100000、MaxHealth 为 1–100000、Armour 为 0–100000、Speed 为 0–300、Paintjob 为 -1–2、Upgrade 模型为 1000–1193；武器类型上限 SA 为 43、VC 为 33、III 为 12。范围合法不代表模型可用或改装适配该车型，动作仍可能失败。

### 易混淆语义

| 动作/调用 | 实际语义 |
|---|---|
| 人物 `Restore` | 恢复到目标的登记血量上限，未登记时为 100，不复活已排除的死亡人物 |
| 载具 `Restore` | 修复车辆并恢复血量，登记上限存在时使用该上限 |
| `Ignite` / `Explode` | 前者取消火焰防护并把车辆血量设为 249 以触发燃烧；后者调用车辆爆炸操作，不是同一个动作 |
| `MaxHealth` | 设置当前血量并登记持续上限；SA 人物还写原生最大血量字段，不是单纯 UI 显示值 |
| `Bring` | 把目标拉到玩家旁边；人物有载具关联时搬动其载具，并清零移动/转动速度 |
| `Teleport` | 玩家传到目标旁边；玩家仍有载具关联时返回失败，不自动携带玩家车辆 |
| `EnterVehicle` | 进入驾驶位；已有驾驶员先移到车旁，玩家在别的车上先移出，再跨帧重试 |
| `WarpToSeat` | 进入指定空座位，不替换已有驾驶员或乘客；超过该车辆实际座位数失败 |
| 轮盘 `Action::Delete` | 执行脚本实体删除 |
| `DeleteSelected()` | 当前三作后端将选中实体血量设为零，成功后清选择；不是轮盘 Delete 的实体删除操作 |

换车等待最多处理 60 帧，超时或目标失效即丢弃请求。上车成功后更新游戏相机目标并跳切恢复，XBase 自定义 Camera 活跃时不覆盖它；接受了轮盘命令不代表最终已上车。

MaxHealth 的上限在 `Process()` 中持续校验，实体失效时移除记录；仅将 `enabled` 设为 false 不清除已登记上限，效果处理仍继续。`NotifyGameInit()` / `Shutdown()` 清空上限记录与待处理换车请求，**不恢复已经写入的实体血量、SA 最大血量、冻结、防护等动作结果**。不能把 Shutdown 当作全部动作撤销。

## 线程、输入与目标有效性

游戏线程的 `Process()` 扫描对象、刷新锁定目标、验证待处理轮盘命令并执行实体动作；Hooks 回调里的 `Draw()` 负责绘制、选择和登记轮盘意图，不在该绘制路径直接改实体。`SetSelectedHealth()` / `DeleteSelected()` 则是立即操作接口，宿主应从游戏逻辑安全点调用，不能因为内部有快照锁就从任意线程修改实体。

准星附近目标由中键边沿锁定，轮盘使用相对鼠标输入；普通扫描不接管全局鼠标与镜头，锁定轮盘时后端消费操作指针增量。右键或 Escape 取消，菜单可见、键盘输入被捕获或窗口不在前台时停止交互。Targeting 按状态管理 Hooks 中键抑制，停用/卸载时释放。

锁定目标不会因距离排序或 `maxTargets` 截断被静默换成另一对象；离开扫描半径后仍尝试通过对象池刷新。轮盘执行前再次检查种类、ID、模型与当前选择，过期输入直接丢弃。此校验不是允许永久缓存 EntityId 的保证。

`Labels::actions` 按 `Action` 枚举索引存放动作文本，`Restore` 分别使用 `heal/restore` 字段，人物/载具类型使用 `ped/vehicle`。宿主使用 `ActionInfo::labelKey` 翻译并调用 `SetLabels()`，语言切换时重新提交标签；模块不自动读取宿主语言包。

## 宿主示例

以下函数应接入宿主已有的游戏初始化、处理与卸载回调，不再创建第二套 Host 事件循环：

```cpp
#include <XBase/XBase.h>

static XBase::Hooks::DrawCallbackId g_target_draw;

void EnableTargeting() {
    XBase::Targeting::Config config;
    config.enabled = true;
    XBase::Targeting::SetConfig(config);
    g_target_draw = XBase::Hooks::RegisterDrawCallback([] {
        XBase::Targeting::Draw();
    });
}

void ProcessTargeting() {
    if (!XBase::Core::IsWorldReady()) return;
    XBase::Targeting::Process();
}

void ResetTargeting() {
    XBase::Targeting::NotifyGameInit();
}

void StopTargeting() {
    if (g_target_draw) {
        XBase::Hooks::UnregisterDrawCallback(g_target_draw);
        g_target_draw = {};
    }
    XBase::Targeting::Shutdown();
}
```

`EnableTargeting()` 只执行一次，Hooks 初始化仍归宿主。默认 `enabled = false`，忘记 SetConfig 不会显示链路。自定义菜单只修改 `GetConfig()` 返回副本中的六槽数组，再调用 SetConfig；没有公共 `ExecuteBinding()`，不要引用私有 backend 来执行动作。

## 排查路径

1. 无目标：检查 enabled、世界就绪、种类开关、radius 和每帧 Process；玩家与其当前车本来就被排除。
2. 有目标但无轮盘：检查 Draw 回调、mouseSelect、中键是否被其他所有者抑制、菜单/键盘捕获与前台窗口。
3. 自定义动作变空：查看 GetActions(kind) 的 supported、动作种类和 SetConfig 的净化结果。
4. 锁定立即丢失：是否每帧 SetConfig、对象已死亡/离池或切换菜单。
5. 换车后镜头异常：核对请求是否最终成功及自定义 Camera 是否活跃，不只看命令提交。
6. 关闭功能后血量仍受限：enabled 只停扫描交互，已登记上限由 ProcessEffects 继续处理；按上述清理边界设计宿主生命周期。