# Character System

The Character System is an optional integration layer activated by adding `GOAPENGINE_CHARACTER` to **Project Settings → Player → Scripting Define Symbols**. It provides a `Character` MonoBehaviour and a suite of `CharacterAbility` plug-ins for movement, weapons, pickups, health, input, animation, IK, HUD, navigation, and camera. Premade GOAP actions check for this symbol at compile time and delegate to the character abilities when present.

***

## Character Component

`Character` is the root MonoBehaviour for a playable or AI-controlled character. It owns the physics body (`Rigidbody`, `CapsuleCollider`), manages ground detection, and routes `OnTriggerEnter`/`OnTriggerExit` physics events to all registered `CharacterAbility` instances.

**Runtime state properties:**

| Property      | Type      | Description                                                    |
| ------------- | --------- | -------------------------------------------------------------- |
| `IsGrounded`  | `bool`    | True when the character is touching ground this physics tick   |
| `OnAir`       | `bool`    | True when not grounded (`!IsGrounded`)                         |
| `IsJumping`   | `bool`    | True from jump impulse until descent back to ground            |
| `SlopeAngle`  | `float`   | Current surface slope in degrees (0 on flat ground)            |
| `SlopeNormal` | `Vector3` | Surface normal of the ground currently under the character     |
| `OnSlope`     | `bool`    | True when slope angle exceeds 0.5° and is within `_slopeLimit` |

**Key events:**

| Event               | Fired when                                       |
| ------------------- | ------------------------------------------------ |
| `OnGroundedChanged` | `IsGrounded` changes (arg = new value)           |
| `OnLanded`          | Character returns to ground after being airborne |
| `OnJumped`          | Jump impulse is applied                          |

**Ability access:**

```csharp
var weapon = character.GetAbility<CharacterWeapon>();
```

***

## CharacterAbility

`CharacterAbility` is the abstract base class for all character abilities. Subclasses override lifecycle methods rather than Unity messages:

| Method                     | When called                                                |
| -------------------------- | ---------------------------------------------------------- |
| `OnInit()`                 | Once, after all abilities are registered (replaces Awake)  |
| `OnUpdate()`               | Every frame (replaces Update)                              |
| `OnFixedUpdate()`          | Every fixed step (replaces FixedUpdate)                    |
| `OnDestroy()`              | When the character is destroyed                            |
| `OnTriggerEnter(Collider)` | Forwarded from `Character` when the body trigger fires     |
| `OnTriggerExit(Collider)`  | Forwarded from `Character` when a collider leaves the body |

Abilities communicate with each other via `Character.GetAbility<T>()` — no direct Inspector wiring between abilities is needed.

***

## Ability Overview

| Ability                    | Purpose                                                                                                                                                                         |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `CharacterMovement`        | Translates `CharacterInput` into `Rigidbody` force/velocity; handles slope movement                                                                                             |
| `CharacterRun`             | Multiplies movement speed when running is active; exposes `SetRunning(bool)`                                                                                                    |
| `CharacterJump`            | Applies jump impulse on input; integrates with `Character.IsJumping`                                                                                                            |
| `CharacterRotate`          | Rotates the character toward movement direction or a `LookAtPosition` target (used by auto-aim)                                                                                 |
| `CharacterInput`           | Reads `UnityEngine.Input`; exposes `MoveDirection`, `FirePressed`, `ReloadPressed`, `DiscardPressed`, `InteractPressed`, and more as frame-cached properties                    |
| `CharacterWeapon`          | Manages weapon slots, pickup rules, equip/unequip, firing, reloading, throwables, and discard                                                                                   |
| `CharacterPickUp`          | Detects `PickableBase` objects in range; drives `TryPickup` on interact or auto-collect                                                                                         |
| `CharacterHealth`          | Bridges `Health` component to GOAP state; exposes `OnDied` event and `IsDead` property                                                                                          |
| `CharacterAnimation`       | Drives an `Animator` from character state (movement speed, grounded, jumping, firing, reloading, etc.)                                                                          |
| `CharacterAnimatorIKRelay` | Relays `OnAnimatorIK` callback to `CharacterWeapon` for weapon IK blending                                                                                                      |
| `CharacterNavMeshAgent`    | Bridges `NavMeshAgent` to the character's movement system; exposes `TargetTransform` and `TargetDestination` for the active navigation target; used by AI-controlled characters |
| `CharacterHUD`             | Projects world-space health bars and name labels above characters; hides on death when configured                                                                               |
| `CharacterCameraTopDown`   | Follows and optionally orbits the character from a configurable top-down camera offset                                                                                          |

