/** * @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 #include #include // ---- 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. */ akgl_CollisionShape shape; /**< The collision volume this actor occupies. Copied from the character by akgl_actor_set_character unless #shape_override is set. Zeroed by akgl_actor_initialize, so an actor nobody configured does not collide. */ bool shape_override; /**< The game set #shape itself and akgl_actor_set_character must leave it alone. Distinct from "the shape is empty": an actor deliberately given no collider and an actor not yet given one are different states, and only this flag tells them apart. */ struct akgl_CollisionProxy *proxy; /**< This actor's registration in the broad phase, or `NULL` when it has none. Owned by the library and released with the actor; a game neither sets nor frees 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. */ akerr_ErrorContext AKERR_NOIGNORE *(*collidefunc)(struct akgl_Actor *obj, akgl_Contact *contact); /**< Responds to one contact. Defaults to akgl_actor_collide_block. Called once per contact found against this actor, with the normal already pointing the way this actor has to move. */ } 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 seven * 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); /** * @brief The default `collidefunc`: stop at the surface and give up the motion into it. * * Moves @p obj out along the contact normal by exactly the penetration depth, * and removes the component of its motion that was going into the surface. What * is left is the tangential part, which is what makes an actor slide along a * wall instead of sticking to it. * * @section collide_block_velocity Why this writes `e` and `t` and not `v` * * akgl_physics_simulate recomputes velocity as `e + t` at the top of every step, * so a write to `vx`/`vy`/`vz` is discarded before anything reads it. The * component has to come off the environmental and thrust terms themselves -- * `e` is where gravity accumulates and `t` is the actor's own effort -- or an * actor holding a direction into a wall keeps accumulating thrust while standing * still and leaves at speed the instant the wall ends. * * They are treated separately rather than as their sum, so an actor pressing * into a floor loses its downward gravity without losing the sideways run it is * also doing. * * @section collide_block_separating A separating contact is left alone * * If the actor is already moving out of the surface, nothing is removed. Zeroing * a separating velocity is how an actor gets stuck to a wall it is walking away * from, and it looks like the collision "grabbing" the player. * * @section collide_block_sensor A sensor is reported and never pushed * * A contact flagged as a sensor returns success having changed nothing. Walking * through a coin is not being blocked by it. * * @note This does **not** pre-load the environmental term with a step of * gravity. A resolver that runs *before* the step has to, or the step it * is correcting for immediately re-adds `gravity * dt` and commits * `gravity * dt^2` of penetration -- a quarter of a pixel at 60 Hz, which * is invisible and still fatal, because the actor is then overlapping the * floor and every horizontal move reads as blocked. This library resolves * *after* the move, so the case does not arise. The sidescroller example * carries that workaround because it had to. * * @param obj The actor to resolve. Required. * @param contact The contact. Required. Its normal points the way @p obj must * move. * @return `NULL` on success, otherwise an error context owned by the caller. * @throws AKERR_NULLPOINTER If @p obj or @p contact is `NULL`. */ akerr_ErrorContext AKERR_NOIGNORE *akgl_actor_collide_block(akgl_Actor *obj, akgl_Contact *contact); /* * 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_