/*
* 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(" %s> (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(" %s> (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(" %s> (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(" %s> (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(" %s> (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(" %s> (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);
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;
}
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_bool tile_found;
SDL_Rect srcrect;
tile_found = SDL_FALSE;
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;
}
tile_found = SDL_TRUE;
break;
}
}
if (!tile_found) {
db_warn("Tile with gid 0x%8.8x not found", (unsigned int) gid);
return 0;
}
return 1;
}