Mercurial > molko
diff libmlk-rpg/mlk/rpg/tileset-loader.c @ 552:ffd972a3d084
rpg: major rewrite of tilesets
- Now tilesets can be opened using a custom allocator/loader.
- A default mlk_tileset_loader_file implementation is provided.
- Put a simple example-tileset example to demonstrate.
author | David Demelier <markand@malikania.fr> |
---|---|
date | Tue, 07 Mar 2023 20:45:00 +0100 |
parents | libmlk-rpg/mlk/rpg/tileset-file.c@c7664b679a95 |
children | cdbc13ceff85 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libmlk-rpg/mlk/rpg/tileset-loader.c Tue Mar 07 20:45:00 2023 +0100 @@ -0,0 +1,318 @@ +/* + * tileset-loader.c -- abstract tileset loader + * + * Copyright (c) 2020-2023 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <mlk/util/util.h> + +#include <mlk/core/animation.h> +#include <mlk/core/err.h> +#include <mlk/core/sprite.h> +#include <mlk/core/util.h> + +#include "tileset-loader.h" +#include "tileset.h" + +static int +collision_cmp(const void *d1, const void *d2) +{ + const struct mlk_tileset_collision *c1 = d1; + const struct mlk_tileset_collision *c2 = d2; + + if (c1->id < c2->id) + return -1; + if (c1->id > c2->id) + return 1; + + return 0; +} + +static int +animation_cmp(const void *d1, const void *d2) +{ + const struct mlk_tileset_animation *a1 = d1; + const struct mlk_tileset_animation *a2 = d2; + + if (a1->id < a2->id) + return -1; + if (a1->id > a2->id) + return 1; + + return 0; +} + +static int +parse_tilewidth(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)loader; + (void)tileset; + (void)fp; + + if (sscanf(line, "tilewidth|%u", &loader->tilewidth) != 1 || loader->tilewidth == 0) + return mlk_errf("tilewidth is null or invalid"); + + return 0; +} + +static int +parse_tileheight(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)loader; + (void)tileset; + (void)fp; + + if (sscanf(line, "tileheight|%u", &loader->tileheight) != 1 || loader->tileheight == 0) + return mlk_errf("tileheight is null or invalid"); + + return 0; +} + +static int +parse_collisions(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)line; + + struct mlk_tileset_collision *array, *collision, *collisions = NULL; + unsigned int id, w, h; + int x, y; + size_t collisionsz = 0; + + while (fscanf(fp, "%u|%d|%d|%u|%u\n", &id, &x, &y, &w, &h) == 5) { + if (!(array = loader->expand_collisions(loader, collisions, collisionsz + 1))) + return -1; + + collisions = array; + collision = &collisions[collisionsz++]; + collision->id = id; + collision->x = x; + collision->y = y; + collision->w = w; + collision->h = h; + } + + /* + * Sort and link this array in the final tileset, user has ownership of + * the data. + */ + qsort(collisions, collisionsz, sizeof (*collisions), collision_cmp); + tileset->collisions = collisions; + tileset->collisionsz = collisionsz; + + return 0; +} + +static int +parse_animations(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)line; + + char fmt[64], filename[MLK_PATH_MAX]; + unsigned int id, delay; + struct mlk_tileset_animation *array, *tileanimation, *tileanimations = NULL; + struct mlk_texture *texture; + struct mlk_sprite *sprite; + struct mlk_animation *animation; + size_t tileanimationsz = 0; + + /* Create a format string for fscanf. */ + snprintf(fmt, sizeof (fmt), "%%u|%%%zu[^|]|%%u", sizeof (filename)); + + /* + * When parsing animations, we have to create three different + * structures: + * + * 1. The texture itself. + * 2. The sprite object that will use the above texture. + * 3. The animation object. + * 4. Link the animation to the tileset animation. + */ + while (fscanf(fp, fmt, &id, filename, &delay) == 3) { + if (!(texture = loader->init_texture(loader, filename))) + return -1; + if (!(sprite = loader->init_sprite(loader))) + return -1; + if (!(animation = loader->init_animation(loader))) + return -1; + if (!(array = loader->expand_animations(loader, tileanimations, tileanimationsz + 1))) + return -1; + + /* Bind the texture to the new sprite. */ + sprite->texture = texture; + sprite->cellw = loader->tilewidth; + sprite->cellh = loader->tileheight; + mlk_sprite_init(sprite); + + /* Bind the sprite to the new animation. */ + animation->sprite = sprite; + animation->delay = delay; + + /* Add the animation to the array. */ + tileanimations = array; + tileanimation = &tileanimations[tileanimationsz++]; + tileanimation->id = id; + tileanimation->animation = animation; + } + + /* + * Sort and link this array in the final tileset, user has ownership of + * the data. + */ + qsort(tileanimations, tileanimationsz, sizeof (*tileanimations), animation_cmp); + tileset->animations = tileanimations; + tileset->animationsz = tileanimationsz; + + return 0; +} + +static int +parse_image(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + (void)fp; + + const char *p; + struct mlk_texture *texture; + struct mlk_sprite *sprite; + + if (loader->tilewidth == 0 || loader->tileheight == 0) + return mlk_errf("missing tile dimensions before image"); + if (!(p = strchr(line, '|'))) + return mlk_errf("could not parse image"); + if (!(texture = loader->init_texture(loader, p + 1))) + return -1; + if (!(sprite = loader->init_sprite(loader))) + return -1; + + /* Initialize the sprite with the texture. */ + sprite->texture = texture; + sprite->cellw = loader->tilewidth; + sprite->cellh = loader->tileheight; + mlk_sprite_init(sprite); + + /* Link this texture to the final tileset. */ + tileset->sprite = sprite; + + return 0; +} + +static int +parse_line(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *line, + FILE *fp) +{ + static const struct { + const char *property; + int (*read)(struct mlk_tileset_loader *, struct mlk_tileset *, const char *, FILE *); + } props[] = { + { "tilewidth", parse_tilewidth }, + { "tileheight", parse_tileheight }, + { "collisions", parse_collisions }, + { "animations", parse_animations }, + { "image", parse_image } + }; + + for (size_t i = 0; i < MLK_UTIL_SIZE(props); ++i) { + if (strncmp(line, props[i].property, strlen(props[i].property)) == 0) + return props[i].read(loader, tileset, line, fp); + } + + return 0; +} + +static int +check(const struct mlk_tileset *tileset) +{ + if (!tileset->sprite) + return mlk_errf("missing tileset image"); + + return 0; +} + +static int +parse(struct mlk_tileset_loader *loader, struct mlk_tileset *tileset, FILE *fp) +{ + char line[128]; + + while (fgets(line, sizeof (line), fp)) { + /* Remove \n if any */ + line[strcspn(line, "\r\n")] = 0; + + if (parse_line(loader, tileset, line, fp) < 0) + return -1; + } + + return check(tileset); +} + +int +mlk_tileset_loader_open(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const char *path) +{ + assert(loader); + assert(tileset); + assert(path); + + FILE *fp; + + memset(tileset, 0, sizeof (*tileset)); + + if (!(fp = fopen(path, "r"))) + return mlk_errf("%s", strerror(errno)); + + return parse(loader, tileset, fp); +} + +int +mlk_tileset_loader_openmem(struct mlk_tileset_loader *loader, + struct mlk_tileset *tileset, + const void *data, + size_t datasz) +{ + assert(loader); + assert(tileset); + assert(data); + + FILE *fp; + + memset(tileset, 0, sizeof (*tileset)); + + if (!(fp = mlk_util_fmemopen((void *)data, datasz, "r"))) + return -1; + + return parse(loader, tileset, fp); +}