Mercurial > hg > batmud > maputils
view src/mkloc.c @ 2712:ac63db65b917
Implement -Wextra for some extra warnings.
author | Matti Hamalainen <ccr@tnsp.org> |
---|---|
date | Mon, 04 Mar 2024 11:20:27 +0200 |
parents | 99c6d0cea264 |
children |
line wrap: on
line source
/* * Manipulate and convert BatMUD location data files * Programmed by Matti 'ccr' Hämäläinen <ccr@tnsp.org> * (C) Copyright 2006-2024 Tecnic Software productions (TNSP) */ #include "libmaputils.h" #include "liblocfile.h" #include "th_args.h" #include "th_string.h" #include "th_datastruct.h" enum { OUTFMT_MAP = 0, OUTFMT_LOCFILE, OUTFMT_SCRIPT, OUTFMT_MAPLOC, OUTFMT_GMAPS }; enum { GMAPS_XML = 0, GMAPS_JSON, GMAPS_LAST }; enum { WARN_NONE = 0x0000, WARN_MARKER_INVALID = 0x0001, WARN_MARKER_MISSING = 0x0002, WARN_AUTHORS_MISSING = 0x0004, WARN_TIMESTAMPS_MISSING = 0x0008, WARN_EXTRA = 0x1000, WARN_ALL = 0x0fff }; // These must be in lower case static const char *gmapsModes[GMAPS_LAST] = { "xml", "json", }; /* Variables */ char *optInFilename = NULL, *optOutFilename = NULL; int noptLocFiles = -1; LocFileInfo optLocFiles[LOC_MAX_FILES]; char *optLocMarkers = LOC_MARKERS; bool optGetUpdateLoc = false, optNoLabels = false, optNoAdjust = false, optLabelType = false, optWarningsToLoc = false; int optWarnings = WARN_NONE; int optOutput = OUTFMT_MAP, optGMapsMode = -1; float optScale = -1, optUnitSize = 1.0f, optFontScale = 1.0f; /* Arguments */ static const th_optarg 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 }, { 21,'c', "continent", "Location continent", 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 location data in Batclient XML or GMaps JSON format", 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', "markers", "Location markers ('" LOC_MARKERS "')", OPT_ARGREQ }, { 22,'W', "warnings", "Output warnings about: " "none, all, timestamps, authors, extra\n" "(NOTE: 'all' does NOT include 'extra')", OPT_ARGREQ }, { 23, 0, "warn-loc", "Output warnings to loc file instead of stderr", OPT_NONE }, }; static const int optListN = sizeof(optList) / sizeof(optList[0]); void argShowHelp() { th_print_banner(stdout, th_prog_name, "[options]"); th_args_help(stdout, optList, optListN, 0, 80 - 2); } bool argHandleOpt(const int optN, char *optArg, char *currArg) { LocFileInfo *f; switch (optN) { case 0: argShowHelp(); exit(0); break; case 2: th_verbosity++; break; case 3: th_verbosity = -1; break; case 1: optOutFilename = optArg; THMSG(2, "Output file '%s'\n", optOutFilename); break; case 5: optInFilename = optArg; THMSG(2, "Map file '%s'\n", optInFilename); break; case 6: noptLocFiles++; if (noptLocFiles < LOC_MAX_FILES) { locSetFileInfo(&optLocFiles[noptLocFiles], optArg, NULL); THMSG(2, "Added location data file #%d '%s'\n", noptLocFiles, 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: case 8: case 21: if (noptLocFiles >= 0) { f = &optLocFiles[noptLocFiles]; switch (optN) { case 7: f->xoffs = atoi(optArg); THMSG(2, "Location file #%d X offset = %d\n", noptLocFiles, f->xoffs); break; case 8: f->yoffs = atoi(optArg); THMSG(2, "Location file #%d Y offset = %d\n", noptLocFiles, f->yoffs); break; case 21: th_free(f->continent); f->continent = th_strdup(optArg); THMSG(2, "Using continent name '%s' for location file #%d\n", optArg, noptLocFiles); break; } } else { THERR("No location files specified, but location option '-%s %s' given.\n", currArg, optArg); return false; } 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 (int 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; case 22: if (th_strcasecmp(optArg, "none") == 0) optWarnings = WARN_NONE; else if (th_strcasecmp(optArg, "all") == 0) optWarnings = WARN_ALL; else switch (tolower(optArg[0])) { case 'a': optWarnings |= WARN_AUTHORS_MISSING; break; case 't': optWarnings |= WARN_TIMESTAMPS_MISSING; break; case 'e': optWarnings |= WARN_EXTRA; break; default: THERR("Invalid argument to -W: '%s'\n", optArg); return false; } break; case 23: THMSG(2, "Outputting warnings to loc file.\n"); optWarningsToLoc = true; break; default: THERR("Unknown option '%s'.\n", currArg); return false; } return true; } int locPrintType(FILE *outFile, const LocMarker *loc, const bool adjust, int (*func)(const char *, FILE *), const bool label) { const char *type = locGetTypePrefix(loc->flags); int len = 0; if (type != NULL && label) { len += strlen(type) + 1; if (outFile != NULL && func != NULL) fprintf(outFile, "%s ", type); } if (func != NULL) { const char *name = (loc->names[0].name != NULL) ? loc->names[0].name : "UNKNOWN"; if (outFile != NULL) func(name, outFile); len += strlen(name); if (label && (loc->flags & LOCF_CLOSED)) { static const char *strClosed = " (CLOSED)"; if (outFile != NULL) func(strClosed, outFile); len += strlen(strClosed); } } return adjust ? len : 0; } /* Adjust location label coordinates for the ASCII-CTRL map */ void adjustLocInfoCoords(MapBlock *map, MapLocations *lp) { for (int i = 0; i < lp->nlocations; i++) { int yc, x0, x1, len; LocMarker *loc = lp->locations[i]; len = strlen(loc->names[0].name); if (optLabelType) len += locPrintType(NULL, loc, true, NULL, false); // Compute text location switch (loc->align) { case LOCD_LEFTDOWN: yc = loc->yc + 1; x0 = loc->xc - len; break; case LOCD_LEFT: yc = loc->yc - 1; x0 = loc->xc - len; break; case LOCD_DOWN: yc = loc->yc + 1; x0 = loc->xc + 1; break; case LOCD_NONE: default: yc = loc->yc - 1; x0 = loc->xc + 1; break; } x1 = x0 + len + 1; if (x1 > map->width) x0 -= (x1 - map->width - 1); if (x0 < 0) x0 = 0; if (yc < 0) yc += 2; if (yc > map->height) yc -= (yc - map->height - 1); // Update location info loc->xc = x0; loc->yc = yc; } } /* Check for adjacent markers */ int checkForAdjacent(const MapLocations *world, const int cx, const int cy, const int mask) { for (int y = -1; y <= 1; y++) for (int x = -1; x <= 1; x++) { int n; if (!(y == 0 && x == 0) && (n = locFindByCoords(world, cx + x, cy + y, true)) >= 0) { const LocMarker *loc = world->locations[n]; if ((loc->flags & mask) == mask) return n; } } return -1; } /* Scan given map and update location list with new locations, * if any are found. */ void updateLocations(const MapBlock *worldMap, MapLocations *worldLoc) { int n, numNewLoc = 0, numInvLoc = 0, numNoMarker = 0; size_t noptLocMarkers = strlen(optLocMarkers); LocMarker *tmpl; THMSG(2, "Updating location information ..\n"); // Find new, unknown locations for (int yc = 0; yc < worldMap->height; yc++) { unsigned char *dp = worldMap->data + (worldMap->scansize * yc); for (int xc = 0; xc < worldMap->width; xc++) { if (muStrChr((unsigned char *) optLocMarkers, noptLocMarkers, dp[xc])) { LocName tmpNames[LOC_MAX_NAMES]; char tmpDesc[512]; int tmpFlags; // Check for new locations if (locFindByCoords(worldLoc, xc, yc, true) >= 0) continue; if (dp[xc] == 'C') { // In case of player cities, check for existing adjacent blocks // so we can match with an existing pcity .. n = checkForAdjacent(worldLoc, xc, yc, LOCF_M_PCITY); if (n >= 0) { // Found, use its name for this block tmpl = worldLoc->locations[n]; tmpFlags = LOCF_M_PCITY | LOCF_INVIS; snprintf(tmpDesc, sizeof(tmpDesc), "%s", tmpl->names[0].name); } else { // Nope, it is a "new" pcity numNewLoc++; tmpFlags = LOCF_M_PCITY; snprintf(tmpDesc, sizeof(tmpDesc), "%.3s-PCITY #%d", optInFilename, numNewLoc); } } else { // Non-pcities are handled here numNewLoc++; tmpFlags = LOCF_NONE; snprintf(tmpDesc, sizeof(tmpDesc), "%.3s-UNK #%d", optInFilename, numNewLoc); } memset(tmpNames, 0, sizeof(tmpNames)); tmpNames[0].name = tmpDesc; locAddNew(worldLoc, xc, yc, LOCD_NONE, tmpFlags, tmpNames, NULL, NULL, false, NULL, NULL, NULL); } else { // Check for misplaced locations if ((n = locFindByCoords(worldLoc, xc, yc, true)) >= 0) { tmpl = worldLoc->locations[n]; if (tmpl->flags == LOCF_NONE) { // Mark as possibly invalid tmpl->flags |= LOCF_INVALID; numInvLoc++; } else if ((tmpl->flags & LOCF_M_MASK) == 0) { // No apparent marker tmpl->flags |= LOCF_NOMARKER; numNoMarker++; } } } } } THMSG(1, "%d new locations, %d invalid, %d missing marker.\n", numNewLoc, numInvLoc, numNoMarker); } /* Quicksort comparision function for location names */ int maplocCompare(const void *pp1, const void *pp2) { const LocMarker *vp1 = *(const LocMarker **) pp1, *vp2 = *(const LocMarker **) pp2; if (vp1->vsort.v_int == vp2->vsort.v_int) return strcmp(vp1->names[0].name, vp2->names[0].name); else return vp1->vsort.v_int - vp2->vsort.v_int; } /* Sort locations by name */ void maplocSort(MapLocations *lp) { for (int i = 0; i < lp->nlocations; i++) { LocMarker *loc = lp->locations[i]; loc->vsort.v_int = 2; switch (loc->flags & LOCF_M_MASK) { case LOCF_M_CITY: loc->vsort.v_int = 1; break; case LOCF_M_PCITY: loc->vsort.v_int = 10; break; default: switch (loc->flags & LOCF_T_MASK) { case LOCF_T_GUILD: loc->vsort.v_int = 3; break; case LOCF_T_TRAINER: loc->vsort.v_int = 4; break; case LOCF_T_SHRINE: loc->vsort.v_int = 5; break; case LOCF_T_SS: loc->vsort.v_int = 6; break; case LOCF_T_MONSTER: loc->vsort.v_int = 7; break; case LOCF_T_FORT: loc->vsort.v_int = 100; break; } } } qsort(lp->locations, lp->nlocations, sizeof(LocMarker *), maplocCompare); } /* Output the map with labels and location markers, etc. in * special ASCII-CTRL format understood by colormap utility. */ void outputMapCTRL(FILE *outFile, const MapBlock *map, const MapLocations *lp) { for (int yc = 0; yc < map->height; yc++) { unsigned char *dp = map->data + (map->scansize * yc); for (int xc = 0; xc < map->width; xc++) { int n; if ((n = locFindByCoords(lp, xc, yc, true)) >= 0) { LocMarker *loc = lp->locations[n]; char chm = dp[xc]; switch (loc->flags & LOCF_M_MASK) { case LOCF_M_SCENIC1: chm = '?'; break; case LOCF_M_SCENIC2: chm = '%'; break; case LOCF_M_PCITY: chm = 'C'; break; case LOCF_M_CITY: chm = 'c'; break; default: if (loc->flags & LOCF_INVALID) chm = '$'; break; } fputc(0xfb, outFile); fprintf(outFile, "mloc%d_%d", loc->ox + 1, loc->oy + 1); fputc(0xfc, outFile); fputc(chm, outFile); fputc(0xfe, outFile); } else if (!optNoLabels && (n = locFindByCoords(lp, xc, yc, false)) >= 0) { LocMarker *loc = lp->locations[n]; if ((loc->flags & LOCF_INVIS) == 0) { int col = col_light_white; fputc(0xff, outFile); fprintf(outFile, "loc%d_%d", loc->ox + 1, loc->oy + 1); fputc(0xfc, outFile); switch (loc->flags & LOCF_M_MASK) { case LOCF_M_PCITY: col = col_light_green; break; case LOCF_M_CITY: col = col_light_red; break; default: switch (loc->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; } if (loc->flags & LOCF_CLOSED) col = col_light_red; fputc(col, outFile); if (optLabelType) { xc += locPrintType(outFile, loc, !optNoAdjust, NULL, false); } fputs(loc->names[0].name, outFile); fputc(0xfe, outFile); if (!optNoAdjust) xc += strlen(loc->names[0].name) - 1; else fputc(dp[xc], outFile); } else fputc(dp[xc], outFile); } else fputc(dp[xc], outFile); } fprintf(outFile, "\n"); } } /* Print out location list as HTML option list. */ void outputMapLocHTML(FILE *outFile, const MapLocations *lp) { fprintf(outFile, "<option value=\"\">-</option>\n"); for (int i = 0; i < lp->nlocations; i++) { const LocMarker *loc = lp->locations[i]; if (loc->flags & LOCF_INVIS) continue; fprintf(outFile, "<option value=\"loc%d_%d\">", loc->ox + 1, loc->oy + 1); locPrintType(outFile, loc, false, fputse, true); fprintf(outFile, "</option>\n"); } } /* Output generated locations into given file stream */ void printLocNameEsc(FILE *outFile, const LocName *name) { fputs(locGetAreaAuthorRole(name->flags, false), outFile); fputsesc2(name->name, outFile); } void printLocWarning(bool *first, FILE *fh, const LocMarker *loc, const char *msg) { FILE *outFH = optWarningsToLoc ? fh : stderr; if (*first) { // Get continent name char *csep, *continent = loc->file != NULL ? th_strdup((loc->file->continent != NULL) ? loc->file->continent : loc->file->filename) : NULL; // Remove filename extension, if found if (continent != NULL && (csep = strstr(continent, ".loc")) != NULL && csep[4] == 0) *csep = 0; fprintf(outFH, "\n# '%s' @ go %d,%d,%s\n", loc->names[0].name, loc->ox + 1, loc->oy + 1, continent); th_free(continent); } fprintf(outFH, "# - %s\n", msg); *first = false; } void outputLocationFile(FILE *outFile, MapLocations *lp) { // 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 (int i = 0; i < lp->nlocations; i++) { LocMarker *loc = lp->locations[i]; // Add comment in few cases if (optWarnings) { bool first = true; if ((optWarnings & WARN_MARKER_INVALID) && (loc->flags & LOCF_INVALID)) printLocWarning(&first, outFile, loc, "Possibly invalid location marker"); if ((optWarnings & WARN_MARKER_MISSING) && (loc->flags & LOCF_NOMARKER)) printLocWarning(&first, outFile, loc, "Location missing marker"); // The next warnings apply only to visible non-pcity / non-shrine if ((loc->flags & LOCF_INVIS) == 0 && (loc->flags & LOCF_M_PCITY) == 0 && (loc->flags & LOCF_T_SHRINE) == 0) { if ((optWarnings & WARN_TIMESTAMPS_MISSING) && !loc->valid) printLocWarning(&first, outFile, loc, "No timestamp"); if ((optWarnings & WARN_AUTHORS_MISSING) && loc->nauthors == 0) printLocWarning(&first, outFile, loc, "No authors listed"); if (optWarnings & WARN_EXTRA) { if (loc->valid) { const char *msg = NULL; switch (loc->added.accuracy) { case TS_ACC_DEFAULT: msg = "Addition timestamp 'default'."; break; /* case TS_ACC_GUESSTIMATE: msg = "Addition timestamp 'approximate'."; break; */ } if (msg != NULL) printLocWarning(&first, outFile, loc, msg); } if (loc->nauthors > 0) { int flags = 0; for (int nauthor = 0; nauthor < loc->nauthors; nauthor++) { flags |= loc->authors[nauthor].flags; } if ((flags & AUTHOR_ORIG) == 0) { printLocWarning(&first, outFile, loc, "Primary author not set"); } } } } } fprintf(outFile, "%d\t; %d\t; %d", loc->ox + 1, loc->oy + 1, loc->align); switch (loc->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 (loc->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 (loc->flags & LOCF_CLOSED) fputc('!', outFile); if (loc->flags & LOCF_INSTANCED) fputc('I', outFile); if (loc->flags & LOCF_INVIS) fputc('-', outFile); fprintf(outFile, "\t;"); printLocNameEsc(outFile, &loc->names[0]); for (int n = 1; n < loc->nnames; n++) { fprintf(outFile, "|"); printLocNameEsc(outFile, &loc->names[n]); } fprintf(outFile, ";"); if (loc->nauthors > 0) { printLocNameEsc(outFile, &loc->authors[0]); for (int n = 1; n < loc->nauthors; n++) { fprintf(outFile, ","); printLocNameEsc(outFile, &loc->authors[n]); } } fprintf(outFile, ";"); if (loc->valid) { const char *acc; switch (loc->added.accuracy) { case TS_ACC_DEFAULT: acc = ""; break; case TS_ACC_KNOWN : acc = "!"; break; case TS_ACC_GUESSTIMATE : acc = "?"; break; case TS_ACC_APPROXIMATE : acc = "#"; break; default: acc = "ERROR! "; break; } fprintf(outFile, "%s" LOC_TIMEFMT, acc, loc->added.day, loc->added.month, loc->added.year); } fprintf(outFile, ";"); if (loc->uri) fputsesc2(loc->uri, outFile); fprintf(outFile, ";"); if (loc->freeform) fputsesc2(loc->freeform, outFile); fprintf(outFile, "\n"); } } /* Generate a shell-script for running ImageMagick to add * location and label information to an map image. */ void outputMagickScript(FILE *outFile, MapLocations *lp) { // Output script start fprintf(outFile, "#!/bin/sh\n" "convert \"$1\" @OPTS_START@ \\\n"); // Output instructions for each visible location for (int i = 0; i < lp->nlocations; i++) { LocMarker *loc = lp->locations[i]; int xc, yc, leftMove; char *cs; // Is location visible? if (loc->flags & LOCF_INVIS) continue; leftMove = ((float) strlen(loc->names[0].name)) * optFontScale; switch (loc->align) { case LOCD_LEFTDOWN: yc = loc->yc + optUnitSize*3.0f; xc = loc->xc - leftMove; break; case LOCD_LEFT: yc = loc->yc - optUnitSize; xc = loc->xc - leftMove; break; case LOCD_DOWN: yc = loc->yc + optUnitSize*3.0f; xc = loc->xc + optUnitSize; break; case LOCD_NONE: default: yc = loc->yc - optUnitSize; xc = loc->xc + optUnitSize; break; } // Determine colour switch (loc->flags & LOCF_M_MASK) { case LOCF_M_CITY: cs = "'#880000'"; break; case LOCF_M_PCITY: cs = "'#00ff00'"; break; default: switch (loc->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; } break; } if (loc->flags & LOCF_CLOSED) cs = "'#ff0000'"; // Location marker fprintf(outFile, "\t-fill black -draw \"circle %d,%d %d,%d\" ", loc->xc, loc->yc, (int) (loc->xc + optUnitSize), (int) (loc->yc + optUnitSize)); fprintf(outFile, "-fill %s -draw \"circle %d,%d %d,%d\" ", cs, loc->xc, loc->yc, (int) (loc->xc + optUnitSize * 0.90f), (int) (loc->yc + optUnitSize * 0.90f)); // Location description text if (!optNoLabels) { fprintf(outFile, "-fill %s -box '#00000080' -draw \"text %d,%d '", cs, xc, yc); fputsesc3(loc->names[0].name, outFile); 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. */ static char *getQuestLink(const char *name, const char *desc) { char *str, *tmp = th_strdup(name); for (size_t i = 0; i < strlen(tmp); i++) tmp[i] = th_isspace(tmp[i]) ? '+' : th_tolower(tmp[i]); str = th_strdup_printf("<a target=\"_blank\" href=\"http://www.bat.org/help/quests?str=%s\">%s</a>", tmp, desc); th_free(tmp); return str; } static const char *addQuestLink(char **buf, size_t *bufSize, size_t *bufLen, const char *ptr, const char *start, const char *end) { if (start != NULL && end != NULL) { char *name = th_strndup(start + 1, end - start - 1); char *desc = th_strndup(ptr, end - ptr + 1); char *tmp = getQuestLink(name, desc); th_strbuf_puts(buf, bufSize, bufLen, tmp); th_free(name); th_free(desc); th_free(tmp); return end + 1; } else return ptr; } void outputGMapsHTML(FILE *outFile, const LocMarker *loc, int (*fpr)(FILE *, const char *fmt, ...), int (*fps)(const char *, FILE *)) { if (loc->uri != NULL) { fpr(outFile, "<b><a target=\"_blank\" href=\"%s\">", loc->uri); locPrintType(outFile, loc, false, fps, true); fpr(outFile, "</a></b><br>"); } else { fpr(outFile, "<b>"); locPrintType(outFile, loc, false, fps, true); fpr(outFile, "</b><br>"); } // Alternative names, if any if (loc->nnames > 1) { fpr(outFile, "Also known as <i>"); for (int n = 1; n < loc->nnames; n++) { fps(loc->names[n].name, outFile); if (loc->names[n].flags & NAME_ORIG) fprintf(outFile, " (*)"); if (n < loc->nnames - 1) fprintf(outFile, " ; "); } fpr(outFile, "</i>.<br>"); } // Added to game timestamp if (loc->valid) { fpr(outFile, "Added " LOC_TIMEFMT ".<br>", loc->added.day, loc->added.month, loc->added.year); } // Author names or societies if (loc->nauthors > 0) { if (loc->flags & LOCF_M_PCITY) { fprintf(outFile, "Societies: "); for (int n = 0; n < loc->nauthors; n++) { fps(loc->authors[n].name, outFile); if (n < loc->nauthors - 1) fprintf(outFile, ", "); } } else { fprintf(outFile, "Authors: "); for (int n = 0; n < loc->nauthors; n++) { char *info = "", *sinfo = ""; switch (loc->authors[n].flags) { case AUTHOR_ORIG: info = " (O)"; sinfo = "Original coder"; break; case AUTHOR_RECODER: info = " (R)"; sinfo = "Re-coder"; break; case AUTHOR_MAINTAINER: info = " (M)"; sinfo = "Maintainer"; break; case AUTHOR_EXPANDER: info = " (E)"; sinfo = "Expander"; break; } //fpr(outFile, "<a target=\"_blank\" href=\"http://www.bat.org/char/%s\">%s%s</a>", fpr(outFile, "<a target=\"_blank\" href=\"https://tnsp.org/maps/loc.php?a=%s\" title=\"%s\">%s%s</a>", loc->authors[n].name, sinfo, loc->authors[n].name, info); if (n < loc->nauthors - 1) fprintf(outFile, ", "); } } fps(".<br>", outFile); } // Print out freeform information field if (loc->freeform) { const char *ptr = loc->freeform; char *buf = NULL; size_t bufLen = 0, bufSize = 0; while (*ptr != 0) { const char *start; // Detect AQ and LQ strings if (ptr[0] == 'A' && ptr[1] == 'Q' && th_isspace(ptr[2]) && (start = strchr(ptr + 3, '"')) != NULL) { ptr = addQuestLink(&buf, &bufSize, &bufLen, ptr, start, strchr(start + 1, '"')); } else if (ptr[0] == 'L' && ptr[1] == 'Q' && th_isdigit(ptr[2]) && (start = strchr(ptr + 3, '"')) != NULL) { ptr = addQuestLink(&buf, &bufSize, &bufLen, ptr, start, start ? strchr(start + 1, '"') : NULL); } if (*ptr != 0) th_strbuf_putch(&buf, &bufSize, &bufLen, *ptr++); } th_strbuf_putch(&buf, &bufSize, &bufLen, 0); fpr(outFile, "<br>%s<br>", buf); th_free(buf); } } void outputGMapsXML(FILE *outFile, MapLocations *lp) { fprintf(outFile, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<markers>\n"); // Output each location entry for (int i = 0; i < lp->nlocations; i++) { LocMarker *loc = lp->locations[i]; // Skip disabled / invisible locations if (loc->flags & (LOCF_INVIS | LOCF_INVALID)) continue; // Coordinates and label direction/alignment fprintf(outFile, "<marker x=\"%d\" y=\"%d\" labeldir=\"%d\"", loc->ox, loc->oy, loc->align); // Primary location name fprintf(outFile, " name=\""); locPrintType(outFile, loc, false, fputse, false); fprintf(outFile, "\""); // HTML fprintf(outFile, " html=\""); outputGMapsHTML(outFile, loc, fprintfe, fputse); fprintf(outFile, "\""); // Flags fprintf(outFile, " flags=\"%d\"", loc->flags); // Continent name if (loc->file != NULL && loc->file->continent != NULL) { fprintf(outFile, " continent=\""); fputse(loc->file->continent, outFile); fprintf(outFile, "\""); } // Type of the marker fprintf(outFile, " type=\"%s\"", locGetTypeName(loc->flags)); // Added to game timestamp if (loc->valid) { const char *acc; switch (loc->added.accuracy) { case TS_ACC_DEFAULT: acc = "default"; break; case TS_ACC_KNOWN : acc = "known"; break; case TS_ACC_GUESSTIMATE : acc = "guesstimate"; break; case TS_ACC_APPROXIMATE : acc = "approximate"; break; default: acc = "ERROR"; break; } fprintf(outFile, " added=\"" LOC_TIMEFMT "\" added-accuracy=\"%s\"", loc->added.day, loc->added.month, loc->added.year, acc); } fprintf(outFile, ">"); // Location alternative names if (loc->nnames > 1) { fprintf(outFile, "<altnames>"); for (int n = 1; n < loc->nnames; n++) { const char *tmps = locGetAreaNameType(loc->names[n].flags, true); fprintf(outFile, "<name"); if (tmps != NULL) fprintf(outFile, " type=\"original\""); fprintf(outFile, ">"); fputse(loc->names[n].name, outFile); fprintf(outFile, "</name>"); } fprintf(outFile, "</altnames>"); } // Authors or secret societies if (loc->nauthors > 0) { if (loc->flags & LOCF_M_PCITY) { fprintf(outFile, "<societies>"); for (int n = 0; n < loc->nauthors; n++) { fprintf(outFile, "<name>"); fputse(loc->authors[n].name, outFile); fprintf(outFile, "</name>"); } fprintf(outFile, "</societies>"); } else { fprintf(outFile, "<authors>"); for (int n = 0; n < loc->nauthors; n++) { fprintf(outFile, "<author role=\"%s\">", locGetAreaAuthorRole(loc->authors[n].flags, true)); fputse(loc->authors[n].name, outFile); fprintf(outFile, "</author>"); } fprintf(outFile, "</authors>"); } } fprintf(outFile, "</marker>\n"); } fprintf(outFile, "</markers>\n"); } void outputGMapsJSON(FILE *outFile, MapLocations *lp) { fprintf(outFile, "[\n"); // Output each location entry for (int i = 0; i < lp->nlocations; i++) { LocMarker *loc = lp->locations[i]; // Skip disabled / invisible locations if (loc->flags & (LOCF_INVIS | LOCF_INVALID)) continue; // Print out coordinates etc. fprintf(outFile, "{\"x\":%d,\"y\":%d,\"labeldir\":%d,\"name\":\"", loc->ox, loc->oy, loc->align); // Location name locPrintType(outFile, loc, false, fputsesc1, false); fprintf(outFile, "\",\"html\":\""); outputGMapsHTML(outFile, loc, fprintfesc1, fputsesc1); // Flags fprintf(outFile, "\",\"flags\":%d", loc->flags); // Continent name if (loc->file != NULL && loc->file->continent != NULL) { fprintf(outFile, ",\"continent\":\""); fputsesc1(loc->file->continent, outFile); fprintf(outFile, "\""); } fprintf(outFile, "}%s\n", (i < lp->nlocations - 1) ? "," : ""); } fprintf(outFile, "]\n"); } int main(int argc, char *argv[]) { int res = 0; FILE *outFile = NULL, *inFile = NULL; MapBlock *worldMap = NULL; MapLocations worldLoc; memset(&worldLoc, 0, sizeof(worldLoc)); memset(&optLocFiles, 0, sizeof(optLocFiles)); // Initialize th_init("mkloc", "Manipulate and convert location files and ASCII map data", "1.6", NULL, NULL); th_verbosity = 0; // Parse arguments if (!th_args_process(argc, argv, optList, optListN, argHandleOpt, NULL, OPTH_BAILOUT)) { res = 1; goto out; } // Check the mode and arguments if (optInFilename == NULL && optGetUpdateLoc && optOutput == OUTFMT_LOCFILE) { argShowHelp(); THERR("Map file required for location update mode!\n"); res = 1; goto out; } if (optOutput == OUTFMT_LOCFILE && noptLocFiles < 0 && !optGetUpdateLoc) { argShowHelp(); THERR("Location file or location update mode required for location file output!\n"); res = 1; goto out; } if ((optOutput == OUTFMT_SCRIPT || optOutput == OUTFMT_MAPLOC) && noptLocFiles < 0) { argShowHelp(); THERR("Location file required for script or MapLoc HTML output!\n"); res = 1; goto out; } if (optInFilename == NULL && optOutput == OUTFMT_MAP) { argShowHelp(); THERR("Map file required for map generation.\n"); res = 1; goto out; } // Read initial map if (optInFilename != NULL) { THMSG(2, "Reading map '%s'\n", optInFilename); worldMap = mapBlockParseFile(optInFilename, false); if (worldMap == NULL) { THERR("World map could not be loaded!\n"); res = -2; goto out; } THMSG(2, "Initial dimensions (%d x %d)\n", worldMap->width, worldMap->height); } // Read location info for (int i = 0; i <= noptLocFiles; i++) { LocFileInfo *fp = &optLocFiles[i]; if (optOutput == OUTFMT_GMAPS && fp->continent == NULL) { THERR("Required continent name not set for #%d '%s'.\n", i, fp->filename); res = -3; goto out; } THMSG(2, "Reading location info '%s'\n", fp->filename); if ((inFile = fopen(fp->filename, "rb")) == NULL) { THERR("Could not open location file '%s' for reading.\n", fp->filename); res = -3; goto out; } if (!locParseLocStream(inFile, fp, &worldLoc, fp->xoffs, fp->yoffs)) { res = -4; goto out; } fclose(inFile); inFile = NULL; } // Update locations if (optGetUpdateLoc) updateLocations(worldMap, &worldLoc); // Scale locations if (optScale > 0) { THMSG(1, "Scaling locations ..\n"); for (int i = 0; i < worldLoc.nlocations; i++) { LocMarker *loc = worldLoc.locations[i]; loc->xc = ((float) loc->xc) * optScale; loc->yc = ((float) loc->yc) * optScale; } } // Open output file if (optOutFilename == NULL) outFile = stdout; else if ((outFile = fopen(optOutFilename, "wb")) == NULL) { THERR("Error opening output file '%s'!\n", optOutFilename); res = -5; goto out; } // 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.nlocations); break; case OUTFMT_MAPLOC: maplocSort(&worldLoc); THMSG(1, "Outputting MapLoc HTML ...\n"); outputMapLocHTML(outFile, &worldLoc); THMSG(2, "%d locations\n", worldLoc.nlocations); 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_JSON: outputGMapsJSON(outFile, &worldLoc); break; } THMSG(2, "%d locations\n", worldLoc.nlocations); break; case OUTFMT_MAP: THMSG(1, "Outputting generated map of (%d x %d) ...\n", worldMap->width, worldMap->height); THMSG(2, "Adjusting location labels ..\n"); adjustLocInfoCoords(worldMap, &worldLoc); outputMapCTRL(outFile, worldMap, &worldLoc); break; } out: if (outFile != NULL) fclose(outFile); if (inFile != NULL) fclose(inFile); mapBlockFree(worldMap); locFreeMapLocations(&worldLoc); return res; }