Docs
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user