diff options
Diffstat (limited to 'src/mapcat.c')
-rw-r--r-- | src/mapcat.c | 483 |
1 files changed, 483 insertions, 0 deletions
diff --git a/src/mapcat.c b/src/mapcat.c new file mode 100644 index 0000000..6420cf5 --- /dev/null +++ b/src/mapcat.c @@ -0,0 +1,483 @@ +/* +Copyright (C) 2016 Paweł Redman + +This program 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. + +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 General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#define DEBUG +#include "common.h" + +typedef struct { + float def[9]; + char *shader; + float texmap[8]; + elist_header_t list; +} brush_face_t; + +typedef struct { + brush_face_t *faces; + elist_header_t list; +} brush_t; + +typedef struct { + char *key; + char *value; + elist_header_t list; +} entity_key_t; + +typedef struct { + char *classname; + brush_t *brushes; + entity_key_t *keys; + + elist_header_t list; +} entity_t; + +entity_t *worldspawn; +entity_t *entities; + +static int read_entity_key(lexer_state_t *ls, entity_t *entity) +{ + entity_key_t *key; + + // classnames are stored separately for easier access later + if (!vstr_cmp(ls->token, "classname")) { + if (entity->classname) { + lexer_perror(ls, "warning: duplicate classname\n"); + free(entity->classname); + entity->classname = NULL; + } + + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "the classname"); + return 1; + } + + entity->classname = vstr_strdup(ls->token); + if (!entity->classname) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + return 0; + } + + key = malloc(sizeof(entity_key_t)); + if (!key) + goto error_oom; + + memset(key, 0, sizeof(*key)); + elist_append(&entity->keys, key, list); + + key->key = vstr_strdup(ls->token); + if (!key->key) + goto error_oom; + + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "the key value"); + return 1; + } + + key->value = vstr_strdup(ls->token); + if (!key->value) + goto error_oom; + + return 0; +error_oom: + lexer_perror(ls, "out of memory\n"); + return 1; +} + +static int read_brush_face(lexer_state_t *ls, brush_t *brush) +{ + brush_face_t *face; + + face = malloc(sizeof(brush_face_t)); + if (!face) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + memset(face, 0, sizeof(*face)); + elist_append(&brush->faces, face, list); + + if (lexer_get_floats(ls, face->def, 3)) + return 1; + + if (lexer_assert(ls, ")", "the end of this face's 1st vector")) + return 1; + + if (lexer_assert(ls, "(", "the beginning of this face's 2nd vector")) + return 1; + + if (lexer_get_floats(ls, face->def + 3, 3)) + return 1; + + if (lexer_assert(ls, ")", "the end of this face's 2nd vector")) + return 1; + + if (lexer_assert(ls, "(", "the beginning of this face's 3rd vector")) + return 1; + + if (lexer_get_floats(ls, face->def + 6, 3)) + return 1; + + if (lexer_assert(ls, ")", "the end of this face's 3rd vector")) + return 1; + + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "a shader name"); + return 1; + } + + face->shader = vstr_strdup(ls->token); + if (!face->shader) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + if (lexer_get_floats(ls, face->texmap, 8)) + return 1; + + return 0; +} + +static int read_brush_faces(lexer_state_t *ls, brush_t *brush) +{ + while (1) { + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "the beginning of a brush face " + " ('(') or the end of this brush" + " ('}')"); + return 1; + } + + if (!vstr_cmp(ls->token, "(")) { + if (read_brush_face(ls, brush)) + return 1; + } else if (!vstr_cmp(ls->token, "}")) + break; + } + + return 0; +} + +static void free_entity_keys(entity_t *entity) +{ + entity_key_t *key, *next; + + for (key = entity->keys; key; key = next) { + next = elist_next(key, list); + + free(key->key); + free(key->value); + free(key); + } +} + +static void free_brush(brush_t *brush) +{ + brush_face_t *face, *face_next; + + for (face = brush->faces; face; face = face_next) { + face_next = elist_next(face, list); + + free(face->shader); + free(face); + } + + free(brush); +} + +// this frees an entity_t and its children +// note: this function does NOT unlink the entity from the global list +static void free_entity(entity_t *entity) +{ + brush_t *brush, *next; + + free_entity_keys(entity); + free(entity->classname); + + for (brush = entity->brushes; brush; brush = next) { + next = elist_next(brush, list); + free_brush(brush); + } + + free(entity); +} + +static void merge_into_worldspawn(entity_t *entity) +{ + // the first worldspawn becomes the output's worldspawn. + // other worldspawn's brushes are appended to it + if (!worldspawn) { + worldspawn = entity; + return; + } + + // keep only the original key-value pairs + free_entity_keys(entity); + + elist_append_list(worldspawn->brushes, entity->brushes, list); + + free(entity); +} + +static bool brush_discard(brush_t *brush) +{ + brush_face_t *face; + + elist_for(face, brush->faces, list) + if (!strcmp(face->shader, "mapcat_discard")) + return true; + + return false; +} + +static int read_entity(lexer_state_t *ls) +{ + entity_t *entity; + + entity = malloc(sizeof(entity_t)); + if (!entity) { + lexer_perror(ls, "out of memory\n"); + return 1; + } + + memset(entity, 0, sizeof(*entity)); + + // read keys and values + while (1) { + if (lexer_get_token(ls)) { + lexer_perror_eg(ls, "a key or the beginning of a brush" + " \"{\" or the end of this entity" + " \"}\""); + goto error; + } + + if (!vstr_cmp(ls->token, "{")) + break; + + if (!vstr_cmp(ls->token, "}")) + goto no_brushes; + + if (read_entity_key(ls, entity)) + goto error; + } + + // the opening brace of the first brush in this entity was already + // read in the loop above, so it has to be skipped in the loop below. + // the problem is solved by jumping in the middle of the loop + goto skip_first_brace; + + while (1) { + brush_t *brush; + + if (lexer_get_token(ls)) { + L1: + lexer_perror_eg(ls, "the beginning of a brush \"{\"" + " or the end of this entity \"}\""); + goto error; + } + + if (!vstr_cmp(ls->token, "}")) + break; + else if (vstr_cmp(ls->token, "{")) + goto L1; + + skip_first_brace: + brush = malloc(sizeof(brush_t)); + if (!brush) { + lexer_perror(ls, "out of memory\n"); + goto error; + } + + memset(brush, 0, sizeof(*brush)); + + if (read_brush_faces(ls, brush)) { + free_brush(brush); + goto error; + } + + if (brush_discard(brush)) + free_brush(brush); + else + elist_append(&entity->brushes, brush, list); + } + +no_brushes: + + if (entity->classname && !strcmp(entity->classname, "worldspawn")) + merge_into_worldspawn(entity); + else + elist_append(&entities, entity, list); + + return 0; +error: + free_entity(entity); + return 1; +} + + +int mapcat_load(const char *path) +{ + int rv = 1; + lexer_state_t lexer; + vstr_t token; + + vstr_init(&token); + + if (lexer_open(&lexer, path, &token)) { + perror(path); + return 1; + } + + while (1) { + int ret; + + ret = lexer_assert_or_eof(&lexer, "{", + "the beginning of an entity"); + if (ret == -1) + break; + if (ret > 0) + goto out; + + if (read_entity(&lexer)) + goto out; + } + + rv = 0; +out: + vstr_free(&token); + return rv; +} + +static int write_brush(FILE *fp, brush_t *brush) +{ + brush_face_t *face; + + elist_for(face, brush->faces, list) { + size_t i; + + for (i = 0; i < 9; i += 3) { + if (i) + fprintf(fp, " "); + + fprintf(fp, "( %f %f %f )", face->def[i], + face->def[i + 1], face->def[i + 2]); + } + + fprintf(fp, " %s", face->shader); + + for (i = 0; i < 5; i++) + fprintf(fp, " %f", face->texmap[i]); + + // the last three values are integers + for (i = 5; i < 8; i++) + fprintf(fp, " %.0f", face->texmap[i]); + + fprintf(fp, "\n"); + } + + if (ferror(fp)) + return -errno; + + return 0; +} + +static int write_entity(FILE *fp, entity_t *entity) +{ + entity_key_t *key; + brush_t *brush; + size_t brush_counter = 0; + + fprintf(fp, "\"classname\" \"%s\"\n", entity->classname); + + elist_for(key, entity->keys, list) + fprintf(fp, "\"%s\" \"%s\"\n", key->key, key->value); + + elist_for(brush, entity->brushes, list) { + fprintf(fp, "// brush %zu\n{\n", brush_counter); + write_brush(fp, brush); + fprintf(fp, "}\n"); + brush_counter++; + } + + if (ferror(fp)) + return -errno; + + return 0; +} + +int mapcat_save(const char *path) +{ + int rv = 1; + FILE *fp; + entity_t *entity; + size_t entity_counter = 1; // worldspawn is #0 + + fp = fopen(path, "w"); + if (!fp) { + perror(path); + goto out; + } + + if (!worldspawn) { + fprintf(stderr, "error: worldspawn is missing\n"); + goto out; + } + + fprintf(fp, "// entity 0\n{\n"); + write_entity(fp, worldspawn); + fprintf(fp, "}\n"); + + elist_for(entity, entities, list) { + fprintf(fp, "// entity %zu\n{\n", entity_counter); + + if (write_entity(fp, entity)) { + perror(path); + goto out; + } + + fprintf(fp, "}\n"); + entity_counter++; + } + + if (ferror(fp) || fclose(fp)) { + perror(path); + goto out; + } + + fp = NULL; + rv = 0; + +out: + if (fp) + fclose(fp); + return rv; +} + +void mapcat_free(void) +{ + entity_t *entity, *next; + + if (worldspawn) + free_entity(worldspawn); + + for (entity = entities; entity; entity = next) { + next = elist_next(entity, list); + free_entity(entity); + } +} |