/* * Copyright (C) 2013 Patrick "P. J." McDermott * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program. If not, see * . */ #include #include #include #include #include #include #include #include "map.h" #include "resource.h" #include "tmx.h" #include "tileset.h" #include "../xml.h" #include "../base64.h" #include "../compression.h" #include "../logging.h" struct resource_table map_res; static void XMLCALL tmx_map_start(void *, const char *, const char **); static void XMLCALL tmx_map_end(void *, const char *); static void XMLCALL tmx_map_el_start(void *, const char *, const char **); static void XMLCALL tmx_map_property_start(void *, const char *, const char **); static void XMLCALL tmx_tilesetemb_end(void *, const char *); static void XMLCALL tmx_layer_el_start(void *, const char *, const char **); static void XMLCALL tmx_layer_end(void *, const char *); static void XMLCALL tmx_data_end(void *, const char *); static void XMLCALL tmx_data_cdata(void *, const char *, int); static void XMLCALL tmx_objectgroup_el_start(void *, const char *, const char **); static void XMLCALL tmx_objectgroup_end(void *, const char *); static void XMLCALL tmx_object_exit_el_start(void *, const char *, const char **); static void XMLCALL tmx_object_exit_end(void *, const char *); static void XMLCALL tmx_object_spawn_el_start(void *, const char *, const char **); static void XMLCALL tmx_object_spawn_end(void *, const char *); static void XMLCALL tmx_object_exit_property_start(void *, const char *, const char **); struct map * map_get(const char *path) { struct map *map; XML_Parser p; FILE *tmx_fp; void *tmx_buf; size_t len; enum XML_Status status; map = (struct map *) resource_get(&map_res, path); if (map != NULL) { resource_use((struct resource *) map); return map; } map = resource_alloc(path, sizeof(*map)); if (map == NULL) { return NULL; } map->dirname = dirname(strdup(path)); p = XML_ParserCreate(NULL); if (p == NULL) { warn("Failed to create TMX parser"); return NULL; } XML_UseParserAsHandlerArg(p); xml_node_push(p, map, tmx_map_start, tmx_invalid_end, NULL); tmx_fp = fopen(path, "rb"); if (tmx_fp == NULL) { warn("Failed to open TMX file"); xml_node_pop(p); XML_ParserFree(p); return NULL; } tmx_buf = XML_GetBuffer(p, 8192); if (tmx_buf == NULL) { warn("Failed to create TMX parse buffer"); xml_node_pop(p); XML_ParserFree(p); fclose(tmx_fp); return NULL; } while (!feof(tmx_fp)) { len = fread(tmx_buf, 1, 8192, tmx_fp); status = XML_ParseBuffer(p, len, feof(tmx_fp)); if (status == XML_STATUS_OK) { continue; } warn("Failed to parse TMX file (%s)", XML_ErrorString(XML_GetErrorCode(p))); xml_node_pop(p); XML_ParserFree(p); fclose(tmx_fp); return NULL; } XML_ParserFree(p); fclose(tmx_fp); resource_use((struct resource *) map); resource_add(&map_res, path, (struct resource *) map); return map; } void map_add_tileset(struct map *m, struct tileset *t, Uint32 firstgid) { struct map_tileset *mts; mts = malloc(sizeof(*mts)); if (mts == NULL) { return; } mts->tileset = t; mts->firstgid = firstgid; mts->next = NULL; if (m->tilesets_head == NULL) { m->tilesets_head = mts; } else { m->tilesets_tail->next = mts; } m->tilesets_tail = mts; } struct map_palette * map_get_palette(struct map *m, const char *name) { if (strcmp(name, "default") == 0) { return &m->palettes[MAP_PALETTE_DEFAULT]; } else if (strcmp(name, "morn") == 0) { return &m->palettes[MAP_PALETTE_MORN]; } else if (strcmp(name, "day") == 0) { return &m->palettes[MAP_PALETTE_DAY]; } else if (strcmp(name, "eve") == 0) { return &m->palettes[MAP_PALETTE_EVE]; } else if (strcmp(name, "night") == 0) { return &m->palettes[MAP_PALETTE_NIGHT]; } else { return NULL; } } struct layer * map_get_layer(struct map *m, const char *name) { if (strcmp(name, "ground") == 0) { return &m->layers[MAP_LAYER_GROUND]; } else if (strcmp(name, "obj-low") == 0) { return &m->layers[MAP_LAYER_OBJ_LOW]; } else if (strcmp(name, "obj-mid") == 0) { return &m->layers[MAP_LAYER_OBJ_MID]; } else if (strcmp(name, "obj-high") == 0) { return &m->layers[MAP_LAYER_OBJ_HIGH]; } else if (strcmp(name, "collision") == 0) { return &m->layers[MAP_LAYER_COLLISION]; } else { return NULL; } } void map_add_exit(struct map *m, struct map_exit *e) { int i, j; e->next = NULL; if (m->map_exits_head == NULL) { m->map_exits_head = e; } else { m->map_exits_tail->next = e; } m->map_exits_tail = e; for (i = 0; i < e->width; ++i) { for (j = 0; j < e->height; ++j) { m->collisions[j * m->width + i].type = COLLISION_EXIT; m->collisions[j * m->width + i].data.e = e; } } } static void XMLCALL tmx_map_start(void *pv, const char *name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct map *m; #ifdef DEBUG_TMX debug("<%s> (map)", name); #endif if (xml_check_tag(name, "map")) { m = xml_node_peek(p); xml_get_int_attr(p, attr, "width", &m->width, 1); xml_get_int_attr(p, attr, "height", &m->height, 1); xml_get_int_attr(p, attr, "tilewidth", &m->tilewidth, 1); xml_get_int_attr(p, attr, "tileheight", &m->tileheight, 1); m->collisions = malloc(m->width * m->height * sizeof(*m->collisions)); if (m->collisions == NULL) { return; } memset(m->collisions, 0, m->width * m->height * sizeof(*m->collisions)); xml_node_push(p, m, tmx_map_el_start, tmx_map_end, NULL); } else { xml_unexpected_start_tag(p, name, "map"); } } static void XMLCALL tmx_map_end(void *pv, const char *name) { XML_Parser p = (XML_Parser) pv; #ifdef DEBUG_TMX debug(" (map)", name); #endif if (xml_check_tag(name, "map")) { xml_node_pop(p); } else { xml_unexpected_end_tag(p, name, "map"); } } static void XMLCALL tmx_map_el_start(void *pv, const char *name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct map *m; char *dirname; char *source; char *path; struct tileset *ts; char *ly_name; struct layer *ly; #ifdef DEBUG_TMX debug("<%s> (map child)", name); #endif m = xml_node_peek(p); if (xml_check_tag(name, "properties")) { xml_node_push(p, m, tmx_map_property_start, tmx_unused_end, NULL); } else if (xml_check_tag(name, "tileset")) { dirname = m->dirname; source = NULL; xml_get_string_attr(p, attr, "source", &source, 0); if (source != NULL) { /* External tileset. */ path = malloc(strlen(dirname) + strlen(source) + 2); if (path == NULL) { return; } sprintf(path, "%s/%s", dirname, source); ts = tileset_get(path, dirname); free(source); xml_node_push(p, ts, tmx_invalid_start, tmx_tilesetemb_end, NULL); } else { /* Embedded tileset. */ ts = resource_alloc("internal", sizeof(*ts)); if (ts == NULL) { return; } ts->dirname = dirname; xml_get_string_attr(p, attr, "name", &ts->name, 1); xml_get_int_attr(p, attr, "tilewidth", &ts->tilewidth, 1); xml_get_int_attr(p, attr, "tileheight", &ts->tileheight, 1); if (strcmp(ts->name, "collision") == 0) { ts->type = TILESET_TYPE_COLLISION; } else { ts->type = TILESET_TYPE_IMAGE; } xml_node_push(p, ts, tmx_tileset_el_start, tmx_tilesetemb_end, NULL); } xml_get_uint32_attr(p, attr, "firstgid", &m->cur_ts_firstgid, 1); } else if (xml_check_tag(name, "layer")) { ly_name = NULL; xml_get_string_attr(p, attr, "name", &ly_name, 1); ly = map_get_layer(m, ly_name); free(ly_name); if (ly != NULL) { ly->map = m; xml_node_push(p, ly, tmx_layer_el_start, tmx_layer_end, NULL); } else { xml_node_push(p, NULL, tmx_unused_start, tmx_layer_end, NULL); } } else if (xml_check_tag(name, "objectgroup")) { xml_node_push(p, m, tmx_objectgroup_el_start, tmx_objectgroup_end, NULL); } else { xml_unexpected_start_tag(p, name, "properties, tileset, layer, or objectgroup"); } } static void XMLCALL tmx_map_property_start(void *pv, const char *name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct map *m; char *attr_name; char *attr_value; char *path; struct map_palette *pal; Uint32 pal_h; Uint32 pal_m; char pal_name[256]; #ifdef DEBUG_TMX debug("<%s> (map property)", name); #endif m = xml_node_peek(p); if (xml_check_tag(name, "property")) { xml_get_string_attr(p, attr, "name", &attr_name, 1); xml_get_string_attr(p, attr, "value", &attr_value, 1); if (strcmp(attr_name, "palette-default") == 0) { pal = map_get_palette(m, "default"); path = malloc(strlen(m->dirname) + strlen(attr_value) + 2); if (path == NULL) { /* XXX: This won't run xml_node_push() and the * stack will be corrupted. */ return; } sprintf(path, "%s/%s", m->dirname, attr_value); pal->palette = palette_get(path); pal->min = 0; free(path); } else if (strncmp(attr_name, "palette-", 7) == 0) { pal = map_get_palette(m, attr_name + 8); if (pal != NULL) { /* Uint32 is overkill for pal_h and pal_m, but * GCC complains about mismatched "%u" format * modifiers and "Uint8 *"-type arguments. */ /* XXX: This won't handle file names with * spaces. */ sscanf(attr_value, "%" PRIu32 ":%" PRIu32 "," "%255s", &pal_h, &pal_m, pal_name); path = malloc(strlen(m->dirname) + strlen(pal_name) + 2); if (path == NULL) { /* XXX: This won't run xml_node_push() * and the stack will be corrupted. */ return; } sprintf(path, "%s/%s", m->dirname, pal_name); pal->palette = palette_get(path); pal->min = pal_h * 60 + pal_m; free(path); } } free(attr_name); free(attr_value); xml_node_push(p, NULL, tmx_invalid_start, tmx_unused_end, NULL); } else { xml_unexpected_start_tag(p, name, "property"); } } static void XMLCALL tmx_tilesetemb_end(void *pv, const char *name) { XML_Parser p = (XML_Parser) pv; struct tileset *ts; struct map *m; #ifdef DEBUG_TMX debug(" (tileset)", name); #endif if (xml_check_tag(name, "tileset")) { ts = xml_node_pop(p); m = xml_node_peek(p); map_add_tileset(m, ts, m->cur_ts_firstgid); } else { xml_unexpected_end_tag(p, name, "tileset"); } } static void XMLCALL tmx_layer_el_start(void *pv, const char *name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct layer *ly; #ifdef DEBUG_TMX debug("<%s> (layer child)", name); #endif ly = xml_node_peek(p); if (xml_check_tag(name, "properties")) { /* Not used by engine. */ xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } else if (xml_check_tag(name, "data")) { /* NB: This parser requires an encoding. The author is too lazy * to parse a bunch of elements. */ xml_get_string_attr(p, attr, "encoding", &ly->encoding, 1); xml_get_string_attr(p, attr, "compression", &ly->compression, 0); xml_node_push(p, ly, tmx_invalid_start, tmx_data_end, tmx_data_cdata); } else { xml_unexpected_start_tag(p, name, "properties or data"); } } static void XMLCALL tmx_layer_end(void *pv, const char *name) { XML_Parser p = (XML_Parser) pv; #ifdef DEBUG_TMX debug(" (layer)", name); #endif if (xml_check_tag(name, "layer")) { xml_node_pop(p); } else { xml_unexpected_end_tag(p, name, "layer"); } } static void XMLCALL tmx_data_end(void *pv, const char *name) { XML_Parser p = (XML_Parser) pv; struct layer *ly; size_t decoded_len; size_t decomp_len; char *decoded_buf; char *decomp_buf; size_t i; #ifdef DEBUG_TMX debug(" (data)", name); #endif ly = xml_node_peek(p); if (xml_check_tag(name, "data")) { decoded_len = strlen(ly->raw_data); decomp_len = 4 * ly->map->width * ly->map->height; #ifdef DEBUG_TMX debug("Expected map data size: %d", decomp_len); #endif decoded_buf = malloc(decoded_len + 1); if (decoded_buf == NULL) { warn("Failed to allocate layer data buffer"); return; } if (strcmp(ly->encoding, "base64") == 0) { #ifdef DEBUG_TMX debug("Decoding base 64 layer data..."); #endif base64_decode(ly->raw_data, decoded_buf, decoded_len + 1); } if (ly->compression == NULL) { #ifdef DEBUG_TMX debug("Layer data already decompressed"); #endif decomp_buf = decoded_buf; } else if (strcmp(ly->compression, "zlib") == 0) { #ifdef DEBUG_TMX debug("Decompressing layer data with zlib..."); #endif decomp_buf = malloc(decomp_len); if (decomp_buf == NULL) { warn("Failed to allocate layer data buffer"); return; } decompress(decoded_buf, decoded_len, decomp_buf, decomp_len); free(decoded_buf); } else if (strcmp(ly->compression, "gzip") == 0) { #ifdef DEBUG_TMX debug("Decompressing layer data with gzip..."); #endif decomp_buf = malloc(decomp_len); if (decomp_buf == NULL) { warn("Failed to allocate layer data buffer"); } decompress(decoded_buf, decoded_len, decomp_buf, decomp_len); free(decoded_buf); } else { /* This should never happen. This branch exists only to * silence GCC's maybe-uninitialized warning on * decomp_buf below. */ return; } free(ly->raw_data); ly->tiles = malloc(decomp_len); for (i = 0; i < decomp_len / 4; ++i) { /* Convert each tile GID to the system's byte order. */ ly->tiles[i] = (decomp_buf[i * 4 + 0] & 0xFF << 000) | (decomp_buf[i * 4 + 1] & 0xFF << 010) | (decomp_buf[i * 4 + 2] & 0xFF << 020) | (decomp_buf[i * 4 + 3] & 0xFF << 030); } free(decomp_buf); xml_node_pop(p); } else { xml_unexpected_end_tag(p, name, "data"); } } static void XMLCALL tmx_data_cdata(void *pv, const char *s, int len) { XML_Parser p = (XML_Parser) pv; struct layer *ly; char *s_z; char *s_z_trimmed, *s_z_trimmed_end; #ifdef DEBUG_TMX debug("[CDATA]"); #endif ly = xml_node_peek(p); s_z = s_z_trimmed = strndup(s, len); while(isspace(*s_z_trimmed)) { ++s_z_trimmed; --len; } if (*s_z_trimmed == '\0') { return; } s_z_trimmed_end = s_z_trimmed + len - 1; while (s_z_trimmed_end > s_z_trimmed && isspace(*s_z_trimmed_end)) { --s_z_trimmed_end; } *(s_z_trimmed_end + 1) = '\0'; ly->raw_data = strdup(s_z_trimmed); free(s_z); } static void XMLCALL tmx_objectgroup_el_start(void *pv, const char * name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct map *m; char *type; struct map_exit *e; struct map_spawn *s; #ifdef DEBUG_TMX debug("<%s> (objectgroup child)", name); #endif m = xml_node_peek(p); if (xml_check_tag(name, "properties")) { /* Not used by engine. */ xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } else if (xml_check_tag(name, "object")) { type = NULL; xml_get_string_attr(p, attr, "type", &type, 0); if (type == NULL) { xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } else if (strcmp(type, "exit") == 0) { e = malloc(sizeof(*e)); if (e == NULL) { return; } e->map = m; xml_get_int_attr(p, attr, "x", &e->x, 1); xml_get_int_attr(p, attr, "y", &e->y, 1); xml_get_int_attr(p, attr, "width", &e->width, 1); xml_get_int_attr(p, attr, "height", &e->height, 1); e->x /= m->tilewidth; e->y /= m->tileheight; e->width /= m->tilewidth; e->height /= m->tileheight; xml_node_push(p, e, tmx_object_exit_el_start, tmx_object_exit_end, NULL); } else if (strcmp(type, "spawn") == 0) { s = malloc(sizeof(*s)); if (s == NULL) { return; } s->map = m; xml_get_int_attr(p, attr, "x", &s->x, 1); xml_get_int_attr(p, attr, "y", &s->y, 1); xml_get_int_attr(p, attr, "width", &s->width, 1); xml_get_int_attr(p, attr, "height", &s->height, 1); xml_node_push(p, s, tmx_object_spawn_el_start, tmx_object_spawn_end, NULL); } else { xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } } else { xml_unexpected_start_tag(p, name, "properties or object"); } } static void XMLCALL tmx_objectgroup_end(void *pv, const char *name) { XML_Parser p = (XML_Parser) pv; #ifdef DEBUG_TMX debug(" (objectgroup)", name); #endif if (xml_check_tag(name, "objectgroup")) { xml_node_pop(p); } else { xml_unexpected_end_tag(p, name, "objectgroup"); } } static void XMLCALL tmx_object_exit_el_start(void *pv, const char * name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct map_exit *e; #ifdef DEBUG_TMX debug("<%s> (object type=\"exit\" child)", name); #endif e = xml_node_peek(p); if (xml_check_tag(name, "properties")) { /* has no attributes, but GCC warns of an * "unused parameter ‘attr’". */ for (; 0; ++attr); xml_node_push(p, e, tmx_object_exit_property_start, tmx_unused_end, NULL); } else if (xml_check_tag(name, "polygon")) { /* Not used by engine. */ xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } else if (xml_check_tag(name, "polyline")) { /* Not used by engine. */ xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } else { xml_unexpected_start_tag(p, name, "properties, polygon, or polyline"); } } static void XMLCALL tmx_object_exit_end(void *pv, const char *name) { XML_Parser p = (XML_Parser) pv; struct map_exit *e; struct map *m; #ifdef DEBUG_TMX debug(" (object type=\"exit\")", name); #endif if (xml_check_tag(name, "object")) { e = xml_node_pop(p); m = xml_node_peek(p); map_add_exit(m, e); } else { xml_unexpected_end_tag(p, name, "object"); } } static void XMLCALL tmx_object_exit_property_start(void *pv, const char *name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct map_exit *e; char *attr_name; char *attr_value; char *path; #ifdef DEBUG_TMX debug("<%s> (object type=\"exit\" property)", name); #endif e = xml_node_peek(p); if (xml_check_tag(name, "property")) { xml_get_string_attr(p, attr, "name", &attr_name, 1); xml_get_string_attr(p, attr, "value", &attr_value, 1); if (strcmp(attr_name, "map") == 0) { e->target_map_name = strdup(attr_value); /* TODO: Lazily get map. We don't need to load the * entire world immediately. */ path = malloc(strlen(e->map->dirname) + strlen(attr_value) + 6); if (path == NULL) { return; } sprintf(path, "%s/%s.tmx", e->map->dirname, attr_value); e->target_map = map_get(path); free(path); /* Yeah, don't do that yet. ^ */ } else if (strcmp(attr_name, "coords") == 0) { sscanf(attr_value, "%d,%d", &e->target_x_coord, &e->target_y_coord); } free(attr_name); free(attr_value); xml_node_push(p, NULL, tmx_invalid_start, tmx_unused_end, NULL); } else { xml_unexpected_start_tag(p, name, "property"); } } static void XMLCALL tmx_object_spawn_el_start(void *pv, const char * name, const char **attr) { XML_Parser p = (XML_Parser) pv; struct map_spawn *s; #ifdef DEBUG_TMX debug("<%s> (object type=\"spawn\" child)", name); #endif s = xml_node_peek(p); if (xml_check_tag(name, "properties")) { /* has no attributes, but GCC warns of an * "unused parameter ‘attr’". */ for (; 0; ++attr); /* TODO */ xml_node_push(p, s, tmx_unused_start, tmx_unused_end, NULL); } else if (xml_check_tag(name, "polygon")) { /* Not used by engine. */ xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } else if (xml_check_tag(name, "polyline")) { /* Not used by engine. */ xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL); } else { xml_unexpected_start_tag(p, name, "properties, polygon, or polyline"); } } static void XMLCALL tmx_object_spawn_end(void *pv, const char *name) { XML_Parser p = (XML_Parser) pv; #ifdef DEBUG_TMX debug(" (object type=\"spawn\")", name); #endif if (xml_check_tag(name, "object")) { xml_node_pop(p); } else { xml_unexpected_end_tag(p, name, "object"); } }