# HG changeset patch # User David Demelier # Date 1604914656 -3600 # Node ID 852d0b7817ceac868d0444c92207065234969841 # Parent 658ee50b8bcba5b85bc80e190124fb154013760a rpg: map, extreme cleanup, closes #2508 @4h diff -r 658ee50b8bcb -r 852d0b7817ce doxygen/page-faq.c --- a/doxygen/page-faq.c Mon Nov 09 10:36:39 2020 +0100 +++ b/doxygen/page-faq.c Mon Nov 09 10:37:36 2020 +0100 @@ -64,15 +64,4 @@ * synchronisation and many other stuff than a local game does not require. * * There are no plans to create a network oriented core API anytime soon. - * - * # Can I write an action RPG like Zelda? - * - * Probably. - * - * Do not use the battle system provided and then depending on your game you - * may: - * - * - Edit the \ref map_state.h module to your needs (you may also simply define - * your own input/update handler instead), - * - Create a dedicated state and use \ref map.h if you want. */ diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/CMakeLists.txt --- a/libadventure/CMakeLists.txt Mon Nov 09 10:36:39 2020 +0100 +++ b/libadventure/CMakeLists.txt Mon Nov 09 10:37:36 2020 +0100 @@ -30,6 +30,8 @@ set( SOURCES + ${libadventure_SOURCE_DIR}/adventure/molko.c + ${libadventure_SOURCE_DIR}/adventure/molko.h ${libadventure_SOURCE_DIR}/adventure/trace_hud.c ${libadventure_SOURCE_DIR}/adventure/trace_hud.h ) @@ -40,9 +42,7 @@ ${libadventure_SOURCE_DIR}/adventure/assets/fonts/lato.ttf ${libadventure_SOURCE_DIR}/adventure/assets/fonts/teutonic.ttf ${libadventure_SOURCE_DIR}/adventure/assets/fonts/pirata-one.ttf - ${libadventure_SOURCE_DIR}/adventure/assets/maps/overworld.map ${libadventure_SOURCE_DIR}/adventure/assets/sprites/john.png - ${libadventure_SOURCE_DIR}/adventure/assets/tilesets/world.png ) molko_define_library( @@ -52,8 +52,11 @@ ${SOURCES} ASSETS ${ASSETS} LIBRARIES libcore libui librpg + PRIVATE_INCLUDES + ${libadventure_SOURCE_DIR}/adventure PUBLIC_INCLUDES $ + PRIVATE_FLAGS DIRECTORY="${libadventure_SOURCE_DIR}/adventure/assets" ) source_group(adventure FILES ${SOURCES}) diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/assets/maps/overworld.map --- a/libadventure/adventure/assets/maps/overworld.map Mon Nov 09 10:36:39 2020 +0100 +++ b/libadventure/adventure/assets/maps/overworld.map Mon Nov 09 10:37:36 2020 +0100 @@ -4,6 +4,7 @@ height|100 tilewidth|32 tileheight|32 +tileset|overworld.png layer|background 21 21 @@ -20006,4 +20007,3 @@ 0 0 0 -tileset|test.png diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/assets/maps/overworld.png Binary file libadventure/adventure/assets/maps/overworld.png has changed diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/assets/tilesets/world.png Binary file libadventure/adventure/assets/tilesets/world.png has changed diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/molko.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libadventure/adventure/molko.c Mon Nov 09 10:37:36 2020 +0100 @@ -0,0 +1,132 @@ +/* + * molko.c -- main structure for Molko's Adventure + * + * Copyright (c) 2020 David Demelier + * + * 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 +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include "molko.h" + +#define WINDOW_WIDTH 1280 +#define WINDOW_HEIGHT 720 + +static jmp_buf panic_buf; + +struct molko molko; + +static noreturn void +crash(void) +{ + longjmp(panic_buf, 1); +} + +static void +loop(void) +{ + struct clock clock = {0}; + + while (game.state) { + unsigned int elapsed = clock_elapsed(&clock); + + clock_start(&clock); + + for (union event ev; event_poll(&ev); ) { + switch (ev.type) { + case EVENT_QUIT: + return; + default: + game_handle(&ev); + break; + } + } + + game_update(elapsed); + game_draw(); + + if ((elapsed = clock_elapsed(&clock)) < 20) + delay(20 - elapsed); + } +} + +void +molko_init(void) +{ + if (!core_init() || !ui_init() || !rpg_init()) + panic(); + if (!window_open("Molko's Adventure", WINDOW_WIDTH, WINDOW_HEIGHT)) + panic(); + + /* + * From here, we can setup our panic state which requires a window + * to be running. + */ + + /* Init unrecoverable panic state. */ + panic_state(&molko.states[MOLKO_STATE_PANIC]); + panic_handler = crash; + + /* Init states. */ + splashscreen_state(&molko.states[MOLKO_STATE_SPLASH], &molko.states[MOLKO_STATE_MAINMENU]); + mainmenu_state(&molko.states[MOLKO_STATE_MAINMENU]); + map_state(&molko.map, &molko.states[MOLKO_STATE_MAP]); + + /* Start to splash. */ + game_switch(&molko.states[MOLKO_STATE_SPLASH], true); +} + +void +molko_run(void) +{ + if (setjmp(panic_buf) == 0) { + /* Initial game run. */ + loop(); + } else { + /* Clear event queue to avoid accidental key presses. */ + for (union event ev; event_poll(&ev); ) + continue; + + game_switch(&molko.states[MOLKO_STATE_PANIC], true); + loop(); + } +} + +void +molko_finish(void) +{ + for (size_t i = 0; i < MOLKO_STATE_NUM; ++i) + state_finish(&molko.states[i]); + + window_finish(); + rpg_finish(); + ui_finish(); + core_finish(); +} diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/molko.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libadventure/adventure/molko.h Mon Nov 09 10:37:36 2020 +0100 @@ -0,0 +1,60 @@ +/* + * molko.h -- main structure for Molko's Adventure + * + * Copyright (c) 2020 David Demelier + * + * 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. + */ + +#ifndef MOLKO_H +#define MOLKO_H + +#include +#include +#include +#include + +#include +#include + +enum molko_state { + MOLKO_STATE_SPLASH, + MOLKO_STATE_MAINMENU, + MOLKO_STATE_PANIC, + MOLKO_STATE_MAP, + MOLKO_STATE_NUM +}; + +struct molko { + struct game engine; + struct state states[MOLKO_STATE_NUM]; + + /* MOLKO_STATE_MAP. */ + struct texture map_player_texture; + struct sprite map_player_sprite; + struct map_file map_file; + struct map map; +}; + +extern struct molko molko; + +void +molko_init(void); + +void +molko_run(void); + +void +molko_finish(void); + +#endif /* !MOLKO_H */ diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/state/mainmenu.c --- a/libadventure/adventure/state/mainmenu.c Mon Nov 09 10:36:39 2020 +0100 +++ b/libadventure/adventure/state/mainmenu.c Mon Nov 09 10:37:36 2020 +0100 @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -39,6 +40,7 @@ #include #include "mainmenu.h" +#include "molko.h" struct mainmenu { struct { @@ -54,6 +56,20 @@ new(void) { /* TODO: implement here. */ + if (!map_file_open(&molko.map_file, DIRECTORY "/maps/overworld.map", &molko.map)) + panic(); + + /* Put a sprite. */ + if (!image_open(&molko.map_player_texture, DIRECTORY "/sprites/john.png")) + panic(); + + sprite_init(&molko.map_player_sprite, &molko.map_player_texture, 48, 48); + molko.map.player_sprite = &molko.map_player_sprite; + + if (!map_init(&molko.map)) + panic(); + + game_switch(&molko.states[MOLKO_STATE_MAP], false); } static void diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/state/panic.c --- a/libadventure/adventure/state/panic.c Mon Nov 09 10:36:39 2020 +0100 +++ b/libadventure/adventure/state/panic.c Mon Nov 09 10:37:36 2020 +0100 @@ -37,8 +37,6 @@ #include #include -#include - #include "panic.h" #define BACKGROUND 0x4f5070ff diff -r 658ee50b8bcb -r 852d0b7817ce libadventure/adventure/state/splashscreen.c --- a/libadventure/adventure/state/splashscreen.c Mon Nov 09 10:36:39 2020 +0100 +++ b/libadventure/adventure/state/splashscreen.c Mon Nov 09 10:37:36 2020 +0100 @@ -98,9 +98,6 @@ { struct splashscreen *splash = state->data; - if (!splash) - return; - texture_finish(&splash->tex); free(splash); diff -r 658ee50b8bcb -r 852d0b7817ce librpg/CMakeLists.txt --- a/librpg/CMakeLists.txt Mon Nov 09 10:36:39 2020 +0100 +++ b/librpg/CMakeLists.txt Mon Nov 09 10:37:36 2020 +0100 @@ -54,8 +54,8 @@ ${librpg_SOURCE_DIR}/rpg/item.h ${librpg_SOURCE_DIR}/rpg/map.c ${librpg_SOURCE_DIR}/rpg/map.h - ${librpg_SOURCE_DIR}/rpg/map_state.c - ${librpg_SOURCE_DIR}/rpg/map_state.h + ${librpg_SOURCE_DIR}/rpg/map-file.c + ${librpg_SOURCE_DIR}/rpg/map-file.h ${librpg_SOURCE_DIR}/rpg/message.c ${librpg_SOURCE_DIR}/rpg/message.h ${librpg_SOURCE_DIR}/rpg/rpg.c diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/map-file.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/map-file.c Mon Nov 09 10:37:36 2020 +0100 @@ -0,0 +1,290 @@ +/* + * map-file.c -- map file loader + * + * Copyright (c) 2020 David Demelier + * + * 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. + */ + +#define _XOPEN_SOURCE 700 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "map-file.h" + +/* Create %c string literal for scanf */ +#define MAX_F(v) MAX_F_(v) +#define MAX_F_(v) "%" #v "c" + +struct parser { + struct map_file *mf; /* Map loader. */ + struct map *map; /* Map object to fill. */ + FILE *fp; /* Map file pointer. */ + char basedir[PATH_MAX]; /* Parent map directory */ +}; + +static bool +parse_layer(struct parser *ps, const char *line) +{ + char layer_name[32 + 1] = {0}; + enum map_layer_type layer_type; + size_t amount, current; + + /* Check if weight/height has been specified. */ + if (ps->map->w == 0 || ps->map->h == 0) + return errorf("missing map dimensions before layer"); + + /* Determine layer type. */ + if (sscanf(line, "layer|%32s", layer_name) <= 0) + return errorf("missing layer type definition"); + + if (strcmp(layer_name, "background") == 0) + layer_type = MAP_LAYER_TYPE_BACKGROUND; + else if (strcmp(layer_name, "foreground") == 0) + layer_type = MAP_LAYER_TYPE_FOREGROUND; + else + return errorf("invalid layer type: %s", layer_name); + + amount = ps->map->w * ps->map->h; + current = 0; + + /* + * The next line after a layer declaration is a list of plain integer + * that fill the layer tiles. + */ + if (!(ps->mf->layers[layer_type].tiles = alloc_zero(amount, sizeof (unsigned short)))) + return false; + + for (int tile; fscanf(ps->fp, "%d", &tile) && current < amount; ++current) + ps->mf->layers[layer_type].tiles[current] = tile; + + ps->map->layers[layer_type].tiles = ps->mf->layers[layer_type].tiles; + + return true; +} + +static bool +parse_tileset(struct parser *ps, const char *line) +{ + char filename[FILENAME_MAX + 1] = {0}; + char filepath[PATH_MAX]; + int ret; + + if (ps->map->tile_w == 0 || ps->map->tile_h == 0) + return errorf("missing map dimensions before tileset"); + + if ((ret = sscanf(line, "tileset|" MAX_F(FILENAME_MAX), filename)) == 1) { + snprintf(filepath, sizeof (filepath), "%s/%s", ps->basedir, filename); + + if (!image_open(&ps->mf->tileset, filepath)) + return false; + } + + /* Initialize sprite. */ + sprite_init(&ps->mf->sprite, &ps->mf->tileset, ps->map->tile_w, ps->map->tile_h); + ps->map->tileset = &ps->mf->sprite; + + return true; +} + +static bool +parse_title(struct parser *ps, const char *line) +{ + if (sscanf(line, "title|" MAX_F(MAP_FILE_TITLE_MAX), ps->mf->title) != 1 || strlen(ps->mf->title) == 0) + return errorf("null map title"); + + ps->map->title = ps->mf->title; + + return true; +} + +static bool +parse_width(struct parser *ps, const char *line) +{ + if (sscanf(line, "width|%u", &ps->map->w) != 1 || ps->map->w == 0) + return errorf("null map width"); + + return true; +} + +static bool +parse_height(struct parser *ps, const char *line) +{ + if (sscanf(line, "height|%u", &ps->map->h) != 1 || ps->map->h == 0) + return errorf("null map height"); + + return true; +} + +static bool +parse_tilewidth(struct parser *ps, const char *line) +{ + if (sscanf(line, "tilewidth|%hu", &ps->map->tile_w) != 1 || ps->map->tile_w == 0) + return errorf("null map tile width"); + if (ps->map->w == 0) + return errorf("missing map width before tilewidth"); + + ps->map->real_w = ps->map->w * ps->map->tile_w; + + return true; +} + +static bool +parse_tileheight(struct parser *ps, const char *line) +{ + if (sscanf(line, "tileheight|%hu", &ps->map->tile_h) != 1 || ps->map->tile_h == 0) + return errorf("null map tile height"); + if (ps->map->h == 0) + return errorf("missing map height before tileheight"); + + ps->map->real_h = ps->map->h * ps->map->tile_h; + + return true; +} + +static bool +parse_origin(struct parser *ps, const char *line) +{ + if (sscanf(line, "origin|%d|%d", &ps->map->origin_x, &ps->map->origin_y) != 2) + return errorf("invalid origin"); + + /* + * We adjust the player position here because it should not be done in + * the map_init function. This is because the player should not move + * magically each time we re-use the map (saving position). + */ + ps->map->player_x = ps->map->origin_x; + ps->map->player_y = ps->map->origin_y; + + return true; +} + +static bool +parse_line(struct parser *ps, const char *line) +{ + static const struct { + const char *property; + bool (*read)(struct parser *, const char *); + } props[] = { + { "title", parse_title }, + { "width", parse_width }, + { "height", parse_height }, + { "tilewidth", parse_tilewidth }, + { "tileheight", parse_tileheight }, + { "tileset", parse_tileset }, + { "origin", parse_origin }, + { "layer", parse_layer } + }; + + for (size_t i = 0; i < NELEM(props); ++i) + if (strncmp(line, props[i].property, strlen(props[i].property)) == 0) + return props[i].read(ps, line); + + return true; +} + +static bool +parse(struct map_file *loader, const char *path, struct map *map, FILE *fp) +{ + char line[1024]; + struct parser ps = { + .mf = loader, + .map = map, + .fp = fp + }; + + /* + * Even though dirname(3) usually not modify the path as argument it may + * do according to POSIX specification, as such we still need a + * temporary buffer. + */ + snprintf(ps.basedir, sizeof (ps.basedir), "%s", path); + snprintf(ps.basedir, sizeof (ps.basedir), "%s", dirname(ps.basedir)); + + while (fgets(line, sizeof (line), fp)) { + /* Remove \n if any */ + line[strcspn(line, "\n")] = '\0'; + + if (!parse_line(&ps, line)) + return false; + } + + return true; +} + +static bool +check(struct map *map) +{ + /* + * Check that we have parsed every required components. + */ + if (!map->title) + return errorf("missing title"); + + /* + * We don't need to check width/height because parsing layers and + * tilesets already check for their presence, so only check layers. + */ + if (!map->layers[0].tiles) + return errorf("missing background layer"); + if (!map->layers[1].tiles) + return errorf("missing foreground layer"); + if (!sprite_ok(map->tileset)) + return errorf("missing tileset"); + + return true; +} + +bool +map_file_open(struct map_file *file, const char *path, struct map *map) +{ + assert(file); + assert(path); + assert(map); + + FILE *fp; + bool ret = true; + + memset(file, 0, sizeof (*file)); + memset(map, 0, sizeof (*map)); + + if (!(fp = fopen(path, "r"))) + return errorf("%s", strerror(errno)); + + if (!(ret = parse(file, path, map, fp)) || !(ret = check(map))) { + map_finish(map); + map_file_finish(file); + } + + fclose(fp); + + return ret; +} + +void +map_file_finish(struct map_file *file) +{ + assert(file); + + texture_finish(&file->tileset); + memset(file, 0, sizeof (*file)); +} diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/map-file.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/librpg/rpg/map-file.h Mon Nov 09 10:37:36 2020 +0100 @@ -0,0 +1,102 @@ +/* + * map-file.h -- map file loader + * + * Copyright (c) 2020 David Demelier + * + * 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. + */ + +#ifndef MOLKO_MAP_FILE_H +#define MOLKO_MAP_FILE_H + +/** + * \file map-file.h + * \brief Map file loader. + * + * Because a map is a complicated object that require several components to be + * loaded, the way it is loaded from a source is implemented separately to keep + * the \ref map.h module as simple as possible. + * + * This module load map files generated from mlk-map tool and expect some + * convention from the user: + * + * - A tileset image must be present in the same directory as the map itself. + * + * This module allocates some dynamic data that are stored in the map object as + * such the map_file structure must be kept until the map is no longer used + * itself. + */ + +#include + +#include +#include +#include + +#include "map.h" + +/** + * \brief Maximum title map length in file. + */ +#define MAP_FILE_TITLE_MAX 64 + +/** + * \brief Context for loading maps. + * + * Member fields should not be modified directly but must be modified in the + * underlying associated map object once loaded. + * + * Since the map object does not own resources, this map file will dynamically + * load and store them from the file and thus must not be destroyed until the + * map is no longer use itself. + */ +struct map_file { + /** + * (*) Map title loaded from file. + */ + char title[MAP_FILE_TITLE_MAX]; + + /** + * (*) Map layers stored dynamically here. + */ + struct map_layer layers[MAP_LAYER_TYPE_NUM]; + + struct texture tileset; /*!< (*) Tileset image. */ + struct sprite sprite; /*!< (*) Tileset sprite. */ +}; + +/** + * Try to open a map from a file path. + * + * \pre file != NULL + * \pre path != NULL + * \pre map != NULL + * \param file the loader to use + * \param path the path to the map file + * \param map the map to set + * \warning Keep file object until map is no longer used. + */ +bool +map_file_open(struct map_file *file, const char *path, struct map *map) PLAT_NODISCARD; + +/** + * Close resources from the loader. + * + * \pre file != NULL + * \param file the file to loader to destroy + * \warning Destroy the map itself before calling this function. + */ +void +map_file_finish(struct map_file *file); + +#endif /* !MOLKO_MAP_FILE_H */ diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/map.c --- a/librpg/rpg/map.c Mon Nov 09 10:36:39 2020 +0100 +++ b/librpg/rpg/map.c Mon Nov 09 10:37:36 2020 +0100 @@ -22,84 +22,254 @@ #include #include +#include #include #include #include +#include #include #include #include +#include + #include "map.h" -/* Create %c string literal for scanf */ -#define MAX_F(v) MAX_F_(v) -#define MAX_F_(v) "%" #v "c" +/* + * This is the speed the player moves on the map. + * + * SPEED represents the number of pixels it must move per SEC. + * SEC simply represends the number of milliseconds in one second. + */ +#define SPEED 200 +#define SEC 1000 + +/* + * Those are margins within the edge of the screen. The camera always try to + * keep those padding between the player and the screen. + */ +#define MARGIN_WIDTH 80 +#define MARGIN_HEIGHT 80 + +/* + * This structure defines the possible movement of the player as flags since + * it's possible to make diagonal movements. + */ +enum movement { + MOVING_UP = 1 << 0, + MOVING_RIGHT = 1 << 1, + MOVING_DOWN = 1 << 2, + MOVING_LEFT = 1 << 3 +}; + +/* + * A bit of explanation within this array. The structure walksprite requires + * an orientation between 0-7 depending on the user direction. + * + * Since keys for moving the character may be pressed at the same time, we need + * a conversion table from "key pressed" to "orientation". + * + * When an orientation is impossible, it is set to -1. Example, when both left + * and right are pressed. + * + * MOVING_UP = 0001 = 0x1 + * MOVING_RIGHT = 0010 = 0x2 + * MOVING_DOWN = 0100 = 0x3 + * MOVING_LEFT = 1000 = 0x4 + */ +static unsigned int orientations[16] = { + [0x1] = 0, + [0x2] = 2, + [0x3] = 1, + [0x4] = 4, + [0x6] = 3, + [0x8] = 6, + [0x9] = 7, + [0xC] = 5 +}; static void -parse_layer(struct map_data *data, const char *line, FILE *fp) +center(struct map *map) { - char layer_name[32 + 1] = { 0 }; - struct map_layer *layer; - size_t amount, current; + map->view_x = map->player_x - (int)(map->view_w / 2); + map->view_y = map->player_y - (int)(map->view_h / 2); + + if (map->view_x < 0) + map->view_x = 0; + else if ((unsigned int)map->view_x > map->real_w - map->view_w) + map->view_x = map->real_w - map->view_w; + + if (map->view_y < 0) + map->view_y = 0; + else if ((unsigned int)map->view_y > map->real_h - map->view_h) + map->view_y = map->real_h - map->view_h; +} + +static void +start(struct map *map) +{ + map_repaint(map); + + /* Adjust view. */ + map->view_w = window.w; + map->view_h = window.h; + + /* Adjust margin. */ + map->margin_w = map->view_w - (MARGIN_WIDTH * 2); + map->margin_h = map->view_h - (MARGIN_HEIGHT * 2); - /* Determine layer. */ - if (sscanf(line, "layer|%32s", layer_name) <= 0) - return; - if (strcmp(layer_name, "background") == 0) - layer = &data->layers[0]; - else if (strcmp(layer_name, "foreground") == 0) - layer = &data->layers[1]; - else - return; + /* Center the view by default. */ + center(map); + + /* Final bits. */ + walksprite_init(&map->player_ws, map->player_sprite, 150); +} - /* Check if weight/height has been specified. */ - if (data->w == 0 || data->h == 0) - return; +static void +handle_keydown(struct map *map, const union event *event) +{ + switch (event->key.key) { + case KEY_UP: + map->player_movement |= MOVING_UP; + break; + case KEY_RIGHT: + map->player_movement |= MOVING_RIGHT; + break; + case KEY_DOWN: + map->player_movement |= MOVING_DOWN; + break; + case KEY_LEFT: + map->player_movement |= MOVING_LEFT; + break; + default: + break; + } - amount = data->w * data->h; - current = 0; - - if (!(layer->tiles = calloc(amount, sizeof (unsigned short)))) - return; - - for (int tile; fscanf(fp, "%d", &tile) && current < amount; ++current) - layer->tiles[current] = tile; + map->player_angle = orientations[map->player_movement]; } static void -parse(struct map_data *data, const char *line, FILE *fp) +handle_keyup(struct map *map, const union event *event) +{ + switch (event->key.key) { + case KEY_UP: + map->player_movement &= ~(MOVING_UP); + break; + case KEY_RIGHT: + map->player_movement &= ~(MOVING_RIGHT); + break; + case KEY_DOWN: + map->player_movement &= ~(MOVING_DOWN); + break; + case KEY_LEFT: + map->player_movement &= ~(MOVING_LEFT); + break; + default: + break; + } +} + +static void +move_up(struct map *map, int delta) { - if (strncmp(line, "title", 5) == 0) - sscanf(line, "title|" MAX_F(MAP_TITLE_MAX), data->title); - else if (strncmp(line, "width", 5) == 0) - sscanf(line, "width|%u", &data->w); - else if (strncmp(line, "height", 6) == 0) - sscanf(line, "height|%u", &data->h); - else if (strncmp(line, "tilewidth", 9) == 0) - sscanf(line, "tilewidth|%hu", &data->tile_w); - else if (strncmp(line, "tileheight", 10) == 0) - sscanf(line, "tileheight|%hu", &data->tile_h); - else if (strncmp(line, "origin", 6) == 0) - sscanf(line, "origin|%d|%d", &data->origin_x, &data->origin_y); - else if (strncmp(line, "tileset", 7) == 0) - sscanf(line, "tileset|" MAX_F(MAP_TILESET_MAX), data->tileset); - else if (strncmp(line, "layer", 5) == 0) - parse_layer(data, line, fp); + map->player_y -= delta; + + if (map->player_y < map->margin_y) { + map->view_y = map->player_y - MARGIN_HEIGHT; + + if (map->view_y < 0) + map->view_y = 0; + } + + if (map->player_y < 0) + map->player_y = 0; +} + +static void +move_right(struct map *map, int delta) +{ + if (map->player_x + map->player_sprite->cellw + delta > map->real_w) + map->player_x = map->real_w - map->player_sprite->cellw; + else + map->player_x += delta; + + if (map->player_x + map->player_sprite->cellw > map->margin_x + map->margin_w) { + map->view_x += delta; + + if (map->view_x + map->view_w > map->real_w) + map->view_x = map->real_w - map->view_w; + } } -static bool -check(struct map_data *data) +static void +move_down(struct map *map, int delta) +{ + if (map->player_y + map->player_sprite->cellh + delta > map->real_h) + map->player_y = map->real_h - map->player_sprite->cellh; + else + map->player_y += delta; + + if (map->player_y + map->player_sprite->cellh > map->margin_y + map->margin_h) { + map->view_y += delta; + + if (map->view_y + map->view_h > map->real_h) + map->view_y = map->real_h - map->view_h; + } +} + +static void +move_left(struct map *map, int delta) +{ + if (map->player_x < delta) + map->player_x = 0; + else + map->player_x -= delta; + + if (map->player_x < map->margin_x) { + if (map->view_x < delta) + map->view_x = 0; + else + map->view_x -= delta; + } +} + +static void +move(struct map *map, unsigned int ticks) { - if (strlen(data->title) == 0) - return errorf("data has no title"); - if (data->w == 0 || data->h == 0) - return errorf("data has null sizes"); - if (data->tile_w == 0 || data->tile_h == 0) - return errorf("data has null tile sizes"); - if (!data->layers[0].tiles || !data->layers[1].tiles) - return errorf("could not allocate data"); + /* This is the amount of pixels the player must move. */ + const int delta = SPEED * ticks / SEC; + + /* This is the rectangle within the view where users must be. */ + map->margin_x = map->view_x + MARGIN_WIDTH; + map->margin_y = map->view_y + MARGIN_HEIGHT; + + int dx = 0; + int dy = 0; + + if (map->player_movement == 0) + return; - return true; + if (map->player_movement & MOVING_UP) + dy = -1; + if (map->player_movement & MOVING_DOWN) + dy = 1; + if (map->player_movement & MOVING_LEFT) + dx = -1; + if (map->player_movement & MOVING_RIGHT) + dx = 1; + + /* Move the player and adjust view if needed. */ + if (dx > 0) + move_right(map, delta); + else if (dx < 0) + move_left(map, delta); + + if (dy > 0) + move_down(map, delta); + else if (dy < 0) + move_up(map, delta); + + walksprite_update(&map->player_ws, ticks); } static void @@ -108,124 +278,130 @@ assert(map); assert(layer); - struct sprite sprite; int x = 0, y = 0; - sprite_init(&sprite, map->tileset, map->data->tile_w, map->data->tile_h); - - for (unsigned int r = 0; r < map->data->w; ++r) { - for (unsigned int c = 0; c < map->data->h; ++c) { - unsigned int si = r * map->data->w + c; - unsigned int sr = (layer->tiles[si] - 1) / sprite.ncols; - unsigned int sc = (layer->tiles[si] - 1) % sprite.nrows; + for (unsigned int r = 0; r < map->w; ++r) { + for (unsigned int c = 0; c < map->h; ++c) { + unsigned int si = r * map->w + c; + unsigned int sr = (layer->tiles[si] - 1) / map->tileset->ncols; + unsigned int sc = (layer->tiles[si] - 1) % map->tileset->nrows; if (layer->tiles[si] != 0) - sprite_draw(&sprite, sr, sc, x, y); + sprite_draw(map->tileset, sr, sc, x, y); - x += map->data->tile_w; + x += map->tile_w; } x = 0; - y += map->data->tile_h; + y += map->tile_h; } } +static void +handle(struct state *state, const union event *ev) +{ + map_handle(state->data, ev); +} + +static void +update(struct state *state, unsigned int ticks) +{ + map_update(state->data, ticks); +} + +static void +draw(struct state *state) +{ + return map_draw(state->data); +} + bool -map_data_open_fp(struct map_data *data, FILE *fp) +map_init(struct map *map) { - assert(data); + assert(map); - char line[1024]; - - if (!fp) + if (!texture_new(&map->picture, map->real_w, map->real_h)) return false; - memset(data, 0, sizeof (*data)); - - while (fgets(line, sizeof (line), fp)) { - /* Remove \n if any */ - line[strcspn(line, "\n")] = '\0'; - parse(data, line, fp); - } - - fclose(fp); - - if (!check(data)) { - map_data_finish(data); - return false; - } - - /* Compute real size. */ - data->real_w = data->w * data->tile_w; - data->real_h = data->h * data->tile_h; - - return true; -} - -bool -map_data_open(struct map_data *data, const char *path) -{ - assert(data); - assert(path); - - return map_data_open_fp(data, fopen(path, "r")); -} - -bool -map_data_openmem(struct map_data *data, const void *buf, size_t bufsz) -{ - assert(data); - assert(buf); - - return map_data_open_fp(data, fmemopen((void *)buf, bufsz, "r")); -} - -void -map_data_finish(struct map_data *data) -{ - assert(data); - - free(data->layers[0].tiles); - free(data->layers[1].tiles); - - memset(data, 0, sizeof (*data)); -} - -bool -map_init(struct map *map, struct map_data *data, struct texture *tileset) -{ - assert(map); - assert(data); - assert(tileset && texture_ok(tileset)); - - if (!(texture_new(&map->picture, data->real_w, data->real_h))) - return false; - - map->data = data; - map->tileset = tileset; - - map_repaint(map); + start(map); return true; } void -map_draw(struct map *map, int srcx, int srcy) +map_handle(struct map *map, const union event *ev) +{ + assert(map); + assert(ev); + + switch (ev->type) { + case EVENT_KEYDOWN: + handle_keydown(map, ev); + break; + case EVENT_KEYUP: + handle_keyup(map, ev); + break; + default: + break; + } +} + +void +map_update(struct map *map, unsigned int ticks) { - texture_scale(&map->picture, srcx, srcy, window.w, window.h, + assert(map); + + move(map, ticks); +} + +void +map_draw(const struct map *map) +{ + assert(map); + + struct debug_report report = {0}; + + /* Draw the texture about background/foreground. */ + texture_scale(&map->picture, map->view_x, map->view_y, window.w, window.h, 0, 0, window.w, window.h, 0.0); + + walksprite_draw( + &map->player_ws, + map->player_angle, + map->player_x - map->view_x, + map->player_y - map->view_y); + + debugf(&report, "position: %d, %d", map->player_x, + map->player_y); + debugf(&report, "view: %d, %d", map->view_x, + map->view_y); } void map_repaint(struct map *map) { + assert(map); + PAINTER_BEGIN(&map->picture); - draw_layer(map, &map->data->layers[0]); - draw_layer(map, &map->data->layers[1]); + draw_layer(map, &map->layers[MAP_LAYER_TYPE_BACKGROUND]); + draw_layer(map, &map->layers[MAP_LAYER_TYPE_FOREGROUND]); PAINTER_END(); } void +map_state(struct map *map, struct state *state) +{ + assert(map); + assert(state); + + memset(state, 0, sizeof (*state)); + state->data = map; + state->draw = draw; + state->handle = handle; + state->update = update; +} + +void map_finish(struct map *map) { assert(map); @@ -234,3 +410,4 @@ memset(map, 0, sizeof (*map)); } + diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/map.h --- a/librpg/rpg/map.h Mon Nov 09 10:36:39 2020 +0100 +++ b/librpg/rpg/map.h Mon Nov 09 10:37:36 2020 +0100 @@ -24,37 +24,43 @@ * \brief Game map. */ -#include -#include +#include + +#include "walksprite.h" -#include +struct sprite; +struct state; + +union event; /** - * \brief Max title length for a map. + * \brief Map layer type. */ -#define MAP_TITLE_MAX 32 - -/** - * \brief Max filename for tilesets. - */ -#define MAP_TILESET_MAX FILENAME_MAX +enum map_layer_type { + MAP_LAYER_TYPE_BACKGROUND, /*!< Background layer. */ + MAP_LAYER_TYPE_FOREGROUND, /*!< Foreground layer. */ + MAP_LAYER_TYPE_ABOVE, /*!< Above foreground layer. */ + MAP_LAYER_TYPE_NUM /*!< Number of layers. */ +}; /** * \brief Map layer. */ struct map_layer { - unsigned short *tiles; /*!< (+) Array of tiles, depending on the map size. */ + unsigned short *tiles; /*!< (+&) Array of tiles, depending on the map size. */ }; /** - * \brief Map definition structure. + * \brief Map object. + * + * The map object is used to move a player within the map according to the + * tilesets and collisions masks. * - * This structure only defines the map characteristics. It does not have any - * logic and is left for game state. + * By itself, a map does not know how to be loaded from a file and must be done + * from an helper like \ref map_file. */ -struct map_data { - char title[MAP_TITLE_MAX]; /*!< (+) The map title. */ - char tileset[MAP_TILESET_MAX]; /*!< (+) Name of tileset to use. */ +struct map { + const char *title; /*!< (+) Map title name. */ int origin_x; /*!< (+) Where the player starts in X. */ int origin_y; /*!< (+) Where the player starts in Y. */ unsigned int real_w; /*!< (-) Real width in pixels. */ @@ -63,79 +69,76 @@ unsigned int h; /*!< (-) Map height in cells. */ unsigned short tile_w; /*!< (-) Pixels per cell (width). */ unsigned short tile_h; /*!< (-) Pixels per cell (height). */ - struct map_layer layers[2]; /*!< (+) Layers (background, foreground). */ -}; + struct sprite *tileset; /*!< (+&) Tileset to use. */ + struct texture picture; /*!< (-) Map drawn into a texture. */ + + /* Player. */ + struct sprite *player_sprite; /*!< (+) The sprite to use */ + struct walksprite player_ws; /*!< (-) Walking sprite for moving the player. */ + int player_x; /*!< (+) Player position in x */ + int player_y; /*!< (+) Player position in y */ + int player_angle; /*!< (+) Player angle (see walksprite) */ + unsigned int player_movement; /*!< (*) Current player movements. */ -/** - * \brief High level map object. - * - * This structure reference a map and perform drawing operations. - */ -struct map { - struct map_data *data; /*!< (+&) Map data. */ - struct texture *tileset; /*!< (+&) Tileset to use. */ - struct texture picture; /*!< (-) Map drawn into a picture. */ + /* View to zoom/locate. */ + int view_x; /*!< (+) Position in x */ + int view_y; /*!< (+) Position in y */ + unsigned int view_w; /*!< (+) View width */ + unsigned int view_h; /*!< (+) View height */ + + /* View margin. */ + int margin_x; /*!< (+) View margin in x. */ + int margin_y; /*!< (+) View margin in y. */ + unsigned int margin_w; /*!< (+) Margin width. */ + unsigned int margin_h; /*!< (+) Margin height. */ + + /** + * Different tile layers. + */ + struct map_layer layers[MAP_LAYER_TYPE_NUM]; }; /** - * Open a map defintion. + * Initialize the map. * - * \pre data != NULL - * \pre path != NULL - * \param data the map defintion to fill - * \param path the path to the map - * \return True if successfully loaded. + * This function will re-generate the terrain and center the view to the player + * position. + * + * \pre map != NULL + * \param map the map to initialize */ bool -map_data_open(struct map_data *data, const char *path); - -/** - * Open map data definition from memory. - * - *\pre data != NULL - *\pre buf != NULL - *\param data the map definition to fill - *\param buf the source buffer - *\param bufsz the source buffer size - */ -bool -map_data_openmem(struct map_data *data, const void *buf, size_t bufsz); +map_init(struct map *map); /** - * Dispose the map definition data. - * - * \pre data != NULL - * \param data the map definition - */ -void -map_data_finish(struct map_data *data); - -/** - * Initialize this map. + * Handle an event. * * \pre map != NULL - * \pre data != NULL - * \pre tileset != NULL && texture_ok(tileset) - * \param map the map to initialize - * \param data the definition to reference - * \param tileset the tileset to use - * \return False on errors. + * \pre ev != NULL + * \param map the map + * \param ev the event to handle */ -bool -map_init(struct map *map, - struct map_data *data, - struct texture *tileset); +void +map_handle(struct map *map, const union event *ev); + +/** + * Update the map. + * + * \pre map != NULL + * \param map the map + * \param ticks ellapsed milliseconds since last frame + */ +void +map_update(struct map *map, unsigned int ticks); /** * Render a map. * * \pre map != NULL * \param map the map to render - * \param srcx the x coordinate region - * \param srcy the y coordinate region */ void -map_draw(struct map *map, int srcx, int srcy); +map_draw(const struct map *map); /** * Force map repaint on its texture. @@ -148,6 +151,23 @@ map_repaint(struct map *map); /** + * Convert the map into a game state. + * + * Both objects must exist until the state is no longer used. + * + * \pre map != NULL + * \pre state != NULL + * \param map the map to use + * \param state the state to fill + * \post state->data is set to map + * \post state->handle is set + * \post state->update is set + * \post state->draw is set + */ +void +map_state(struct map *map, struct state *state); + +/** * Dispose map resources. * * \pre map != NULL diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/map_state.c --- a/librpg/rpg/map_state.c Mon Nov 09 10:36:39 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,354 +0,0 @@ -/* - * map_state.c -- state when player is on a map - * - * Copyright (c) 2020 David Demelier - * - * 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 - -#include -#include -#include -#include -#include -#include - -#include - -#include "map.h" -#include "map_state.h" -#include "walksprite.h" - -/* - * This is the speed the player moves on the map. - * - * SPEED represents the number of pixels it must move per SEC. - * SEC simply represends the number of milliseconds in one second. - */ -#define SPEED 100 -#define SEC 1000 - -/* - * Those are margins within the edge of the screen. The camera always try to - * keep those padding between the player and the screen. - */ -#define MARGIN_WIDTH 80 -#define MARGIN_HEIGHT 80 - -/* - * Convenient macros to access the state data. - */ -#define MAP() (&map_state_data.map) -#define PLAYER() (&map_state_data.player) -#define VIEW() (&map_state_data.view) - -/* - * This structure defines the possible movement of the player as flags since - * it's possible to make diagonal movements. - */ -enum movement { - MOVING_UP = 1 << 0, - MOVING_RIGHT = 1 << 1, - MOVING_DOWN = 1 << 2, - MOVING_LEFT = 1 << 3 -}; - -/* - * A bit of explanation within this array. The structure walksprite requires - * an orientation between 0-7 depending on the user direction. - * - * Since keys for moving the character may be pressed at the same time, we need - * a conversion table from "key pressed" to "orientation". - * - * When an orientation is impossible, it is set to -1. Example, when both left - * and right are pressed. - * - * MOVING_UP = 0001 = 0x1 - * MOVING_RIGHT = 0010 = 0x2 - * MOVING_DOWN = 0100 = 0x3 - * MOVING_LEFT = 1000 = 0x4 - */ -static unsigned int orientations[16] = { - [0x1] = 0, - [0x2] = 2, - [0x3] = 1, - [0x4] = 4, - [0x6] = 3, - [0x8] = 6, - [0x9] = 7, - [0xC] = 5 -}; - -/* - * Additional data that is not necessary to expose in map_state_data. - */ -static struct { - struct { - enum movement moving; - struct walksprite ws; - } player; - - struct { - int x; - int y; - unsigned int w; - unsigned int h; - } margin; -} cache; - -static void -center(void) -{ - VIEW()->x = PLAYER()->x - (VIEW()->w / 2); - VIEW()->y = PLAYER()->y - (VIEW()->h / 2); - - if (VIEW()->x < 0) - VIEW()->x = 0; - else if ((unsigned int)VIEW()->x > MAP()->data.real_w - VIEW()->w) - VIEW()->x = MAP()->data.real_w - VIEW()->w; - - if (VIEW()->y < 0) - VIEW()->y = 0; - else if ((unsigned int)VIEW()->y > MAP()->data.real_h - VIEW()->h) - VIEW()->y = MAP()->data.real_h - VIEW()->h; -} - -static void -start(struct state *st) -{ - (void)st; - - /* Adjust map properties. */ - struct map *m = &map_state_data.map.map; - - map_repaint(m); - MAP()->data.real_w = m->picture.w; - MAP()->data.real_h = m->picture.h; - - /* Adjust view. */ - VIEW()->w = window.w; - VIEW()->h = window.h; - - /* Adjust margin. */ - cache.margin.w = VIEW()->w - (MARGIN_WIDTH * 2); - cache.margin.h = VIEW()->h - (MARGIN_HEIGHT * 2); - - /* Center the view by default. */ - center(); - - /* Final bits. */ - walksprite_init(&cache.player.ws, &PLAYER()->sprite, 300); -} - -static void -handle_keydown(const union event *event) -{ - switch (event->key.key) { - case KEY_UP: - cache.player.moving |= MOVING_UP; - break; - case KEY_RIGHT: - cache.player.moving |= MOVING_RIGHT; - break; - case KEY_DOWN: - cache.player.moving |= MOVING_DOWN; - break; - case KEY_LEFT: - cache.player.moving |= MOVING_LEFT; - break; - default: - break; - } - - PLAYER()->angle = orientations[cache.player.moving]; -} - -static void -handle_keyup(const union event *event) -{ - switch (event->key.key) { - case KEY_UP: - cache.player.moving &= ~(MOVING_UP); - break; - case KEY_RIGHT: - cache.player.moving &= ~(MOVING_RIGHT); - break; - case KEY_DOWN: - cache.player.moving &= ~(MOVING_DOWN); - break; - case KEY_LEFT: - cache.player.moving &= ~(MOVING_LEFT); - break; - default: - break; - } -} - -static void -move_right(unsigned int delta) -{ - PLAYER()->x += delta; - - if (PLAYER()->x > (int)(cache.margin.x + cache.margin.w)) { - VIEW()->x = (PLAYER()->x - VIEW()->w) + MARGIN_WIDTH; - - if (VIEW()->x >= (int)(MAP()->data.real_w - VIEW()->w)) - VIEW()->x = MAP()->data.real_w - VIEW()->w; - } - - if (PLAYER()->x > (int)MAP()->data.real_w - 48) - PLAYER()->x = MAP()->data.real_w - 48; -} - -static void -move_left(unsigned int delta) -{ - PLAYER()->x -= delta; - - if (PLAYER()->x < cache.margin.x) { - VIEW()->x = PLAYER()->x - MARGIN_WIDTH; - - if (VIEW()->x < 0) - VIEW()->x = 0; - } - - if (PLAYER()->x < 0) - PLAYER()->x = 0; -} - -static void -move_down(unsigned int delta) -{ - PLAYER()->y += delta; - - if (PLAYER()->y > (int)(cache.margin.y + cache.margin.h)) { - VIEW()->y = (PLAYER()->y - VIEW()->h) + MARGIN_HEIGHT; - - if (VIEW()->y >= (int)(MAP()->data.real_h - VIEW()->h)) - VIEW()->y = MAP()->data.real_h - VIEW()->h; - } - - if (PLAYER()->y > (int)MAP()->data.real_h - 48) - PLAYER()->y = MAP()->data.real_h - 48; -} - -static void -move_up(unsigned int delta) -{ - PLAYER()->y -= delta; - - if (PLAYER()->y < cache.margin.y) { - VIEW()->y = PLAYER()->y - MARGIN_HEIGHT; - - if (VIEW()->y < 0) - VIEW()->y = 0; - } - - if (PLAYER()->y < 0) - PLAYER()->y = 0; -} - -static void -move(unsigned int ticks) -{ - /* This is the amount of pixels the player must move. */ - const int delta = SPEED * ticks / SEC; - - /* This is the rectangle within the view where users must be. */ - cache.margin.x = VIEW()->x + MARGIN_WIDTH; - cache.margin.y = VIEW()->y + MARGIN_HEIGHT; - - int dx = 0; - int dy = 0; - - if (cache.player.moving == 0) - return; - - if (cache.player.moving & MOVING_UP) - dy = -1; - if (cache.player.moving & MOVING_DOWN) - dy = 1; - if (cache.player.moving & MOVING_LEFT) - dx = -1; - if (cache.player.moving & MOVING_RIGHT) - dx = 1; - - /* Move the player and adjust view if needed. */ - if (dx > 0) - move_right(delta); - else if (dx < 0) - move_left(delta); - - if (dy > 0) - move_down(delta); - else if (dy < 0) - move_up(delta); - - walksprite_update(&cache.player.ws, ticks); -} - -static void -handle(struct state *st, const union event *event) -{ - (void)st; - - switch (event->type) { - case EVENT_KEYDOWN: - handle_keydown(event); - break; - case EVENT_KEYUP: - handle_keyup(event); - break; - default: - break; - } -} - -static void -update(struct state *st, unsigned int ticks) -{ - (void)st; - - move(ticks); -} - -static void -draw(struct state *st) -{ - (void)st; - - struct debug_report report = {0}; - - map_draw(&map_state_data.map.map, VIEW()->x, VIEW()->y); - walksprite_draw( - &cache.player.ws, - PLAYER()->angle, - PLAYER()->x - VIEW()->x, - PLAYER()->y - VIEW()->y); - - debugf(&report, "position: %d, %d", PLAYER()->x, - PLAYER()->y); - debugf(&report, "view: %d, %d", VIEW()->x, - VIEW()->y); -} - -struct map_state_data map_state_data; - -struct state map_state = { - .start = start, - .update = update, - .handle = handle, - .draw = draw -}; diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/map_state.h --- a/librpg/rpg/map_state.h Mon Nov 09 10:36:39 2020 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -/* - * map_state.h -- state when player is on a map - * - * Copyright (c) 2020 David Demelier - * - * 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. - */ - -#ifndef MOLKO_MAP_STATE_H -#define MOLKO_MAP_STATE_H - -/** - * \file map_state.h - * \brief State when player is on a map. - * \ingroup states - */ - -#include - -#include "map.h" - -/** - * \brief Data for the state. - * - * Update this structure before switching to this state. - */ -extern struct map_state_data { - /** - * Map properties. - */ - struct { - struct map_data data; /*!< (+) Map data. */ - struct map map; /*!< (+) Map object. */ - } map; - - /** - * Player position. - * - * If you adjust this structure, it is strictly encouraged to update - * the view as well. - */ - struct { - struct sprite sprite; /*!< (+) The sprite to use */ - int x; /*!< (+) Player position in x */ - int y; /*!< (+) Player position in y */ - int angle; /*!< (+) Player angle (see walksprite) */ - } player; - - /** - * Position and size of the view. - */ - struct { - int x; /*!< (+) Position in x */ - int y; /*!< (+) Position in y */ - unsigned int w; /*!< (+) View width */ - unsigned int h; /*!< (+) View height */ - } view; -} map_state_data; /*!< Access to data. */ - -/** - * \brief State when player is on a map. - */ -extern struct state map_state; - -#endif /* !MOLKO_MAP_STATE_H */ diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/walksprite.c --- a/librpg/rpg/walksprite.c Mon Nov 09 10:36:39 2020 +0100 +++ b/librpg/rpg/walksprite.c Mon Nov 09 10:37:36 2020 +0100 @@ -52,7 +52,7 @@ } void -walksprite_draw(struct walksprite *ws, unsigned int orientation, int x, int y) +walksprite_draw(const struct walksprite *ws, unsigned int orientation, int x, int y) { assert(ws); assert(orientation < 8); diff -r 658ee50b8bcb -r 852d0b7817ce librpg/rpg/walksprite.h --- a/librpg/rpg/walksprite.h Mon Nov 09 10:36:39 2020 +0100 +++ b/librpg/rpg/walksprite.h Mon Nov 09 10:37:36 2020 +0100 @@ -98,6 +98,6 @@ * \param y the y coordinate */ void -walksprite_draw(struct walksprite *ws, unsigned int orientation, int x, int y); +walksprite_draw(const struct walksprite *ws, unsigned int orientation, int x, int y); #endif /* !MOLKO_WALKSPRITE_H */ diff -r 658ee50b8bcb -r 852d0b7817ce molko/main.c --- a/molko/main.c Mon Nov 09 10:36:39 2020 +0100 +++ b/molko/main.c Mon Nov 09 10:37:36 2020 +0100 @@ -16,105 +16,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#define WINDOW_WIDTH 1280 -#define WINDOW_HEIGHT 720 - -static struct { - struct state splash; - struct state mainmenu; - struct state panic; -} states; - -static jmp_buf panic_buf; - -static noreturn void -unrecoverable(void) -{ - longjmp(panic_buf, 1); -} - -static void -init(void) -{ - if (!core_init() || !ui_init() || !rpg_init()) - panic(); - if (!window_open("Molko's Adventure", WINDOW_WIDTH, WINDOW_HEIGHT)) - panic(); - - /* - * From here, we can setup our panic state which requires a window - * to be running. - */ - - /* Init unrecoverable panic state. */ - panic_state(&states.panic); - panic_handler = unrecoverable; - - /* Init states. */ - splashscreen_state(&states.splash, &states.mainmenu); - mainmenu_state(&states.mainmenu); - - game_switch(&states.splash, true); -} - -static void -run(void) -{ - struct clock clock = { 0 }; - - while (game.state) { - unsigned int elapsed = clock_elapsed(&clock); - - clock_start(&clock); - - for (union event ev; event_poll(&ev); ) { - switch (ev.type) { - case EVENT_QUIT: - return; - default: - game_handle(&ev); - break; - } - } - - game_update(elapsed); - game_draw(); - - if ((elapsed = clock_elapsed(&clock)) < 20) - delay(20 - elapsed); - } -} - -static void -close(void) -{ - window_finish(); - rpg_finish(); - ui_finish(); - core_finish(); -} +#include int main(int argc, char **argv) @@ -122,20 +24,9 @@ (void)argc; (void)argv; - if (setjmp(panic_buf) == 0) { - /* Initial game run. */ - init(); - run(); - } else { - /* Clear event queue to avoid accidental key presses. */ - for (union event ev; event_poll(&ev); ) - continue; - - game_switch(&states.panic, true); - run(); - } - - close(); + molko_init(); + molko_run(); + molko_finish(); return 0; } diff -r 658ee50b8bcb -r 852d0b7817ce tests/CMakeLists.txt --- a/tests/CMakeLists.txt Mon Nov 09 10:36:39 2020 +0100 +++ b/tests/CMakeLists.txt Mon Nov 09 10:37:36 2020 +0100 @@ -26,13 +26,7 @@ molko_define_test( TARGET map SOURCES test-map.c - ASSETS - ${tests_SOURCE_DIR}/assets/maps/error-height.map - ${tests_SOURCE_DIR}/assets/maps/error-tileheight.map - ${tests_SOURCE_DIR}/assets/maps/error-tilewidth.map - ${tests_SOURCE_DIR}/assets/maps/error-title.map - ${tests_SOURCE_DIR}/assets/maps/error-width.map - ${tests_SOURCE_DIR}/assets/maps/sample-map.map + FLAGS DIRECTORY="${tests_SOURCE_DIR}/assets/maps/" ) molko_define_test(TARGET save SOURCES test-save.c) molko_define_test(TARGET state SOURCES test-state.c) diff -r 658ee50b8bcb -r 852d0b7817ce tests/assets/maps/sample-map.png Binary file tests/assets/maps/sample-map.png has changed diff -r 658ee50b8bcb -r 852d0b7817ce tests/assets/tilesets/sample-map.png Binary file tests/assets/tilesets/sample-map.png has changed diff -r 658ee50b8bcb -r 852d0b7817ce tests/test-map.c --- a/tests/test-map.c Mon Nov 09 10:36:39 2020 +0100 +++ b/tests/test-map.c Mon Nov 09 10:37:36 2020 +0100 @@ -19,26 +19,22 @@ #define GREATEST_USE_ABBREVS 0 #include +#include #include #include #include #include +#include #include -#include -#include -#include -#include -#include -#include - GREATEST_TEST test_sample(void) { - struct map_data map; + struct map_file loader = {0}; + struct map map = {0}; - GREATEST_ASSERT(map_data_openmem(&map, maps_sample_map, sizeof (maps_sample_map))); + GREATEST_ASSERT(map_file_open(&loader, DIRECTORY "sample-map.map", &map)); GREATEST_ASSERT_STR_EQ("This is a test map", map.title); GREATEST_ASSERT_EQ(2, map.w); GREATEST_ASSERT_EQ(2, map.h); @@ -52,51 +48,80 @@ GREATEST_ASSERT_EQ(5, map.layers[1].tiles[1]); GREATEST_ASSERT_EQ(6, map.layers[1].tiles[2]); GREATEST_ASSERT_EQ(7, map.layers[1].tiles[3]); + + map_finish(&map); + map_file_finish(&loader); + GREATEST_PASS(); } GREATEST_TEST test_error_title(void) { - struct map_data map; + struct map_file loader = {0}; + struct map map = {0}; - GREATEST_ASSERT(!map_data_openmem(&map, maps_error_title, sizeof (maps_error_title))); + GREATEST_ASSERT(!map_file_open(&loader, DIRECTORY "error-title.map", &map)); + + map_finish(&map); + map_file_finish(&loader); + GREATEST_PASS(); } GREATEST_TEST test_error_width(void) { - struct map_data map; + struct map_file loader = {0}; + struct map map = {0}; - GREATEST_ASSERT(!map_data_openmem(&map, maps_error_width, sizeof (maps_error_width))); + GREATEST_ASSERT(!map_file_open(&loader, DIRECTORY "error-width.map", &map)); + + map_finish(&map); + map_file_finish(&loader); + GREATEST_PASS(); } GREATEST_TEST test_error_height(void) { - struct map_data map; + struct map_file loader = {0}; + struct map map = {0}; - GREATEST_ASSERT(!map_data_openmem(&map, maps_error_height, sizeof (maps_error_height))); + GREATEST_ASSERT(!map_file_open(&loader, DIRECTORY "error-height.map", &map)); + + map_finish(&map); + map_file_finish(&loader); + GREATEST_PASS(); } GREATEST_TEST test_error_tilewidth(void) { - struct map_data map; + struct map_file loader = {0}; + struct map map = {0}; - GREATEST_ASSERT(!map_data_openmem(&map, maps_error_tilewidth, sizeof (maps_error_tilewidth))); + GREATEST_ASSERT(!map_file_open(&loader, DIRECTORY "error-tilewidth.map", &map)); + + map_finish(&map); + map_file_finish(&loader); + GREATEST_PASS(); } GREATEST_TEST test_error_tileheight(void) { - struct map_data map; + struct map_file loader = {0}; + struct map map = {0}; - GREATEST_ASSERT(!map_data_openmem(&map, maps_error_tileheight, sizeof (maps_error_tileheight))); + GREATEST_ASSERT(!map_file_open(&loader, DIRECTORY "error-tileheight.map", &map)); + + map_finish(&map); + map_file_finish(&loader); + GREATEST_PASS(); } @@ -120,8 +145,18 @@ main(int argc, char **argv) { GREATEST_MAIN_BEGIN(); - GREATEST_RUN_SUITE(basics); - GREATEST_RUN_SUITE(errors); + + /* + * This test opens graphical images and therefore need to initialize a + * window and all of the API. As tests sometime run on headless machine + * we will skip if it fails to initialize. + */ + + if (core_init() && window_open("test-map", 100, 100)) { + GREATEST_RUN_SUITE(basics); + GREATEST_RUN_SUITE(errors); + } + GREATEST_MAIN_END(); return 0;