***

## Weapon System

The weapon system is managed by `CharacterWeapon`. Weapons and their world-pickup counterparts (`WeaponPickup`) live on the **same GameObject**.

### Weapon Slots

| Slot array        | Capacity | Weapon types                  |
| ----------------- | -------- | ----------------------------- |
| `_bigGunSlots`    | 2        | Rifle, Auto, Submachine, etc. |
| `_handgunSlots`   | 1        | Handgun types                 |
| `_meleeSlots`     | 1        | Melee types                   |
| `_throwableSlots` | 2        | Throwable types               |

`_weapons` is a flat combined array of `_bigGunSlots + _handgunSlots + _meleeSlots` (throwables are excluded — they are fired directly via a dedicated key).

### WeaponPickupResult

`TryReceivePickup(Weapon, out Weapon droppedWeapon)` returns one of:

| Value             | Meaning                                                                     |
| ----------------- | --------------------------------------------------------------------------- |
| `Rejected`        | No slot available and no weapon to replace; pickup is left in the world     |
| `AmmoRefill`      | Weapon already owned; only reserve ammo is added                            |
| `Accepted`        | Weapon slotted (may have displaced an existing weapon into `droppedWeapon`) |
| `AcceptedNoEquip` | Weapon slotted in a non-equipped slot (stored, not made active)             |

### Equip / Unequip

* `EquipWeapon(Weapon)` — equips and activates a weapon; calls `Weapon.Equip()` which disables the pickup collider via `WeaponPickup.SetWorldState(false)`.
* `UnequipWeapon()` — unequips the current weapon without discarding it.
* Cycle through the `_weapons` array with the switch input (next/prev).

### Discard

`DiscardWeapon(Weapon)` removes a weapon from its slot, detaches it from the rig, and places it back in the world as a live pickup:

```csharp
// Called automatically when _input.DiscardPressed is true (key G by default).
// Can also be called directly from code:
charWeapon.DiscardWeapon(charWeapon.EquippedWeapon);
```

* A throw impulse is applied via `Rigidbody.AddForce` using `_discardThrowForce` and `_discardThrowArc`.
* `WeaponPickup.SetWorldState(true)` is called on the discarded weapon, enabling its collider and starting the expiration timer.
* A fallback weapon is equipped automatically if the discarded weapon was the active one.

### Death Discard

When `_unequipOnDeath = true` (default false), all held weapons are automatically discarded when `CharacterHealth.OnDied` fires — each weapon drops into the world as a live pickup.

### Weapon.ResetState

`Weapon.ResetState()` restores the weapon to its initial state: full magazine, full reserve ammo, `WeaponState.Idle`. Used by object pools when recycling a weapon instance. Setting `_resetOnEnable = true` on `Weapon` auto-calls `ResetState()` each time the GameObject is re-enabled.

***

## Pickup System

### PickableBase

`PickableBase` is the abstract base for all world-space pickups. Add it alongside a trigger `Collider` on any item prefab.

| Member                 | Description                                                                                                       |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `TryPickup(picker)`    | Safe entry point — validates layer mask, calls `PerformPickup`, raises `OnPickedUp`, and deactivates the object   |
| `IsCollected`          | `true` from the moment `TryPickup` begins; reset by `ResetPickup()` or `OnEnable`                                 |
| `ResetPickup()`        | Clears the collected flag so the pickup can be triggered again                                                    |
| `_preventDeactivation` | Set `true` inside `PerformPickup` to block the automatic `SetActive(false)` after pickup (used by `WeaponPickup`) |
| `OnPickedUp`           | `UnityEvent<GameObject>` raised after `PerformPickup` completes                                                   |
| `_expirationTime`      | Seconds before the pickup auto-despawns (0 = never expires)                                                       |
| `_destroyOnExpire`     | When true, the GameObject is `Destroy`ed on despawn; when false it is only deactivated (pool-friendly)            |
| `StartDespawnTimer()`  | `protected` — starts the countdown; called from `OnEnable` for generic pickups                                    |
| `StopDespawnTimer()`   | `protected` — cancels a running countdown                                                                         |

