Files
libakgl/include/akgl/actor.h
Andrew Kesterson 3a262bee54 Give every exported function a declaration, and check that it stays that way
Closes internal-consistency items 7 through 15. Nineteen non-static functions
were in the ABI with no declaration anywhere, so no consumer could call them
and any consumer could collide with them.

The four gamepad_handle_* functions are the ones that mattered: controller.h
declared akgl_controller_handle_button_down and three siblings that did not
exist, so anything compiled against the header alone failed to link. The
definitions carry the declared names now, which also closes Defects -> Known
and still open item 10, and their documentation moved to the header.

The rest are either declared under a "part of the internal API" block --
akgl_game_save_actors and akgl_game_load_versioncmp, which tests/game.c had to
declare for itself, plus six tilemap loader helpers the untested-loader work
wants to reach -- or static, which is what the four save iterators and
load_objectnamemap should always have been. akgl_path_relative_from is deleted:
declared nowhere, called from nowhere, never wrote its output, and leaked a
pooled string on every call, so it closes Known and still open item 4 and item
40 by ceasing to exist.

scripts/check_api_surface.sh keeps it closed. It reads the built library's
dynamic symbol table and every public header with comments stripped, and fails
on an exported akgl_* symbol that is declared nowhere. Stripping comments is
the whole point -- four of these were mentioned in controller.h prose, which is
how they went unnoticed.

The pool-size ceilings are defined once, in heap.h, so the #ifndef override
hook fires for the first time; actor.h, sprite.h and character.h were defining
the same four unconditionally from headers heap.h includes above its own guard.
tests/header_pool_override.c fails the compile if that regresses.

Also here: (void) rather than () on the twelve no-argument entry points,
AKERR_NOIGNORE only on declarations, static helpers with the akgl_ prefix
dropped, and the six parameter-name mismatches. akgl_get_json_with_default had
its two contexts swapped rather than merely misspelled -- the incoming one was
`err` and its own was `e`, which is the name reserved for an incoming one.

24/24 pass, reindent --check clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 07:33:35 -04:00

458 lines
28 KiB
C

