summaryrefslogtreecommitdiffstats
path: root/src/resources/map.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/resources/map.c')
-rw-r--r--src/resources/map.c423
1 files changed, 423 insertions, 0 deletions
diff --git a/src/resources/map.c b/src/resources/map.c
new file mode 100644
index 0000000..b703629
--- /dev/null
+++ b/src/resources/map.c
@@ -0,0 +1,423 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <expat.h>
+#include "map.h"
+#include "resource.h"
+#include "tileset.h"
+#include "../xml.h"
+#include "../logging.h"
+
+struct tmx {
+ XML_Parser p;
+ struct map *map;
+ char *layer_data;
+};
+
+struct resource_table map_res;
+
+static void XMLCALL xml_map_start(void *data, const char *el,
+ const char **attr);
+static void XMLCALL xml_map_end(void *data, const char *el);
+static void XMLCALL xml_mapelem_start(void *data, const char *el,
+ const char **attr);
+static void XMLCALL xml_mapelem_end(void *data, const char *el);
+static void XMLCALL xml_image_start(void *data, const char *el,
+ const char **attr);
+static void XMLCALL xml_image_end(void *data, const char *el);
+static void XMLCALL xml_data_start(void *data, const char *el,
+ const char **attr);
+static void XMLCALL xml_data_data(void *data, const char *s, int len);
+static void XMLCALL xml_data_end(void *data, const char *el);
+static void XMLCALL xml_object_start(void *data, const char *el,
+ const char **attr);
+static void XMLCALL xml_object_end(void *data, const char *el);
+
+void map_add_layer(struct map *m, struct layer *l);
+void map_add_tileset(struct map *m, struct tileset *t);
+
+static void XMLCALL
+xml_map_start(void *data, const char *el, const char **attr)
+{
+ struct tmx *tmx;
+
+ debug("<%s> (type \"map\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "map")) {
+ if (tmx->map != NULL) {
+ warn("Found multiple maps in TMX file");
+ XML_StopParser(tmx->p, XML_FALSE);
+ }
+ tmx->map = malloc(sizeof(*tmx->map));
+ if (tmx->map == NULL) {
+ warn("Failed to allocate map");
+ XML_StopParser(tmx->p, XML_FALSE);
+ }
+ memset(tmx->map, 0, sizeof(*tmx->map));
+ xml_get_int_attr(tmx->p, attr, "width",
+ &tmx->map->width, 1);
+ xml_get_int_attr(tmx->p, attr, "width",
+ &tmx->map->width, 1);
+ xml_get_int_attr(tmx->p, attr, "tilewidth",
+ &tmx->map->tilewidth, 1);
+ xml_get_int_attr(tmx->p, attr, "tileheight",
+ &tmx->map->tileheight, 1);
+ XML_SetStartElementHandler(tmx->p, xml_mapelem_start);
+ XML_SetEndElementHandler(tmx->p, xml_map_end);
+ } else {
+ xml_unexpected_start_tag(tmx->p, el,
+ "map");
+ }
+}
+
+static void XMLCALL
+xml_map_end(void *data, const char *el)
+{
+ struct tmx *tmx;
+
+ debug("</%s> (type \"map\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "map")) {
+ XML_SetStartElementHandler(tmx->p, NULL);
+ XML_SetEndElementHandler(tmx->p, NULL);
+ } else {
+ xml_unexpected_end_tag(tmx->p, el,
+ "map");
+ }
+}
+
+static void XMLCALL
+xml_mapelem_start(void *data, const char *el, const char **attr)
+{
+ struct tmx *tmx;
+ char *source;
+ struct tileset *ts;
+ struct layer *ly;
+
+ debug("<%s> (type \"mapelem\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "tileset")) {
+ source = NULL;
+ xml_get_string_attr(tmx->p, attr, "source",
+ &source, 0);
+ if (source != NULL) {
+ ts = tileset_get(source);
+ free(source);
+ XML_SetStartElementHandler(tmx->p, NULL);
+ XML_SetEndElementHandler(tmx->p, xml_tileset_end);
+ } else {
+ ts = resource_alloc("", sizeof(*ts));
+ xml_get_int_attr(tmx->p, attr, "tilewidth",
+ &ts->tilewidth, 1);
+ xml_get_int_attr(tmx->p, attr, "tileheight",
+ &ts->tileheight, 1);
+ /* TODO: Change xml_image_start to xml_tselem_start.
+ * A <tileset> can contain:
+ * * Zero or one <properties>,
+ * * Zero or one <image>s, and
+ * * Zero or more <tile>s. */
+ XML_SetStartElementHandler(tmx->p, xml_image_start);
+ XML_SetEndElementHandler(tmx->p, xml_tileset_end);
+ }
+ xml_get_int_attr(tmx->p, attr, "firstgid",
+ &ts->firstgid, 1);
+ xml_get_string_attr(tmx->p, attr, "name",
+ &ts->name, 1);
+ map_add_tileset(tmx->map, ts);
+ } else if (xml_check_tag(el, "layer")) {
+ ly = resource_alloc("", sizeof(*ly));
+ xml_get_string_attr(tmx->p, attr, "name",
+ &ly->name, 1);
+ map_add_layer(tmx->map, ly);
+ XML_SetStartElementHandler(tmx->p, xml_data_start);
+ XML_SetEndElementHandler(tmx->p, xml_mapelem_end);
+ } else if (xml_check_tag(el, "objectgroup")) {
+ XML_SetStartElementHandler(tmx->p, xml_object_start);
+ XML_SetEndElementHandler(tmx->p, xml_mapelem_end);
+ } else {
+ xml_unexpected_start_tag(tmx->p, el,
+ "tileset, layer, or objectgroup");
+ }
+}
+
+static void XMLCALL
+xml_mapelem_end(void *data, const char *el)
+{
+ struct tmx *tmx;
+
+ debug("</%s> (type \"mapelem\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "tileset")) {
+ XML_SetStartElementHandler(tmx->p, xml_mapelem_start);
+ XML_SetEndElementHandler(tmx->p, xml_map_end);
+ } else if (xml_check_tag(el, "layer")) {
+ XML_SetStartElementHandler(tmx->p, xml_mapelem_start);
+ XML_SetEndElementHandler(tmx->p, xml_map_end);
+ } else if (xml_check_tag(el, "objectgroup")) {
+ XML_SetStartElementHandler(tmx->p, xml_mapelem_start);
+ XML_SetEndElementHandler(tmx->p, xml_map_end);
+ } else {
+ xml_unexpected_end_tag(tmx->p, el,
+ "tileset, layer, or objectgroup");
+ }
+}
+
+static void XMLCALL
+xml_image_start(void *data, const char *el, const char **attr)
+{
+ struct tmx *tmx;
+ struct tileset *ts;
+ char *source;
+
+ debug("<%s> (type \"image\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "image")) {
+ ts = tmx->map->tilesets_tail;
+ xml_get_string_attr(tmx->p, attr, "source",
+ &source, 1);
+ ts->image = img_png_get(source);
+ free(source);
+ XML_SetStartElementHandler(tmx->p, NULL);
+ XML_SetEndElementHandler(tmx->p, xml_image_end);
+ } else {
+ xml_unexpected_end_tag(tmx->p, el,
+ "image");
+ }
+}
+
+static void XMLCALL
+xml_image_end(void *data, const char *el)
+{
+ struct tmx *tmx;
+
+ tmx = (struct tmx *) data;
+
+ debug("</%s> (type \"image\")", el);
+
+ if (xml_check_tag(el, "image")) {
+ XML_SetStartElementHandler(tmx->p, NULL);
+ XML_SetEndElementHandler(tmx->p, xml_mapelem_end);
+ } else {
+ xml_unexpected_end_tag(tmx->p, el,
+ "image");
+ }
+}
+
+static void XMLCALL
+xml_data_start(void *data, const char *el, const char **attr)
+{
+ struct tmx *tmx;
+ struct layer *ly;
+
+ debug("<%s> (type \"data\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "data")) {
+ ly = tmx->map->layers_tail;
+ xml_get_string_attr(tmx->p, attr, "encoding",
+ &ly->encoding, 0);
+ xml_get_string_attr(tmx->p, attr, "compression",
+ &ly->compression, 0);
+ XML_SetStartElementHandler(tmx->p, NULL);
+ XML_SetEndElementHandler(tmx->p, xml_data_end);
+ XML_SetCharacterDataHandler(tmx->p, xml_data_data);
+ } else {
+ xml_unexpected_start_tag(tmx->p, el,
+ "data");
+ }
+}
+
+static void XMLCALL
+xml_data_data(void *data, const char *s, int len)
+{
+ struct tmx *tmx;
+ char *s_z;
+ char *s_z_trimmed, *s_z_trimmed_end;
+
+ debug("...LAYER DATA...");
+
+ tmx = (struct tmx *) data;
+
+ 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';
+
+ tmx->layer_data = strdup(s_z_trimmed);
+
+ free(s_z);
+}
+
+static void XMLCALL
+xml_data_end(void *data, const char *el)
+{
+ struct tmx *tmx;
+
+ debug("</%s> (type \"data\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "data")) {
+ XML_SetStartElementHandler(tmx->p, NULL);
+ XML_SetEndElementHandler(tmx->p, xml_mapelem_end);
+ XML_SetCharacterDataHandler(tmx->p, NULL);
+ } else {
+ xml_unexpected_end_tag(tmx->p, el,
+ "data");
+ }
+}
+
+static void XMLCALL
+xml_object_start(void *data, const char *el,
+ const char **attr)
+{
+ struct tmx *tmx;
+ char *type;
+
+ debug("<%s> (type \"object\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "object")) {
+ /* TODO: Handle objects. */
+ xml_get_string_attr(tmx->p, attr, "type",
+ &type, 1);
+ if (strcmp(type, "exit") == 0) {
+ } else if (strcmp(type, "exit") == 0) {
+ }
+ XML_SetStartElementHandler(tmx->p, NULL);
+ XML_SetEndElementHandler(tmx->p, xml_object_end);
+ } else {
+ xml_unexpected_start_tag(tmx->p, el,
+ "object");
+ }
+}
+
+static void XMLCALL
+xml_object_end(void *data, const char *el)
+{
+ struct tmx *tmx;
+
+ debug("</%s> (type \"object\")", el);
+
+ tmx = (struct tmx *) data;
+
+ if (xml_check_tag(el, "object")) {
+ XML_SetStartElementHandler(tmx->p, xml_object_start);
+ XML_SetEndElementHandler(tmx->p, xml_mapelem_end);
+ } else {
+ xml_unexpected_end_tag(tmx->p, el,
+ "object");
+ }
+}
+
+struct map *
+map_get(const char *path)
+{
+ struct tmx tmx;
+ FILE *tmx_fp;
+ void *tmx_buf;
+ size_t len;
+ enum XML_Status status;
+
+ memset(&tmx, 0, sizeof(tmx));
+
+ tmx.map = (struct map *) resource_get(&map_res, path);
+ if (tmx.map != NULL) {
+ resource_use((struct resource *) tmx.map);
+ return tmx.map;
+ }
+
+ tmx.map = resource_alloc(path, sizeof(*tmx.map));
+
+ tmx.p = XML_ParserCreate(NULL);
+ if (tmx.p == NULL) {
+ warn("Failed to create TMX parser");
+ return NULL;
+ }
+
+ XML_SetStartElementHandler(tmx.p, xml_map_start);
+ XML_SetEndElementHandler(tmx.p, NULL);
+ XML_SetCharacterDataHandler(tmx.p, NULL);
+
+ XML_SetUserData(tmx.p, &tmx);
+
+ tmx_fp = fopen(path, "rb");
+ if (tmx_fp == NULL) {
+ warn("Failed to open TMX file");
+ XML_ParserFree(tmx.p);
+ return NULL;
+ }
+
+ tmx_buf = XML_GetBuffer(tmx.p, 8192);
+ if (tmx_buf == NULL) {
+ warn("Failed to create TMX parse buffer");
+ XML_ParserFree(tmx.p);
+ fclose(tmx_fp);
+ return NULL;
+ }
+
+ while (!feof(tmx_fp)) {
+ len = fread(tmx_buf, 1, 8192, tmx_fp);
+ status = XML_ParseBuffer(tmx.p, len, feof(tmx_fp));
+ if (status == XML_STATUS_OK) {
+ continue;
+ }
+ warn("Failed to parse TMX file (%s)",
+ XML_ErrorString(XML_GetErrorCode(tmx.p)));
+ XML_ParserFree(tmx.p);
+ fclose(tmx_fp);
+ return NULL;
+ }
+
+ XML_ParserFree(tmx.p);
+ fclose(tmx_fp);
+
+ resource_use((struct resource *) tmx.map);
+ resource_add(&map_res, path, (struct resource *) tmx.map);
+
+ return tmx.map;
+}
+
+void
+map_add_layer(struct map *m, struct layer *l)
+{
+ if (m->layers_head == NULL) {
+ m->layers_head = l;
+ } else {
+ m->layers_tail->next = l;
+ }
+ m->layers_tail = l;
+}
+
+void
+map_add_tileset(struct map *m, struct tileset *t)
+{
+ if (m->tilesets_head == NULL) {
+ m->tilesets_head = t;
+ } else {
+ m->tilesets_tail->next = t;
+ }
+ m->tilesets_tail = t;
+}