/* * Copyright (C) 2021 P. J. McDermott * * This file is part of Dodge Balls * * Dodge Balls is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Dodge Balls is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Dodge Balls. If not, see . */ #include #include #include #include #include "defs.h" #include "dirs.h" #include "map.h" #include "output.h" #include "xml.h" struct db_map { int w; int h; int tw; int th; Uint8 bg_r; Uint8 bg_g; Uint8 bg_b; int fr; }; static char * _db_map_path(const char *game_id, const char *file, const char *ext) { const char *games_dir; char *path; games_dir = db_get_games_dir(); path = malloc((strlen(games_dir) + strlen(game_id) + strlen(file) + strlen(ext) + 3) * sizeof(*path)); if (path == NULL) { db_err("Failed to allocate memory"); return NULL; } sprintf(path, "%s/%s/%s%s", games_dir, game_id, file, ext); return path; } static void XMLCALL _db_tmx_invalid_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (invalid)", name); p = (XML_Parser) pv; db_xml_node_pop(p); } static void XMLCALL _db_tmx_invalid_start(void *pv, const char *name, const char **attr __attribute__((__unused__))) { XML_Parser p; db_dbg(" <%s> (invalid)", name); p = (XML_Parser) pv; db_xml_node_push(p, NULL, _db_tmx_invalid_start, _db_tmx_invalid_end, NULL); } static void XMLCALL _db_tmx_property_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (property)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "property")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "property"); } } static void XMLCALL _db_tmx_property_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; char *p_name; char *p_type; db_dbg(" <%s> (property)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "property")) { db_xml_get_string_attr(p, attr, "name", &p_name, 1); db_xml_get_string_attr(p, attr, "type", &p_type, 1); if (strcmp(p_name, "framerate") == 0) { if (strcmp(p_type, "int") != 0) { db_err("Framerate must be an integer"); free(p_name); free(p_type); XML_StopParser(p, XML_FALSE); return; } db_xml_get_int_attr(p, attr, "value", &map->fr, 1); db_dbg(" Framerate: %d", map->fr); } else { db_dbg("Skipping unknown property \"%s\"", p_name); } free(p_name); free(p_type); db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_property_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "property"); } } static void XMLCALL _db_tmx_properties_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (properties)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "properties")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "properties"); } } static void XMLCALL _db_tmx_tileset_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (tileset)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "tileset")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "tileset"); } } static void XMLCALL _db_tmx_layer_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (layer)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "layer")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "layer"); } } static void XMLCALL _db_tmx_objectgroup_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (objectgroup)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "objectgroup")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "objectgroup"); } } static void XMLCALL _db_tmx_map_el_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; db_dbg(" <%s> (map child)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "properties")) { db_xml_node_push(p, map, _db_tmx_property_start, _db_tmx_properties_end, NULL); } else if (db_xml_check_tag(name, "tileset")) { /* TODO: attr */ db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_tileset_end, NULL); } else if (db_xml_check_tag(name, "layer")) { /* TODO: data */ db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_layer_end, NULL); } else if (db_xml_check_tag(name, "objectgroup")) { /* TODO: object */ db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_objectgroup_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "properties, tileset, layer, or objectgroup"); } } static void XMLCALL _db_tmx_map_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (map)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "map")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "map"); } } static void XMLCALL _db_tmx_map_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; char *orientation; char *renderorder; char *backgroundcolor; unsigned int bg_r; unsigned int bg_g; unsigned int bg_b; db_dbg(" <%s> (map)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "map")) { /* Check orientation */ db_xml_get_string_attr(p, attr, "orientation", &orientation, 1); if (strcmp(orientation, "orthogonal") != 0) { db_err("Map must be orthogonal"); XML_StopParser(p, XML_FALSE); return; } free(orientation); /* Check render order */ db_xml_get_string_attr(p, attr, "renderorder", &renderorder, 1); if (strcmp(renderorder, "right-down") != 0) { db_err("Map must be rendered right-down"); XML_StopParser(p, XML_FALSE); return; } free(renderorder); /* Get and check size */ db_xml_get_int_attr(p, attr, "width", &map->w, 1); db_xml_get_int_attr(p, attr, "height", &map->h, 1); db_xml_get_int_attr(p, attr, "tilewidth", &map->tw, 1); db_xml_get_int_attr(p, attr, "tileheight", &map->th, 1); if (map->w * map->tw != DB_WINDOW_W || map->h * map->th != DB_WINDOW_H) { db_err("Map size must be %dx%d px", DB_WINDOW_W, DB_WINDOW_H); XML_StopParser(p, XML_FALSE); return; } db_dbg(" Map size: %dx%d px", map->w, map->h); db_dbg(" Tile size: %dx%d px", map->tw, map->th); /* Get and check background color */ db_xml_get_string_attr(p, attr, "backgroundcolor", &backgroundcolor, 1); if (sscanf(backgroundcolor, "#%02x%02x%02x", &bg_r, &bg_g, &bg_b) != 3) { db_err("Background color must be \"#xxxxxx\""); XML_StopParser(p, XML_FALSE); return; } map->bg_r = bg_r; map->bg_g = bg_g; map->bg_b = bg_b; db_dbg(" Background color: (0x%02x, 0x%02x, 0x%02x)", map->bg_r, map->bg_g, map->bg_b); db_xml_node_push(p, map, _db_tmx_map_el_start, _db_tmx_map_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "map"); } } struct db_map * db_map_new(const char *game_id, const char *level_id) { struct db_map *map; XML_Parser p; char *path; FILE *fp; void *buf; size_t len; enum XML_Status status; map = calloc(1, sizeof(*map)); if (map == NULL) { db_err("Failed to allocate memory"); return NULL; } p = XML_ParserCreate(NULL); if (p == NULL) { free(map); return NULL; } XML_UseParserAsHandlerArg(p); db_xml_node_push(p, map, _db_tmx_map_start, _db_tmx_invalid_end, NULL); path = _db_map_path(game_id, level_id, ".tmx"); if (path == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); free(map); return NULL; } fp = fopen(path, "rb"); if (fp == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); free(map); return NULL; } buf = XML_GetBuffer(p, 8192); if (buf == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); fclose(fp); free(map); return NULL; } db_dbg("Parsing <%s>:", path); free(path); while (!feof(fp)) { len = fread(buf, 1, 8192, fp); status = XML_ParseBuffer(p, len, feof(fp)); if (status == XML_STATUS_OK) { continue; } db_err("Failed to parse map information (%s)", XML_ErrorString(XML_GetErrorCode(p))); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); free(map); return NULL; } db_dbg("Parsing done"); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); return map; }