/**
* @file actor.h
* @brief A live thing in the world: state bits, motion, hierarchy, and behaviour hooks.
*
* An actor is an instance; the akgl_Character it points at is its template. The
* actor carries only what is unique to it -- where it is, which way it is
* moving, which animation frame it is on -- and borrows speed, acceleration and
* the state-to-sprite map from the character. That is what makes a hundred of
* one kind of thing cheap.
*
* **State is a bitmask**, not an enum. An actor is facing left *and* moving left
* *and* alive at the same time, and the whole combination is the key that
* selects a sprite (see akgl_character_sprite_get). Adding a state means adding
* a bit here *and* a name in `src/actor_state_string_names.c`, or character JSON
* cannot refer to it.
*
* **Behaviour attaches as function pointers**, not by inheritance. Every actor
* gets the library's default `updatefunc`, `renderfunc`, `facefunc`,
* `movementlogicfunc`, `changeframefunc` and `addchild` from
* akgl_actor_initialize; a game that wants a different flavour of anything
* replaces the pointer on that one actor.
*
* **Motion is split four ways** -- thrust, environmental, acceleration, and the
* velocity that is their sum -- so that a character's own walking and the
* world's gravity and drag can be reasoned about separately. See physics.h for
* the model and which fields mean what.
*
* Actors are pool objects (akgl_heap_next_actor) published in
* #AKGL_REGISTRY_ACTOR under their name.
*/
#ifndef _AKGL_ACTOR_H_
#define _AKGL_ACTOR_H_
#include <stdint.h>
#include <akgl/types.h>
#include <akgl/character.h>
// ---- LOW WORD STATUSES ----
#define AKGL_ACTOR_STATE_FACE_DOWN 1 << 0 // 1 0000 0000 0000 0001
#define AKGL_ACTOR_STATE_FACE_LEFT 1 << 1 // 2 0000 0000 0000 0010
#define AKGL_ACTOR_STATE_FACE_RIGHT 1 << 2 // 4 0000 0000 0000 0100
#define AKGL_ACTOR_STATE_FACE_UP 1 << 3 // 8 0000 0000 0000 1000
#define AKGL_ACTOR_STATE_ALIVE 1 << 4 // 16 0000 0000 0001 0000
#define AKGL_ACTOR_STATE_DYING 1 << 5 // 32 0000 0000 0010 0000
#define AKGL_ACTOR_STATE_DEAD 1 << 6 // 64 0000 0000 0100 0000
#define AKGL_ACTOR_STATE_MOVING_LEFT 1 << 7 // 128 0000 0000 1000 0000
#define AKGL_ACTOR_STATE_MOVING_RIGHT 1 << 8 // 256 0000 0001 0000 0000
#define AKGL_ACTOR_STATE_MOVING_UP 1 << 9 // 512 0000 0010 0000 0000
#define AKGL_ACTOR_STATE_MOVING_DOWN 1 << 10 // 1024 0000 0100 0000 0000
#define AKGL_ACTOR_STATE_MOVING_IN 1 << 11 // 2048 0000 1000 0000 0000
#define AKGL_ACTOR_STATE_MOVING_OUT 1 << 12 // 4096 0001 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_13 1 << 13 // 8192 0010 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_14 1 << 14 // 16384 0100 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_15 1 << 15 // 32768 1000 0000 0000 0000
// ----- HIGH WORD STATUSES -----
#define AKGL_ACTOR_STATE_UNDEFINED_16 1 << 16 // 65536 0000 0000 0000 0001
#define AKGL_ACTOR_STATE_UNDEFINED_17 1 << 17 // 131072 0000 0000 0000 0010
#define AKGL_ACTOR_STATE_UNDEFINED_18 1 << 18 // 262144 0000 0000 0000 0100
#define AKGL_ACTOR_STATE_UNDEFINED_19 1 << 19 // 524288 0000 0000 0000 1000
#define AKGL_ACTOR_STATE_UNDEFINED_20 1 << 20 // 1048576 0000 0000 0001 0000
#define AKGL_ACTOR_STATE_UNDEFINED_21 1 << 21 // 2097152 0000 0000 0010 0000
#define AKGL_ACTOR_STATE_UNDEFINED_22 1 << 22 // 4194304 0000 0000 0100 0000
#define AKGL_ACTOR_STATE_UNDEFINED_23 1 << 23 // 8388608 0000 0000 1000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_24 1 << 24 // 16777216 0000 0001 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_25 1 << 25 // 33554432 0000 0010 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_26 1 << 26 // 67108864 0000 0100 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_27 1 << 27 // 134217728 0000 1000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_28 1 << 28 // 268435456 0001 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_29 1 << 29 // 536870912 0010 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_30 1 << 30 // 1073741824 0100 0000 0000 0000
#define AKGL_ACTOR_STATE_UNDEFINED_31 1 << 31 // 2147483648 1000 0000 0000 0000
/** @brief Bits in an actor's state word. Fixed by the width of `int32_t state`. */
#define AKGL_ACTOR_MAX_STATES 32
// This is an array of strings equal to actor states from 1-32.
// This is built by a utility script and not kept in git, see
// the Makefile for lib_src/actor_state_string_names.c
/**
* @brief Bit position -> state name, as text. Index `i` names the bit `1 << i`.
*
* akgl_registry_init_actor_state_strings builds
* #AKGL_REGISTRY_ACTOR_STATE_STRINGS out of this, which is what lets character
* JSON write `"AKGL_ACTOR_STATE_FACE_LEFT"` instead of `2`. A bit whose name
* here does not match its `#define` above cannot be referred to from JSON at
* all.
*
* @warning Three known defects, all tracked in TODO.md items 24-26: the
* definition in `src/actor_state_string_names.c` is 32 entries while
* this declares 33, so reading index 32 reads past the object; bits 11
* and 12 are named `UNDEFINED_11`/`UNDEFINED_12` rather than
* `MOVING_IN`/`MOVING_OUT`; and the comment above about a generator
* script is stale -- there is no such script, and the file is tracked
* in git and maintained by hand.
*/
extern char *AKGL_ACTOR_STATE_STRING_NAMES[AKGL_ACTOR_MAX_STATES+1];
/** @brief Every facing bit. Clear this before setting one, so an actor faces exactly one way. */
#define AKGL_ACTOR_STATE_FACE_ALL (AKGL_ACTOR_STATE_FACE_DOWN | AKGL_ACTOR_STATE_FACE_LEFT | AKGL_ACTOR_STATE_FACE_RIGHT | AKGL_ACTOR_STATE_FACE_UP)
/** @brief Every planar movement bit. The two depth bits, MOVING_IN and MOVING_OUT, are not in it. */
#define AKGL_ACTOR_STATE_MOVING_ALL (AKGL_ACTOR_STATE_MOVING_LEFT | AKGL_ACTOR_STATE_MOVING_RIGHT | AKGL_ACTOR_STATE_MOVING_UP | AKGL_ACTOR_STATE_MOVING_DOWN)
/** @brief Longest actor name, including the terminator. Names are truncated, not rejected. */
#define AKGL_ACTOR_MAX_NAME_LENGTH 128
/** @brief Children one actor can carry. A child moves with its parent rather than simulating. */
#define AKGL_ACTOR_MAX_CHILDREN 8
/** @brief Represents a live actor, including state, motion, hierarchy, and behavior callbacks. */
typedef struct akgl_Actor {
uint8_t refcount; /**< Pool bookkeeping; 0 means the slot is free. One reference per parent, plus one for the registry. */
char name[AKGL_ACTOR_MAX_NAME_LENGTH]; /**< Registry key. Truncated, not rejected, if the source name is longer. */
akgl_Character *basechar; /**< The template this actor instantiates. Borrowed; no reference is taken. Required before the actor can update, render, or simulate. */
uint8_t curSpriteFrameId; /**< Index into the current sprite's `frameids`, not a frame number on the sheet. */
SDL_Time curSpriteFrameTimer; /**< When the current frame was shown. The next frame is due once the sprite's `speed` has elapsed. */
bool curSpriteReversing; /**< Walking the animation backwards, for a sprite with `loopReverse` set. */
uint32_t layer; /**< Which tilemap layer the actor is drawn and simulated on. Set from the object layer it was placed in. */
int32_t state; /**< The `AKGL_ACTOR_STATE_*` bitmask. The whole value is the key that selects a sprite. */
bool movement_controls_face; /**< When set, the default `facefunc` turns the actor to face whichever way it is moving. Clear it for an actor that aims independently. */
void *actorData; /**< The game's own per-actor data. Never read or freed by the library. */
bool visible; /**< Whether to draw at all. An actor off-camera is skipped separately, so this is for deliberate hiding. */
SDL_Time movetimer; /**< Unused. Nothing in the library reads or writes it. */
// Velocity. Combined effect of all forces acting on the actor resulting
// in energy along an axis.
float32_t vx; /**< Velocity along x, world units per second. Recomputed each step as `ex + tx`; writing it directly is overwritten. */
float32_t vy; /**< Velocity along y. On a child actor this is read as an offset from the parent instead. */
float32_t vz; /**< Velocity along z. */
// Environmental velocity. These are the forces acting on the actor by the
// environment (such as gravity and atmospheric drag)
float32_t ex; /**< Environmental velocity along x: what gravity and drag have done. Accumulates across steps. */
float32_t ey; /**< Environmental velocity along y. This is what carries a falling actor. */
float32_t ez; /**< Environmental velocity along z. */
// Thrust. Energy originating only from the actor's own acceleration on a
// given axis, before the effects of gravity and drag.
float32_t tx; /**< Thrust along x: the actor's own effort. Capped at `sx`, which is why gravity can outrun top speed and walking cannot. */
float32_t ty; /**< Thrust along y, capped at `sy`. */
float32_t tz; /**< Thrust along z, capped at `sz`. */
// Acceleration. These are borrowed from the base character object.
// A given axis resets to 0 when the actor stops moving in a given axis.
float32_t ax; /**< Acceleration along x, signed by the direction of travel. Copied from the character each step by the default movement logic. */
float32_t ay; /**< Acceleration along y, signed the same way. */
float32_t az; /**< Acceleration along z. Not set by the default movement logic. */
// Max speed. These are borrowed from the base character object.
float32_t sx; /**< Maximum thrust along x, copied from the character. */
float32_t sy; /**< Maximum thrust along y. */
float32_t sz; /**< Maximum thrust along z. */
// Position.
float32_t x; /**< Position in map pixels. For a child, an offset from the parent. */
float32_t y; /**< Position in map pixels. Also what the tilemap's perspective band scales against. */
float32_t z; /**< Depth. Carried through the simulation but not used at draw time. */
float32_t scale; /**< Draw scale, 1.0 for natural size. Written by akgl_tilemap_scale_actor, or forced to 1.0 when tilemap scaling is off. */
struct akgl_Actor *children[AKGL_ACTOR_MAX_CHILDREN]; /**< Attached actors, `NULL` in unused slots. Each holds a reference and is released with the parent. */
struct akgl_Actor *parent; /**< The actor this one is attached to, or `NULL`. A child is positioned relative to it and does not simulate on its own. */
akerr_ErrorContext AKERR_NOIGNORE *(*updatefunc)(struct akgl_Actor *obj); /**< Per-frame logic: facing, then animation frame. Defaults to akgl_actor_update. */
akerr_ErrorContext AKERR_NOIGNORE *(*renderfunc)(struct akgl_Actor *obj); /**< Per-frame draw. Defaults to akgl_actor_render. */
akerr_ErrorContext AKERR_NOIGNORE *(*facefunc)(struct akgl_Actor *obj); /**< Chooses the facing bits. Defaults to akgl_actor_automatic_face. */
akerr_ErrorContext AKERR_NOIGNORE *(*movementlogicfunc)(struct akgl_Actor *obj, float32_t dt); /**< Called by the physics step before gravity. Defaults to akgl_actor_logic_movement. May raise AKGL_ERR_LOGICINTERRUPT to skip the rest of the step. */
akerr_ErrorContext AKERR_NOIGNORE *(*changeframefunc)(struct akgl_Actor *obj, akgl_Sprite *curSprite, SDL_Time curtimems); /**< Advances the animation. Defaults to akgl_actor_logic_changeframe. */
akerr_ErrorContext AKERR_NOIGNORE *(*addchild)(struct akgl_Actor *obj, struct akgl_Actor *child); /**< Attaches a child. Defaults to akgl_actor_add_child. */
} akgl_Actor;
/**
* @brief Zero a pooled actor, name it, wire up its default behaviour, and register it.
*
* Sets `scale` to 1.0 and `movement_controls_face` to true, installs all six
* default function pointers, publishes the actor in #AKGL_REGISTRY_ACTOR, and
* takes the first reference. It does *not* set a character: the actor cannot
* update, render, or simulate until akgl_actor_set_character has run.
*
* @param obj Pooled actor to initialize, normally from akgl_heap_next_actor.
* Required. Any previous contents are discarded -- including its
* child list, so release an actor rather than reinitializing it.
* @param name Registry key, NUL-terminated. Required. Truncated at
* #AKGL_ACTOR_MAX_NAME_LENGTH. An existing entry with the same name
* is silently replaced, and the actor 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 obj or @p name is `NULL`.
* @throws AKERR_KEY If the actor cannot be written into #AKGL_REGISTRY_ACTOR --
* in practice, because akgl_registry_init has not run.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_initialize(akgl_Actor *obj, char *name);
/**
* @brief Bind an actor to a registered character, and copy its top speeds.
*
* Looks the character up by name and takes its `sx`/`sy` as this actor's speed
* limits, zeroing `ax` and `ay` so the actor starts from rest. The character is
* borrowed, not referenced: releasing it out from under a live actor leaves a
* dangling pointer.
*
* @param obj The actor to bind. Required.
* @param basecharname Registry name of the character. Required. Rebinding an
* actor mid-life is allowed, and is how a character swaps
* its whole sprite set at once.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p basecharname is `NULL`, or if no
* character is registered under that name. The last case is a lookup
* miss reported with a pointer status rather than AKERR_KEY.
*
* @note `sz` is not copied and `az` is not zeroed, so an actor's depth speed
* keeps whatever it had. The default movement logic re-copies all three
* each step, which papers over it for anything that simulates.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_set_character(akgl_Actor *obj, char *basecharname);
/**
* @brief Draw an actor's current animation frame, if it is on camera.
*
* Selects the sprite for the actor's current state, checks it against the
* camera, and blits the frame at the actor's position translated into screen
* space. A child actor is drawn at its parent's position plus its own, which is
* what makes an offset mean an offset.
*
* Several things are skipped rather than reported, because a frame is not the
* place to fail: an actor with no sprite for its state, one off camera, one with
* `visible` clear, and one whose frame index has run past its current sprite
* are all simply not drawn.
*
* @param obj The actor to draw. Required, along with its `basechar`.
* @return `NULL` on success -- including every skipped case above -- otherwise
* an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
* @throws AKERR_* Whatever akgl_spritesheet_coords_for_frame or the renderer's
* `draw_texture` raises.
*
* @note The destination height is computed from the sprite's *width*, so a
* non-square sprite is drawn square.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_render(akgl_Actor *obj);
/**
* @brief Per-frame logic: turn the actor to face its direction, then advance its animation.
*
* Calls the actor's `facefunc`, then selects the sprite for the resulting state
* and calls `changeframefunc` if that sprite's frame time has elapsed. An actor
* with no sprite registered for its current state is left alone and reported as
* success -- states change faster than art gets drawn, and a missing sprite
* should not stop the frame.
*
* @param obj The actor to update. Required, along with its `basechar`.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
* @throws AKERR_* Whatever `facefunc` or `changeframefunc` raises. Note that an
* AKERR_KEY from either is swallowed along with the missing-sprite case.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_update(akgl_Actor *obj);
/**
* @brief The default `movementlogicfunc`: turn movement bits into signed acceleration.
*
* Re-copies the character's speed limits onto the actor -- so a change to the
* character takes effect next step -- and sets `ax`/`ay` to plus or minus the
* character's acceleration according to which movement bits are set. It does not
* integrate anything; the physics backend does that.
*
* Opposing bits do not cancel: `MOVING_LEFT` wins over `MOVING_RIGHT` because it
* is tested first. An actor with no movement bits keeps its previous
* acceleration rather than being zeroed, which is why the input handlers clear
* it themselves on key release.
*
* @param obj The actor to compute acceleration for. Required, along with its
* `basechar`.
* @param dt Seconds since the previous step. Accepted for the hook's signature;
* this implementation does not use it, since it sets an acceleration
* rather than integrating one.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
*
* @note A replacement for this hook may raise AKGL_ERR_LOGICINTERRUPT to tell
* the physics step to skip the rest of this actor's tick. This
* implementation never does.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_logic_movement(akgl_Actor *obj, float32_t dt);
/**
* @brief The default `changeframefunc`: step to the next animation frame.
*
* Walks forward through the sprite's frames; at the last one it either wraps to
* 0, or -- for a sprite with both `loop` and `loopReverse` -- turns round and
* walks back down, turning again at frame 0. A sprite with `loop` clear also
* wraps to 0 rather than holding the final frame.
*
* @param obj The actor whose frame index to advance. Required.
* @param curSprite The sprite currently selected for the actor, whose frame
* count and loop flags decide what happens at the end.
* Required in practice, and **not** checked -- a `NULL` here is
* a crash, not an error.
* @param curtimems The current clock reading. Accepted for the hook's signature;
* this implementation does not use it, because the caller has
* already decided the frame is due.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_logic_changeframe(akgl_Actor *obj, akgl_Sprite *curSprite, SDL_Time curtimems);
/**
* @brief The default `facefunc`: turn the actor to face whichever way it is moving.
*
* Clears every facing bit and sets the one matching the first movement bit it
* finds, in the order left, right, up, down -- so an actor moving diagonally
* faces the horizontal component. An actor with `movement_controls_face` clear
* is left alone entirely, which is the hook for something that aims
* independently of the way it walks.
*
* @param obj The actor to turn. Required.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
*
* @note An actor that has stopped moving is left with no facing bit at all,
* rather than keeping the way it was last facing -- so its state no longer
* matches any "standing still facing left" sprite. The implementation
* carries a TODO saying as much.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_automatic_face(akgl_Actor *obj);
/**
* @brief Attach one actor to another so it moves with it.
*
* A child is positioned relative to its parent rather than simulated: the
* physics step snaps it to the parent's position plus its own velocity fields,
* used here as a fixed offset. That is what a carried lantern or a turret on a
* tank wants.
*
* The parent takes a reference on the child, and releasing the parent releases
* every child with it.
*
* @param obj The parent. Required.
* @param child The actor to attach. Required. Must not already have a parent --
* this builds a tree, not a graph. Its `x`/`y` become an offset
* from the parent's position rather than a world position.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p child is `NULL`.
* @throws AKERR_RELATIONSHIP If @p child already has a parent. Detach it first --
* though note there is no detach function, so in practice this means
* releasing and rebuilding it.
* @throws AKERR_OUTOFBOUNDS If @p obj already has #AKGL_ACTOR_MAX_CHILDREN
* children.
*
* @warning Nothing checks for a cycle. Making an actor its own ancestor makes
* akgl_heap_release_actor recurse until the stack runs out.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_add_child(akgl_Actor *obj, akgl_Actor *child);
/*
* The control-map handler functions ("cmhf"). These are what
* akgl_controller_default binds the arrow keys and the D-pad to, and what a
* game's own akgl_Control bindings can point at. They are called with the
* control map's target actor, not with a global "player", so they work for any
* number of locally controlled actors.
*
* Each pair is symmetric: the `_on` handler starts movement in a direction and
* turns the actor to face it, and the `_off` handler stops it dead -- zeroing
* acceleration, thrust, environmental velocity and velocity on that axis, so
* there is no coasting. That is an arcade feel rather than a physical one, and
* it is deliberate; a game wanting momentum binds its own handlers instead.
*
* An `_on` handler clears *every* facing and movement bit before setting its
* own, so holding two directions at once moves in whichever was pressed last
* rather than diagonally.
*/
/**
* @brief Start the actor moving left, and turn it to face left.
* @param obj The actor to move, supplied by the control map. Required, along
* with its `basechar`, whose acceleration is negated onto `ax`.
* @param event The event that triggered this. Required, but not read -- the
* binding has already decided what the event means.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving left, zeroing everything on the x axis.
* @param obj The actor to stop. Required. `basechar` is not needed here.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
* @note It clears the x axis whichever way the actor was going, so releasing
* left while holding right also stops the rightward movement.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_left_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving right, and turn it to face right.
* @param obj The actor to move. Required, along with its `basechar`.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving right, zeroing everything on the x axis.
* @param obj The actor to stop. Required.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_right_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving up the screen, and turn it to face up.
* @param obj The actor to move. Required, along with its `basechar`, whose y
* acceleration is negated -- y grows downward.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving up, zeroing everything on the y axis.
* @param obj The actor to stop. Required.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
* @note This also zeroes `ey`, so an actor under gravity has its accumulated
* fall cancelled by releasing a movement key.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_up_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Start the actor moving down the screen, and turn it to face down.
* @param obj The actor to move. Required, along with its `basechar`.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj, @p event, or `obj->basechar` is `NULL`.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_on(akgl_Actor *obj, SDL_Event *event);
/**
* @brief Stop the actor moving down, zeroing everything on the y axis.
* @param obj The actor to stop. Required.
* @param event The event that triggered this. Required, but not read.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER If @p obj or @p event is `NULL`.
* @note Zeroes `ey` as well; see akgl_actor_cmhf_up_off.
*/
akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_cmhf_down_off(akgl_Actor *obj, SDL_Event *event);
/**
* @brief `SDL_EnumerateProperties` callback that applies an akgl_Iterator to one registered actor.
*
* Runs the operations the iterator asks for, in a fixed order regardless of the
* order the bits were set: layer filter, update, tilemap scaling, render. With
* #AKGL_ITERATOR_OP_LAYERMASK set, an actor on any other layer is skipped
* entirely; with #AKGL_ITERATOR_OP_TILEMAPSCALE clear, `scale` is forced to 1.0
* rather than left alone.
*
* @param userdata The akgl_Iterator carrying the operation flags, passed through
* by `SDL_EnumerateProperties`. Required despite the `void *`.
* @param registry #AKGL_REGISTRY_ACTOR, supplied by SDL.
* @param name The actor's registry key. 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. An actor whose `updatefunc` fails therefore terminates the
* game rather than skipping a frame. Install your own
* `akerr_handler_unhandled_error` if that is not what you want.
*/
void akgl_registry_iterate_actor(void *userdata, SDL_PropertiesID registry, const char *name);
#endif // _AKGL_ACTOR_H_