This commit is contained in:
2026-06-18 14:59:21 -05:00
parent 6135d60ddc
commit 730a5b2b10
17 changed files with 2825 additions and 415 deletions
+140
View File
@@ -0,0 +1,140 @@
# 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.