The Doxygen comments were generated from the declarations, so 217 @throws lines across 21 headers read "When the corresponding validation or operation fails" and told a caller nothing beyond the status name. The @param lines were the same shape: every output was "Output destination populated by the function", every instance "Object to initialize, inspect, or modify". Rewritten against the implementations, following the pattern libakstdlib already uses: - @throws names the condition. akgl_sprite_load_json separated AKERR_KEY (absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS (filename too long, or array indexed past its end), and gained AKGL_ERR_SDL and AKGL_ERR_HEAP, which it raises and never declared. - Parameters say whether they are required, what a NULL means, and what is written on a failure path. Where an argument is not checked, the doc says so: akgl_heap_next_actor's dest is a crash on NULL, not an error, and akgl_render_2d_frame_start dereferences self before testing it. - The conventions move up to the file blocks so the per-function docs stay short. json_helpers.h states once that absence is an error here and that json_t * results are borrowed; heap.h explains the pool model and the acquire asymmetry; physics.h carries the thrust/environmental/velocity table. - Struct fields, enum values, macros and exported globals are documented, including the dead ones - sprite_w/sprite_h, movetimer, p_scale and timer_gravity are read by nothing, and say so. Also fixes ten comments in error.h and audio.h that opened with /** rather than /**<, so Doxygen attached them to the following entity and rendered the text as part of the macro's value. Verified against the generated HTML. Comments only - no declaration changed. Doxygen builds clean under WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
165 lines
9.8 KiB
C
165 lines
9.8 KiB
C
/**
|
|
* @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 <SDL3/SDL_properties.h>
|
|
#include "types.h"
|
|
#include "sprite.h"
|
|
|
|
#define AKGL_SPRITE_MAX_CHARACTER_NAME_LENGTH 128
|
|
#define AKGL_MAX_HEAP_CHARACTER 256
|
|
|
|
/** @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_SPRITE_MAX_CHARACTER_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_ONESEC_MS, which despite its name is nanoseconds-per-millisecond. TODO.md item 6. */
|
|
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 basechar 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_SPRITE_MAX_CHARACTER_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 *basechar, 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": <name>, "state": [<state names>] }`
|
|
* 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_
|