/* * 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 "dirs.h" #include "output.h" #include "tileset.h" #include "xml.h" struct db_tileset { char *game_id; SDL_Surface *image; SDL_Texture *texture; int tw; int th; int tc; int cols; Uint32 b_col; Uint32 p_col; int cur_tile; int firstgid; struct db_tileset *next; }; static char * _db_tileset_path(const char *game_id, const char *file) { const char *games_dir; char *path; games_dir = db_get_games_dir(); path = malloc((strlen(games_dir) + strlen(game_id) + strlen(file) + 3) * sizeof(*path)); if (path == NULL) { db_err("Failed to allocate memory"); return NULL; } sprintf(path, "%s/%s/%s", games_dir, game_id, file); return path; } static void XMLCALL _db_tsx_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_tsx_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_tsx_invalid_start, _db_tsx_invalid_end, NULL); } static void XMLCALL _db_tsx_image_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (image)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "image")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "image"); } } static void XMLCALL _db_tsx_tile_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (tile)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "tile")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "tile"); } } static void XMLCALL _db_tsx_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_tsx_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_tsx_property_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_tileset *tileset; char *p_name; char *p_type; SDL_bool col; db_dbg(" <%s> (property)", name); p = (XML_Parser) pv; tileset = 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, "ballcollides") == 0) { if (strcmp(p_type, "bool") != 0) { db_err("Ball collision must be a Boolean " "value"); free(p_name); free(p_type); XML_StopParser(p, XML_FALSE); return; } col = SDL_FALSE; /* Shut up, GCC. */ db_xml_get_bool_attr(p, attr, "value", &col, 1); if (col) { tileset->b_col |= (1 << tileset->cur_tile); } db_dbg(" Ball collision: %s", (col ? "true" : "false")); } else if (strcmp(p_name, "playercollides") == 0) { if (strcmp(p_type, "bool") != 0) { db_err("Player collision must be a Boolean " "value"); free(p_name); free(p_type); XML_StopParser(p, XML_FALSE); return; } col = SDL_FALSE; /* Shut up, GCC. */ db_xml_get_bool_attr(p, attr, "value", &col, 1); if (col) { tileset->p_col |= (1 << tileset->cur_tile); } db_dbg(" Player collision: %s", (col ? "true" : "false")); } else { db_dbg(" Skipping unknown property \"%s\"", p_name); } free(p_name); free(p_type); db_xml_node_push(p, tileset, _db_tsx_invalid_start, _db_tsx_property_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "property"); } } static void XMLCALL _db_tsx_properties_start(void *pv, const char *name, const char **attr __attribute__((__unused__))) { XML_Parser p; struct db_tileset *tileset; db_dbg(" <%s> (properties)", name); p = (XML_Parser) pv; tileset = db_xml_node_peek(p); if (db_xml_check_tag(name, "properties")) { db_xml_node_push(p, tileset, _db_tsx_property_start, _db_tsx_properties_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "properties"); } } static void XMLCALL _db_tsx_tileset_el_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_tileset *tileset; char *source; char *path; db_dbg(" <%s> (tileset child)", name); p = (XML_Parser) pv; tileset = db_xml_node_peek(p); if (db_xml_check_tag(name, "image")) { db_xml_get_string_attr(p, attr, "source", &source, 1); db_dbg(" Image: <%s>", source); path = _db_tileset_path(tileset->game_id, source); free(source); if (path == NULL) { XML_StopParser(p, XML_FALSE); return; } tileset->image = IMG_Load(path); free(path); if (tileset->image == NULL) { XML_StopParser(p, XML_FALSE); return; } db_xml_node_push(p, tileset, _db_tsx_invalid_start, _db_tsx_image_end, NULL); } else if (db_xml_check_tag(name, "tile")) { db_xml_get_int_attr(p, attr, "id", &tileset->cur_tile, 1); db_dbg(" Tile size: %dx%d px", tileset->tw, tileset->th); db_xml_node_push(p, tileset, _db_tsx_properties_start, _db_tsx_tile_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "image or tile"); } } static void XMLCALL _db_tsx_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_tsx_tileset_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_tileset *tileset; int tw; int th; db_dbg(" <%s> (tileset)", name); p = (XML_Parser) pv; tileset = db_xml_node_peek(p); if (db_xml_check_tag(name, "tileset")) { db_xml_get_int_attr(p, attr, "tilewidth", &tw, 1); db_xml_get_int_attr(p, attr, "tileheight", &th, 1); if (tw != tileset->tw || th != tileset->th) { db_err("Tileset has %dx%d tiles, not %dx%d like map", tw, th, tileset->tw, tileset->th); XML_StopParser(p, XML_FALSE); return; } db_xml_get_int_attr(p, attr, "tilecount", &tileset->tc, 1); if (tileset->tc > 32) { db_err("Tileset may not have more than 32 tiles"); XML_StopParser(p, XML_FALSE); return; } db_xml_get_int_attr(p, attr, "columns", &tileset->cols, 1); db_dbg(" Tile size: %dx%d px", tileset->tw, tileset->th); db_xml_node_push(p, tileset, _db_tsx_tileset_el_start, _db_tsx_tileset_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "tileset"); } } struct db_tileset * db_tileset_new(const char *game_id, const char *file, int firstgid, int tilewidth, int tileheight, struct db_tileset *prev) { struct db_tileset *tileset; XML_Parser p; char *path; FILE *fp; void *buf; size_t len; enum XML_Status status; tileset = calloc(1, sizeof(*tileset)); if (tileset == NULL) { db_err("Failed to allocate memory"); return NULL; } tileset->firstgid = firstgid; tileset->tw = tilewidth; tileset->th = tileheight; if (prev != NULL) { prev->next = tileset; } tileset->game_id = strdup(game_id); if (tileset->game_id == NULL) { db_err("Failed to allocate memory"); free(tileset); return NULL; } p = XML_ParserCreate(NULL); if (p == NULL) { free(tileset->game_id); free(tileset); return NULL; } XML_UseParserAsHandlerArg(p); db_xml_node_push(p, tileset, _db_tsx_tileset_start, _db_tsx_invalid_end, NULL); path = _db_tileset_path(game_id, file); if (path == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); free(tileset->game_id); free(tileset); return NULL; } fp = fopen(path, "rb"); if (fp == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); free(tileset->game_id); free(tileset); return NULL; } buf = XML_GetBuffer(p, 8192); if (buf == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); fclose(fp); free(tileset->game_id); free(tileset); 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 tileset information (%s)", XML_ErrorString(XML_GetErrorCode(p))); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); free(tileset->game_id); free(tileset); return NULL; } db_dbg(" Parsing done"); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); return tileset; } SDL_bool db_tile_player_collides(struct db_tileset *tileset, int gid) { for (; tileset != NULL; tileset = tileset->next) { if (gid >= tileset->firstgid && gid < tileset->firstgid + tileset->tc) { gid -= tileset->firstgid; return tileset->p_col & 1 << gid; } } return SDL_FALSE; } SDL_bool db_tile_ball_collides(struct db_tileset *tileset, int gid) { for (; tileset != NULL; tileset = tileset->next) { if (gid >= tileset->firstgid && gid < tileset->firstgid + tileset->tc) { gid -= tileset->firstgid; return tileset->b_col & 1 << gid; } } return SDL_FALSE; } static SDL_Texture * _db_tileset_texture(struct db_tileset *tileset, SDL_Renderer *renderer) { if (tileset->texture == NULL) { tileset->texture = SDL_CreateTextureFromSurface(renderer, tileset->image); } return tileset->texture; } int db_tile_render(struct db_tileset *tileset, SDL_Renderer *renderer, int gid, SDL_Rect *dstrect) { SDL_Rect srcrect; for (; tileset != NULL; tileset = tileset->next) { if (gid >= tileset->firstgid && gid < tileset->firstgid + tileset->tc) { gid -= tileset->firstgid; srcrect.x = tileset->tw * (gid % tileset->cols); srcrect.y = tileset->th * (gid / tileset->cols); srcrect.w = tileset->tw; srcrect.h = tileset->th; db_dbg("Blitting %dx%d tile at (%d,%d) onto %dx%d area " "at (%d,%d)...", srcrect.w, srcrect.h, srcrect.x, srcrect.y, dstrect->w, dstrect->h, dstrect->x, dstrect->y); if (SDL_RenderCopy(renderer, _db_tileset_texture( tileset, renderer), &srcrect, dstrect) != 0) { db_err("Failed to copy texture (%s)", SDL_GetError()); return -1; } return 1; } } db_warn("Tile with gid 0x%8.8x not found", (unsigned int) gid); return 0; }