/* * Copyright (C) 2013, 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 #include #include "ball.h" #include "base64.h" #include "compression.h" #include "defs.h" #include "dirs.h" #include "map.h" #include "tileset.h" #include "output.h" #include "xml.h" struct db_map_layer { Uint32 *tiles; char *encoding; char *compression; char *raw_data; struct db_map_layer *next; }; struct db_map { char *game_id; int w; int h; int tw; int th; Uint8 bg_r; Uint8 bg_g; Uint8 bg_b; int fr; struct db_tileset *tileset_head; struct db_tileset *tileset_tail; struct db_map_layer *layer_head; struct db_map_layer *layer_tail; int obj_gid; char *obj_type; int obj_x; int obj_y; int obj_r; int obj_a; int obj_d; int obj_sr; double obj_s; int player_x; int player_y; int player_r; int player_gid; int target_x; int target_y; int target_r; int target_gid; struct db_ball *ball_head; struct db_ball *ball_tail; Uint8 *p_col; Uint8 *b_col; }; static char * _db_map_path(const char *game_id, const char *file, const char *ext) { const char *games_dir; char *path; games_dir = db_get_games_dir(); path = malloc((strlen(games_dir) + strlen(game_id) + strlen(file) + strlen(ext) + 3) * sizeof(*path)); if (path == NULL) { db_err("Failed to allocate memory"); return NULL; } sprintf(path, "%s/%s/%s%s", games_dir, game_id, file, ext); return path; } static void XMLCALL _db_tmx_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_tmx_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_tmx_invalid_start, _db_tmx_invalid_end, NULL); } static void XMLCALL _db_tmx_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_tmx_property_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; char *p_name; char *p_type; db_dbg(" <%s> (property)", name); p = (XML_Parser) pv; map = 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, "framerate") == 0) { if (strcmp(p_type, "int") != 0) { db_err("Framerate must be an integer"); free(p_name); free(p_type); XML_StopParser(p, XML_FALSE); return; } db_xml_get_int_attr(p, attr, "value", &map->fr, 1); db_dbg(" Framerate: %d", map->fr); } else { db_dbg("Skipping unknown property \"%s\"", p_name); } free(p_name); free(p_type); db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_property_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "property"); } } static void XMLCALL _db_tmx_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_tmx_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_tmx_cdata(void *pv, const char *s, int len) { XML_Parser p; struct db_map *map; char *s_z; char *s_z_trimmed; char *s_z_trimmed_end; char *old_data; p = (XML_Parser) pv; map = db_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') { db_dbg(" (null)"); free(s_z); 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'; db_dbg(" %s", s_z_trimmed); if (map->layer_tail->raw_data != NULL && *map->layer_tail->raw_data != '\0') { old_data = map->layer_tail->raw_data; sprintf(map->layer_tail->raw_data, "%s %s", map->layer_tail->raw_data, strdup(s_z_trimmed)); free(old_data); } else { map->layer_tail->raw_data = strdup(s_z_trimmed); } free(s_z); } static void XMLCALL _db_tmx_data_end(void *pv, const char *name) { XML_Parser p; struct db_map *map; size_t decoded_len; size_t decomp_len; char *decoded_buf; char *decomp_buf; size_t i; db_dbg(" (data)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "data")) { decoded_len = strlen(map->layer_tail->raw_data); decomp_len = 4 * map->w* map->h; db_dbg(" Expected map data size: %zu", decomp_len); decoded_buf = malloc(decoded_len + 1); if (decoded_buf == NULL) { db_warn(" Failed to allocate layer data buffer"); XML_StopParser(p, XML_FALSE); return; } if (strcmp(map->layer_tail->encoding, "base64") == 0) { db_dbg(" Decoding base 64 layer data..."); db_base64_decode(map->layer_tail->raw_data, decoded_buf, decoded_len + 1); } if (map->layer_tail->compression == NULL) { db_dbg(" Layer data already decompressed"); decomp_buf = decoded_buf; } else if (strcmp(map->layer_tail->compression, "zlib") == 0) { db_dbg(" Decompressing layer data with zlib..."); decomp_buf = malloc(decomp_len); if (decomp_buf == NULL) { db_err("Failed to allocate layer data buffer"); free(decoded_buf); XML_StopParser(p, XML_FALSE); return; } db_decompress(decoded_buf, decoded_len, decomp_buf, decomp_len); free(decoded_buf); } else if (strcmp(map->layer_tail->compression, "gzip") == 0) { db_dbg(" Decompressing layer data with gzip..."); decomp_buf = malloc(decomp_len); if (decomp_buf == NULL) { db_err("Failed to allocate layer data buffer"); free(decoded_buf); XML_StopParser(p, XML_FALSE); return; } db_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(map->layer_tail->raw_data); map->layer_tail->tiles = malloc(decomp_len); for (i = 0; i < decomp_len / 4; ++i) { /* Convert each tile GID to the system's byte order. */ map->layer_tail->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); db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "data"); } } static void XMLCALL _db_tmx_data_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; db_dbg(" <%s> (data)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "data")) { map->layer_tail = calloc(1, sizeof(*map->layer_tail)); if (map->layer_tail == NULL) { XML_StopParser(p, XML_FALSE); return; } if (map->layer_head == NULL) { map->layer_head = map->layer_tail; } db_xml_get_string_attr(p, attr, "encoding", &map->layer_tail->encoding, 1); db_xml_get_string_attr(p, attr, "compression", &map->layer_tail->compression, 0); db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_data_end, _db_tmx_cdata); } else { db_xml_unexpected_start_tag(p, name, "data"); } } static void XMLCALL _db_tmx_layer_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (layer)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "layer")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "layer"); } } static void XMLCALL _db_tmx_object_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_tmx_object_property_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; char *p_name; char *p_type; char *dir; db_dbg(" <%s> (property)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "property")) { db_xml_get_string_attr(p, attr, "name", &p_name, 1); if (strcmp(p_name, "type") == 0) { db_xml_get_string_attr(p, attr, "value", &map->obj_type, 1); db_dbg(" Object type: %s", map->obj_type); } else if (strcmp(p_name, "speed") == 0) { db_xml_get_string_attr(p, attr, "type", &p_type, 1); if (strcmp(p_type, "int") == 0) { db_xml_get_int_attr(p, attr, "value", (int *) &map->obj_s, 1); } else if (strcmp(p_type, "float") == 0) { db_xml_get_float_attr(p, attr, "value", (float *) &map->obj_s, 1); } else { db_err("Object speed must be an integer or " "float"); free(p_name); free(p_type); XML_StopParser(p, XML_FALSE); return; } free(p_type); db_dbg(" Object speed: %f", map->obj_s); } else if (strcmp(p_name, "direction") == 0) { db_xml_get_string_attr(p, attr, "value", &dir, 1); if (strcmp(dir, "lr") == 0) { map->obj_a = (rand() / (RAND_MAX / 2)) * 180.0; db_dbg(" Object direction: " "left and right"); } else if (strcmp(dir, "ud") == 0) { map->obj_a = (rand() / (RAND_MAX / 2)) * 180.0 + 90.0; db_dbg(" Object direction: " "up and down"); } else if (strcmp(dir, "random") == 0) { map->obj_a = rand() / (RAND_MAX / 360); db_dbg(" Object direction: " "random"); } else if (strcmp(dir, "cw") == 0) { map->obj_a = rand() / (RAND_MAX / 360); map->obj_d = -1; db_dbg(" Object direction: " "clockwise"); } else if (strcmp(dir, "ccw") == 0) { map->obj_a = rand() / (RAND_MAX / 360); map->obj_d = 1; db_dbg(" Object direction: " "counter-clockwise"); } } else if (strcmp(p_name, "radius") == 0) { db_xml_get_string_attr(p, attr, "type", &p_type, 1); if (strcmp(p_type, "int") != 0) { db_err("Object spin radius must be an integer"); free(p_name); free(p_type); XML_StopParser(p, XML_FALSE); return; } free(p_type); db_xml_get_int_attr(p, attr, "value", &map->obj_sr, 1); db_dbg(" Object spin radius: %d", map->obj_sr); } else { db_dbg("Skipping unknown property \"%s\"", p_name); } free(p_name); db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_object_property_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "property"); } } static void XMLCALL _db_tmx_object_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_tmx_object_properties_start(void *pv, const char *name, const char **attr __attribute__((__unused__))) { XML_Parser p; struct db_map *map; db_dbg(" <%s> (properties)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "properties")) { db_xml_node_push(p, map, _db_tmx_object_property_start, _db_tmx_object_properties_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "properties"); } } static void XMLCALL _db_tmx_object_end(void *pv, const char *name) { XML_Parser p; struct db_map *map; db_dbg(" (object)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "object")) { if (strcmp(map->obj_type, "player") == 0) { map->player_x = map->obj_x; map->player_y = map->obj_y; map->player_r = map->obj_r; map->player_gid = map->obj_gid; } else if (strcmp(map->obj_type, "target") == 0) { map->target_x = map->obj_x; map->target_y = map->obj_y; map->target_r = map->obj_r; map->target_gid = map->obj_gid; } else if (strcmp(map->obj_type, "ball") == 0) { map->ball_tail = db_ball_new(map->obj_x, map->obj_y, map->obj_r, map->obj_a, map->obj_d, map->obj_sr, map->obj_s, map->tileset_head, map->obj_gid, map->ball_tail); if (map->ball_tail == NULL) { XML_StopParser(p, XML_FALSE); free(map->obj_type); return; } if (map->ball_head == NULL) { map->ball_head = map->ball_tail; } } free(map->obj_type); db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "object"); } } static void XMLCALL _db_tmx_object_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; int x; int y; int w; int h; db_dbg(" <%s> (object)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "object")) { db_xml_get_int_attr(p, attr, "gid", &map->obj_gid, 1); db_xml_get_int_attr(p, attr, "x", &x, 1); db_xml_get_int_attr(p, attr, "y", &y, 1); db_xml_get_int_attr(p, attr, "width", &w, 1); db_xml_get_int_attr(p, attr, "height", &h, 1); if (w != h) { db_err("Objects must be circular"); XML_StopParser(p, XML_FALSE); return; } map->obj_x = x + w / 2; map->obj_y = y - h / 2; /* Object origin is BOTTOM left */ map->obj_r = w / 2; map->obj_a = 0; map->obj_d = 0; map->obj_sr = 0; map->obj_s = 0.0; db_xml_node_push(p, map, _db_tmx_object_properties_start, _db_tmx_object_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "object"); } } static void XMLCALL _db_tmx_objectgroup_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (objectgroup)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "objectgroup")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "objectgroup"); } } static void XMLCALL _db_tmx_map_el_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; int firstgid; char *source; db_dbg(" <%s> (map child)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "properties")) { db_xml_node_push(p, map, _db_tmx_property_start, _db_tmx_properties_end, NULL); } else if (db_xml_check_tag(name, "tileset")) { db_xml_get_int_attr(p, attr, "firstgid", &firstgid, 1); db_xml_get_string_attr(p, attr, "source", &source, 1); db_dbg(" Tileset: <%s>", source); map->tileset_tail = db_tileset_new(map->game_id, source, firstgid, map->tw, map->th, map->tileset_tail); if (map->tileset_head == NULL) { map->tileset_head = map->tileset_tail; } free(source); if (map->tileset_tail == NULL) { XML_StopParser(p, XML_FALSE); return; } db_xml_node_push(p, map, _db_tmx_invalid_start, _db_tmx_tileset_end, NULL); } else if (db_xml_check_tag(name, "layer")) { db_xml_node_push(p, map, _db_tmx_data_start, _db_tmx_layer_end, NULL); } else if (db_xml_check_tag(name, "objectgroup")) { db_xml_node_push(p, map, _db_tmx_object_start, _db_tmx_objectgroup_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "properties, tileset, layer, or objectgroup"); } } static void XMLCALL _db_tmx_map_end(void *pv, const char *name) { XML_Parser p; db_dbg(" (map)", name); p = (XML_Parser) pv; if (db_xml_check_tag(name, "map")) { db_xml_node_pop(p); } else { db_xml_unexpected_end_tag(p, name, "map"); } } static void XMLCALL _db_tmx_map_start(void *pv, const char *name, const char **attr) { XML_Parser p; struct db_map *map; char *orientation; char *renderorder; char *backgroundcolor; unsigned int bg_r; unsigned int bg_g; unsigned int bg_b; db_dbg(" <%s> (map)", name); p = (XML_Parser) pv; map = db_xml_node_peek(p); if (db_xml_check_tag(name, "map")) { /* Check orientation */ db_xml_get_string_attr(p, attr, "orientation", &orientation, 1); if (strcmp(orientation, "orthogonal") != 0) { db_err("Map must be orthogonal"); XML_StopParser(p, XML_FALSE); return; } free(orientation); /* Check render order */ db_xml_get_string_attr(p, attr, "renderorder", &renderorder, 1); if (strcmp(renderorder, "right-down") != 0) { db_err("Map must be rendered right-down"); XML_StopParser(p, XML_FALSE); return; } free(renderorder); /* Get and check size */ db_xml_get_int_attr(p, attr, "width", &map->w, 1); db_xml_get_int_attr(p, attr, "height", &map->h, 1); db_xml_get_int_attr(p, attr, "tilewidth", &map->tw, 1); db_xml_get_int_attr(p, attr, "tileheight", &map->th, 1); if (map->w * map->tw != DB_WINDOW_W || map->h * map->th != DB_WINDOW_H) { db_err("Map size must be %dx%d px", DB_WINDOW_W, DB_WINDOW_H); XML_StopParser(p, XML_FALSE); return; } db_dbg(" Map size: %dx%d px", map->w, map->h); db_dbg(" Tile size: %dx%d px", map->tw, map->th); /* Get and check background color */ db_xml_get_string_attr(p, attr, "backgroundcolor", &backgroundcolor, 1); if (sscanf(backgroundcolor, "#%02x%02x%02x", &bg_r, &bg_g, &bg_b) != 3) { db_err("Background color must be \"#xxxxxx\""); XML_StopParser(p, XML_FALSE); return; } map->bg_r = bg_r; map->bg_g = bg_g; map->bg_b = bg_b; db_dbg(" Background color: (0x%02x, 0x%02x, 0x%02x)", map->bg_r, map->bg_g, map->bg_b); db_xml_node_push(p, map, _db_tmx_map_el_start, _db_tmx_map_end, NULL); } else { db_xml_unexpected_start_tag(p, name, "map"); } } static void _db_map_set_collision_tiles(struct db_map *map) { struct db_map_layer *layer; int i; int gid; db_dbg("Allocating %d bit field elements", (map->w * map->h + 7) / 8); map->p_col = calloc((map->w * map->h + 7) / 8, sizeof(*map->p_col)); if (map->p_col == NULL) { db_err("Failed to allocate memory"); return; } map->b_col = calloc((map->w * map->h + 7) / 8, sizeof(*map->p_col)); if (map->b_col == NULL) { db_err("Failed to allocate memory"); free(map->p_col); return; } for (layer = map->layer_head; layer != NULL; layer = layer->next) { for (i = 0; i < map->w * map->h; ++i) { gid = layer->tiles[i]; if (gid == 0) { continue; } if (db_tile_player_collides(map->tileset_head, gid)) { db_dbg("Tile %d collides: setting bit %d of " "field element %d", i, i % 8, i / 8); map->p_col[i / 8] |= 1 << (i % 8); } if (db_tile_ball_collides(map->tileset_head, gid)) { map->b_col[i / 8] |= 1 << (i % 8); } } } } struct db_map * db_map_new(const char *game_id, const char *level_id) { struct db_map *map; XML_Parser p; char *path; FILE *fp; void *buf; size_t len; enum XML_Status status; map = calloc(1, sizeof(*map)); if (map == NULL) { db_err("Failed to allocate memory"); return NULL; } map->game_id = strdup(game_id); if (map->game_id == NULL) { db_err("Failed to allocate memory"); free(map); return NULL; } p = XML_ParserCreate(NULL); if (p == NULL) { free(map->game_id); free(map); return NULL; } XML_UseParserAsHandlerArg(p); db_xml_node_push(p, map, _db_tmx_map_start, _db_tmx_invalid_end, NULL); path = _db_map_path(game_id, level_id, ".tmx"); if (path == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); free(map->game_id); free(map); return NULL; } fp = fopen(path, "rb"); if (fp == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); free(map->game_id); free(map); return NULL; } buf = XML_GetBuffer(p, 8192); if (buf == NULL) { db_xml_node_pop(p); XML_ParserFree(p); free(path); fclose(fp); free(map->game_id); free(map); 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 map information (%s)", XML_ErrorString(XML_GetErrorCode(p))); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); free(map->game_id); free(map); return NULL; } db_dbg("Parsing done"); db_xml_node_pop(p); XML_ParserFree(p); fclose(fp); _db_map_set_collision_tiles(map); return map; } static int _db_map_render_layer(struct db_map *map, struct db_map_layer *layer, SDL_Renderer *renderer) { int i; int gid; SDL_Rect layer_rect; for (i = 0; i < map->w * map->h; ++i) { gid = layer->tiles[i]; if (gid == 0) { continue; } layer_rect.x = map->tw * (i % map->w); layer_rect.y = map->th * (i / map->w); layer_rect.w = map->tw; layer_rect.h = map->th; db_tile_render(map->tileset_head, renderer, gid, &layer_rect); } return 0; } SDL_Texture * db_map_render(struct db_map *map, SDL_Renderer *renderer) { SDL_Texture *texture; SDL_Texture *old_target; struct db_map_layer *layer; texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, map->w * map->tw, map->h * map->th); old_target = SDL_GetRenderTarget(renderer); SDL_SetRenderTarget(renderer, texture); if (SDL_SetRenderDrawColor(renderer, map->bg_r, map->bg_g, map->bg_b, 0xFF) != 0) { db_err("Failed to set drawing color (%s)", SDL_GetError()); return NULL; } if (SDL_RenderClear(renderer) != 0) { db_err("Failed to clear texture (%s)", SDL_GetError()); return NULL; } for (layer = map->layer_head; layer != NULL; layer = layer->next) { if (_db_map_render_layer(map, layer, renderer) != 0) { return NULL; } } SDL_SetRenderTarget(renderer, old_target); return texture; } struct db_tileset * db_map_get_tilesets(struct db_map *map) { return map->tileset_head; } void db_map_get_player(struct db_map *map, int *x, int *y, int *r, int *gid) { *x = map->player_x; *y = map->player_y; *r = map->player_r; *gid = map->player_gid; } void db_map_get_target(struct db_map *map, int *x, int *y, int *r, int *gid) { *x = map->target_x; *y = map->target_y; *r = map->target_r; *gid = map->target_gid; } struct db_ball *db_map_get_balls(struct db_map *map) { return map->ball_head; }