Add two tutorial games that build, run in CI, and cannot drift
examples/sidescroller and examples/jrpg are complete programs, built with the
library and exercised headless by ctest. The chapters quote them with
`c excerpt=examples/...` blocks rather than restating the code, so a chapter
cannot drift from a program that compiles -- the excerpt check fails the moment
the source moves. 34 excerpts in one chapter, 21 in the other.
The two are complementary. The sidescroller is the physics tutorial: gravity, a
jump, coins, hazards. The JRPG is the content-pipeline tutorial: a town map,
NPCs spawned from map objects, four-way per-facing animation, a text box, a
follower.
Both smoke tests drive real SDL_Events through akgl_controller_handle_event and
step the physics clock at a fixed 1/60s rather than sleeping, so a scripted run
is deterministic and finishes in under five seconds.
Writing them is what turned up most of the defects recorded in the next commit,
because a game exercises paths a unit test does not. Each workaround says in the
chapter which library gap forced it:
- collision is written in a custom movementlogicfunc, because
akgl_physics_arcade_collide raises AKERR_API and akgl_physics_simulate never
calls collide at all;
- the sidescroller cancels the step's own gravity when it blocks downward,
because otherwise a quarter-pixel of penetration makes the *horizontal* sweep
report blocked and the character walks backwards a tile at a time;
- both clear movement_controls_face on every map-spawned actor, because the
default facefunc leaves a stopped actor with no facing bit, no sprite, and no
draw;
- the JRPG's follower gets a renderfunc that nulls obj->parent for the duration
of the draw, because a child's offset is counted twice.
Assets are CC0 from three Kenney packs, vendored with per-pack licence text,
per-file provenance, and the geometry contract in
docs/tutorials/assets/README.md. CC0 specifically rather than merely free: a
reader who copies a tutorial into their own game inherits no obligation.
scripts/fetch_tutorial_assets.sh refreshes them in the shape
mkcontrollermappings.sh was fixed into for 0.5.0 -- it checks curl's status,
refuses a pack page that does not say CC0, verifies the archive and the staged
dimensions, and leaves the tracked bytes untouched on any failure. Both failure
paths were tested, and a no-op refresh is byte-identical.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:59:00 -04:00
|
|
|
/**
|
|
|
|
|
* @file jrpg.h
|
|
|
|
|
* @brief Shared declarations for the JRPG tutorial game.
|
|
|
|
|
*
|
|
|
|
|
* Every function with external linkage in this program is declared here, the
|
|
|
|
|
* way AGENTS.md requires of the library itself. It is a three-translation-unit
|
|
|
|
|
* program and it would compile without the header; declaring them anyway is
|
|
|
|
|
* what keeps a signature from drifting between the definition and the call.
|
|
|
|
|
*
|
|
|
|
|
* Everything this program exports carries the `jrpg_` prefix. `static` helpers
|
|
|
|
|
* drop it, because the prefix exists only to avoid collisions with libakgl,
|
|
|
|
|
* SDL and libc -- the same rule, for the same reason.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
#ifndef _JRPG_JRPG_H_
|
|
|
|
|
#define _JRPG_JRPG_H_
|
|
|
|
|
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
|
#include <akerror.h>
|
|
|
|
|
#include <akgl/actor.h>
|
Delete the JRPG's collision too, and wall the map with proxies
cell_solid and feet_blocked go, and so does the prediction in the player's
movementlogicfunc. That prediction was only ever exact because the town has zero
gravity and zero drag -- v is t, so `x + tx * dt` is where the step lands. A map
with gravity would have had to fold ey in as well, which is the game
re-implementing the integrator. Resolution runs after the move now, so there is
nothing to predict.
The map's decoration layer carries `collidable`, which retires JRPG_LAYER_SOLID:
akgl_TilemapLayer has no name member, so the game and the map had to agree on an
index out of band and inserting a layer in Tiled broke it.
The edge of the world is not on any layer, so it is four static proxies covering
the outer ring plus a tile of overhang -- one pool slot per side instead of a
hundred solid tiles, and the first use of the static-proxy path in either
example. They carry LAYER_STATIC explicitly: shape_box defaults a shape to
LAYER_ACTOR, and a wall left on that layer is a wall everything walks through.
Only the player gets a shape. NPCs stand still and are spoken to; the follower
is a child snapped to its parent every step.
Verified against the old implementation with the demo script rewritten to hold
one direction: holding left into a building stops the player at x=122 with both,
and holding up into the map's edge stops them at y=-4 with both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 07:46:19 -04:00
|
|
|
#include <akgl/collision.h>
|
Add two tutorial games that build, run in CI, and cannot drift
examples/sidescroller and examples/jrpg are complete programs, built with the
library and exercised headless by ctest. The chapters quote them with
`c excerpt=examples/...` blocks rather than restating the code, so a chapter
cannot drift from a program that compiles -- the excerpt check fails the moment
the source moves. 34 excerpts in one chapter, 21 in the other.
The two are complementary. The sidescroller is the physics tutorial: gravity, a
jump, coins, hazards. The JRPG is the content-pipeline tutorial: a town map,
NPCs spawned from map objects, four-way per-facing animation, a text box, a
follower.
Both smoke tests drive real SDL_Events through akgl_controller_handle_event and
step the physics clock at a fixed 1/60s rather than sleeping, so a scripted run
is deterministic and finishes in under five seconds.
Writing them is what turned up most of the defects recorded in the next commit,
because a game exercises paths a unit test does not. Each workaround says in the
chapter which library gap forced it:
- collision is written in a custom movementlogicfunc, because
akgl_physics_arcade_collide raises AKERR_API and akgl_physics_simulate never
calls collide at all;
- the sidescroller cancels the step's own gravity when it blocks downward,
because otherwise a quarter-pixel of penetration makes the *horizontal* sweep
report blocked and the character walks backwards a tile at a time;
- both clear movement_controls_face on every map-spawned actor, because the
default facefunc leaves a stopped actor with no facing bit, no sprite, and no
draw;
- the JRPG's follower gets a renderfunc that nulls obj->parent for the duration
of the draw, because a child's offset is counted twice.
Assets are CC0 from three Kenney packs, vendored with per-pack licence text,
per-file provenance, and the geometry contract in
docs/tutorials/assets/README.md. CC0 specifically rather than merely free: a
reader who copies a tutorial into their own game inherits no obligation.
scripts/fetch_tutorial_assets.sh refreshes them in the shape
mkcontrollermappings.sh was fixed into for 0.5.0 -- it checks curl's status,
refuses a pack page that does not say CC0, verifies the archive and the staged
dimensions, and leaves the tracked bytes untouched on any failure. Both failure
paths were tested, and a no-op refresh is byte-identical.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:59:00 -04:00
|
|
|
#include <akgl/types.h>
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Where the game's data lives. Both are absolute paths baked in by
|
|
|
|
|
* examples/jrpg/CMakeLists.txt, because the tutorial has to run from the build
|
|
|
|
|
* tree, from the source tree, and from wherever CTest happens to put its
|
|
|
|
|
* working directory. The fallbacks are what a reader compiling this by hand
|
|
|
|
|
* from the repository root would want.
|
|
|
|
|
*/
|
|
|
|
|
#ifndef JRPG_ASSET_DIR
|
|
|
|
|
#define JRPG_ASSET_DIR "docs/tutorials/assets/jrpg"
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef JRPG_FONT_FILE
|
|
|
|
|
#define JRPG_FONT_FILE "tests/assets/akgl_test_mono.ttf"
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* The screen size is written as text because that is the only form
|
|
|
|
|
* akgl_set_property takes, and akgl_render_2d_init copies it onto `akgl_camera`
|
|
|
|
|
* on the way past. Everything below reads the camera rather than a second copy
|
|
|
|
|
* of the same two numbers.
|
|
|
|
|
*/
|
|
|
|
|
#define JRPG_SCREEN_WIDTH "320"
|
|
|
|
|
#define JRPG_SCREEN_HEIGHT "240"
|
|
|
|
|
|
|
|
|
|
/** @brief Registry name of the one font this game loads. */
|
|
|
|
|
#define JRPG_FONT_NAME "jrpg"
|
|
|
|
|
/** @brief Point size the font is rasterized at. A size is baked into the handle. */
|
|
|
|
|
#define JRPG_FONT_SIZE 12
|
|
|
|
|
|
|
|
|
|
/** @brief Longest line an NPC can say, terminator included. */
|
|
|
|
|
#define JRPG_TEXTBOX_MAX_TEXT 256
|
|
|
|
|
|
|
|
|
|
/** @brief Registry name of the party member this game attaches to the player. */
|
|
|
|
|
#define JRPG_FOLLOWER_NAME "companion"
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief One entry in the scripted demo the headless smoke run drives.
|
|
|
|
|
*
|
|
|
|
|
* The keystrokes go in through akgl_controller_handle_event like any other
|
|
|
|
|
* event, so the smoke run exercises the same binding, state and physics path a
|
|
|
|
|
* player does rather than a separate one written to be testable.
|
|
|
|
|
*/
|
|
|
|
|
typedef struct {
|
|
|
|
|
long frame; /**< Frame number this step fires on. */
|
|
|
|
|
SDL_Keycode key; /**< Key to synthesize. */
|
|
|
|
|
bool down; /**< True for a press, false for a release. */
|
|
|
|
|
} jrpg_ScriptStep;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Load every sprite, then every character, then the town map.
|
|
|
|
|
*
|
|
|
|
|
* In that order, and the order is not a preference: akgl_character_load_json
|
|
|
|
|
* resolves each sprite name through #AKGL_REGISTRY_SPRITE, and
|
|
|
|
|
* akgl_tilemap_load resolves each `character` property through
|
|
|
|
|
* #AKGL_REGISTRY_CHARACTER while it spawns the map's actor objects.
|
|
|
|
|
*
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *jrpg_world_load(void);
|
Delete the JRPG's collision too, and wall the map with proxies
cell_solid and feet_blocked go, and so does the prediction in the player's
movementlogicfunc. That prediction was only ever exact because the town has zero
gravity and zero drag -- v is t, so `x + tx * dt` is where the step lands. A map
with gravity would have had to fold ey in as well, which is the game
re-implementing the integrator. Resolution runs after the move now, so there is
nothing to predict.
The map's decoration layer carries `collidable`, which retires JRPG_LAYER_SOLID:
akgl_TilemapLayer has no name member, so the game and the map had to agree on an
index out of band and inserting a layer in Tiled broke it.
The edge of the world is not on any layer, so it is four static proxies covering
the outer ring plus a tile of overhang -- one pool slot per side instead of a
hundred solid tiles, and the first use of the static-proxy path in either
example. They carry LAYER_STATIC explicitly: shape_box defaults a shape to
LAYER_ACTOR, and a wall left on that layer is a wall everything walks through.
Only the player gets a shape. NPCs stand still and are spoken to; the follower
is a child snapped to its parent every step.
Verified against the old implementation with the demo script rewritten to hold
one direction: holding left into a building stops the player at x=122 with both,
and holding up into the map's edge stops them at y=-4 with both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 07:46:19 -04:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief The collision world, owned by world.c.
|
|
|
|
|
*
|
|
|
|
|
* The physics backend resolves through it after every sub-move. Nothing else in
|
|
|
|
|
* the game touches it: the town is walls and people, and the walls are the
|
|
|
|
|
* map's own decoration layer plus four proxies around the edge.
|
|
|
|
|
*/
|
|
|
|
|
extern akgl_CollisionWorld jrpg_collision;
|
Add two tutorial games that build, run in CI, and cannot drift
examples/sidescroller and examples/jrpg are complete programs, built with the
library and exercised headless by ctest. The chapters quote them with
`c excerpt=examples/...` blocks rather than restating the code, so a chapter
cannot drift from a program that compiles -- the excerpt check fails the moment
the source moves. 34 excerpts in one chapter, 21 in the other.
The two are complementary. The sidescroller is the physics tutorial: gravity, a
jump, coins, hazards. The JRPG is the content-pipeline tutorial: a town map,
NPCs spawned from map objects, four-way per-facing animation, a text box, a
follower.
Both smoke tests drive real SDL_Events through akgl_controller_handle_event and
step the physics clock at a fixed 1/60s rather than sleeping, so a scripted run
is deterministic and finishes in under five seconds.
Writing them is what turned up most of the defects recorded in the next commit,
because a game exercises paths a unit test does not. Each workaround says in the
chapter which library gap forced it:
- collision is written in a custom movementlogicfunc, because
akgl_physics_arcade_collide raises AKERR_API and akgl_physics_simulate never
calls collide at all;
- the sidescroller cancels the step's own gravity when it blocks downward,
because otherwise a quarter-pixel of penetration makes the *horizontal* sweep
report blocked and the character walks backwards a tile at a time;
- both clear movement_controls_face on every map-spawned actor, because the
default facefunc leaves a stopped actor with no facing bit, no sprite, and no
draw;
- the JRPG's follower gets a renderfunc that nulls obj->parent for the duration
of the draw, because a child's offset is counted twice.
Assets are CC0 from three Kenney packs, vendored with per-pack licence text,
per-file provenance, and the geometry contract in
docs/tutorials/assets/README.md. CC0 specifically rather than merely free: a
reader who copies a tutorial into their own game inherits no obligation.
scripts/fetch_tutorial_assets.sh refreshes them in the shape
mkcontrollermappings.sh was fixed into for 0.5.0 -- it checks curl's status,
refuses a pack page that does not say CC0, verifies the archive and the staged
dimensions, and leaves the tracked bytes untouched on any failure. Both failure
paths were tested, and a no-op refresh is byte-identical.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:59:00 -04:00
|
|
|
/**
|
|
|
|
|
* @brief Fix up the actors the map spawned, and attach the party member.
|
|
|
|
|
*
|
|
|
|
|
* Clears `movement_controls_face` on everything -- without which the default
|
|
|
|
|
* `facefunc` erases the facing bit the map set and every NPC stops being drawn
|
|
|
|
|
* on frame one -- installs the blocking movement logic on the player, and
|
|
|
|
|
* builds the follower as a child actor.
|
|
|
|
|
*
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKGL_ERR_REGISTRY If the map spawned no actor named "player".
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *jrpg_world_populate(void);
|
|
|
|
|
/**
|
|
|
|
|
* @brief Centre the camera on the player, clamped to the edges of the map.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKGL_ERR_REGISTRY If there is no actor named "player".
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *jrpg_camera_follow(void);
|
|
|
|
|
/**
|
Delete the JRPG's collision too, and wall the map with proxies
cell_solid and feet_blocked go, and so does the prediction in the player's
movementlogicfunc. That prediction was only ever exact because the town has zero
gravity and zero drag -- v is t, so `x + tx * dt` is where the step lands. A map
with gravity would have had to fold ey in as well, which is the game
re-implementing the integrator. Resolution runs after the move now, so there is
nothing to predict.
The map's decoration layer carries `collidable`, which retires JRPG_LAYER_SOLID:
akgl_TilemapLayer has no name member, so the game and the map had to agree on an
index out of band and inserting a layer in Tiled broke it.
The edge of the world is not on any layer, so it is four static proxies covering
the outer ring plus a tile of overhang -- one pool slot per side instead of a
hundred solid tiles, and the first use of the static-proxy path in either
example. They carry LAYER_STATIC explicitly: shape_box defaults a shape to
LAYER_ACTOR, and a wall left on that layer is a wall everything walks through.
Only the player gets a shape. NPCs stand still and are spoken to; the follower
is a child snapped to its parent every step.
Verified against the old implementation with the demo script rewritten to hold
one direction: holding left into a building stops the player at x=122 with both,
and holding up into the map's edge stops them at y=-4 with both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 07:46:19 -04:00
|
|
|
* @brief The player's `movementlogicfunc`: the library default, plus the freeze.
|
Add two tutorial games that build, run in CI, and cannot drift
examples/sidescroller and examples/jrpg are complete programs, built with the
library and exercised headless by ctest. The chapters quote them with
`c excerpt=examples/...` blocks rather than restating the code, so a chapter
cannot drift from a program that compiles -- the excerpt check fails the moment
the source moves. 34 excerpts in one chapter, 21 in the other.
The two are complementary. The sidescroller is the physics tutorial: gravity, a
jump, coins, hazards. The JRPG is the content-pipeline tutorial: a town map,
NPCs spawned from map objects, four-way per-facing animation, a text box, a
follower.
Both smoke tests drive real SDL_Events through akgl_controller_handle_event and
step the physics clock at a fixed 1/60s rather than sleeping, so a scripted run
is deterministic and finishes in under five seconds.
Writing them is what turned up most of the defects recorded in the next commit,
because a game exercises paths a unit test does not. Each workaround says in the
chapter which library gap forced it:
- collision is written in a custom movementlogicfunc, because
akgl_physics_arcade_collide raises AKERR_API and akgl_physics_simulate never
calls collide at all;
- the sidescroller cancels the step's own gravity when it blocks downward,
because otherwise a quarter-pixel of penetration makes the *horizontal* sweep
report blocked and the character walks backwards a tile at a time;
- both clear movement_controls_face on every map-spawned actor, because the
default facefunc leaves a stopped actor with no facing bit, no sprite, and no
draw;
- the JRPG's follower gets a renderfunc that nulls obj->parent for the duration
of the draw, because a child's offset is counted twice.
Assets are CC0 from three Kenney packs, vendored with per-pack licence text,
per-file provenance, and the geometry contract in
docs/tutorials/assets/README.md. CC0 specifically rather than merely free: a
reader who copies a tutorial into their own game inherits no obligation.
scripts/fetch_tutorial_assets.sh refreshes them in the shape
mkcontrollermappings.sh was fixed into for 0.5.0 -- it checks curl's status,
refuses a pack page that does not say CC0, verifies the archive and the staged
dimensions, and leaves the tracked bytes untouched on any failure. Both failure
paths were tested, and a no-op refresh is byte-identical.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:59:00 -04:00
|
|
|
*
|
Delete the JRPG's collision too, and wall the map with proxies
cell_solid and feet_blocked go, and so does the prediction in the player's
movementlogicfunc. That prediction was only ever exact because the town has zero
gravity and zero drag -- v is t, so `x + tx * dt` is where the step lands. A map
with gravity would have had to fold ey in as well, which is the game
re-implementing the integrator. Resolution runs after the move now, so there is
nothing to predict.
The map's decoration layer carries `collidable`, which retires JRPG_LAYER_SOLID:
akgl_TilemapLayer has no name member, so the game and the map had to agree on an
index out of band and inserting a layer in Tiled broke it.
The edge of the world is not on any layer, so it is four static proxies covering
the outer ring plus a tile of overhang -- one pool slot per side instead of a
hundred solid tiles, and the first use of the static-proxy path in either
example. They carry LAYER_STATIC explicitly: shape_box defaults a shape to
LAYER_ACTOR, and a wall left on that layer is a wall everything walks through.
Only the player gets a shape. NPCs stand still and are spoken to; the follower
is a child snapped to its parent every step.
Verified against the old implementation with the demo script rewritten to hold
one direction: holding left into a building stops the player at x=122 with both,
and holding up into the map's edge stops them at y=-4 with both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KzBDV2fqgnUAcqCKqKvc71
2026-08-02 07:46:19 -04:00
|
|
|
* Walls are the library's now -- see chapter 15 -- so all this adds to
|
|
|
|
|
* akgl_actor_logic_movement is raising AKGL_ERR_LOGICINTERRUPT while the text
|
|
|
|
|
* box is open, which is the documented way to tell the simulator to skip an
|
|
|
|
|
* actor's tick. That also skips `collide`, since the CATCH around
|
|
|
|
|
* movementlogicfunc jumps past the whole rest of the step.
|
Add two tutorial games that build, run in CI, and cannot drift
examples/sidescroller and examples/jrpg are complete programs, built with the
library and exercised headless by ctest. The chapters quote them with
`c excerpt=examples/...` blocks rather than restating the code, so a chapter
cannot drift from a program that compiles -- the excerpt check fails the moment
the source moves. 34 excerpts in one chapter, 21 in the other.
The two are complementary. The sidescroller is the physics tutorial: gravity, a
jump, coins, hazards. The JRPG is the content-pipeline tutorial: a town map,
NPCs spawned from map objects, four-way per-facing animation, a text box, a
follower.
Both smoke tests drive real SDL_Events through akgl_controller_handle_event and
step the physics clock at a fixed 1/60s rather than sleeping, so a scripted run
is deterministic and finishes in under five seconds.
Writing them is what turned up most of the defects recorded in the next commit,
because a game exercises paths a unit test does not. Each workaround says in the
chapter which library gap forced it:
- collision is written in a custom movementlogicfunc, because
akgl_physics_arcade_collide raises AKERR_API and akgl_physics_simulate never
calls collide at all;
- the sidescroller cancels the step's own gravity when it blocks downward,
because otherwise a quarter-pixel of penetration makes the *horizontal* sweep
report blocked and the character walks backwards a tile at a time;
- both clear movement_controls_face on every map-spawned actor, because the
default facefunc leaves a stopped actor with no facing bit, no sprite, and no
draw;
- the JRPG's follower gets a renderfunc that nulls obj->parent for the duration
of the draw, because a child's offset is counted twice.
Assets are CC0 from three Kenney packs, vendored with per-pack licence text,
per-file provenance, and the geometry contract in
docs/tutorials/assets/README.md. CC0 specifically rather than merely free: a
reader who copies a tutorial into their own game inherits no obligation.
scripts/fetch_tutorial_assets.sh refreshes them in the shape
mkcontrollermappings.sh was fixed into for 0.5.0 -- it checks curl's status,
refuses a pack page that does not say CC0, verifies the archive and the staged
dimensions, and leaves the tracked bytes untouched on any failure. Both failure
paths were tested, and a no-op refresh is byte-identical.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:59:00 -04:00
|
|
|
*
|
|
|
|
|
* @param obj The actor to compute acceleration for. Required, along with its
|
|
|
|
|
* `basechar`.
|
|
|
|
|
* @param dt Seconds this step covers.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER If @p obj or `obj->basechar` is `NULL`.
|
|
|
|
|
* @throws AKGL_ERR_LOGICINTERRUPT While a conversation is open.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *jrpg_actor_logic_walk(akgl_Actor *obj, float32_t dt);
|
|
|
|
|
/**
|
|
|
|
|
* @brief The follower's `renderfunc`: akgl_actor_render with the parent detached.
|
|
|
|
|
*
|
|
|
|
|
* The guard for a defect. akgl_physics_simulate writes a child's `x` as
|
|
|
|
|
* `parent->x + vx` -- an absolute world position -- and akgl_actor_render then
|
|
|
|
|
* draws it at `parent->x + obj->x`, adding the parent's position a second time.
|
|
|
|
|
* Nulling `parent` for the duration of the draw takes the branch that does not
|
|
|
|
|
* add it. See chapter 20.
|
|
|
|
|
*
|
|
|
|
|
* @param obj The child actor to draw. Required.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER If @p obj is `NULL`.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *jrpg_follower_render(akgl_Actor *obj);
|
|
|
|
|
/**
|
|
|
|
|
* @brief Control handler: talk to whoever is standing nearby, or close the box.
|
|
|
|
|
* @param obj The actor the control map drives -- the player. 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 *jrpg_cmhf_talk(akgl_Actor *obj, SDL_Event *event);
|
|
|
|
|
/**
|
|
|
|
|
* @brief Control handler that does nothing, for the release half of a binding.
|
|
|
|
|
*
|
|
|
|
|
* akgl_controller_handle_event does not check a matched binding's handler
|
|
|
|
|
* pointer before calling it, so a binding with a `NULL` `handler_off` is a
|
|
|
|
|
* crash rather than an error. Every binding gets both halves.
|
|
|
|
|
*
|
|
|
|
|
* @param obj The actor the control map drives. Required.
|
|
|
|
|
* @param event The event that triggered this. Required.
|
|
|
|
|
* @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 *jrpg_cmhf_ignore(akgl_Actor *obj, SDL_Event *event);
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @brief Put a line of dialogue on screen and freeze the world.
|
|
|
|
|
* @param text The line to show. Required. Truncated at #JRPG_TEXTBOX_MAX_TEXT.
|
|
|
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
|
|
|
* @throws AKERR_NULLPOINTER If @p text is `NULL`.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *jrpg_textbox_open(char *text);
|
|
|
|
|
/** @brief Dismiss the text box. */
|
|
|
|
|
void jrpg_textbox_close(void);
|
|
|
|
|
/** @brief True while a line of dialogue is on screen. */
|
|
|
|
|
bool jrpg_textbox_showing(void);
|
|
|
|
|
/**
|
|
|
|
|
* @brief Draw the panel and its text, if there is anything to draw.
|
|
|
|
|
*
|
|
|
|
|
* Called after akgl_game_update, so it lands on top of the world rather than
|
|
|
|
|
* under it, and in screen coordinates rather than world ones --
|
|
|
|
|
* akgl_text_rendertextat does not go through the camera, which is what a HUD
|
|
|
|
|
* wants.
|
|
|
|
|
*
|
|
|
|
|
* @return `NULL` on success -- including when the box is closed -- otherwise an
|
|
|
|
|
* error context owned by the caller.
|
|
|
|
|
* @throws AKGL_ERR_REGISTRY If #JRPG_FONT_NAME is not a loaded font.
|
|
|
|
|
*/
|
|
|
|
|
akerr_ErrorContext AKERR_NOIGNORE *jrpg_textbox_draw(void);
|
|
|
|
|
|
|
|
|
|
#endif // _JRPG_JRPG_H_
|