/*
* 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 "../xml.h"
#include "../base64.h"
#include "../compression.h"
#include "../logging.h"
struct resource_table ts_res;
struct resource_table map_res;
static struct tileset *tileset_get(const char *, const char *);
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_tileset_start(void *, const char *, const char **);
static void XMLCALL tmx_tileset_end(void *, const char *);
static void XMLCALL tmx_tilesetemb_end(void *, const char *);
static void XMLCALL tmx_tileset_el_start(void *, const char *, const char **);
static void XMLCALL tmx_image_end(void *, const char *);
static void XMLCALL tmx_tile_end(void *, const char *);
static void XMLCALL tmx_tile_properties_start(void *, const char *,
const char **);
static void XMLCALL tmx_tile_property_start(void *, const char *,
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 **);
static void XMLCALL tmx_unused_start(void *, const char *, const char **);
static void XMLCALL tmx_unused_end(void *, const char *);
static void XMLCALL tmx_invalid_start(void *, const char *, const char **);
static void XMLCALL tmx_invalid_end(void *, 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 struct tileset *
tileset_get(const char *path, const char *basedir)
{
struct tileset *ts;
XML_Parser p;
FILE *tmx_fp;
void *tmx_buf;
size_t len;
enum XML_Status status;
ts = (struct tileset *) resource_get(&ts_res, path);
if (ts != NULL) {
resource_use((struct resource *) ts);
return ts;
}
ts = resource_alloc(path, sizeof(*ts));
if (ts == NULL) {
return NULL;
}
ts->dirname = strdup(basedir);
p = XML_ParserCreate(NULL);
if (p == NULL) {
warn("Failed to create TSX parser");
return NULL;
}
XML_UseParserAsHandlerArg(p);
xml_node_push(p, ts, tmx_tileset_start, tmx_invalid_end, NULL);
tmx_fp = fopen(path, "rb");
if (tmx_fp == NULL) {
warn("Failed to open TSX file");
xml_node_pop(p);
XML_ParserFree(p);
return NULL;
}
tmx_buf = XML_GetBuffer(p, 8192);
if (tmx_buf == NULL) {
warn("Failed to create TSX 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 TSX 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 *) ts);
resource_add(&ts_res, path, (struct resource *) ts);
return ts;
}
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("%s> (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_tileset_start(void *pv, const char *name, const char **attr)
{
XML_Parser p = (XML_Parser) pv;
struct tileset *ts;
#ifdef DEBUG_TMX
debug("<%s> (external tileset)", name);
#endif
if (xml_check_tag(name, "tileset")) {
ts = xml_node_peek(p);
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_tileset_end,
NULL);
} else {
xml_unexpected_start_tag(p, name, "tileset");
}
}
static void XMLCALL
tmx_tileset_end(void *pv, const char *name)
{
XML_Parser p = (XML_Parser) pv;
#ifdef DEBUG_TMX
debug("%s> (external tileset)", name);
#endif
if (xml_check_tag(name, "tileset")) {
xml_node_pop(p);
} else {
xml_unexpected_end_tag(p, name, "tileset");
}
}
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("%s> (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_tileset_el_start(void *pv, const char *name, const char **attr)
{
XML_Parser p = (XML_Parser) pv;
struct tileset *ts;
char *dirname;
char *source;
char *path;
struct image *img;
#ifdef DEBUG_TMX
debug("<%s> (tileset child)", name);
#endif
ts = xml_node_peek(p);
if (xml_check_tag(name, "tileoffset")) {
/* Not used by engine. */
xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL);
} else 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, "image")) {
xml_get_int_attr(p, attr, "width", &ts->width, 1);
xml_get_int_attr(p, attr, "height", &ts->height, 1);
ts->width /= ts->tilewidth;
ts->height /= ts->tileheight;
img = NULL;
if (ts->type == TILESET_TYPE_IMAGE) {
dirname = ts->dirname;
xml_get_string_attr(p, attr, "source", &source, 0);
path = malloc(strlen(dirname) + strlen(source) + 2);
if (path == NULL) {
return;
}
sprintf(path, "%s/%s", dirname, source);
img = img_png_get(path);
/* TODO: Get the color key from the trans attribute. */
SDL_SetColorKey(img->image, SDL_SRCCOLORKEY,
SDL_MapRGB(img->image->format,
0xFC, 0x00, 0xFF));
free(source);
} else if (ts->type == TILESET_TYPE_COLLISION) {
ts->collision_tiles = malloc(ts->width * ts->height *
sizeof(*ts->collision_tiles));
if (ts->collision_tiles == NULL) {
return;
}
}
xml_node_push(p, img, tmx_invalid_start, tmx_image_end, NULL);
} else if (xml_check_tag(name, "tile")) {
xml_get_int_attr(p, attr, "id", &ts->cur_collision_tile, 1);
xml_node_push(p, ts, tmx_tile_properties_start, tmx_tile_end,
NULL);
} else {
xml_unexpected_start_tag(p, name, "tileoffset, image, tile");
}
}
static void XMLCALL
tmx_image_end(void *pv, const char *name)
{
XML_Parser p = (XML_Parser) pv;
struct image *img;
struct tileset *ts;
#ifdef DEBUG_TMX
debug("%s> (image)", name);
#endif
if (xml_check_tag(name, "image")) {
img = (struct image *) xml_node_pop(p);
ts = (struct tileset *) xml_node_peek(p);
ts->image = img;
} else {
xml_unexpected_end_tag(p, name, "image");
}
}
static void XMLCALL
tmx_tile_end(void *pv, const char *name)
{
XML_Parser p = (XML_Parser) pv;
#ifdef DEBUG_TMX
debug("%s> (tile)", name);
#endif
xml_node_pop(p);
if (xml_check_tag(name, "tile")) {
} else {
xml_unexpected_end_tag(p, name, "tile");
}
}
static void XMLCALL
tmx_tile_properties_start(void *pv, const char *name, const char **attr)
{
XML_Parser p = (XML_Parser) pv;
struct tileset *ts;
#ifdef DEBUG_TMX
debug("<%s> (tile properties)", name);
#endif
ts = 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, ts, tmx_tile_property_start, tmx_unused_end,
NULL);
} else {
xml_unexpected_start_tag(p, name, "properties");
}
}
static void XMLCALL
tmx_tile_property_start(void *pv, const char *name, const char **attr)
{
XML_Parser p = (XML_Parser) pv;
struct tileset *ts;
char *attr_name;
char *attr_value;
int coll_t, coll_r, coll_b, coll_l;
int n;
#ifdef DEBUG_TMX
debug("<%s> (tile property)", name);
#endif
ts = 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, "collision") ==0) {
n = sscanf(attr_value, "%d,%d,%d,%d",
&coll_t, &coll_r, &coll_b, &coll_l);
if (n != 4) {
return;
}
ts->collision_tiles[ts->cur_collision_tile] =
coll_t << 3 | coll_r << 2 |
coll_b << 1 | coll_l << 0;
}
free(attr_name);
free(attr_value);
for (; 0; ++attr, ++ts);
xml_node_push(p, NULL, tmx_invalid_start, tmx_unused_end, NULL);
} else {
xml_unexpected_start_tag(p, name, "property");
}
}
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("%s> (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("%s> (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("%s> (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("%s> (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("%s> (object type=\"spawn\")", name);
#endif
if (xml_check_tag(name, "object")) {
xml_node_pop(p);
} else {
xml_unexpected_end_tag(p, name, "object");
}
}
static void XMLCALL
tmx_unused_start(void *pv, const char *name, const char **attr)
{
XML_Parser p = (XML_Parser) pv;
#ifdef DEBUG_TMX
debug("<%s> (unused)", name);
#endif
/* Shut up, GCC. */
for (; *name != '\0'; ++name);
for (; *attr != NULL; ++attr);
xml_node_push(p, NULL, tmx_unused_start, tmx_unused_end, NULL);
}
static void XMLCALL
tmx_unused_end(void *pv, const char *name)
{
XML_Parser p = (XML_Parser) pv;
#ifdef DEBUG_TMX
debug("%s> (unused)", name);
#endif
/* Shut up, GCC. */
for (; *name != '\0'; ++name);
xml_node_pop(p);
}
static void XMLCALL
tmx_invalid_start(void *pv, const char *name, const char **attr)
{
XML_Parser p = (XML_Parser) pv;
#ifdef DEBUG_TMX
debug("<%s> (invalid)", name);
#endif
/* Shut up, GCC. */
for (; *attr != NULL; ++attr);
xml_unexpected_start_tag(p, name, "");
}
static void XMLCALL
tmx_invalid_end(void *pv, const char *name)
{
XML_Parser p = (XML_Parser) pv;
#ifdef DEBUG_TMX
debug("<%s> (invalid)", name);
#endif
xml_unexpected_end_tag(p, name, "");
}