/** * @file character.h * @brief The reusable half of an actor: movement constants and a state-to-sprite map. * * A character is the template; an akgl_Actor is the instance. Everything that is * the same for every goblin on the map -- top speed, acceleration, frame dwell * time, and which sprite to draw for which combination of actor-state bits -- * lives here and is shared by pointer, so a hundred actors cost one character. * * Characters are pool objects (akgl_heap_next_character) and are published in * the #AKGL_REGISTRY_CHARACTER property registry under their name, which is how * akgl_actor_set_character finds them. akgl_registry_init must have run first. */ #ifndef _AKGL_CHARACTER_H_ #define _AKGL_CHARACTER_H_ #include #include #include #define AKGL_CHARACTER_MAX_NAME_LENGTH 128 /** @brief Defines reusable movement parameters and actor-state sprite bindings. */ typedef struct akgl_Character { uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. */ char name[AKGL_CHARACTER_MAX_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */ SDL_PropertiesID state_sprites; /**< State bitmask (decimal, as a string) -> akgl_Sprite *. */ uint64_t speedtime; /**< Nanoseconds one sprite frame is held before advancing. Read from JSON in milliseconds and scaled by #AKGL_TIME_ONEMS_NS. */ float32_t ax; /**< Acceleration along x, world units per second squared. Copied into an actor by akgl_actor_set_character. */ float32_t ay; /**< Acceleration along y. */ float32_t az; /**< Acceleration along z. Not read from JSON; stays 0 unless set by hand. */ float32_t sx; /**< Maximum speed along x, world units per second. */ float32_t sy; /**< Maximum speed along y. */ float32_t sz; /**< Maximum speed along z. Not read from JSON; stays 0 unless set by hand. */ akerr_ErrorContext AKERR_NOIGNORE *(*sprite_add)(struct akgl_Character *, akgl_Sprite *, int); /**< Bound to akgl_character_sprite_add by akgl_character_initialize. */ akerr_ErrorContext AKERR_NOIGNORE *(*sprite_get)(struct akgl_Character *, int, akgl_Sprite **); /**< Bound to akgl_character_sprite_get by akgl_character_initialize. */ } akgl_Character; /** * @brief Zero a pooled character, name it, and publish it in the character registry. * * Wipes the struct, copies @p name into it, creates the empty state-to-sprite * property set, binds the `sprite_add`/`sprite_get` function pointers, inserts * the character into #AKGL_REGISTRY_CHARACTER under @p name, and takes the first * reference. Everything numeric (speeds, accelerations, `speedtime`) is left at * zero for the caller -- or akgl_character_load_json -- to fill in. * * @param obj Pooled character to initialize, normally straight from * akgl_heap_next_character. Required. Any previous contents are * discarded without releasing the sprites they referenced. * @param name Registry key, NUL-terminated. Required. Truncated at * #AKGL_CHARACTER_MAX_NAME_LENGTH. A name already in the * registry is silently replaced, and the character it displaced * becomes unreachable rather than being released. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p basechar or @p name is `NULL`, or if SDL * cannot create the state-to-sprite property set (out of memory). * @throws AKERR_KEY If the character cannot be written into * #AKGL_REGISTRY_CHARACTER -- in practice, because akgl_registry_init * has not run and the registry id is still 0. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_character_initialize(akgl_Character *obj, char *name); /** * @brief Bind a sprite to one exact combination of actor-state bits. * * The map key is the decimal spelling of @p state, so lookups match the *whole* * value: a sprite added for `FACE_LEFT|MOVING_LEFT` is not found by a lookup for * `FACE_LEFT` alone. Callers are expected to register every combination they * intend to draw. Adding a sprite takes a reference on it. * * @param basechar Character to add the mapping to. Required. Must already have * been through akgl_character_initialize. * @param ref Sprite to draw for @p state. Required. Its `refcount` is * incremented, so the character keeps it alive. * @param state The exact actor-state bitmask (`AKGL_ACTOR_STATE_*`) this * sprite is for. 0 is accepted and is a usable key. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p basechar or @p ref is `NULL`. * * @note Re-adding a different sprite for a @p state that is already mapped * replaces the entry without releasing the sprite it displaced, so the * displaced sprite's reference is never given back. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_character_sprite_add(akgl_Character *basechar, akgl_Sprite *ref, int state); /** * @brief Look up the sprite bound to one exact combination of actor-state bits. * * The counterpart to akgl_character_sprite_add, and matched the same way: an * exact match on the whole @p state value, with no fallback to a subset of the * bits and no default sprite. Unlike most lookups in this codebase, *not finding * one is an error* rather than a successful "nothing here" -- an actor with no * sprite for its current state cannot be drawn. * * @param basechar Character to search. Required. * @param state The exact actor-state bitmask to look up. * @param dest Receives the mapped sprite. Required -- the return value is * spoken for by the error context. Set to `NULL` when there is * no mapping, alongside the AKERR_KEY error. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p basechar or @p dest is `NULL`. * @throws AKERR_KEY If no sprite is bound to exactly that @p state. The message * carries the state both as a decimal and as a bit pattern. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_character_sprite_get(akgl_Character *basechar, int state, akgl_Sprite **dest); // This is an SDL iterator so we can't return our error state from it. /** * @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one state-to-sprite entry. * * Currently implements exactly one operation: with #AKGL_ITERATOR_OP_RELEASE * set, it hands the mapped sprite back to its heap layer. Every other flag is * ignored here. Used to tear a character's sprite map down entry by entry. * * @param userdata The akgl_Iterator carrying the operation flags, passed through * by `SDL_EnumerateProperties`. Required despite the `void *` -- * a `NULL` here is an error, not "no operations". * @param props The character's `state_sprites` set, supplied by SDL. * @param name The property key: one state bitmask in decimal. Required. * * @warning This is an SDL callback, so it returns `void` and has nowhere to * propagate an error to. It ends in `FINISH_NORETURN`, which logs the * stack trace and then calls libakerror's unhandled-error handler -- * whose default implementation **exits the process** with the error * status. A missing sprite or a `NULL` @p userdata therefore terminates * the game rather than skipping an entry. Install your own * `akerr_handler_unhandled_error` if that is not what you want. */ void akgl_character_state_sprites_iterate(void *userdata, SDL_PropertiesID props, const char *name); /** * @brief Build a character from a JSON definition file and register it. * * Claims a character from the pool, then reads: `name` (the registry key), * `sprite_mappings` (an array of `{ "sprite": , "state": [] }` * objects), `speedtime` in seconds, `speed_x`, `speed_y`, `acceleration_x`, and * `acceleration_y`. Each mapping's `state` array is OR-ed together into one * bitmask by looking every name up in #AKGL_REGISTRY_ACTOR_STATE_STRINGS, so * `["FACE_LEFT", "MOVING_LEFT"]` becomes a single key. * * Every referenced sprite must already be in #AKGL_REGISTRY_SPRITE: this loads * characters, not sprites, so akgl_sprite_load_json runs first. On any failure * the pooled character is released again. * * @param filename Path to the JSON document. Required. Used verbatim -- it is * not resolved against `SDL_GetBasePath()`. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p filename is `NULL`; if the file cannot be * opened or does not parse (the message carries jansson's line number * and text); or if a mapping names a sprite that is not in the sprite * registry. * @throws AKERR_KEY If a `state` array contains a name that is not a known * actor state, if a required top-level key is absent, or if the * character cannot be added to the registry. * @throws AKERR_TYPE If a key is present but holds the wrong JSON type -- for * example `speedtime` as a string. The message names the key. * @throws AKERR_OUTOFBOUNDS If a `state` array is indexed past its end. * @throws AKGL_ERR_HEAP If the character pool or the string pool is exhausted. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_character_load_json(char *filename); #endif // _AKGL_CHARACTER_H_