### WeaponPickup

`WeaponPickup` extends `PickableBase` and overrides `OnEnable` to suppress the auto-start timer — the timer is instead driven by `SetWorldState`:

```csharp
// Place weapon in the world (enables collider, starts expiration timer):
weaponPickup.SetWorldState(true);

// Remove weapon from world (disables collider, stops timer):
weaponPickup.SetWorldState(false);
```

`WeaponPickup` calls `CharacterWeapon.TryReceivePickup` in `PerformPickup` and handles the displaced weapon drop (sets it to world state on a new position).

***

## Health & Damage

### Health

`Health` is a standalone MonoBehaviour (no `CharacterAbility` dependency). It manages a float HP value, optional invincibility, and timed death cleanup.

| Field                | Description                                                                  |
| -------------------- | ---------------------------------------------------------------------------- |
| `_maxHealth`         | Maximum HP                                                                   |
| `_invincible`        | When true, `TakeDamage` is silently ignored                                  |
| `_deactivateOnDeath` | Deactivate the GameObject after `_deathDelay` seconds (default true)         |
| `_destroyOnDeath`    | Destroy instead of deactivate (mutually exclusive with `_deactivateOnDeath`) |
| `_deathDelay`        | Seconds between death and deactivation/destruction                           |

**Events:** `OnDied` (UnityEvent), `OnHealthChanged` (float currentHP), `OnRevived`.

**Key methods:** `TakeDamage(float, GameObject source)`, `SetHealth(float)`, `Revive(float)`.

Objects that can receive damage must implement `IDamageable`. Projectiles and melee hitboxes call `TakeDamage` through `IDamageable` — the source `GameObject` is passed so the receiver can award score, trigger AI reactions, or check team membership.

### CharacterHealth

`CharacterHealth` bridges `Health` to the GOAP world state. It writes a dead state key (default `"isDead"`) into the behavior's state when `Health.OnDied` fires, enabling reactive GOAP re-planning on death.

***

## Scene Utilities — Spawner & WeaponSpawner

Both utilities live in `Scenes/Scripts/` under the `GOAPEngine.Demo` namespace.

### Spawner

General-purpose object pool for AI characters (or any GameObject).

| Field            | Description                                                       |
| ---------------- | ----------------------------------------------------------------- |
| `_prefabs`       | Array of prefabs to draw from (cycles through them)               |
| `_spawnInterval` | Seconds between spawn attempts (0 = disabled after initial spawn) |
| `_spawnArea`     | `Vector3` size of the box volume within which objects are placed  |
| `_initialCount`  | Number of objects activated at scene start                        |
| `_maxActive`     | Maximum simultaneously active objects (also the pool size)        |

`Update` automatically reclaims any object that was deactivated externally — this keeps `ActiveCount` accurate so new spawns are never permanently blocked by dead-but-un-despawned objects.

`Despawn(GameObject)` can be called from code to return an object to the pool manually.

### WeaponSpawner

Object pool specifically for world-space weapon pickups. Pre-instantiates `_maxCount` weapon instances as inactive children of the spawner, then places them in the world at randomised positions within `_spawnArea`.

| Field           | Description                                                              |
| --------------- | ------------------------------------------------------------------------ |
| `_prefabs`      | `Weapon[]` — each prefab must have `Weapon` + `WeaponPickup` on its root |
| `_spawnRate`    | Seconds between spawn attempts (0 = initial count only)                  |
| `_spawnArea`    | `Vector3` size of the spawn volume centred on the spawner                |
| `_initialCount` | Weapons activated at scene start (clamped to `_maxCount`)                |
| `_maxCount`     | Maximum simultaneously active weapons; also the pool size                |

**Pool slot availability:** a slot is free when the weapon is inactive **and** its parent is either `null` or the spawner itself. This distinguishes expired weapons (no parent) and available pool slots (parented to spawner) from inactive weapons that are stored on a character rig (parented to a rig bone).

When recycled, `Weapon.ResetState()` is called before `WeaponPickup.SetWorldState(true)` so every re-spawned weapon has a full magazine.

A green wire-cube gizmo is drawn in the Scene view to visualise the spawn area.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://beelabs-dev.gitbook.io/beelabs-docs/goap-engine/character-system.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
