141 lines
4.6 KiB
Markdown
141 lines
4.6 KiB
Markdown
# Entities
|
|
|
|
Source: `src/dusk/rpg/entity/`
|
|
|
|
---
|
|
|
|
## Storage
|
|
|
|
Entities live in a single fixed global array:
|
|
|
|
```c
|
|
entity_t ENTITIES[ENTITY_COUNT]; // ENTITY_COUNT = 64
|
|
```
|
|
|
|
A slot is "empty" when `entity->type == ENTITY_TYPE_NULL`. Never allocate entity memory dynamically — always find a free slot with `entityGetAvailable()`, which returns its index (`0xFF` if none free).
|
|
|
|
---
|
|
|
|
## `entity_t` structure
|
|
|
|
```c
|
|
typedef struct entity_s {
|
|
uint8_t id; // index in ENTITIES[]
|
|
entitytype_t type; // ENTITY_TYPE_NULL / PLAYER / NPC
|
|
entitytypedata_t data; // union: player_t | npc_t
|
|
|
|
entitydir_t direction; // facing direction (N/S/E/W)
|
|
fixed_t position[3]; // current sub-tile position (x, y, z)
|
|
fixed_t lastPosition[3]; // position before last move (for interpolation)
|
|
|
|
entityanim_t animation; // IDLE / TURN / WALK
|
|
fixed_t animTime; // countdown timer for current animation
|
|
|
|
entityinteract_t interact; // optional interact component
|
|
} entity_t;
|
|
```
|
|
|
|
---
|
|
|
|
## Type system
|
|
|
|
Entity types are defined in `entitytype.h` using the enum+integer-typedef pattern:
|
|
|
|
```c
|
|
typedef enum { ENTITY_TYPE_NULL, ENTITY_TYPE_PLAYER, ENTITY_TYPE_NPC, ENTITY_TYPE_COUNT } entitytype_enum_t;
|
|
typedef uint8_t entitytype_t;
|
|
```
|
|
|
|
Each type has a `entitycallback_t` entry in the `ENTITY_CALLBACKS[ENTITY_TYPE_COUNT]` static table:
|
|
|
|
```c
|
|
typedef struct {
|
|
void (*init)(entity_t *entity);
|
|
void (*movement)(entity_t *entity);
|
|
bool_t (*interact)(entity_t *player, entity_t *entity);
|
|
} entitycallback_t;
|
|
```
|
|
|
|
Callbacks not applicable to a type are `NULL`; `entityUpdate()` guards against this before calling.
|
|
|
|
Type-specific data sits in `entitytypedata_t` (a union of `player_t` and `npc_t`). Currently both are stubs (`void *nothing`).
|
|
|
|
---
|
|
|
|
## Direction (`entitydir.h`)
|
|
|
|
```c
|
|
ENTITY_DIR_NORTH / EAST / SOUTH / WEST
|
|
```
|
|
|
|
Aliases: `UP = NORTH`, `DOWN = SOUTH`, `LEFT = WEST`, `RIGHT = EAST`.
|
|
|
|
Utilities:
|
|
- `entityDirGetOpposite(dir)` — returns the opposite direction.
|
|
- `entityDirGetRelative(dir, &relX, &relY)` — fills in the ±1 XY delta for that direction.
|
|
- `assertValidEntityDir(dir, msg)` — assertion macro.
|
|
|
|
---
|
|
|
|
## Animation (`entityanim.h`)
|
|
|
|
```c
|
|
ENTITY_ANIM_IDLE // standing still
|
|
ENTITY_ANIM_TURN // turning to a new direction (ENTITY_ANIM_TURN_DURATION = FIXED(0.06))
|
|
ENTITY_ANIM_WALK // mid-step (ENTITY_ANIM_WALK_DURATION = FIXED(0.1))
|
|
```
|
|
|
|
`entityAnimUpdate(entity)` decrements `animTime` each frame and transitions back to `IDLE` when it reaches zero.
|
|
|
|
`entityCanWalk(entity)` / `entityCanTurn(entity)` both return true only when `animation == ENTITY_ANIM_IDLE`.
|
|
|
|
The renderer interpolates between `lastPosition` and `position` using `animTime / WALK_DURATION` to produce smooth motion even at low frame rates.
|
|
|
|
---
|
|
|
|
## Movement
|
|
|
|
`entityWalk(entity, direction)`:
|
|
|
|
1. Converts `entity->position` to a `worldpos_t` (truncates fractional part).
|
|
2. Applies the directional delta to get `newPos`.
|
|
3. Checks the current and target tiles for ramp raise/fall logic (see [world.md](world.md)).
|
|
4. Checks `ENTITIES[]` for another entity occupying `newPos` — blocks if found.
|
|
5. On success: copies `position` to `lastPosition`, updates `position` to `newPos` (via `worldPosToFixed`), sets `animation = ENTITY_ANIM_WALK`.
|
|
|
|
`entityTurn(entity, direction)`: sets `direction` and starts a brief turn animation.
|
|
|
|
---
|
|
|
|
## Interaction (`entityinteract.h`)
|
|
|
|
The `entityinteract_t` component is embedded in every entity. It is optional — set `type = ENTITY_INTERACT_NULL` for non-interactable entities.
|
|
|
|
```c
|
|
typedef enum {
|
|
ENTITY_INTERACT_NULL,
|
|
ENTITY_INTERACT_CUTSCENE, // plays a cutscene_t *
|
|
ENTITY_INTERACT_PRINT, // prints a short message[32]
|
|
} entityinteracttype_t;
|
|
```
|
|
|
|
`entityInteractWith(player, target)` dispatches:
|
|
1. If the interact component's `type != NULL`, handles it (starts the cutscene or prints the message).
|
|
2. Otherwise falls back to `ENTITY_CALLBACKS[type].interact` if set.
|
|
|
|
---
|
|
|
|
## Player (`player.h` / `player.c`)
|
|
|
|
`playerInit()` is called via `ENTITY_CALLBACKS[ENTITY_TYPE_PLAYER].init`.
|
|
|
|
`playerInput(entity)` is the movement callback. It reads `PLAYER_INPUT_DIR_MAP[]` — a static table mapping input actions (`INPUT_ACTION_UP/DOWN/LEFT/RIGHT`) to entity directions — and calls `entityWalk` or `entityTurn` accordingly.
|
|
|
|
The player entity is normally `ENTITIES[0]` but there is no hardcoded assumption about its index beyond being initialized with `ENTITY_TYPE_PLAYER`.
|
|
|
|
---
|
|
|
|
## NPC (`npc.h` / `npc.c`)
|
|
|
|
`npcInit()`, `npcMovement()`, and `npcInteract()` provide the NPC type callbacks. Currently stubs; movement does nothing, interact returns false.
|