The Doxygen comments were generated from the declarations, so 217 @throws lines across 21 headers read "When the corresponding validation or operation fails" and told a caller nothing beyond the status name. The @param lines were the same shape: every output was "Output destination populated by the function", every instance "Object to initialize, inspect, or modify". Rewritten against the implementations, following the pattern libakstdlib already uses: - @throws names the condition. akgl_sprite_load_json separated AKERR_KEY (absent) from AKERR_TYPE (present, wrong type) from AKERR_OUTOFBOUNDS (filename too long, or array indexed past its end), and gained AKGL_ERR_SDL and AKGL_ERR_HEAP, which it raises and never declared. - Parameters say whether they are required, what a NULL means, and what is written on a failure path. Where an argument is not checked, the doc says so: akgl_heap_next_actor's dest is a crash on NULL, not an error, and akgl_render_2d_frame_start dereferences self before testing it. - The conventions move up to the file blocks so the per-function docs stay short. json_helpers.h states once that absence is an error here and that json_t * results are borrowed; heap.h explains the pool model and the acquire asymmetry; physics.h carries the thrust/environmental/velocity table. - Struct fields, enum values, macros and exported globals are documented, including the dead ones - sprite_w/sprite_h, movetimer, p_scale and timer_gravity are read by nothing, and say so. Also fixes ten comments in error.h and audio.h that opened with /** rather than /**<, so Doxygen attached them to the following entity and rendered the text as part of the macro's value. Verified against the generated HTML. Comments only - no declaration changed. Doxygen builds clean under WARN_AS_ERROR, scripts/reindent.sh --check passes, 19/19 suites pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
494 lines
30 KiB
C
494 lines
30 KiB
C
/**
|
|
* @file tilemap.h
|
|
* @brief Loading and drawing Tiled maps: tilesets, layers, embedded actors, per-map physics.
|
|
*
|
|
* The map format is the JSON export from the Tiled editor, so the level design
|
|
* tool is somebody else's and the loader is the only thing that has to know
|
|
* about it. A map is a stack of layers -- tile grids, images, and object groups
|
|
* -- drawn over a set of tilesets, each tileset being one image cut into a grid.
|
|
*
|
|
* Three things beyond ordinary Tiled semantics are worth knowing:
|
|
*
|
|
* - **Object layers can spawn actors.** An object of type `actor` names a
|
|
* character in its custom properties, and loading the map creates and
|
|
* registers the actor rather than just recording a rectangle.
|
|
* - **A map can carry its own physics.** A `physics.model` custom property on
|
|
* the map selects a backend, and `physics.gravity.*` / `physics.drag.*` set
|
|
* its constants, so a swimming level and a walking level differ by data
|
|
* rather than by code.
|
|
* - **A map can carry a pseudo-3D perspective.** Two objects named
|
|
* `p_foreground` and `p_vanishing` in an object layer, each with a `scale`
|
|
* property, define a band down the screen over which actors are scaled --
|
|
* which is what makes a character look further away as they walk up the
|
|
* screen. akgl_tilemap_scale_actor applies it.
|
|
*
|
|
* All paths inside a map -- tileset images, layer images -- are resolved
|
|
* relative to the map file, so a map and its art move together.
|
|
*
|
|
* Note the size of akgl_Tilemap: a layer's tile grid alone is 512x512 ints, and
|
|
* a tileset's offset table is 65536 pairs. It is a megabytes-large object and
|
|
* belongs in static storage, which is where `_akgl_gamemap` puts it. Do not put
|
|
* one on the stack.
|
|
*/
|
|
|
|
#ifndef _TILEMAP_H_
|
|
#define _TILEMAP_H_
|
|
|
|
#include <limits.h>
|
|
#include <akgl/actor.h>
|
|
#include <akgl/staticstring.h>
|
|
#include <akgl/physics.h>
|
|
#include <jansson.h>
|
|
|
|
/** @brief Widest map, in tiles. Width times height is what is actually bounded. */
|
|
#define AKGL_TILEMAP_MAX_WIDTH 512
|
|
/** @brief Tallest map, in tiles. */
|
|
#define AKGL_TILEMAP_MAX_HEIGHT 512
|
|
/** @brief Layers per map. Also the number of draw passes akgl_render_2d_draw_world makes. */
|
|
#define AKGL_TILEMAP_MAX_LAYERS 16
|
|
/** @brief Tilesets per map. */
|
|
#define AKGL_TILEMAP_MAX_TILESETS 16
|
|
/** @brief Entries in a tileset's offset table. Indexed by *local* tile id, so a tileset with a high `firstgid` still starts at 0. */
|
|
#define AKGL_TILEMAP_MAX_TILES_PER_IMAGE 65536
|
|
/** @brief Longest tileset name. */
|
|
#define AKGL_TILEMAP_MAX_TILESET_NAME_SIZE 512
|
|
/** @brief Longest resolved tileset image path. */
|
|
#define AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE PATH_MAX
|
|
/** @brief Longest object name. Note that an object naming an actor is truncated at #AKGL_ACTOR_MAX_NAME_LENGTH (128) instead. */
|
|
#define AKGL_TILEMAP_MAX_OBJECT_NAME_SIZE 512
|
|
/** @brief Objects in one object layer. Not enforced by the loader -- a longer group writes past the array. */
|
|
#define AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER 128
|
|
|
|
/** @brief Object `type` for a Tiled object whose `type` field is the string "actor". */
|
|
#define AKGL_TILEMAP_OBJECT_TYPE_ACTOR 1
|
|
|
|
/** @brief Layer `type` for a Tiled `tilelayer`: a grid of tile ids. */
|
|
#define AKGL_TILEMAP_LAYER_TYPE_TILES 1
|
|
/** @brief Layer `type` for a Tiled `objectgroup`: actors and perspective markers. */
|
|
#define AKGL_TILEMAP_LAYER_TYPE_OBJECTS 2
|
|
/** @brief Layer `type` for a Tiled `imagelayer`: one image drawn at the origin. */
|
|
#define AKGL_TILEMAP_LAYER_TYPE_IMAGE 3
|
|
|
|
/** @brief One object placed in a Tiled object layer. */
|
|
typedef struct {
|
|
float x; /**< Position in map pixels, from the Tiled object. Copied onto the actor it spawns. */
|
|
float y; /**< Position in map pixels. */
|
|
int gid; /**< Global tile id, for a tile object. Not read by the loader. */
|
|
int id; /**< Tiled's object id. Not read by the loader. */
|
|
int height; /**< Height in map pixels. Read into the map's perspective bands, not into this field. */
|
|
int width; /**< Width in map pixels. Not read by the loader. */
|
|
int rotation; /**< Rotation in degrees. Not read by the loader. */
|
|
int type; /**< #AKGL_TILEMAP_OBJECT_TYPE_ACTOR, or 0 for anything else. */
|
|
bool visible; /**< Copied onto the actor. Forced to `false` for perspective markers, which are geometry rather than scenery. */
|
|
akgl_Actor *actorptr; /**< The actor this object spawned or attached to, for an actor object. `NULL` otherwise. Borrowed. */
|
|
char name[AKGL_TILEMAP_MAX_OBJECT_NAME_SIZE]; /**< Object name. For an actor object this is the registry key; for a perspective marker it is `p_foreground` or `p_vanishing`. */
|
|
} akgl_TilemapObject;
|
|
|
|
/** @brief One layer of a tilemap: a tile grid, an image, or a group of objects. */
|
|
typedef struct {
|
|
short type; /**< Which of the `AKGL_TILEMAP_LAYER_TYPE_*` kinds this is; decides which of the members below mean anything. */
|
|
float opacity; /**< 0.0 to 1.0, from Tiled. Recorded but not yet applied at draw time. */
|
|
bool visible; /**< From Tiled. Recorded but not yet consulted at draw time. */
|
|
int height; /**< Tile layer: height in tiles. Image layer: the texture's height in pixels. */
|
|
int width; /**< Tile layer: width in tiles. Image layer: the texture's width in pixels. */
|
|
int x; /**< Layer offset from Tiled. Recorded but not applied at draw time. */
|
|
int y; /**< Layer offset from Tiled. */
|
|
int id; /**< Tiled's layer id. Not the same as the index into akgl_Tilemap::layers. */
|
|
SDL_Texture *texture; /**< Image layers only: the whole layer as one texture. `NULL` otherwise. Owned by this struct. */
|
|
int data[AKGL_TILEMAP_MAX_WIDTH * AKGL_TILEMAP_MAX_HEIGHT]; /**< Tile layers only: global tile ids in row-major order. 0 means an empty cell. */
|
|
akgl_TilemapObject objects[AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER]; /**< Object layers only. */
|
|
} akgl_TilemapLayer;
|
|
|
|
/** @brief One tileset: an image cut into a grid, plus the lookup table that finds a tile in it. */
|
|
typedef struct {
|
|
int columns; /**< Tiles per row in the image. What turns a linear tile id into a row and column. */
|
|
int firstgid; /**< Global id of this tileset's first tile. Subtracting it from a map cell gives the local tile id. */
|
|
char imagefilename[AKGL_TILEMAP_MAX_TILESET_FILENAME_SIZE]; /**< Resolved absolute path to the image, from the map file's directory. */
|
|
int imageheight; /**< Image height in pixels, as declared by Tiled. */
|
|
int imagewidth; /**< Image width in pixels, as declared. */
|
|
char name[AKGL_TILEMAP_MAX_TILESET_NAME_SIZE]; /**< Tileset name from Tiled. Diagnostic only; lookups go by `firstgid`. */
|
|
SDL_Texture *texture; /**< The image as one texture. Owned by this struct. */
|
|
// Use this as a lookup table instead of storing tiles
|
|
// in individual textures to blit them from a single
|
|
// texture at runtime
|
|
// FIXME: This is probably not very efficient. For a map
|
|
// with a single tileset it makes sense. For a map with
|
|
// multiple tilesets you may have set A start at firstgid 1
|
|
// and have 1728 tiles. Set B may start at firstgid 1729 and
|
|
// have 1728 more tiles. This means Set B has 1728 empty
|
|
// tile_offsets[] entries before firstgid 1729 because of the
|
|
// way akgl_tilemap_load_tilesets() works. This is really inefficient
|
|
// and should be improved in the future, and will eventually
|
|
// lead to premature exhaustion of AKGL_TILEMAP_MAX_TILES_PER_IMAGE
|
|
// because set D or E may only have 64 tiles but they may be
|
|
// at the upper end of the array bound already because of this.
|
|
int tile_offsets[AKGL_TILEMAP_MAX_TILES_PER_IMAGE][2]; /**< Local tile id -> {x, y} pixel offset into the image. Computed once at load by akgl_tilemap_compute_tileset_offsets. */
|
|
int tilecount; /**< How many tiles the image holds. */
|
|
int tileheight; /**< Height of one tile in pixels. */
|
|
int tilewidth; /**< Width of one tile in pixels. */
|
|
int spacing; /**< Pixels between adjacent tiles in the image. */
|
|
int margin; /**< Pixels of border around the whole grid. Recorded but **not** accounted for by the offset computation. */
|
|
} akgl_Tileset;
|
|
|
|
/** @brief Represents a complete tilemap and its optional physics backend. */
|
|
typedef struct {
|
|
int tilewidth; /**< Width of one map cell in pixels. Tiles from a tileset with a different tile size are not rescaled. */
|
|
int tileheight; /**< Height of one map cell in pixels. */
|
|
int width; /**< Map width in tiles. */
|
|
int height; /**< Map height in tiles. */
|
|
int numlayers; /**< Layers actually loaded, at most #AKGL_TILEMAP_MAX_LAYERS. */
|
|
int orientation; /**< 0 = orthogonal, 1 = isometric. Always set to 0 by the loader; isometric is not implemented. */
|
|
int numtilesets; /**< Tilesets actually loaded. */
|
|
int p_foreground_y; /**< Y of the `p_foreground` marker: the row at which actors are at full size. 0 disables perspective. */
|
|
int p_vanishing_y; /**< Y of the `p_vanishing` marker: the row at which actors are smallest. 0 disables perspective. */
|
|
int p_foreground_h; /**< Height of the `p_foreground` marker object. Recorded; not used in the current rate calculation. */
|
|
int p_vanishing_h; /**< Height of the `p_vanishing` marker object. Recorded, likewise. */
|
|
float p_foreground_scale; /**< Actor scale at `p_foreground_y`. Defaults to 1.0. */
|
|
float p_vanishing_scale; /**< Actor scale at `p_vanishing_y`. Defaults to 1.0. */
|
|
float p_scale; /**< Unused. Left from an earlier formulation of the perspective maths. */
|
|
float p_rate; /**< Scale change per pixel of y between the two markers. Derived at load; what akgl_tilemap_scale_actor interpolates with. */
|
|
akgl_Tileset tilesets[AKGL_TILEMAP_MAX_TILESETS]; /**< The tilesets, in the order Tiled listed them. */
|
|
akgl_TilemapLayer layers[AKGL_TILEMAP_MAX_LAYERS]; /**< The layers, in draw order: index 0 is furthest back. */
|
|
|
|
// Different levels may have different physics.
|
|
bool use_own_physics; /**< Set when the map declared a `physics.model` property. A caller that honours it simulates through `physics` instead of the global backend. */
|
|
akgl_PhysicsBackend physics; /**< This map's own backend, valid only when `use_own_physics` is set. */
|
|
} akgl_Tilemap;
|
|
|
|
/**
|
|
* @brief Load a Tiled JSON map: physics, geometry, layers, tilesets, and any actors it spawns.
|
|
*
|
|
* Zeroes @p dest, then reads the map's own physics properties, its tile
|
|
* dimensions, its size in tiles, its layers, and its tilesets -- in that order,
|
|
* because layers reference tilesets by global id and objects reference
|
|
* characters by name. Finally, if the map carried both perspective markers, it
|
|
* works out the per-pixel scaling rate between them.
|
|
*
|
|
* Loading a map has side effects beyond @p dest: tileset and layer images are
|
|
* uploaded as textures, and every `actor` object in an object layer is created
|
|
* in the actor pool and published in #AKGL_REGISTRY_ACTOR. So the renderer, the
|
|
* pools, the registries, and the characters the map names all have to be in
|
|
* place first.
|
|
*
|
|
* @param fname Path to the map JSON. Required. Its directory becomes the root
|
|
* that every tileset and layer image inside is resolved against.
|
|
* @param dest The tilemap to fill in. Required. Zeroed first, so a map already
|
|
* loaded into it has its textures leaked rather than destroyed --
|
|
* call akgl_tilemap_release first if you are reusing one. Do not
|
|
* put one of these on the stack; see the note on this file.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p fname or @p dest is `NULL`; if the map file
|
|
* cannot be opened or does not parse, with jansson's line and text; or
|
|
* if a tileset image cannot be loaded.
|
|
* @throws AKERR_OUTOFBOUNDS If the map is larger than
|
|
* #AKGL_TILEMAP_MAX_WIDTH x #AKGL_TILEMAP_MAX_HEIGHT tiles, if it has
|
|
* more than #AKGL_TILEMAP_MAX_LAYERS layers, or if a layer's own
|
|
* declared size exceeds the same bound.
|
|
* @throws AKERR_KEY If a required key is absent, if an `actor` object has an
|
|
* empty name, or if the map names a physics model that does not exist.
|
|
* @throws AKERR_TYPE If a key is present with the wrong JSON type.
|
|
* @throws ENOENT If @p fname or a path referenced inside it does not exist.
|
|
* @throws AKGL_ERR_SDL If a layer image cannot be loaded.
|
|
* @throws AKGL_ERR_HEAP If the string or actor pool is exhausted.
|
|
*
|
|
* @note Nothing is released on a failure part-way through: textures already
|
|
* uploaded and actors already created stay. Treat a failed load as
|
|
* needing akgl_tilemap_release and a fresh actor registry.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load(char *fname, akgl_Tilemap *dest);
|
|
/**
|
|
* @brief Draw one layer of a map, clipped to a viewport.
|
|
*
|
|
* Only the tiles that intersect @p viewport are drawn, and the tiles at its
|
|
* edges are drawn partially, so scrolling is smooth rather than snapping to the
|
|
* tile grid. An image layer ignores the viewport entirely and is drawn once at
|
|
* the origin.
|
|
*
|
|
* Drawing goes through the global `renderer`, not through a backend passed in.
|
|
*
|
|
* @param dest The map to draw from. Required. Not an output parameter
|
|
* despite the name.
|
|
* @param viewport The rectangle of the map, in map pixels, that is on screen.
|
|
* Required. Usually the global `camera`.
|
|
* @param layeridx Which layer to draw, 0 to `numlayers - 1`. **Not**
|
|
* bounds-checked: an index past the end reads a neighbouring
|
|
* layer, or past the array.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p dest or @p viewport is `NULL`.
|
|
* @throws AKERR_* Whatever the renderer's `draw_texture` raises. The first
|
|
* failure abandons the rest of the layer.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw(akgl_Tilemap *dest, SDL_FRect *viewport, int layeridx);
|
|
/**
|
|
* @brief Draw a whole tileset to the screen, tile by tile, from its offset table.
|
|
*
|
|
* A debugging tool, not a rendering path. It reconstructs the original tileset
|
|
* image out of the offsets computed at load time, so if the picture that appears
|
|
* matches the source image the offset table is right -- and if tiles are shifted
|
|
* or duplicated, it is not. The `charviewer` utility uses it.
|
|
*
|
|
* @param dest The map holding the tileset. Required. Not an output
|
|
* parameter despite the name.
|
|
* @param tilesetidx Which tileset to draw, 0 to `numtilesets - 1`.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p dest is `NULL`.
|
|
* @throws AKERR_OUTOFBOUNDS If @p tilesetidx is at or above `numtilesets`. A
|
|
* negative index is not rejected.
|
|
* @throws AKERR_* Whatever the renderer's `draw_texture` raises.
|
|
*
|
|
* @note Tiles are laid out using the *map's* tile size rather than the
|
|
* tileset's, so a tileset whose tiles are a different size from the map's
|
|
* is reconstructed at the wrong pitch.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_draw_tileset(akgl_Tilemap *dest, int tilesetidx);
|
|
|
|
/*
|
|
* These functions are part of the internal API and should not be called by the user.
|
|
* They are only exposed here for unit testing.
|
|
*/
|
|
|
|
/**
|
|
* @brief Find one entry in a Tiled `properties` array by name, checking its declared type.
|
|
*
|
|
* Tiled does not store custom properties as JSON object members. It stores them
|
|
* as an array of `{"name": ..., "type": ..., "value": ...}` objects, so getting
|
|
* one means a linear scan and a type-string comparison rather than a lookup.
|
|
* That is what this does; the `akgl_get_json_properties_*` wrappers put a typed
|
|
* face on it.
|
|
*
|
|
* @param obj The Tiled object -- map, layer, or object -- whose `properties`
|
|
* array to search. Required.
|
|
* @param key The property name to find. Required.
|
|
* @param type The type string Tiled wrote, e.g. `"string"`, `"int"`, `"float"`.
|
|
* A property found under the right name but the wrong type is an
|
|
* error, not a miss. Not `NULL`-checked.
|
|
* @param dest Receives a borrowed pointer to the whole property object -- the
|
|
* one with `name`, `type`, and `value` -- not to its value. Not
|
|
* `NULL`-checked.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
|
|
* @throws AKERR_KEY If @p obj has no `properties` array, or if no entry in it
|
|
* has that name.
|
|
* @throws AKERR_TYPE If `properties` is not an array, an entry is malformed, or
|
|
* the named property's declared type is not @p type. That last message
|
|
* reports both the expected and the actual type.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_tilemap_property(json_t *obj, char *key, char *type, json_t **dest);
|
|
/**
|
|
* @brief Read a Tiled custom property declared as `string`.
|
|
* @param obj The Tiled object whose `properties` array to search. Required.
|
|
* @param key The property name. Required.
|
|
* @param dest Receives the value in a *newly claimed* pool string. Required. Note
|
|
* that this always claims -- unlike akgl_get_json_string_value it
|
|
* does not reuse a string already in `*dest`, so passing one leaks
|
|
* it. The caller releases the result with akgl_heap_release_string.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
|
|
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
|
|
* @throws AKERR_TYPE If the property is not declared `string`.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_string(json_t *obj, char *key, akgl_String **dest);
|
|
/**
|
|
* @brief Read a Tiled custom property declared as `int`.
|
|
* @param obj The Tiled object whose `properties` array to search. Required.
|
|
* @param key The property name. Required.
|
|
* @param dest Receives the value. Not written on any failure path.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p obj or @p key is `NULL`.
|
|
* @throws AKERR_KEY If the property is absent, or @p obj has no properties.
|
|
* @throws AKERR_TYPE If the property is not declared `int`, or its `value` is
|
|
* not an integer.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_get_json_properties_integer(json_t *obj, char *key, int *dest);
|
|
/**
|
|
* @brief Build a tileset's local-id-to-pixel-offset table.
|
|
*
|
|
* Run once per tileset at load time so drawing a tile is an array lookup rather
|
|
* than a division. Walks the grid left to right and top to bottom, stepping by
|
|
* tile size plus `spacing`.
|
|
*
|
|
* @param dest The map holding the tileset. Required in practice, though
|
|
* not checked -- it is dereferenced immediately.
|
|
* @param tilesetidx Which tileset, 0 to `numtilesets - 1`. Not bounds-checked.
|
|
* @return `NULL`. There is no failure path: it is arithmetic into an already
|
|
* allocated table.
|
|
*
|
|
* @note Two known limits. `margin` is not accounted for, so a tileset image with
|
|
* a border produces offsets shifted by it. And the table is indexed by
|
|
* local id from 0, while akgl_tilemap_draw indexes it by
|
|
* `tilenum - firstgid`, so the entries a second tileset needs sit at the
|
|
* front of its own table -- see the FIXME on akgl_Tileset::tile_offsets
|
|
* for why that wastes space.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_compute_tileset_offsets(akgl_Tilemap *dest, int tilesetidx);
|
|
/**
|
|
* @brief Load one object layer: spawn its actors and record its perspective markers.
|
|
*
|
|
* Two kinds of object are understood. `actor` objects name a registered actor,
|
|
* or create one -- claiming it from the pool, initializing it, and binding the
|
|
* character named in its `character` property -- and place it at the object's
|
|
* coordinates on this layer. `perspective` objects named `p_foreground` or
|
|
* `p_vanishing` set the map's scaling band and are themselves invisible.
|
|
* Anything else is recorded and otherwise ignored.
|
|
*
|
|
* An actor named by two objects is not duplicated: the second takes another
|
|
* reference on the first.
|
|
*
|
|
* @param dest The map to load into. Required.
|
|
* @param root The layer's JSON object -- not the map root. Required.
|
|
* @param layerid Index of the layer being loaded, which becomes each spawned
|
|
* actor's `layer`. Not bounds-checked.
|
|
* @param dirname Directory to resolve paths against. Accepted for signature
|
|
* consistency with the other layer loaders; nothing here uses it.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p dest or @p root is `NULL`.
|
|
* @throws AKERR_KEY If the layer has no `objects` array, if a required key is
|
|
* absent, or if an `actor` object has an empty name.
|
|
* @throws AKERR_TYPE If a key is present with the wrong JSON type.
|
|
* @throws AKERR_OUTOFBOUNDS If the array is indexed past its end.
|
|
* @throws AKGL_ERR_HEAP If the actor or string pool is exhausted.
|
|
* @throws AKERR_* Whatever akgl_actor_initialize or akgl_actor_set_character
|
|
* raises -- notably AKERR_KEY if the named character is not registered.
|
|
*
|
|
* @warning The object count is not checked against
|
|
* #AKGL_TILEMAP_MAX_OBJECTS_PER_LAYER, so a layer with more than 128
|
|
* objects writes past the array.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_objects(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname);
|
|
/**
|
|
* @brief Load one tile layer's grid of global tile ids.
|
|
*
|
|
* @param dest The map to load into. Required.
|
|
* @param root The layer's JSON object -- not the map root. Required.
|
|
* @param layerid Index of the layer being loaded. Not bounds-checked.
|
|
* @param dirname Directory to resolve paths against. Required, though a tile
|
|
* layer references no files and nothing here uses it.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p dest, @p root, or @p dirname is `NULL`.
|
|
* @throws AKERR_OUTOFBOUNDS If the layer's declared `width` times `height`
|
|
* reaches #AKGL_TILEMAP_MAX_WIDTH x #AKGL_TILEMAP_MAX_HEIGHT, or if the
|
|
* `data` array is shorter than that product.
|
|
* @throws AKERR_KEY If `height`, `width`, or `data` is absent.
|
|
* @throws AKERR_TYPE If one of them has the wrong JSON type, or a cell is not an
|
|
* integer.
|
|
*
|
|
* @note The declared `width` and `height` are trusted over the actual length of
|
|
* `data`: a layer declaring more cells than it lists fails on the read
|
|
* past the end rather than on a length check.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layer_tile(akgl_Tilemap *dest, json_t *root, int layerid, akgl_String *dirname);
|
|
/**
|
|
* @brief Load every layer of a map, dispatching on each layer's declared type.
|
|
*
|
|
* Reads the common fields -- id, opacity, visibility, offset -- then hands the
|
|
* layer to the loader for its kind: `objectgroup`, `tilelayer`, or `imagelayer`.
|
|
* A layer of any other type keeps its common fields and is otherwise skipped,
|
|
* with `type` left at 0.
|
|
*
|
|
* @param dest The map to load into. Required. `numlayers` is set from the
|
|
* array's length before any bound is checked, so it may briefly
|
|
* exceed #AKGL_TILEMAP_MAX_LAYERS on the failure path.
|
|
* @param root The map's root JSON object. Required.
|
|
* @param dirname Directory to resolve layer image paths against. Required.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p dest, @p root, or @p dirname is `NULL`.
|
|
* @throws AKERR_OUTOFBOUNDS If the map has more than #AKGL_TILEMAP_MAX_LAYERS
|
|
* layers.
|
|
* @throws AKERR_KEY If the map has no `layers` array, or a layer is missing a
|
|
* common field.
|
|
* @throws AKERR_TYPE If a field has the wrong JSON type.
|
|
* @throws AKERR_* Whatever the per-kind loader raises.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_layers(akgl_Tilemap *dest, json_t *root, akgl_String *dirname);
|
|
/**
|
|
* @brief Load one tileset's metadata and upload its image.
|
|
*
|
|
* @param tileset The tileset's JSON object. Required in practice, though not
|
|
* checked -- it is passed straight to the accessors, which
|
|
* report it.
|
|
* @param dest The map to load into. Required, unchecked, dereferenced at once.
|
|
* @param tsidx Which slot to fill, 0 to #AKGL_TILEMAP_MAX_TILESETS - 1. Not
|
|
* bounds-checked.
|
|
* @param dirname Directory to resolve the tileset's `image` path against --
|
|
* the map file's own directory. Required.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If a required key is missing its object, or if the
|
|
* tileset image fails to load. The image message carries
|
|
* `SDL_GetError()`.
|
|
* @throws AKERR_KEY If one of `columns`, `firstgid`, `imageheight`,
|
|
* `imagewidth`, `margin`, `spacing`, `tilecount`, `tileheight`,
|
|
* `tilewidth`, `name`, or `image` is absent.
|
|
* @throws AKERR_TYPE If one of them has the wrong JSON type.
|
|
* @throws AKERR_OUTOFBOUNDS If the resolved image path is too long for a pooled
|
|
* string.
|
|
* @throws ENOENT If the image path does not exist.
|
|
* @throws AKGL_ERR_HEAP If the string pool is exhausted.
|
|
*
|
|
* @note This does not go through the spritesheet registry, so a tileset image
|
|
* shared between two maps is loaded twice.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_tilesets_each(json_t *tileset, akgl_Tilemap *dest, int tsidx, akgl_String *dirname);
|
|
/**
|
|
* @brief Load every tileset of a map and compute each one's offset table.
|
|
*
|
|
* @param dest The map to load into. Required. `numtilesets` is reset to 0 and
|
|
* incremented as each tileset succeeds, so it always reflects
|
|
* what actually loaded.
|
|
* @param root The map's root JSON object. Required.
|
|
* @param dirname Directory to resolve tileset image paths against. Required in
|
|
* practice; not checked here.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p dest or @p root is `NULL`.
|
|
* @throws AKERR_KEY If the map has no `tilesets` array.
|
|
* @throws AKERR_TYPE If `tilesets` is not an array of objects.
|
|
* @throws AKERR_* Whatever akgl_tilemap_load_tilesets_each raises.
|
|
*
|
|
* @warning The tileset count is not checked against
|
|
* #AKGL_TILEMAP_MAX_TILESETS, so a map with more than 16 tilesets
|
|
* writes past the array.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_load_tilesets(akgl_Tilemap *dest, json_t *root, akgl_String *dirname);
|
|
/**
|
|
* @brief Destroy the textures a map owns.
|
|
*
|
|
* Call before reusing a tilemap struct for a different map; akgl_tilemap_load
|
|
* zeroes rather than releases, so without this the previous map's textures are
|
|
* leaked.
|
|
*
|
|
* @param dest The map to release. Required.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p dest is `NULL`.
|
|
*
|
|
* @warning Known defect: the layer loop destroys `tilesets[i].texture` rather
|
|
* than `layers[i].texture`, so tileset textures are destroyed twice and
|
|
* image-layer textures never. Nothing is set to `NULL` either, so a
|
|
* second call is a use-after-free. It also does not release the actors
|
|
* the map's object layers created. TODO.md, "Known and still open"
|
|
* item 2.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_release(akgl_Tilemap *dest);
|
|
/**
|
|
* @brief Scale an actor for its distance up the screen, using the map's perspective band.
|
|
*
|
|
* The pseudo-3D trick: an actor at or below `p_foreground_y` is drawn at
|
|
* `p_foreground_scale`, one at or above `p_vanishing_y` at `p_vanishing_scale`,
|
|
* and one in between is interpolated linearly. A map with no perspective markers
|
|
* has both scales at 1.0 and a rate of 0, so this is a no-op rather than a
|
|
* special case.
|
|
*
|
|
* Only the actor's `scale` is written; nothing is drawn. akgl_game_update calls
|
|
* it before each actor's update when #AKGL_ITERATOR_OP_TILEMAPSCALE is set.
|
|
*
|
|
* @param map The map supplying the perspective band. Required.
|
|
* @param actor The actor to scale. Required. Its `y` is read and its `scale`
|
|
* written.
|
|
* @return `NULL` on success, otherwise an error context owned by the caller.
|
|
* @throws AKERR_NULLPOINTER If @p map or @p actor is `NULL`.
|
|
*/
|
|
akerr_ErrorContext AKERR_NOIGNORE *akgl_tilemap_scale_actor(akgl_Tilemap *map, akgl_Actor *actor);
|
|
|
|
#endif //_TILEMAP_H_
|