Files
libakgl/include/akgl/actor.h
Andrew Kesterson 19530f6a97 Fix the type, macro and state-table defects, and the leftover debris
Closes internal-consistency items 19 through 36, 38 and 41. Item 37, the ~180
redundant casts, is deliberately left open with its reasoning in TODO.md: the
benefit only arrives once the build turns on the warnings those casts suppress,
and doing it before that is churn across the two files with the most
outstanding functional defects.

The two that were real bugs are in the actor state table.
AKGL_ACTOR_STATE_STRING_NAMES was declared [AKGL_ACTOR_MAX_STATES+1] and
defined [32], so a consumer trusting the declared bound read past the object;
and indices 11 and 12 were named UNDEFINED_11 and UNDEFINED_12 where actor.h
has MOVING_IN and MOVING_OUT, so no character JSON could bind a sprite to
either state. tests/registry.c now walks the whole table -- every entry
non-NULL, every entry resolving to its own bit, no two entries sharing a name.

The bitmask macros are parenthesized and AKGL_BITMASK_CLEAR has lost the
semicolon inside its body. Writing tests/bitmasks.c for that turned up
something worth knowing: the obvious test does not catch it. For a bit that is
set, the misparse `!(mask & bit) == bit` gives the same answer as the correct
one. It only diverges for an unset bit whose value is not 1, and that is the
shape the suite uses now.

akgl_draw_background was the last public function outside the error protocol.
It takes a backend like everything else in draw.h, restores the draw colour it
found, and is tested -- TODO.md had it filed under "needs the offscreen
renderer harness", which was never true; what it needed was to stop reading the
global.

All eight registry initializers go through one helper, so the seven that leaked
an SDL_PropertiesID on every call after the first no longer do. Fixed in the
same place because it is the same function: akgl_registry_init never called
akgl_registry_init_properties, which made akgl_set_property a silent no-op for
anyone not going through akgl_game_init -- Defects, Known and still open item 3.

Also: AKGL_COLLIDE_RECTANGLES (three open parens, two closes) and akgl_Frame
deleted, float32_t/float64_t used consistently, the developer-specific debug
logging removed from the controller inner loop, the abandoned SDL_GetBasePath
comments removed, nine unused locals removed, and dst renamed to dest.

akgl_game_update's default flags no longer OR the same bit twice. That changes
nothing today, and the reason is Performance item 32: the loop never reads
either bit, which is why every actor is updated sixteen times a frame. Still
open.

25/25 pass, memcheck clean, reindent --check clean.

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

456 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 ----
//
// The trailing column is the bit pattern within its own 16-bit half -- the
// low half here, the high half below -- not the full 32-bit value. Read as a
// whole word it looks like bit 16 restarts at bit 0, which it does not.
#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
/**
* @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 -- which was the case for `MOVING_IN` and `MOVING_OUT` until 0.5.0.
*
* Maintained by hand in `src/actor_state_string_names.c`, and exactly
* #AKGL_ACTOR_MAX_STATES entries long: it was declared one longer than it was
* defined until 0.5.0, so a consumer trusting the declared bound read past the
* object. `tests/registry.c` checks every entry against its bit.
*/
extern char *AKGL_ACTOR_STATE_STRING_NAMES[AKGL_ACTOR_MAX_STATES];
/** @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_