/** * @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 #include #include #include #include /** @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_