Mercurial > hg > batmud > maputils
view mkloc.c @ 941:e8d728edb54a
Set link targets in GMapsXML output to "_blank" to open them in separate windows/tabs.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Wed, 12 May 2010 12:40:03 +0000 |
parents | ff806678ef3f |
children | e98b0f94327e |
line wrap: on
line source
/* * mkloc - Manipulate and convert BatMUD location data files * * Programmed by Matti 'ccr' Hämäläinen (Ggr Pupunen) * (C) Copyright 2006-2009 Tecnic Software productions (TNSP) */ #include "maputils.h" #include <string.h> #include <strings.h> #include "th_args.h" #include "th_string.h" #include <time.h> /* Version string */ #define LOC_MAGIC "MapUtils LOC file" #define LOC_VERSION_MAJOR (3) #define LOC_VERSION_MINOR (0) /* LOCD_* is an enum describing the preferred orientation of the * location marker _label_ in relation to the marker spot. */ enum { LOCD_NONE = 0, LOCD_LEFT, LOCD_LEFTDOWN, LOCD_DOWN }; /* Timestamp used in loc-files, in sscanf() format. */ #define LOC_TIMEFMT "%02d.%02d.%04d" /* These flags are used to describe the marker/location type and label * related information: [LOCF_M_*] Draw marker of specified type (by default, * no marker is drawn). LOCF_M_CITY and LOCF_M_PCITY are special, they also * work as aliases to LOCF_T_CITY/PCITY and thus have similar functionality. * * [LOCF_T_*] specify the type of the location/marker. Currently the effect * of this settings is addition of "GUILD", "SHRINE"-type strings to labels, * but this information can be used in other ways too. */ #define LOCF_NONE (0x0000) #define LOCF_M_SCENIC1 (0x0001) /* '?' Scenic marker */ #define LOCF_M_SCENIC2 (0x0002) /* '%' Shrine marker/etc */ #define LOCF_M_PCITY (0x0004) /* 'C' Player city */ #define LOCF_M_CITY (0x0008) /* 'c' City */ #define LOCF_M_MASK (0x000F) #define LOCF_T_SHRINE (0x0010) /* 'S' Raceshrine */ #define LOCF_T_GUILD (0x0020) /* 'G' Guild */ #define LOCF_T_SS (0x0040) /* 'P' Player guild/Secret Society */ #define LOCF_T_MONSTER (0x0080) /* 'M' Special monster */ #define LOCF_T_TRAINER (0x0100) /* 'T' Guild trainer */ #define LOCF_T_FORT (0x0200) /* 'F' Regions fort */ #define LOCF_T_MASK (0x0FF0) #define LOCF_MASK (LOCF_M_MASK | LOCF_T_MASK) #define LOCF_INVIS (0x1000) /* '-' Invisible marker / Don't show label */ #define LOCF_INVALID (0x2000) /* Possibly invalid location */ #define LOCF_NOMARKER (0x4000) /* Location has no marker in mapdata or explicitly defined */ #define LOCF_Q_MASK (0xF000) /* Misc constants */ #define LOC_MAX_NAMES (64) /* Probably more than enough? */ #define LOC_MARKERS "?%C" #define LOC_MAX_FILES (64) /* Structures */ typedef struct { int day, month, year; } tm_t; typedef struct { char *filename; char *continent; int x, y; FILE *f; size_t lineNum; int c; } fileinfo_t; typedef struct { int x, y, ox, oy; int dir, flags; tm_t added; BOOL addedValid; int nnames, ncoders; char *names[LOC_MAX_NAMES], *coders[LOC_MAX_NAMES]; char *uri, *freeform; fileinfo_t *file; } marker_t; typedef struct { int numLocations; marker_t **locations; } locations_t; enum { OUTFMT_MAP = 0, OUTFMT_LOCFILE, OUTFMT_SCRIPT, OUTFMT_MAPLOC, OUTFMT_GMAPS }; enum { GMAPS_XML = 0, GMAPS_OVERLAY, GMAPS_LABELS, GMAPS_LAST }; /* These must be in lower case */ const char *gmapsModes[GMAPS_LAST] = { "xml", "overlay", "labels" }; /* Variables */ char *srcFile = NULL, *destFile = NULL; int nlocFiles = -1; fileinfo_t locFiles[LOC_MAX_FILES]; char *optLocMarkers = LOC_MARKERS; BOOL optGetUpdateLoc = FALSE, optOutput = OUTFMT_MAP, optNoLabels = FALSE, optNoAdjust = FALSE, optLabelType = FALSE; int optGMapsMode = -1; float optScale = -1, optUnitSize = 1.0f, optFontScale = 1.0f; /* Arguments */ optarg_t optList[] = { { 0, '?', "help", "Show this help", OPT_NONE }, { 2, 'v', "verbose", "Be more verbose", OPT_NONE }, { 3, 'q', "quiet", "Be quiet", OPT_NONE }, { 1, 'o', "output", "Output file (default stdout)", OPT_ARGREQ }, { 5, 'm', "map", "Input map file", OPT_ARGREQ }, { 6, 'l', "locinfo", "Input location info file", OPT_ARGREQ }, { 4, 'g', "getloc", "Generate/update location info", OPT_NONE }, { 7, 'x', "offset-x", "Location X offset", OPT_ARGREQ }, { 8, 'y', "offset-y", "Location Y offset", OPT_ARGREQ }, { 9, 's', "scale", "Scale coordinates by", OPT_ARGREQ }, { 12,'f', "font-scale", "(-S) Font scale factor", OPT_ARGREQ }, { 13,'u', "unit-size", "(-S) Unit size", OPT_ARGREQ }, { 10,'S', "out-script", "Output script for ImageMagick", OPT_NONE }, { 11,'L', "out-locinfo","Output location info file", OPT_NONE }, { 17,'M', "out-maploc", "Output MapLoc HTML", OPT_NONE }, { 20,'G', "out-gmaps", "Output GMaps data (xml, overlay, labels)", OPT_ARGREQ }, { 21,'c', "continent", "Continent for GMaps XML output", OPT_ARGREQ }, { 15,'n', "no-labels", "No labels, only markers", OPT_NONE }, { 16,'N', "no-adjust", "No label adjustment", OPT_NONE }, { 18,'t', "type-prefix","Prepend labels with type prefix", OPT_NONE }, { 19,'X', "loc-markers","Location markers ('" LOC_MARKERS "')", OPT_ARGREQ }, }; const int optListN = (sizeof(optList) / sizeof(optarg_t)); void argShowHelp() { th_args_help(stdout, optList, optListN, th_prog_name, "[options]"); } BOOL argHandleOpt(const int optN, char *optArg, char *currArg) { int i; fileinfo_t *f; switch (optN) { case 0: argShowHelp(); exit(0); break; case 2: th_verbosityLevel++; break; case 3: th_verbosityLevel = -1; break; case 1: destFile = optArg; THMSG(2, "Output file '%s'\n", destFile); break; case 5: srcFile = optArg; THMSG(2, "Map file '%s'\n", srcFile); break; case 6: nlocFiles++; if (nlocFiles < LOC_MAX_FILES) { f = &locFiles[nlocFiles]; f->filename = optArg; f->x = f->y = 0; f->lineNum = 1; f->continent = NULL; THMSG(2, "Added location data file #%d '%s'\n", nlocFiles, optArg); } else { THERR("Too many location data files specified, maximum is %d.\n", LOC_MAX_FILES); return FALSE; } break; case 4: THMSG(2, "Location updating/generation mode\n"); optGetUpdateLoc = TRUE; break; case 7: f = &locFiles[nlocFiles]; f->x = atoi(optArg); THMSG(2, "Location file #%d X offset = %d\n", nlocFiles, f->x); break; case 8: f = &locFiles[nlocFiles]; f->y = atoi(optArg); THMSG(2, "Location file #%d Y offset = %d\n", nlocFiles, f->y); break; case 21: f = &locFiles[nlocFiles]; th_free(f->continent); f->continent = th_strdup(optArg); THMSG(2, "Using continent name '%s' for location file #%d\n", optArg, nlocFiles); break; case 9: optScale = atof(optArg); THMSG(2, "Location scale factor = %1.3f\n", optScale); break; case 10: THMSG(2, "Script output mode\n"); optOutput = OUTFMT_SCRIPT; break; case 11: THMSG(2, "Location file output mode\n"); optOutput = OUTFMT_LOCFILE; break; case 17: THMSG(2, "MapLoc HTML output mode\n"); optOutput = OUTFMT_MAPLOC; break; case 12: optFontScale = atof(optArg); THMSG(2, "Font scale factor = %1.3f\n", optFontScale); break; case 13: optUnitSize = atof(optArg); THMSG(2, "Unit size = %1.3f\n", optUnitSize); break; case 15: THMSG(2, "Not adding labels to locations\n"); optNoLabels = TRUE; break; case 16: THMSG(2, "Not adjusting data under labels\n"); optNoAdjust = TRUE; break; case 18: THMSG(2, "Adding label type prefixes\n"); optLabelType = TRUE; break; case 19: optLocMarkers = optArg; THMSG(2, "Using location markers = '%s'\n", optLocMarkers); break; case 20: optOutput = OUTFMT_GMAPS; optGMapsMode = -1; for (i = 0; i < GMAPS_LAST; i++) if (tolower(optArg[0]) == gmapsModes[i][0]) { optGMapsMode = i; break; } if (optGMapsMode < 0) { THERR("Invalid GMaps mode '%s'.\n", optArg); return FALSE; } THMSG(2, "GMaps output mode '%s' selected\n", gmapsModes[optGMapsMode]); break; default: THERR("Unknown option '%s'.\n", currArg); return FALSE; } return TRUE; } void fprintEsc1(FILE *f, const char *s) { const char *p = s; if (s == NULL) return; while (*p) { switch (*p) { case ';': fputs("\\;", f); break; case '\\': fputs("\\", f); break; case '\'': fputs("\\'", f); break; case '"': fputs("\\\"", f); break; default: fputc(*p, f); } p++; } } void fprintEsc2(FILE *f, const char *s) { const char *p = s; if (s == NULL) return; while (*p) { switch (*p) { case ';': fputs("\\;", f); break; case '\\': fputs("\\", f); break; default: fputc(*p, f); } p++; } } BOOL addLocation(locations_t *l, int x, int y, int dir, int flags, char **names, char **coders, tm_t *added, BOOL addedValid, char *uri, char *freeform, fileinfo_t *file) { marker_t *tmp; int i; /* Allocate location struct */ if ((tmp = th_calloc(1, sizeof(marker_t))) == NULL) return FALSE; tmp->x = tmp->ox = x; tmp->y = tmp->oy = y; tmp->dir = dir; tmp->flags = flags; tmp->file = file; for (i = 0; i < LOC_MAX_NAMES; i++) { if (names != NULL && names[i] != NULL) { tmp->names[tmp->nnames] = th_strdup(names[i]); tmp->nnames++; } if (coders != NULL && coders[i] != NULL) { tmp->coders[tmp->ncoders] = th_strdup(coders[i]); tmp->ncoders++; } } if (added != NULL) { memcpy(&(tmp->added), added, sizeof(tmp->added)); tmp->addedValid = addedValid; } else { time_t tmpT = time(NULL); struct tm *tmpTime = localtime(&tmpT); tmp->added.day = tmpTime->tm_mday; tmp->added.month = tmpTime->tm_mon + 1; tmp->added.year = tmpTime->tm_year + 1900; tmp->addedValid = TRUE; } tmp->uri = th_strdup(uri); tmp->freeform = th_strdup(freeform); /* Add new location */ l->locations = (marker_t **) th_realloc(l->locations, sizeof(marker_t*) * (l->numLocations+1)); if (l->locations == NULL) return FALSE; l->locations[l->numLocations] = tmp; l->numLocations++; return TRUE; } int findLocationByCoords(locations_t *l, int x, int y, BOOL locTrue) { int i; marker_t *tmp; for (i = 0; i < l->numLocations; i++) { tmp = l->locations[i]; if (locTrue) { if (tmp->ox == x && tmp->oy == y) return i; } else { if (tmp->x == x && tmp->y == y) return i; } } return -1; } static BOOL checkFlag(int flags, int mask, int flag) { if (mask) return (flags & mask) == flag; else return (flags & flag); } static BOOL checkMutex(fileinfo_t *f, int *flags, int mask, int flag) { if (!checkFlag(*flags, mask, 0) && !checkFlag(*flags, mask, flag)) { THERR("Invalid flags setting on line #%d in '%s'\n", f->lineNum, f->filename); return FALSE; } else { *flags |= flag; return TRUE; } } static BOOL parseFieldInt(fileinfo_t *f, int *val) { int res = 0; if (!isdigit(f->c)) return FALSE; while (isdigit(f->c)) { res *= 10; res += f->c - '0'; f->c = fgetc(f->f); } *val = res; return TRUE; } static char * parseFieldString(fileinfo_t *f, const char *endch) { char res[4096]; BOOL end = FALSE; size_t pos = 0; int i; if (strchr(endch, f->c)) return NULL; while (!end && !strchr(endch, f->c) && pos < 4095) { switch (f->c) { case '\n': case '\r': THERR("Unexpected EOL inside text field on line #%d of '%s'.\n", f->lineNum, f->filename); return NULL; break; case EOF: THERR("Unexpected EOF inside text field on line #%d of '%s'.\n", f->lineNum, f->filename); return NULL; break; case '\\': /* Enable continuation via '\' at EOL */ i = fgetc(f->f); if (i == EOF) { THERR("Unexpected EOF inside text field on line #%d of '%s'.\n", f->lineNum, f->filename); return NULL; } else if (i == '\n' || i == '\r') { f->c = fgetc(f->f); if (i == '\r' && f->c == '\n') f->c = fgetc(f->f); } else { res[pos++] = i; f->c = fgetc(f->f); } break; default: res[pos++] = f->c; f->c = fgetc(f->f); break; } } while (pos > 0 && isspace(res[pos - 1])) { pos--; res[pos] = 0; } res[pos] = 0; return (pos > 0) ? th_strdup(res) : NULL; } static BOOL parseFlags(fileinfo_t *f, int *flags) { BOOL endFlags; *flags = LOCF_NONE; endFlags = FALSE; while (!endFlags) { switch (f->c) { /* Scenic marker flags */ case '?': if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_SCENIC1)) return FALSE; break; case '%': if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_SCENIC2)) return FALSE; break; case 'C': if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_PCITY)) return FALSE; break; case 'c': if (!checkMutex(f, flags, LOCF_M_MASK, LOCF_M_CITY)) return FALSE; break; /* Marker type flags */ case 'S': if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_SHRINE)) return FALSE; break; case 'G': if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_GUILD)) return FALSE; break; case 'P': if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_SS)) return FALSE; break; case 'M': if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_MONSTER)) return FALSE; break; case 'T': if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_TRAINER)) return FALSE; break; case 'F': if (!checkMutex(f, flags, LOCF_T_MASK, LOCF_T_FORT)) return FALSE; break; /* Additional flags */ case '-': *flags |= LOCF_INVIS; break; default: endFlags = TRUE; break; } if (!endFlags) { f->c = fgetc(f->f); } } return TRUE; } enum { STATE_IDLE, STATE_FIELD, STATE_FIELD_SEP, STATE_COMMENT, STATE_NEXT, STATE_END, STATE_ERROR }; static BOOL checkForEOL(fileinfo_t *f, int *state) { if (f->c == '\n' || f->c == '\r') { *state = STATE_ERROR; THERR("Unexpected EOL on line #%d of '%s'.\n", f->lineNum, f->filename); return FALSE; } else return TRUE; } BOOL parseLocationStream(fileinfo_t *f, locations_t *l, int offX, int offY) { int state, prev, next, field, subfield, sep; int tmpX, tmpY, tmpOrient, tmpFlags, i; char *fieldsep = NULL, *tmpStr, *tmpURI = NULL; char *tmpLocNames[LOC_MAX_NAMES], *tmpCoderNames[LOC_MAX_NAMES]; BOOL res, tmpTimeSet = FALSE, versionSet = FALSE; tm_t tmpTime; /* Parse data */ memset(tmpLocNames, 0, sizeof(tmpLocNames)); memset(tmpCoderNames, 0, sizeof(tmpCoderNames)); state = STATE_IDLE; next = prev = STATE_ERROR; field = subfield = sep = -1; res = FALSE; f->c = fgetc(f->f); do { switch (state) { case STATE_IDLE: if (f->c == EOF) state = STATE_END; else if (f->c == '\r') { f->lineNum++; f->c = fgetc(f->f); if (f->c == '\n') f->c = fgetc(f->f); } else if (f->c == '\n') { f->lineNum++; f->c = fgetc(f->f); } else if (f->c == '#') { prev = state; state = STATE_COMMENT; next = STATE_IDLE; } else if (isdigit(f->c)) { /* Start of a record */ state = STATE_FIELD; field = 1; } else if (isspace(f->c)) { f->c = fgetc(f->f); } else { /* Syntax error */ state = STATE_ERROR; THERR("Syntax error in '%s' line #%d.\n", f->filename, f->lineNum); } break; case STATE_COMMENT: switch (f->c) { case '\r': f->c = fgetc(f->f); if (f->c == '\n') f->c = fgetc(f->f); f->lineNum++; prev = state; state = next; break; case '\n': f->c = fgetc(f->f); f->lineNum++; prev = state; state = next; break; case EOF: state = STATE_END; break; default: f->c = fgetc(f->f); /* Because loc file identification should be the first * comment line, we check it here. */ if (!versionSet && isalpha(f->c)) { char *tmp = parseFieldString(f, "(\n\r"); if (tmp != NULL && !strcmp(tmp, LOC_MAGIC)) { /* ID found, check version */ char *verStr = parseFieldString(f, ")\n\r"); int verMajor, verMinor; if (verStr != NULL && sscanf(verStr, "(version %d.%d", &verMajor, &verMinor) == 2) { if (verMajor != LOC_VERSION_MAJOR) { /* Major version mismatch, bail out with informative message */ THERR("LOC file format version %d.%d detected, internal version is %d.%d. " "Refusing to read due to potential incompatibilities. If you neverthless " "wish to proceed, change the loc file's version to match internal version.\n", verMajor, verMinor, LOC_VERSION_MAJOR, LOC_VERSION_MINOR); state = STATE_ERROR; } else if (verMinor != LOC_VERSION_MINOR) { /* Minor version mismatch, just inform about it */ THERR("LOC file format version %d.%d detected, internal version is %d.%d, proceeding.\n", verMajor, verMinor, LOC_VERSION_MAJOR, LOC_VERSION_MINOR); } } else { THERR("Invalid or malformed LOC file, version not found (%s).\n", verStr); state = STATE_ERROR; } th_free(verStr); } else { THERR("Invalid LOC file, the file ID is missing ('# %s (version %d.%d)' should be the first line.)\n", LOC_MAGIC, LOC_VERSION_MAJOR, LOC_VERSION_MINOR); state = STATE_ERROR; } th_free(tmp); versionSet = TRUE; } break; } break; case STATE_NEXT: switch (f->c) { case EOF: state = STATE_ERROR; THERR("Unexpected end of file on line #%d of '%s'.\n", f->lineNum, f->filename); break; case 32: case 9: f->c = fgetc(f->f); break; case '\\': /* Enable continuation via '\' at EOL */ i = fgetc(f->f); if (i != '\n' && i != '\r') { state = STATE_ERROR; THERR("Expected EOL on line #%d of '%s'.\n", f->lineNum, f->filename); } else { f->lineNum++; f->c = fgetc(f->f); if (i == '\r' && f->c == '\n') f->c = fgetc(f->f); } break; default: prev = state; state = next; break; } break; case STATE_FIELD_SEP: if (strchr(fieldsep, f->c) != NULL) { sep = f->c; f->c = fgetc(f->f); prev = state; next = STATE_FIELD; state = STATE_NEXT; } else { state = STATE_ERROR; THERR("Expected field separator '%s', got '%c' on line #%d of '%s'.\n", fieldsep, f->c, f->lineNum, f->filename); } break; case STATE_FIELD: if (field > 0 && field < 8 && !checkForEOL(f, &state)) break; switch (field) { case 1: /* X-coordinate */ res = parseFieldInt(f, &tmpX); fieldsep = ";"; if (res) { field++; prev = state; next = STATE_FIELD_SEP; state = STATE_NEXT; } break; case 2: /* Y-coordinate */ res = parseFieldInt(f, &tmpY); if (res) { field++; prev = state; next = STATE_FIELD_SEP; state = STATE_NEXT; } break; case 3: /* Label orientation and flags */ res = parseFieldInt(f, &tmpOrient); if (res) res = parseFlags(f, &tmpFlags); if (res) { field++; prev = state; next = STATE_FIELD_SEP; state = STATE_NEXT; } break; case 4: /* Location name(s) */ if (subfield < 0) { subfield = 0; fieldsep = "|;"; sep = '|'; } if (sep == '|') { if (subfield < LOC_MAX_NAMES) { th_free(tmpLocNames[subfield]); tmpLocNames[subfield++] = parseFieldString(f, fieldsep); prev = state; next = STATE_FIELD_SEP; state = STATE_NEXT; if (!strchr(fieldsep, f->c)) { state = STATE_ERROR; THERR("Expected field separator '%s' after LOCNAMES on line #%d of '%s'.\n", fieldsep, f->lineNum, f->filename); } } else { state = STATE_ERROR; THERR("Too many location names (max %d) on line #%d of '%s'.\n", LOC_MAX_NAMES, f->lineNum, f->filename); } } else { fieldsep = ";"; subfield = -1; field++; prev = state; state = STATE_FIELD; } break; case 5: /* Coders */ if (subfield < 0) { subfield = 0; fieldsep = ",;"; sep = ','; } if (sep == ',') { if (subfield < LOC_MAX_NAMES) { th_free(tmpCoderNames[subfield]); tmpCoderNames[subfield++] = parseFieldString(f, fieldsep); prev = state; next = STATE_FIELD_SEP; state = STATE_NEXT; if (!strchr(fieldsep, f->c)) { state = STATE_ERROR; THERR("Expected field separator '%s' after CODERNAMES on line #%d of '%s'.\n", fieldsep, f->lineNum, f->filename); } } else { state = STATE_ERROR; THERR("Too many coder names (max %d) on line #%d of '%s'.\n", LOC_MAX_NAMES, f->lineNum, f->filename); } } else { fieldsep = ";"; subfield = -1; field++; prev = state; state = STATE_FIELD; } break; case 6: /* Date */ tmpTimeSet = FALSE; tmpStr = parseFieldString(f, fieldsep); if (tmpStr && tmpStr[0]) { if (sscanf(tmpStr, LOC_TIMEFMT, &tmpTime.day, &tmpTime.month, &tmpTime.year) == 3) tmpTimeSet = TRUE; else { THERR("Warning, invalid timestamp '%s' in '%s' (line #%d in '%s')\n", tmpStr, tmpLocNames[0], f->lineNum, f->filename); state = STATE_ERROR; } } if (!strchr(fieldsep, f->c)) { state = STATE_ERROR; THERR("Expected field separator '%s' after DATE on line #%d of '%s'.\n", fieldsep, f->lineNum, f->filename); } else { field++; prev = state; next = STATE_FIELD_SEP; state = STATE_NEXT; } th_free(tmpStr); break; case 7: /* URI */ th_free(tmpURI); tmpURI = parseFieldString(f, fieldsep); field++; prev = state; next = STATE_FIELD_SEP; state = STATE_NEXT; break; case 8: /* Freeform */ tmpStr = parseFieldString(f, "\r\n"); /* Check if location already exists */ tmpX += offX; tmpY += offY; i = findLocationByCoords(l, tmpX, tmpY, TRUE); if (i >= 0) { marker_t *tloc = l->locations[i]; THERR("Warning, location already in list!\n" "(%d,%d) '%s' <-> (%d,%d) '%s'\n", tloc->x, tloc->y, tloc->names[0], tmpX, tmpY, tmpLocNames[0]); state = STATE_ERROR; } else { /* Add new location to our list */ addLocation(l, tmpX, tmpY, tmpOrient, tmpFlags, tmpLocNames, tmpCoderNames, &tmpTime, tmpTimeSet, tmpURI, tmpStr, f); prev = state; state = STATE_IDLE; } for (i = 0; i < LOC_MAX_NAMES; i++) { th_free(tmpLocNames[i]); th_free(tmpCoderNames[i]); tmpLocNames[i] = tmpCoderNames[i] = NULL; } th_free(tmpStr); break; default: THERR("FATAL ERROR! Invalid state=%d!\n", state); state = STATE_ERROR; } if (!res) { state = STATE_ERROR; THERR("Error parsing field %d on line #%d of '%s'.\n", field, f->lineNum, f->filename); } break; default: THERR("Invalid state in loc-file parser - state=%d, prev=%d, next=%d, lineNum=%d, file='%s'.\n", state, prev, next, f->lineNum, f->filename); state = STATE_ERROR; break; } } while (state != STATE_ERROR && state != STATE_END); for (i = 0; i < LOC_MAX_NAMES; i++) { th_free(tmpLocNames[i]); th_free(tmpCoderNames[i]); } th_free(tmpURI); return (state == STATE_END); } int printLocType(FILE *outFile, marker_t *loc, BOOL doPrint) { char *s = NULL; switch (loc->flags & LOCF_M_MASK) { case LOCF_M_PCITY: s = "PCITY"; break; case LOCF_M_CITY: s = "CITY"; break; default: switch (loc->flags & LOCF_T_MASK) { case LOCF_T_SHRINE: s = "SHRINE"; break; case LOCF_T_GUILD: s = "GUILD"; break; case LOCF_T_SS: s = "SS"; break; case LOCF_T_TRAINER: s = "TRAINER"; break; case LOCF_T_FORT: s = "FORT"; break; } } if (s != NULL) { if (doPrint) fprintf(outFile, "%s ", s); return strlen(s) + 1; } else return 0; } /* Adjust location label coordinates for the ASCII-CTRL map */ void adjustLocInfoCoords(mapblock_t *map, locations_t *l) { int i; for (i = 0; i < l->numLocations; i++) { int y, x0, x1, len; marker_t *tmp = l->locations[i]; len = strlen(tmp->names[0]); if (optLabelType) len += printLocType(NULL, tmp, FALSE); /* Compute text location */ switch (tmp->dir) { case LOCD_LEFTDOWN: y = tmp->y + 1; x0 = tmp->x - len; break; case LOCD_LEFT: y = tmp->y - 1; x0 = tmp->x - len; break; case LOCD_DOWN: y = tmp->y + 1; x0 = tmp->x + 1; break; case LOCD_NONE: default: y = tmp->y - 1; x0 = tmp->x + 1; break; } x1 = x0 + len + 1; if (x1 > map->w) x0 -= (x1 - map->w - 1); if (x0 < 0) x0 = 0; if (y < 0) y += 2; if (y > map->h) y -= (y - map->h - 1); /* Update location info */ tmp->x = x0; tmp->y = y; } } /* Check for adjacent markers */ int checkForAdjacent(locations_t *worldLoc, int cx, int cy, int locMask) { int x, y, n; for (y = -1; y <= 1; y++) for (x = -1; x <= 1; x++) if (!(y == 0 && x == 0) && (n = findLocationByCoords(worldLoc, cx+x, cy+y, TRUE)) >= 0) { marker_t *tmpl = worldLoc->locations[n]; if ((tmpl->flags & locMask) == locMask) return n; } return -1; } /* Scan given map and update location list with new locations, * if any are found. */ void updateLocations(mapblock_t *worldMap, locations_t *worldLoc) { int x, y, n, numNewLoc = 0, numInvLoc = 0, numNoMarker = 0; unsigned char *d = worldMap->d; marker_t *tmpl; THMSG(2, "Updating location information ..\n"); /* Find new, unknown locations */ for (y = 0; y < worldMap->h; y++) for (x = 0; x < worldMap->w; x++) { if (strchr(optLocMarkers, *d)) { /* Check for new locations */ if (findLocationByCoords(worldLoc, x, y, TRUE) < 0) { char *tmpNames[LOC_MAX_NAMES]; char tmpDesc[512]; int tmpFlags; if (*d == 'C') { n = checkForAdjacent(worldLoc, x, y, LOCF_M_PCITY); if (n >= 0) { tmpl = worldLoc->locations[n]; tmpFlags = LOCF_M_PCITY | LOCF_INVIS; snprintf(tmpDesc, sizeof(tmpDesc), "%s", tmpl->names[0]); } else { numNewLoc++; tmpFlags = LOCF_M_PCITY; snprintf(tmpDesc, sizeof(tmpDesc), "%.3s-PCITY #%d", srcFile, numNewLoc); } } else { numNewLoc++; tmpFlags = LOCF_NONE; snprintf(tmpDesc, sizeof(tmpDesc), "%.3s-UNK #%d", srcFile, numNewLoc); } memset(tmpNames, 0, sizeof(tmpNames)); tmpNames[0] = tmpDesc; addLocation(worldLoc, x, y, LOCD_NONE, tmpFlags, tmpNames, NULL, NULL, FALSE, NULL, NULL, NULL); } } else { /* Check for misplaced locations */ if ((n = findLocationByCoords(worldLoc, x, y, TRUE)) >= 0) { tmpl = worldLoc->locations[n]; if (tmpl->flags == LOCF_NONE) { /* Mark as possibly invalid */ tmpl->flags |= LOCF_INVALID; numInvLoc++; } if ((tmpl->flags & LOCF_M_MASK) == 0) { /* No apparent marker */ tmpl->flags |= LOCF_NOMARKER; numNoMarker++; } } } d++; } THMSG(1, "%d new locations, %d invalid, %d missing marker.\n", numNewLoc, numInvLoc, numNoMarker); } /* Quicksort comparision function for location names */ int maplocCompare(const void *p1, const void *p2) { marker_t *vp1 = *(marker_t **) p1, *vp2 = *(marker_t **) p2; if (vp1->names[0] && vp2->names[0]) return strcmp(vp1->names[0], vp2->names[0]); else return 0; } /* Sort locations by name */ void maplocSort(locations_t *l) { qsort(l->locations, l->numLocations, sizeof(marker_t *), maplocCompare); } /* Output the map with labels and location markers, etc. in * special ASCII-CTRL format understood by colormap utility. */ void outputMapCTRL(FILE *outFile, mapblock_t *map, locations_t *l) { unsigned char *d; int x, y, n; d = map->d; for (y = 0; y < map->h; y++) { for (x = 0; x < map->w; x++) { if ((n = findLocationByCoords(l, x, y, TRUE)) >= 0) { marker_t *tmp = l->locations[n]; switch (tmp->flags & LOCF_M_MASK) { case LOCF_M_SCENIC1: fputc('?', outFile); break; case LOCF_M_SCENIC2: fputc('%', outFile); break; case LOCF_M_PCITY: fputc('C', outFile); break; case LOCF_M_CITY: fputc('c', outFile); break; default: if (tmp->flags & LOCF_INVALID) fputc('$', outFile); else fputc(d[(map->w * y) + x], outFile); break; } } else if (!optNoLabels && (n = findLocationByCoords(l, x, y, FALSE)) >= 0) { marker_t *tmp = l->locations[n]; if ((tmp->flags & LOCF_INVIS) == 0) { int col = col_light_white; fputc(0xff, outFile); fprintf(outFile, "loc%d_%d", tmp->ox, tmp->oy); fputc(0xfc, outFile); switch (tmp->flags & LOCF_M_MASK) { case LOCF_M_PCITY: col = col_light_green; break; case LOCF_M_CITY: col = col_light_red; break; default: switch (tmp->flags & LOCF_T_MASK) { case LOCF_T_SHRINE: col = col_light_yellow; break; case LOCF_T_GUILD: col = col_light_magenta; break; case LOCF_T_MONSTER: col = col_light_cyan; break; case LOCF_T_TRAINER: col = col_light_magenta; break; case LOCF_T_FORT: col = col_light_cyan; break; default: col = col_light_white; break; } break; } fputc(col, outFile); if (optLabelType) { if (optNoAdjust) printLocType(outFile, tmp, TRUE); else x += printLocType(outFile, tmp, TRUE); } fputs(tmp->names[0], outFile); fputc(0xfe, outFile); if (!optNoAdjust) x += strlen(tmp->names[0]) - 1; else fputc(d[(map->w * y) + x], outFile); } else { fputc(d[(map->w * y) + x], outFile); } } else { fputc(d[(map->w * y) + x], outFile); } } fprintf(outFile, "\n"); } } /* Print out location list as HTML option list. */ void outputMapLocHTML(FILE *outFile, locations_t *l) { int i; assert(l != NULL); for (i = 0; i < l->numLocations; i++) { marker_t *tmp = l->locations[i]; if ((tmp->flags & LOCF_INVIS) == 0) { fprintf(outFile, "<option value=\"loc%d_%d\">", tmp->ox, tmp->oy); printLocType(outFile, tmp, TRUE); fprinte(outFile, tmp->names[0]); fprintf(outFile, "</option>\n"); } } } /* Output generated locations into given file stream */ void outputLocationFile(FILE *outFile, locations_t *l) { int i, n; /* Output header */ fprintf(outFile, "# " LOC_MAGIC " (version %d.%d)\n", LOC_VERSION_MAJOR, LOC_VERSION_MINOR ); fprintf(outFile, "# Refer to README.loc for more information.\n" "#\n" ); /* Output each location entry */ for (i = 0; i < l->numLocations; i++) { marker_t *tmp = l->locations[i]; /* Add comment in few cases */ if (tmp->flags & LOCF_Q_MASK) { char *s = NULL; if (tmp->flags & LOCF_NOMARKER) s = "Location missing marker"; else if (tmp->flags & LOCF_INVALID) s = "Possibly invalid location"; if (s) { fprintf(outFile, "\n# %s #%d: %s\n", s, i, tmp->names[0]); } } fprintf(outFile, "%d\t; %d\t; %d", tmp->ox, tmp->oy, tmp->dir); switch (tmp->flags & LOCF_M_MASK) { case LOCF_M_SCENIC1: fputc('?', outFile); break; case LOCF_M_SCENIC2: fputc('%', outFile); break; case LOCF_M_PCITY: fputc('C', outFile); break; case LOCF_M_CITY: fputc('c', outFile); break; } switch (tmp->flags & LOCF_T_MASK) { case LOCF_T_SHRINE: fputc('S', outFile); break; case LOCF_T_GUILD: fputc('G', outFile); break; case LOCF_T_SS: fputc('P', outFile); break; case LOCF_T_MONSTER: fputc('M', outFile); break; case LOCF_T_TRAINER: fputc('T', outFile); break; case LOCF_T_FORT: fputc('F', outFile); break; } if (tmp->flags & LOCF_INVIS) fputc('-', outFile); fprintf(outFile, "\t;"); fprintEsc2(outFile, tmp->names[0]); for (n = 1; n < tmp->nnames; n++) { fprintf(outFile, "|"); fprintEsc2(outFile, tmp->names[n]); } fprintf(outFile, ";"); if (tmp->ncoders > 0) { fprintEsc2(outFile, tmp->coders[0]); for (n = 1; n < tmp->ncoders; n++) { fprintf(outFile, ","); fprintEsc2(outFile, tmp->coders[n]); } } fprintf(outFile, ";"); if (tmp->addedValid) { fprintf(outFile, LOC_TIMEFMT, tmp->added.day, tmp->added.month, tmp->added.year); } fprintf(outFile, ";"); if (tmp->uri) fprintEsc2(outFile, tmp->uri); fprintf(outFile, ";"); if (tmp->freeform) fprintEsc2(outFile, tmp->freeform); fprintf(outFile, "\n"); } } /* Generate a shell-script for running ImageMagick to add * location and label information to an map image. */ void outputMagickScript(FILE *outFile, locations_t *l) { int i, numCity, numLoc, numTotal; /* Output script start */ fprintf(outFile, "#!/bin/sh\n" "convert \"$1\" @OPTS_START@ \\\n"); numCity = numLoc = numTotal = 0; /* Output instructions for each visible location */ for (i = 0; i < l->numLocations; i++) { marker_t *tmp = l->locations[i]; int x, y, leftMove; char *cs; /* Is location visible? */ if ((tmp->flags & LOCF_INVIS) == 0) { leftMove = ((float) strlen(tmp->names[0])) * optFontScale; switch (tmp->dir) { case LOCD_LEFTDOWN: y = tmp->y + optUnitSize*3.0f; x = tmp->x - leftMove; break; case LOCD_LEFT: y = tmp->y - optUnitSize; x = tmp->x - leftMove; break; case LOCD_DOWN: y = tmp->y + optUnitSize*3.0f; x = tmp->x + optUnitSize; break; case LOCD_NONE: default: y = tmp->y - optUnitSize; x = tmp->x + optUnitSize; break; } /* Determine colour */ switch (tmp->flags & LOCF_M_MASK) { case LOCF_M_CITY: cs = "red"; numLoc++; break; case LOCF_M_PCITY: cs = "'#00ff00'"; numCity++; break; default: switch (tmp->flags & LOCF_T_MASK) { case LOCF_T_SHRINE: cs = "yellow"; break; case LOCF_T_GUILD: cs = "magenta"; break; case LOCF_T_MONSTER: cs = "cyan"; break; case LOCF_T_TRAINER: cs = "magenta"; break; case LOCF_T_FORT: cs = "'#00ffff'"; break; default: cs = "white"; break; } numLoc++; break; } numTotal++; /* Location marker */ fprintf(outFile, "\t-fill black -draw \"circle %d,%d %d,%d\" ", tmp->x, tmp->y, (int) (tmp->x + optUnitSize), (int) (tmp->y + optUnitSize)); fprintf(outFile, "-fill %s -draw \"circle %d,%d %d,%d\" ", cs, tmp->x, tmp->y, (int) (tmp->x + optUnitSize * 0.90f), (int) (tmp->y + optUnitSize * 0.90f)); /* Location description text */ if (!optNoLabels) { fprintf(outFile, "-fill %s -box '#00000080' -draw \"text %d,%d '", cs, x, y); fprintEsc1(outFile, tmp->names[0]); fprintf(outFile, "'\" "); } fprintf(outFile, " \\\n"); } } fprintf(outFile, "@OPTS_END@" "'\" \\\n"); fprintf(outFile, "\t\"$2\"\n"); } /* Output locations in GMaps XML format. Character set conversion * is not performed, caller must take care of it via iconv or similar. */ char *getLocType(marker_t *loc) { char *type = NULL; switch (loc->flags & LOCF_M_MASK) { case LOCF_M_CITY: type = "city"; break; case LOCF_M_PCITY: type = "pcity"; break; default: switch (loc->flags & LOCF_T_MASK) { case LOCF_T_SHRINE: type = "shrine"; break; case LOCF_T_GUILD: type = "guild"; break; case LOCF_T_SS: type = "ss"; break; case LOCF_T_MONSTER: type = "monster"; break; case LOCF_T_TRAINER: type = "trainer"; break; case LOCF_T_FORT: type = "fort"; break; default: type = "default"; break; } break; } return type; } void outputGMapsOverlay(FILE *outFile, locations_t *l) { int i; for (i = 0; i < l->numLocations; i++) { marker_t *tmp = l->locations[i]; int type; /* Skip disabled / invisible locations */ //if (tmp->flags & LOCF_INVIS) continue; switch (tmp->flags & LOCF_M_MASK) { case LOCF_M_SCENIC1: type = '?'; break; case LOCF_M_SCENIC2: type = '%'; break; case LOCF_M_CITY: type = 'c'; break; case LOCF_M_PCITY: type = 'C'; break; default: type = -1; } if (type > 0) fprintf(outFile, "%d,%d,%c\n", tmp->x, tmp->y, type); } } void outputGMapsLabels(FILE *outFile, locations_t *l) { int i; for (i = 0; i < l->numLocations; i++) { marker_t *tmp = l->locations[i]; /* Skip disabled / invisible locations */ if (tmp->flags & LOCF_INVIS) continue; fprintf(outFile, "%d,%d,", tmp->x, tmp->y); printLocType(outFile, tmp, TRUE); fprintf(outFile, "%s\n", tmp->names[0]); } } void outputGMapsXML(FILE *outFile, locations_t *l) { int i, n; fprintf(outFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<markers>\n"); /* Output each location entry */ for (i = 0; i < l->numLocations; i++) { marker_t *tmp = l->locations[i]; char *str; /* Skip disabled / invisible locations */ if (tmp->flags & LOCF_INVIS) continue; /* Print out coordinates etc. */ fprintf(outFile, "<marker x=\"%d\" y=\"%d\" labeldir=\"%d\" name=\"", tmp->ox, tmp->oy, tmp->dir); /* Location name */ str = (tmp->names[0] != NULL) ? tmp->names[0] : "UNKNOWN"; printLocType(outFile, tmp, TRUE); fprinte(outFile, str); fprintf(outFile, "\" html=\""); if (tmp->uri != NULL) fprintfe(outFile, "<b><a target=\"_blank\" href=\"%s\">%s</a></b><br>", tmp->uri, str); else fprintfe(outFile, "<b>%s</b><br>", str); /* Alternative names, if any */ if (tmp->nnames > 1) { fprinte(outFile, "Also known as <i>"); for (n = 1; n < tmp->nnames; n++) { fprinte(outFile, tmp->names[n]); if (n < tmp->nnames - 1) fprinte(outFile, " | "); } fprinte(outFile, "</i>.<br>"); } /* Added to game timestamp */ if (tmp->addedValid) { fprintfe(outFile, "Added " LOC_TIMEFMT ".<br>", tmp->added.day, tmp->added.month, tmp->added.year); } /* Coder names or societies */ if (tmp->ncoders > 0) { if (tmp->flags & LOCF_M_PCITY) { fprinte(outFile, "Societies: "); for (n = 0; n < tmp->ncoders; n++) { fprinte(outFile, tmp->coders[n]); if (n < tmp->ncoders - 1) fprinte(outFile, ", "); } } else { fprinte(outFile, "Coders: "); for (n = 0; n < tmp->ncoders; n++) { fprintfe(outFile, "<a target=\"_blank\" href=\"http://www.bat.org/char/%s\">%s</a>", tmp->coders[n], tmp->coders[n]); if (n < tmp->ncoders - 1) fprinte(outFile, ", "); } } fprinte(outFile, ".<br>"); } /* Print out freeform information field */ if (tmp->freeform) fprintfe(outFile, "<br>%s<br>", tmp->freeform); /* Flags */ fprintf(outFile, "\" flags=\"%d\"", tmp->flags); /* Continent name */ if (tmp->file != NULL && tmp->file->continent != NULL) { fprintf(outFile, " continent=\""); fprinte(outFile, tmp->file->continent); fprintf(outFile, "\""); } /* Type of the marker */ fprintf(outFile, " type=\"%s\"/>\n", getLocType(tmp)); } fprintf(outFile, "</markers>\n"); } int main(int argc, char *argv[]) { FILE *outFile; mapblock_t *worldMap = NULL; locations_t worldLoc; int i; th_memset(&worldLoc, 0, sizeof(worldLoc)); /* Initialize */ th_init("mkloc", "Manipulate and convert location files and ASCII map data", "1.3", NULL, NULL); th_verbosityLevel = 0; /* Parse arguments */ if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, NULL, FALSE)) exit(1); /* Check the mode and arguments */ if (srcFile == NULL && optGetUpdateLoc && optOutput == OUTFMT_LOCFILE) { THERR("Map file required for location update mode!\n"); exit(0); } if (optOutput == OUTFMT_LOCFILE && nlocFiles < 0 && !optGetUpdateLoc) { THERR("Location file or location update mode required for location file output!\n"); exit(0); } if ((optOutput == OUTFMT_SCRIPT || optOutput == OUTFMT_MAPLOC) && nlocFiles < 0) { THERR("Location file required for script or MapLoc HTML output!\n"); exit(0); } if (srcFile == NULL && optOutput == OUTFMT_MAP) { THERR("Mapfile required for map generation.\n"); exit(0); } /* Read initial map */ if (srcFile) { THMSG(2, "Reading map '%s'\n", srcFile); worldMap = parseFile(srcFile, FALSE); if (!worldMap) { THERR("World map could not be loaded!\n"); exit(1); } THMSG(2, "Initial dimensions (%d x %d)\n", worldMap->w, worldMap->h); } /* Read location info */ for (i = 0; i <= nlocFiles; i++) { fileinfo_t *f = &locFiles[i]; if (optOutput == OUTFMT_GMAPS && f->continent == NULL) { THERR("Required continent name not set for #%d '%s'.\n", i, f->filename); exit(19); } THMSG(2, "Reading location info '%s'\n", f->filename); if ((f->f = fopen(f->filename, "rb")) == NULL) { THERR("Could not open location file '%s' for reading.\n", f->filename); exit(2); } if (!parseLocationStream(f, &worldLoc, f->x, f->y)) exit(1); fclose(f->f); } /* Update locations */ if (optGetUpdateLoc) updateLocations(worldMap, &worldLoc); /* Scale locations */ if (optScale > 0) { int i; THMSG(1, "Scaling locations ..\n"); for (i = 0; i < worldLoc.numLocations; i++) { marker_t *tmp = worldLoc.locations[i]; tmp->x = ((float) tmp->x) * optScale; tmp->y = ((float) tmp->y) * optScale; } } /* Open output file */ if (destFile == NULL) outFile = stdout; else if ((outFile = fopen(destFile, "wb")) == NULL) { THERR("Error opening output file '%s'!\n", destFile); exit(1); } /* Output results */ switch (optOutput) { case OUTFMT_SCRIPT: THMSG(1, "Generating ImageMagick script ...\n"); outputMagickScript(outFile, &worldLoc); break; case OUTFMT_LOCFILE: THMSG(1, "Outputting generated location list ...\n"); outputLocationFile(outFile, &worldLoc); THMSG(2, "%d locations\n", worldLoc.numLocations); break; case OUTFMT_MAPLOC: maplocSort(&worldLoc); THMSG(1, "Outputting MapLoc HTML ...\n"); outputMapLocHTML(outFile, &worldLoc); THMSG(2, "%d locations\n", worldLoc.numLocations); break; case OUTFMT_GMAPS: maplocSort(&worldLoc); THMSG(1, "Outputting GMaps data (%s) ...\n", gmapsModes[optGMapsMode]); switch (optGMapsMode) { case GMAPS_XML: outputGMapsXML(outFile, &worldLoc); break; case GMAPS_OVERLAY: outputGMapsOverlay(outFile, &worldLoc); break; case GMAPS_LABELS: outputGMapsLabels(outFile, &worldLoc); break; } THMSG(2, "%d locations\n", worldLoc.numLocations); break; case OUTFMT_MAP: THMSG(1, "Outputting generated map of (%d x %d) ...\n", worldMap->w, worldMap->h); THMSG(2, "Adjusting location labels ..\n"); adjustLocInfoCoords(worldMap, &worldLoc); outputMapCTRL(outFile, worldMap, &worldLoc); break; } fclose(outFile); exit(0); return 0; }