Files
dusk/.claude/rpg/entity.md
T
2026-06-18 14:59:21 -05:00

4.6 KiB

Entities

Source: src/dusk/rpg/entity/


Storage

Entities live in a single fixed global array:

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

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:

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:

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)

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)

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).
  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.

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.